bareloop 0.10.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 +351 -0
- package/README.md +17 -7
- package/bareloop.context.md +179 -16
- package/package.json +3 -3
- package/src/authorflow.js +114 -16
- package/src/authoring.js +100 -33
- package/src/authorjob.js +50 -6
- package/src/authorscout.js +13 -1
- package/src/bridges.js +1 -1
- package/src/clock.js +21 -3
- package/src/declaredclose.js +88 -11
- package/src/index.js +8 -1
- package/src/job.js +7 -2
- package/src/kinds.js +190 -9
- package/src/ledger.js +24 -1
- package/src/planrun.js +195 -5
- package/src/ralph.js +9 -0
- package/src/reuse.js +195 -7
- package/src/root.js +1 -1
- package/src/run.js +10 -4
- package/src/selection.js +1 -1
- package/types/authorflow.d.ts +86 -12
- package/types/authoring.d.ts +49 -35
- package/types/authorjob.d.ts +9 -1
- package/types/authorscout.d.ts +7 -1
- 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 +76 -4
- package/types/planrun.d.ts +18 -2
- package/types/reuse.d.ts +74 -0
- package/types/run.d.ts +12 -2
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,357 @@ 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
|
+
|
|
8
359
|
## [0.10.0] — 2026-08-13
|
|
9
360
|
|
|
10
361
|
### Added
|
package/README.md
CHANGED
|
@@ -32,20 +32,30 @@ 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
|
-
##
|
|
35
|
+
## Two kinds of done
|
|
36
36
|
|
|
37
|
-
Jobs differ by
|
|
38
|
-
|
|
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
|
-
|
|
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
|
-
|
|
48
|
-
|
|
49
|
+
## You have the last word
|
|
50
|
+
|
|
51
|
+
Every run ends at a **review door**: here is what changed, here is what every check said, here
|
|
52
|
+
is what I'm asking you. **Accept** it, **rerun** it — what you type is the feedback the next
|
|
53
|
+
attempt works from, funded fresh — or **pause** it and decide another day. A pause costs
|
|
54
|
+
nothing, keeps everything, and picks up from the start of the last step whenever you come back.
|
|
55
|
+
Never come back, and it quietly expires on its own.
|
|
56
|
+
|
|
57
|
+
The door never rewrites the run's own verdict. A green stays green in the record whatever you
|
|
58
|
+
decide about it; your answer says what happens to the work, not what the work scored.
|
|
49
59
|
|
|
50
60
|
## Why the agent authors the workflow
|
|
51
61
|
|