Skip to content

Commit 8410b68

Browse files
committed
build tools
1 parent b546dab commit 8410b68

File tree

7 files changed

+409
-32
lines changed

7 files changed

+409
-32
lines changed

.cargo/config.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
[net]
22
offline = true
33

4+
[alias]
5+
dist = "run -q -p xtask --frozen -- dist"
6+
47
[source.crates-io]
58
replace-with = "vendored-sources"
69

AGENTS.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,13 @@ The system’s posture assumes **authors are cooperating** (instrumentation, not
3333

3434
This repo vendors its Rust dependencies under `vendor/` and forces offline builds via `.cargo/config.toml`. If you update dependencies, keep `Cargo.lock` and `vendor/` in sync by running `cargo vendor vendor --locked`.
3535

36+
## Build + release artifacts
37+
38+
- **Local build:** `cargo build --bin fencerunner` (debug) or `cargo build --release --bin fencerunner`.
39+
- **Release artifacts:** `cargo dist` builds `--release` and writes `dist/fencerunner-v<VERSION>-<TARGET>` plus `.tar.gz` + `.sha256` files.
40+
- `<VERSION>` comes from `[package].version` in `Cargo.toml`.
41+
- `<TARGET>` is the host triple by default (`rustc -vV`), or `cargo dist --target <triple>`.
42+
3643
### Run-dir shape
3744
- Run dirs are **flat**: every top-level `*.sh` is a script; subdirectories are ignored.
3845
- Script ids come from filenames (`<script_id>.sh`) and must be **globally unique across all run dirs** in one run.

Cargo.lock

Lines changed: 5 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "fencerunner"
3-
version = "1.0.1"
3+
version = "1.0.2"
44
edition = "2024"
55
rust-version = "1.85"
66
description = "Portable script harness to determine ambient permissions by testing them directly."
@@ -11,6 +11,10 @@ default-run = "fencerunner"
1111
autobins = false
1212
# No build script; runner-owned assets are embedded and materialized at runtime.
1313

14+
[workspace]
15+
members = ["xtask"]
16+
default-members = ["."]
17+
1418
[lib]
1519
name = "fencerunner"
1620
doctest = false

docs/fencerunner-user.md

Lines changed: 21 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,14 @@ Most of the guide is about making that stream pleasant to consume and easy to ev
2020
- [Generating boundaries](#generating-boundaries)
2121
- [Troubleshooting](#troubleshooting)
2222
- [Advanced](#advanced)
23-
- [Signal audit grab bag](#signal-audit-grab-bag)
2423

2524
## The contract
2625

2726
>stdout is reserved.
2827
2928
`fencerunner` treats a script’s stdout as its interface: the boundary record. Anything else on stdout (logs, progress, stray `echo`) is a contract break. Send diagnostics to stderr, capture any command output you care about, and emit exactly one boundary record when the script is done.
3029

31-
Each top-level `*.sh` is one script, and each script emits one record. The record must declare `script.id`, and that id must match the filename stem (`my_probe.sh``my_probe`), so identity is stable and deterministic. In a single run, script ids must be unique so the stream can be consumed without ambiguity. `fencerunner` also enforces a fixed result.outcome vocabulary (success|denied|partial|error) for all scripts so downstream tools can treat `result.outcome` as a stable enum.
30+
Each top-level `*.sh` is one script, and each script emits one record. The record must declare `script.id`, and that id must match the filename stem (`my_probe.sh``my_probe`), so identity is stable and deterministic. In a single run, script ids must be unique so the stream can be consumed without ambiguity. `fencerunner` (and `emit-record`) enforce a fixed `result.outcome` vocabulary (`success|denied|partial|error`) for all scripts so downstream tools can treat it as a stable enum. Unknown outcomes are a contract break: strict mode fails and supervised mode emits a synthetic error record.
3231

3332
That’s most of what `fencerunner` insists on. Everything else—what you encode in the record, how strict the schema is, which operation kinds exist—is defined by the contracts you choose to write in the run dir.
3433

@@ -307,6 +306,8 @@ The recommended baseline treats every boundary record as the same five-part enve
307306

308307
In that envelope, downstream tools almost always care about the same few fields: `script.id` (filename stem), `operation.kind` and `operation.target` (what this record *is about*), `result.outcome` (fixed enum: `success|denied|partial|error`), and whatever you choose to put in `payload.raw` as your suite-specific structured payload.
309308

309+
If you need richer result semantics, keep `result.outcome` in this vocabulary and encode your extra meaning under `operation.*` or `payload.raw`.
310+
310311
`context.commitments` is where scripts record lightweight “I relied on / observed / emitted X” signals via `commit_help_me` (empty allowed). `payload.stdout_snippet` and `payload.stderr_snippet` are string evidence channels (empty allowed). If you want multiple run dirs to produce a unified stream, make `operation.kind` and `operation.target` mean the same thing everywhere.
311312

312313
### Using `emit-record`
@@ -365,6 +366,12 @@ head -c 2000 "${stderr_file}" > "${stderr_file}.trimmed"
365366

366367
Then pass the trimmed files to `emit-record` so stdout stays clean and payloads stay bounded.
367368

369+
`emit-record` (and supervised synthetic records) keep records compact and predictable by enforcing payload bounds:
370+
371+
- `payload` (as serialized JSON) is capped at 16 KiB (16384 bytes).
372+
- `payload.stdout_snippet` and `payload.stderr_snippet` are NUL-stripped and truncated to 2000 characters (with an ellipsis).
373+
- Common failure: `Payload exceeds 16384 bytes (got N)`; keep `payload.raw` summary-sized and write large artifacts to files (then reference paths/hashes in `payload.raw`).
374+
368375
---
369376

370377
## Run modes
@@ -375,13 +382,13 @@ Most users end up using both modes: strict when authoring and evolving a suite,
375382

376383
### Strict mode
377384

378-
Strict mode is the default. Use it when you want contract breaks to fail the run with a non-zero exit code. In strict mode a script must emit exactly one schema-valid boundary record on stdout; if it emits invalid JSON, violates `boundaries.json`, mismatches `script.id`, exits non-zero, or violates an enforced gate, the run fails and no record is emitted for that script.
385+
Strict mode is the default. Use it when you want contract breaks to fail the run with a non-zero exit code. In strict mode a script must emit exactly one schema-valid boundary record on stdout; if it emits invalid JSON, violates `boundaries.json`, mismatches `script.id`, emits an unknown `result.outcome`, exits non-zero, or violates an enforced gate, the run fails and no record is emitted for that script.
379386

380387
Strict mode fails fast: the runner stops at the first script-level contract break, and any remaining scripts are not executed.
381388

382389
### Supervised mode
383390

384-
Supervised mode (`--supervised`) is for pipelines where a well-formed NDJSON stream matters more than perfect script behavior. `fencerunner` will output one record per script; when a script breaks the contract it emits a synthetic error record that captures stdout/stderr snippets and explains what happened. Supervised exits `0` unless preflight or the runner itself fails (missing contracts, invalid contracts, script not executable, duplicate script ids, and similar harness-level failures).
391+
Supervised mode (`--supervised`) is for pipelines where a well-formed NDJSON stream matters more than perfect script behavior. `fencerunner` will output one record per script; when a script breaks the contract (including unknown outcomes) it emits a synthetic error record that captures stdout/stderr snippets and explains what happened. Supervised exits `0` unless preflight or the runner itself fails (missing contracts, invalid contracts, script not executable, duplicate script ids, and similar harness-level failures).
385392

386393
---
387394

@@ -397,6 +404,8 @@ Some examples below use `jq` for reporting; `fencerunner` does not ship it.
397404

398405
Commitment ids are **simple tokens**: `^[A-Za-z0-9_.-]+$` (letters/digits plus `_`, `.`, `-`). If you need spaces, slashes, or other punctuation, put that detail into `payload.raw` or `operation.args` and keep the commitment id as the stable label. If you call `commit_help_me` with an invalid id, it fails and your script should treat that as a hard error.
399406

407+
`commit_help_me` treats duplicate enrollments as a contract break. If a script calls the same `<verb> <commitment.id>` pair twice, `commit-help-me` exits non-zero and the script should fail fast.
408+
400409
### Branching canaries
401410

402411
Sometimes you want the thinnest possible instrumentation: “did this code path run?”
@@ -573,6 +582,8 @@ In both run dirs, create the triad by copying the same `gates.json`, `commitment
573582

574583
Now add one script per run dir (note: ids must be globally unique across the whole run):
575584

585+
Script ids must be globally unique in a single run. If you run `fencerunner ./dirA ./dirB` and both contain `probe.sh` (id `probe`), preflight fails with a “Duplicate script id …” error. Fix: rename one of the scripts (or split runs).
586+
576587
`./suite/run_dirs/env_probes/env_python3_version.sh`
577588

578589
```bash
@@ -1058,6 +1069,12 @@ Fixes:
10581069

10591070
Tip: run the same invocation with `--supervised` to get a synthetic record that captures stdout/stderr snippets.
10601071

1072+
### “Payload exceeds 16384 bytes …”
1073+
1074+
- `emit-record` caps `payload` (as serialized JSON) at 16 KiB (16384 bytes) to keep streams compact and predictable.
1075+
- Keep `payload.raw` summary-sized and write large artifacts to files (then reference paths/hashes in `payload.raw`).
1076+
- Keep `payload.stdout_snippet` / `payload.stderr_snippet` small by trimming captured output before passing it to `emit-record`.
1077+
10611078
### “record violates boundaries.json”
10621079

10631080
- Your script emitted JSON, but it didn’t satisfy the schema in `boundaries.json`.
@@ -1094,29 +1111,3 @@ If you want supervised mode *and* strict schema validation of synthetic records,
10941111
- `operation.kind = "harness.supervised"`
10951112

10961113
Keep this in the “advanced” bucket unless you have consumers that require “every line validates against `boundaries.json` even for synthetic errors”.
1097-
1098-
---
1099-
1100-
## Signal audit grab bag
1101-
1102-
>Loose ends worth validating.
1103-
1104-
### Payload size limits
1105-
1106-
`emit-record` (and supervised synthetic records) keep records compact and predictable by enforcing a size limit and truncating snippets:
1107-
1108-
- `payload` (as serialized JSON) is capped at 16 KiB (16384 bytes).
1109-
- `payload.stdout_snippet` and `payload.stderr_snippet` are NUL-stripped and truncated to 2000 characters (with an ellipsis).
1110-
- Common failure: `Payload exceeds 16384 bytes (got N)`; keep `payload.raw` summary-sized and write large artifacts to files (then reference paths/hashes in `payload.raw`).
1111-
1112-
### Duplicate commitment enrollments
1113-
1114-
`commit_help_me` treats duplicates as a contract break. If a script calls the same `<verb> <commitment.id>` pair twice, `commit-help-me` exits non-zero and the script should fail fast.
1115-
1116-
### Duplicate script ids across run dirs
1117-
1118-
Script ids must be globally unique in a single run. If you run `fencerunner ./dirA ./dirB` and both contain `probe.sh` (id `probe`), preflight fails with a “Duplicate script id …” error. Fix: rename one of the scripts (or split runs).
1119-
1120-
### Outcome vocabulary
1121-
1122-
`fencerunner` enforces a fixed outcome vocabulary: `success|denied|partial|error` (and `emit-record` enforces it too). If a script emits any other `result.outcome`, strict mode fails and supervised mode emits a synthetic error record. If you need richer result semantics, keep `result.outcome` in this vocabulary and encode your extra meaning under `operation.*` or `payload.raw`.

xtask/Cargo.toml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
[package]
2+
name = "xtask"
3+
version = "0.0.0"
4+
edition = "2024"
5+
rust-version = "1.85"
6+
publish = false
7+
8+
[dependencies]
9+

0 commit comments

Comments
 (0)