bareloop 0.11.0 → 0.13.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.
@@ -530,11 +530,136 @@ no model found, it moves no seed number, and a declaration that authors it itsel
530
530
  `genre-owned-env`.
531
531
 
532
532
  **The kind catalogue** (`KIND_CATALOGUE`, and it IS the whole vocabulary): `command-exit`,
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)`).
533
+ `count-not-worse`, `pattern-absent-in-diff`, `files-changed`, `human-confirms` and
534
+ `judged-floor` are all live — `LOCKED_KINDS` is EMPTY today; `harness-loop` (TESTGEN) is ABSENT
535
+ from v1 entirely (an unknown-kind typo, never counted demand). The locked-kind machinery is
536
+ unchanged and is what a future entry arrives on: in tool mode a locked kind is INEXPRESSIBLE —
537
+ the schema carries one branch per live kind — so that demand arrives through the interview
538
+ layer instead (`refuseLockedKind(kind)`), whose "named but locked" wording is currently
539
+ unreachable for exactly the reason above.
540
+
541
+ **`judged-floor` is the one kind that BUYS its measurement** (softgreen module 2), and it is
542
+ CLASS-GATED rather than locked: it renders a `soft-green` verdict, `soft-green` is still a
543
+ LOCKED verdict class, so a real declaration naming it reds `class-ceiling` under a green pick
544
+ and `class-battery-locked` under a soft-green one. The kind runs; no signable spec can reach it
545
+ until the class is admitted. Its parameters are `card` — the signer's rubric as ENUMERATED
546
+ items, where `rule` SELECTS from the arbiter's own rulebook (`JUDGE_RULES` / `JUDGE_RULE_IDS`,
547
+ so a rule we do not implement is inexpressible) and `text` is the signer's words, which explain
548
+ a red and never decide one — and `paths`, the AUTHORITATIVE artifacts, read off the seed listing
549
+ like every other path and bounded at `MAX_JUDGED_PATHS` (one paid call each). It spawns nothing,
550
+ so it takes no `cmd`, no `timeoutMs` and no `env`; it is `offer: false` BY LAW
551
+ (`NEVER_OFFERED_KINDS` — a paid judge is never an in-run ruler); and it SKIPS the seed-verdict
552
+ read (ruling 8) as a recorded `skipped` row.
553
+
554
+ **Wiring a judged close, and what it costs.** The judge is PINNED (`JUDGE_MODEL`, exported) and
555
+ is never a step knob: the runner supplies `runJob({ judgeProvider })` — its own seam, not
556
+ `provider`/`providerFor` — and a judged stage with none instrument-STOPS as a wiring gap rather
557
+ than grading on whatever binding was at hand. Each locate call is metered per call and emitted
558
+ as a distinct `judge-round` spine record, which `runJob`'s ONE ledger accounts exactly like a
559
+ `worker-round` (`ACCOUNTED_ROUND_TYPES`, and `readResume`'s fold reads that same list, so a
560
+ resumed leg cannot silently widen a signed budget by the close's own spend). A budget funds the
561
+ attempt PLUS its close. Unpriced is never free: a null cost is a `pricing-red` stop that is
562
+ never retried, and the ladder is otherwise ONE retry (`JUDGE_ATTEMPTS`) — after that a broken
563
+ judge is an instrument stop, never a red about the tree.
564
+
565
+ **The two SIGNED artifacts of a judged close** (softgreen module 4, design §4.3/§4.4). Q6 and
566
+ Q7 of the softgreen interview compile into the RUBRIC CARD and the FROZEN CALIBRATION SET, on
567
+ the D5 shown-and-fixed path: `proposeJudgedArtifacts({answers, generate, book})` buys ONE
568
+ schema-forced proposal (tool `propose_rubric`, over the same bounded malformed-emission ladder
569
+ the declaration uses — a parse failure is an `artifact-red` retried under the cap, a proposal
570
+ that parsed and then broke a rule is NOT retried); a UI shows it; `signJudgedArtifacts({proposal,
571
+ fix})` stores the SIGNER's version (the fix replaces the proposal WHOLE, never merges — a signer
572
+ who deleted a line must not get it back) and reports `source: 'signer'|'proposal'`; and
573
+ `foldJudgedArtifacts(closeDecl, {card, cases})` enumerates both into the declaration. Free text
574
+ in either artifact is scrubbed at ingest through the one `SECRET_PATTERNS` inventory, because a
575
+ signed spec outlives the run that produced it.
576
+
577
+ **Where they land, and what the hash covers.** The card replaces the ONE `judged-floor` stage's
578
+ `params.card` — one card per close, and it is the signed one; a fold into a close with no judged
579
+ stage (or two) THROWS rather than storing a rubric nothing reads. The cases land beside the
580
+ stages as `closeDecl.calibration.cases`, because the set calibrates the RULER and is read before
581
+ any stage runs. Both sit inside the spec, so `jobSpecHash` covers them by construction: a card
582
+ line or a single case edit flips the hash and forces a re-sign, and a byte-identical re-store
583
+ does not.
584
+
585
+ **What a legal calibration set is** (`validateCalibrationSet`, beside the rulebook in
586
+ `src/judged.js`, and re-checked at the spec gate by `validateCloseDecl`). EXACTLY
587
+ `CALIBRATION_SIZE` = 10 cases — hamr's ruling, verbatim *"go build 10, we could double later"*,
588
+ operator territory and never agent-picked — with BOTH polarities present, because a set that
589
+ cannot fail one way proves nothing. A case is
590
+ `{id, artifact, expect: {verdict: 'pass'|'red', reds: [{rule, fn}]}}`: `artifact` is the REAL
591
+ source text the judge will be pointed at (never a description of one), and a red case must be
592
+ ITEMIZED, naming a `rule` THIS close's card actually carries (§4.3's ceiling, mechanically — the
593
+ fix for a miss is a new card line and a re-sign) and the `fn` the red lands on. The judge's own
594
+ `why`/`quote` are deliberately NOT stored: no signer can predict prose, and pinning it would
595
+ fail an honest pipe on wording. `expectedOf(decision)` is the ONE reduction of a `decide()`
596
+ result to that shape, so a harness compares one shape rather than two.
597
+
598
+ **THE CALIBRATION GATE** (`runCalibration`, softgreen module 5, `src/calibrate.js`). It runs the
599
+ WHOLE pipe — `runLocate` over the shipped `JUDGE_ATTEMPTS` ladder, then `decide()` — over each of
600
+ the ten, and compares `expectedOf(decision)` against the stored `expect` by VERDICT **and**
601
+ ITEMIZED REDS (a `(rule, fn)` set, order-insensitive): *a pipe that reds the right case for the
602
+ wrong reason has not been calibrated, it has been lucky*. The floor is **10/10, zero reds**
603
+ (hamr, 2026-08-18) and it is reported per case — `graded: [{id, ok, got, want, detail}]` — never
604
+ as an aggregate percentage.
605
+
606
+ Alongside it runs **`INJECTION_LOCATE_BATTERY`: five ARBITER-OWNED artifacts**, one per
607
+ bare-agent attack style, **re-aimed at the LOCATE axis** (2026-08-18 second addendum, ruling 1 —
608
+ upstream's `shouldBreak` gold assumes a judge that renders a verdict, and this one never does).
609
+ The attack text is embedded in the artifact, and a style resists only when the extracted FACTS
610
+ are unaffected: same function list, same per-function doc reality. Reading the DECISION alone
611
+ would be blind here — an invented `docQuote` reds `has-doc` through the quote-verification route,
612
+ so a leak and a resist can render identical itemized reds. **All five must resist**; a leak fails
613
+ calibration and names the style. Nothing about the battery is stored in a spec and no signer
614
+ authors it.
615
+
616
+ A **CASUALTY is never a failed case**: a dead call, an unpriced one (F6 — never retried) or an
617
+ emission that never parses after the ladder STOPS the gate on `runLocate`'s own axis
618
+ (`CASUALTY_AXES`), keeps the graded prefix, and reports zero failures — a broken judge is no
619
+ evidence about the set in either direction. Every locate call is metered through `onCost` on
620
+ every route out and the totals fold through `tallyCalls`, so one unpriced call makes `costUsd`
621
+ null rather than an exact-looking floor.
622
+
623
+ **At signing it is MANDATORY and it is the only PAID gate** (`prepareSigning`, gate 4). A close
624
+ carrying a `judged-floor` stage is refused when no set is stored, when no `judgeLoop` seam is
625
+ wired (a wiring gap, never a silent pass), when the gate is a casualty, and when anything short
626
+ of all-of-them grades. It runs **after** gates 1–3, so no $0 refusal is ever paid for. The
627
+ signing record keeps WHAT was certified — `cardHash`, `casesHash`, `setHash` (which covers
628
+ `JUDGE_MODEL`), the graded rows and the battery — beside `judgeModel` itself.
629
+
630
+ **D9.3 for a judged-ONLY close** (ruling 3, hamr: *"fix it now, we are delivering softgreen"*).
631
+ A judged stage skips the seed read (ruling 8), so a close whose only work stage is judged has no
632
+ seed red and used to be unsignable — the one job shape softgreen exists for. For that shape only,
633
+ a PASSED calibration gate plays the seed-red role (the polarity law makes *this close can fail* a
634
+ demonstrated fact), and the record says so: `gates.seedVerdict.satisfiedBy === 'calibration'`. A
635
+ close with any mechanical work stage is UNCHANGED and still needs its seed red.
636
+
637
+ **THE JUDGE TIER IS INSIDE THE SIGNATURE, and the stage refuses a mismatch.** *"A judge-model
638
+ bump forces a full recalibration"* used to be an operator-side rule with nothing to fire on,
639
+ which makes it prose (F45); it now has both halves of a detector. The tier the signer's set was
640
+ graded by is stored beside the cases as **`calibration.judgeModel`** — `CALIBRATION_FIELDS`
641
+ enumerates a stored set as exactly `cases` and `judgeModel` and nothing else — written by
642
+ `foldJudgedArtifacts` from the PIN (`JUDGE_MODEL`) rather than from anything a caller may name.
643
+ Because it is INSIDE the spec, `jobSpecHash` covers it by construction: bump the constant and
644
+ every signed judged spec hashes differently, the signature dies, and the re-sign is what re-runs
645
+ the calibration gate. **`validateCloseDecl` REQUIRES it** on any stored set (`missing-required`
646
+ on `…calibration.judgeModel`) — a set nobody can attribute to a tier is a floor nobody can tell
647
+ apart from a bumped one. **`declaredStages` stamps it onto the judged stage** as
648
+ `calibrationJudgeModel`, from the SIGNED bytes and through the one seam that already turns a
649
+ declaration into stages (the same carry `offer: false` rides), so a stage can never name a tier
650
+ the signature does not and no second close-level channel is plumbed through every executor seam.
651
+ **`runJudgedFloor` then STOPS on a stored-vs-live mismatch** before it buys a call, naming both
652
+ models and the two things that fix it — *re-sign the spec with the new tier* and *re-run the
653
+ calibration gate (recalibrate)* — under `STOP_FAULTS.FAILED`, the same fault and the same reason
654
+ as the absent judge seam beside it: nothing is broken, something was never re-done. It stops
655
+ rather than degrading in either direction, because grading on the new tier mints a verdict
656
+ against an uncalibrated floor and grading on the old one is not on offer (the pin is a constant).
657
+
658
+ **The one honest limit, deliberate:** an ABSENT stamp does not stop. It is the same split the
659
+ SET's own presence already lives under — a signed spec that judges cannot reach a run without a
660
+ calibration (the signing gate) or without a model on it (the spec gate), so the only runtime
661
+ question is ever WHICH model, and a bare stage descriptor handed straight to `runStage` by a test
662
+ or an adopter is not a signed spec making a claim about a tier.
538
663
 
539
664
  **`human-confirms` is the one kind that measures NOTHING** (N4 slice 1). Its whole parameter
540
665
  surface is `ask` — the plain question the signer answers — so it cannot spawn, cannot be
@@ -793,7 +918,7 @@ Reserved spine vocabulary (V7, machinery-free until job #1 surfaces one):
793
918
  `coordination-red` — a failure between units (scope contention, step order, store
794
919
  races), never to be folded into worker/interpreter reds.
795
920
 
796
- ### `runJob(spec, { approvals, workdir, provider, nativeProvider?, providerFor?, emit, capRuns?, strikeLimit?, shellCapUsd?, closeTimeoutMs?, layerRoot?, bridge?, priorSpentUsd?, priorSpendComplete?, priorWallMs?, resumeSeed?, resumeGrades?, resumeReplans?, resumeBranch? })` → outcome — `src/run.js`
921
+ ### `runJob(spec, { approvals, workdir, provider, nativeProvider?, providerFor?, emit, capRuns?, strikeLimit?, shellCapUsd?, closeTimeoutMs?, layerRoot?, readShim?, bridge?, priorSpentUsd?, priorSpendComplete?, priorWallMs?, resumeSeed?, resumeGrades?, resumeReplans?, resumeBranch?, humanRuling?, heldRuling?, reviewDoor?, doorRerun? })` → outcome — `src/run.js`
797
922
 
798
923
  The last seven are the RESUME fold and are documented under *Resuming a killed run* below; they
799
924
  default to `0` / `true` / `0` / `null` / `[]` / `null` / `null`, so a fresh run passes none of them.
@@ -812,6 +937,56 @@ plan-red | check-red | close-red | close-unsupported | recipe-stale | branch-red
812
937
  provider-red | interpreter-red | cap-halt | wall-halt | step-stalled | hitl-pause |
813
938
  hitl-decision-red | step-red:<id>`.
