bareloop 0.6.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -96,7 +96,7 @@ the step plan at run time (gated by `validatePlan`); the human signs only:
96
96
  | field | shape | notes |
97
97
  |---|---|---|
98
98
  | `goal` | non-empty text | what the agent plans against |
99
- | `verdictType` | `green` \| `soft-green` \| `hitl` | declared radio, never inferred (`VERDICT_TYPES`, frozen). v1 ADMITS only `green`; declaring `soft-green`/`hitl` reds `request-red` with the type as a structured `verb` field (declared-but-locked — the tool-menu pattern) |
99
+ | `verdictType` | `green` \| `soft-green` \| `hitl` | declared radio, never inferred (`VERDICT_TYPES`, frozen). v1 ADMITS only `green`; declaring `soft-green`/`hitl` reds `request-red` with the type as a structured `verb` field (declared-but-locked — the tool-menu pattern). Every `request-red` also carries `lib` — the territory the demand lands against, stamped at the emit site (`verdictType` → `bareloop`, a locked tool verb → `bare-agent`): the ledger keys and its `suggestedAsk` seed on it, so a bareloop-catalogue refusal never files as an upstream ask |
100
100
  | `close` | **an ORDERED LIST of named stages** `[{ name, cmd, expect, judged?, gapKeep?, offer?, needs? }, ...]` (PRD v1.28), or a close object (table below) **only** for the declared-but-locked verdict classes | the destination, the only thing hand-authored; the check menu DERIVES from it (below). The plan flow executes a staged close directly and adapts a bare `predicate` object into a one-stage list; a `gold`/`rubric`/`hitl` object close validates (the declared-but-locked verdict classes still parse) but the plan flow refuses it at runtime as `close-unsupported` — it names no command to run |
101
101
  | `checks` | **RETIRED** (PRD v1.28/v1.32) | hand-authored checks are gone, not merely discouraged: declaring `checks` reds `checks-derived` by name. The check menu is DERIVED from the close's own stages instead — see **Staged close** below. The hazard this removes is measured, not theoretical: job #5's three hand-written checks were re-implementations of three stages the close already ran, and a hand-carved copy can drift LENIENT (the worker passes the operator's ruler and fails the real inspection) |
102
102
  | `tools` | optional unique subset of `TOOL_MENU` (14 verbs, below) | the CEILING every plan step's grant must fit inside (omitting it means the full menu — and the hash is taken over that RESOLVED form, so a `TOOL_MENU` widening flips an omitted-`tools` spec's hash and forces a re-sign; see `jobSpecHash`, MED-1); `run` is `LOCKED_TOOLS` and reds `request-red` — locked-but-listed, and the red IS the admission evidence the ledger tallies (a typo stays `invalid-value`). A ceiling of write-class and store-class verbs ONLY reds `invalid-value`: the scout surveys read-only, so it would be handed an empty menu and survey blind |
@@ -152,7 +152,7 @@ contract the single close object has always used) plus:
152
152
  | stage field | shape | notes |
153
153
  |---|---|---|
154
154
  | `name` | unique kebab-case slug | the plan references it via `check-passes(name)`; duplicates red `duplicate-id` |
155
- | `offer` | optional boolean, default offered | `offer: false` hides the stage from the derived menu — it never reaches the agent. For a stage that cannot stand alone as a ruler: a PRECONDITION (e.g. "the seed commit exists" — passes instantly, teaches nothing) or the **final grading stage** — of the six specs in `jobs/`, four hide a final `verdict` stage and two hide a `changed-from-seed` precondition instead (a per-spec convention, not a schema rule — nothing stops a stage named last from being offered except the spec author remembering to set the flag; preflight's `check-menu` event always names the full menu either way, so a forgotten flag is visible in the run's own record) |
155
+ | `offer` | optional boolean, default offered | `offer: false` hides the stage from the derived menu — it never reaches the agent. For a stage that cannot stand alone as a ruler: a PRECONDITION (e.g. "the seed commit exists" — passes instantly, teaches nothing) or the **final grading stage** — of the ten specs in `jobs/`, four hide a final `verdict` stage and six hide a `changed-from-seed` precondition instead (a per-spec convention, not a schema rule — nothing stops a stage named last from being offered except the spec author remembering to set the flag; preflight's `check-menu` event always names the full menu either way, so a forgotten flag is visible in the run's own record) |
156
156
  | `needs` | optional non-empty array of EARLIER stage names | a stage that reads what an earlier stage built (e.g. "the public API matches what an earlier stage emitted") names its prerequisite chain; picking it via `check-passes` runs the chain first, then the stage itself. Every name must be declared before this stage (`invalid-value` otherwise); `needs` + `offer: false` together is incoherent (`invalid-value`) — a stage with a chain to run must be reachable |
157
157
 
158
158
  The stages run in declared order as the close itself; the first red renders the verdict and
@@ -161,6 +161,33 @@ run chain, prerequisites first); a hidden or partial menu is an acceptable case,
161
161
  failure — `check-menu` on the spine reports `hidden` + `meaning` whenever the derived menu is
162
162
  narrower than the stage list.
163
163
 
164
+ **The law for close authors: ONE POPULATION PER STAGE.** The trend reader buckets one series
165
+ per stage NAME, and a stage name is not an axis — nothing in the library can stop a stage from
166
+ redding on two structurally different measurements under one name, and a run that crosses that
167
+ seam donates both genres to one series (a 29 → 4 across such a seam reads *converging* and
168
+ would recommend a top-up on work that had only swapped which wall it was behind). If a stage
169
+ can report two unlike numbers, **split it into two stages** in the close, each where its branch
170
+ already runs — the gate sequence is unchanged because the close is first-red-wins. The shipped
171
+ `u-*` closes were split exactly that way (`typecheck` / `typecheck-outside` /
172
+ `tests-kept` / `suite-green`), and the fix is never a sharpened reader: `readGrade` is one
173
+ reader for every close, deliberately (the F49 precedent). **Splitting a stage moves the spec
174
+ hash** — stage names are in the signed spec — so it is a spec edit the operator re-signs, and
175
+ it changes what stored bridges match (below).
176
+
177
+ **The law for spec authors: your `goal` must state everything your close will judge.**
178
+ Nothing derives the goal from the close or the close from the goal, and nothing checks the
179
+ two against each other — the ONE derivation in the library is close stages → the agent's
180
+ `check-passes` menu, one hop, one direction. That separation is the arbiter rule (the agent
181
+ references the operator's ruler and never authors it) and it is not negotiable, so the drift
182
+ is yours to close: the planner and the worker read the `goal` text, not your stage list, and
183
+ they will spend their single check slot (above) on the constraint the goal sentence names.
184
+ A stage your goal never mentions is a **cost trap, not a safety hole** — the close still
185
+ refuses the tree and the run still cannot mint a green — but the run discovers that stage at
186
+ the tail, after paying for work aimed elsewhere, and may not have the money or wall left to
187
+ redo it. Write the goal from the close's own stage list, in the close's own words; keep it to
188
+ a few explicit requirements rather than prose. Preflight already names every stage on the
189
+ `check-menu` spine event — read it back and check your goal against it before you sign.
190
+
164
191
  **Close types and the hierarchy — the OBJECT form, which survives only for the
165
192
  declared-but-locked verdict classes** (a close is data, never code; verdict-class laundering
166
193
  is a named red `close-hierarchy`). The go-forward shape is the staged list above; a
@@ -220,6 +247,19 @@ first) so a pathological close cannot rebuild the very bloat the bound exists to
220
247
  when the cap trims matches it **says so** with an explicit marker (silent truncation is the
221
248
  disease this cures, never a cure that truncates silently). Validated like `judged.pattern`:
222
249
  a non-empty string that must **compile as a RegExp**, else a spec red before any tokens.
250
+
251
+ **Both operator patterns must also survive the ReDoS shape check** (`invalid-value`, F49 —
252
+ the same reject the agent's `artifact-written.pattern` gets): an unbounded quantifier over a
253
+ group that itself repeats unboundedly (`(a+)+`, `(\d*)*`, even wrapped: `((a+))+`) is
254
+ rejected at the gate. **This is stricter than an "it compiles" contract, and deliberately
255
+ so.** `judged.pattern` is exec'd by `runClose` over the close's whole stdout+stderr and
256
+ `gapKeep` is run by `boundGap` **once per line** — both untimed, both in bareloop's own
257
+ process. Measured: `boundGap(stream, "(a+)+$")` on a 40-character line does not finish in
258
+ 20 seconds. Because the hang blocks the main event loop, the in-process stall fuse cannot
259
+ fire, the run dies to the outside watchdog, and **a bad regex in a signed spec presents as a
260
+ provider problem** — so this reds at signing time rather than being diagnosed later. All 92
261
+ operator regexes across the 10 in-repo specs pass unchanged. Known limit, parked: the scan
262
+ does not catch the alternation-overlap class (`(a|aa)+$`) — see PRD v1.55.
223
263
  Omitting it is exactly today's bound. Like `judged`, it is **arbiter territory** — the
224
264
  drafted workflow config cannot express it (unknown-field red). The gap path also combines
225
265
  **both** streams (stdout + stderr), so a failure printed to stdout survives stderr noise
@@ -236,11 +276,23 @@ middle writes; `validatePlan` gates it against the SIGNED job spec before tokens
236
276
  | `steps[].tools` | non-empty unique subset of the SPEC ceiling | a verb beyond the ceiling reds `verb-escape` with the verb as structured data (overreach, distinct from the operator-side `request-red`); `run` is `verb-escape` at every layer |
237
277
  | `steps[].rounds` | int 1..shell cap (default 40) | the step's per-attempt tool-round bound (the Gate's `maxTurns` natively) |
238
278
  | `steps[].target` | path inside the fence | v1.18 deliverable; REQUIRED on write-granted steps |
