bareloop 0.9.0 → 0.11.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 +555 -0
- package/README.md +111 -117
- package/bareloop.context.md +314 -24
- package/package.json +3 -3
- package/src/authorflow.js +300 -35
- package/src/authoring.js +290 -33
- package/src/authorjob.js +59 -7
- package/src/authorscout.js +83 -10
- package/src/bridges.js +15 -2
- package/src/clock.js +21 -3
- package/src/declaredclose.js +88 -11
- package/src/index.js +11 -1
- package/src/job.js +7 -2
- package/src/kinds.js +292 -20
- package/src/ledger.js +29 -2
- package/src/planrun.js +404 -29
- package/src/ralph.js +14 -0
- package/src/reuse.js +247 -9
- package/src/root.js +1 -1
- package/src/run.js +12 -5
- package/src/selection.js +1 -1
- package/src/text.js +79 -0
- package/src/tools.js +11 -0
- package/src/validate.js +168 -11
- package/types/authorflow.d.ts +135 -17
- package/types/authoring.d.ts +161 -35
- package/types/authorjob.d.ts +11 -2
- package/types/authorscout.d.ts +22 -3
- package/types/clock.d.ts +5 -2
- package/types/declaredclose.d.ts +64 -6
- package/types/index.d.ts +4 -4
- package/types/job.d.ts +5 -0
- package/types/kinds.d.ts +134 -10
- package/types/planrun.d.ts +61 -2
- package/types/reuse.d.ts +75 -1
- package/types/run.d.ts +12 -2
- package/types/text.d.ts +67 -0
- package/types/validate.d.ts +74 -9
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,561 @@ 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.11.0] — 2026-08-18
|
|
9
|
+
|
|
10
|
+
### Changed (2026-08-18 — the review door's third button)
|
|
11
|
+
|
|
12
|
+
- **The three doors are `accept` / `rerun` / `pause`; `cancel` is DELETED as a concept.** hamr:
|
|
13
|
+
*"what's the point of cancel anyways? pause can resume — that would be more honest"* and
|
|
14
|
+
*"rerun implies I don't like it, go again"*. A `pause` answer runs nothing, spends nothing,
|
|
15
|
+
buys no worker round, never opens the fix loop and does NOT consume the one-shot rerun
|
|
16
|
+
allowance: it mints the ordinary `hitl-pause` checkpoint with an explicit
|
|
17
|
+
`humanDecision: 'pause'` and leaves the run resumable from the start of its last step. An
|
|
18
|
+
unresumed pause simply expires under the existing 60-day `PAUSE_TTL_MS` — **that expiry is
|
|
19
|
+
what cancel used to be**, without forcing a permanent decision. `HUMAN_DECISIONS` is the
|
|
20
|
+
enumerated set the surface derives from, so `cancel` is now inexpressible rather than
|
|
21
|
+
rejected after the fact; `hitl-decision-red` still refuses any word that is not a door.
|
|
22
|
+
- **`hitl-cancel` is no longer mintable and the constant is gone** (removed from
|
|
23
|
+
`src/declaredclose.js` and the package exports). The ledger keeps the bare string in its
|
|
24
|
+
excluded-escalation set — the `gate-red` precedent — so a spine written before this change
|
|
25
|
+
still reads as governance rather than as a counted capability gap; no spine on disk carries
|
|
26
|
+
it (the door never fired live), so that entry is forward compatibility, not bookkeeping.
|
|
27
|
+
- **A human checkpoint row is classed `checkpoint`, not `casualty`** (`src/reuse.js`, both the
|
|
28
|
+
live try loop and the resume reconstruction). A casualty is a run that died; a checkpoint is
|
|
29
|
+
a run that is waiting on a person with nothing lost — every other reader already treated it
|
|
30
|
+
that way, and the row was the last place the machinery mis-described itself.
|
|
31
|
+
- **Runner surface (`scripts/` never ships):** `scripts/run-u.mjs --decide pause` answers the
|
|
32
|
+
door and launches nothing at all — it prints the decision, names the TTL and the ORIGINAL
|
|
33
|
+
runid, and exits having spent nothing. Deliberate: the runner writes one spine per leg, and a
|
|
34
|
+
leg that returns before drafting emits no `plan-accepted` and no `step-end`, so launching a
|
|
35
|
+
run to say "not now" would mint a runid whose own checkpoint is empty and re-pay for finished
|
|
36
|
+
steps on the next resume. Door rendering (`scripts/u-readout.mjs`) and the escalation
|
|
37
|
+
passthrough (`src/ralph.js`) carry the new third door.
|
|
38
|
+
- **Docs:** dated addendum on `docs/02-features/2026-08-17-softgreen-review-door-design.md`
|
|
39
|
+
(closed sections untouched), **PRD Addendum v1.72**, README's *"You have the last word"*,
|
|
40
|
+
`bareloop.context.md`'s terminal/decision surface, and `docs/01-product/LAYERS.md`.
|
|
41
|
+
|
|
42
|
+
### Docs (2026-08-17 — decisions only; NO code changed, and no build is authorized)
|
|
43
|
+
|
|
44
|
+
- **New design record `docs/02-features/2026-08-17-softgreen-review-door-design.md`, and PRD
|
|
45
|
+
Addendum v1.71: `hitl` is RETIRED as a verdict class, its pause becomes the REVIEW DOOR at
|
|
46
|
+
the end of EVERY run, and `soft-green` becomes the forward path.** The hitl slice below FIRED
|
|
47
|
+
live first and worked — the first hitl pause in programme history, 64 strict errors to 0, the
|
|
48
|
+
authored guard catching the cheat genre twice, evidence package and three doors and a stopped
|
|
49
|
+
clock (F105). hamr then retired the class for what it asks of a composer and of a person:
|
|
50
|
+
*"checkers are subjective human experience that they should grade for"*, and *"it's hard to
|
|
51
|
+
apply deterministic flow on a probabilistic throughput"*. **The machinery is re-homed, not
|
|
52
|
+
deleted.** The door never changes the loop's own verdict (*"it's important not to change the
|
|
53
|
+
loop self verdict"*): a green stays green in the ledger and the door records a disposition —
|
|
54
|
+
`accept` (and, on soft-green, the release of quarantined learning credit), `rerun` (the text
|
|
55
|
+
IS the gap), `cancel` (*"the verdict stands, but I'm not taking the merchandise"*). A rerun is
|
|
56
|
+
a **FRESH ENGAGEMENT** with its own money and time and two side-by-side counters, and **any**
|
|
57
|
+
wall or halt now records full state — time, money, strikes, notes/progress and any pending
|
|
58
|
+
human decision. `soft-green` gets a `judged-floor` kind on the LOCATE+DECIDE pattern (the
|
|
59
|
+
judge extracts facts with quotes from the real artifact; an arbiter-owned `decide()` renders
|
|
60
|
+
pass/fail; unsure = red), behind a signed rubric card (Q6) and a signed calibration set (Q7)
|
|
61
|
+
the whole pipe must grade correctly before the close is signable. **Nothing is built.**
|
|
62
|
+
- **F102, F103, F104 and F105 in `docs/FINDINGS.md`**, each grounded in today's spines under
|
|
63
|
+
`bareloop-patients/litectx-maintainer-bareloop/`: a pending rerun decision does not survive a
|
|
64
|
+
wall-halt → resume (two byte-identical pause records — the human paid the same decision
|
|
65
|
+
twice); the wall folds across legs, so a decide-time rerun inherited 87 seconds; the
|
|
66
|
+
catalogue cannot count documentation, so a composer measured a typecheck PROXY instead —
|
|
67
|
+
which is what retired the class; and the positive record of everything the first live hitl
|
|
68
|
+
loop proved, including that rerun DELIVERY works and rerun CONVERSION is still unproven-live.
|
|
69
|
+
- **`docs/01-product/LAYERS.md`** gains the review door, the two-verdict table and the next
|
|
70
|
+
rung; the **2026-08-13 N4 build record** (closed) gains a dated addendum pointing forward;
|
|
71
|
+
**README** and **`bareloop.context.md`** carry the direction change, the contract still
|
|
72
|
+
describing what the code on this branch actually does today.
|
|
73
|
+
|
|
74
|
+
### Added
|
|
75
|
+
|
|
76
|
+
- **N4 slice 1 — the `hitl` verdict class is ADMITTED, and the run PAUSES for a person.** A job
|
|
77
|
+
whose *done* needs someone to say so now runs through the same signed spec, the same
|
|
78
|
+
agent-authored plan and the same arbiter as a green job. The refusal was never one flag, so
|
|
79
|
+
every coupled site moved together: `LOCKED_VERDICTS` / `LOCKED_CLASSES` drop it,
|
|
80
|
+
`DECLARED_CLOSE_CLASSES` widens to `['hard','hitl']`, the class battery is the GREEN mechanical
|
|
81
|
+
battery inherited verbatim (hamr's OPEN-1 ruling: nothing a human stage cannot see), and the
|
|
82
|
+
interview gains a hitl question set (the green questions byte for byte, plus the one thing
|
|
83
|
+
they cannot supply — the question the signer actually answers). **`soft-green` stays
|
|
84
|
+
declared-but-locked**: its judged floor is slice 2, and a wobbly ruler must mint nothing.
|
|
85
|
+
- **`human-confirms` goes live as the one kind that measures nothing.** Its whole parameter
|
|
86
|
+
surface is `ask`; it cannot spawn, is never env-capable, is `offer: false` BY LAW (a
|
|
87
|
+
declaration that offers one is a validation red, and the arbiter's own bridge stamps the law
|
|
88
|
+
onto the stage the runner sees, so the agent can never compose `check-passes(<a person>)`), is
|
|
89
|
+
at-most-once, and must be the LAST stage — first-red-wins would otherwise silently delete every
|
|
90
|
+
mechanical stage behind it. It also SKIPS the seed-verdict read (ruling 8), and the skip is
|
|
91
|
+
recorded as a row rather than taken in silence.
|
|
92
|
+
- **Three new terminals, minted rather than borrowed.** `hitl-pause` is a decision-ready
|
|
93
|
+
CHECKPOINT carrying the evidence package (every mechanical stage's result, the close's own
|
|
94
|
+
question, and what the run changed) — never a bare "approve?"; `hitl-cancel` is terminal with
|
|
95
|
+
an explicit `gap: null`, no fix loop and no worker round; `hitl-decision-red` refuses a
|
|
96
|
+
decision the run cannot act on (a fourth door, a `rerun` with empty text, or a decision handed
|
|
97
|
+
to a close with no human stage) before anything is spent. All three are excluded from the
|
|
98
|
+
ledger's escalation counting for their own stated reasons, and none demotes a bridge. The
|
|
99
|
+
legacy `hitl-close` entry keeps its own meaning; nothing was renamed in passing.
|
|
100
|
+
- **The RETURN needs no new channel.** A resumed leg carrying the signer's `rerun` feeds their
|
|
101
|
+
words through the same seam `post.gap` uses — same bound, same scrub — and the run continues
|
|
102
|
+
under the folded wallet on an unbilled clock. The ruling is spent when the fix loop opens, so
|
|
103
|
+
the next machine-clean tree pauses for a second review instead of converting one sentence
|
|
104
|
+
forever, while a mechanical red in between is still converted normally.
|
|
105
|
+
- **New public exports**: `HUMAN_DECISIONS`, `SEED_EXEMPT_KINDS`, `normalizeHumanRuling`,
|
|
106
|
+
`NEVER_OFFERED_KINDS`, `HUMAN_PAUSE`, `CHECKPOINT_OUTCOMES`, `PAUSE_TTL_MS`,
|
|
107
|
+
`checkpointAgeGate`, `HITL_PAUSE`, `HITL_DECISION_RED`, `HUMAN_CHECKPOINTS`. The 60-day pause TTL and the
|
|
108
|
+
canonical checkpoint list live in the LIBRARY (hamr's OPEN-2 ruling) so the exported bundle
|
|
109
|
+
inherits them rather than every runner re-implementing them. The two terminal names are
|
|
110
|
+
spelled ONCE, in `src/declaredclose.js` beside the `HUMAN_PAUSE` close-verdict word they
|
|
111
|
+
translate, because four different readers key on them — the emitters (`src/planrun.js`), the
|
|
112
|
+
ledger's excluded-escalation set, the resume reader (`src/reuse.js`) and the runner script —
|
|
113
|
+
and hand-spelled copies of one terminal is how those readers come to disagree.
|
|
114
|
+
- **A registry guard that was only holding by accident**: an `already-green` terminal now mints
|
|
115
|
+
no bridge version for a NAMED reason (`green-predates-run`) rather than because no plan
|
|
116
|
+
happened to be on the spine — a hitl try that paused and came back with `accept` has its
|
|
117
|
+
predecessor's plan in the very same try window.
|
|
118
|
+
- **An authoring run says what it is doing while it is still doing it — three REPORTING seams,
|
|
119
|
+
`onPhase` / `onStage` / `onCall`.** The pipeline ran up to ~15 minutes between `author-start`
|
|
120
|
+
and its result with nothing on the terminal or the spine — a real survey ladder, a real
|
|
121
|
+
declaration ladder, and a real toolchain per close stage, all inside one await — and silence
|
|
122
|
+
is byte-for-byte what a hang looks like, so the operator's only lever was to kill a run that
|
|
123
|
+
might be working. The library REPORTS and the shell PRINTS, the same shape `runJob` → `runPlan`
|
|
124
|
+
already has: `onPhase` fires at the composition's boundaries in `authorCloseForJob`
|
|
125
|
+
(`src/authorjob.js`) and `authorClose` (`src/authorflow.js`) — `seed`, `scout`/`scout-done`,
|
|
126
|
+
`listing`/`listing-done`, `author`, `author-call`, `seed-read`/`seed-read-done`; `onStage`
|
|
127
|
+
(a new `Ctx` field in `src/kinds.js`) fires per seed-read stage, AFTER the stage lands, because
|
|
128
|
+
only then is there a verdict to carry; `onCall` fires from inside each of the two metered
|
|
129
|
+
recorders (`makeCostBook.add`, `runAuthorScout`'s `record`) rather than at the call sites, so a
|
|
130
|
+
paid call cannot be metered without its report going out. **Nothing here is consulted and
|
|
131
|
+
nothing decides**: `capStop` is still the one ceiling predicate, still asked between calls,
|
|
132
|
+
still reading the same entries — and every callback defaults to a no-op, so every existing
|
|
133
|
+
caller is byte-identical. Two deliberate omissions: `makeCostBook.absorb` does NOT fire
|
|
134
|
+
`onCall` (those calls were already metered and reported by whoever made them, and reporting
|
|
135
|
+
them twice would double the scout's spend in every reader downstream), and the `seed` phase is
|
|
136
|
+
announced only on the path that actually reads one, because a phase line for work nobody did is
|
|
137
|
+
the blind-instrument class one line wide. The renderer is `phaseLine` in
|
|
138
|
+
`scripts/author-readout.mjs` (a readout no test can reach is a readout nothing checks); it
|
|
139
|
+
prints WHAT is happening and never a fraction — a survey attempt has no progress and a suite
|
|
140
|
+
stage finishes when it finishes — and an unmeasured `durationMs` prints as `unknown`, never as
|
|
141
|
+
`0` (F6 in its time form). A phase the renderer does not know is printed rather than swallowed.
|
|
142
|
+
|
|
143
|
+
The runner half of the same slice landed beside it and is deliberately NOT in the tarball
|
|
144
|
+
(`scripts/` never ships): `scripts/run-u.mjs` gains `--decide accept|rerun|cancel` (+ `--text`)
|
|
145
|
+
on the existing `--approve <specHash>` signature, renders the pause's evidence package plus the
|
|
146
|
+
line-level diff, consumes `CHECKPOINT_OUTCOMES` and `checkpointAgeGate` instead of re-spelling
|
|
147
|
+
either, and prefers the run's own terminal over a stale watchdog record when folding the wall —
|
|
148
|
+
so a person's deciding time is never billed to it. What an adopter's own runner has to do is in
|
|
149
|
+
`bareloop.context.md`.
|
|
150
|
+
|
|
151
|
+
Beside it, `scripts/run-interview.mjs` — **the terminal interview, and the answer to hand-writing
|
|
152
|
+
an `answers.json`**, which is precisely the SWE tax this product exists to refuse. It asks the
|
|
153
|
+
LIBRARY's frozen questions ONE AT A TIME, **calls no model and costs $0**, and writes
|
|
154
|
+
`answers.json` (the shape `run-author.mjs` consumes) plus `specdraft.json` (the operator half — no
|
|
155
|
+
close and no `verdictType`, because those are what `run-author` authors). The draft is checked for
|
|
156
|
+
$0 by `validateJob`, the SAME validator that will judge the resolved spec, so a draft that cannot
|
|
157
|
+
become a spec says so before a provider is ever built. It then OFFERS to spawn the paid
|
|
158
|
+
`run-author.mjs` under its OWN `--budget` ceiling — **default NO** (`[y/N]`, the same lean the
|
|
159
|
+
pause's three doors take: the answer that costs nothing is the one you get by saying nothing), and
|
|
160
|
+
it prints the exact command either way. The two ceilings are kept apart out loud: `--budget` is
|
|
161
|
+
what the AUTHORING call may spend, `budgetUsd` is what the RUN may spend, signed into the spec.
|
|
162
|
+
|
|
163
|
+
And `scripts/run-u.mjs` gains a `litectx-maintainer` JOBS row — **N4's hitl PROVING job, dark
|
|
164
|
+
since the legacy `steps[]` deletion took its old spec's path with it**, back as a plan-flow job
|
|
165
|
+
with an AUTHORED close. `jobs/litectx-maintainer.json` **DOES NOT EXIST, and the row does not
|
|
166
|
+
create it**: the spec is authored live through `run-interview.mjs` → `run-author.mjs` and signed
|
|
167
|
+
by hamr, goal and answers included, and until it lands **the runner REFUSES BY NAME** — a row is
|
|
168
|
+
the runner's half of a job, never the signer's. The patient is a COPY at the convention this table
|
|
169
|
+
already uses, and the seed is that copy's own HEAD (litectx v0.32.0, `115213d`).
|
|
170
|
+
|
|
171
|
+
### Changed
|
|
172
|
+
|
|
173
|
+
- **Both suite pins move up a minor — `bare-agent ^0.35.0 → ^0.36.0`, `bareguard ^0.12.0 →
|
|
174
|
+
^0.13.0` — because on 0.x a caret locks the MINOR**, so the old pins admitted neither release
|
|
175
|
+
and the bump is the only way to consume them. **bare-agent 0.36 delivers BA-20**, the
|
|
176
|
+
productized decisive judge this queue reopened on: `judge` (verbatim request + one structured
|
|
177
|
+
artifact → `honored`/`broke` with a mechanical `where`, truncated/unparseable responses as
|
|
178
|
+
DISTINCT flagged outcomes excluded from graded denominators, composing **around** a provider
|
|
179
|
+
and never inside the Loop), the calibration harness (`calibrate`, `CALIBRATION_CASES`,
|
|
180
|
+
`INJECTION_BATTERY`, `constantHonored` — the negative control that lets the harness fail), and
|
|
181
|
+
the pure `judgeToAnnotation` mapping into bareguard's shipped fact envelope. **bareguard
|
|
182
|
+
0.13.0** is the companion hardening: `gate.annotate` now REJECTS malformed facts into distinct
|
|
183
|
+
`annotate_malformed` rows instead of normalizing them into silently-`honored` ones, `verdict`
|
|
184
|
+
goes through the audit redactor (it was written RAW — a real secret-leak fix), `meta`'s
|
|
185
|
+
1000-byte bound became a decoupled copy, and `__proto__` keys are dropped at every depth.
|
|
186
|
+
**That behaviour change is zero-cost to bareloop today**: it imports only `Gate` and `redact`
|
|
187
|
+
and has **no `gate.annotate` call site** at all. `npm ls` resolves `bare-agent@0.36.1` +
|
|
188
|
+
`bareguard@0.13.0`, deduped to one shared copy, and **full fresh gates are green against the
|
|
189
|
+
installed versions** — `npm test` 1569/1569 exit 0, `typecheck` clean, `build:types` clean.
|
|
190
|
+
**The judge is NOT consumed here**: its consumer is the N4 (soft-green) rung, so wiring it into
|
|
191
|
+
a `judged` close stage and executing BA-20's acceptance criteria live (running `calibrate`
|
|
192
|
+
against the frozen floor and naming the hash it graded) are deferred to N4's opening — a
|
|
193
|
+
verified delivery, not a verified acceptance.
|
|
194
|
+
|
|
195
|
+
- **The interview loses its repo question and widens its scope question — hamr drove the terminal
|
|
196
|
+
himself and ruled on the wording (2026-08-15).** `GREEN_QUESTIONS` drops *"Is there a code repo I
|
|
197
|
+
can look at? Where?"*: the repository is already MANDATORY STRUCTURED input (`runInterview`'s
|
|
198
|
+
`repoPath`, taken from `--patient`, without which nothing starts), so asking a person to re-type
|
|
199
|
+
a path the machine is holding is the SWE tax this product exists to refuse — and it invites a
|
|
200
|
+
second, drifting answer for one fact (hamr's own live answer was *"Yes — the patient itself."*).
|
|
201
|
+
Question 2 gains the other half of the same decision — *"…And which files should the work read or
|
|
202
|
+
draw from (they stay untouched)?"* The sets **renumber contiguously from 1**, so the green set is
|
|
203
|
+
five questions and hitl's signer-ask is now **6**, not 7. Nothing hardcodes a count: `run-author`,
|
|
204
|
+
`run-interview` and the suite all read `requiredAnswersFor(cls)`. **Adopter note: an
|
|
205
|
+
`answers.json` written before this change is SILENTLY MISREAD** — its key `6` was the repo answer
|
|
206
|
+
and now reads as hitl's signer-ask, and its key `7` is dropped as unasked. Re-run the interview
|
|
207
|
+
(it is $0 and calls no model) rather than hand-editing.
|
|
208
|
+
- **The interview's own prose stops citing finding numbers and says the mechanics out loud.** The
|
|
209
|
+
goal prompt no longer prints *"F87"* at a person who has never read the findings — the rule is
|
|
210
|
+
unchanged and the price is now named as a price. The fence prompt says what a fence IS (writable
|
|
211
|
+
files, everything else read-only), that patterns are repo-root-relative, and that absolute paths
|
|
212
|
+
are refused because the run works on a copy. The multi-line hint spells the keystroke: *press
|
|
213
|
+
Enter on an empty line, i.e. Enter twice*.
|
|
214
|
+
- **Suspended minutes stop charging a signed wall — the run clock is MONOTONIC** (hamr's ruling,
|
|
215
|
+
2026-08-15: *"if computer suspended, time shouldn't count."*). `createClock`'s default source
|
|
216
|
+
moves from `Date.now` — CLOCK_REALTIME, which counts every minute the machine was asleep — to
|
|
217
|
+
`performance.now`, which reads libuv's `uv_hrtime` and so CLOCK_MONOTONIC, which per
|
|
218
|
+
`clock_gettime(2)` *"does not count time that the system is suspended"*. That was not
|
|
219
|
+
hypothetical: an s2idle suspend landing mid-run charged **45 phantom minutes against a
|
|
220
|
+
45-minute wall**, and the same exposure covers an NTP step or a hand-set date. Nothing corrects
|
|
221
|
+
for a suspend after the fact; the reading simply never includes one. **The epoch change is safe
|
|
222
|
+
by construction**: every use of `now()` in `src/clock.js` is RELATIVE (`now() - startedAt`) and
|
|
223
|
+
no value derived from this clock is ever published as a calendar timestamp — `report()` carries
|
|
224
|
+
durations only, and the `at:`/`ts:` stamps a run writes come from their own `Date.now` seams in
|
|
225
|
+
`src/reuse.js`, which stay calendar-correct on purpose. `now` is still injectable, which is what
|
|
226
|
+
every clock test drives. Three runJob-level wall tests mocked `Date` because that is the only
|
|
227
|
+
seam `runJob` exposes, and the monotonic source made them BLIND — they redded `provider-red` /
|
|
228
|
+
`plan-red` instead of `wall-halt`, which is the failing-first evidence that the mock was
|
|
229
|
+
load-bearing; they now go through one shared helper (`tests/helpers.js`) that pins
|
|
230
|
+
`performance.now` to the mocked `Date` and restores it after. **KNOWN LIMITATION, recorded
|
|
231
|
+
rather than fixed** (PRD Addendum v1.67, hamr: *"leave it and mark it in prd as possible known
|
|
232
|
+
limitations"*): the OUTSIDE watchdog still measures silence in CALENDAR time, so a long suspend
|
|
233
|
+
can still read to it as a stall. The half that charges money is fixed; the half that reads
|
|
234
|
+
silence is not.
|
|
235
|
+
- **`scripts/run-u.mjs` PRINTS the sleep-inhibitor launch line** the way `scripts/run-reuse.mjs`
|
|
236
|
+
has since F72, instead of carrying it as a comment — a rule nobody reads at the moment they
|
|
237
|
+
launch (and like every runner change here, `scripts/` never ships in the tarball). A suspend
|
|
238
|
+
freezes EVERY guard: the outside watchdog is a POLLER, so it cannot observe — let alone kill — a
|
|
239
|
+
run whose machine is asleep, and after the monotonic clock above the run's own wall does not
|
|
240
|
+
count those minutes either. A suspended run is therefore simply UNWATCHED for as long as it
|
|
241
|
+
sleeps, and the launch banner is the last moment the operator can still do something about it.
|
|
242
|
+
The printed line is `systemd-inhibit --what=idle:sleep --why="bareloop u run" env …`, with `env`
|
|
243
|
+
before the key assignment on purpose: `systemd-inhibit` execs a COMMAND, and a bare `VAR=value`
|
|
244
|
+
prefix is shell syntax it would try to run as one — the key still rides an env assignment and
|
|
245
|
+
never argv. A pause with no ruling prints the line prefixed to NONE of its three doors, because
|
|
246
|
+
prefixing one would quietly recommend it and this surface's lean runs the other way.
|
|
247
|
+
- **The hitl terminals get one canonical spelling, and the pause screen gets one renderer**
|
|
248
|
+
(`/code-review medium`, findings 2 and 3). The terminal names were hand-spelled string
|
|
249
|
+
literals at every reader: 9 sites in `src/planrun.js`, the ledger's `EXCLUDED_ESCALATIONS`
|
|
250
|
+
set, `src/reuse.js`, the `src/index.js` re-export and 7 sites in `scripts/run-u.mjs`. They now
|
|
251
|
+
all import the constants exported from `src/declaredclose.js` (the `hitl-cancel` constant
|
|
252
|
+
introduced with them is gone again — see the review-door entry above). Beside it,
|
|
253
|
+
`printPauseEvidence()` in `scripts/run-u.mjs` becomes the SINGLE assembly point for both pause
|
|
254
|
+
screens — the resume preview and the fresh-pause terminal — so the fallback rule cannot drift
|
|
255
|
+
between them: with a pause record the package is the close's own ask plus the diff of what
|
|
256
|
+
changed; without one it says so plainly, rather than rendering the bare "approve?" that ruling
|
|
257
|
+
2 forbids. Finding 1 — `runReuse` classing a `hitl-pause` as a casualty and buying another try
|
|
258
|
+
— was parked as arbiter territory when this bullet was first written, and has since been FIXED
|
|
259
|
+
on this same branch: `d183c95` gave the checkpoint its hard stop (`hardStop` reads
|
|
260
|
+
`HUMAN_CHECKPOINTS` before the retry table, so the loop hands the run back rather than re-asking
|
|
261
|
+
a question already put to someone who has not answered), and `4e0ab94` gave the ROW its own
|
|
262
|
+
`checkpoint` class, so the machinery no longer mis-describes a waiting run as one that died.
|
|
263
|
+
|
|
264
|
+
### Fixed
|
|
265
|
+
|
|
266
|
+
- **`--lang` given with no value no longer poisons the command the interview prints.** `arg()`
|
|
267
|
+
returns the EMPTY STRING for a flag typed with nothing after it and `?? 'js'` passes an empty
|
|
268
|
+
string straight through, so the printed next-step command collapsed to `--lang --budget 2.5` —
|
|
269
|
+
copy-paste it and `run-author` reads the word `--budget` as its language. Both scripts now stop
|
|
270
|
+
LOUD on a present-but-empty `--lang` (an ABSENT flag still takes the default, the same rule the
|
|
271
|
+
money ceilings are parsed by).
|
|
272
|
+
- **A crashed authoring run left one line and no body — `scripts/run-author.mjs`'s spine now says
|
|
273
|
+
it died.** A real run's `author-<runid>.jsonl` held exactly ONE line, `author-start`, and
|
|
274
|
+
stopped: the paid pipeline threw, the error reached the operator's terminal as an unhandled
|
|
275
|
+
rejection, and the spine's record of the run was **byte-for-byte what a run still IN FLIGHT
|
|
276
|
+
looks like**. A log that cannot tell a death from a hang is not a record of either — which is
|
|
277
|
+
why this is a fix to the record and not a new feature, even though the mechanism is new
|
|
278
|
+
records. The fallible span — from just after `author-start` through the end of the main flow, a
|
|
279
|
+
real scout, a real model call and a real toolchain per close stage — is now ONE try/catch, and
|
|
280
|
+
nothing above it is inside, deliberately: the argv/config `die()` paths run before the spine
|
|
281
|
+
file exists, and a crash record with no spine to land in is a record nobody can read (those
|
|
282
|
+
still stop loud on stderr and 2). **The catch RETRIES NOTHING and SWALLOWS NOTHING**: the whole
|
|
283
|
+
error reaches stderr FIRST and verbatim — before the two writes that could themselves fail,
|
|
284
|
+
because a diagnosis that only arrives if the disk is writable has a dependency nobody asked for
|
|
285
|
+
— and only then is it written down as `author-crash` plus `author-end {outcome:'crashed'}`.
|
|
286
|
+
`crashRecord` lives in `scripts/author-readout.mjs`, where the runner's other
|
|
287
|
+
untestable-in-place rules live: `name`/`message`/`code` go through `redactSecrets`, and the
|
|
288
|
+
STACK — the evidence, and the string most likely to quote a credential out of a URL or an env
|
|
289
|
+
value into a file that outlives the run — goes through `scrubRaw`, the ONE persist boundary,
|
|
290
|
+
which redacts over the same `SECRET_PATTERNS` inventory the validator reds on and bounds it with
|
|
291
|
+
the bound announcing its own size (F28). One level of `cause`, no chain-walking. A non-Error
|
|
292
|
+
throw is recorded honestly rather than coerced into an Error shape. The detail is said ONCE, on
|
|
293
|
+
the crash record; `author-end` carries only the outcome, because two hand-spelled copies of one
|
|
294
|
+
fact are two instruments that can disagree (this file already paid for that in the
|
|
295
|
+
cap-halt/pricing-red type). The spine write is itself guarded — a crash handler that crashes
|
|
296
|
+
destroys the report it was built to make (F70) — and a failed append says so out loud while the
|
|
297
|
+
original error still stands. **NEW EXIT CODE 4**, distinct from 1 (a refusal or a failed gate),
|
|
298
|
+
2 (operator/config) and 3 (a leak): a crash is none of those, and sharing a code with a refusal
|
|
299
|
+
would file a bug as a result. The leak scan still OVERRIDES a 4 deliberately — a written secret
|
|
300
|
+
is the harder line, and the crash keeps both of its own louder channels. **No reader changes**:
|
|
301
|
+
`classifyIncidents` (`src/ledger.js`) keys on event types it does not emit, so both new records
|
|
302
|
+
fall through every branch and are simply not counted — uncounted, never miscounted, which is
|
|
303
|
+
the fail-safe direction.
|
|
304
|
+
- **A KILLED authoring run kept losing 100% of its own money record — it now leaves a body, the
|
|
305
|
+
way a crash does.** The crash catch above covers a THROW and never a SIGNAL, so a `^C` on a run
|
|
306
|
+
that merely looked hung ended the process with the spine holding one line, `author-start`, and
|
|
307
|
+
with the spend existing only inside the cost book, which dies with the process (F6/F12: a
|
|
308
|
+
halted attempt's spend was invisible by 300×). `scripts/run-author.mjs` now handles `SIGINT`,
|
|
309
|
+
`SIGTERM` and `SIGHUP` by emitting `author-killed {signal, phase, …cost}` plus
|
|
310
|
+
`author-end {outcome:'killed'}` — `appendFileSync` inside `emit` is synchronous, so the record
|
|
311
|
+
is on disk before the process goes — then removing its own listener and re-raising the signal,
|
|
312
|
+
because the honest exit code for a signal death is 128+signo and any code set here would file a
|
|
313
|
+
killed run under a name this runner's vocabulary already spends on something else. **SIGKILL is
|
|
314
|
+
not covered and is not pretended otherwise**: it is uncatchable, and there is no handler to
|
|
315
|
+
write for it. The killed report and the live running total read ONE list (`metered`) through
|
|
316
|
+
`tallyCalls` — the same reader the library's own cost book uses — rather than a second
|
|
317
|
+
hand-spelled accumulator, because this file has already paid once for a pair of instruments
|
|
318
|
+
over one fact (the cap-halt/pricing-red type); `costUsd` rides as `null` when a call was
|
|
319
|
+
unpriced, so the known half is reported as a `≥` floor instead of `?? 0` laundering unknown
|
|
320
|
+
into $0. Both reporting seams are wrapped best-effort (`try/catch`) on purpose: progress
|
|
321
|
+
reporting on a PAID run must never take down the work being paid for (F70), and the run's real
|
|
322
|
+
records — `authored.json`, the crash catch, `author-end` — are all downstream and unaffected.
|
|
323
|
+
- **`scripts/run-interview.mjs` stops making an offer that can only be refused.** With no
|
|
324
|
+
`ANTHROPIC_API_KEY` in the shell, `run-author.mjs` exits 2 at its own door before a spine
|
|
325
|
+
exists, so the *"Run it now? [y/N]"* question had exactly one possible outcome and spent a
|
|
326
|
+
person's attention on a choice they did not have. Unkeyed, the question is not asked at all and
|
|
327
|
+
the closing line says which state it is in (*"Not offered — there is no key in this shell to
|
|
328
|
+
run it with"*) rather than reporting a refusal typed on the operator's behalf. What is printed
|
|
329
|
+
instead is what is actionable: the exact command, and one line saying to set the key in the
|
|
330
|
+
shell from their own secret store — deliberately WITHOUT a specific incantation, because which
|
|
331
|
+
store a person keeps a key in is theirs, and the one thing this script must never do is put a
|
|
332
|
+
key anywhere a command line can be read from.
|
|
333
|
+
- **A run that landed its own terminal now dates its own stop (N4 surface §1.6).** The resumed
|
|
334
|
+
leg's `deathAt` preferred the watchdog's kill record whenever one existed. That is right for a
|
|
335
|
+
run that was KILLED — the process was alive, silently, right up to the signal, and billing only
|
|
336
|
+
to its last emitted event under-reports the wall it really burnt — and wrong for a run that
|
|
337
|
+
ENDED ITSELF. A `hitl-pause` is the second kind: a clean terminal a person may answer days
|
|
338
|
+
later, and any record dated after it bills their deciding time to the run's wall. The POC
|
|
339
|
+
measured the shape — a report 45 days after the pause put `RESUME_WALL_MS` at 0 and doomed the
|
|
340
|
+
resumed leg before it opened, on a run that had barely started. So the preference order is not
|
|
341
|
+
*"the later record wins"* but *"the run's own record wins when there is one"*: a spine carrying
|
|
342
|
+
a `job-end` returns `null`, which hands `readResume` back its own documented default (the last
|
|
343
|
+
event's timestamp, which for a clean terminal IS the terminal). The order moved out of
|
|
344
|
+
`scripts/run-u.mjs` into `deathAtOf` (`scripts/u-readout.mjs`) so a test can reach it at all,
|
|
345
|
+
with the killed-run case kept as the control, and an unreadable watchdog stamp reads as UNKNOWN
|
|
346
|
+
rather than handing a `NaN` to a fold (F6).
|
|
347
|
+
- **A pause is a PHASE — the resume readout stops walking off the end of the plan (N4 #9).** hamr
|
|
348
|
+
read `at step 2 of 1 "(unknown)"` on the first paused resume preview he drove. A pause happens
|
|
349
|
+
AFTER the plan's steps, at the close's human stage, and the checkpoint a person actually meets
|
|
350
|
+
reads `phase:'steps'` with every step green (the pause site emits no `outer-close` of its own),
|
|
351
|
+
so the `at` line's step arithmetic ran past the plan and named an id nobody drafted. The same
|
|
352
|
+
walk-off was reachable one terminal over with no person involved: a kill between the last
|
|
353
|
+
`step-end` and the `outer-close` leaves exactly that shape and printed `step 3 of 2`. The
|
|
354
|
+
rendering moves to `resumeAtLines` (`scripts/u-readout.mjs`, where a test can reach it) and
|
|
355
|
+
holds one rule — a step count never exceeds the plan, and the phase is said in words: the human
|
|
356
|
+
review, the close and its fix loop, the close after an exhausted plan, or a named next step.
|
|
357
|
+
Rendering only; it decides nothing and bounds nothing.
|
|
358
|
+
|
|
359
|
+
## [0.10.0] — 2026-08-13
|
|
360
|
+
|
|
361
|
+
### Added
|
|
362
|
+
|
|
363
|
+
- **The close-AUTHORING pipeline gets a money ceiling** (`--budget`, `ceilingUsd`) — the lever
|
|
364
|
+
parked at v0.9.0 as arbiter territory, approved by hamr. Until now the flow **metered** spend
|
|
365
|
+
and nothing **bounded** it: the runner printed a total and no number anywhere could stop a
|
|
366
|
+
call. It has **NO DEFAULT** — omitting it runs UNBOUNDED and the runner **prints that** before
|
|
367
|
+
the provider is built, because a defaulted cap is a silent second ceiling (the `maxWallMs`
|
|
368
|
+
precedent) and an unbounded run must be a visible operator choice. A malformed value is an
|
|
369
|
+
error, never a silent fall back to unbounded. The ceiling **binds BETWEEN metered calls** —
|
|
370
|
+
the survey's attempts and its F59 recovery round, and the declaration loop's author call,
|
|
371
|
+
each revise, and each malformed-emission retry — because a cap that binds mid-call kills the
|
|
372
|
+
row before it can be graded (F45). ONE number reaches BOTH paid seams, and spend already
|
|
373
|
+
incurred folds in, so re-entering cannot silently widen it. **F6 keeps its own axis**: a
|
|
374
|
+
null/unknown cost never counts as $0, so spend that cannot be *known* stops as `pricing-red`
|
|
375
|
+
rather than passing silently — while known spend at or over the cap is `cap-halt` even when
|
|
376
|
+
the total is unknown, since that breach is certain on the priced half alone. The stop is a
|
|
377
|
+
governance stop, not an error: it names the cap and the spend, nothing retries, partial
|
|
378
|
+
artifacts stay on disk, and it is DISTINCT from `artifact-red` and `provider-red`. A survey
|
|
379
|
+
the ceiling stopped is named a money stop (`budgetStop`, typed cause `not-funded`, outside
|
|
380
|
+
`SCOUT_RETRY_CAUSES`) instead of blaming a model that never spoke. One predicate, one
|
|
381
|
+
spelling (`capStop`/`tallyCalls` in `src/text.js`, beside `priceOf` and for the same reason).
|
|
382
|
+
|
|
383
|
+
- **A resume may DERIVE `wall-halt` from a recorded `step-red`.** Only when that run's OWN
|
|
384
|
+
spine shows the wall crossed BEFORE the terminal was minted — a derivation from the primary
|
|
385
|
+
record, which never rewrites the recorded outcome and announces itself with a banner. The
|
|
386
|
+
terminal's NAME is what gates resume (`readResume`'s `resumableOutcomes`, which the operator
|
|
387
|
+
runner supplies as `cap-halt` / `wall-halt`), so a run mislabelled `step-red` past its own
|
|
388
|
+
wall silently lost its resumability. **It is a PERMANENT read-side safety net, not a
|
|
389
|
+
migration shim** (ruled 2026-08-13; the expiry question that was parked here is closed). The
|
|
390
|
+
defect that minted the shape is fixed at source, but spines are append-only forever, so the
|
|
391
|
+
population it protects can never shrink; it fires only on the run's own un-forgeable
|
|
392
|
+
`wall-bounded` record, cannot rescue a `step-red` that has no wall record in front of it, and
|
|
393
|
+
is a never-taken branch on a healthy spine.
|
|
394
|
+
|
|
395
|
+
### Changed
|
|
396
|
+
|
|
397
|
+
- **The two revise-loop tighten-only clamps get a detector.** `maxRevisions` and
|
|
398
|
+
`structureRetries` have been clamped since `40672ae`, but nothing could *fail* if they
|
|
399
|
+
stopped clamping: MEASURED, three mutations survived the whole suite — including deleting
|
|
400
|
+
the `structureRetries` clamp outright, a straight revert of the fix that added it. The two
|
|
401
|
+
existing tests pinned only the ceiling and the floor, and neither can fail when the clamp
|
|
402
|
+
stops honouring values *between* them; the direction the rule exists to permit — the
|
|
403
|
+
operator **lowering** a bound — was the untested one. Seven tests now cover the middle of
|
|
404
|
+
both ranges, the non-numeric floor, and `Infinity` (a widening, which clamps to the ceiling,
|
|
405
|
+
matching `runAuthorScout`'s own reading). The three floors are pinned as *deliberately
|
|
406
|
+
different* (`SCOUT_ATTEMPTS` at 1, both revise axes at 0) so a later "harmonisation" has to
|
|
407
|
+
argue with a test. No production behaviour changed — this is the detector the rule was
|
|
408
|
+
missing (F45's class: a frozen rule without a wired detector is prose, not protection).
|
|
409
|
+
|
|
410
|
+
- **A stop past the wall never funds a replan draft, on ANY trigger.** W-2's *"no new step
|
|
411
|
+
starts"* now covers the drafting call itself (hamr: *"it's a replan that is doomed to
|
|
412
|
+
die"*), and the guard is trigger-agnostic rather than wired to one path. A declined grant is
|
|
413
|
+
not consumed. With time left on the clock every one of these paths is byte-identical to
|
|
414
|
+
before — pinned as a control.
|
|
415
|
+
- **The drafter is told its worker has no shell.** A replan wrote an action opening *"Run
|
|
416
|
+
`npx tsc --strict --noEmit`…"*; `run` is locked out of `TOOL_MENU` permanently, so the
|
|
417
|
+
worker obeyed as far as its verbs allowed — 25 reads and greps, zero edits, a whole step
|
|
418
|
+
spent hunting six errors by hand, on a plan that validated clean. The no-shell law now sits
|
|
419
|
+
in the DRAFTER prompt unconditionally, beside the exit-freedom law, with `read`/`grep`
|
|
420
|
+
glossed as the worker's only eyes; the scout and worker prompts already carried it, and the
|
|
421
|
+
one component that WRITES instructions did not. **A regex over action prose was explicitly
|
|
422
|
+
refused** (F86's anti-precedent): the same sentence is legitimate in an operator's signed
|
|
423
|
+
goal, so the instrument is a prompt register, never a matcher. F95.
|
|
424
|
+
- **The worker persona REGISTERS the arbiter's books instead of leaving them to be
|
|
425
|
+
discovered.** `PERSONA_TOOLS` now names `gate-audit.jsonl`, `.smoke`, `.litectx` and the
|
|
426
|
+
run's spine as always-denied — records of how the worker is judged, holding nothing about
|
|
427
|
+
its task — beside the absolute-path law it is the twin of, so it renders for every worker on
|
|
428
|
+
every grant (a `write`-only grant gets no component strategy paragraph at all and must still
|
|
429
|
+
be told). **The fence is unchanged**: nothing widened, no book became readable. The only way
|
|
430
|
+
to learn the rule was previously to spend rounds of a bounded attempt on it. F98.
|
|
431
|
+
- **Both signing surfaces show the GOAL and the judged stages as ONE reading.** F87's law is
|
|
432
|
+
that the goal must state everything the close judges while NOTHING derives one from the
|
|
433
|
+
other or checks them against each other — so the sole defence is the signer reading both
|
|
434
|
+
halves at once, and neither surface offered it: `run-author` printed the declaration and
|
|
435
|
+
never the goal, `run-u --approve` printed the goal and never the declaration. `run-u`'s gate
|
|
436
|
+
now names every close stage (name + kind) under the goal, **for BOTH spec forms** — the
|
|
437
|
+
pairing was first gated on `closeDecl`, so 10 of the 11 shipped specs (the command `close[]`
|
|
438
|
+
form) still met the signer with a goal and none of the stages that judge it, the exact
|
|
439
|
+
half-reading this entry claimed to end; `closeStagesOf(spec)` now renders either form, and a
|
|
440
|
+
command stage prints `[command]` rather than the catalogue kind name `command-exit` it does
|
|
441
|
+
not carry (spec hashes and exit codes byte-identical, proven live). `run-author`'s block moves to
|
|
442
|
+
**`scripts/author-readout.mjs`** (`declarationLines(spec)`) and gains the goal above it,
|
|
443
|
+
rendered from the RESOLVED spec — the bytes that get hashed. It moved because the block was
|
|
444
|
+
otherwise reachable only after a paid scout and a paid model call, and a readout no test can
|
|
445
|
+
reach is a readout nothing checks. An absent goal renders as ABSENT, never as a bare label.
|
|
446
|
+
Still a READING and not a validator — comparing the two is the move F87 forbids.
|
|
447
|
+
- **README rewritten value-first** (hamr's framing, straight to `main`). The pitch now leads
|
|
448
|
+
with the trust-but-verify lineage (the ralph loop, code machine MCP, and RLMs converged on
|
|
449
|
+
the same principle), the three kinds of done (green / softgreen / hitl, one everyday
|
|
450
|
+
example each), why the agent authors the workflow (forty-plus primitives — counted 51+
|
|
451
|
+
across the suite's real surfaces — and no one recipe picked), and "build it once, keep it"
|
|
452
|
+
(localhost UI, whole-workflow export with its self-healing harness). One `[WIP]` badge is
|
|
453
|
+
the only status marker; all mechanics, layer tables, findings numbers and rung history
|
|
454
|
+
moved out of the pitch — `bareloop.context.md` stays the contract, the README the pitch.
|
|
455
|
+
- **`step-stalled` joins the RESUMABLE halts** (hamr's go, 2026-08-13). A stall that trips with
|
|
456
|
+
time left and only reaches the replan gate after the wall has expired kept its NAME — `run.js`
|
|
457
|
+
keys the F44 spend floor on that exact outcome, and renaming it would report unknown spend as
|
|
458
|
+
an exact total — but the name is also what `readResume` gates on, so keeping it cost the run
|
|
459
|
+
its checkpoint. A `step-stalled` spine now previews as **RESUME**, folds the spend floor in as
|
|
460
|
+
`>=$x`, and re-enters at the recorded branch and step. A pure widening of the resumable set:
|
|
461
|
+
nothing else about the terminal moved, and `reuse.js`'s D4a path was checked unaffected.
|
|
462
|
+
- **The arbiter-book names get ONE home.** `gate-audit.jsonl`, `.smoke` and `.litectx` were
|
|
463
|
+
spelled independently in `isArbiterBook`, in BOTH fence deny lists, in the `PERSONA_TOOLS`
|
|
464
|
+
prose and in `run.js`'s smoke root — five sites, the exact drift class F98 had just paid for,
|
|
465
|
+
with the prose copy unguardable by any test. `kinds.js` now exports
|
|
466
|
+
`SMOKE_STORE`/`LITECTX_STORE`/`GATE_AUDIT_FILE`/`ARBITER_BOOK_STORES` and all five consume it.
|
|
467
|
+
Purely mechanical and verified as such: the deny-list joins and the persona prose are
|
|
468
|
+
**byte-identical** to the strings they replace, so no fence decision and no worker prompt
|
|
469
|
+
changed, and there is no import cycle (`kinds.js` sits below `tools.js`/`run.js`).
|
|
470
|
+
`tools.test.js`'s literal-spelling guard now asserts the fence consumes the constant — the
|
|
471
|
+
drift it guarded is impossible by construction.
|
|
472
|
+
|
|
473
|
+
### Fixed
|
|
474
|
+
|
|
475
|
+
- **A time-stop is NAMED a time-stop — W-2 closes over the sibling terminals.** The ruling
|
|
476
|
+
(*"when time is up, keep the grade we already have and stop"*) was implemented on the
|
|
477
|
+
cap-halt path and not on the variance path: one run minted `step-red` **9.9 seconds past its
|
|
478
|
+
own wall**, having recorded the crossing in its own spine 5.6 seconds earlier. Now **every
|
|
479
|
+
variance and cap-halt terminal re-reads the clock before minting**, through ONE shared
|
|
480
|
+
`wallHaltTerminal` emission site — one site, one spelling —
|
|
481
|
+
and the ladder's cap-halt past the wall is a `wall-halt`. **`step-stalled` is pinned OUT of
|
|
482
|
+
the class by test:** `run.js` keys `spendComplete:false` on that exact outcome name because
|
|
483
|
+
an abandoned-and-reissued call may already be billed, and relabelling it would report a
|
|
484
|
+
floor as an exact total (F6 in a self-heal coat). **With both allowances exhausted the MONEY
|
|
485
|
+
cut is reported** (`cap-halt` wins over `wall-halt`) — hamr: *"understood"*; recorded so the
|
|
486
|
+
choice stays visible. F96 / PRD v1.59.
|
|
487
|
+
- **A declared `count-not-worse` red gap carries the LINES it counted, not just how many.**
|
|
488
|
+
`"8 match(es)"` with no file, no line and no error code is a number with nowhere to open —
|
|
489
|
+
F28's rule (*the close's output format is part of the contract*) recurring one rung later
|
|
490
|
+
inside the authored-close executor, with the `gapKeep` machinery it needed sitting in the
|
|
491
|
+
same file. `parseValue` now returns `matched`: the **KEPT** lines only — those that survived
|
|
492
|
+
the scope filter and reached aggregation, so a scope-DROPPED line is never named and the
|
|
493
|
+
worker is never aimed outside its own population (F84). The harvest is **per-term**, not
|
|
494
|
+
global: each term's kept lines nest under that term's own breakdown row — the one place its
|
|
495
|
+
`+n`/`-n` already prints — a `first` aggregate echoes only the one line it read, and dedup is
|
|
496
|
+
WITHIN a term, so a line feeding two terms plays both roles and appears under each. Harvested
|
|
497
|
+
globally, a negative-sign term's lines and a `first` term's never-read lines were echoed flat
|
|
498
|
+
as *the lines the count is made of*, handing the worker a line to go fix when that term
|
|
499
|
+
SUBTRACTED; labelling per term rather than dropping negatives keeps the subtracted term
|
|
500
|
+
visible, because under higher-is-better that is where the news is. The value is
|
|
501
|
+
still computed from the parsed values and never from the list. The lines ride the EXISTING
|
|
502
|
+
channel and invent none: the stage's `gapKeep` prefix on every line (Layer R's `redKeep` is
|
|
503
|
+
DERIVED from it), the existing `GAP_LINE_CAP`, and the announced trim on overflow.
|
|
504
|
+
**Nothing here can flip a signed spec hash** — verdict logic, baselines, counting,
|
|
505
|
+
`closeDecl` schema and `detail` untouched. Measured, not asserted: the identical job, same
|
|
506
|
+
patient, same hash, went from stalling at 8 and dying to **67 → 8 → 1 → 0** in three
|
|
507
|
+
iterations. F98.
|
|
508
|
+
- **A fence deny streak ends the ATTEMPT, never the RUN — `gate-red` is extinct as a mintable
|
|
509
|
+
terminal.** A converging run (close reading 46 → 10 → 8, no strikes) probed three of the
|
|
510
|
+
arbiter's own books, the fence denied all three CORRECTLY, bare-agent's BA-11 deny-spin
|
|
511
|
+
guard ended the loop, and the plan flow mapped that to a terminal `gate-red` — escalate,
|
|
512
|
+
never retry. The run died with **$1.77 and 21.6 wall-minutes unspent, on a fence that had
|
|
513
|
+
worked perfectly.** BA-11's stop now joins `max_turns` in the **bounded-attempt lane on BOTH
|
|
514
|
+
worker surfaces**: `attempt-bounded` carries the `reason`, the close judges the partial
|
|
515
|
+
work, the gap feeds forward, caps unchanged, the loop continues. **And the next attempt is
|
|
516
|
+
told which bound actually fired.** All three attempt bounds wrote the same bare iteration
|
|
517
|
+
number, so the following prompt rendered *"CUT OFF after N tool rounds"* for a denial streak —
|
|
518
|
+
false on cause and count, aiming the worker at its read budget instead of at the fence, while
|
|
519
|
+
`r.error` reached only the spine. The bound now carries `{iteration, cause, reason}`, and the
|
|
520
|
+
denial branch quotes the recorded `denied:<tool>` terminal verbatim — bare-agent returns the
|
|
521
|
+
tool name only, so no path and no streak count is invented — trimmed under `GAP_TRIM_MARKER`
|
|
522
|
+
and scrubbed once at capture, one scrub shared by prompt and spine (an append-only spine that
|
|
523
|
+
captures a key captures it forever). The round-bound sentence stays **byte-identical**, pinned
|
|
524
|
+
by block-equality after an `includes` guard survived a sabotage prefix. This is F32's routing rule
|
|
525
|
+
applied to a new stop cause, not a new one — a stop arriving AFTER gate-audited worker
|
|
526
|
+
writes is non-terminal by construction, and a denial is its most benign member. **Nothing is
|
|
527
|
+
widened**: the same actions are denied and the same audit rows written; a genuine scope
|
|
528
|
+
escape now costs one bounded attempt instead of the whole run. The category stays in
|
|
529
|
+
`EXCLUDED_ESCALATIONS` and in `ralph`'s passthrough decision table — that set is EXECUTABLE,
|
|
530
|
+
so dropping the name would re-file any future emission as a counted capability gap rather
|
|
531
|
+
than delete it. F98.
|
|
532
|
+
- **A malformed money ceiling is an ERROR at the library seam, never a silent UNBOUNDED.**
|
|
533
|
+
`capStop` read `'2.50'`, `NaN`, `Infinity`, `true` and `{}` as *no ceiling* while
|
|
534
|
+
`makeCostBook` advertised the same value straight back — the advertised and the enforced
|
|
535
|
+
budget were different numbers, the one thing a budget may never be — and the only guard
|
|
536
|
+
(`parseCeiling`) was CLI-side, so every library caller ran unprotected. A non-finite non-null
|
|
537
|
+
now throws **before the first paid call**: it costs $0 and propagates uncaught, so no catch
|
|
538
|
+
can launder it into an ABSENT survey. `null`/`undefined` stay the stated operator choice for
|
|
539
|
+
unbounded; `0` and negatives stay finite and cap-halt immediately, pinned as they were.
|
|
540
|
+
- **`budgetStop` latches only when the ceiling actually ended the ladder.** The recovery-seam
|
|
541
|
+
latch fired for EVERY final cause: a 1-turn transport death and even a healthy PRESENT survey
|
|
542
|
+
that spent past the ceiling both came back `budgetStop:'cap-halt'`, on a field whose own
|
|
543
|
+
docstring says *"the ceiling, when it is what ended the ladder"* (the second instance was not
|
|
544
|
+
in the finding). The recovery predicate is settled first now, and `capStop` consulted only
|
|
545
|
+
when a recovery call was actually PENDING. The genuine cap-plus-transport-death case keeps its
|
|
546
|
+
`cap-halt`, but the refusal detail **concedes the concurrent cause instead of contradicting
|
|
547
|
+
the `ENETUNREACH` it quotes** — and concedes it for every INCOMPLETE cause, not only transport
|
|
548
|
+
death: a SHORT/EMPTY/UNPARSEABLE survey whose repair round the ceiling refused was still
|
|
549
|
+
emitting *"the survey stopped on the authoring ceiling, not on anything it read"* directly
|
|
550
|
+
above the reply it was quoting. `CALL_FAILED` keeps the transport wording; `NOT_FUNDED` — no
|
|
551
|
+
call ever made — keeps the ceiling-only wording, the one case where it is true. No budget
|
|
552
|
+
semantics moved: the PRD already said only the former is why the loop ended, and the code now
|
|
553
|
+
honours its own sentence.
|
|
554
|
+
- **The authoring spine event rides its own stop.** `scripts/run-author.mjs` hardcoded
|
|
555
|
+
`type:'cap-halt'` on the governance readout even when the stop was `pricing-red` — the only
|
|
556
|
+
site in the repo where a spine event's type contradicted its own category, and a `pricing-red`
|
|
557
|
+
event carrying *"not under cap"* sends the operator off to raise a number when the repair is a
|
|
558
|
+
priced provider. The old spelling could also arm `ledger.js`'s capability-gap fuse on a run
|
|
559
|
+
whose wallet was never empty (latent — no script feeds author spines to `updateLedger` today,
|
|
560
|
+
but type-keyed slicing is exactly how F45's misread happened). The emit now spends
|
|
561
|
+
`authored.stop` for both fields, so meaning derives from the stop once.
|
|
562
|
+
|
|
8
563
|
## [0.9.0] — 2026-08-09
|
|
9
564
|
|
|
10
565
|
The **close-authoring rung**. The close stops being a hand-written script and becomes a
|