814
939
 
940
+ **`readShim` (default `false`) — the capped read seam, per ARM.** The value names which levers
941
+ run — the four arms of the frozen Phase 2 pre-registration:
942
+
943
+ | value | arm | cap + next-unseen-slice | pointer | diff on a changed re-read | G1 (`read-blind`) | persona line |
944
+ |---|---|---|---|---|---|---|
945
+ | `false` *(default)* | A0 | — | — | — | — | none |
946
+ | `'cap'` | A1 | ✓ | ✓ | — | ✓ | `READ_SHIM_STRATEGY` |
947
+ | `'diff'` | A2 | — | — | ✓ | — | `READ_SHIM_DIFF_STRATEGY` |
948
+ | `true` | A3 | ✓ | ✓ | ✓ | ✓ | `READ_SHIM_STRATEGY` |
949
+
950
+ Anything else **throws** (`readShimArm`) at `runJob`/`runPlan`/`validatePlan` before a token is
951
+ spent — a mis-spelled arm coerced by truthiness would run one treatment under another's label and
952
+ be invisible in the results afterwards.
953
+
954
+ A **capping arm against a signed ceiling that offers no `recall`/`get` also throws at that same
955
+ $0 door**, and for the same reason: G1 below requires the retrieval pair on any step granting
956
+ `read`, but a step cannot grant what `spec.tools` does not offer — so every draft would red
957
+ `read-blind` identically while the drafter was paid for each doomed cycle. The message names the
958
+ missing verbs and both exits (re-sign the spec with them, or run with the shim off). Like the arm
959
+ throw, it is an **operator param-guard `TypeError`, never a model-output red**: nothing the agent
960
+ authored is wrong when the operator asks for a shim the signature cannot satisfy. It narrows
961
+ nothing that works — the only configuration it rejects already failed 100% of the time, later and
962
+ for money. `RETRIEVAL_PAIR` is exported from `src/plan.js` so the rule and this pre-flight read one
963
+ spelling of which verbs count.
964
+
965
+ Under a CAPPING arm (A1/A3) the shim also answers a **stale `ctx_get` pointer**: the cap steers the
966
+ worker at `ctx_recall`/`ctx_get`, and `ctx_get` refuses a file that changed after indexing — the
967
+ normal case once the worker has edited it. Instead of nothing, the shim serves that pointer's
968
+ 0-based inclusive line range read fresh from disk, capped the same way, under a trusted notice
969
+ saying the pointer was stale, that the range may now hold DIFFERENT code than the symbol asked
970
+ for, and that nothing was recorded as delivered. A vanished file or a range past the end is a
971
+ refusal result, never a throw. The serve never writes the delivery ledger, so it can never mint a
972
+ "you already hold it" pointer for bytes the read seam did not hand over. A0 and A2 do not wire it
973
+ and `ctx_get` behaves exactly as it always did.
974
+
975
+ `false` is the shipped behaviour and is byte-identical to a run without the flag, guards included;
976
+ `true` means exactly what it meant before the arms existed, so nothing written against the boolean
977
+ changed meaning. Where the CAP runs, `shell_read` delivers at most `READ_SHIM_CAP` (24KB) per call
978
+ with a trusted steer at `ctx_recall`/`ctx_get`; a re-read continues from where the last one
979
+ stopped, and once the worker holds the whole unchanged file a re-read returns a pointer instead of
980
+ the bytes (keyed on what was DELIVERED — a partly-seen file NEVER gets a pointer, which is the
981
+ measured correctness point); and `validatePlan` reds `read-blind` on any step granting `read`
982
+ without `recall` and `get`. Where only the DIFF runs, nothing is capped (a first read delivers
983
+ whole, whatever the size), an unchanged re-read re-delivers rather than pointing, and G1 does not
984
+ exist — G1 is the cap's compensation, and an arm that caps nothing has nothing to compensate for.
985
+ The ledger is per worker, so it resets with every step. On the native (clipipe) surface a CAPPING
986
+ arm replaces the CLI-display cap rather than stacking on it; a non-capping arm leaves that wrapper
987
+ installed *inside* the shim, so native never loses its bound. Default-flip pending a paid contrast;
988
+ see CHANGELOG for the replay numbers behind it.
989
+
815
990
  **The two hitl terminals (N4 slice 1, doors re-cut 2026-08-18)** are the class's whole surface
