bareloop 0.6.0 → 0.7.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.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,413 @@ All notable changes to bareloop are documented here. Format:
5
5
  [SemVer](https://semver.org/spec/v2.0.0.html). Pre-1.0: **minor** = a ladder rung or
6
6
  feature lands, **patch** = docs, fixes, scaffolding.
7
7
 
8
+ ## [0.7.0] — 2026-08-05
9
+
10
+ ### Changed
11
+ - **`ralph()` now REFUSES a call with no exhaustion rule** (public API). `capRuns` became
12
+ optional in the signature when the `ladder` governor arrived — *"required unless `ladder` is
13
+ supplied"* — and nothing enforced the "unless". With neither, `1 <= undefined` is false: the
14
+ middle ran **zero** times and the call still returned `escalated` after emitting `cap-halt`
15
+ and an escalation reading `undefined/undefined runs spent, close still red` — a governance
16
+ stop narrated for a loop that was never entered. A param guard at entry now throws, naming
17
+ both alternatives (either one repairs the call). `capRuns` must be a positive integer;
18
+ `0`, `2.5`, `'3'`, `null` and `NaN` are refused the same way. **No shipped path changes** —
19
+ both in-library callers (the step loop and the close-fix loop) pass a `ladder`.
20
+ - **ONE progress instrument for both governance halts.** The wall halt used to run its own
21
+ reader — byte equality of the last two close gaps, reporting `stalled` / `moving` / `unknown`
22
+ beside the money halt's `flat` / `converging` / `unknown` on the same run. Two readers for one
23
+ question is how the two come to disagree, and the byte reader could only ever report MOTION —
24
+ that something changed, never which way. Both halts now read the run's own `src/trend.js`
25
+ reader. Where a stage reported a comparable NUMBER it decides (so a wall halt on a converging
26
+ run now says `converging`, with the series, instead of "it moved"); where none did, the byte
27
+ comparison survives INSIDE `unknown` as a new `motion` field (`changed | unchanged | null`)
28
+ and as a clause in the `reading`, with the lever adapting (`unchanged` → revise the goal;
29
+ `changed` → read the last close output). **Never promoted to a direction and never mapped onto
30
+ `trend`** — "the close's output changed" is not "the run got better" (F6 in a trend's coat).
31
+ W-2's semantics are untouched: the grade already minted is kept, the three levers are the same
32
+ three, and the close is still never bounded by the wall. **Spine change:** the `wall-halt`
33
+ record's `trend` now carries the money halt's vocabulary and a sibling `motion` field; no
34
+ `src/` or `scripts/` reader consumed the old values (grepped).
35
+
36
+ ### Added
37
+ - **A MONEY halt is decision-ready, exactly as W-2 made the wall one** (PRD v1.46 §2). A run
38
+ cut by its budget KEEPS the verdict already minted and emits a new `money-halt` spine record
39
+ carrying `budgetUsd`, `remainingUsd`, the kept `verdict`/`stage`, a progress `trend`
40
+ (`converging | flat | unknown`) with the `reading` and `series` it judged, and three
41
+ `options`: top up and resume, revise the spec, abandon. Emitted at every site that cuts a run
42
+ on money — the step loop, the close-fix loop, and the scout/draft relay. **The library only
43
+ reports**: `budgetUsd` is in the spec hash, so a top-up is a spec edit a human signs, and
44
+ nothing in a run may widen its own budget (unchanged hard line).
45
+ - **The close TREND instrument** (`src/trend.js`, internal). Reads a run's own close grades as
46
+ a series **per stage**, compared only against that stage's own best so far. Accuracy law:
47
+ never across stages — merging a staged close's axes reads a real see-saw (suppressions
48
+ 12 → 5 while the type errors that scrub re-exposed went 2 → 0) as a regression. Never model
49
+ prose: the reading is the first number on the first red-marked line of the stage's output.
50
+ A stage that reports no number donates nothing — not a zero, not a strike, and the verdict
51
+ says `unknown` out loud (F6). Reaching a later stage than ever before counts as progress
52
+ (a staged close is first-red-wins). Known accepted limit, documented at the site: a
53
+ floor-shaped stage (`N tests executed, below the seed's M`) reads as not-improving rather
54
+ than converging — the fail-safe direction.
55
+ - **A resumed run's trend judges the WHOLE chain, not just the leg.** `readResume`'s `restart`
56
+ gains `grades`: the dead leg's close grades in order, as `[{stage, value}]` — counts and stage
57
+ names only, never a gap byte. `runJob`/`runPlan` take them as `resumeGrades` (beside
58
+ `resumeSeed`) and `createTrend` takes them as `seed`, folding them into the run trend's series
59
+ and per-stage bests. Without it a resumed leg restarts the trend at its own first close and can
60
+ report `flat` — *revise the goal* — on a run that was converging when its allowance ran out; a
61
+ leg that re-grades an unchanged tree is flat on its own evidence while the RUN is what the
62
+ top-up decision is about. The seed is history, not this leg's evidence, so it counts no
63
+ iteration, mints no strike, never sets `comparableEver` on its own (the blind-cap backstop
64
+ still governs a leg whose own readings never compare) and donates no stage position. The
65
+ close-fix loop's governor is deliberately **not** seeded — it answers the leg-local question
66
+ ("is this loop out of ideas"), and seeding it rendered "still progressing" inside the terminal
67
+ of a loop that had just struck out flat. Grades are read from the `ladder` records the live
68
+ governor already wrote (`governor: 'close-trend'`, verbatim, never re-parsed), from
69
+ `close-precheck`/`outer-close`, and — only on a spine predating that governor — from the
70
+ `close-verdict` records after the `fix-loop` marker, which is the same population and excludes
71
+ a STEP's failing exit. Known limit: one spine is read, so a resume of a resume inherits the
72
+ previous leg's chain and not the one before it. `scripts/run-u.mjs --resume` wires it and names
73
+ the inherited baselines in its preview.
74
+ - **`--resume` on `scripts/run-u.mjs`** (PRD v1.46 §3) — the third leg of the resume rulings.
75
+ It skips the patient reset (`resumeTreeGate` instead: a dirty tree is what a resume expects;
76
+ only a moved HEAD stops it), folds the halted run's money and wall in so the signed ceiling
77
+ cannot widen by being re-invoked, re-enters at the checkpoint the steps already earned, and
78
+ arms the outside watchdog on the REMAINING wall. `readResume` gains two options, both OFF by
79
+ default so the reuse loop is byte-unchanged: `direct` reads a plain `runJob` spine as one
80
+ implicit try, and `resumableOutcomes` reclassifies a landed `job-end` that is a GOVERNANCE
81
+ HALT (`cap-halt`/`wall-halt`) as a checkpoint rather than a graded row. Green and every red
82
+ stay non-resumable — a verdict already rendered is never re-bought. `job-start` now carries
83
+ the declared fold (`priorSpentUsd` with its `priorSpendComplete`, and `priorWallMs`) when
84
+ there is one, so a chain of resumes adds only each attempt's new rounds instead of
85
+ re-deriving and double-billing.
86
+ **Validated live and pinned by cycle tests (F83).** Live: a signed top-up resumed a real
87
+ money cap-halt at its close checkpoint — finished step skipped, no re-scout or re-draft,
88
+ patient continued, watchdog armed on the REMAINDER — and greened, with `job-end` stating
89
+ the folded total across both legs. Pinned deterministically by five `$0` scripted-round
90
+ tests covering the whole cycle: leg-1 halt → top-up → leg-2 green at the checkpoint (a
91
+ close-precheck red proving the green was earned, not inherited); a **no-top-up control**
92
+ where the same budget buys no second pass and leaves a byte-identical tree (one
93
+ round-boundary overshoot round, the documented behaviour of a cap that binds BETWEEN
94
+ rounds); an **unsolvable cycle** where a topped-up leg making no per-stage progress
95
+ strikes out `flat` with the wallet still largely unburned — the fix loop's strike rule
96
+ governs a resumed leg exactly as it governs a cold one; and an F6 floor surviving halt →
97
+ fold → green terminal.
98
+ - **Layer 3, the REUSE rung — the registry, the load gate, and the MECHANICAL START**
99
+ (design record `docs/plans/2026-08-01-layer-3-reuse-design.md`; execution probe green
100
+ before any of it was built). A green's plan is now an artifact the next run of the same
101
+ SHAPE can start from, instead of being discarded.
102
+ - **`src/bridges.js` — the registry (D1/D6).** A directory of plain JSON files at an
103
+ OPERATOR-supplied path (no database, no new dependency, no default location).
104
+ `mintBridge` / `appendGreen` / `appendRed` / `saveBridge` / `loadBridge` /
105
+ `loadRegistry` / `makeRegistry` / `registryExists` / `validateBridge` / `listingRow` /
106
+ `deriveStatus`, all exported. **Only a green writes the box (R1):** a red writes a
107
+ history row and never touches `versions`. **Status is DERIVED, never stored (D6):**
108
+ candidate = one green, proven = greens on two DISTINCT patients, a red on a proven
109
+ entry demotes it — and a CASUALTY is not a red. There is deliberately no probability
110
+ score. Costs and durations are a number or an EXPLICIT `null` with the key required
111
+ either way, so an unknown is said rather than omitted (F6).
112
+ - **`loadGate(bridge, job)` — D2 as SPLIT (2026-08-01 addendum).** Three shape checks at
113
+ the door — verdict type, close-stage kinds, verbs within the signed menu — and
114
+ **nothing about paths, scopes or targets**: those are instance-bound and expected to
115
+ red, which the pre-probe measured the drafter fixing unaided (3/3 legal after tweak),
116
+ while the gate as originally frozen would have refused every cross-patient recipe.
117
+ - **`bridge?` on `runJob` and `runPlan` — the mechanical start (D4).** With a bridge, the
118
+ gate runs before the clock, the close precheck and any token; on a pass the NEWEST
119
+ version's plan-as-executed becomes the drafter's STARTING DRAFT inside the ordinary
120
+ drafting prompt (`planPrompt` gained an optional `startingDraft` argument). The tweaked
121
+ plan then passes the SAME `validatePlan` every cold draft passes — **no second, looser
122
+ path exists for an inherited plan** — and the ONE-replan ceiling is untouched (D5): a
123
+ replan drafts from the run's own state and never re-injects the bridge.
124
+ - **New terminal `recipe-stale`** (`runJob`/`runPlan`): a bridge that fails the load gate
125
+ is refused at the door with `bridge-gate {outcome, name, reds}` on the spine, a
126
+ decision-ready escalation, and **zero spend**. Falling back to cold drafting is the
127
+ CALLER's decision, never an automatic silent fallback. Classified as a deliberate
128
+ ledger exclusion — the gate refusing a wrong-KIND recipe is the mechanism working, not
129
+ a library bug to file upstream.
130
+ - **New spine events:** `bridge-gate` (a refusal, with its reds) and `bridge-loaded`
131
+ (`{name, versions, runid}` — which version inherited).
132
+ - **`src/selection.js` — D3's display half.** `renderListing(registry)` renders the
133
+ operator/LLM-facing workflow listing (name, goal, status, greens/reds, the cost/time
134
+ band of the greens), deterministic by name and total on garbage; an unknown cost or
135
+ time renders `UNKNOWN` and a partial aggregate says how many it skipped, and registry
136
+ entries that could not be read are NAMED so the listing is never quietly shorter than
137
+ the directory. `selectionPrompt(listing, ask)` is the pure prompt text for the pick,
138
+ carrying D3's two standing rules: *"none matches"* is a first-class answer that means
139
+ drafting new, and a PINNED workflow may be refused only EXPLICITLY, with a reason. Both
140
+ are pure text — the selection call, the pin/shortlist/force-cold flow and the parse of
141
+ the answer belong to the caller.
142
+ - **The cold path is byte-identical without a bridge** (the F47 works-both-ways rule),
143
+ pinned by test: no new event fires and no starting draft appears anywhere in the prompt.
144
+ - **`src/reuse.js` — the D7 ENVELOPE and the REUSE RUNNER.** hamr's sentence made
145
+ executable: *"we ask user for cost/time and how many workflows to try before starting
146
+ new like $5 and 30 mins x2 then start anew if both red"*.
147
+ - **`validateEnvelope(input, { job? })`** — `{ perTryBudgetUsd, perTryWallMs,
148
+ bridgeTries }`, **all three required and explicit, no defaults** (a defaulted cap is
149
+ a silent second ceiling); `bridgeTries: 0` = force-cold. The envelope may only
150
+ **TIGHTEN** the signed spec — a per-try number above `job.budgetUsd`/`job.maxWallMs`
151
+ is `envelope-widens`, never a silent raise.
152
+ - **`resolveTrySpec(job, envelope)`** — the per-try spec every try runs, cold leg
153
+ included. Tightening moves the SPEC HASH, so a tightened run is a new spec VERSION
154
+ the operator signs; `runReuse` passes `approvals` straight through and cannot forge
155
+ one. An envelope equal to the spec's numbers is hash-identical.
156
+ - **`selectBridge({registry, job, ask, provider, pinned?, shortlist?, forceCold?,
157
+ exclude?})`** — ONE model call on the caller's drafter-tier provider (none is
158
+ constructed inside), parsed through the repo's one `extractArtifact` into a strict
159
+ `{choice, reason}`. `forceCold` and an empty candidate set skip the call for $0. A
160
+ name outside the listing is a red, never used. **A pin does not bypass the call**
161
+ (D3): it is stated in the prompt, and an answer naming anything else returns
162
+ `{refused: true}` — the substitute is NOT adopted and the decision goes back to the
163
+ operator. Cost metered and reported (F6).
164
+ - **`runReuse({job, approvals, registryDir, envelope, patient, workdir, provider,
165
+ emit, …})`** — selection → try → box → next try → cold. Selection re-reads the
166
+ registry per try with the tried names excluded; **a green ENDS the loop**; tries
167
+ exhausted falls through to a cold run under the same per-try numbers, whose green
168
+ mints a new bridge for the job slug (or appends if that name already holds greens —
169
+ clobbering a green is what R1 exists to prevent), and whose red writes nothing (the
170
+ entry bar is a green). Every non-green return is decision-ready. The three answers
171
+ **no further try could change** — `unapproved-spec` (a tightened envelope is a new
172
+ spec version, signed not inherited; the decision hands over the exact hash),
173
+ `job-red`, `smoke-red` — end the run where they happen instead of reproducing
174
+ themselves down the whole envelope. A cold green also REFUSES to mint over a file of
175
+ that name it could not read (`mint-collision`): an unreadable entry is not an absent
176
+ one, and whatever greens it held are gone once overwritten.
177
+ - **`REUSE_GRADED_RED`** — only `escalated` demotes: the terminal where the close
178
+ judged the tree, the fix loop spent its attempts with money still on the table, and
179
+ the close was still red. Every other non-green outcome is a CASUALTY recorded under
180
+ its own name (`cap-halt`/`wall-halt` governance, `provider-red`/`step-stalled`
181
+ transport, `close-red` the close FAULTING and rendering no judgment, `recipe-stale`
182
+ refused at the door, `plan-red`/`step-red:*`/`check-red` stopping before the close
183
+ ever judged). Full detail still lands on the history row.
184
+ - **F45 made visible instead of thresholded.** A try's budget must fund the attempt
185
+ PLUS its close, and nothing in-process can know what a close costs — so **no
186
+ threshold is invented** (threshold-setting is arbiter territory, from a measured
187
+ base rate). Instead every try row carries `spentUsd` vs `capUsd`, `wallMs` vs
188
+ `wallCapMs`, `capBound`, `wallBound` and **`closeReached`**: a cap that died before
189
+ any grading is a fact on the row, not an inference.
190
+ - **New spine events:** `reuse-start`, `selection-result`, `try-start`, `try-end`,
191
+ `bridge-write`, `reuse-end`, plus `envelope-red`. **New escalation categories**
192
+ `reuse-exhausted` / `selection-refused` / `selection-red` / `registry-red` /
193
+ `envelope-red`, all added to the ledger's executable excluded-set — the envelope and
194
+ the operator speaking, never a library failing (a real transport fault out of the
195
+ selection call still classifies as `provider-red`).
196
+ - **`selectionPrompt(listing, ask, pinned?)`** gained the optional pin argument, so the
197
+ pin sentence has ONE spelling rather than being assembled by each caller.
198
+ - **`scripts/run-reuse.mjs`** — the reference operator runner: `--job` / `--registry` /
199
+ `--budget` / `--wall` / `--tries` (+ `--pin` / `--force-cold`), approval gate on the
200
+ RESOLVED per-try hash, seed refusal (it never resets the patient for you), F67 outside
201
+ watchdog sized for the SUM of the tries, gate-audit relocation, secrets scan, and a
202
+ sleep-inhibitor note in the header (F72 — a suspend freezes every guard, including the
203
+ watchdog).
204
+ - **RESUME AFTER A KILL (module C).** A killed reuse run comes back losing as little as
205
+ possible — hamr's ruling: *"money, signature and checkpoint (starts from where it
206
+ stopped) if mid loop, restart that loop"*.
207
+ - **`readResume(events, {deathAt?})`** turns a dead run's OWN spine back into the
208
+ state a resume continues from: which tries completed (never re-run, bridges still
209
+ spoken for), whether a try was mid-flight (a `try-start` with no `try-end` — never
210
+ graded, so never consumed), what that attempt already spent in money and wall, and
211
+ which signed envelope the spine belongs to. Spend is summed from `worker-round`
212
+ ONLY — the same event `runJob`'s ledger accounts — so a selection call's cost is
213
+ never attributed to a worker (F45); a restarted try DECLARES its inherited fold on
214
+ its own `try-start`, so a resume of a resume folds once rather than double-billing
215
+ an abandoned attempt. A try whose `job-end` landed is COMPLETE, not restarted, and
216
+ a registry row lost to the kill is named (`r1Missing`), never re-derived.
217
+ - **The remainder, never a fresh allotment.** `runJob` gained `priorSpentUsd` /
218
+ `priorSpendComplete` / `priorWallMs` and `createClock` gained `priorElapsedMs`: the
219
+ killed attempt's spend and time FOLD IN, so the restart runs under what is left of the
220
+ SIGNED per-try numbers. The caps themselves are never rewritten — they are in the spec
221
+ hash, and editing them would need a signature nobody typed. An unfundable remainder caps
222
+ honestly (`cap-halt`, or `wall-halt` below one close timeout) having launched
223
+ nothing. **A floor stays a floor across the resume:** if any round inside the dead
224
+ attempt came back unpriced, `priorSpendComplete: false` rides one-way onto every
225
+ `job-end` this run emits, so a resumed attempt states a floor instead of an
226
+ exact-looking total (F6 — it was previously computed and recorded on `try-start` but
227
+ never reached the component whose terminal says so). The restart fold reads ALL four
228
+ of `runJob`'s floor causes: a landed resumable `job-end` carrying
229
+ `spendComplete: false` (a stall, or a call cut mid-flight) floors the fold exactly
230
+ like a declared prior floor or an unpriced round (F83's laundering finding, fixed;
231
+ the no-`job-end` killed-mid-flight restart is control-pinned unchanged).
232
+ - **`runReuse({… resume})`** seeds the completed tries, restarts the mid-flight one
233
+ against the SAME workflow with no second selection call, and carries on into
234
+ whatever the envelope still authorizes. It refuses (`resume-red`, ledger-excluded)
235
+ a seed signed under a different envelope, or a restart whose workflow has left the
236
+ registry. New spine event `resume-start`; `try-start` gains the declared fold.
237
+ - **`resumeTreeGate({head, seed, dirty})`** — a resumed patient is CONTINUED, never
238
+ reset: the dead tries' edits are real progress. Only HEAD moving off the seed (a
239
+ commit or rebase under the dead run — operator intervention) stops it.
240
+ - **`makeSpine(file, {startSeq})`** so a resumed process continues one append-only
241
+ log with `seq` still monotonic, and **`scripts/run-reuse.mjs --resume <runid|path>`**:
242
+ it refuses a spine that is missing, corrupt mid-file, already terminal, or whose
243
+ process is STILL ALIVE (pid from the run's new `runner-start` record and the
244
+ watchdog report, discriminated by `/proc/<pid>/cmdline` because pids are recycled),
245
+ prints both hashes on an envelope mismatch, previews the whole reconstruction
246
+ before you sign, archives the killed run's watchdog record so it cannot be read as
247
+ this run's, and sizes the watchdog for the REMAINING work.
248
+
249
+ ### Changed
250
+ - **`capRuns` retires as the CLOSE-FIX loop's governor** (PRD v1.46 §4). That loop now ends on
251
+ the same 2-strike no-progress rule the step ladder uses, read off the close's own per-stage
252
+ numbers rather than repeats and writes. Validated by a $0 replay over every archived fix
253
+ loop before the build: 0 greens harmed (all three historical fix-loop greens converted in
254
+ ≤ 2 verdicts) and 1 real waste case caught (dead flat at 2 errors for 7 consecutive fix
255
+ verdicts until the wall killed it). `capRuns` survives ONLY as the bound for a close whose
256
+ output carries no number at all — a governor that cannot see the variable must not be the
257
+ governor — and lifts the moment a stage reports a comparable number. Money and the wall keep
258
+ every bit of their authority. The exhaustion terminal is unchanged in both category
259
+ (`cap-halt`) and outcome (`escalated`); `ralph`'s `ladder` option is now an INTERFACE two
260
+ governors implement, with an optional `terminal()` that overrides the exhaustion prose only
261
+ (the step ladder's copy offers a replan and there is no planner at the close). Every `ladder`
262
+ spine record now names its `governor` (`step-ladder` | `close-trend`). The wall-halt readout
263
+ no longer quotes `capRuns` as a denominator, because it no longer governs.
264
+ - **A plan step is bounded by PROGRESS, not by a count** (`src/ladder.js`; F77–F79). The step
265
+ loop's fixed iteration cap is replaced by a strike ladder: a *strike* is a red iteration that
266
+ repeats an already-seen gap (matched against a seen-set for the whole step, never just the
267
+ last one) or made no gate-audit writes (the F32 record-count instrument — never `git status`,
268
+ never a tree diff); strikes are sticky and `strikeLimit` of them (new shell option on
269
+ `runJob`/`runPlan`, **default 2**) ends the step. Gaps are normalized for comparison only —
270
+ timing bytes dropped, counts and file names kept — so a shrinking error count reads as
271
+ progress. The wallet, the wall, the variance meter and the stall fuse keep every bit of their
272
+ authority; the exhaustion terminal is the same `cap-halt`. Measured motivation: two
273
+ calibrations died `step-red` while still converging with money and wall unspent; a $0 replay
274
+ over 110 archived step ladders showed every converging red continuing and thrash ending
275
+ same-or-one-earlier. **`capRuns` now bounds the CLOSE-FIX loop only.** New per-iteration spine
276
+ record `ladder`; exhaustion records carry `strikes`/`strikeLimit`/`distinctGaps`.
277
+ - **The replan brief names the MECHANISM.** An exhausted step now tells the redrafting planner
278
+ which shape ended it (converging-cut / stalled-no-write / repeated-gap, with iteration numbers
279
+ as evidence), its gap trajectory, and how much money and wall the stop left unspent (time
280
+ reported UNBOUNDED when no wall was set, never `0`) — replacing *"it ran N attempts and its
281
+ exits were still red"*, which named no mechanism and read identically for opposite failures.
282
+ - **Layer R's VERBATIM ratchet stage is retired on the STEP path** (hamr's ruling): fixation is
283
+ a repeat, so a fixated step strikes out before the fourth iteration can open. The stage stays
284
+ reachable in the close-fix loop and fully unit-covered; Layer R still ships OFF by default.
285
+
286
+ ### Deprecated
287
+ - **`steps[].attempts` is retired and INERT.** It is no longer offered in the drafting prompt
288
+ and the runner ignores it. It is still ACCEPTED by `validatePlan` — stored bridge plans carry
289
+ it, and a plan minted under one runner's number must not red under another's — with the shape
290
+ still checked and the old `<= capRuns` ceiling dropped. It was TIGHTEN-only, which made the
291
+ correct heal for a converging step inexpressible. `validatePlan`'s `capRuns?` option is
292
+ removed with the ceiling it fed (it existed only to bound `attempts`; an extra property is
293
+ ignored, so no direct caller breaks).
294
+
295
+ ### Changed (the close-stage AXIS SPLIT — hamr: "#1 fix")
296
+ - **One POPULATION per close stage, so unlike numbers can never share a trend series.** The
297
+ trend reads one bucket per stage NAME, and a stage name is not an axis: `typecheck` red
298
+ either `N error(s) in <scope>` (the in-scope faults) or `the target files are clean but M
299
+ strict error(s) exist outside them` — a different population, reached only once the first is
300
+ zero — and `suite-green` mixed a failure count with an executed-count floor. A run crossing
301
+ either seam donated two genres to one series, so a 29 → 4 across it read **converging** and
302
+ would have recommended a top-up on work that had only swapped which wall it was behind.
303
+ Every mixed stage in the six `u-*` closes is now split: **`typecheck-outside`** carries the
304
+ outside-scope population immediately after `typecheck`, and **`tests-kept`** carries the
305
+ executed-count floor immediately before `suite-green`. Each new stage sits exactly where its
306
+ branch already ran, so the gate sequence is byte-order identical to the old single-stage
307
+ walk and first-red-wins is unchanged; `litectx`/`spawner` `typecheck` was verified
308
+ single-population and left alone. `src/trend.js`'s KNOWN LIMIT is rewritten as the historical
309
+ record plus **the law for close authors** — the remedy for a mixed stage is that close's stage
310
+ list, never a detector taught to tell two prose shapes apart (the F49 precedent).
311
+ - **All six `u-*` spec hashes flip**, by construction: a close's stage names are in the
312
+ signed spec, so the runner refuses every one of them until it is re-signed. That is
313
+ refuse-until-re-signed working, not a migration to smooth over.
314
+ - **Stored bridges whose `closeStageNames` predate the change refuse to load** — the load
315
+ gate's close-stage check is the doctrine that a changed close is a different KIND of job.
316
+ `aurora-u-spawner` and `litectx-u` are in that state now and are re-minted by a green under
317
+ the new close, never edited into agreement.
318
+ - **The F28 trim announcement is restored** where the split surfaced its loss: the
319
+ outside-scope branch pre-sliced its own echo, silently suppressing the "trimmed" notice a
320
+ gap bound must announce (a 341-line elide is visible again).
321
+ - **New `tests/close-stages.test.js` (20 tests)**, including a can-it-fail pre-flight against
322
+ the pre-split tree, spec↔script stage-list agreement over spawned real closes, and the
323
+ 29 → 4 repro reading as two stages with a same-stage contrast proving the test can fail.
324
+ Live `$0` validation: pulselog green across all four split stages, baremobile
325
+ `typecheck-outside` RED at 381 reading correctly as its own series. **1007 tests.**
326
+
327
+ ### Fixed (whole-branch review, 2026-08-05 — 4 opus finders over `main...layer-3-reuse`; F84)
328
+
329
+ *Every confirmed finding fixed, each verified against source before it was believed; no fix
330
+ forced, and three finder claims corrected in detail during the fixing.*
331
+
332
+ - **CRITICAL — the close trend's BLIND CAP was a one-way latch.** `capRuns` survives as the
333
+ fallback for a close whose output carries no comparable number, and it was armed by
334
+ "has this leg ever compared anything" — which a bare STAGE ADVANCE satisfies, on
335
+ essentially every run that does any work at all. One advance therefore disarmed the
336
+ fallback **permanently**: a later run of numberless reds (the shipped aurora TESTGEN
337
+ `verdict` stage is exactly that shape) accrued neither a strike nor a blind tick and was
338
+ bounded by money and the wall alone, where the retired count would have stopped it.
339
+ Reproduced by execution before the change — 27 iterations, no terminal. The cap now bounds
340
+ the **consecutive uncomparable streak**: it lifts the moment the instrument can read and
341
+ **re-arms the moment it cannot**, binding a recovered run never and a blind-from-birth run
342
+ on exactly the iteration it always did (pinned byte-stable by test, at every cap). The
343
+ `report()` `blind` flag follows the same question — a live streak IS blindness, so the
344
+ terminal no longer offers "0/2 strikes" as the reason a loop it did not strike out was
345
+ stopped.
346
+ - **`priorFloor` no longer requires money to have been spent.** `runJob` derived it as
347
+ `spentUsd > 0 && priorSpendComplete === false`, so a resumed leg inheriting a fold of **$0
348
+ that was not exact** — every round of the dead attempt unpriced — came back reading as an
349
+ exact zero. A floor OF zero is still a floor (F6). Read off the flag alone now, and the
350
+ onward `job-start` declaration is emitted for that case too (`spentUsd > 0 || priorFloor`),
351
+ which is the only field that can carry the unknown forward.
352
+ - **A reuse RESUME's halt readout now spans the chain.** `restart.grades` was computed by
353
+ `readResume`, declared on the record, and then handed to nobody: only `scripts/run-u.mjs`
354
+ passed `resumeGrades` on. `runReuse`'s own per-try execution now threads it into `runJob`,
355
+ so a restarted leg's `money-halt`/`wall-halt` trend judges the RUN rather than restarting at
356
+ this leg's first close and reporting `flat` — *revise the goal* — on a run that was
357
+ converging when its allowance ran out. `resume-start`'s `restart` states
358
+ `gradesInherited` (a COUNT; the seam carries stage names and numbers, never a close byte),
359
+ and only when there is one — the cold path is unchanged.
360
+ - **The `money-halt` record carries `spendComplete`.** `remainingUsd` is `budget − spent`, so
361
+ a run whose spend is a FLOOR has a remaining that is a CEILING; quoting it to four decimals
362
+ read as the number. `runPlan` takes the ledger's own `spendComplete` (one spelling of the
363
+ four causes, never a second arithmetic) and states it beside the figure — the same duty
364
+ `wall-halt` has carried since W-2.
365
+ - **A try row's ROUNDS fold across a resume, like its money and its wall.** A restarted try
366
+ reported only the restart's turns against a span its `$` already covered both legs of.
367
+ `try-start` declares `priorRounds` (including when it is `null`: absence means a spine older
368
+ than the field, an explicit null is a predecessor that could not count its own turns — and
369
+ an unfoldable count stays `null` rather than becoming the half this leg can see, F6). The
370
+ inherited-graded row's `wallMs` is likewise measured to the try's **own terminal** now, not
371
+ to a kill that may have landed minutes later.
372
+ - **The replan brief no longer calls an IDLE strike-out "converging".** The sentence was gated
373
+ on repeats alone, so a step that wrote nothing in any iteration was handed *"no file was
374
+ written in iteration(s) 1, 2"* and *"the step was converging"* in the same paragraph — to
375
+ the one channel the redrafting planner adapts to. Both signals gate it now; a moving exit
376
+ output on a step that wrote nothing is a close re-running, not work converging.
377
+ - **`planPrompt`'s JSDoc was detached from `planPrompt`.** An intervening helper landed between
378
+ the block and the function, so every parameter shipped as `any` in the generated `.d.ts`.
379
+ Reattached; no behaviour change, and the types are the contract.
380
+ - **W-2 on the LAUNCH side: a zero wall remainder now REFUSES, loudly.** `--wall-ms 0` reached
381
+ `scripts/u-watchdog.mjs`, which defaulted a non-positive value to `null` and armed **no
382
+ deadline at all** while the runner's own banner still advertised one — a guard reading zero
383
+ and saying nothing. The watchdog now refuses any PRESENT-but-unreadable numeric flag
384
+ (`0`, negative, garbage) with exit 2, while an OMITTED `--wall-ms` stays the legal unbounded
385
+ choice with the stale trigger armed. Both runners refuse above the spawn, before the key and
386
+ before the patient: `scripts/run-u.mjs` on an exhausted resume remainder, `scripts/run-reuse.mjs`
387
+ on `plannedWallMs <= 0`. **Two zeros, two levers** — a restart whose wall is burned wants
388
+ `--wall` (NO WALL LEFT), a run whose authorized attempts have all run wants `--tries` or
389
+ nothing (NOTHING TO ARM) — because naming the wrong lever sends the operator to re-sign a
390
+ number that buys no attempt. Both are in the approval hash, so either is a new signature.
391
+ - **`scripts/run-reuse.mjs` hardening.** A corrupt/truncated watchdog report can no longer abort
392
+ a signed resume at the last gate (it is parsed once, defensively, and reused; the archive
393
+ suffix falls back to the ARCHIVE moment so a second unreadable report cannot overwrite the
394
+ first). `runner-start`'s `argv` is redacted **at the write site** with the shipped
395
+ `redactSecrets` inventory — an append-only log that captures a key captures it forever, so an
396
+ after-the-fact whole-file scan is too late. A trailing value-flag (`--tries` with nothing
397
+ after it) now refuses instead of reading `Number('') === 0` and silently reshaping the run
398
+ being signed. The header's deliberate-differences list gains the fourth: the end-of-run leak
399
+ scan is a READOUT here, not a gate, because `runReuse` writes registry rows mid-run.
400
+ - **Archived runners annotated** (`reuse-preprobe`, `run-calibration-testgen`, `run-poc-layer2`,
401
+ `run-probe-testgen`): their `CAP_RUNS` comments described the retired drafting-prompt
402
+ interpolation, not the close-fix loop's blind fallback it actually is now.
403
+
404
+ - **Tests: 987 total** (from 959). The mutation-proven hole: a widening of `REUSE_GRADED_RED` — the
405
+ set whose members DEMOTE a proven bridge — survived the entire suite, so the demotion table
406
+ is now pinned outcome by outcome (`escalated` demotes; `step-red`/`cap-halt`/`wall-halt`/
407
+ `provider-red`/`close-red`/`step-stalled`/`pricing-red` are casualties and proven stays
408
+ proven). Plus the blind-cap backstop battery (blind-from-birth pin, streak-not-birth,
409
+ never-fires-while-reading, recovery resets), the launch-side refusals at all three sites,
410
+ the resume grade/round folds with their absent-baseline controls, the money-halt floor
411
+ readout, and a first battery for `scripts/migrate-bridge-patient-slugs.mjs` (dry-run
412
+ byte-identity, whole-file refusal over half-migration, name/filename disagreement — 4
413
+ mutants killed).
414
+
8
415
  ## [0.6.0] — 2026-07-31
