bareloop 0.10.0 → 0.12.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,516 @@ 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.12.0] — 2026-08-23
9
+
10
+ ### Added (N4 slice 2 — the softgreen rung: a close that JUDGES, and a signer who has the last word)
11
+
12
+ - **The judged floor is real: LOCATE + DECIDE (`src/judged.js`).** A judge (haiku-4.5, pinned —
13
+ never a step knob) extracts FACTS from the real artifact with quotes; an arbiter-owned `decide()`
14
+ renders pass/fail from a signed rubric card; *unsure* is a red, never a pass. The judge never
15
+ renders a verdict, so the rubric-is-self-consistency gotcha stays closed. Wired as the
16
+ `judged-floor` close kind — metered from the run's own wallet, `offer:false` by law, and skipping
17
+ the seed-verdict read (its bar comes from calibration instead).
18
+ - **`soft-green` is ADMITTED as a verdict class** — the class-scoped question set, the inherited
19
+ green guard battery (hamr: *"battery ties with job type"*), the class-scoped kind menu, and the
20
+ promise rule: composition stays ≤ the picked class, and exceeding it is an honest red rather than
21
+ a silent up- or downgrade.
22
+ - **The signer's two artifacts are AUTHORED and enumerated in the hash** (`src/cardauthor.js`): the
23
+ rubric card comes from Q6 and the calibration set from Q7, both handed to the compiler verbatim,
24
+ both SHOWN to the signer (D5 shown-and-fixed), and what the signer fixed is what is stored —
25
+ `source: 'proposal'` says *nobody changed this*, which is a different fact from *a person read it
26
+ and changed nothing*.
27
+ - **The calibration gate — ten graded truths and five resisted lies buy a signature**
28
+ (`src/calibrate.js`, gate 4 of `prepareSigning`). The whole pipe grades the signer's own ten cases
29
+ and faces a 5-artifact injection battery adapted to the locate axis; the floor is **10/10 with
30
+ itemized reds** (hamr, 2026-08-18) and all-or-nothing on both halves. The $0 legality check runs
31
+ first and buys no calls. A dead judge is a CASUALTY under the transport's name, never a reading of
32
+ the operator's rubric.
33
+ - **Quarantine: a judged green earns no learning credit until an `accept` releases it.** Minted
34
+ soft-green bridge rows are held at mint, the release is forward-only, a rerun re-quarantines, and
35
+ a held entry is never offered by selection — so a soft-green pass cannot feed N3 before the judged
36
+ floor is proven.
37
+ - **The review door — every run ends at the signer's three doors** (`accept` / `rerun` / `pause`).
38
+ The door records a DISPOSITION and never touches the loop's own verdict (hamr: *"it's important
39
+ not to change the loop self verdict"*): `runPlan` returns `green`/`already-green` exactly as
40
+ before. It opens at all three green sites (precheck `already-green`, the outer close, and the fix
41
+ loop's green). **Soft-green always** (`REVIEW_DOOR_CLASSES`), **green opt-in** via `reviewDoor:
42
+ true` / `--review-door` — the flag wins in both directions and is never inferred. An `accept`
43
+ re-runs the close's MECHANICAL stages (never a judged floor, never a person): a door keeps for 60
44
+ days and nothing freezes a tree.
45
+
46
+ - **The judge tier joins the SIGNATURE — a bump flips the hash and the stage refuses the
47
+ mismatch** (`649d8a5`). `JUDGE_MODEL`'s docstring has always said a judge-model bump forces a
48
+ full recalibration, and nothing detected one: `closeDecl.calibration` stored only its `cases`,
49
+ so the tier that graded them sat OUTSIDE the signed bytes, a bump flipped no hash, and the
50
+ judged stage never compared the model it was about to buy against the model the signer's set
51
+ was graded by. A rule with no wired detector is prose (F45). The tier now lives beside the
52
+ cases as **`calibration.judgeModel`** (`CALIBRATION_FIELDS` in `src/declaredclose.js`
53
+ enumerates a stored set as exactly those two and nothing else), written by
54
+ `foldJudgedArtifacts` from the PIN itself rather than from anything a caller may name — and
55
+ because it is inside the spec, `jobSpecHash` covers it by construction: bump the constant,
56
+ every signed judged spec hashes differently, the signature dies, and the re-sign is what
57
+ re-runs the calibration gate. `validateCloseDecl` REQUIRES it on any stored set (a set nobody
58
+ can attribute to a tier is a floor nobody can tell apart from a bumped one).
59
+ `declaredStages` carries the close-level fact onto the judged stage as
60
+ `calibrationJudgeModel` — the same seam, and for the same reason, that already stamps
61
+ `offer: false` — and it carries it from the SIGNED bytes, so a stage can never name a tier the
62
+ signature does not. `runJudgedFloor` then refuses to grade on a mismatch, naming both models
63
+ and the two things that fix it (*re-sign* and *recalibrate*), under the same `FAILED` fault the
64
+ absent judge seam already stops on: nothing is broken, something was never re-done. An ABSENT
65
+ stamp is deliberately NOT a runtime stop — a signed spec cannot reach a run without a
66
+ calibration (the signing gate) or without a model on it (the spec gate), so the only runtime
67
+ question is WHICH model. No signed spec on disk moves: `jobs/*.json` carries zero
68
+ `judged-floor` stages today.
69
+ - **The U runner mints the row its review door promises to release** (`e01d0af`). The door tells
70
+ a person that `accept` *"releases this run's learning credit"*; for every run
71
+ `scripts/run-u.mjs` has driven that sentence was false by construction — a cold green wrote a
72
+ standalone `bridge-<job>-<runid>.json` FILE and never a registry ROW, so
73
+ `--registry --workflow --decide accept` reached `recordDoor`, found no green row for the runid
74
+ and died `no-row-for-run`. The held credit could be described and never released. **STORAGE
75
+ ONLY:** nothing here selects, promotes or reuses a bridge — that rung stays parked on
76
+ `layer-3-reuse` (F88) — and exactly one thing reads the row back, the door. `writeGreenRow` is
77
+ `runReuse`'s own cold-leg green write LIFTED out of its closure unchanged (the
78
+ fork-on-a-different-close-shape rule, both collision refusals, the `appendGreen` leg), so the
79
+ two runners mint a green through ONE spelling; a second spelling of what a green writes is the
80
+ two-transforms class applied to the ledger itself. **`writeRunGreenRow`** (newly exported) is
81
+ the U runner's terminal policy on top of it, and every guard is a NAMED refusal rather than a
82
+ silence: no `--registry` (`no-registry`), an already-green run (`green-predates-run` — accept
83
+ confirms a verdict, it never mints one), a green with no executed plan (`no-plan-executed`), a
84
+ class whose credit is not held (`credit-not-held` — `green`-class runs mint nothing here,
85
+ which is what this runner has always done), and a spine leak blocks the row exactly as it
86
+ already blocks the bridge file. The row is born HELD because `greenParts` reads the SIGNED
87
+ class off the record — nothing new sets a flag — and `applyDoorDecision` releases it through
88
+ the path that already existed. `src/index.js`'s claim that `runReuse` is the only registry
89
+ writer is CORRECTED rather than left standing.
90
+ ### Fixed
91
+
92
+ - **F102 — a pending human decision now survives a wall-halt → resume** (`a6f62c9`). Two records
93
+ answering two different questions: `human-decision` (the door, the words, and by whom, with
94
+ `source: 'operator'|'checkpoint'` and the moment the PERSON spoke) and `human-decision-spent`
95
+ (what actually bought it). The spend marker fires on the **WORK** — a fix round bought, an accept
96
+ that greened, a pause honoured — **not on the fix loop opening**, which is precisely the shape of
97
+ the incident (`iterationsUsed: 0`). A resume carrying one re-enters the fix loop through the same
98
+ seam a fresh answer uses; the ask is never re-rendered. Two answers to one question are REFUSED
99
+ (`hitl-decision-red` naming what is held), never merged.
100
+ - **F103 — a rerun buys its own clock** (`a6f62c9`). Two counters that never do each other's job:
101
+ CHAIN (`chainWallMs`/`chainSpentUsd`, `job-end.spentUsd`) is every leg added up and is what the
102
+ halt readout reports; ENGAGEMENT (`priorElapsedMs`, `job-end.engagementSpentUsd`) is what bounds
103
+ THIS leg. Money is deliberately not symmetric — the signed budget stays the CHAIN's ceiling,
104
+ because a refilled wallet is the failure F45 measured. W-2 is untouched where it was designed for:
105
+ an ordinary resume folds exactly as it always has, and so does an `accept`.
106
+ - **F106 (HIGH, review) — the calibration gate now meets the one ceiling** (`0ae7693`). The run's
107
+ third and largest paid seam (up to 10 locate calls plus the 5-artifact battery, each under its own
108
+ retry ladder) ran with no bound while the scout and the declaration loop were both stopped by
109
+ `run-author --budget` — the advertised budget and the enforced budget were two different numbers.
110
+ `prepareSigning`/`calibrationGate` take `ceilingUsd` + `priorCalls`, a cost book absorbs prior
111
+ spend so the gate reads the REMAINING balance, `capStop` is asked before each paid call inside the
112
+ pipe (retries included), and a ceiling stop returns as an operator refusal naming money
113
+ (`cap-halt` / `pricing-red`) — never a casualty, and never a part-graded set presented as graded.
114
+ - **A review door's rerun is a fresh engagement too** (`0ae7693`): `resolveHumanRuling` takes
115
+ `doorRerun` as a third input, OR'd and never merged, so *"is this leg a fresh engagement"* keeps
116
+ ONE spelling and a door-rerun leg cannot inherit the rejected leg's wall.
117
+ - **`scripts/run-u.mjs` wires `judgeProvider`** (pinned to the judge model, never a step knob) — a
118
+ live soft-green run no longer instrument-stops at the judged stage.
119
+ - **Two mutation-blind gaps in `src/kinds.js` covered by tests** (found by the same review).
120
+
121
+ - **The medium whole-branch code review's five findings** (`e0c87d3`). An absent `job-end` in
122
+ `scripts/run-u.mjs` (a crash between the door record and `job-end`, or a legacy spine) no
123
+ longer launders into an exact complete `$0` that folds into the rerun ceiling — unknown is
124
+ reported unknown (F6), regression test watched failing first. One name per kind and per code:
125
+ the `'judged-floor'` literal at 7 sites becomes **`JUDGED_FLOOR_KIND`** (`src/kinds.js`,
126
+ exported) and the `'quarantined'` code at 3 sites becomes **`QUARANTINED_CODE`**
127
+ (`src/bridges.js` — deliberately not `reuse.js`, which would cycle reuse ↔ selection). One
128
+ `threeDoors()` builder replaces the copy-pasted door lines in `scripts/u-readout.mjs` (output
129
+ byte-identical against HEAD), and a `changedEvidence()` helper in `src/planrun.js` replaces
130
+ the duplicated evidence block and the double `verdictType` default, with `doorOpens`'
131
+ signature untouched. Two findings were ESCALATED rather than decided — the judge-model bump
132
+ with no detector, and the U runner's door promising to release credit it never minted — and
133
+ both are the `1A`/`2B` entries above.
134
+ - **The tail review's six findings over `1A`+`2B`** (`493b2ce`). The door PREVIEW path now
135
+ checks row-EXISTS before promising a credit release, and its printed commands carry
136
+ `--registry`/`--workflow` forward — the `e01d0af` fix had landed at the end-of-run readout and
137
+ not at this sibling site, so the operator's next invocation would have rediscovered
138
+ `no-row-for-run` one hop later. The registry row records **`rounds` + `roundsComplete`**: a
139
+ resume leg writes a fresh spine and the halted leg's restart record declares money and wall
140
+ but never rounds, so on a resume that count is a FLOOR and now says so the way the money
141
+ beside it already does (F6). A NAMED `--registry` that attempted no row prints the reason
142
+ rather than staying silent. The two near-identical record builders in `src/reuse.js`
143
+ consolidate into `commitWrite`, and the duplicated `worker-round` scan is hoisted to one
144
+ spelling.
145
+ - **The release gate's two findings** (`a5a4731`). `docs/UPSTREAM-ASKS.md`'s BA-21 carries a
146
+ dated CORRECTION note: the retracted 2.0–2.5× figure was a clipipe-surface number (F48 — a
147
+ provider-billed surface that never pools with `anthropic-api`), and the measured API-side
148
+ numbers are ~5.7% / 1.586×; the acceptance criteria are unaffected. In `src/judged.js`, two
149
+ literal NUL bytes inside composite-key template strings are spelled `\x00` — byte-identical
150
+ at runtime, but the literal made `grep`/`file` treat the source as binary (the documented git
151
+ binary-detector trap).
152
+
153
+ ### Docs (2026-08-23)
154
+
155
+ - **`docs/FINDINGS.md`: F102 and F103 move from NOT FIXED / NOT BUILT to FIXED**, each cited to
156
+ the commit that did it — `a6f62c9` for both, plus `0ae7693` for F103's extension (the review
157
+ door's rerun as `resolveHumanRuling`'s third input, OR'd and never merged). **F106 is minted**
158
+ from the 3-lens review's HIGH: F45's class in a money coat with the direction reversed — the
159
+ ENFORCED number was the looser one, which is the failure that leaves a bill instead of a red
160
+ (`cce3ce5`).
161
+ - **PRD Addendum v1.76** (this release): the three doors restated, the green-class review door
162
+ kept OPT-IN, rerun named as *not a replay*, the `1A`+`2B` escalations ruled, and the one
163
+ remaining door hole named and left deliberately.
164
+ - **`bareloop.context.md`**: the judge-tier paragraph rewritten from DEFERRED to the live
165
+ detector it now describes.
166
+
167
+ ## [0.11.0] — 2026-08-18
168
+
169
+ ### Changed (2026-08-18 — the review door's third button)
170
+
171
+ - **The three doors are `accept` / `rerun` / `pause`; `cancel` is DELETED as a concept.** hamr:
172
+ *"what's the point of cancel anyways? pause can resume — that would be more honest"* and
173
+ *"rerun implies I don't like it, go again"*. A `pause` answer runs nothing, spends nothing,
174
+ buys no worker round, never opens the fix loop and does NOT consume the one-shot rerun
175
+ allowance: it mints the ordinary `hitl-pause` checkpoint with an explicit
176
+ `humanDecision: 'pause'` and leaves the run resumable from the start of its last step. An
177
+ unresumed pause simply expires under the existing 60-day `PAUSE_TTL_MS` — **that expiry is
178
+ what cancel used to be**, without forcing a permanent decision. `HUMAN_DECISIONS` is the
179
+ enumerated set the surface derives from, so `cancel` is now inexpressible rather than
180
+ rejected after the fact; `hitl-decision-red` still refuses any word that is not a door.
181
+ - **`hitl-cancel` is no longer mintable and the constant is gone** (removed from
182
+ `src/declaredclose.js` and the package exports). The ledger keeps the bare string in its
183
+ excluded-escalation set — the `gate-red` precedent — so a spine written before this change
184
+ still reads as governance rather than as a counted capability gap; no spine on disk carries
185
+ it (the door never fired live), so that entry is forward compatibility, not bookkeeping.
186
+ - **A human checkpoint row is classed `checkpoint`, not `casualty`** (`src/reuse.js`, both the
187
+ live try loop and the resume reconstruction). A casualty is a run that died; a checkpoint is
188
+ a run that is waiting on a person with nothing lost — every other reader already treated it
189
+ that way, and the row was the last place the machinery mis-described itself.
190
+ - **Runner surface (`scripts/` never ships):** `scripts/run-u.mjs --decide pause` answers the
191
+ door and launches nothing at all — it prints the decision, names the TTL and the ORIGINAL
192
+ runid, and exits having spent nothing. Deliberate: the runner writes one spine per leg, and a
193
+ leg that returns before drafting emits no `plan-accepted` and no `step-end`, so launching a
194
+ run to say "not now" would mint a runid whose own checkpoint is empty and re-pay for finished
195
+ steps on the next resume. Door rendering (`scripts/u-readout.mjs`) and the escalation
196
+ passthrough (`src/ralph.js`) carry the new third door.
197
+ - **Docs:** dated addendum on `docs/02-features/2026-08-17-softgreen-review-door-design.md`
198
+ (closed sections untouched), **PRD Addendum v1.72**, README's *"You have the last word"*,
199
+ `bareloop.context.md`'s terminal/decision surface, and `docs/01-product/LAYERS.md`.
200
+
201
+ ### Docs (2026-08-17 — decisions only; NO code changed, and no build is authorized)
202
+
203
+ - **New design record `docs/02-features/2026-08-17-softgreen-review-door-design.md`, and PRD
204
+ Addendum v1.71: `hitl` is RETIRED as a verdict class, its pause becomes the REVIEW DOOR at
205
+ the end of EVERY run, and `soft-green` becomes the forward path.** The hitl slice below FIRED
206
+ live first and worked — the first hitl pause in programme history, 64 strict errors to 0, the
207
+ authored guard catching the cheat genre twice, evidence package and three doors and a stopped
208
+ clock (F105). hamr then retired the class for what it asks of a composer and of a person:
209
+ *"checkers are subjective human experience that they should grade for"*, and *"it's hard to
210
+ apply deterministic flow on a probabilistic throughput"*. **The machinery is re-homed, not
211
+ deleted.** The door never changes the loop's own verdict (*"it's important not to change the
212
+ loop self verdict"*): a green stays green in the ledger and the door records a disposition —
213
+ `accept` (and, on soft-green, the release of quarantined learning credit), `rerun` (the text
214
+ IS the gap), `cancel` (*"the verdict stands, but I'm not taking the merchandise"*). A rerun is
215
+ a **FRESH ENGAGEMENT** with its own money and time and two side-by-side counters, and **any**
216
+ wall or halt now records full state — time, money, strikes, notes/progress and any pending
217
+ human decision. `soft-green` gets a `judged-floor` kind on the LOCATE+DECIDE pattern (the
218
+ judge extracts facts with quotes from the real artifact; an arbiter-owned `decide()` renders
219
+ pass/fail; unsure = red), behind a signed rubric card (Q6) and a signed calibration set (Q7)
220
+ the whole pipe must grade correctly before the close is signable. **Nothing is built.**
221
+ - **F102, F103, F104 and F105 in `docs/FINDINGS.md`**, each grounded in today's spines under
222
+ `bareloop-patients/litectx-maintainer-bareloop/`: a pending rerun decision does not survive a
223
+ wall-halt → resume (two byte-identical pause records — the human paid the same decision
224
+ twice); the wall folds across legs, so a decide-time rerun inherited 87 seconds; the
225
+ catalogue cannot count documentation, so a composer measured a typecheck PROXY instead —
226
+ which is what retired the class; and the positive record of everything the first live hitl
227
+ loop proved, including that rerun DELIVERY works and rerun CONVERSION is still unproven-live.
228
+ - **`docs/01-product/LAYERS.md`** gains the review door, the two-verdict table and the next
229
+ rung; the **2026-08-13 N4 build record** (closed) gains a dated addendum pointing forward;
230
+ **README** and **`bareloop.context.md`** carry the direction change, the contract still
231
+ describing what the code on this branch actually does today.
232
+
233
+ ### Added
234
+
235
+ - **N4 slice 1 — the `hitl` verdict class is ADMITTED, and the run PAUSES for a person.** A job
236
+ whose *done* needs someone to say so now runs through the same signed spec, the same
237
+ agent-authored plan and the same arbiter as a green job. The refusal was never one flag, so
238
+ every coupled site moved together: `LOCKED_VERDICTS` / `LOCKED_CLASSES` drop it,
239
+ `DECLARED_CLOSE_CLASSES` widens to `['hard','hitl']`, the class battery is the GREEN mechanical
240
+ battery inherited verbatim (hamr's OPEN-1 ruling: nothing a human stage cannot see), and the
241
+ interview gains a hitl question set (the green questions byte for byte, plus the one thing
242
+ they cannot supply — the question the signer actually answers). **`soft-green` stays
243
+ declared-but-locked**: its judged floor is slice 2, and a wobbly ruler must mint nothing.
244
+ - **`human-confirms` goes live as the one kind that measures nothing.** Its whole parameter
245
+ surface is `ask`; it cannot spawn, is never env-capable, is `offer: false` BY LAW (a
246
+ declaration that offers one is a validation red, and the arbiter's own bridge stamps the law
247
+ onto the stage the runner sees, so the agent can never compose `check-passes(<a person>)`), is
248
+ at-most-once, and must be the LAST stage — first-red-wins would otherwise silently delete every
249
+ mechanical stage behind it. It also SKIPS the seed-verdict read (ruling 8), and the skip is
250
+ recorded as a row rather than taken in silence.
251
+ - **Three new terminals, minted rather than borrowed.** `hitl-pause` is a decision-ready
252
+ CHECKPOINT carrying the evidence package (every mechanical stage's result, the close's own
253
+ question, and what the run changed) — never a bare "approve?"; `hitl-cancel` is terminal with
254
+ an explicit `gap: null`, no fix loop and no worker round; `hitl-decision-red` refuses a
255
+ decision the run cannot act on (a fourth door, a `rerun` with empty text, or a decision handed
256
+ to a close with no human stage) before anything is spent. All three are excluded from the
257
+ ledger's escalation counting for their own stated reasons, and none demotes a bridge. The
258
+ legacy `hitl-close` entry keeps its own meaning; nothing was renamed in passing.
259
+ - **The RETURN needs no new channel.** A resumed leg carrying the signer's `rerun` feeds their
260
+ words through the same seam `post.gap` uses — same bound, same scrub — and the run continues
261
+ under the folded wallet on an unbilled clock. The ruling is spent when the fix loop opens, so
262
+ the next machine-clean tree pauses for a second review instead of converting one sentence
263
+ forever, while a mechanical red in between is still converted normally.
264
+ - **New public exports**: `HUMAN_DECISIONS`, `SEED_EXEMPT_KINDS`, `normalizeHumanRuling`,
265
+ `NEVER_OFFERED_KINDS`, `HUMAN_PAUSE`, `CHECKPOINT_OUTCOMES`, `PAUSE_TTL_MS`,
266
+ `checkpointAgeGate`, `HITL_PAUSE`, `HITL_DECISION_RED`, `HUMAN_CHECKPOINTS`. The 60-day pause TTL and the
267
+ canonical checkpoint list live in the LIBRARY (hamr's OPEN-2 ruling) so the exported bundle
268
+ inherits them rather than every runner re-implementing them. The two terminal names are
269
+ spelled ONCE, in `src/declaredclose.js` beside the `HUMAN_PAUSE` close-verdict word they
270
+ translate, because four different readers key on them — the emitters (`src/planrun.js`), the
271
+ ledger's excluded-escalation set, the resume reader (`src/reuse.js`) and the runner script —
272
+ and hand-spelled copies of one terminal is how those readers come to disagree.
273
+ - **A registry guard that was only holding by accident**: an `already-green` terminal now mints
274
+ no bridge version for a NAMED reason (`green-predates-run`) rather than because no plan
275
+ happened to be on the spine — a hitl try that paused and came back with `accept` has its
276
+ predecessor's plan in the very same try window.
277
+ - **An authoring run says what it is doing while it is still doing it — three REPORTING seams,
278
+ `onPhase` / `onStage` / `onCall`.** The pipeline ran up to ~15 minutes between `author-start`
279
+ and its result with nothing on the terminal or the spine — a real survey ladder, a real
280
+ declaration ladder, and a real toolchain per close stage, all inside one await — and silence
281
+ is byte-for-byte what a hang looks like, so the operator's only lever was to kill a run that
282
+ might be working. The library REPORTS and the shell PRINTS, the same shape `runJob` → `runPlan`
283
+ already has: `onPhase` fires at the composition's boundaries in `authorCloseForJob`
284
+ (`src/authorjob.js`) and `authorClose` (`src/authorflow.js`) — `seed`, `scout`/`scout-done`,
285
+ `listing`/`listing-done`, `author`, `author-call`, `seed-read`/`seed-read-done`; `onStage`
286
+ (a new `Ctx` field in `src/kinds.js`) fires per seed-read stage, AFTER the stage lands, because
287
+ only then is there a verdict to carry; `onCall` fires from inside each of the two metered
288
+ recorders (`makeCostBook.add`, `runAuthorScout`'s `record`) rather than at the call sites, so a
289
+ paid call cannot be metered without its report going out. **Nothing here is consulted and
290
+ nothing decides**: `capStop` is still the one ceiling predicate, still asked between calls,
291
+ still reading the same entries — and every callback defaults to a no-op, so every existing
292
+ caller is byte-identical. Two deliberate omissions: `makeCostBook.absorb` does NOT fire
293
+ `onCall` (those calls were already metered and reported by whoever made them, and reporting
294
+ them twice would double the scout's spend in every reader downstream), and the `seed` phase is
295
+ announced only on the path that actually reads one, because a phase line for work nobody did is
296
+ the blind-instrument class one line wide. The renderer is `phaseLine` in
297
+ `scripts/author-readout.mjs` (a readout no test can reach is a readout nothing checks); it
298
+ prints WHAT is happening and never a fraction — a survey attempt has no progress and a suite
299
+ stage finishes when it finishes — and an unmeasured `durationMs` prints as `unknown`, never as
300
+ `0` (F6 in its time form). A phase the renderer does not know is printed rather than swallowed.
301
+
302
+ The runner half of the same slice landed beside it and is deliberately NOT in the tarball
303
+ (`scripts/` never ships): `scripts/run-u.mjs` gains `--decide accept|rerun|cancel` (+ `--text`)
304
+ on the existing `--approve <specHash>` signature, renders the pause's evidence package plus the
305
+ line-level diff, consumes `CHECKPOINT_OUTCOMES` and `checkpointAgeGate` instead of re-spelling
306
+ either, and prefers the run's own terminal over a stale watchdog record when folding the wall —
307
+ so a person's deciding time is never billed to it. What an adopter's own runner has to do is in
308
+ `bareloop.context.md`.
309
+
310
+ Beside it, `scripts/run-interview.mjs` — **the terminal interview, and the answer to hand-writing
311
+ an `answers.json`**, which is precisely the SWE tax this product exists to refuse. It asks the
312
+ LIBRARY's frozen questions ONE AT A TIME, **calls no model and costs $0**, and writes
313
+ `answers.json` (the shape `run-author.mjs` consumes) plus `specdraft.json` (the operator half — no
314
+ close and no `verdictType`, because those are what `run-author` authors). The draft is checked for
315
+ $0 by `validateJob`, the SAME validator that will judge the resolved spec, so a draft that cannot
316
+ become a spec says so before a provider is ever built. It then OFFERS to spawn the paid
317
+ `run-author.mjs` under its OWN `--budget` ceiling — **default NO** (`[y/N]`, the same lean the
318
+ pause's three doors take: the answer that costs nothing is the one you get by saying nothing), and
319
+ it prints the exact command either way. The two ceilings are kept apart out loud: `--budget` is
320
+ what the AUTHORING call may spend, `budgetUsd` is what the RUN may spend, signed into the spec.
321
+
322
+ And `scripts/run-u.mjs` gains a `litectx-maintainer` JOBS row — **N4's hitl PROVING job, dark
323
+ since the legacy `steps[]` deletion took its old spec's path with it**, back as a plan-flow job
324
+ with an AUTHORED close. `jobs/litectx-maintainer.json` **DOES NOT EXIST, and the row does not
325
+ create it**: the spec is authored live through `run-interview.mjs` → `run-author.mjs` and signed
326
+ by hamr, goal and answers included, and until it lands **the runner REFUSES BY NAME** — a row is
327
+ the runner's half of a job, never the signer's. The patient is a COPY at the convention this table
328
+ already uses, and the seed is that copy's own HEAD (litectx v0.32.0, `115213d`).
329
+
330
+ ### Changed
331
+
332
+ - **Both suite pins move up a minor — `bare-agent ^0.35.0 → ^0.36.0`, `bareguard ^0.12.0 →
333
+ ^0.13.0` — because on 0.x a caret locks the MINOR**, so the old pins admitted neither release
334
+ and the bump is the only way to consume them. **bare-agent 0.36 delivers BA-20**, the
335
+ productized decisive judge this queue reopened on: `judge` (verbatim request + one structured
336
+ artifact → `honored`/`broke` with a mechanical `where`, truncated/unparseable responses as
337
+ DISTINCT flagged outcomes excluded from graded denominators, composing **around** a provider
338
+ and never inside the Loop), the calibration harness (`calibrate`, `CALIBRATION_CASES`,
339
+ `INJECTION_BATTERY`, `constantHonored` — the negative control that lets the harness fail), and
340
+ the pure `judgeToAnnotation` mapping into bareguard's shipped fact envelope. **bareguard
341
+ 0.13.0** is the companion hardening: `gate.annotate` now REJECTS malformed facts into distinct
342
+ `annotate_malformed` rows instead of normalizing them into silently-`honored` ones, `verdict`
343
+ goes through the audit redactor (it was written RAW — a real secret-leak fix), `meta`'s
344
+ 1000-byte bound became a decoupled copy, and `__proto__` keys are dropped at every depth.
345
+ **That behaviour change is zero-cost to bareloop today**: it imports only `Gate` and `redact`
346
+ and has **no `gate.annotate` call site** at all. `npm ls` resolves `bare-agent@0.36.1` +
347
+ `bareguard@0.13.0`, deduped to one shared copy, and **full fresh gates are green against the
348
+ installed versions** — `npm test` 1569/1569 exit 0, `typecheck` clean, `build:types` clean.
349
+ **The judge is NOT consumed here**: its consumer is the N4 (soft-green) rung, so wiring it into
350
+ a `judged` close stage and executing BA-20's acceptance criteria live (running `calibrate`
351
+ against the frozen floor and naming the hash it graded) are deferred to N4's opening — a
352
+ verified delivery, not a verified acceptance.
353
+
354
+ - **The interview loses its repo question and widens its scope question — hamr drove the terminal
355
+ himself and ruled on the wording (2026-08-15).** `GREEN_QUESTIONS` drops *"Is there a code repo I
356
+ can look at? Where?"*: the repository is already MANDATORY STRUCTURED input (`runInterview`'s
357
+ `repoPath`, taken from `--patient`, without which nothing starts), so asking a person to re-type
358
+ a path the machine is holding is the SWE tax this product exists to refuse — and it invites a
359
+ second, drifting answer for one fact (hamr's own live answer was *"Yes — the patient itself."*).
360
+ Question 2 gains the other half of the same decision — *"…And which files should the work read or
361
+ draw from (they stay untouched)?"* The sets **renumber contiguously from 1**, so the green set is
362
+ five questions and hitl's signer-ask is now **6**, not 7. Nothing hardcodes a count: `run-author`,
363
+ `run-interview` and the suite all read `requiredAnswersFor(cls)`. **Adopter note: an
364
+ `answers.json` written before this change is SILENTLY MISREAD** — its key `6` was the repo answer
365
+ and now reads as hitl's signer-ask, and its key `7` is dropped as unasked. Re-run the interview
366
+ (it is $0 and calls no model) rather than hand-editing.
367
+ - **The interview's own prose stops citing finding numbers and says the mechanics out loud.** The
368
+ goal prompt no longer prints *"F87"* at a person who has never read the findings — the rule is
369
+ unchanged and the price is now named as a price. The fence prompt says what a fence IS (writable
370
+ files, everything else read-only), that patterns are repo-root-relative, and that absolute paths
371
+ are refused because the run works on a copy. The multi-line hint spells the keystroke: *press
372
+ Enter on an empty line, i.e. Enter twice*.
373
+ - **Suspended minutes stop charging a signed wall — the run clock is MONOTONIC** (hamr's ruling,
374
+ 2026-08-15: *"if computer suspended, time shouldn't count."*). `createClock`'s default source
375
+ moves from `Date.now` — CLOCK_REALTIME, which counts every minute the machine was asleep — to
376
+ `performance.now`, which reads libuv's `uv_hrtime` and so CLOCK_MONOTONIC, which per
377
+ `clock_gettime(2)` *"does not count time that the system is suspended"*. That was not
378
+ hypothetical: an s2idle suspend landing mid-run charged **45 phantom minutes against a
379
+ 45-minute wall**, and the same exposure covers an NTP step or a hand-set date. Nothing corrects
380
+ for a suspend after the fact; the reading simply never includes one. **The epoch change is safe
381
+ by construction**: every use of `now()` in `src/clock.js` is RELATIVE (`now() - startedAt`) and
382
+ no value derived from this clock is ever published as a calendar timestamp — `report()` carries
383
+ durations only, and the `at:`/`ts:` stamps a run writes come from their own `Date.now` seams in
384
+ `src/reuse.js`, which stay calendar-correct on purpose. `now` is still injectable, which is what
385
+ every clock test drives. Three runJob-level wall tests mocked `Date` because that is the only
386
+ seam `runJob` exposes, and the monotonic source made them BLIND — they redded `provider-red` /
387
+ `plan-red` instead of `wall-halt`, which is the failing-first evidence that the mock was
388
+ load-bearing; they now go through one shared helper (`tests/helpers.js`) that pins
389
+ `performance.now` to the mocked `Date` and restores it after. **KNOWN LIMITATION, recorded
390
+ rather than fixed** (PRD Addendum v1.67, hamr: *"leave it and mark it in prd as possible known
391
+ limitations"*): the OUTSIDE watchdog still measures silence in CALENDAR time, so a long suspend
392
+ can still read to it as a stall. The half that charges money is fixed; the half that reads
393
+ silence is not.
394
+ - **`scripts/run-u.mjs` PRINTS the sleep-inhibitor launch line** the way `scripts/run-reuse.mjs`
395
+ has since F72, instead of carrying it as a comment — a rule nobody reads at the moment they
396
+ launch (and like every runner change here, `scripts/` never ships in the tarball). A suspend
397
+ freezes EVERY guard: the outside watchdog is a POLLER, so it cannot observe — let alone kill — a
398
+ run whose machine is asleep, and after the monotonic clock above the run's own wall does not
399
+ count those minutes either. A suspended run is therefore simply UNWATCHED for as long as it
400
+ sleeps, and the launch banner is the last moment the operator can still do something about it.
401
+ The printed line is `systemd-inhibit --what=idle:sleep --why="bareloop u run" env …`, with `env`
402
+ before the key assignment on purpose: `systemd-inhibit` execs a COMMAND, and a bare `VAR=value`
403
+ prefix is shell syntax it would try to run as one — the key still rides an env assignment and
404
+ never argv. A pause with no ruling prints the line prefixed to NONE of its three doors, because
405
+ prefixing one would quietly recommend it and this surface's lean runs the other way.
406
+ - **The hitl terminals get one canonical spelling, and the pause screen gets one renderer**
407
+ (`/code-review medium`, findings 2 and 3). The terminal names were hand-spelled string
408
+ literals at every reader: 9 sites in `src/planrun.js`, the ledger's `EXCLUDED_ESCALATIONS`
409
+ set, `src/reuse.js`, the `src/index.js` re-export and 7 sites in `scripts/run-u.mjs`. They now
410
+ all import the constants exported from `src/declaredclose.js` (the `hitl-cancel` constant
411
+ introduced with them is gone again — see the review-door entry above). Beside it,
412
+ `printPauseEvidence()` in `scripts/run-u.mjs` becomes the SINGLE assembly point for both pause
413
+ screens — the resume preview and the fresh-pause terminal — so the fallback rule cannot drift
414
+ between them: with a pause record the package is the close's own ask plus the diff of what
415
+ changed; without one it says so plainly, rather than rendering the bare "approve?" that ruling
416
+ 2 forbids. Finding 1 — `runReuse` classing a `hitl-pause` as a casualty and buying another try
417
+ — was parked as arbiter territory when this bullet was first written, and has since been FIXED
418
+ on this same branch: `d183c95` gave the checkpoint its hard stop (`hardStop` reads
419
+ `HUMAN_CHECKPOINTS` before the retry table, so the loop hands the run back rather than re-asking
420
+ a question already put to someone who has not answered), and `4e0ab94` gave the ROW its own
421
+ `checkpoint` class, so the machinery no longer mis-describes a waiting run as one that died.
422
+
423
+ ### Fixed
424
+
425
+ - **`--lang` given with no value no longer poisons the command the interview prints.** `arg()`
426
+ returns the EMPTY STRING for a flag typed with nothing after it and `?? 'js'` passes an empty
427
+ string straight through, so the printed next-step command collapsed to `--lang --budget 2.5` —
428
+ copy-paste it and `run-author` reads the word `--budget` as its language. Both scripts now stop
429
+ LOUD on a present-but-empty `--lang` (an ABSENT flag still takes the default, the same rule the
430
+ money ceilings are parsed by).
431
+ - **A crashed authoring run left one line and no body — `scripts/run-author.mjs`'s spine now says
432
+ it died.** A real run's `author-<runid>.jsonl` held exactly ONE line, `author-start`, and
433
+ stopped: the paid pipeline threw, the error reached the operator's terminal as an unhandled
434
+ rejection, and the spine's record of the run was **byte-for-byte what a run still IN FLIGHT
435
+ looks like**. A log that cannot tell a death from a hang is not a record of either — which is
436
+ why this is a fix to the record and not a new feature, even though the mechanism is new
437
+ records. The fallible span — from just after `author-start` through the end of the main flow, a
438
+ real scout, a real model call and a real toolchain per close stage — is now ONE try/catch, and
439
+ nothing above it is inside, deliberately: the argv/config `die()` paths run before the spine
440
+ file exists, and a crash record with no spine to land in is a record nobody can read (those
441
+ still stop loud on stderr and 2). **The catch RETRIES NOTHING and SWALLOWS NOTHING**: the whole
442
+ error reaches stderr FIRST and verbatim — before the two writes that could themselves fail,
443
+ because a diagnosis that only arrives if the disk is writable has a dependency nobody asked for
444
+ — and only then is it written down as `author-crash` plus `author-end {outcome:'crashed'}`.
445
+ `crashRecord` lives in `scripts/author-readout.mjs`, where the runner's other
446
+ untestable-in-place rules live: `name`/`message`/`code` go through `redactSecrets`, and the
447
+ STACK — the evidence, and the string most likely to quote a credential out of a URL or an env
448
+ value into a file that outlives the run — goes through `scrubRaw`, the ONE persist boundary,
449
+ which redacts over the same `SECRET_PATTERNS` inventory the validator reds on and bounds it with
450
+ the bound announcing its own size (F28). One level of `cause`, no chain-walking. A non-Error
451
+ throw is recorded honestly rather than coerced into an Error shape. The detail is said ONCE, on
452
+ the crash record; `author-end` carries only the outcome, because two hand-spelled copies of one
453
+ fact are two instruments that can disagree (this file already paid for that in the
454
+ cap-halt/pricing-red type). The spine write is itself guarded — a crash handler that crashes
455
+ destroys the report it was built to make (F70) — and a failed append says so out loud while the
456
+ original error still stands. **NEW EXIT CODE 4**, distinct from 1 (a refusal or a failed gate),
457
+ 2 (operator/config) and 3 (a leak): a crash is none of those, and sharing a code with a refusal
458
+ would file a bug as a result. The leak scan still OVERRIDES a 4 deliberately — a written secret
459
+ is the harder line, and the crash keeps both of its own louder channels. **No reader changes**:
460
+ `classifyIncidents` (`src/ledger.js`) keys on event types it does not emit, so both new records
461
+ fall through every branch and are simply not counted — uncounted, never miscounted, which is
462
+ the fail-safe direction.
463
+ - **A KILLED authoring run kept losing 100% of its own money record — it now leaves a body, the
464
+ way a crash does.** The crash catch above covers a THROW and never a SIGNAL, so a `^C` on a run
465
+ that merely looked hung ended the process with the spine holding one line, `author-start`, and
466
+ with the spend existing only inside the cost book, which dies with the process (F6/F12: a
467
+ halted attempt's spend was invisible by 300×). `scripts/run-author.mjs` now handles `SIGINT`,
468
+ `SIGTERM` and `SIGHUP` by emitting `author-killed {signal, phase, …cost}` plus
469
+ `author-end {outcome:'killed'}` — `appendFileSync` inside `emit` is synchronous, so the record
470
+ is on disk before the process goes — then removing its own listener and re-raising the signal,
471
+ because the honest exit code for a signal death is 128+signo and any code set here would file a
472
+ killed run under a name this runner's vocabulary already spends on something else. **SIGKILL is
473
+ not covered and is not pretended otherwise**: it is uncatchable, and there is no handler to
474
+ write for it. The killed report and the live running total read ONE list (`metered`) through
475
+ `tallyCalls` — the same reader the library's own cost book uses — rather than a second
476
+ hand-spelled accumulator, because this file has already paid once for a pair of instruments
477
+ over one fact (the cap-halt/pricing-red type); `costUsd` rides as `null` when a call was
478
+ unpriced, so the known half is reported as a `≥` floor instead of `?? 0` laundering unknown
479
+ into $0. Both reporting seams are wrapped best-effort (`try/catch`) on purpose: progress
480
+ reporting on a PAID run must never take down the work being paid for (F70), and the run's real
481
+ records — `authored.json`, the crash catch, `author-end` — are all downstream and unaffected.
482
+ - **`scripts/run-interview.mjs` stops making an offer that can only be refused.** With no
483
+ `ANTHROPIC_API_KEY` in the shell, `run-author.mjs` exits 2 at its own door before a spine
484
+ exists, so the *"Run it now? [y/N]"* question had exactly one possible outcome and spent a
485
+ person's attention on a choice they did not have. Unkeyed, the question is not asked at all and
486
+ the closing line says which state it is in (*"Not offered — there is no key in this shell to
487
+ run it with"*) rather than reporting a refusal typed on the operator's behalf. What is printed
488
+ instead is what is actionable: the exact command, and one line saying to set the key in the
489
+ shell from their own secret store — deliberately WITHOUT a specific incantation, because which
490
+ store a person keeps a key in is theirs, and the one thing this script must never do is put a
491
+ key anywhere a command line can be read from.
492
+ - **A run that landed its own terminal now dates its own stop (N4 surface §1.6).** The resumed
493
+ leg's `deathAt` preferred the watchdog's kill record whenever one existed. That is right for a
494
+ run that was KILLED — the process was alive, silently, right up to the signal, and billing only
495
+ to its last emitted event under-reports the wall it really burnt — and wrong for a run that
496
+ ENDED ITSELF. A `hitl-pause` is the second kind: a clean terminal a person may answer days
497
+ later, and any record dated after it bills their deciding time to the run's wall. The POC
498
+ measured the shape — a report 45 days after the pause put `RESUME_WALL_MS` at 0 and doomed the
499
+ resumed leg before it opened, on a run that had barely started. So the preference order is not
500
+ *"the later record wins"* but *"the run's own record wins when there is one"*: a spine carrying
501
+ a `job-end` returns `null`, which hands `readResume` back its own documented default (the last
502
+ event's timestamp, which for a clean terminal IS the terminal). The order moved out of
503
+ `scripts/run-u.mjs` into `deathAtOf` (`scripts/u-readout.mjs`) so a test can reach it at all,
504
+ with the killed-run case kept as the control, and an unreadable watchdog stamp reads as UNKNOWN
505
+ rather than handing a `NaN` to a fold (F6).
506
+ - **A pause is a PHASE — the resume readout stops walking off the end of the plan (N4 #9).** hamr
507
+ read `at step 2 of 1 "(unknown)"` on the first paused resume preview he drove. A pause happens
508
+ AFTER the plan's steps, at the close's human stage, and the checkpoint a person actually meets
509
+ reads `phase:'steps'` with every step green (the pause site emits no `outer-close` of its own),
510
+ so the `at` line's step arithmetic ran past the plan and named an id nobody drafted. The same
511
+ walk-off was reachable one terminal over with no person involved: a kill between the last
512
+ `step-end` and the `outer-close` leaves exactly that shape and printed `step 3 of 2`. The
513
+ rendering moves to `resumeAtLines` (`scripts/u-readout.mjs`, where a test can reach it) and
514
+ holds one rule — a step count never exceeds the plan, and the phase is said in words: the human
515
+ review, the close and its fix loop, the close after an exhausted plan, or a named next step.
516
+ Rendering only; it decides nothing and bounds nothing.
517
+
8
518
  ## [0.10.0] — 2026-08-13
9
519
 
10
520
  ### Added
package/README.md CHANGED
@@ -32,20 +32,31 @@ bareloop takes that principle and makes it the product. You describe a repeated,
32
32
  job and what *done* means for it. The agent handles the rest, under verification it never
33
33
  gets to touch.
34
34
 
35
- ## Three kinds of done
35
+ ## Two kinds of done
36
36
 
37
- Jobs differ by who or what decides the result is good. That difference is the product's
38
- spine, so you choose it when you create the job.
37
+ Jobs differ by what decides the result is good. That difference is the product's spine, so you
38
+ choose it when you create the job.
39
39
 
40
40
  **green** — a predicate decides. Tests pass, the build exits clean, the type checker is
41
41
  quiet.
42
42
  > *"Fix the failing suite."* · *"Migrate this package to strict types."*
43
43
 
44
- **softgreen** — an assessment decides against a rubric.
45
- > *"Reserve a ticket from SF to LA under $400."*
44
+ **softgreen** — an assessment decides, against a rubric you wrote and signed. You say what
45
+ separates a pass from a fail, and give one example of each; the judge only quotes back what it
46
+ finds in the real work, and fixed rules — not the judge — render the verdict.
47
+ > *"Reserve a ticket from SF to LA under $400."* · *"Improve my resume."*
46
48
 
47
- **hitl** — a human makes the final call.
48
- > *"Improve my resume."*
49
+ ## You have the last word
50
+
51
+ Every judged run ends at a **review door** (a purely mechanical green finishes on its own —
52
+ ask for the door with `--review-door`): here is what changed, here is what every check said,
53
+ here is what I'm asking you. **Accept** it, **rerun** it — what you type is the feedback the next
54
+ attempt works from, funded fresh — or **pause** it and decide another day. A pause costs
55
+ nothing, keeps everything, and picks up from the start of the last step whenever you come back.
56
+ Never come back, and it quietly expires on its own.
57
+
58
+ The door never rewrites the run's own verdict. A green stays green in the record whatever you
59
+ decide about it; your answer says what happens to the work, not what the work scored.
49
60
 
50
61
  ## Why the agent authors the workflow
51
62