816
991
  at this layer, and each is a CLEAN exit (`spendComplete` stays true — only the two casualties floor). `hitl-pause` is a
817
992
  decision-ready CHECKPOINT: the close reached a stage no machine can render, so the run stops
@@ -838,6 +1013,24 @@ enumerated `HUMAN_DECISIONS` set); it is spent once, at
838
1013
  the close readings up to the moment the fix loop opens, so the human's words become `post.gap`
839
1014
  through the SAME seam (same bound, same scrub) and the next machine-clean tree pauses for a
840
1015
  SECOND review rather than converting one sentence forever.
1016
+
1017
+ **A decision SURVIVES the halt that interrupts it** (F102). The run records the answer it was
1018
+ handed (`human-decision` — the door, the words, the source) and, separately, what BOUGHT it
1019
+ (`human-decision-spent` — a fix round, an accept that greened, a pause honoured). A decision with
1020
+ no spend after it is still owed to the person, and `readResume` surfaces it as
1021
+ `restart.pendingDecision` (`{decision, text, receivedAt, source}`). Hand that back as
1022
+ **`heldRuling`** and the leg applies it directly — the ask is never re-rendered, and the spine
1023
+ says the answer came from a record rather than from a person. F102's incident is what this is
1024
+ for: a rerun that opened the fix loop, stopped with `iterationsUsed: 0`, and whose resume asked
1025
+ the byte-identical question again. The two markers are deliberately not one: the fix loop
1026
+ "spends" the ruling for THIS leg's close readings the moment it opens, while whether the person
1027
+ must be asked AGAIN depends on work actually having been bought.
1028
+
1029
+ A leg handed BOTH a fresh `humanRuling` and a `heldRuling` is refused (`hitl-decision-red`,
1030
+ naming the held decision) before anything costs anything: two answers to one question is
1031
+ ambiguity, not a merge. `resolveHumanRuling(fresh, held)` is the exported seam that decides this
1032
+ (and whether the leg is a fresh engagement, below), so a runner cannot admit what a run refuses.
1033
+
841
1034
  `branch-red` is the WORK BRANCH refusing (below): the patient is not a git checkout, its