9
416
 
10
417
  ### Added
package/README.md CHANGED
@@ -25,15 +25,32 @@ inheritance with ledger-counted attribution.
25
25
  The pitch in one line: **workflows that earn their own design, with receipts** — every
26
26
  inherited rule carries the green that minted it and the contrast that attributed it.
27
27
 
28
+ **What you keep is the workflow, not the session.** An agent figures your recurring,
29
+ verifiable job out once; what survives is a verified, self-healing workflow you can run
30
+ again — with bounded spend under an envelope *you* sign, deterministic verification you
31
+ don't author, and cost and time reported honestly (an unknown is reported unknown, never
32
+ rounded to zero). *Direction, not yet shipped:* a local workbench where you experiment with
33
+ a job, graduate a workflow once it greens, and **export** it — signed spec, workflow, close
34
+ scripts and a thin runner that depends on bareloop, so the whole self-healing library
35
+ (bare-agent · bareguard · litectx) comes along as npm dependencies. That bundle runs
36
+ headless as an enclosed CLI and asks you the same signing questions at the prompt that the
37
+ workbench would. It is a dependency, never generated standalone code: the gate travels with
38
+ it and never disappears.
39
+
28
40
  > **Status: Layer 2 — accepted.** The agent now authors its own workflow: `runJob()`
