Immutable. This exact content is served forever at /api/v1/blob/089f0584880ec014.
# workflows/
The v3 review pipeline. `pipeline.js` is invoked by the skill through the `Workflow` tool
(`scriptPath` + args); it has no disk, shell, or `process.env`.
## Runtime
- **Node 24 is the runtime.** JS tests run under `node --test`. No npm, no `package.json`, no
`node_modules` — Node builtins and language globals only. Stable `Array.prototype.sort` is relied on.
- **Only JSON-safe language globals exist in the sandbox.** `structuredClone`, `Buffer`,
`TextEncoder`/`TextDecoder`, `URL`, `setTimeout`/`queueMicrotask`, `process` and `console` are
provided by `node --test` but **not** by the sandbox: a reference throws on the first live
dispatch while every test stays green. Deep-clone with `deepClone`, never `structuredClone`.
`pipeline_run.test.js` and `stages_persist.test.js` run with all of these except `URL`,
`process` and `console` deleted from `globalThis`; a reference to one of those three is caught
only on a live dispatch.
- **No wall-clock.** Timestamps arrive through the args waist (`generatedAt`); environment values
are read by the skill and passed in `policy`.
## JS lint
CI and contributors run the same `python3 workflows/test/tools/biome_check.py`; the operational
version and per-asset SHA-256 pins live only in `biome-pin.json`.
To bump, regenerate every row with one command: `gh api repos/biomejs/biome/releases/tags/@biomejs/biome@<new> --jq '.assets[] | {name, digest}'`; update every entry in `biome-pin.json` together and never add a package manifest for auto-bumps.
Formatter is **off**: default `lineWidth: 80` would wrap long `import … from` lines and break
`build.js`'s single-line import `strip()` regex, failing `tests/test_bundle_fresh.py`.
`noRestrictedGlobals` applies to `src/` only; `test/**` and `build.js` override it (they
legitimately use `process` / `console`).
Deferred rules (measured 2026-07-30, HEAD `ebf399d`) — hit counts are why they
stay off so a later re-evaluation need not re-derive them:
| Rule | Hits | Why deferred |
| --- | --- | --- |
| `useOptionalChain` | 51 | Style churn; no reliability delta |
| `noUnusedFunctionParameters` | 55 | Dominated by positional mock callback params in tests |
| `noAssignInExpressions` | 7 | Six are intentional `args.js` pattern |
| `noControlCharactersInRegex` | 2 | Intentional U+0000/U+001F sanitization in `args.js` |
| `organizeImports` | 24 | Assist/format-adjacent; out of scope with formatter off |
## The bundle
- `pipeline.js` is **generated** — never hand-edit it. Source is `src/*.js`, which may use ESM
`import`/`export` for tests only; `build.js` strips those, drops full-line `//` comments and
blank lines that V8 proves inert (a `//` line inside a template literal survives), and
concatenates. It fails the build when the bundle exceeds `WORKFLOW_SCRIPT_CAP -
BUNDLE_HEADROOM`; the constants and rationale live in `build.js`.
- Only a single-line `import … from './sibling.js'` is strippable; `build.js` (`unsafeImports`)
fails on anything else. A `node:`/bare specifier inlines nothing, so stripping it ships an
undefined reference; a side-effect or multi-line import `strip()` never matches ships verbatim.
Neither lint nor the bundle-fresh check sees either — inline the value into `src/`.
- Rebuild after any source change: `node workflows/build.js`. `tests/test_bundle_fresh.py` requires
the committed bundle to be byte-identical to a fresh build, and import-free.
- **`meta.name` must never equal the skill's name.** Both are registered by this plugin; an
identical name makes `/code-gauntlet:code-gauntlet` resolve to the workflow, which then receives a
raw user string instead of the args waist.
## Persistence
**Artifacts reach disk through the workflow's own return value, not through an agent.** A language
model asked to reproduce large escape-dense JSON verbatim does not: measured across every recorded
run, 26 of 73 writes failed their own content proof, ranging from escape mangling to silently
rewriting long prose shorter, and nothing about a document predicts which fails. So the Persist
stage returns the three primaries at `persistReturn` and the *harness* serializes them —
byte-exact at every size probed to 4 MB, against ~66 KB of unique content in the largest recorded
run. **The content proof survives the move and now grades the harness-written copy; do not remove
it. Respelling JSON backslash escapes on the persist wire was measured and did not fix
transcription.**
- The two budgets are unrelated and must not re-merge. `VERIFY_INLINE_CHAR_BUDGET` (50k) sizes the
percent-encoded verify token handed to an executor; `RETURN_CHAR_BUDGET` (1M) sizes what the
harness serializes. Grading resume state or the returned primaries against the verify budget
throws away recoverable runs.
- `fnv1a32` is defined over UTF-16 code units and **must agree between runtimes** — JS uses
`charCodeAt` + `Math.imul`; Python reads `utf-16-le` pairs. `tests/test_assemble_artifacts.py`
pins the parity over surrogates and control characters.
- The checkpoint-skeleton guards must stay in lockstep: `persistPlan` empties
`phases.challenge.findings` only when it holds an array, and `assemble_artifacts.py` refills it
under the identical predicate. A looser Python predicate fabricates an array.
- Structural failures (missing file, unparseable JSON, duplicate id, bad plan checksum) hard-fail.
A *primary* content mismatch does not — it still derives from on-disk truth and raises a gap.
- Both writer paths stay live and are not dead code: the derived one is the automatic fallback when
the primaries exceed `RETURN_CHAR_BUDGET`, the legacy by-value one is the safety net for
pathological input. Do not delete either.
Verify is the exception to the persist-wire encoding rule: its inline token uses a restricted
percent-encoded alphabet with no escape semantics left for the model to normalize, measured exact
in both of the two recorded executor copies at 43,636 characters and again in both copies at
51,744 characters; larger payloads are unmeasured. The input content proof remains the belt if
the inline channel is ever transcribed incorrectly.
## The verify boundary
The executor echoes a per-id delta, never findings.
Verify hands each projected slice to the executor as one quoted `--input-inline` token. The token
is planned under `VERIFY_INLINE_CHAR_BUDGET`; the executor reproduces it exactly, and the script
decodes it and writes the destination path before verification.
- `_DELTA_FIELDS` (Python) and `DELTA_VALUE_KEYS` (JS — `DELTA_KEYS` minus the structural
`id`/`verified`) are one list in two runtimes, walked in the same order.
- `result.deltas` must stay the **first** key of `result` — the reading executor's `Read` is
length-capped with no truncation notice, so what it echoes must be a prefix.
- The slice input is a projection, not a full finding copy: `VERIFY_SLICE_FIELDS` (JS) and
`_SLICE_INPUT_FIELDS` (Python) are one list in two runtimes, walked in the same order. Every
field the script consults on dispatched slices must be listed there — a lockstep test pins the
JS/Python pair, and a read-site scan over `verify_findings.py`'s own source (both in
`tests/test_verify_findings.py`) enforces the list against the script, exempting its own writes
(`_SCRIPT_WRITTEN_FIELDS`), legacy-CLI-only reads (`_LEGACY_CLI_FIELDS`), and the `line`/`end_line`
numeric-coercion loop (`_NUMERIC_FIELDS`).
## Reading the shared context file
A `Read` returns only part of a large file and gives **no truncation notice**. `contextReadPlan`
computes the exact offsets from `contextLines`/`contextChars` stamped by the skill — this is
arithmetic, not an instruction to paginate. Stages receive a prebuilt sentence, never the context
path, so they cannot construct a read instruction of their own. Do not re-thread `contextPath` into
a stage input, and never replace that structural property with a phrase check.
`contextLines` counts as `cat -n` does, not `wc -l` (which reports one fewer without a trailing
newline). It is `1` for an empty file.