239
- | `steps[].model` | optional; `sonnet` \| `haiku` (`STEP_MODELS`) | the step's model TIER — a menu, never a model string the agent spells (the tier→model mapping and any per-model effort params are the runner's). `opus` is deliberately absent: reserved for work the human assigns, never plan-selectable. Off-menu reds `invalid-value`. A step naming a tier when the caller supplied no `providerFor` factory is an `interpreter-red` STOP, never a silent run on the default tier |
240
- | `steps[].attempts` | optional; int `1..capRuns` | TIGHTEN-only: the step's own retry cap inside `ralph`, floored against the shell's `capRuns` (default 3). Above it reds `bounds`; a plan may narrow the shell's cap, never raise it |
279
+ | `steps[].model` | optional; `sonnet` (`STEP_MODELS`) | the step's model TIER — a menu, never a model string the agent spells (the tier→model mapping and any per-model effort params are the runner's). `opus` is deliberately absent: reserved for work the human assigns, never plan-selectable; `haiku` was removed 2026-08-06 as a reversible ATTRIBUTION PROBE (F87, see `STEP_MODELS`), so the menu is one entry deep and the field is no longer offered in the drafting prompt. The field and its validator branch STAY: `model: "sonnet"` still validates, a stored bridge declaring it still loads, and restoring haiku is a one-token edit — this is not a finding that haiku is incapable. **The narrowing binds only what the AGENT may express.** Which real model a tier maps to, and what the caller runs as its own default worker model, stay the OPERATOR's: `providerFor` is yours, and the in-repo runner's `--model haiku` probe knob is untouched (PRD v1.36) — the runner's tier map is deliberately WIDER than the plan menu. Off-menu reds `invalid-value`. A step naming a tier when the caller supplied no `providerFor` factory is an `interpreter-red` STOP, never a silent run on the default tier |
280
+ | `steps[].attempts` | optional; int `>= 1` | **RETIRED and INERT (F79) — it bounds nothing.** A step's iterations are governed by the STRIKE LADDER (below), which is shell-owned and inexpressible from the plan. The field is no longer offered in the drafting prompt and the runner ignores it; it is deliberately still ACCEPTED, because stored bridge plans carry it and a plan minted under one runner's number must not red under another's. The shape is still checked (a known field holding garbage stays a `bounds` red); the old `<= capRuns` ceiling is gone with the cap it named. It was previously TIGHTEN-only — which meant a step that needed MORE iterations could not ask, and drafters measurably tightened it instead (F77) |
241
281
  | `steps[].scope` | optional; one value from the SAME menu `tree-changed` uses | narrows this step's live WRITE fence to a subset of the signed `writeScope` — chosen from the offered scope menu (below), never authored. Off-menu reds `invalid-value` carrying the menu |