29
41
  > takes a signed job spec, surveys the repo, **drafts a validated plan of bounded steps**
30
42
  > (each with an operator-signed self-check it may reference but never author), and runs it
31
43
  > under an un-gameable outer close and one budget it can only tighten. On the real-model
32
44
  > acceptance battery (F47) the emergent flow converted a job the same worker failed 4/4
33
45
  > before — 3/3 runs, clearing the owned bar every time, composing its own check exits. The
34
- > within-run ratchet (Layer R) ships armed-and-inert by measurement (F41). Next: **N3 —
35
- > executed inheritance**, where the workflow persists and improves across runs (the
36
- > ladder's kill-switch question).
46
+ > within-run ratchet (Layer R) ships armed-and-inert by measurement (F41). **In flight:
47
+ > N3 — executed inheritance**, where a workflow that greened is kept, offered to the next
48
+ > similar job, and improved as executed instead of drafted from scratch (the ladder's
49
+ > kill-switch question). As of 0.7.0 N3's machinery **ships** — the bridge registry, the
50
+ > operator-signed reuse envelope, the selection call and step-level resume are all in the
51
+ > public API — but shipping is not acceptance: one execution green proves reuse safe and
52
+ > legal, never *beneficial*. The lift claim waits on the ON-vs-forced-cold contrast, which
53
+ > has not fired. Until it does, treat reuse as available and unproven.
37
54
 
38
55
  ## Quick start
39
56
 
@@ -60,17 +77,17 @@ Three layers; nothing inside negotiates with the layer above it.
60
77
  | Layer | What it is | Emergent? |
61
78
  |---|---|---|
62
79
  | **Outer shell** | Per-run budget cap (bareguard), retry cap, verdict collection, escalation routing. Stateless across runs | never — permanent, dumb, un-gameable |
63
- | **Emergent middle** | The authored plan: bounded steps, each with its granted verbs, its own round/attempt caps, its write scope and its form-checkable exits — schema-validated, red before tokens burn | yes — authored and improved by the agent |
80
+ | **Emergent middle** | The authored plan: bounded steps, each with its granted verbs, its own round cap, its write scope and its form-checkable exits — schema-validated, red before tokens burn. A step keeps iterating while it is making progress; when it stops, the shell ends it and the planner re-allocates | yes — authored and improved by the agent |
64
81
  | **Floor** | Append-only JSONL spine (single source for every UI), litectx store per job, per-run ledger | never — the record |
65
82
 
66
- Every checkpoint in a workflow carries its own **verdict class**, and the class decides
67
- what the run's learning is worth:
83
+ Every job declares a **verdict class**, and the class decides what the run's learning is
84
+ worth. Today exactly one is built:
68
85
 
69
- | Verdict | Truth source | Mints inheritance? |
70
- |---|---|---|
71
- | **Hard green** | predicate / exit-code (tests, build, lint) | automatically |
72
- | **Soft green** | rubric / assessment | only with HITL confirm or N consistent repeats |
73
- | **HITL green** | a human is the close (PR merge, "publish") | yes — and merge stays human, forever |
86
+ | Verdict | Truth source | Mints inheritance? | Status |
87
+ |---|---|---|---|
88
+ | **Hard green** | predicate / exit-code (tests, build, lint) | automatically | **built** |
89
+ | **Soft green** | rubric / assessment | only with HITL confirm or N consistent repeats | declared, not built (rung N4) |
90
+ | **HITL green** | a human is the close (PR merge, "publish") | yes — and merge stays human, forever | declared, not built (rung N4) |
74
91
 
75
92
  The full bare-suite surface is *disclosed* to the authoring agent; only *admitted* verbs
76
93
  are callable per job. A request against a locked primitive is a structured red — real