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/bareloop.context.md
CHANGED
|
@@ -15,6 +15,20 @@
|
|
|
15
15
|
> file ships with the package and is the complete adopter contract; the README is only
|
|
16
16
|
> the pitch.
|
|
17
17
|
|
|
18
|
+
> **DIRECTION CHANGE — 2026-08-17 (design decided, NOT built; nothing below has moved yet).**
|
|
19
|
+
> **`hitl` is RETIRED as a verdict class.** The verdict classes become **`green` +
|
|
20
|
+
> `soft-green`**, and the hitl pause machinery is **RE-HOMED, not deleted**: it becomes the
|
|
21
|
+
> **review door** at the end of EVERY run (green and soft-green alike), where the person
|
|
22
|
+
> `accept`s, `rerun`s (their text is the gap, funded as a FRESH engagement with its own money
|
|
23
|
+
> and time) or `pause`s — and **the door never changes the loop's own verdict**. `soft-green`
|
|
24
|
+
> gets a `judged-floor` stage behind a signed rubric card and a signed calibration set.
|
|
25
|
+
> **Everything documented in this file describes the code as it actually is on this branch**,
|
|
26
|
+
> where `hitl` IS admitted, `human-confirms` IS live, and the three `hitl-*` terminals exist —
|
|
27
|
+
> integrate against what is written here, and expect the class-level rename when the next rung
|
|
28
|
+
> lands. Records: PRD Addendum v1.71 and
|
|
29
|
+
> `docs/02-features/2026-08-17-softgreen-review-door-design.md` (repo-only, not shipped);
|
|
30
|
+
> evidence F102–F105.
|
|
31
|
+
|
|
18
32
|
## What this is
|
|
19
33
|
|
|
20
34
|
**"Automate this job — I don't know the best workflow."** bareloop runs tasks that are
|
|
@@ -96,7 +110,7 @@ the step plan at run time (gated by `validatePlan`); the human signs only:
|
|
|
96
110
|
| field | shape | notes |
|
|
97
111
|
|---|---|---|
|
|
98
112
|
| `goal` | non-empty text | what the agent plans against |
|
|
99
|
-
| `verdictType` | `green` \| `soft-green` \| `hitl` | declared radio, never inferred (`VERDICT_TYPES`, frozen).
|
|
113
|
+
| `verdictType` | `green` \| `soft-green` \| `hitl` | declared radio, never inferred (`VERDICT_TYPES`, frozen). **`green` and `hitl` are ADMITTED** (N4 slice 1) — *and `hitl` is RETIRED as a class by the 2026-08-17 design (see the direction note at the top): it still works exactly as documented here on this branch, and the forward path for a job needing judgement is `soft-green` plus the end-of-run review door*; `soft-green` is still declared-but-locked and reds `request-red` with the type as a structured `verb` field (the tool-menu pattern) until its judged floor exists. Every `request-red` also carries `lib` — the territory the demand lands against, stamped at the emit site (`verdictType` → `bareloop`, a locked tool verb → `bare-agent`): the ledger keys and its `suggestedAsk` seed on it, so a bareloop-catalogue refusal never files as an upstream ask |
|
|
100
114
|
| `close` | **an ORDERED LIST of named stages** `[{ name, cmd, expect, judged?, gapKeep?, offer?, needs? }, ...]` (PRD v1.28), or a close object (table below) **only** for the declared-but-locked verdict classes | the destination, the only thing hand-authored; the check menu DERIVES from it (below). The plan flow executes a staged close directly and adapts a bare `predicate` object into a one-stage list; a `gold`/`rubric`/`hitl` object close validates (the declared-but-locked verdict classes still parse) but the plan flow refuses it at runtime as `close-unsupported` — it names no command to run |
|
|
101
115
|
| `closeDecl` | **the AUTHORED close** `{ genre: "TYPES", lang: "js"\|"python", stages: [{ name, kind, params }], notes? }` | the ALTERNATIVE to `close`, and the point of the close-authoring rung: the user answers an interview and an LLM composes a DECLARATION over kinds whose implementations we own — never a script, never a shell fragment, never a new kind. **`close` and `closeDecl` are alternatives**: declaring both reds `close-duplicated` (two closes are two arbiters). It is HARD-class by construction (`CLASS_BY_CLOSE.declared`), so a locked verdict on one reds `close-hierarchy` as well as `request-red`. The declaration is validated by `validateCloseDecl`; the TREE-GROUNDED half of that gate (the path rule, and the scoped-job derivation that arms the F84 one-population law) is DEFERRED at spec-validation time — a job spec is validated with no repository in hand — and the runner re-runs it GROUNDED against the real seed before any stage and before any token. It stores the counting RULE and never a number (D12): there is no seed field, and `baseline: "seed"` is measured at each run's own HEAD |
|
|
102
116
|
| `checks` | **RETIRED** (PRD v1.28/v1.32) | hand-authored checks are gone, not merely discouraged: declaring `checks` reds `checks-derived` by name. The check menu is DERIVED from the close's own stages instead — see **Staged close** below. The hazard this removes is measured, not theoretical: job #5's three hand-written checks were re-implementations of three stages the close already ran, and a hand-carved copy can drift LENIENT (the worker passes the operator's ruler and fails the real inspection) |
|
|
@@ -194,7 +208,9 @@ declared-but-locked verdict classes** (a close is data, never code; verdict-clas
|
|
|
194
208
|
is a named red `close-hierarchy`). The go-forward shape is the staged list above; a
|
|
195
209
|
`predicate` object is legal shorthand for a one-stage list and the plan flow adapts it, but
|
|
196
210
|
`gold`/`rubric`/`hitl` validate and then refuse at runtime (`close-unsupported`) since v1
|
|
197
|
-
admits `verdictType: green` only
|
|
211
|
+
admits `verdictType: green` and `hitl` only — and N4's hitl is a STAGE
|
|
212
|
+
(`human-confirms` inside a `closeDecl`), never a close TYPE, so the `hitl` OBJECT form below
|
|
213
|
+
stays refused at runtime exactly as it was:
|
|
198
214
|
|
|
199
215
|
| type | fields (exact — extras red) | legal class |
|
|
200
216
|
|---|---|---|
|
|
@@ -294,6 +310,14 @@ N−1 are enforced only at the close, at the end of the run. That is a COST shap
|
|
|
294
310
|
hole — the close still refuses a tree that fails any stage, and nothing green is minted mid-run
|
|
295
311
|
— but a stage first tested at the tail is a stage the run pays full price to discover.
|
|
296
312
|
|
|
313
|
+
**The ceiling of 2 is RULED, not provisional (2026-08-13, PRD v1.63 §3).** One check per write
|
|
314
|
+
step is the contract: a step passing its own in-run check is never a verdict, only the close is
|
|
315
|
+
truth, and widening the ceiling so a step's own gate could approximate the close would move
|
|
316
|
+
judgement into the part the agent authors. Plan around one check; do not plan for a second one
|
|
317
|
+
arriving. It is raised only on a demonstrated starvation case (a real run where a step provably
|
|
318
|
+
cannot express its progress gate within one check and stalls for it), never for wording comfort,
|
|
319
|
+
and the change is the operator's — arbiter territory.
|
|
320
|
+
|
|
297
321
|
Red vocabulary (both validators): `parse-error`, `unknown-field`, `missing-required`,
|
|
298
322
|
`invalid-value`, `bounds`, `duplicate-id`, `close-type`, `close-hierarchy`,
|
|
299
323
|
`secret-literal`, `scope-escape`,
|
|
@@ -467,7 +491,9 @@ is COUNTED.
|
|
|
467
491
|
**The verdict class is the USER's answer (PRD v1.57 §1), and it DRIVES the authoring.**
|
|
468
492
|
`verdictType` is a declared radio the preflight validates, never inferred:
|
|
469
493
|
`VERDICT_CLASSES` = `green` | `soft-green` | `hitl`, with `LOCKED_CLASSES` =
|
|
470
|
-
`soft-green`
|
|
494
|
+
`soft-green` alone (N4 slice 1 admitted `hitl`; `soft-green` waits for its judged floor —
|
|
495
|
+
**which is now the forward path: `hitl` is retired as a class by the 2026-08-17 design and its
|
|
496
|
+
pause becomes the end-of-run review door, unbuilt as of this branch**). Picking a locked one returns a counted
|
|
471
497
|
`request-red` refusal at ADMISSION — before that class's questions are ever asked. The
|
|
472
498
|
pick is also a PROMISE: every catalogue kind carries the `verdictClass` it can honestly
|
|
473
499
|
render, `closeCeiling(declaration)` reports the highest class a declaration demands, and a
|
|
@@ -479,11 +505,11 @@ a step, or test it without a provider.
|
|
|
479
505
|
|
|
480
506
|
| step | call | what it is |
|
|
481
507
|
|---|---|---|
|
|
482
|
-
| interview | `runInterview({ verdictType, answers, repoPath })` → `{ ok, answers, verdictType, refusal, reds }` | PURE — no model, no repo, no clock. THREE frozen question sets keyed by verdict class (`QUESTION_SETS`; `questionsFor(cls)` / `requiredAnswersFor(cls)`), and the interview asks NOTHING about a genre.
|
|
483
|
-
| survey | `runAuthorScout({ workdir, provider, attempts? })` → `{ state, facts, reason, meta, raw, raws, calls }` | a bounded READ-ONLY LLM survey. Read-only by MENU CONSTRUCTION (`AUTHOR_SCOUT_VERBS` = the full menu minus write-class and store-class verbs), 8 rounds, F59's reserved toolless final round. **`state: 'ABSENT'` means the scout did not complete — never "no special facts are needed"**, and a parsed `{}` is one of its five ABSENT routes. Up to `SCOUT_ATTEMPTS` (**3**, hardcoded — PRD v1.58) attempts: `attempts` is TIGHTEN-ONLY and CLAMPS rather than throws (floor **1**, because a scout that never ran is an ABSENT nobody can act on), the direction every cap in this system runs in. **A retry fires on the typed MALFORMED class alone** (`SCOUT_RETRY_CAUSES` = `empty` \| `unparseable`); what is excluded is excluded for a reason — `call-failed` covers transport and `truncated:max_tokens`, both provider-red with NO redraft; `short` is F59's cut-off population and already has its own instrument INSIDE the attempt; `non-object` and `empty-object` are valid JSON with wrong or vacuous content, which is a SEMANTIC failure, and F38/F39 measured what re-asking one buys (the same distribution, sampled twice) — that is the self-healing line, and it is not crossed here. The re-ask is TOOLLESS over the conversation the survey already produced (the repository was already read; only the emission was unreadable) and names the mechanical parse error and nothing else. **There is no JSON repair behind it and there never will be** — a repairer decides what the model MEANT and writes it down as though the model had said it, in the one artefact whose whole job is to be honest about what a repository contains. `meta.attempts` / `meta.attemptsAllowed` record what was paid for; every attempt is metered under its own label (`author-scout`, `author-scout#2`, …) and leaves its raw behind |
|
|
508
|
+
| interview | `runInterview({ verdictType, answers, repoPath })` → `{ ok, answers, verdictType, refusal, reds }` | PURE — no model, no repo, no clock. THREE frozen question sets keyed by verdict class (`QUESTION_SETS`; `questionsFor(cls)` / `requiredAnswersFor(cls)`), and the interview asks NOTHING about a genre and NOTHING about the repository. **Ask the library for the set and its numbers — never hardcode a count or a slot number**: the green set has lost two slots since it was frozen (D13's genre confirm, then the repo question, dropped 2026-08-15 because `repoPath` is mandatory structured input and asking for it again invites a second, drifting answer for a fact the machine already holds), and it renumbers CONTIGUOUSLY from 1 each time rather than leaving a gap. Today the green set is five questions, answers keyed by number; the HITL set is those five byte for byte plus one more (what the signer is deciding when they look at the result — `human-confirms`' `ask`), which is the spread's next number and moves with a green-side deletion; the soft-green set is named but LOCKED (`questions: null`, absent rather than empty) and its selection refuses before it runs. `verdictType` and `repoPath` are STRUCTURED input, never parsed out of prose. Answers are scrubbed at INGEST |
|
|
509
|
+
| survey | `runAuthorScout({ workdir, provider, attempts?, ceilingUsd? })` → `{ state, facts, reason, meta, raw, raws, calls, budgetStop }` | a bounded READ-ONLY LLM survey. Read-only by MENU CONSTRUCTION (`AUTHOR_SCOUT_VERBS` = the full menu minus write-class and store-class verbs), 8 rounds, F59's reserved toolless final round. **`state: 'ABSENT'` means the scout did not complete — never "no special facts are needed"**, and a parsed `{}` is one of its five ABSENT routes. Up to `SCOUT_ATTEMPTS` (**3**, hardcoded — PRD v1.58) attempts: `attempts` is TIGHTEN-ONLY and CLAMPS rather than throws (floor **1**, because a scout that never ran is an ABSENT nobody can act on), the direction every cap in this system runs in. **A retry fires on the typed MALFORMED class alone** (`SCOUT_RETRY_CAUSES` = `empty` \| `unparseable`); what is excluded is excluded for a reason — `call-failed` covers transport and `truncated:max_tokens`, both provider-red with NO redraft; `short` is F59's cut-off population and already has its own instrument INSIDE the attempt; `non-object` and `empty-object` are valid JSON with wrong or vacuous content, which is a SEMANTIC failure, and F38/F39 measured what re-asking one buys (the same distribution, sampled twice) — that is the self-healing line, and it is not crossed here. The re-ask is TOOLLESS over the conversation the survey already produced (the repository was already read; only the emission was unreadable) and names the mechanical parse error and nothing else. **There is no JSON repair behind it and there never will be** — a repairer decides what the model MEANT and writes it down as though the model had said it, in the one artefact whose whole job is to be honest about what a repository contains. `meta.attempts` / `meta.attemptsAllowed` record what was paid for; every attempt is metered under its own label (`author-scout`, `author-scout#2`, …) and leaves its raw behind. **`ceilingUsd` is the operator's money ceiling** (PRD v1.62) — checked BETWEEN attempts and before F59's reserved round, so no paid call escapes it; `null`/absent is UNBOUNDED, and a non-null non-finite value THROWS (the shared `capStop` seam — see `authorClose` below). When it ends the ladder, `budgetStop` names which stop (`cap-halt` \| `pricing-red`) and a survey that was never asked for carries the typed cause `not-funded` — deliberately OUTSIDE `SCOUT_RETRY_CAUSES`, because a retry is precisely what the cap forbade |
|
|
484
510
|
| listing | `buildSeedListing({ workdir, seedRef, sourcePaths, testPaths })` | mechanical, `$0`, no model. `files` is the WHOLE tree (what the validator judges paths against); `block` is scoped to the survey's own paths and capped in ANNOUNCED tiers (what the prompt carries). Handing the validator the scoped half would make a job scoped to `src/` read as whole-tree and silently disarm the one-population law |
|
|
485
|
-
| authoring | `authorClose({ workdir, seedRef, lang, verdictType, answers, scout, listing, generate })` | the grounded loop: author → validate → run EVERY stage at the seed → feed the MEASURED results back → revise, bounded at `MAX_REVISIONS` (2) — a passed `maxRevisions` is TIGHTEN-ONLY, so a caller may LOWER that ceiling and never buy more revise rounds than the constant (floor **0**: one authoring call with no revise round is a legal ask, and unlimited revising would launder thrash as adaptation) — early-stop on an unchanged declaration. The declaration is emitted through a SCHEMA-FORCED TOOL CALL (`declare_close`), never parsed out of prose; the feedback is EXECUTION OUTPUT only — no model ever reviews another model's close. The return carries **`raws`**: what every model call actually SAID, the scout's attempts absorbed together with the declaration's, in the cost book's own order and under the same labels it meters. Each is `{ label, attempt, bytes, trimmed, text, cause, reason }` through the ONE persist helper (`scrubRaw` — redacted over the ONE `SECRET_PATTERNS` inventory, since a raw is the record most likely in this system to carry a live credential; bounded at `RAW_PERSIST_MAX` (8000) with the trim ANNOUNCING its own full size, and the cut walked back off a continuation byte so a multi-byte character is never split). `raws` is present on EVERY path **including the `$0` preflight refusals** — the path that spends nothing more is exactly the one whose evidence used to vanish with the process. `iterations` records what each turn MEANT (declaration, validation, seed read); `raws` records what it SAID, and a malformation is only ever visible in the second |
|
|
486
|
-
| everything above, composed | `authorCloseForJob({ verdictType, answers, repoPath, lang, generate, ... })` → `{ ok, closeDecl, verdictType, refusal, cost, ... }` | refuses at the cheapest gate that can refuse: an interview refusal costs **zero**. THE GENRE REFUSAL LIVES HERE (not in the interview): a language the catalogue owns no data for, and a LOCKED KIND the model reached for, both come back as counted `request-red` demand |
|
|
511
|
+
| authoring | `authorClose({ workdir, seedRef, lang, verdictType, answers, scout, listing, generate, ceilingUsd? })` | the grounded loop: author → validate → run EVERY stage at the seed → feed the MEASURED results back → revise, bounded at `MAX_REVISIONS` (2) — a passed `maxRevisions` is TIGHTEN-ONLY, so a caller may LOWER that ceiling and never buy more revise rounds than the constant (floor **0**: one authoring call with no revise round is a legal ask, and unlimited revising would launder thrash as adaptation) — early-stop on an unchanged declaration. The declaration is emitted through a SCHEMA-FORCED TOOL CALL (`declare_close`), never parsed out of prose; the feedback is EXECUTION OUTPUT only — no model ever reviews another model's close. The return carries **`raws`**: what every model call actually SAID, the scout's attempts absorbed together with the declaration's, in the cost book's own order and under the same labels it meters. Each is `{ label, attempt, bytes, trimmed, text, cause, reason }` through the ONE persist helper (`scrubRaw` — redacted over the ONE `SECRET_PATTERNS` inventory, since a raw is the record most likely in this system to carry a live credential; bounded at `RAW_PERSIST_MAX` (8000) with the trim ANNOUNCING its own full size, and the cut walked back off a continuation byte so a multi-byte character is never split). `raws` is present on EVERY path **including the `$0` preflight refusals** — the path that spends nothing more is exactly the one whose evidence used to vanish with the process. `iterations` records what each turn MEANT (declaration, validation, seed read); `raws` records what it SAID, and a malformation is only ever visible in the second. **`ceilingUsd` is the same operator ceiling the survey took** — the scout's spend is ABSORBED into this loop's cost book before its first call is weighed, so spend already incurred folds in and re-entering cannot silently widen it. It is checked immediately before EVERY paid call (the author call, each revise, and each malformed-emission retry). **A MALFORMED ceiling is an ERROR, not a silent unbounded run** (v1.64 §3): the seam every ceiling read goes through (`capStop`, `src/text.js`) THROWS on a non-null non-finite value — `'2.50'`, `NaN`, `Infinity`, `true`, `{}` — before the first paid call, and the throw propagates uncaught rather than being caught into an ABSENT survey, because a caught one would hand you the very silent-unbounded run it exists to prevent. `null`/omitted is the STATED operator choice for unbounded; `0` and negatives are finite, legal, and cap-halt immediately. The advertised ceiling and the enforced ceiling can therefore never be different numbers. A `cap-halt` here can arrive with `ok:true` — a close that was already validated and measured survives the money stop, exactly as a late provider casualty does |
|
|
512
|
+
| everything above, composed | `authorCloseForJob({ verdictType, answers, repoPath, lang, generate, ceilingUsd?, ... })` → `{ ok, closeDecl, verdictType, refusal, cost, ... }` | refuses at the cheapest gate that can refuse: an interview refusal costs **zero**. THE GENRE REFUSAL LIVES HERE (not in the interview): a language the catalogue owns no data for, and a LOCKED KIND the model reached for, both come back as counted `request-red` demand. **`ceilingUsd` is ONE number handed to BOTH paid seams** (the survey's and the declaration loop's) — the advertised ceiling and the enforced ceiling are the same ceiling. It travels as an EXPLICIT `null` when unset, because unbounded is a stated choice and an absent field read as falsy is that state reached by accident — and anything that is neither a finite number nor `null` throws at the seam rather than becoming that state by typo |
|
|
487
513
|
| assembly | `assembleSpec(specDraft, { closeDecl, verdictType })` | folds the authored half into the OPERATOR's half. Budgets, the fence, cadence, escalation and the provider are never authored by anything here. The GOAL is passed through, not generated. **A draft that ALREADY carries the authored half is REFUSED — it THROWS, never merges and never overwrites.** `AUTHORED_SPEC_FIELDS` (`close`, `closeDecl`, `verdictType`) names the fields this fold WRITES, as data, so the refusal and the fold cannot disagree about which half of the spec is which; the rule is `job.js`'s one step earlier (two closes are two arbiters, and picking one silently is how a signed artefact stops meaning what the signer read). `scripts/run-author.mjs` asks the same question at **$0 BEFORE the scout**, so the answer never arrives after the model has been paid |
|
|
488
514
|
| the three gates | `prepareSigning({ spec, workdir, seedRef? })` → `{ ok, specHash, gates, work, guards, refusal }` | D9, and it NEVER signs |
|
|
489
515
|
|
|
@@ -504,11 +530,53 @@ no model found, it moves no seed number, and a declaration that authors it itsel
|
|
|
504
530
|
`genre-owned-env`.
|
|
505
531
|
|
|
506
532
|
**The kind catalogue** (`KIND_CATALOGUE`, and it IS the whole vocabulary): `command-exit`,
|
|
507
|
-
`count-not-worse`, `pattern-absent-in-diff`, `files-changed`
|
|
508
|
-
`
|
|
509
|
-
|
|
510
|
-
tool mode a locked kind is INEXPRESSIBLE — the schema carries one branch per live
|
|
511
|
-
that demand arrives through the interview layer instead (`refuseLockedKind(kind)`).
|
|
533
|
+
`count-not-worse`, `pattern-absent-in-diff`, `files-changed` and `human-confirms` are live;
|
|
534
|
+
`judged-floor` is NAMED BUT LOCKED (`verdictClass: 'soft-green'`), so declaring it is a counted
|
|
535
|
+
`locked-kind` red rather than an unknown-kind typo; `harness-loop` (TESTGEN) is ABSENT from v1
|
|
536
|
+
entirely. In tool mode a locked kind is INEXPRESSIBLE — the schema carries one branch per live
|
|
537
|
+
kind — so that demand arrives through the interview layer instead (`refuseLockedKind(kind)`).
|
|
538
|
+
|
|
539
|
+
**`human-confirms` is the one kind that measures NOTHING** (N4 slice 1). Its whole parameter
|
|
540
|
+
surface is `ask` — the plain question the signer answers — so it cannot spawn, cannot be
|
|
541
|
+
env-capable, and cannot be handed a second job. Four laws ride on it, all checked rather than
|
|
542
|
+
assumed: it is `offer: false` BY LAW (a declaration that offers one reds `human-stage-offered`,
|
|
543
|
+
and `declaredStages` stamps the law onto the stage the runner sees, so `checkMenu` can never
|
|
544
|
+
hand the agent `check-passes(<a person>)`); it must be the LAST stage
|
|
545
|
+
(`human-stage-not-last` — first-red-wins would otherwise silently delete every mechanical stage
|
|
546
|
+
behind it and empty the evidence package the person is owed); it is at-most-once (one signer,
|
|
547
|
+
one judge); and it SKIPS the seed-verdict read (ruling 8), with the skip recorded as a row
|
|
548
|
+
carrying `verdict: 'skipped'` rather than taken in silence.
|
|
549
|
+
|
|
550
|
+
**A human stage does not RUN — it PAUSES.** `StageResult.verdict` gains `pause` (and `skipped`),
|
|
551
|
+
`runDeclaredClose` reports `pause`/`pausedAt`, and the arbiter bridge translates it to the
|
|
552
|
+
distinct `HUMAN_PAUSE` verdict — never `satisfied`, never `needs_revision`, and never a
|
|
553
|
+
`CLOSE_FAULTS` word (a fault says the close could not render a verdict; a pause says it is not
|
|
554
|
+
FINISHED rendering one). With the signer's ruling in hand (`ctx.humanRuling`, carried by the
|
|
555
|
+
runner and never authored) the same stage renders what the person said: `accept` is a green a
|
|
556
|
+
PERSON judged, `rerun` is a red whose gap is their own words. `normalizeHumanRuling` is the
|
|
557
|
+
library half of the gate — `null` is the pause path, a fourth door is refused, and a `rerun`
|
|
558
|
+
with empty or whitespace-only text is refused by name, because an empty gap re-runs the worker
|
|
559
|
+
as though nothing had been said.
|
|
560
|
+
|
|
561
|
+
**A red `count-not-worse` gap carries the LINES it counted, not only how many (F98).** Each
|
|
562
|
+
term's **KEPT** matched lines nest under that term's own breakdown row — the row that already
|
|
563
|
+
prints its `contributes +n`/`-n`, so a line's arithmetic role travels with it. Kept means the
|
|
564
|
+
lines that survived the scope filter and were actually read by the aggregate (a `first` term
|
|
565
|
+
echoes only the line that produced its value), which is to say the ones the count is made of.
|
|
566
|
+
A scope-DROPPED line is never among them (it was never counted, so naming it would aim the
|
|
567
|
+
worker outside its own population, F84's one-population law), duplicates collapse within a
|
|
568
|
+
term (a line feeding two terms plays two roles and appears under each), and the value is
|
|
569
|
+
still computed from the parsed values and never from this list.
|
|
570
|
+
They ride the **existing** gap channel and invent none: the declared stage's `gapKeep` prefix
|
|
571
|
+
on every line (Layer R's `redKeep` is DERIVED from that prefix), the same `GAP_LINE_CAP`, and
|
|
572
|
+
the same announced trim on overflow — so a consumer parsing the gap sees more lines under the
|
|
573
|
+
same shape, never a new one. `parseValue` returns them as `matched` alongside `value`,
|
|
574
|
+
per-term, index-aligned with the breakdown. The
|
|
575
|
+
licence is the one `pattern-absent-in-diff` already runs under: the instrument NAMED these
|
|
576
|
+
lines, and echoing what it named is not the barred move of naming a culprit it never
|
|
577
|
+
reported. **Nothing here can flip a signed spec hash** — the verdict logic, the baselines,
|
|
578
|
+
the counting, the `closeDecl` schema and `detail` are untouched; only what the executor TELLS
|
|
579
|
+
the worker changed.
|
|
512
580
|
|
|
513
581
|
**The three gates, and the signature (D9).** Nothing LLM-judges a close.
|
|
514
582
|
1. `validateCloseDecl(closeDecl, { verdictType, ... })` — schema, kinds, params, the F84
|
|
@@ -524,12 +592,119 @@ that demand arrives through the interview layer instead (`refuseLockedKind(kind)
|
|
|
524
592
|
**A close with no WORK stage red at seed is refused decision-ready** — it grades nothing,
|
|
525
593
|
and an instrument that scans nothing reads clean exactly like one that measures correctly.
|
|
526
594
|
|
|
595
|
+
**KNOWN LIMIT: gate 1 does not check a parser's FORMAT, and never will (F94, ruled WON'T-BUILD
|
|
596
|
+
2026-08-13, PRD v1.63 §1).** A declaration can name the right stage, over the right population,
|
|
597
|
+
with the right aggregate, and carry a `lineMatch` for a shape the tool never prints. Detecting
|
|
598
|
+
that at draft time needs stage → tool attribution, and `npm run typecheck` defeats it
|
|
599
|
+
mechanically: the declared command names npm, what prints is `tsc`, and the mapping lives in a
|
|
600
|
+
`package.json` the validator does not read. A detector that resolved some spellings and passed
|
|
601
|
+
the rest would read as coverage while being blind exactly where patients differ. **Gates 2 and 3
|
|
602
|
+
are the instrument for this class, and they work** — a term matching nothing over a non-empty
|
|
603
|
+
population reads unknown-not-zero (F6/F45/F93), so the seed-verdict read refuses it at $0 before
|
|
604
|
+
a signature, which is how the real case was caught. Formats are checked by RUNNING the tool,
|
|
605
|
+
never by reading a string. The complementary defence is upstream of the gate: for the tools the
|
|
606
|
+
genre owns facts about, `genreInstruments` hands the author the measured pattern (with its real
|
|
607
|
+
captured line) instead of asking, so fewer declarations reach gate 1 carrying an invented format
|
|
608
|
+
at all. Reopens only if a wrong-format parser ever reaches a SIGNED close.
|
|
609
|
+
|
|
527
610
|
Then the human signs `specHash`, unchanged: the approvals array and the human's word are
|
|
528
611
|
the arbiter relocating to the user, not disappearing. **Re-authoring is a spec edit (D6)** —
|
|
529
612
|
a different declaration is different bytes, a different resolved hash, and a new signature.
|
|
530
613
|
The guards are stored ENUMERATED and every short-form parser is expanded, so no
|
|
531
614
|
omittable-with-a-default field can change what runs without changing what was signed.
|
|
532
615
|
|
|
616
|
+
**A signing surface must show the GOAL and the judged stages as one reading (F87).** The
|
|
617
|
+
goal has to state everything the close will judge, and **nothing derives one from the other
|
|
618
|
+
or checks them against each other** — the only derivation runs close-stages → check menu, one
|
|
619
|
+
hop, one direction. So the sole defence against an unstated stage is that the person signing
|
|
620
|
+
reads both halves at once, and any surface you build around `prepareSigning` inherits that
|
|
621
|
+
obligation. Both reference runners now do it: `scripts/run-u.mjs --approve` names every close
|
|
622
|
+
stage (name + kind) under the goal — for **BOTH spec forms**, through `closeStagesOf`, since
|
|
623
|
+
gating that block on `closeDecl` reached exactly one of the eleven shipped specs and left the
|
|
624
|
+
other ten (command `close[]`) showing the goal with no stages under it, which is the same
|
|
625
|
+
half-reading in the guard against it (F100); a command stage prints `[command]` and NOT the
|
|
626
|
+
catalogue kind name it does not carry — and `scripts/run-author.mjs` prints the goal above the
|
|
627
|
+
declaration via `declarationLines(spec)` in `scripts/author-readout.mjs`, rendered from the
|
|
628
|
+
**RESOLVED** spec — the bytes that get hashed, never the draft and never the authored half
|
|
629
|
+
alone. An absent goal renders as absent rather than as a bare label. This is a READING, not a
|
|
630
|
+
validator: nothing compares the goal to the declaration, because deriving one from the other
|
|
631
|
+
is exactly what F87 forbids.
|
|
632
|
+
|
|
633
|
+
**The path an adopter actually drives is TWO scripts, and the first one spends nothing.**
|
|
634
|
+
`runInterview` is a pure function over answers with no prompt loop in it, so until
|
|
635
|
+
`scripts/run-interview.mjs` existed the only way in was to hand-write an `answers.json` and a
|
|
636
|
+
spec draft — precisely the SWE tax this product refuses. That script is the half that ASKS
|
|
637
|
+
(`--patient <repoPath> --verdict <class> --out <outdir> [--budget <usd>] [--lang js]`, default
|
|
638
|
+
`js`), and it is deliberately GLUE: **it calls no provider at all**, so a whole interview costs
|
|
639
|
+
$0 and no model ever sees it. Everything load-bearing in it is borrowed rather than respelled —
|
|
640
|
+
the QUESTIONS are the library's frozen sets (`questionsFor` / `requiredAnswersFor`), printed as
|
|
641
|
+
handed over and never re-worded, re-ordered or re-numbered by the script; the REFUSALS are
|
|
642
|
+
`runInterview`'s; the SCRUB is `redactSecrets` at capture AND again at the library's own ingest,
|
|
643
|
+
so no keystroke reaches stdout or disk by a route that skips the one inventory; and the spec
|
|
644
|
+
draft is checked by the SAME `validateJob` that will judge it after the paid call, with the
|
|
645
|
+
authored half's expected reds filtered off `AUTHORED_SPEC_FIELDS` by name. A typo'd slug or an
|
|
646
|
+
under-floor wall therefore costs $0 rather than a scout plus a model call.
|
|
647
|
+
|
|
648
|
+
A LOCKED class refuses BEFORE a single question is asked: the script hands `runInterview` an
|
|
649
|
+
empty answer set, prints the counted `request-red` verbatim, and stops — nothing is asked and
|
|
650
|
+
nothing is written, because asking a class's questions for a job nothing here can close is an
|
|
651
|
+
interview at the wrong price. On success it writes exactly TWO files into `--out`:
|
|
652
|
+
`answers.json`, which is the LIBRARY's redacted reading of the answers (`iv.answers`) and never
|
|
653
|
+
the raw keystrokes; and `specdraft.json`, which is the OPERATOR's half ONLY — no `close`, no
|
|
654
|
+
`closeDecl`, no `verdictType`, because those three ARE `AUTHORED_SPEC_FIELDS` and `assembleSpec`
|
|
655
|
+
refuses a draft carrying any of them rather than merging over it. `tools` is deliberately
|
|
656
|
+
OMITTED from the draft too (MED-1: an omitted menu hashes as the concrete current `TOOL_MENU`,
|
|
657
|
+
which pins WHICH menu was signed; naming today's list would freeze it into the operator's half).
|
|
658
|
+
An interview that never finishes writes NOTHING: a half-collected answer set that looks finished
|
|
659
|
+
is the failure nobody sees.
|
|
660
|
+
|
|
661
|
+
**Two ceilings, two names, never pooled on screen or anywhere else.** `--budget` is the
|
|
662
|
+
AUTHORING ceiling — what the authoring call may spend — parsed by the same `parseCeiling` rule
|
|
663
|
+
`run-author.mjs` parses it with: absent is UNBOUNDED and ANNOUNCED before anything spends,
|
|
664
|
+
malformed is an ERROR rather than a silent fallback, and zero or negative is rejected on the
|
|
665
|
+
same rule. `budgetUsd` is the JOB's budget, asked separately and signed into the spec. The
|
|
666
|
+
interview stores neither: the ceiling is handed straight to the child, which is the process that
|
|
667
|
+
spends it.
|
|
668
|
+
|
|
669
|
+
**It ends by OFFERING the paid step, and the offer's default is NO.** `run-author.mjs` is a
|
|
670
|
+
DIFFERENT PROCESS under its own ceiling; the interview prints its exact command line (with
|
|
671
|
+
`--budget` propagated only when one was given, and a note when `ANTHROPIC_API_KEY` is unset)
|
|
672
|
+
before asking `Run it now? [y/N]`, so declining still leaves a command to paste and two files
|
|
673
|
+
already on disk. Only an explicit yes spends — the answer that costs nothing is the one you get
|
|
674
|
+
by saying nothing, the same lean the pause's doors take. On a yes it releases stdin before
|
|
675
|
+
spawning (two readers on one terminal is a keystroke landing in whichever happens to be
|
|
676
|
+
listening) and then propagates the CHILD's exit status rather than flattening a refusal there
|
|
677
|
+
into a success one process up.
|
|
678
|
+
|
|
679
|
+
**Both scripts speak exit codes, and an adopter wrapping them in CI reads them:**
|
|
680
|
+
|
|
681
|
+
- `run-interview.mjs` — **0** the interview finished (and, when it spawned the paid step, that
|
|
682
|
+
step's own status is what you get, never a flattened 0); **1** a REFUSAL — a locked class, the
|
|
683
|
+
library's own reds over the answers, or a spec draft that does not validate; **2**
|
|
684
|
+
operator/config — usage, a missing or non-existent `--patient`, a present-but-empty `--lang`,
|
|
685
|
+
a malformed `--budget`, stdin ending mid-interview, or a child that could not be started;
|
|
686
|
+
**3** a LEAK that `scanSecrets` found in a file it had just written (count and path only —
|
|
687
|
+
echoing the matched string is the same leak, one hop on).
|
|
688
|
+
- `run-author.mjs` — **1** a refusal or a failed gate; **2** operator/config; **3** a leak;
|
|
689
|
+
**4** a CRASH inside the paid span. **3 deliberately OVERRIDES a 4**: a secret sitting in a
|
|
690
|
+
written file is the harder line of the two, and the crash keeps both of its own louder
|
|
691
|
+
channels — the whole error on stderr and its own `author-crash` spine record.
|
|
692
|
+
|
|
693
|
+
**A crash inside the paid span leaves a BODY.** The fallible span — from just after
|
|
694
|
+
`author-start` to the end of the main flow — sits in ONE try/catch that RETRIES NOTHING and
|
|
695
|
+
SWALLOWS NOTHING. The operator's copy goes first and verbatim (it must not depend on the two
|
|
696
|
+
writes below it succeeding), then the spine gets `author-crash` via `crashRecord`: `name`,
|
|
697
|
+
`message` and `code` through `redactSecrets`, the STACK through `scrubRaw` — the ONE persist
|
|
698
|
+
boundary, the same `SECRET_PATTERNS` inventory, bounded with the bound announcing its own size —
|
|
699
|
+
plus `author-end {outcome: 'crashed'}`, because `author-end` is the one record that says a run
|
|
700
|
+
stopped AT ALL and a crash omitting it leaves a spine that reads as still-running. The spine
|
|
701
|
+
write is itself guarded and its failure said out loud (F70: a crash handler that crashes
|
|
702
|
+
destroys the report it was built to make). Nothing ABOVE the try is inside it, deliberately —
|
|
703
|
+
the argv and config `die()` paths run before the spine file exists, so they stop loud on stderr
|
|
704
|
+
and 2 instead. **Latent, not live:** nothing reads the author spine today, and the nearest
|
|
705
|
+
reader (`classifyIncidents`, `src/ledger.js`) keys on types neither record uses, so both fall
|
|
706
|
+
through every branch UNCOUNTED — the fail-safe direction, uncounted rather than miscounted.
|
|
707
|
+
|
|
533
708
|
**Refusals are COUNTED (D13).** `refusalEvents(refusal)` returns the spine events: a
|
|
534
709
|
`job-red` carrying `{ code: 'request-red', verb, lib: 'bareloop' }` — the `lib` stamped at
|
|
535
710
|
the EMIT SITE, so `classifyIncidents` files it against bareloop's own catalogue and its
|
|
@@ -634,7 +809,35 @@ known-answer round-trip before tokens: `smoke-red` — a silent degradation thro
|
|
|
634
809
|
|
|
635
810
|
Outcomes: `green | already-green | escalated | unapproved-spec | job-red | smoke-red |
|
|
636
811
|
plan-red | check-red | close-red | close-unsupported | recipe-stale | branch-red | pricing-red |
|
|
637
|
-
provider-red | interpreter-red | cap-halt | wall-halt | step-stalled |
|
|
812
|
+
provider-red | interpreter-red | cap-halt | wall-halt | step-stalled | hitl-pause |
|
|
813
|
+
hitl-decision-red | step-red:<id>`.
|
|
814
|
+
|
|
815
|
+
**The two hitl terminals (N4 slice 1, doors re-cut 2026-08-18)** are the class's whole surface
|
|
816
|
+
at this layer, and each is a CLEAN exit (`spendComplete` stays true — only the two casualties floor). `hitl-pause` is a
|
|
817
|
+
decision-ready CHECKPOINT: the close reached a stage no machine can render, so the run stops
|
|
818
|
+
holding everything it has done, emits the evidence package (every mechanical stage's result, the
|
|
819
|
+
close's own `ask`, and the changed paths — bounded, trim announced), and the clock stops with
|
|
820
|
+
it. It is on `CHECKPOINT_OUTCOMES` (the canonical `resumableOutcomes` list, exported so an
|
|
821
|
+
exported bundle inherits one spelling), and `checkpointAgeGate` refuses one older than
|
|
822
|
+
`PAUSE_TTL_MS` (60 days) by NAMING the age and the TTL. The signer's own `pause` door mints
|
|
823
|
+
that SAME terminal, with `humanDecision: 'pause'` and an explicit `gap: null` on the record: no
|
|
824
|
+
fix loop, no worker round, nothing spent, the rerun allowance untouched, and the checkpoint left
|
|
825
|
+
exactly as it was. `hitl-decision-red` refuses a decision the run cannot act on — a word that is
|
|
826
|
+
not one of the three doors, a `rerun` with empty text, or a decision handed to a close with no
|
|
827
|
+
human stage — before anything is spent. Neither demotes a bridge, and both are excluded from the
|
|
828
|
+
ledger's escalation counting.
|
|
829
|
+
|
|
830
|
+
There was a third terminal, `hitl-cancel`, for a `cancel` door that no longer exists (hamr,
|
|
831
|
+
2026-08-17: *"pause can resume — that would be more honest"*). Nothing mints it and no constant
|
|
832
|
+
exports it; the ledger still RECOGNISES the bare string so a spine written before the change
|
|
833
|
+
reads as governance rather than as a counted capability gap. An unresumed pause expiring under
|
|
834
|
+
the TTL is what cancel used to be.
|
|
835
|
+
|
|
836
|
+
Pass the signer's answer as `humanRuling: { decision, text? }` (`accept | rerun | pause`, the
|
|
837
|
+
enumerated `HUMAN_DECISIONS` set); it is spent once, at
|
|
838
|
+
the close readings up to the moment the fix loop opens, so the human's words become `post.gap`
|
|
839
|
+
through the SAME seam (same bound, same scrub) and the next machine-clean tree pauses for a
|
|
840
|
+
SECOND review rather than converting one sentence forever.
|
|
638
841
|
`branch-red` is the WORK BRANCH refusing (below): the patient is not a git checkout, its
|
|
639
842
|
branch namespace has no free name, or a resume's recorded branch is gone. Zero tokens, and
|
|
640
843
|
never a fallback to working on the branch the run was handed. `provider-red` is a
|
|
@@ -661,9 +864,37 @@ three times on one call — each stall silently abandons the hung call and reiss
|
|
|
661
864
|
laundered into `provider-red`, and carries `spendComplete:false` (an abandoned call may
|
|
662
865
|
already have been billed). **The reissue is wall-aware:** past the run's deadline the fuse does
|
|
663
866
|
not self-heal — self-heal is what a run does with time left — and it gives up as `wall-halt`,
|
|
664
|
-
not `step-stalled`, since a replan has nothing left to re-allocate.
|
|
867
|
+
not `step-stalled`, since a replan has nothing left to re-allocate. **A `step-stalled` run is
|
|
868
|
+
a CHECKPOINT, not a verdict** (hamr's ruling, 2026-08-13 — PRD v1.64 §1): it is resumable on
|
|
869
|
+
the reference runner alongside the two governance halts, the work on disk stands, and its
|
|
870
|
+
`spendComplete:false` carries forward as a floor. Every one of them is a
|
|
665
871
|
decision-ready escalation with a terminal `job-end`: the spine never dangles.
|
|
666
872
|
|
|
873
|
+
**`gate-red` is not in that list, and the plan flow no longer mints it (F98).** A fence
|
|
874
|
+
DENIAL STREAK — bare-agent's BA-11 deny-spin guard returning `denied:*` after consecutive
|
|
875
|
+
refusals — is a **BOUNDED ATTEMPT, never a terminal**, on BOTH worker surfaces: the attempt
|
|
876
|
+
ends, `attempt-bounded` is emitted with the `reason`, the close judges the partial work, the
|
|
877
|
+
gap feeds forward, the caps are unchanged and the loop continues.
|
|
878
|
+
**The NEXT attempt is told which bound fired** (v1.64 §2): the bound carries
|
|
879
|
+
`{iteration, cause, reason}`, and where the cause is `denied` the note says the GATE cut the
|
|
880
|
+
attempt and quotes what the gate recorded, verbatim — instead of the round-cutoff sentence,
|
|
881
|
+
which was false on cause and on count and aimed the worker at its read budget when the thing
|
|
882
|
+
that stopped it was the fence. **Nothing is invented**: bare-agent returns `denied:<tool>`
|
|
883
|
+
and nothing else — no path, no streak count — so neither is claimed. Every other cause keeps
|
|
884
|
+
the round-bound wording byte for byte. The reason is scrubbed ONCE at capture through the one
|
|
885
|
+
`SECRET_PATTERNS` inventory and shared by prompt and spine, and trimmed under the repo's one
|
|
886
|
+
`GAP_TRIM_MARKER` (F90.2). It is the same lane
|
|
887
|
+
`max_turns` and `loop.stop()` take, for the same reason — the attempt ran, produced audited
|
|
888
|
+
work, and was cut short by a governance bound doing its job. **The fence holding is the
|
|
889
|
+
system working, which is the opposite of a fault**; routing it as an instrument stop killed a
|
|
890
|
+
converging run with $1.77 and 21.6 wall-minutes still on it. Nothing about the gate changed:
|
|
891
|
+
the same actions are denied, the same audit rows are written, a genuine scope escape produces
|
|
892
|
+
exactly what it did before — it now costs one bounded attempt instead of the whole run. The
|
|
893
|
+
category survives only as a passthrough (`ralph`'s decision table still answers to it if a
|
|
894
|
+
caller's own middle throws it, and `EXCLUDED_ESCALATIONS` still lists it so a future emission
|
|
895
|
+
is filed as governance rather than re-read as a capability gap), but no site in this library
|
|
896
|
+
raises it.
|
|
897
|
+
|
|
667
898
|
**Every `job-end` carries the money, on every path**: `{ outcome, spentUsd, spendComplete }`
|
|
668
899
|
(plus `step`/`cause`/`detail` where the outcome has them). `spentUsd` is the accumulated sum
|
|
669
900
|
of PRICED rounds ONLY — never an estimate from token counts or averages (cap-not-estimate) —
|
|
@@ -701,7 +932,10 @@ never replans) — plus at most ONE arbiter-granted extra on a second variance s
|
|
|
701
932
|
REAL close and governed by the close TREND rule below (`capRuns`, default 3, survives only as
|
|
702
933
|
its blind fallback). `plan-executed` (the plan-as-executed record, design law #2) lands on the
|
|
703
934
|
spine on every path that executed steps. Additional outcomes: `already-green |
|
|
704
|
-
plan-red | check-red | close-red | wall-halt
|
|
935
|
+
plan-red | check-red | close-red | wall-halt | hitl-pause | hitl-decision-red`.
|
|
936
|
+
A `human-confirms` stage is caught at BOTH close seams — the precheck (reaching a person there
|
|
937
|
+
means the machine half already passes on the untouched tree, so pausing costs $0 where drafting
|
|
938
|
+
a plan first would spend a budget to arrive at the same question) and the post-steps close. Worker prompts hold the v1.12 §5 contract
|
|
705
939
|
(mutation-proven): the absolute repo root, the step's action/target, prior artifacts,
|
|
706
940
|
the gap — NEVER the budget, the close command, a check's command, or the arbiter's books.
|
|
707
941
|
|
|
@@ -1000,7 +1234,9 @@ learning credit and runs no on-green retention. A close that cannot RUN is a `br
|
|
|
1000
1234
|
escalation before any provider call. So the resume story is: a `cap-halt` or `wall-halt`
|
|
1001
1235
|
stop IS the checkpoint (the workdir + the close), the human raises `budgetUsd` or
|
|
1002
1236
|
`maxWallMs` (a new spec hash — re-sign), and the rerun picks up where the budget died,
|
|
1003
|
-
paying zero provider calls for anything already green.
|
|
1237
|
+
paying zero provider calls for anything already green. (Those two are the *allowance* stops,
|
|
1238
|
+
the ones with a lever to raise. They are not the whole resumable set — `step-stalled` is also
|
|
1239
|
+
a checkpoint, with nothing to re-sign; see `resumableOutcomes` under the resume reader.)
|
|
1004
1240
|
|
|
1005
1241
|
**Unpriced is never free (F6/F12):** the ledger meters **per ROUND** (`worker-round`, at
|
|
1006
1242
|
bare-agent's `onLlmResult` seam) — money is counted as it is spent, never at the end of an
|
|
@@ -1022,8 +1258,16 @@ the loop contract (F16 — it is ONE attempt inside `while close-red and under-c
|
|
|
1022
1258
|
re-run with the close's verdict; without this a model one-shots and can eat the budget in
|
|
1023
1259
|
reads before ever writing). It is NOT allowed to read the run's own machinery (F14): the gate
|
|
1024
1260
|
audit, the smoke store and the litectx store are denied — the agent neither authors the
|
|
1025
|
-
arbiter nor reads its books.
|
|
1026
|
-
`
|
|
1261
|
+
arbiter nor reads its books. **That denial is now REGISTERED in the persona, not left to be
|
|
1262
|
+
discovered (F98):** `PERSONA_TOOLS` names `gate-audit.jsonl`, `.smoke`, `.litectx` and the
|
|
1263
|
+
run's spine as always-denied, records of how the worker is being judged and never anything
|
|
1264
|
+
about its task, beside the absolute-path law it is the twin of — both are fence facts a worker
|
|
1265
|
+
cannot infer. It renders for EVERY worker on every grant (a worker granted only `write` gets
|
|
1266
|
+
no component strategy paragraph at all and must still be told). The fence is unchanged; what
|
|
1267
|
+
changed is that the rule is stated instead of learned one refusal at a time, which cost a real
|
|
1268
|
+
run the rounds of a bounded attempt. N2 bounds (honest): `gold`/`rubric`/`hitl` OBJECT
|
|
1269
|
+
closes refuse `close-unsupported` — N4's hitl is a `human-confirms` STAGE inside a `closeDecl`,
|
|
1270
|
+
never a close TYPE, so there is one live expression of hitl rather than two.
|
|
1027
1271
|
|
|
1028
1272
|
### The reuse registry (Layer 3) — `src/bridges.js`, `src/selection.js`
|
|
1029
1273
|
|
|
@@ -1242,11 +1486,35 @@ semantics are byte-unchanged (PRD v1.46 §3):
|
|
|
1242
1486
|
is `$0`: a leg whose every round came back unpriced folds nothing countable and is still not
|
|
1243
1487
|
exact, so the declaration is emitted to carry that unknown forward rather than letting it read
|
|
1244
1488
|
as an exact zero (F6).
|
|
1245
|
-
- **`resumableOutcomes: [
|
|
1246
|
-
|
|
1247
|
-
complete" is true of a VERDICT; a
|
|
1248
|
-
|
|
1249
|
-
|
|
1489
|
+
- **`resumableOutcomes: [...]`** reclassifies a landed `job-end` whose outcome is a
|
|
1490
|
+
CHECKPOINT rather than a verdict as a restart rather than a completed row. "A landed
|
|
1491
|
+
job-end is complete" is true of a VERDICT; a checkpoint means the work is on disk and the
|
|
1492
|
+
plan is on the spine with nothing decided against them. **The set is the CALLER's** — the
|
|
1493
|
+
parameter stays a parameter, and its empty default is what the reuse loop's own graded-row
|
|
1494
|
+
semantics depend on — but the canonical ANSWER is exported as **`CHECKPOINT_OUTCOMES`**
|
|
1495
|
+
(`['cap-halt', 'wall-halt', 'step-stalled', 'hitl-pause']`), and the reference runner
|
|
1496
|
+
CONSUMES it rather than keeping a copy. The first two are governance halts — an operator-owned allowance ran
|
|
1497
|
+
out. `step-stalled` joined them 2026-08-13 on hamr's ruling (PRD v1.64 §1): it is the one
|
|
1498
|
+
terminal whose OWN escalation already offers *"retry the run"*, nothing about the work on
|
|
1499
|
+
disk is wrong, and the shape that named the fix is a stall that trips with time left and
|
|
1500
|
+
then rides out past the wall — losing a checkpoint a `wall-halt` would have kept. Its NAME
|
|
1501
|
+
deliberately stays `step-stalled` because `runJob` keys the **F44 spend FLOOR** on that
|
|
1502
|
+
outcome; renaming it would report unknown spend as exact. The floor travels: a stalled
|
|
1503
|
+
leg's `spendComplete:false` reaches the preview as `≥$x` and the resumed leg as
|
|
1504
|
+
`priorSpendComplete: false`, so the next terminal stays a floor rather than healing an
|
|
1505
|
+
unknown by inheriting it. A green or any red stays non-resumable under every setting — a
|
|
1506
|
+
verdict already rendered is never re-bought.
|
|
1507
|
+
**One exception, and it is a DERIVATION rather than a widening (D4a, F96).** A recorded
|
|
1508
|
+
`step-red` is re-read as a wall-halt — and becomes resumable — when the run's OWN spine
|
|
1509
|
+
carries a `wall-bounded {bounded: true}` record STRICTLY BEFORE its terminal, and only when
|
|
1510
|
+
`'wall-halt'` is itself in `resumableOutcomes`. It fires on the run's own primary evidence,
|
|
1511
|
+
which no caller can assert; a `step-red` with no wall record in front of it is untouched, so
|
|
1512
|
+
"a red is an answer" survives intact. It touches no close verdict — the grades stand and the
|
|
1513
|
+
resume seeds from them. The readout surfaces `wallDerivedHalt` so an operator resuming a spine
|
|
1514
|
+
that SAYS `step-red` is told which record turned it into a checkpoint. **This is a PERMANENT
|
|
1515
|
+
safety net, not a migration shim (ruled 2026-08-13, PRD v1.63 §2):** the defect that minted
|
|
1516
|
+
the shape is fixed at source, but spines are append-only forever, so the population it protects
|
|
1517
|
+
can never shrink — and on a healthy spine it is a branch that is never taken.
|
|
1250
1518
|
|
|
1251
1519
|
`restart.branch` is the WORK BRANCH the dead leg was working on, READ off its `work-branch`
|
|
1252
1520
|
record and never re-derived: the name is deterministic from the signed spec, so a restart that
|
|
@@ -1266,6 +1534,20 @@ watchdog `--wall-ms 0`, which it defaulted to `null` and armed no deadline at al
|
|
|
1266
1534
|
still advertising one. The refusal names the lever: raise `maxWallMs`, which moves the spec hash
|
|
1267
1535
|
and is re-signed.
|
|
1268
1536
|
|
|
1537
|
+
**A `hitl-pause` checkpoint is answered on the same command line** (N4, 2026-08-12 §5.2 — the
|
|
1538
|
+
terminal is the v1 surface; the panel is N6's). The reference runner takes
|
|
1539
|
+
`--decide accept|rerun|pause` with `--text` for the rerun door, gated on the SAME
|
|
1540
|
+
`--approve <specHash>` signature the run itself is signed with (ruling 4: the spec hash is the
|
|
1541
|
+
only signer proof this repo has, and no second identity mechanism was invented). The decision
|
|
1542
|
+
SEMANTICS are the library's — an adopter's runner should call `normalizeHumanRuling` and print
|
|
1543
|
+
its refusal rather than re-spell "three doors, no fourth" or "a rerun needs text", and hand the
|
|
1544
|
+
result to `runJob` as `humanRuling`. Two questions belong to the RUNNER because only it can ask
|
|
1545
|
+
them: a decision with no run to answer, and a decision aimed at a checkpoint that is not a pause
|
|
1546
|
+
(an `accept` there would mint a green over evidence nobody was shown). Before any of it, the
|
|
1547
|
+
paused spine goes through `checkpointAgeGate`, and the run is not resumed without an explicit
|
|
1548
|
+
decision: the lean-rerun rule (2026-08-12 §4) governs which door a prompt LEADS with, never an
|
|
1549
|
+
action taken for the operator.
|
|
1550
|
+
|
|
1269
1551
|
- **Completed tries are not re-run.** They come back as rows (marked `inherited`) and
|
|
1270
1552
|
their bridges stay excluded from every later selection. A try whose `job-end` landed —
|
|
1271
1553
|
the close judged it — counts as completed even if the kill beat its `try-end`; paying
|
|
@@ -1328,6 +1610,12 @@ and is re-signed.
|
|
|
1328
1610
|
fold — a garbage, non-finite or negative `count` reads as `0` rather than widening the ceiling.
|
|
1329
1611
|
Null/omitted is the cold path. The disarm control is pinned: a resumed leg with an UNSPENT
|
|
1330
1612
|
ceiling still replans, so the bound is bounded, not switched off.
|
|
1613
|
+
**NEVER-REFILL is now the RULING, not just the behaviour (2026-08-13, PRD v1.63 §4).** A resume
|
|
1614
|
+
inherits the spent replan ledger, entire; a refill would let every kill buy an allowance, which
|
|
1615
|
+
is unlimited replanning through the side door, and unlimited replanning launders thrash as
|
|
1616
|
+
adaptation. The consequence for an adopter is a real one and is deliberate: a resume whose
|
|
1617
|
+
ledger is already spent re-enters its plan with no channel to replace it. That is not funded —
|
|
1618
|
+
it is SHOWN, by the resume banner, before you sign.
|
|
1331
1619
|
- **Under the REMAINDER, never a fresh allotment.** The killed attempt's spend and wall
|
|
1332
1620
|
FOLD IN (`runJob`'s `priorSpentUsd`/`priorSpendComplete`/`priorWallMs`, `createClock`'s
|
|
1333
1621
|
`priorElapsedMs`), so
|
|
@@ -1405,7 +1693,9 @@ seeding an upstream ask. Deliberate exclusions
|
|
|
1405
1693
|
counted as unmapped, never dropped): `cap-halt`/`wall-halt` (budget stories, money and
|
|
1406
1694
|
time), `step-stalled` (the stall fuse firing is our governance, not an observed provider
|
|
1407
1695
|
failure), `step-variance` (a planning story), `gate-red`/`smoke-red` (governance working
|
|
1408
|
-
as intended / already counted
|
|
1696
|
+
as intended / already counted — and `gate-red` is no longer minted anywhere in this library,
|
|
1697
|
+
kept listed because this set is EXECUTABLE: dropping a name would not delete the category, it
|
|
1698
|
+
would re-file any future emission of it as a counted capability gap), `hitl-close`/`close-unsupported`/`close-unauthorable` (by design — and the last one
|
|
1409
1699
|
is excluded for a SECOND reason: its demand is already counted once as the `request-red`
|
|
1410
1700
|
the same refusal emits, so counting the escalation too would double every refusal),
|
|
1411
1701
|
`close-timeout`/`close-killed`/`close-crashed` (the arbiter's own named terminals, F17);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bareloop",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "Workflows that earn their own design, with receipts — agent-authored workflows for repeated, long, verifiable jobs that improve across runs under an un-gameable gate.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
"prepublishOnly": "npm run build:types"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"bare-agent": "^0.
|
|
27
|
-
"bareguard": "^0.
|
|
26
|
+
"bare-agent": "^0.36.0",
|
|
27
|
+
"bareguard": "^0.13.0",
|
|
28
28
|
"litectx": "^0.32.0"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|