242
282
  | `steps[].exit` | 1..2 items (`MAX_EXITS_PER_STEP`), ALL must pass (AND-only, no OR/NOT) | closed menu (`EXIT_TYPES`): `artifact-written(path, pattern?)` · `tree-changed(scope)` · `json-valid(path)` · `check-passes(name)`. **`tree-changed.scope` is a MENU, not a glob the agent authors** — the runner enumerates the signed fence entries plus the real directories beneath them (shallowest-first, capped at `MAX_SCOPE_MENU`=24; `legalScopes` in `src/plan.js`), `planPrompt` lists them verbatim, and `validatePlan` accepts membership only — the SAME array on both sides, so what was offered is what is accepted; omitted, it derives from `writeScope` alone — never a free-text fallback. An off-menu value that escapes the fence still reds `scope-escape` (the ledger's attribution class), an in-fence unoffered value reds `invalid-value` carrying the menu. `runPlan` emits `scope-menu {offered, truncated, offerableCount?, cap?}` so a capped menu is never silently complete. `check-passes` must name a stage on the close's DERIVED menu (`check-unknown` red names the offered menu — no operator authors this list, it comes straight from `checkMenu(close)`); on a write-granted step it must be paired with `tree-changed` (`exit-illegal` — the seed tree is green, a lone check would pass untouched, F17/F46). **`check-passes` also requires a write-class verb (`write`/`edit`) on the SAME step** (`exit-illegal` — BREAKING for plan authors: a read-only "verify" step carrying a check used to validate and no longer does). A failing check's gap is re-delivered to that step's OWN worker, so a step holding a check must be able to act on it; a read-only one is a mailbox with no hands and stalls to cap on a byte-identical gap (measured on 4 of 4 drafted plans). The check belongs on the step that fixes; the run's final verification is the operator's close, which the agent never authors. The rule is stated identically in the drafter prompt and the validator, so the two can never disagree — and it is suppressed when `tools` failed to parse (the step's hands are unknowable then, and the real defect already redded: one defect, one red). `artifact-written.pattern` must compile AND survive a ReDoS shape check — an unbounded quantifier over a group that itself repeats unboundedly (`(a+)+`, `(\d*)*`, even wrapped: `((a+))+`) is an `invalid-value` red (F49, catastrophic-backtracking footgun; rewrite without a repeated group inside a repeat). Exits verify FORM, not truth — progress gates; the operator's close stays the one arbiter |
243
283
 
284
+ **A step has exactly ONE check slot — plan for it.** The three exit rules above compose into
285
+ a ceiling worth stating on its own, because it decides what a plan can enforce *during* a
286
+ run: `MAX_EXITS_PER_STEP` is **2**, the two are ANDed (no OR/NOT, `exit-illegal` past the
287
+ bound), and a `check-passes` on a write-granted step MUST be paired with a `tree-changed`
288
+ exit. Both slots are therefore spent the moment a step carries one check, so **a step cannot
289
+ carry two checks** — `check-passes(typecheck)` and `check-passes(no-suppressions)` together
290
+ on one step is inexpressible today, not merely discouraged. The consequence for an adopter:
291
+ if your close judges N stages, at most ONE of them can be enforced inside a step; the other
292
+ N−1 are enforced only at the close, at the end of the run. That is a COST shape, not a safety
293
+ hole — the close still refuses a tree that fails any stage, and nothing green is minted mid-run
294
+ — but a stage first tested at the tail is a stage the run pays full price to discover.
295
+
244
296
  Red vocabulary (both validators): `parse-error`, `unknown-field`, `missing-required`,
245
297
  `invalid-value`, `bounds`, `duplicate-id`, `close-type`, `close-hierarchy`,
246
298
  `secret-literal`, `scope-escape`,
@@ -273,9 +325,22 @@ Append-only JSONL event emitter bound to one file. `seq` monotonic per spine, `t
273
325
  last. Consumers are pure listeners; nothing reads the file back. Returns each event as
274
326
  written.
275
327
 
276
- ### `ralph({ middle, close?, judge?, capRuns, emit, redact?, closeTimeoutMs?, cwd?, expect?, judged?, gapKeep?, workerWrites? })` → `'green' | 'escalated'` — `src/ralph.js`
277
-
278
- The dumb outer shell: `while close-red and under-cap: run the middle`. `close` is an argv
328
+ ### `ralph({ middle, close?, judge?, capRuns?, ladder?, emit, redact?, closeTimeoutMs?, cwd?, expect?, judged?, gapKeep?, workerWrites? })` → `'green' | 'escalated'` — `src/ralph.js`
329
+
330
+ The dumb outer shell: `while close-red and not exhausted: run the middle`. **Exhaustion has
331
+ two alternative rules and you supply exactly one** — `capRuns` (a fixed number of middle runs)
332
+ or `ladder` (a PROGRESS governor). With a governor the iteration count floats and the loop
333
+ ends on STRIKES instead; ralph still interprets nothing — it hands the governor each red
334
+ iteration's gap and reads back a boolean. Both rules exhaust through the SAME terminal:
335
+ category `cap-halt`, the same three records in the same order, so nothing downstream (the
336
+ ledger's excluded-set, the step loop's one replan trigger) has to know which rule fired.
337
+ `ladder` is an INTERFACE, not one module: `runPlan` wires `createLadder` (`src/ladder.js`,
338
+ repeats + writes) per step and — since v1.46 — the close TREND governor (`src/trend.js`,
339
+ per-stage graded numbers) on the close-fix loop. A governor may supply an optional
340
+ `terminal()` to override the exhaustion PROSE (the step ladder's copy offers a replan and
341
+ there is no planner at the close); it may never override the category or the outcome. Both
342
+ modules are INTERNAL (not on the public surface), so a direct `ralph` caller uses
343
+ `capRuns` — the strike rule reaches you through `runJob`/`runPlan`'s `strikeLimit`. `close` is an argv
279
344
  whose exit code is truth (`runClose` is also exported — **async since 0.6: `runClose` and
280
345
  `runStages` return Promises**; the child is awaited instead of spawnSync so a running close
281
346
  no longer freezes the host event loop (F68); every close semantic — timeout signal, output
@@ -298,14 +363,17 @@ if it is still there 2s later, ended with SIGKILL: SIGTERM is a request a close
298
363
  The verdict is untouched — the FIRST fault named the outcome and the kill only enforces it.
299
364
 
300
365
  **The close child's environment is stripped of credential-shaped variables before it runs**
301
- (`CLOSE_ENV_DENY` in `src/ralph.js`: provider keys, `AWS_*`, and any name ending
302
- `_API_KEY`/`_SECRET`/`_TOKEN`/`_PASSWORD`/`_CREDENTIAL(S)`) — a close judges a tree and never
366
+ (`CLOSE_ENV_DENY` in `src/ralph.js`: provider keys, `AWS_*`, any name ending
367
+ `_API_KEY`/`_SECRET`/`_TOKEN`/`_PASSWORD`/`_CREDENTIAL(S)`, plus known credential spellings
368
+ the suffix rule cannot see — `PGPASSWORD`, `MYSQL_PWD`, `SSH_PRIVATE_KEY`, bare
369
+ `TOKEN`/`SECRET`/`API_KEY`, …) — a close judges a tree and never
303
370
  needs them. A COPY is stripped, never `process.env`, so the host keeps the key it spends every
304
371
  round. This is exposure reduction, not a sandbox: worker-authored code run by the close still
305
372
  has network and your OS user's permissions. The strip has no opt-out — no plan, config, or
306
373
  spec field can widen it — so if your close genuinely needs a credential-shaped variable
307
374
  (`AWS_*` regions/profiles included, e.g. a suite booting against localstack with a test DB
308
- password), it will not be there and the close reds; set such values inside the close command
375
+ password, or `PGPASSWORD` for one that hits a real postgres), it will not be there and the
376
+ close reds; set such values inside the close command
309
377
  itself, not the inherited environment.
310
378
 
311
379
  **The forbidden zone (PRD v1.11 / F17).** `runClose` returns a verdict ONLY when judgment
@@ -366,7 +434,7 @@ generic unknown-field. Menus exported: `CLOSE_TYPES`, `CLASS_BY_CLOSE`, `GOLD_CO
366
434
  `CADENCE_UNITS`, `PROVIDERS`, `CONDITION_KEYS`, `TOOL_MENU`, `LOCKED_TOOLS`, `STORE_VERBS`,
367
435
  `VERDICT_TYPES`, `LOCKED_VERDICTS` — plus `checkMenu` itself.
368
436
 
369
- ### `validatePlan(input, { job, maxStepRounds?, scopes?, capRuns? })` → `{ ok, reds, plan }` — `src/plan.js`
437
+ ### `validatePlan(input, { job, maxStepRounds?, scopes? })` → `{ ok, reds, plan }` — `src/plan.js`
370
438
 
371
439
  `validateJob`'s SIBLING in the two-document split, never a third validator over it (the
372
440
  two-doc split's third validator never happens: plan-v1 gates the PLAN, the job spec stays
@@ -376,7 +444,8 @@ from `job` (a missing or non-plan-shape job fails CLOSED, `job-invalid`). Never
376
444
  same `{ code, path, detail }` red shape as its siblings; `verb-escape` reds carry the
377
445
  escaping verb as a structured `verb` field (the ledger keys on it). `maxStepRounds`
378
446
  (default 40 — the shell's tool-mode per-attempt bound) ceilings every step's `rounds`;
379
- `capRuns` (default 3) ceilings every step's `attempts`; `scopes` is the offered
447
+ **the `capRuns?` option is GONE** (F79) — it existed only to ceiling `attempts`, now
448
+ retired-but-accepted, so a plan carries no iteration bound to check; `scopes` is the offered
380
449
  `tree-changed` scope menu — the array of scope strings the drafter prompt listed, which
381
450
  `runJob`/`runPlan` build internally from the signed fence plus the real directories beneath
382
451
  it (a direct `validatePlan` caller may pass its own array); omitted, it derives from the
@@ -401,6 +470,17 @@ ONE inventory) at the emission boundary, before anything rides the append-only s
401
470
  module itself does not scrub: a direct `evalExits` caller wiring results into its own log
402
471
  must apply its own redaction.
403
472
 
473
+ A `check-passes` detail is sliced at `CHECK_GAP_MAX` (12,000 chars) — a defensive backstop
474
+ above the close path's own `boundGap` envelope, not a second trim of it: a normally-bounded
475
+ gap passes through intact and only a seam that returned something unbounded is ever cut. The
476
+ constant is now `export`ed from `src/exits.js` so the replan brief's own backstop imports it
477
+ rather than respelling it (two spellings would drift on the one property that matters —
478
+ whether the `not ok`/`FAILED` names survive). **It is NOT part of the adopter surface:** the
479
+ package `exports` map admits only `"."`, and `src/index.js` re-exports `snapshotScope` and
480
+ `evalExits` from this module and nothing else, so `CHECK_GAP_MAX` and `boundGap` are internal
481
+ by construction and a deep import cannot reach them. They are named here to explain the
482
+ behaviour you WILL see in a detail string, not as API to call.
483
+
404
484
  ### `jobSpecHash(job)` / `checkApproval(job, approvals)` — `src/job.js`
405
485
 
406
486
  The pure half of **human-signs-always**: an agent may draft a job spec, but no job runs
@@ -424,7 +504,12 @@ Reserved spine vocabulary (V7, machinery-free until job #1 surfaces one):
424
504
  `coordination-red` — a failure between units (scope contention, step order, store
425
505
  races), never to be folded into worker/interpreter reds.
426
506
 
427
- ### `runJob(spec, { approvals, workdir, provider, nativeProvider?, providerFor?, emit, capRuns?, shellCapUsd?, closeTimeoutMs?, layerRoot? })` → outcome — `src/run.js`
507
+ ### `runJob(spec, { approvals, workdir, provider, nativeProvider?, providerFor?, emit, capRuns?, strikeLimit?, shellCapUsd?, closeTimeoutMs?, layerRoot?, bridge?, priorSpentUsd?, priorSpendComplete?, priorWallMs?, resumeSeed?, resumeGrades?, resumeReplans? })` → outcome — `src/run.js`
508
+
509
+ The last six are the RESUME fold and are documented under *Resuming a killed run* below; they
510
+ default to `0` / `true` / `0` / `null` / `[]` / `null`, so a fresh run passes none of them.
511
+ Three of them are folds of a bound the operator SIGNED (money, wall, replans) and one is a
512
+ readout seed (grades) — the distinction matters and is spelled out there.
428
513
 
429
514
  The runner — the shell's top layer, and the ONE entry. It composes everything below it and
430
515
  interprets nothing itself. Sequence: **approval gate** (human-signs-always — refuses an
@@ -434,19 +519,24 @@ known-answer round-trip before tokens: `smoke-red` — a silent degradation thro
434
519
  `min(job budget − spent, shell cap)`.
435
520
 
436
521
  Outcomes: `green | already-green | escalated | unapproved-spec | job-red | smoke-red |
437
- plan-red | check-red | close-red | close-unsupported | pricing-red | provider-red |
438
- interpreter-red | cap-halt | wall-halt | step-stalled | step-red:<id>`. `provider-red` is a
522
+ plan-red | check-red | close-red | close-unsupported | recipe-stale | pricing-red |
523
+ provider-red | interpreter-red | cap-halt | wall-halt | step-stalled | step-red:<id>`. `provider-red` is a
439
524
  transport throw or a worker round the API cut off mid-generation (`truncated:max_tokens`,
440
525
  BA-6 — before which it laundered into a clean finish, F25): no verdict exists and the failed
441
526
  round's spend is only partly known (F6). `cap-halt` is the wallet; `wall-halt` is the clock
442
527
  (F64 — a timeout derived from the run's own deadline is a governance stop, never a transport
443
528
  casualty). **`cap-halt` reaches you from the close-fix loop too, not only from a step:** the
444
- shell spells attempt-exhaustion and a money-gate halt with the same category, so both the
529
+ shell spells exhaustion (strikes on a step, and — since v1.46 — strikes in the fix loop as
530
+ well, `capRuns` only while that loop's trend instrument is blind) and a money-gate halt with
531
+ the same category, so both the
445
532
  step loop and the fix loop read the WALLET to tell them apart — a drained wallet is
446
- `cap-halt` (the resume-to-cap checkpoint), attempts spent with money still on the table is
533
+ `cap-halt` (the resume-to-cap checkpoint), exhaustion with money still on the table is
447
534
  the designed `escalated` terminal ("the close is still red"). The fix worker's gate is built
448
535
  with the wallet at its most drained, which is exactly where a money cut would otherwise
449
- masquerade as a capability read (F45). `step-stalled` is the F66 stall fuse giving up: no
536
+ masquerade as a capability read (F45). A `cap-halt` from the wallet also emits the
537
+ decision-ready `money-halt` record described under the plan flow below — the kept verdict, the
538
+ trend and the three levers — so a consumer never has to reconstruct the stop from the outcome
539
+ name alone. `step-stalled` is the F66 stall fuse giving up: no
450
540
  completed round for 5 minutes,
451
541
  three times on one call — each stall silently abandons the hung call and reissues it
452
542
  (self-heal first); only the third throws. Inside a step it is the THIRD replan trigger
@@ -462,11 +552,13 @@ decision-ready escalation with a terminal `job-end`: the spine never dangles.
462
552
  of PRICED rounds ONLY — never an estimate from token counts or averages (cap-not-estimate) —
463
553
  and it is stated even on the pre-token reds, where the honest figure is a real `0`.
464
554
  `spendComplete` says whether that figure is EXACT; `false` means `spentUsd` is a FLOOR ("at
465
- least $X", true total unknowable) and must not be read as a total. Three things set it, all
555
+ least $X", true total unknowable) and must not be read as a total. Four things set it, all
466
556
  the same class — a call whose cost is unknowable from here: a round that came back unpriced
467
557
  (F6); a run that ABSORBED a stall, because the fuse's silent reissue may pay twice for one
468
- answer and the flag is one-way whatever the outcome; and a `wall-halt` that cut a call
469
- mid-flight (`cutMidCall`). A wall stop read BETWEEN iterations or steps has no call in flight,
558
+ answer and the flag is one-way whatever the outcome; a `wall-halt` that cut a call
559
+ mid-flight (`cutMidCall`); and a RESUME whose fold was itself a floor
560
+ (`priorSpendComplete: false` — an unknown does not heal by being carried forward, so every
561
+ round of this attempt being priced repairs nothing about the one before it). A wall stop read BETWEEN iterations or steps has no call in flight,
470
562
  so it stays exact. Both fields are present on all outcomes, so a consumer never branches on
471
563
  field presence and never has to launder a missing `spentUsd` into `$0`.
472
564
 
@@ -481,16 +573,142 @@ write-class nor the store-class verbs are in its menu; hard-bounded rounds) →
481
573
  the planner never sees the repo, only the scout blob; drafted against a schema
482
574
  description with check NAMES only; `validatePlan` gates it, one redraft with the reds
483
575
  fed back, then `plan-red`) → **EXECUTE** (strictly sequential micro-loops: `ralph` with
484
- the exit-evaluator judge; tree snapshots at step start; the gap names every failing
576
+ the exit-evaluator judge, each step under its own STRIKE LADDER — below; tree snapshots at
577
+ step start; the gap names every failing
485
578
  wall — mechanical genre, F46's measured mechanism; artifacts feed forward labeled by
486
- step id) → **ONE replan**, triggered by exhaustion OR variance (an instrument stop never
487
- replans) → **the operator's close**, a red feeding ONE bounded fix loop judged by the
488
- REAL close. `plan-executed` (the plan-as-executed record, design law #2) lands on the
579
+ step id) → **ONE replan**, triggered by exhaustion OR variance OR a stall (an instrument stop
580
+ never replans) — plus at most ONE arbiter-granted extra on a second variance stop that reads
581
+ `converging`, below → **the operator's close**, a red feeding ONE bounded fix loop judged by the
582
+ REAL close and governed by the close TREND rule below (`capRuns`, default 3, survives only as
583
+ its blind fallback). `plan-executed` (the plan-as-executed record, design law #2) lands on the
489
584
  spine on every path that executed steps. Additional outcomes: `already-green |
490
585
  plan-red | check-red | close-red | wall-halt`. Worker prompts hold the v1.12 §5 contract
491
586
  (mutation-proven): the absolute repo root, the step's action/target, prior artifacts,
492
587
  the gap — NEVER the budget, the close command, a check's command, or the arbiter's books.
493
588
 
589
+ **The strike ladder — how a STEP ends (F77–F79).** A plan step is **not** bounded by a
590
+ number of iterations. It runs until it stops making PROGRESS: a *strike* is a red iteration
591
+ that repeats an already-seen gap or wrote nothing, and `strikeLimit` strikes (shell option on
592
+ `runJob`/`runPlan`, **default 2**) end the step. Concretely — repeats are matched against a
593
+ **seen-set** for the whole step, never just the last gap (an A→B→A oscillator is real and
594
+ last-only reads it as progress); the gap is normalized for COMPARISON only (TAP `duration_ms`
595
+ lines, ISO stamps and `ms` figures dropped so a re-run is byte-stable — error counts, file
596
+ names and line numbers all SURVIVE, so `30 errors` → `22 errors` is progress and not a
597
+ repeat), and the gap the worker sees is never rewritten; "wrote" is the **count delta of the
598
+ gate audit's allow-decision write/edit records** (the F32 instrument — never `git status`,
599
+ never a tree diff, and never the path SET, which is constant after iteration 1 when a step
600
+ rewrites one target); strikes are **sticky** — a good iteration in between never repays one.
601
+ Everything else that bounds a step is unchanged and each still ends it on its own authority:
602
+ the wallet, the wall, the variance meter and the stall fuse. The ceiling is **arbiter
603
+ territory** — the plan cannot express it, tighten it or raise it — and the practical
604
+ consequence for a caller is that a **converging step now runs on the money and wall you
605
+ signed** instead of stopping at a count, so size those two numbers for the job (the ladder
606
+ adds no third ceiling of its own). The exhaustion terminal is the same `cap-halt` category
607
+ as before; the spine gains a per-iteration `ladder` record
608
+ (`{iteration, strike, strikes, limit, wrote, repeatOf, distinctGaps}`), and the `cap-halt`,
609
+ escalation and wall-stop records carry `strikes`/`strikeLimit`/`distinctGaps` beside the
610
+ iteration count rather than a cap the step no longer has. **The replan brief
611
+ names the mechanism**: which shape ended the step (converging-cut / stalled-no-write /
612
+ repeated-gap, with the iteration numbers as evidence), the gap trajectory, and how much money
613
+ and wall the stop left unspent (time reported UNBOUNDED when no wall was set, never `0`).
614
+ **It also carries that step's own last EXIT output, verbatim** (F86) — masked through the repo's
615
+ one secret inventory, and labelled as *the exits'* words rather than the close's, because the
616
+ payload is `lastGap`: the join of EVERY failing exit's detail in exit order. The close stage's
617
+ output is one of those details, never reliably all of them, so a step that wrote nothing opens
618
+ the block with the exit evaluator's own `0 files changed under …` prose. That line is kept (it is
619
+ exactly what a replanner should be told); only the label was wrong. The bound over it is a
620
+ **backstop, not a second envelope** — what arrives has already been bounded by `runClose` under
621
+ the stage's own `gapKeep`, which deliberately rescues the `not ok`/`FAILED` names out of the
622
+ elided middle, and re-enveloping that deletes them a second time (F28 reintroduced; measured, a
623
+ second 400/1500 pass dropped 12 of the 78 names the first had rescued). Under `CHECK_GAP_MAX` —
624
+ `src/exits.js`'s own ceiling, imported rather than respelled so the two seams cannot drift — the
625
+ gap passes through VERBATIM, with ~1.8KB of measured headroom over the largest thing the close
626
+ envelope can produce; the backstop fires only on a gap nothing upstream ever bounded. No gap, no
627
+ block, and the brief is byte-identical to one drafted before this existed.
628
+ Handed over as TEXT: the runner makes **no parsed file claim of its own** in this channel —
629
+ a close's summary line ("N error(s) in a.js, b.js" — every file in scope) and its detail line
630
+ (the one location actually failing) are indistinguishable to a regex, and the shapes differ per
631
+ close anyway. What the planner is told about WHERE the work is comes from the close's own
632
+ bytes, never from the runner's reading of them.
633
+
634
+ **The close TREND rule — how the CLOSE-FIX LOOP ends (PRD v1.46 §4).** `capRuns` retired as
635
+ that loop's governor once its own $0 replay over every archived fix loop came back clean (0
636
+ greens harmed — all three historical fix-loop greens converted in ≤ 2 verdicts; 1 real waste
637
+ case caught, dead flat at 2 errors for 7 verdicts until the wall). It now stops on the same
638
+ 2-strike no-progress rule, read off a DIFFERENT signal: the close's own graded numbers, **per
639
+ stage** (`src/trend.js`). A stage's series is the first number on the first red-marked line of
640
+ its output, compared only against that stage's own BEST so far (never last-only, the same
641
+ oscillator reason the ladder keeps a seen-set); reaching a LATER stage than ever before is
642
+ progress too, since a staged close is first-red-wins. Two consecutive comparable readings with
643
+ nothing improving ends the loop, under the unchanged `cap-halt` terminal and the unchanged
644
+ `escalated` outcome. **Never across stages** — merging a staged close's axes into one series
645
+ reads a real see-saw (suppressions 12 → 5 while the type errors it re-exposed went 2 → 0) as a
646
+ regression, and that mistake is inexpressible here rather than avoided by care. A stage whose
647
+ output carries no number donates NOTHING — not a zero, not a strike (F6): while the instrument
648
+ cannot compare, `capRuns` is what bounds the loop. It bounds the **consecutive run of
649
+ unreadable gradings**, not the loop's opening — it lifts the moment a stage reports a
650
+ comparable number and **re-arms the moment one stops**, so a loop that reads, goes blind and
651
+ reads again is never bounded by it, while a loop that never compares anything binds on exactly
652
+ the iteration the fixed count always bought it (F84: gating it on "has this loop ever compared
653
+ anything" made a single stage ADVANCE disarm it for the rest of the run, and a numberless-red
654
+ close then had money and the wall for a bound). Two known accepted limits, both stated at the
655
+ site: (1) "lower is better" is not derivable from prose, so a floor-shaped stage (`N tests
656
+ executed, below the seed's M`) reads as not-improving rather than converging — the fail-safe
657
+ direction, since a false "flat" costs one conservative stop and a false "converging" costs a
658
+ top-up spent on a dead run; (2) a series is one bucket per stage NAME, and a stage name is not
659
+ an axis — a close free to red one stage on two different populations (`N error(s) in <scope>`
660
+ vs `the target files are clean but M error(s) exist outside them`) donates both genres to one
661
+ series, and a run crossing that seam can read converging on work that only swapped which wall
662
+ it is behind. Neither is sharpened by teaching the reader to tell prose shapes apart (the F49
663
+ precedent); the second's root fix is a stage split in the close, which is the spec's to change.
664
+ The spine gains a per-iteration `ladder` record here too; every reading names its `governor`
665
+ (`step-ladder` | `close-trend`) so two instruments under one event type can never be averaged
666
+ into one number.
667
+
668
+ **A MONEY halt is decision-ready (PRD v1.46 §2).** A cut by the wallet is the same KIND of
669
+ stop the wall is, so it now reads out the same way: the verdict already minted is KEPT (never
670
+ discarded, never re-derived), and a `money-halt` spine record carries `budgetUsd`,
671
+ `remainingUsd` **with its own `spendComplete`** (the remaining is `budget − spent`, so on a run
672
+ whose spend is a FLOOR it is a CEILING and says so rather than reading as the number — the same
673
+ duty `wall-halt` has carried since W-2; it is the ledger's one figure, never re-derived here),
674
+ the kept `verdict`/`stage`, the run's own per-stage `trend`
675
+ (`converging | flat | unknown`) with the `reading` and `series` it judged, and three
676
+ `options` — top up and resume, revise the spec, abandon. Emitted at every site that cuts a run
677
+ on money (the step loop, the close-fix loop, and the scout/draft relay), exactly as
678
+ `wall-halt` is. The trend obeys the accuracy law above and says `unknown` out loud when the
679
+ close rendered no number (F6). **The library only ever REPORTS**: `budgetUsd` is in the spec
680
+ hash, so a top-up is a spec edit a human signs — nothing in a run may widen its own budget.
681
+
682
+ **ONE progress instrument, read by both halts — and REPORTED by the variance meter.** The
683
+ `variance` record and its escalation carry the same four fields off the same instance (below);
684
+ the meter is the one consumer that never decides on the reading. The wall halt used to run its own: byte
685
+ equality of the last two close gaps, reading `stalled` / `moving` / `unknown` beside the money
686
+ halt's `flat` / `converging` / `unknown` on the same run. Both now read `runTrend`, the run's
687
+ own `src/trend.js` reader. Where a stage reported a comparable NUMBER it decides; where none
688
+ did, the byte comparison survives INSIDE `unknown` as a separate `motion` field
689
+ (`changed | unchanged | null`) and as a clause in the `reading` — never as the headline
690
+ verdict, because "the close's output changed" is not "the run got better" (F6 in a trend's
691
+ coat), and never mapped onto `trend`, because that would put the promotion on the spine.
692
+ `unchanged` points the lever at the goal; `changed` points it at reading the last close output.
693
+ The two readers stay SEPARATE INSTANCES for separate questions (`src/trend.js`, "ONE PER
694
+ SERIES"): the halt readouts ask *was the RUN converging when its allowance cut it*, the
695
+ close-fix loop's governor asks *is THIS loop out of ideas*.
696
+
697
+ **A resumed leg's readout judges the whole CHAIN.** `readResume`'s `restart.grades` carries the
698
+ dead leg's recorded close grades forward — `[{stage, value}]`, counts and stage names only,
699
+ never a gap byte — and `runJob`/`runPlan` take them as `resumeGrades`, which seed the run
700
+ trend's BASELINES (`createTrend({seed})`). Without them a resumed leg restarts the trend at its
701
+ own first close and can report `flat` — *revise the goal* — on a run that was converging when
702
+ its allowance ran out. The seed folds into the series and the per-stage best and into nothing
703
+ else: no iteration counted, no strike minted, no `comparableEver`, no stage position — it is
704
+ history, not this leg's evidence, so the blind-cap backstop still governs a leg whose own
705
+ readings never compare. The close-fix loop's governor is deliberately NOT seeded (it is the
706
+ leg-local question). Sources, in order: the `ladder` records the live governor already wrote
707
+ (`governor: 'close-trend'`, taken verbatim), the `close-precheck`/`outer-close` records, and —
708
+ only on a spine predating that governor — the `close-verdict` records after the `fix-loop`
709
+ marker, which is the same population and excludes a STEP's failing exit. Known limit: one spine
710
+ is read, so a resume of a resume inherits the previous leg's chain and not the one before it.
711
+
494
712
  **Time and materials (T + A, PRD v1.27/v1.29).** `runPlan` starts ONE wall clock per run
495
713
  (`createClock`, `src/clock.js`) from the signed `maxWallMs` and emits `wall-clock` with the
496
714
  requested AND enforced numbers up front, plus the `closeStages` count that explains the gap
@@ -499,17 +717,25 @@ stage count comes from `stageClose`, the same staging the runner executes). Enfo
499
717
  **between-round deadline** — the only seam that exists, since `loop.stop()` cannot cut an
500
718
  in-flight call (F61: fired at 500ms,
501
719
  returned at 4,018ms) — so an attempt that crosses the deadline mid-flight emits `wall-bounded`,
502
- is judged, and feeds its gap forward exactly like a round-bounded attempt. **The close is never
720
+ is judged, and feeds its gap forward exactly like a round-bounded attempt. A call that never
721
+ returns is bounded by the two per-call numbers the same clock derives and `runPlan` passes to
722
+ every provider call: `timeoutMs` (BA-18, idle — reset by every byte) and `deadlineMs` (BA-19,
723
+ TOTAL duration — reset by nothing, so it is the bound for a stream that trickles forever, F66's
724
+ 274-minute call). Both are the wall's own remainder read at call time, and `deadlineMs` is
725
+ **omitted entirely** on a time-unbounded run — no wall, no derived ceiling (F45). Because both
726
+ came from the wall, a trip past the cap routes `wall-halt`, never `provider-red`; the
727
+ discriminator is `clock.expired()` and never the error code, so a timeout with time still on the
728
+ clock stays the transport casualty it is (F64). **The close is never
503
729
  bounded by the wall and always runs to completion** — a deadline that kills grading leaves the
504
730
  run unreadable after the money is spent (the F45 class, money generalized to time); what the
505
731
  wall stops is the START of new work. Three sites decide the run-level terminal `wall-halt`, all
506
732
  on the same clock: the step loop after a step returns, a step that would BEGIN already expired,
507
733
  and the close-fix loop before it opens another iteration — which then stops on the verdict the
508
734
  last close already minted (hamr: *"when time is up, keep the grade we already have and stop"*),
509
- carrying that verdict, the iterations spent, and a progress `trend` (`moving` | `stalled` |
510
- `unknown`, read off the last two close gaps) so the human can pick between raising `maxWallMs`
511
- (resume-to-cap: the stop IS the checkpoint) and revising the goal — both spec edits, both a new
512
- hash to re-sign. `cutMidCall` on the `wall-halt` record splits the deadline seen INSIDE a
735
+ carrying that verdict, the iterations spent, and the SAME progress reading the money halt
736
+ carries (`trend`: `converging | flat | unknown`, plus `motion`) so the human can pick between
737
+ raising `maxWallMs` (resume-to-cap: the stop IS the checkpoint) and revising the goal — both
738
+ spec edits, both a new hash to re-sign. `cutMidCall` on the `wall-halt` record splits the deadline seen INSIDE a
513
739
  provider call from the deadline read between them. Time reporting is
514
740
  licensed at ~90% accuracy by ruling: imprecision is fine, reporting an unknown or unbounded
515
741
  duration as `0` never is (F6 extended to time — unbounded reports `null`, never `Infinity`).
@@ -518,14 +744,57 @@ The planner (never the worker) receives a **materials** block at draft and at re
518
744
  spread on verification-gap duration, so a per-round constant describes almost no real round
519
745
  and a rate framing creates a rush-or-fake incentive. The replan trigger gains a second axis
520
746
  (`step-variance`): a step consuming ≥ `VARIANCE_THRESHOLD` (0.5) of the run's REMAINING money
521
- or time with its exits unmoved is pre-empted at the head of its next attempt so the planner
747
+ or time — either axis — is pre-empted at the head of its next attempt so the planner
522
748
  re-allocates instead. **Known and recorded (F63): that trigger fired 0 times across 18
523
749
  archived spines / 54 steps** (near misses 0.35–0.45) — the threshold is arbiter territory,
524
- deliberately not fitted to those points. The ONE-replan ceiling is unchanged.
750
+ deliberately not fitted to those points. It is no longer inert: it fires on real runs (F85).
751
+
752
+ **The meter REPORTS progress; it never decides on it (F85).** The firing condition above is
753
+ the whole trigger — a progress term inside it would let a governance instrument over an
754
+ operator-owned allowance judge capability, and a converging step that is eating the run is
755
+ still stopped. What the meter says about the work is a MEASURED reading, not an assertion: the
756
+ `variance` spine record gains `trend` (`converging | flat | unknown`), `motion`, `reading` and
757
+ `series` beside its unchanged `step`/`iteration`/`threshold`/`moneyShare`/`timeShare`/`axis`,
758
+ and the escalation `detail` (which the replan brief quotes) ends with that same `reading` as a
759
+ second, separate claim — "it was stopped for eating the run" and "here is what it achieved" are
760
+ different statements. All of it is read off the SAME `src/trend.js` instance the money and wall
761
+ halts read — one reader, so the meter and the halts can never disagree, and `unknown` stays
762
+ `unknown` when the close reported no number (F6). This
763
+ replaces a fixed sentence ("with its exits unmoved") that used to print on every variance stop
764
+ whatever had happened; a consumer parsing that string will not find it.
765
+
766
+ **The replan ceiling is no longer flatly ONE.** A SECOND `step-variance` stop earns exactly one
767
+ more replan when the run's own reading is mechanically `converging`; it is granted by the
768
+ ARBITER off that same reader, bounded by a latch rather than a counter compared against a
769
+ limit, so it cannot creep — a third variance stop is the stop however well the run is going.
770
+ `flat` and `unknown` stop exactly as before, and an exhaustion or stall stop past the ceiling
771
+ is unchanged. The agent has no channel to it: it never asks, is never offered it, and cannot
772
+ influence it (no self-adjusted budgets, ever). Observable on the spine as ADDED fields, never
773
+ repurposed ones: `replan` records carry `replan: <n>` and — only on a granted one — `granted:
774
+ "converging"`, and `plan-executed` carries `replans` beside the unchanged `replanned` boolean.
775
+
776
+ **Both of those count the RUN, not the leg — the ceiling spans a resume chain.** `replanned`
777
+ and `replans` were locals in `runPlan`, and a resume is another `runPlan` call, so a kill used
778
+ to hand the next leg a fresh allowance of an allowance that is the RUN's by doctrine (measured:
779
+ leg 1 replanned and stopped, leg 2 replanned twice more — a run total of 2 with every record
780
+ showing 1). **`plan-executed.replans` therefore now means the CHAIN total**; if you parse spines,
781
+ re-read that field. Each leg DECLARES what it inherited on its own `job-start` — new fields
782
+ `priorReplans` and `priorReplanGrantUsed`, present only when there is a fold — and the next
783
+ reader adds only its own window's `replan` records, exactly the way `priorSpentUsd` folds money.
784
+ The grant latch travels beside the count rather than being derived from it (`1` + `false` is a
785
+ real state: an ordinary ceiling spent, the arbiter's extra still unearned). The leg's own number
786
+ is not lost — a leg emits one `replan` record per replan it drafts, so `chain −
787
+ records-in-this-window` is the fold it inherited. You seed it with `resumeReplans` (below);
788
+ omitted is the cold path and byte-identical to a run nobody resumed.
525
789
 
526
790
  **Per-step model tiers — `providerFor` (P).** A plan step may declare a `model` TIER
527
- (`sonnet` \| `haiku`, `STEP_MODELS`). The tier menu is signed into the schema; mapping a tier
528
- onto a real model (and any per-model provider params) is the RUNNER's territory, so both
791
+ (`sonnet`, `STEP_MODELS` — `haiku` removed 2026-08-06 as a reversible attribution probe,
792
+ F87; the field stays legal and one-token reversible, and the narrowing binds only what the
793
+ AGENT may express, never what the operator may run).
794
+ The tier menu is signed into the schema; mapping a tier
795
+ onto a real model (and any per-model provider params) is the RUNNER's territory — nothing
796
+ forces the `sonnet` tier onto any particular model, and running haiku as your default worker
797
+ model stays an operator choice the plan menu never governed — so both
529
798
  `runJob` and `runPlan` take `providerFor?: (tier: string) => provider`. It is called ONLY for
530
799
  a step that declared a tier, once per such step — the scout, the plan drafter and every
531
800
  untiered step stay on `opts.provider`, so a caller that never plans tiers can omit it
@@ -587,6 +856,352 @@ audit, the smoke store and the litectx store are denied — the agent neither au
587
856
  arbiter nor reads its books. N2 bounds (honest): `gold`/`rubric` closes refuse
588
857
  `close-unsupported` (execution lands at N4).
589
858
 
859
+ ### The reuse registry (Layer 3) — `src/bridges.js`, `src/selection.js`
860
+
861
+ A **bridge** is the plan a green actually executed, kept so the next run of the same SHAPE
862
+ starts from it instead of cold. Storage is a directory of plain JSON files at an
863
+ **operator-supplied path** — no database, no default location (a missing registry reds
864
+ rather than being conjured). Everything here is either a pure derivation or an explicit
865
+ read/write of one file; nothing decides a run.
866
+
867
+ **Writing the box (R1 — only a green writes it).** `mintBridge(meta, record)` builds a new
868
+ entry from a FIRST green — there is deliberately no way to create one without a green, so a
869
+ failed plan cannot enter the registry even by accident. `appendGreen(bridge, record)` adds
870
+ the plan AS EXECUTED as the next **version** plus a history row; `appendRed(bridge, record)`
871
+ writes a **history row only** and never touches `versions` — a red demotes, it does not edit
872
+ the recipe. Both are pure (a new entry is returned; the input is untouched).
873
+ `saveBridge(dir, bridge)` writes atomically (temp + rename) and REFUSES an entry its own
874
+ validator rejects; `loadBridge` / `loadRegistry` read back, and a malformed file is
875
+ **skipped and reported** (`ok:false` with one red per bad file) rather than silently
876
+ dropped. `makeRegistry` is separate from `saveBridge` on purpose: saving must never conjure
877
+ a registry from a typo'd path.
878
+
879
+ **Status is DERIVED, never stored** (`deriveStatus`): **candidate** = one green, **proven** =
880
+ greens on ≥2 DISTINCT patients, and a `red` on a proven entry drops it back to candidate.
881
+ A **casualty is not a red** — only the literal outcome `red` demotes; `provider-red`,
882
+ `wall-halt`, `close-crashed` and friends are casualties and are never evidence in either
883
+ direction. There is deliberately **no probability score**: n=1 is an anecdote whichever way
884
+ it points, and a percentage over 2–3 runs is fake precision. `costUsd`/`wallMs`/`rounds` are
885
+ a number or an EXPLICIT `null`, and the key is REQUIRED either way, so an unknown has to be
886
+ said rather than omitted (F6 — an omitted key is how a `?? 0` gets written downstream).
887
+
888
+ **`loadGate(bridge, job)` → `{ ok, reds }` — the load-time gate.** Exactly three checks,
889
+ asked *"is this the right KIND of recipe?"*: the job's **verdict type**, the **close-stage
890
+ kinds** (v1: the same stage names in the same order), and every stored verb **within THIS
891
+ job's signed menu** (an omitted `tools` means the concrete current `TOOL_MENU`, MED-1).
892
+ **Nothing about paths, scopes or targets** — those are instance-bound and are EXPECTED to
893
+ red: every recipe from a different day names yesterday's bricks, and that is what a recipe
894
+ IS. The full `validatePlan` still judges the TWEAKED draft at draft time; no second, looser
895
+ path exists for an inherited plan. A failing gate is a RESULT (`recipe-stale`), never a throw.
896
+
897
+ **A CHANGED CLOSE therefore expires every bridge minted under the old one.** The stored
898
+ `closeStageNames` must match the job's stage names in the same order, so adding, renaming,
899
+ splitting or reordering a stage makes existing entries fail the gate by construction. That is
900
+ the doctrine, not a rough edge: a different close is a different KIND of job, and a recipe
901
+ proven against one ruler has not been proven against another. The remedy is a fresh green under
902
+ the new close, which mints the next version — an entry is **never edited into agreement**, and
903
+ falling back to cold stays the caller's decision. In this repo the `u-*` close split
904
+ (2026-08-05) put `aurora-u-spawner` and `litectx-u` in exactly that state; both are re-minted by
905
+ their next green, not repaired.
906
+
907
+ **`runJob`/`runPlan` take `bridge?`** — a validated entry to reuse. The gate runs **at the
908
+ door**: before the clock, before the close precheck, before any token. On a pass, the
909
+ **newest** version's plan rides into the FIRST drafting prompt as a starting draft
910
+ (`bridge-loaded {name, versions, runid}` on the spine) and everything after it is the
911
+ ordinary shipped path — same validator, same redraft-on-reds, the same replan ceiling, and a
912
+ **replan never re-injects the bridge** (it drafts from the run's own state). On a fail the
913
+ run returns the distinct terminal **`recipe-stale`** with `bridge-gate {outcome, name, reds}`
914
+ and a decision-ready escalation, having spent nothing. **Falling back to cold is the
915
+ caller's decision, not an automatic silent fallback** — starting a paid run on a decision
916
+ nobody made is the same class of error as widening a cap to manufacture a green. Omit
917
+ `bridge` and the flow is byte-identical to a pre-Layer-3 run: no new event, and no starting
918
+ draft anywhere in the prompt.
919
+
920
+ **`renderListing(registry)` / `selectionPrompt(listing, ask)` — `src/selection.js`.** Pure
921
+ text: they read no file, call no model and decide nothing. `renderListing` takes
922
+ `loadRegistry`'s result (or a bare array) and renders one compact block per bridge — name,
923
+ the goal sentence it greened, status, greens/reds with the last outcome, and the cost/time
924
+ BAND of its greens. An unknown cost or duration renders `UNKNOWN` and a partial aggregate
925
+ says how many it skipped (never a `$0.00` that reads as exact); entries that could not be
926
+ read are NAMED, so the listing can never be quietly shorter than the directory. Ordering is
927
+ deterministic by name and it never throws. `selectionPrompt` wraps that listing with the ask
928
+ and D3's two standing rules — *"none matches"* is a first-class answer that means the agent
929
+ drafts new, and a PINNED workflow may be refused only EXPLICITLY, with a reason. The
930
+ selection CALL, the pin/shortlist/force-cold flow and the parse of the answer are YOURS —
931
+ or `runReuse` below, which is the shipped one.
932
+
933
+ ### The reuse ENVELOPE and `runReuse` (Layer 3, D7) — `src/reuse.js`
934
+
935
+ `runReuse` is `runJob` under an operator-signed **envelope**: try a stored workflow, then
936
+ another, then draft cold — hamr's *"$5 and 30 mins x2 then start anew"*. It composes; it
937
+ decides nothing the arbiter owns.
938
+
939
+ **`validateEnvelope(input, { job? })` → `{ ok, reds, envelope }`.** The envelope is
940
+ `{ perTryBudgetUsd, perTryWallMs, bridgeTries }` and **all three are required and
941
+ explicit — there is no default for any of them**, for `maxWallMs`'s reason: a defaulted
942
+ cap is a silent second ceiling. `bridgeTries: 0` is legal and means force-cold. With a
943
+ `job`, the composition is checked: the per-try numbers may only **TIGHTEN** the signed
944
+ spec — a per-try budget above `job.budgetUsd`, or a wall above `job.maxWallMs`, is
945
+ `envelope-widens`, never a silent raise. (A spec with no `maxWallMs` is time-unbounded by
946
+ explicit choice, so any wall tightens it.)
947
+
948
+ **`resolveTrySpec(job, envelope)` → the per-try spec.** Every try — cold leg included —
949
+ runs THIS spec, so the envelope's numbers are the numbers all the way down. Tightening
950
+ makes a genuinely different spec, so it makes a different **spec hash**: the tightened run
951
+ is a new spec VERSION and `runJob`'s approval gate refuses it until the operator signs
952
+ that hash. `runReuse` hands `approvals` straight through and has no way to forge one. An
953
+ envelope equal to the spec's own numbers leaves this spec hash-identical.
954
+
955
+ **`resolveReuse(job, envelope)` → `{ schema: 'reuse-v1', spec, bridgeTries }`, and
956
+ `reuseSpecHash(resolved)` → the hash your operator signs. Show THIS one at your approval
957
+ gate** (`scripts/run-reuse.mjs` does). The envelope is three numbers and the third is a
958
+ MULTIPLIER — the worst case a reuse run authorizes is
959
+ `perTryBudgetUsd × (bridgeTries + 1)` — so a signature over the per-try spec alone covers
960
+ only budget and wall, and `--tries 0` and `--tries 9` print the same hash. The signed
961
+ artifact is therefore the per-try spec WRAPPED with the count. It is a wrapper rather than
962
+ a field on the spec because `validateJob` reds unknown top-level fields: a `bridgeTries`
963
+ written onto the spec would flip the hash and then make every try a `job-red`. The hash is
964
+ composed from `jobSpecHash` (so MED-1's resolved-`tools` pinning is inherited, with no
965
+ second canonicalizer to drift) under a `reuse-v1` domain prefix, and `runReuse` reads its
966
+ loop bound back off the same resolved object — the count enforced is the count signed.
967
+ **This is a signing-SCHEME change: any hash signed before the count was folded in no
968
+ longer matches, by design, and there is no legacy acceptance path — re-sign once.** Your
969
+ gate still mints `approvals` for `runJob` at the PER-TRY SPEC's `jobSpecHash`, which the
970
+ matching approval hash entails (it is a function of exactly that hash plus the count).
971
+
972
+ **`selectBridge({ registry, job, ask, provider, pinned?, shortlist?, forceCold?, exclude? })`.**
973
+ ONE model call on the drafter-tier provider **you** hand in (no provider is constructed
974
+ inside), rendering `renderListing` + `selectionPrompt` and parsing a strict
975
+ `{"choice": <name|null>, "reason": "…"}` through the repo's one `extractArtifact`. Returns
976
+ `{ choice, reason, called, refused, forcedCold, red, costUsd, spendComplete, candidates }`.
977
+ `forceCold` and an empty candidate set skip the call entirely ($0, no tokens). A name that
978
+ is not on the listing it was given is a named red, never used. **A pin does not bypass the
979
+ call** (D3): it is stated in the prompt, and if the answer names anything else the result
980
+ is `{refused: true}` — the substitute is NOT adopted and the decision goes back to you.
981
+ Cost is metered and reported: `costUsd` is a number or an explicit `null` (F6).
982
+
983
+ **`runReuse({ job, approvals, registryDir, envelope, patient, workdir, provider, emit, … })`.**
984
+ Envelope → registry → up to `bridgeTries` tries → the cold leg. Per try: selection runs
985
+ against a **freshly reloaded** registry with the already-tried names excluded, the chosen
986
+ entry rides into `runJob` (whose own load gate is the D2 shape check — a `recipe-stale`
987
+ refusal costs $0 and simply moves to the next candidate), and then **the box is written
988
+ (R1)**: a green appends a VERSION carrying the plan AS EXECUTED (read from the run's own
989
+ `plan-accepted`) plus a history row; a graded red appends a history row only; a casualty
990
+ appends a row under its **own** outcome name and cannot demote. **A green ends the loop.**
991
+ Tries exhausted → a cold run under the same per-try numbers, whose green mints a new
992
+ bridge named for the job slug (or appends, if that name already holds greens — clobbering
993
+ a green is exactly what R1 exists to prevent). A cold RED writes nothing: the entry bar is
994
+ a green.
995
+
996
+ **Which outcomes are a graded RED** (`REUSE_GRADED_RED`): only `escalated` — the terminal
997
+ where the close judged the tree, the bounded fix loop spent its attempts with money still
998
+ on the table, and the close was still red. Everything else non-green is a **casualty** and
999
+ keeps its own name: `cap-halt`/`wall-halt` (governance), `provider-red`/`step-stalled`
1000
+ (transport), `close-red`/`close-unsupported` (the close FAULTED and rendered no judgment),
1001
+ `recipe-stale` (refused at the door), `plan-red`/`step-red:*`/`check-red` (the flow stopped
1002
+ before the close ever judged). All of them are recorded in full on the history row, which
1003
+ is where D6 puts the detail a human reads; the coarse status ladder is left alone.
1004
+
1005
+ **Result:** `{ outcome, tries[], selection[], spentUsd, spendComplete, bridgeWrites[],
1006
+ decision, options, detail, reds, triesUsed, triesAuthorized, envelope, specHash,
1007
+ approvalHash }` — `specHash` is the per-try spec's (what `runJob`'s own gate reads),
1008
+ `approvalHash` is the operator's, covering all three envelope numbers. Every
1009
+ try row is **decision-ready**: `bridge`, `runOutcome`, `verdictClass`, `failingStage`,
1010
+ `spentUsd` vs `capUsd`, `wallMs` vs `wallCapMs`, `capBound`, `wallBound`, `rounds`, and
1011
+ `closeReached`. `spentUsd` is the sum of PRICED figures only across tries AND selection
1012
+ calls; one unknown makes `spendComplete` false, and a try whose `job-end` never landed
1013
+ reports `spentUsd: null` — never a `0` that reads as exact (F6). On a RESTARTED try,
1014
+ `rounds` folds both attempts exactly as `spentUsd` and `wallMs` do (the row states one span,
1015
+ not a count from one leg against money from two), and the fold is declared on `try-start` as
1016
+ `priorRounds`; a leg whose turns cannot be counted leaves it `null` and the row keeps the
1017
+ unknown rather than reporting the half it can see.
1018
+
1019
+ > **The selection calls sit OUTSIDE the per-try caps**, by construction: they happen
1020
+ > before a try starts, so no try's ledger can hold them. They are small (one short prompt
1021
+ > each, no tools) and they ARE metered — every one lands in `selection[].costUsd` and in
1022
+ > the total — but a reader budgeting `perTryBudgetUsd × (bridgeTries + 1)` should know
1023
+ > that is the caps' sum, not the run's ceiling. Named here rather than folded in: an
1024
+ > advertised number the run does not enforce is the thing this repo does not ship.
1025
+
1026
+ > **F45, and why there is no threshold here.** A try's budget must fund **the attempt PLUS
1027
+ > its close** — a cap that dies mid-grading produces an unreadable row, and an unreadable
1028
+ > row is a casualty, not evidence. Nothing in this process can know what a close costs (it
1029
+ > is your command, at your price), so **no threshold is invented** — threshold-setting is
1030
+ > arbiter territory, set from a measured base rate, never fitted to the sample in hand.
1031
+ > What the module does instead is make the bind VISIBLE: `closeReached: false` next to
1032
+ > `capBound: true` on a row IS the diagnosis. Size the envelope with that in hand.
1033
+
1034
+ Fallthrough to the next bridge, and to cold, is automatic **only because the envelope
1035
+ pre-authorized it**. Nothing else is: a refused pin (`selection-refused`), an unusable
1036
+ selection answer (`selection-red`), a missing registry (`registry-red`) and an
1037
+ envelope that will not compose (`envelope-red`) all STOP with a decision-ready escalation
1038
+ and hand the call back to you. So do the three answers **no further try could change** —
1039
+ `unapproved-spec` (a tightened envelope makes a new spec version, and it is signed, not
1040
+ inherited: the decision hands you the exact hash), `job-red`, and `smoke-red` — which end
1041
+ the run on the try that hit them rather than reproducing themselves down the whole
1042
+ envelope. Every category this emits is in the ledger's executable excluded-set, so none of
1043
+ them lands as an unclassified library bug.
1044
+
1045
+ `scripts/run-reuse.mjs` is the reference operator runner: `--job`, `--registry`, and the
1046
+ three envelope numbers `--budget` / `--wall` / `--tries`, plus optional `--pin` /
1047
+ `--force-cold`, with the approval gate on `reuseSpecHash` (all three numbers — a hash
1048
+ signed for a different `--tries` is refused, and says so), the F67 outside watchdog
1049
+ (sized for the SUM of the tries, not one), the seed refusal, the gate-audit relocation and
1050
+ the secrets scan.
1051
+
1052
+ #### Resuming a killed run (module C)
1053
+
1054
+ A reuse run is up to `tries + 1` full jobs long, so a kill mid-run is a real event and it
1055
+ must not cost the whole envelope. **`readResume(events, {deathAt?, direct?, resumableOutcomes?})`**
1056
+ reads the dead run's own spine back into the state a resume continues from: `{ started,
1057
+ approvalHash, completed[], tried[], restart (with its step-level `seed` and its close
1058
+ `grades`), r1Missing, spentUsd,
1059
+ spendComplete, carrySpentUsd, carrySpendComplete, ended, greened, … }`. Hand that object to
1060
+ **`runReuse({…, resume})`** and the run picks up where it stopped.
1061
+
1062
+ Two options widen WHICH spines it reads, both OFF by default so the reuse loop's own
1063
+ semantics are byte-unchanged (PRD v1.46 §3):
1064
+
1065
+ - **`direct: true`** reads a plain `runJob` spine — no envelope, no try windows — as ONE
1066
+ implicit try opened at `job-start`, through the same window machinery rather than a second
1067
+ reader. Without it such a spine is honestly refused (`started: false`), which is what a
1068
+ reuse runner needs. `job-start` carries the DECLARED fold (`priorSpentUsd` with its
1069
+ `priorSpendComplete`, `priorWallMs`, and `priorReplans` with its `priorReplanGrantUsed`, each
1070
+ present only when there is one) exactly as
1071
+ `try-start` does, so a chain of resumes adds only each attempt's own new rounds instead of
1072
+ re-deriving and double-billing. **An inherited FLOOR is one** even when the money it qualifies
1073
+ is `$0`: a leg whose every round came back unpriced folds nothing countable and is still not
1074
+ exact, so the declaration is emitted to carry that unknown forward rather than letting it read
1075
+ as an exact zero (F6).
1076
+ - **`resumableOutcomes: ['cap-halt', 'wall-halt']`** reclassifies a landed `job-end` whose
1077
+ outcome is a GOVERNANCE HALT as a restart rather than a completed row. "A landed job-end is
1078
+ complete" is true of a VERDICT; a halt means an operator-owned allowance ran out with the
1079
+ work on disk and the plan on the spine. A green or any red stays non-resumable under every
1080
+ setting — a verdict already rendered is never re-bought.
1081
+
1082
+ `scripts/run-u.mjs --resume <runid|path>` is the operator-side consumer: it skips the patient
1083
+ reset (`resumeTreeGate` instead — a dirty tree is what a resume expects; only a moved HEAD
1084
+ stops it), folds the halted run's money and wall in, re-enters at the checkpoint, and arms the
1085
+ outside watchdog on the REMAINING wall. The top-up itself stays a spec edit the human signs.
1086
+ **A zero wall remainder REFUSES the launch** (exit 2, before the key and before the patient):
1087
+ W-2 says a run out of time keeps the grade it has and stops, so a resume with no time to start
1088
+ anything in is a decision, not an unbounded launch — and launching it used to hand the outside
1089
+ watchdog `--wall-ms 0`, which it defaulted to `null` and armed no deadline at all under a banner
1090
+ still advertising one. The refusal names the lever: raise `maxWallMs`, which moves the spec hash
1091
+ and is re-signed.
1092
+
1093
+ - **Completed tries are not re-run.** They come back as rows (marked `inherited`) and
1094
+ their bridges stay excluded from every later selection. A try whose `job-end` landed —
1095
+ the close judged it — counts as completed even if the kill beat its `try-end`; paying
1096
+ again for a verdict already rendered is exactly what resume exists to avoid. If the
1097
+ kill also beat the registry write, that row is LOST and named (`r1Missing`); nothing is
1098
+ invented in its place.
1099
+ - **A mid-flight try RESTARTS** against the same workflow, with **no second selection
1100
+ call** (that pick was made and paid for), and it is not counted as consumed.
1101
+ - **It restarts at the STEP it died on, not at its beginning.** hamr's ruling: *"start
1102
+ last step instead from the beginning, why would i want to waste more money on something
1103
+ i already started"*. `restart.seed` is the finest checkpoint the dead spine can PROVE —
1104
+ `{phase, plan, completedSteps[{id, seq, by}], planSeq}` — and it rides into `runJob`
1105
+ (and on to `runPlan`) as `resumeSeed`:
1106
+ - **scout: skipped** (`scout-skipped`, never silence). Its survey is not on the spine,
1107
+ so the one consequence is stated rather than discovered: a replan after a resume
1108
+ drafts from an empty survey plus its failure brief.
1109
+ - **plan: reloaded, not re-drafted** — from the LAST `plan-accepted` in the window (a
1110
+ replan emits its own, and the post-replan plan is the one that was running). It is
1111
+ **re-validated** against the spec signed NOW; a plan that no longer validates is
1112
+ `plan-red` by name (`resume-plan-red`) with nothing run. It is then re-emitted as this
1113
+ leg's own `plan-accepted {phase:'resume'}`, because R1 mints a bridge version from the
1114
+ plan AS EXECUTED read back off the spine.
1115
+ - **completed steps: skipped**, in prefix order by id, each with its own `step-skipped`
1116
+ record naming the event that proves it (`step-end{green}` — or an earlier
1117
+ `step-skipped`, so a resume of a resume does not re-pay for what the first one saved).
1118
+ Never a fake step-start/step-end pair; `plan-executed` records them as `skipped`.
1119
+ - **`phase: 'close'`** (an `outer-close` in the window) means every step is done: they
1120
+ are all skipped and the run goes straight to the close, which re-runs for no tokens.
1121
+ - A window with **no `plan-accepted`** (a scout/draft death) has no seed and simply
1122
+ restarts the try — nothing paid is re-payable there. If that window is itself an
1123
+ abandoned resume, the checkpoint is read from the newest abandoned window **of the
1124
+ same try** (measured on the real killed run: leg 3 died in its redraft, and reading
1125
+ only its own window would have discarded a checkpoint leg 2 paid $8.18 to reach).
1126
+ - The worker's conversation is **not** replayed — a transcript is not a checkpoint, and
1127
+ every attempt is fresh by the loop's own design.
1128
+ - **Its TREND baselines come with it.** `restart.grades` is the dead leg's close grades in
1129
+ order (`[{stage, value}]` — counts and stage names only, never a gap byte), and it rides
1130
+ into `runJob`/`runPlan` as `resumeGrades` beside `resumeSeed` — on the LIBRARY path
1131
+ (`runReuse` hands it to the restarted try itself) as well as through `run-u --resume`;
1132
+ `resume-start`'s `restart` states `gradesInherited`, a count, and only when there is one.
1133
+ It seeds the run trend's
1134
+ baselines so the resumed leg's `money-halt`/`wall-halt` readouts judge the CHAIN: a leg
1135
+ that re-grades an unchanged tree is flat on its own evidence while the RUN — what the
1136
+ top-up decision is actually about — may have been converging when the money ran out. See
1137
+ the trend section above for what the seed does and, deliberately, does not touch.
1138
+ - **Its REPLAN LEDGER comes with it too — and this one is a BOUND, not a readout.**
1139
+ `restart.replans` / `restart.replanGrantUsed` ride into `runJob`/`runPlan` as **`resumeReplans`**
1140
+ (`{count, grantUsed} | null`), seeding the replan ceiling instead of leaving it reborn per call.
1141
+ Without it a kill refilled an allowance PRD v1.12 makes the RUN's — measured, a leg that spent
1142
+ both replans and step-redded handed its checkpoint to a second leg that replanned twice more.
1143
+ **It deliberately does NOT use `resumeGrades`' mechanism.** The grade seed reads ONE spine, so a
1144
+ resume of a resume inherits the previous leg's grades and not the whole chain; that shortening is
1145
+ fail-safe for a READOUT (it can only under-claim a direction) and is the DANGEROUS direction for
1146
+ a ceiling, where an under-claimed ledger is a refilled allowance and every kill buys one. So this
1147
+ seed follows the MONEY fold: each leg declares `priorReplans`/`priorReplanGrantUsed` on its own
1148
+ spine and the next reader adds only its own window, so leg 3 inherits the whole chain. It is also
1149
+ not the same class as the trend's ITERATIONS, which `src/trend.js` refuses to seed on purpose: an
1150
+ attempt allowance is a LEG bound bought with the leg's own money, while a replan ceiling bounds
1151
+ how many times the WORKFLOW may be redrawn, which is the run's span. Belted like every declared
1152
+ fold — a garbage, non-finite or negative `count` reads as `0` rather than widening the ceiling.
1153
+ Null/omitted is the cold path. The disarm control is pinned: a resumed leg with an UNSPENT
1154
+ ceiling still replans, so the bound is bounded, not switched off.
1155
+ - **Under the REMAINDER, never a fresh allotment.** The killed attempt's spend and wall
1156
+ FOLD IN (`runJob`'s `priorSpentUsd`/`priorSpendComplete`/`priorWallMs`, `createClock`'s
1157
+ `priorElapsedMs`), so
1158
+ the restart gets what is left of the signed per-try numbers. The caps are never
1159
+ rewritten — they are in the spec hash. An unfundable remainder is an honest `cap-halt`
1160
+ (or `wall-halt`, below one close timeout: a try that cannot fund its close produces an
1161
+ unreadable row) with **nothing launched**. Topping up is a new envelope, and a new
1162
+ envelope is a new signature.
1163
+ - **Money stays honest end to end.** `carrySpentUsd` (completed tries + selection calls)
1164
+ seeds the resumed ledger; the mid-try fold is deliberately NOT in it, because it comes
1165
+ back inside the restarted try's own terminal — counting it in both places would bill it
1166
+ twice. A try row's `spentUsd` is therefore the try's WHOLE spend across both attempts,
1167
+ and a declared floor stays a floor — through ALL four of `runJob`'s floor causes: a
1168
+ landed resumable `job-end` whose own `spendComplete` is `false` (a stall, or a call cut
1169
+ mid-flight) floors the restart fold exactly like a declared prior floor or an unpriced
1170
+ round does (F83's laundering finding, fixed the same day; the killed-mid-flight restart
1171
+ with no `job-end` at all is control-pinned unchanged).
1172
+ - **Refusals** (`resume-red`, in the ledger's excluded set): a seed signed under a
1173
+ DIFFERENT envelope than the one being enforced (both hashes are named), and a restart
1174
+ whose workflow has left the registry (substituting another would override a decision
1175
+ the runner did not make).
1176
+ - **The patient is CONTINUED, not reset** — `resumeTreeGate({head, seed, dirty})`: a dirty
1177
+ tree is the dead tries' real progress; only HEAD moving off the seed (a commit or rebase
1178
+ under the dead run) stops the resume.
1179
+ - **One run, one log:** the resumed process appends to the dead spine and passes
1180
+ `makeSpine(file, {startSeq})` so `seq` stays monotonic, keeps the dead run's runid (the
1181
+ R1 rows point at a spine FILE), and leaves the cumulative `gate-audit.jsonl` in the
1182
+ workdir to be relocated once at the end as usual.
1183
+ - **Known limit (F6):** a call killed BEFORE it returned wrote no event, so money it may
1184
+ already have been billed for is not on the spine and cannot be folded — the same limit
1185
+ every guard here has (a hung `generate()` leaves zero trace).
1186
+
1187
+ `scripts/run-reuse.mjs --resume <runid|spine path>` is the operator seam: it refuses a
1188
+ spine that is missing, corrupt mid-file, already terminal, or whose process is STILL
1189
+ ALIVE (pids from the run's `runner-start` record and the watchdog report, discriminated
1190
+ by `/proc/<pid>/cmdline`, because a live pid can be a recycled stranger), prints the whole
1191
+ reconstruction as a preview before you sign — including WHERE it picks up ("step 2 of 3
1192
+ \"verify-strict-typecheck\" — 1 step already finished and SKIPPED, not re-paid"), so the
1193
+ signature covers an attempt whose real cost you can see — archives the killed run's
1194
+ watchdog record so the readout cannot mistake it for this run's (defensively: a truncated
1195
+ report is not evidence of anything, and it can no longer abort a signed resume), and sizes the
1196
+ outside watchdog for the work that is actually LEFT. **Nothing left to run REFUSES the launch**
1197
+ (exit 2), with a lever per cause, because they are different asks: `NO WALL LEFT` when the
1198
+ restarted try burned its whole per-try wall (raise `--wall`), `NOTHING TO ARM` when every
1199
+ authorized attempt has already run (raise `--tries`, or read the run as it stands). Both are in
1200
+ the approval hash, so either is a new signature. Two smaller refusals in the same family: a flag
1201
+ given no value (`--tries` with nothing after it) stops rather than reading `Number('') === 0`
1202
+ and reshaping the run being signed, and `runner-start`'s recorded `argv` is redacted at the
1203
+ write site — the spine is append-only, so a scan after the bytes land is too late.
1204
+
590
1205
  ### `updateLedger({ ledgerFile, spineFiles })` → `{ appended, fold }` — `src/ledger.js`
591
1206
 
592
1207
  The upstream ledger: spines fold into ONE append-only incident JSONL both the consumer
@@ -599,9 +1214,13 @@ the same corpus), and `seq` continues monotonically across appends. Keys are
599
1214
  across runs dedupes and distinct bugs in one verb don't merge. Classes, worst-first
600
1215
  (`LEDGER_CLASSES`, frozen): `silent-degradation` (a failed `primitive-smoke` — the class
601
1216
  failures can't derive), `runtime-red`, `provider-red`, `pricing-red` (F6), possibly-dormant
602
- `capability-gap` (cap-halt + request-red in one spine), `broken-close` (consumer-attributed),
1217
+ `capability-gap` (cap-halt + request-red in one spine — one row per `(verb, lib)`, never per
1218
+ verb, so two territories asking for one verb string stay two rows rather than collapsing onto
1219
+ whichever territory was seen first), `broken-close` (consumer-attributed),
603
1220
  `request-red` (admission demand for a locked verb — keyed on the red's structured
604
- `verb` field, prose-quoted verb as legacy fallback), `retention-red`, `config-red`
1221
+ `verb` field, prose-quoted verb as legacy fallback; both classes take their `lib` from the
1222
+ red's own stamp, so neither seeds an upstream ask for a bareloop-catalogue refusal),
1223
+ `retention-red`, `config-red`
605
1224
  (drafting friction — attributed to bareloop's own schema/prompt). Deliberate exclusions
606
1225
  (`EXCLUDED_ESCALATIONS`, a runtime set — anything outside classified ∪ excluded is
607
1226
  counted as unmapped, never dropped): `cap-halt`/`wall-halt` (budget stories, money and
@@ -619,9 +1238,10 @@ panel reads the same file at N6.
619
1238
  ## Architecture
620
1239
 
621
1240
  Three layers. An **outer shell** (dumb, permanent): per-run budget cap via bareguard,
622
- retry cap, verdict collection, escalation routing — stateless across runs; nothing inside
1241
+ retry cap (a count on the close-fix loop, a shell-owned STRIKE ceiling on a plan step),
1242
+ verdict collection, escalation routing — stateless across runs; nothing inside
623
1243
  negotiates with it. An **emergent middle**: the AGENT-authored plan — bounded steps, each
624
- with its granted verbs, its round and attempt caps, its narrowed write scope and its
1244
+ with its granted verbs, its round cap, its narrowed write scope and its
625
1245
  form-checkable exits — schema-validated by `validatePlan` before tokens burn (the
626
1246
  operator-authored `steps[]` shape is deleted, PRD v1.32). A **floor**: append-only
627
1247
  JSONL spine (single source for every UI), litectx store per job, per-run ledger. Built on