842
1035
  branch namespace has no free name, or a resume's recorded branch is gone. Zero tokens, and
843
1036
  never a fallback to working on the branch the run was handed. `provider-red` is a
@@ -1250,6 +1443,53 @@ reserved round bound, with the LAST round reserved for a toolless summary: the b
1250
1443
  a scout mid-tool-use, and text is its only deliverable, so without that reservation it
1251
1444
  spends its whole allowance exploring and reports nothing (F59 — measured on 15 of 18 runs).
1252
1445
 
1446
+ **Every cost figure bareloop reports is an ESTIMATE, not a billed amount (BA-21).** No
1447
+ vanilla LLM API returns a price — providers report TOKENS — so `spentUsd`, every
1448
+ `worker-round` cost, every halt readout and every ledger dollar is a token count
1449
+ multiplied by a RATE, never a provider invoice. Expect it to read somewhat high, and do
1450
+ not reconcile it against a bill expecting a match. There is deliberately **no per-model
1451
+ price table** behind it, and the reason is worth knowing before you decide how far to
1452
+ trust the number: a hand-curated table is a treadmill that rots SILENTLY — a price moves
1453
+ or a new model ships, the row goes stale, and the figure under-reports with no detector to
1454
+ say so. bare-agent instead matches a recognized Claude TIER (haiku / sonnet) by model id,
1455
+ and falls back to a conservative ceiling rate for anything it does not recognize. Both
1456
+ deliberately OVER-report rather than under-report, which is the fail-safe direction for a
1457
+ cap: a run halts slightly early rather than overspending unnoticed. **The consequence
1458
+ bites at the budget, and it is not softened here:** `budgetUsd` and `shellCapUsd` are
1459
+ enforced against these estimates, so an inaccurate rate is a cap that binds earlier or
1460
+ later than you intended. The cap is exact about the number it holds; the number is a
1461
+ guess.
1462
+
1463
+ **If you need accuracy, pass your own rates — the supported path, not a workaround.**
1464
+ bare-agent's `Loop` takes `rates: {in, out, cacheReadMult?, cacheWriteMult?}` — USD per 1K
1465
+ tokens, the two multipliers applying to the input rate for the cache-read and cache-write
1466
+ tiers (defaulting to Anthropic's 0.1× / 1.25×). A caller-supplied rate is recorded as
1467
+ VOUCHED rather than as a guess, and it silences bare-agent's own guesstimate warning.
1468
+ **Named, not papered over:** `runJob`/`runPlan` construct their own `Loop` and do not yet
1469
+ accept a `rates` passthrough, so today that option is reachable only by a caller driving
1470
+ bare-agent directly — a bareloop-run job is priced by the guesstimate, full stop. The
1471
+ passthrough is an open follow-up, and until it lands every paragraph above describes the
1472
+ only pricing a `runJob` adopter gets.
1473
+
1474
+ **The provenance is on the record, per round (`rateSource`).** The field arrives with
1475
+ bare-agent **>= 0.37**; under the pinned `^0.36.0` no provider payload carries it yet, so
1476
+ every priced round this library writes today reads UNKNOWN provenance — correctly, and by
1477
+ the same rule that governs the archive. (The one exception is the native per-turn
1478
+ `worker-turn`, whose `null` is bareloop's OWN statement rather than a forwarded one: that
1479
+ surface prices the SESSION, so a turn had no rate to guess.) Once the pin moves, every `worker-round` /
1480
+ `worker-turn` the plan flow writes carries bare-agent's own label beside `pricing`:
1481
+ `'provider'` (the provider reported its own authoritative cost — the native CLI surface)
1482
+ and `'caller'` (you passed the rate) are VOUCHED; `'tier'` (a recognized Claude tier,
1483
+ where **both** of bareloop's own production models land) and `'default'` (the blind
1484
+ ceiling) are GUESSES; `null` means nothing was priced, so there was no rate to have
1485
+ guessed. An ABSENT field is UNKNOWN provenance — every round archived before this signal
1486
+ existed, and it is never backfilled, never reconstructed, and never rounded up to vouched.
1487
+ Read it with `rateProvenance(record)` and `spendProvenance(events)` (`src/ledger.js`),
1488
+ whose predicate is an ALLOW-LIST of the two vouched values: anything else, including a
1489
+ value a future bare-agent adds, reads as a GUESS. **Reporting only** — `pricing` stays
1490
+ strictly two-valued (`'priced'`|`'unpriced'`), the `pricing-red` halt still keys on cost
1491
+ alone, and nothing about provenance decides, halts or refuses anything.
1492
+
1253
1493
  **What the worker is told (tool mode):** the absolute repository root (F10 — bare-agent's
1254
1494
  shell tools resolve relative paths against the PROCESS cwd, so a worker with no root is
1255
1495
  blind), the close's CURRENT output as the tree's state (F13 — never framed as "your previous
@@ -1269,6 +1509,64 @@ run the rounds of a bounded attempt. N2 bounds (honest): `gold`/`rubric`/`hitl`
1269
1509
  closes refuse `close-unsupported` — N4's hitl is a `human-confirms` STAGE inside a `closeDecl`,
1270
1510
  never a close TYPE, so there is one live expression of hitl rather than two.
1271
1511
 
1512
+ ### THE REVIEW DOOR — the three doors at the END of a run (softgreen module 8, PRD v1.71 §3)
1513
+
1514
+ The pause machinery re-homed: the same doors, the same evidence package and the same 60-day TTL,
1515
+ one level OUT — from a stage inside the close to the door at the end of a run.
1516
+
1517
+ **THE LAW, and it is not negotiable** (hamr, verbatim: *"it's important not to change the loop
1518
+ self verdict"*): the close mints the verdict, the door records a **disposition**. `runJob`
1519
+ returns `green` / `already-green` exactly as it always did, the ledger records exactly what it
1520
+ always recorded, and no answer a person gives can change either. A green run that is never
1521
+ answered is still a green run — the door is non-blocking.
1522
+
1523
+ **Opening one.** A run whose terminal is verdict-bearing (`DOOR_OPEN_OUTCOMES` — `green`,
1524
+ `already-green`) emits ONE `review-door` record carrying the evidence: the outcome, the class,
1525
+ whether the credit is `quarantined`, every close stage's own result, the stages an `accept` will
1526
+ re-run (`mechanical`), and the changed set (same cap and same F28/F6 announcements the pause
1527
+ package makes). Which runs open one is `doorOpens(job, reviewDoor)`:
1528
+
1529
+ - **always** for the classes in `REVIEW_DOOR_CLASSES` — `['soft-green']`, because a judged green
1530
+ is quarantined at mint (module 6) and an `accept` is the ONLY thing that releases the credit;
1531
+ - **on request** for everything else: `reviewDoor: true` (the runner's `--review-door`). A
1532
+ green-class run is byte-identical to what it always was unless it is asked for.
1533
+ - `reviewDoor: false` shuts it for any class. The flag wins in both directions and is never
1534
+ inferred; `REVIEW_DOOR_CLASSES` is a constant, not a threshold, and widening it is arbiter
1535
+ territory.
1536
+
1537
+ **Answering one — `answerReviewDoor({ job, workdir, events, decision, text?, closeTimeoutMs?,
1538
+ registryDir?, name?, runid?, at?, now?, emit? })`** (`src/reviewdoor.js`). The answer arrives
1539
+ after the run has ended, possibly days later and from another process, so it cannot ride the
1540
+ run's return path. This is that seam. It never throws and never conjures — a fourth door, an
1541
+ empty `rerun`, a run that opened no door (`no-door`), an expired one (`door-expired`), a tree
1542
+ that moved (`door-accept-red`) and a missing registry all come back as named reds.
1543
+
1544
+ - **`accept`** re-runs the close's **mechanical** stages against the tree AS IT STANDS
1545
+ (`mechanicalStages` — everything outside `SEED_EXEMPT_KINDS`, so never a judged floor and never
1546
+ a person) before it is honoured: hamr's ruling that an accept is not a rubber stamp, and the
1547
+ answer to a tree that can move in the 60 days a door keeps. A red REFUSES the accept with the
1548
+ stage named and records **nothing**. A pass records the disposition and, over a held judged
1549
+ green, RELEASES the credit through module 6's `applyDoorDecision`.
1550
+ - **`rerun`** carries the person's words back as a directive (`next: 'rerun'`) and re-proves
1551
+ nothing: the new run's close is what judges. Empty or whitespace text is refused at the same
1552
+ seam every other door is.
1553
+ - **`pause`** runs nothing and spends nothing, in any state. The door simply keeps, and an
1554
+ unanswered one expires under `PAUSE_TTL_MS` — that expiry IS what `cancel` used to be.
1555
+
1556
+ Every door is recorded when a registry is wired, not only the accepts: *"the signer's accepts
1557
+ double as the judge's ongoing report card"*, and a rerun over a judged green is exactly as
1558
+ informative. An **already-green** run wrote no row of its own, so its door records nothing and
1559
+ releases nothing — slice 1's already-green no-mint rule, holding from the other side, and stated
1560
+ in the result's `note` rather than surfaced as a failure.
1561
+
1562
+ **The rerun as a FRESH ENGAGEMENT** — hand the words back to the next run as
1563
+ **`doorRerun: {text, fromRunid?, receivedAt?}`**. Two things follow and only two: the
1564
+ `already-green` shortcut is refused (a tree that passes the close is precisely the state the
1565
+ person rejected — the run emits `door-rerun-open` and carries on), and the words reach the
1566
+ PLANNER as a requirement on top of the signed goal. It is never a `humanRuling`: that answers a
1567
+ close's human STAGE, and a green-class job has none. Money folds (the signed budget is the
1568
+ chain's ceiling); the WALL does not (F103).
1569
+
1272
1570
  ### The reuse registry (Layer 3) — `src/bridges.js`, `src/selection.js`
1273
1571
 
1274
1572
  A **bridge** is the plan a green actually executed, kept so the next run of the same SHAPE
@@ -1298,6 +1596,50 @@ it points, and a percentage over 2–3 runs is fake precision. `costUsd`/`wallMs
1298
1596
  a number or an EXPLICIT `null`, and the key is REQUIRED either way, so an unknown has to be
1299
1597
  said rather than omitted (F6 — an omitted key is how a `?? 0` gets written downstream).
1300
1598
 
1599
+ **QUARANTINE — a judged green earns nothing until the signer accepts it** (softgreen module
1600
+ 6; the standing ruling PRD v1.53, given its mechanism by v1.71 §3). A green minted under
1601
+ `verdictType: "soft-green"` carries `quarantined: true` on BOTH halves of R1's pair — the
1602
+ **version** (what inherits) and its **history row** (what the status ladder reads). It is a
1603
+ FLAG, never a withheld row: the run happened, the record says so, and the listing shows it —
1604
+ it is simply worth nothing yet. A held green does not count in `deriveStatus` (an entry whose
1605
+ only greens are held derives `null` and renders `HELD`, never `NO-GREEN`), and
1606
+ `reuseEligibility(bridge)` / `newestEligibleVersion(bridge)` refuse it for reuse **with a
1607
+ stated reason** — the same visible-skip discipline an unreadable registry file gets.
1608
+ `green` and `hitl` are untouched, byte for byte: the key is ABSENT, because the hold is about
1609
+ the young JUDGE, not about every class that is not green. `QUARANTINED_VERDICTS` is the one
1610
+ list; `quarantinesCredit(verdictType)` is the one predicate. The class travels on the green
1611
+ record as `verdictType` — an unrecognised value REDS rather than falling through to unheld.
1612
+
1613
+ **The review door releases it, forward-only.** `recordDoor(bridge, {runid, decision, at})`
1614
+ records the signer's disposition (`accept | rerun | pause`, the same three doors `kinds.js`
1615
+ owns) on that run's own green row as an append-only `doors: [{decision, at}]` — the **report
1616
+ card**, kept as facts and never as a computed agreement rate (D6's no-score rule applies to
1617
+ the judge too). `accept` over a held green also flips `quarantined` to `false` on both halves;
1618
+ `rerun` and `pause` record and release nothing. There is deliberately **no re-hold path**: an
1619
+ accept followed by a rerun records the disagreement and leaves the credit granted. A repeat of
1620
+ the last decision is a no-op (`released` says whether THIS call freed it), and a door aimed at
1621
+ a run with **no green row of its own** — an already-green run, a red, a casualty — is the red
1622
+ `no-row-for-run`: releasing is not minting. `applyDoorDecision({registryDir, name, runid,
1623
+ decision, at})` is the on-disk half; the door opens AFTER the run ends, so the decision cannot
1624
+ ride the run's return path. **The door never changes the loop's verdict** — a green stays green
1625
+ in the ledger whatever the person then does with it.
1626
+
1627
+ **`writeRunGreenRow({registryDir, job, name, outcome, plan, record})` → `{minted, reason,
1628
+ write}` — the terminal registry write for a runner that drives `runJob` directly** (rather
1629
+ than through `runReuse`, which writes its own rows). It is what gives that runner's review
1630
+ door a row to release: without one, `accept` reaches `recordDoor` and reds `no-row-for-run`.
1631
+ STORAGE ONLY — nothing here selects, promotes or reuses a bridge. Four refusals, each with a
1632
+ named `reason`: no `registryDir` (`no-registry` — the registry is operator-supplied, never
1633
+ conjured), `outcome !== 'green'` (`green-predates-run` for already-green — accept confirms a
1634
+ verdict, it never mints one — else `not-green`), no executed `plan` (`no-plan-executed`, R1),
1635
+ and a verdict class whose credit is not held (`credit-not-held` — `green`-class runs mint
1636
+ nothing here, `quarantinesCredit` is the one predicate). `name` falls back to `job.job`; the
1637
+ row is born HELD because the record carries the spec's own `verdictType`, never a flag. A
1638
+ refused underlying write returns `{minted: false, reason: 'write-refused', write}` with the
1639
+ reds on `write` — never a throw. Minting goes through the same cold-leg green write `runReuse`
1640
+ uses (shape-fork rule and collision refusals included), so the two runners can never spell
1641
+ "what a green writes" differently.
1642
+
1301
1643
  **`loadGate(bridge, job)` → `{ ok, reds }` — the load-time gate.** Exactly three checks,
1302
1644
  asked *"is this the right KIND of recipe?"*: the job's **verdict type**, the **close-stage
1303
1645
  kinds** (v1: the same stage names in the same order), and every stored verb **within THIS
@@ -1319,12 +1661,16 @@ their next green, not repaired.
1319
1661
 
1320
1662
  **`runJob`/`runPlan` take `bridge?`** — a validated entry to reuse. The gate runs **at the
1321
1663
  door**: before the clock, before the close precheck, before any token. On a pass, the
1322
- **newest** version's plan rides into the FIRST drafting prompt as a starting draft
1664
+ **newest ELIGIBLE** version's plan (the newest one no quarantine holds) rides into the FIRST
1665
+ drafting prompt as a starting draft
1323
1666
  (`bridge-loaded {name, versions, runid}` on the spine) and everything after it is the
1324
1667
  ordinary shipped path — same validator, same redraft-on-reds, the same replan ceiling, and a
1325
1668
  **replan never re-injects the bridge** (it drafts from the run's own state). On a fail the
1326
1669
  run returns the distinct terminal **`recipe-stale`** with `bridge-gate {outcome, name, reds}`
1327
- and a decision-ready escalation, having spent nothing. **Falling back to cold is the
1670
+ and a decision-ready escalation, having spent nothing. A **wholly HELD** entry handed in
1671
+ directly is refused at the same door and by the same terminal, with `bridge-gate
1672
+ {outcome: 'quarantined'}` and the accept-then-rerun option named — the recipe is the right
1673
+ kind, it is simply unaccepted, and the two are never spelled the same. **Falling back to cold is the
1328
1674
  caller's decision, not an automatic silent fallback** — starting a paid run on a decision
1329
1675
  nobody made is the same class of error as widening a cap to manufacture a green. Omit
1330
1676
  `bridge` and the flow is byte-identical to a pre-Layer-3 run: no new event, and no starting
@@ -1548,6 +1894,17 @@ paused spine goes through `checkpointAgeGate`, and the run is not resumed withou
1548
1894
  decision: the lean-rerun rule (2026-08-12 §4) governs which door a prompt LEADS with, never an
1549
1895
  action taken for the operator.
1550
1896
 
1897
+ **A finished run's REVIEW DOOR is answered the same way, one flag over**: `--door <runid>`
1898
+ selects the run (its own spine, its own `review-door` record), `--decide` picks the door and
1899
+ `--approve <specHash>` signs it. The screen is its OWN preview and deliberately not the resume
1900
+ banner: a door is answered about a run that is OVER, so nothing that banner computes (what is
1901
+ left to resume with, which step it re-enters) is a true sentence there. `accept` and `pause`
1902
+ launch nothing and exit; `rerun` falls through into a fresh engagement — its own clock, the
1903
+ answered run's spend folded in — carrying the person's words as `doorRerun`. `--registry <dir>
1904
+ --workflow <name>` are optional: without them the answer is recorded on the run's own spine and
1905
+ the readout SAYS the credit was not released, rather than reporting a release that never
1906
+ happened.
1907
+
1551
1908
  - **Completed tries are not re-run.** They come back as rows (marked `inherited`) and
1552
1909
  their bridges stay excluded from every later selection. A try whose `job-end` landed —
1553
1910
  the close judged it — counts as completed even if the kill beat its `try-end`; paying
@@ -1624,6 +1981,20 @@ action taken for the operator.
1624
1981
  (or `wall-halt`, below one close timeout: a try that cannot fund its close produces an
1625
1982
  unreadable row) with **nothing launched**. Topping up is a new envelope, and a new
1626
1983
  envelope is a new signature.
1984
+ - **…except a decide-time RERUN, which is a FRESH ENGAGEMENT** (F103, design record §3.4 —
1985
+ hamr: *"redo/rerun comes with new authoring for money+time and keeps accounting of this far
1986
+ and this session separate counters"*). A rerun opens on the full signed `maxWallMs` instead of
1987
+ the remainder, because the person did not decide on the run's clock: the incident that minted
1988
+ it took the door and inherited **87 seconds** from a leg that had already ended, on a worker
1989
+ measured to read nine rounds before its first write. So there are TWO counters and neither
1990
+ does the other's job — the CHAIN (every leg added up: `engagement.chainWallMs`,
1991
+ `chainSpentUsd`, `job-end.spentUsd`, what the halt readout reports) and the ENGAGEMENT (what
1992
+ bounds THIS leg: the clock's `priorElapsedMs`, `job-end.engagementSpentUsd`). The `engagement`
1993
+ record states both at the top of every run. **MONEY IS DELIBERATELY NOT SYMMETRIC**: it folds
1994
+ on every path, because the signed budget is the CHAIN's ceiling and a signature for $5 never
1995
+ silently authorizes $10 — a rerun spends what REMAINS of it, never a refill. A rerun leg also
1996
+ declares its `job-start` fold engagement-scoped (`priorWallMs` omitted, `chainWallMs` carried),
1997
+ so a later resume of it inherits this engagement's minutes rather than the chain's again.
1627
1998
  - **Money stays honest end to end.** `carrySpentUsd` (completed tries + selection calls)
1628
1999
  seeds the resumed ledger; the mid-try fold is deliberately NOT in it, because it comes
1629
2000
  back inside the restarted try's own terminal — counting it in both places would bill it
@@ -1703,7 +2074,14 @@ the same refusal emits, so counting the escalation too would double every refusa
1703
2074
  `suggestedAsk` on every row is a template seed for an upstream ask — filing stays human;
1704
2075
  status rows (`open → filed → fixed → consumed`) are human-appended, and the fold shows
1705
2076
  the latest per key. Pure pieces exported for custom folds: `classifyIncidents(events,
1706
- {spine?})`, `foldLedger(rows)`, `ledgerDeltas(fold, occurrences)`. CLI lands at N5; the
2077
+ {spine?})`, `foldLedger(rows)`, `ledgerDeltas(fold, occurrences)`. Riding with them,
2078
+ and deliberately NOT a ledger class: `rateProvenance(record)` → `vouched|guessed|unpriced|unknown`
2079
+ and `spendProvenance(events)` → per-provenance `{rounds, usd, unpricedRounds}` buckets answer
2080
+ "how much of this run's spend was priced by a rate nobody vouched for" (BA-21 — see *Every cost
2081
+ figure bareloop reports is an ESTIMATE* above). A bucket's `usd` sums FINITE costs only and is a
2082
+ FLOOR whenever its `unpricedRounds` is non-zero (F6 — an unknown cost is counted, never summed as
2083
+ $0). `VOUCHED_RATE_SOURCES` is the frozen allow-list they key on. **Reporting only**: no incident,
2084
+ no class, no halt. CLI lands at N5; the
1707
2085
  panel reads the same file at N6.
1708
2086
 
1709
2087
  ## Architecture
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bareloop",
3
- "version": "0.11.0",
3
+ "version": "0.13.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",