bareloop 0.38.0 → 0.40.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.
Files changed (56) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/bareloop.context.md +87 -24
  3. package/package.json +3 -3
  4. package/src/authorflow.js +335 -59
  5. package/src/authoring.js +125 -3
  6. package/src/authorjob.js +93 -32
  7. package/src/authorreadout.js +498 -3
  8. package/src/authorrun.js +26 -6
  9. package/src/authorscout.js +9 -6
  10. package/src/bridges.js +1 -1
  11. package/src/calibrate.js +85 -11
  12. package/src/cardauthor.js +8 -2
  13. package/src/declaredclose.js +12 -5
  14. package/src/draftspend.js +6 -1
  15. package/src/interviewrun.js +56 -27
  16. package/src/job.js +38 -5
  17. package/src/jobblock.js +140 -0
  18. package/src/judged.js +323 -29
  19. package/src/kinds.js +13 -1
  20. package/src/ledger.js +1 -1
  21. package/src/panel/authorroutes.js +17 -11
  22. package/src/panel/authorsession.js +202 -75
  23. package/src/panel/index.html +302 -312
  24. package/src/panel/jobplan.js +107 -0
  25. package/src/panel/server.js +67 -13
  26. package/src/planrun.js +6 -5
  27. package/src/promptregisters.js +1 -1
  28. package/src/providers.js +111 -2
  29. package/src/reuse.js +1 -1
  30. package/src/root.js +1 -1
  31. package/src/run.js +3 -3
  32. package/src/selection.js +1 -1
  33. package/src/source.js +64 -1
  34. package/src/text.js +13 -0
  35. package/src/transport.js +1 -1
  36. package/src/trend.js +1 -0
  37. package/src/userrun.js +3 -3
  38. package/types/authorflow.d.ts +258 -39
  39. package/types/authoring.d.ts +30 -1
  40. package/types/authorjob.d.ts +52 -13
  41. package/types/authorreadout.d.ts +123 -1
  42. package/types/authorscout.d.ts +5 -3
  43. package/types/calibrate.d.ts +4 -1
  44. package/types/declaredclose.d.ts +5 -1
  45. package/types/draftspend.d.ts +8 -2
  46. package/types/job.d.ts +1 -1
  47. package/types/jobblock.d.ts +102 -0
  48. package/types/judged.d.ts +21 -2
  49. package/types/panel/authorsession.d.ts +57 -7
  50. package/types/panel/jobplan.d.ts +58 -0
  51. package/types/panel/server.d.ts +20 -0
  52. package/types/planrun.d.ts +1 -1
  53. package/types/providers.d.ts +52 -1
  54. package/types/run.d.ts +1 -1
  55. package/types/source.d.ts +26 -0
  56. package/types/trend.d.ts +1 -0
package/CHANGELOG.md CHANGED
@@ -4,6 +4,65 @@ All notable changes to bareloop are documented here. Format:
4
4
  [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versioning:
5
5
  [SemVer](https://semver.org/spec/v2.0.0.html). Pre-1.0: **minor** = a ladder rung or
6
6
  feature lands, **patch** = docs, fixes, scaffolding.
7
+
8
+ ## [0.40.0] - 2026-10-11
9
+
10
+ ### Added
11
+
12
+ - **The job block (P7).** A job is now written as two numbered boxes, **The job** and **Inputs**, replacing Goal / Source / Success / Guardrails / Judge examples. A `~` line is a rule for the line above it; `~ PASS:` / `~ FAIL:` lines are a rubric's judge examples and keep their line number. Inputs are numbered `label: value`: line 1 is the repo, later lines are files inside it, proven at $0 before any session. One parser (`src/jobblock.js`) serves the panel and the CLI interview, which now asks The job and Inputs as multi-line answers.
13
+ - **Every job line is covered.** The signed spec carries `jobLines` and `inputs`. Each stage the drafter writes names the job line(s) it serves (`fromLine`, an array, so one check may serve several lines), or the line is listed in `closeDecl.refused` with a reason; an uncovered line is a $0 red fed back through the revise loop. Signed `jobLines` carry `rules[]` beside `rule`, so Reuse never cuts an example at its own `; `. Only new jobs' hashes move; Inputs is open on a Reuse.
14
+ - **Try-3 questions.** On the last drafting try, after two failed ones (validation red, seed-read failure or vacuous close, or an output cut-off), the drafter may ask up to 2 questions, each naming a job line. The panel asks each through the ordinary ask path (answer required, no Skip), adds the answer word for word as a `~` rule on that line, and drafts once more with no asking. Off unless the caller wires an asker (the CLI never does).
15
+ - **Thinking switch for drafting and the judge.** `thinkingMode` ('on' | 'off', anything else throws) is asked of bare-agent; `off` is sent only for models bare-agent's `thinkingSupport` says are switchable, and nothing is sent for a not-switchable or unverified model. The draft log records `thinking` (off / on / not-switchable / unverified) and bare-agent's `thinkingApplied` verdict per call.
16
+ - **Panel.**
17
+ - One shared plan renderer draws the chat bubble and the Job tab: numbered lines, rules, checks under the line they serve, with *Not checked* and *Always on* folds; an "AI's summary" row, and an "AI's open questions" row (each question its own item, then "Revise if you need to.") shown only when there are questions.
18
+ - The Job tab runs The job, Inputs, Destination, Tools, Cap, Signed, with the job at full height; older jobs show an older-job note and keep their goal as line 1.
19
+ - The job boxes grow to 8 lines then scroll, with the number gutter scrolling alongside; the left pane widens to 630px from 1200px wide.
20
+ - The drafting list opens with a code-owned *thinking* row; if the model reports it thought anyway after `off` was sent, it reads "thinking on (the model thought anyway)".
21
+ - Every drafting stop now says why in plain words (what happened, what it cost, what to do).
22
+
23
+ ### Changed
24
+
25
+ - **Thinking is OFF by default for drafting and the judge, for every switchable model, worker untouched.** The default was decided by a frozen, pre-registered 8-draft ON/OFF contrast measured on **deepseek-flash only** (rule said OFF; underpowered by declaration); extending it to every other switchable model is hamr's ruling of 2026-10-11, not a measurement. `on` sends nothing and is byte-identical to a call with no knob. The deepseek-flash request body is unchanged (thinking disabled, now produced by bare-agent); the per-model allow-lists in `src/providers.js` are deleted.
26
+ - A secret-shaped input file is masked, not refused (matching the worktree repo door); `.env` by name and symlinks are still refused.
27
+ - Dependencies: `bare-agent` ^0.49.0 to ^0.51.0.
28
+
29
+ ### Fixed
30
+
31
+ - **Output cut-off is a failed try, not provider-red.** A drafter answer stopped at the output cap (`truncated:max_tokens`) now goes through the same retry ladder as a malformed artifact, with the reason "the AI's answer was cut off at the output limit"; the cut-off reply is not replayed, `AUTHOR_MAX_TOKENS` is not raised, real transport failures stay provider-red, and spend stays booked. An all-cut-off try also counts for the try-3 question gate. Test-proven only, not exercised live.
32
+ - A dropped connection (ECONNRESET "socket hang up") is a call casualty like ETIMEDOUT, and the scout stop says in words that the first look did not complete (F217).
33
+ - `proveInputFiles` compares like-for-like, so a repo behind a symlinked parent proves its Inputs.
34
+
35
+ ## [0.39.0] - 2026-10-10
36
+
37
+ ### Added
38
+
39
+ - **Rubric judge rulebook (F192).** `says-what`: the doc description must say more than the function's own name; name words match by prefix (init/initialize; a shorter word of 4+ letters), and with no doc block `has-doc` alone owns the red. `params` also reds an extra `@param` that names no parameter, and a missing field of a destructured parameter once the doc names any field. `has-doc` checks the artifact itself: the judge only points at the declaration, code finds the `/**` block above it. Quote matching uses bareguard `quoteIn` with `wholeLines`, so a quote that dropped a leading `* ` still matches. The injection facts check shares `has-doc`'s acceptance, and every rule carries a plain-words `means` for the case-proposal prompt.
40
+ - **A calibration case may not expect a red the rulebook cannot raise** (a `has-doc` red on a function that has a JSDoc block); it is refused before any locate call.
41
+ - **Calibration record.** A per-case `diag` (locate facts, full reds, per-attempt cause) is recorded beside the graded rows, and each failing case gets one plain why-line. Grading, hashes and verdicts are untouched.
42
+ - **Judged write fence.** A judged stage may only judge files inside the job's write fence; otherwise the red `judged-outside-write-scope` fires at draft time, before signing, on every door (authoring revise loop, signing, runner pre-flight, hand-written or reused specs).
43
+ - **Worker warning.** When a job's close carries the js no-suppressions check, the worker is told up front that it reads comment lines too.
44
+ - **Panel.**
45
+ - A refused rubric proposal and a refused signing each say why in plain words (panel and CLI share one owner); the full reds go to `draft-log.jsonl`.
46
+ - The calibrating line shows the gate's real score (8 of 10 / 3 of 5), not a stale check mark.
47
+ - The confirm plan reads as headed sections (#PLAN / #CHECKS / #NOT CHECKED, and #QUESTIONS when raised).
48
+ - The step map is fit-to-text chips with arrows, a line-start arrow on wrapped lines, and ↻N plus a dashed box for retries.
49
+
50
+ ### Changed
51
+
52
+ - **Strike governor.** A judged stage's trend number is its distinct red count, compared to best-so-far; the gap headline reads "N red(s) across X of Y artifact(s)". Identical, flip-flopping or growing judged reds now strike (they read as not comparable before).
53
+ - **Judge thinking is off** for the rubric judge on deepseek-flash, per call and allow-listed; workers and the drafter are unchanged and `JUDGE_MAX_TOKENS` stays 4000.
54
+ - **Revise reopens the job's boxes**, filled as drafted; Start drafting then drafts fresh. The typed change request is gone: `POST /api/author/:id/revise`, `session.revise`, the "N changes left" hint and `revisesLeft` are removed. The CLI confirm turn is unchanged.
55
+ - Dependencies: `bareguard` ^0.13.0 to ^0.21.0, `bare-agent` ^0.43.0 to ^0.49.0.
56
+
57
+ ### Fixed
58
+
59
+ - **A passed calibration satisfies the seed-red gate for any close with a judged stage**, mixed or pure. The old test read a function's arity (always true), so signing refused a mixed rubric close after calibration passed 10/10 (live session smv0tasvb0u89).
60
+
61
+ ### Known limits
62
+
63
+ - The judge's false-red classes seen on live run mv1j01sl are parked (FINDINGS F192 f), not fixed.
64
+ - The step-map chips and the new panel text were not checked at phone width by a person.
65
+
7
66
  ## [0.38.0] - 2026-10-08
8
67
 
9
68
  ### Added
@@ -67,7 +67,7 @@ inherited rule carries the green that minted it and the contrast that attributed
67
67
  an unknown provider name THROWS there, it never falls back to a default. Per-model
68
68
  request-key gating lives in that table: `deepseek-flash` sets bare-agent's
69
69
  `legacyMaxTokens`, because DeepSeek silently ignores `max_completion_tokens` and an
70
- output cap that does not bind is a money hazard. `provider: 'gemini-api'` (PRD item 31.3)
70
+ output cap that does not bind is a money hazard. The judge's calls additionally carry the per-call `thinkingMode: 'off'` where bare-agent's `thinkingSupport` says the model is switchable (read through `judgeCallOptions`, applied by `defaultJudgeLoop`; F192) because deepseek-flash's default thinking ate the 4000-token locate budget; workers are untouched. `provider: 'gemini-api'` (PRD item 31.3)
71
71
  takes the same `Loop` path, reads `GEMINI_API_KEY`, and maps two real tiers
72
72
  (`sonnet` → `gemini-2.5-pro`, `haiku` → `gemini-2.5-flash`). It is
73
73
  **ADMITTED-PENDING-PROBE**: it has ZERO runs, and the probe rule is not waived for it —
@@ -179,9 +179,10 @@ the step plan at run time (gated by `validatePlan`); the human signs only:
179
179
  | field | shape | notes |
180
180
  |---|---|---|
181
181
  | `goal` | non-empty text | what the agent plans against |
182
+ | `jobLines`, `inputs` | optional (P7). `jobLines: [{ n, text, rule, rules? }]` — the person's own numbered job lines, `rule` = their `~` lines joined with `'; '` (`''` when none), `rules` = the same `~` lines as an array BESIDE `rule` (present only when the line has any; must join back to `rule` exactly; the Reuse card prefers it so an example's own `; ` is not cut; an older spec lacking it renders by splitting `rule`); `inputs: [{ n, label, value }]` — line 1 the repo (absolute path), lines 2+ tracked files inside it. Exact fields, numbered 1..n in order | what the Job tab and Reuse show back exactly as typed (the signed `goal` stays the model's confirm sentence, which the worker reads). **A spec without them keeps its `jobSpecHash`** (never rewritten); only NEW jobs carry them, so a new job's `workflowKey` differs from an old one's. `inputs` is OPEN on a reuse (`REUSE_OPEN_SPEC_FIELDS`), so `workflowKey` ignores it; `jobSpecHash` covers it. Parsed by `parseJobBlock` / `parseInputs` (`src/jobblock.js`, one owner for the panel and the CLI) |
182
183
  | `verdictType` | `green` \| `soft-green` | declared radio, never inferred (`VERDICT_TYPES`, frozen). **AUTHORING offers both (`MENU_CLASSES`).** `soft-green` has its judged floor (`judged-floor`, signed rubric card + calibration gate) and is the forward path for a job needing judgement, paired with the end-of-run review door. Every `request-red` (e.g. for a locked TOOL verb, not a verdict class) 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 |
183
184
  | `close` | **an ORDERED LIST of named stages** `[{ name, cmd, expect, judged?, gapKeep?, offer?, needs?, direction? }, ...]` (PRD v1.28), or a close object (table below) | 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; an object close names no command to run, so the plan flow refuses one at runtime as `close-unsupported`. The modern route to a judged or human-decided close is a staged `closeDecl` (`judged-floor` / `human-confirms` stage kinds), not this object form |
184
- | `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 |
185
+ | `closeDecl` | **the AUTHORED close** `{ genre: "TYPES", lang: "js"\|"python", stages: [{ name, kind, params, fromLine? }], notes?, refused? }` | 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. **Job-line coverage (P7, `checkJobLines` in `validateDeclaration`, $0, fed back through the revise loop):** with `jobLines` handed in, every stage the drafter writes carries `fromLine: [N, ...]` — a non-empty, duplicate-free ARRAY of real job lines, so one check (e.g. the one judged stage) may serve several lines, the mandatory guards carry none, and every numbered line has at least one stage OR a `{line, reason}` in `closeDecl.refused` — a note in `notes` does NOT cover a line (`job-line-uncovered`). `validateCloseDecl`/`validateJob` run it when the spec carries `jobLines`. 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 |
185
186
  | `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) |
186
187
  | `tools` | optional unique subset of `TOOL_MENU` (14 verbs, below) | the CEILING every plan step's grant must fit inside (omitting it means the full menu — and the hash is taken over that RESOLVED form, so a `TOOL_MENU` widening flips an omitted-`tools` spec's hash and forces a re-sign; see `jobSpecHash`, MED-1); `run` is `LOCKED_TOOLS` and reds `request-red` — locked-but-listed, and the red IS the admission evidence the ledger tallies (a typo stays `invalid-value`). A ceiling of write-class and store-class verbs ONLY reds `invalid-value`: the scout surveys read-only, so it would be handed an empty menu and survey blind |
187
188
 
@@ -617,7 +618,7 @@ a step, or test it without a provider.
617
618
 
618
619
  | step | call | what it is |
619
620
  |---|---|---|
620
- | interview | `runInterview({ verdictType, answers, repoPath })` → `{ ok, answers, verdictType, refusal, reds }` | PURE — no model, no repo, no clock. The interview ADMITS only what is on the menu (`MENU_CLASSES`): nothing is locked today (`LOCKED_CLASSES` is empty), and any pick off the menu returns a refusal rather than `ok:true` — ask `MENU_CLASSES` which classes can be authored, never this sentence. 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 several slots since it was frozen (D13's genre confirm; the repo question, dropped 2026-08-15 because `repoPath` is mandatory structured input; and, at PRD item 33 M3 piece 3, the old "which files change/read" question, replaced by Source and Destination — MECHANICAL fields, proven against the machine, never a numbered answer), and it renumbers CONTIGUOUSLY from 1 each time rather than leaving a gap. Today the green set is THREE questions (Goal, Success, Guardrails); the `soft-green` set is those three plus one more — Judge Examples (one example to pass, one to fail, and why) — the judged floor's one required input, compiled into the signed rubric card and the frozen calibration set. `verdictType` and `repoPath` are STRUCTURED input, never parsed out of prose. Answers are scrubbed at INGEST |
621
+ | interview | `runInterview({ verdictType, answers, repoPath })` → `{ ok, answers, verdictType, refusal, reds }` | PURE — no model, no repo, no clock. The interview ADMITS only what is on the menu (`MENU_CLASSES`): nothing is locked today (`LOCKED_CLASSES` is empty), and any pick off the menu returns a refusal rather than `ok:true` — ask `MENU_CLASSES` which classes can be authored, never this sentence. 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 several slots since it was frozen (D13's genre confirm; the repo question, dropped 2026-08-15 because `repoPath` is mandatory structured input; and, at PRD item 33 M3 piece 3, the old "which files change/read" question, replaced by Source and Destination — MECHANICAL fields, proven against the machine, never a numbered answer), and it renumbers CONTIGUOUSLY from 1 each time rather than leaving a gap. Today (P7) the green set is ONE question — "The job", the person's numbered lines with their `~` rules, rendered by `renderJobLines` (`src/jobblock.js`); the `soft-green` set is that one plus Judge Examples, DERIVED from the `~ PASS:` / `~ FAIL:` lines of the same block (`renderExamples`, each example kept with its line number) — the judged floor's one required input, compiled into the signed rubric card and the frozen calibration set. Inputs (`label: value` lines) travel beside the answers as `inputs`, and `authorCloseForJob({ jobLines, inputs })` tells the drafter "the person pointed at these inputs: N label path" and orders `fromLine` tagging. `verdictType` and `repoPath` are STRUCTURED input, never parsed out of prose. Answers are scrubbed at INGEST |
621
622
  | 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 |
622
623
  | 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 |
623
624
  | 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. **F176 (fixed):** "kept the last accepted revision" is not the same claim as "kept the best revision" — a later revise round can VALIDATE fine yet MEASURE worse (every stage instrument-stops rather than reading a real red/green verdict). `declaration`/`seedRead`/`finalFrom`/the returned `genreEnv.dropped` are therefore the NEWEST SOUND measured iteration, not simply the last one, when the last one instrument-stopped and an earlier one did not; `stop` itself never changes. The swap is never silent: the return also carries **`fellBack`** — `null` on every run where it never fired, else `{from, to, brokenStages}` naming which iteration was dropped, which now stands, and which of the dropped one's stages instrument-stopped — reported through `onPhase('author-fallback', …)` as it happens and printed at the terminal readout (`fellBackLines`, `src/authorreadout.js` — lifted out of `scripts/author-readout.mjs` by PANEL-BUILD.md P0; called from `src/authorrun.js`). **F179/F180 (history):** bare-agent 0.42.0's OpenAIProvider used to THROW mid-call on malformed tool-call JSON (a raw SyntaxError, after the billed round), crashing past this function; a 2026-09-15 stopgap caught it as a `provider-red`/`ask.providerError` casualty (booked `costUsd: null`) so a sound prior declaration was kept via the fallback above rather than discarded. **F179 (bare-agent 0.43.0, BA-27 — the real mechanism):** the throw is fixed upstream. `OpenAIProvider`/`OllamaProvider.generate` now return a malformed round PRICED (real `usage`, never `costUsd: null`) with `toolCalls: []` plus their own `malformedToolCall: {name, error}` marker, which `Loop.run` surfaces unchanged on its return. For the authoring declaration and confirm-turn calls, `askStructured` reads that marker and RETRIES through the existing malformed-emission retry ladder (`MAX_STRUCTURE_RETRIES`) instead of stopping the run — nothing is ever repaired, and a malformed FIRST call no longer needs an earlier sound declaration to fall back to. The generic provider-casualty fallback above (idle-timeout, etc.) still applies for every other admitted class. **F184 (fixed, worker path):** the scout and the worker path never joined that retry ladder (still true — retrying is arbiter-adjacent, out of scope), but the worker path's `ask()` (`src/planrun.js`) now at least READS the marker where it already sees it and emits a distinct spine record, `worker-malformed-tool-call` (`{phase, iteration, name, error}`), before returning `r` unexamined exactly as before — no retry, no change to strikes/ladder/attempt counting or verdict routing, purely the same visibility bar this section's authoring-path fix already set |
@@ -625,6 +626,49 @@ a step, or test it without a provider.
625
626
  | 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). `src/authorrun.js` (lifted out of `scripts/run-author.mjs` by PANEL-BUILD.md P0) asks the same question at **$0 BEFORE the scout**, so the answer never arrives after the model has been paid |
626
627
  | the three gates | `prepareSigning({ spec, workdir, seedRef? })` → `{ ok, specHash, gates, work, guards, refusal }` | D9, and it NEVER signs |
627
628
 
629
+ **Drafting stops and try-3 questions (P7, F215).** (1) A drafter round cut off at the output cap (bare-agent tags it
630
+ `error: 'truncated:max_tokens'`, neutral `stopReason: 'max_tokens'`) is a FAILED TRY on `askStructured`'s retry ladder with the
631
+ reason "the AI's answer was cut off at the output limit" (`axis: 'output-truncated'`) — NOT `provider-red`; all attempts stay
632
+ booked, `AUTHOR_MAX_TOKENS` is never raised, and only a real transport failure stays `provider-red`. `draftStopText`
633
+ (`src/authorreadout.js`) words any drafting stop that has no wording of its own; the panel puts it on the failed step. (2)
634
+ `authorClose({ offerQuestions })` (default `false`; the CLI never sets it) offers the optional `questions` property on the LAST
635
+ try of the full ladder only, and only when the two tries before it both failed — validation OR the seed read (hamr ruled 2026-10-10; this DIFFERS
636
+ from fwdloop's validation-only rule): a seed-read failure is a work stage that instrument-stopped or a close with no work stage
637
+ red at the seed (vacuous), and its lines are the `fromLine`s of the stages named, else every job line (`seedReadFailure`); a try
638
+ whose EVERY structure attempt was cut off at the output cap also counts as failed (hamr ruled 2026-10-10; a cut-off names no line, so
639
+ every job line counts), while a try cut off once but finished by a later attempt is judged by that finished attempt (the ladder carries
640
+ on to try 3 after an all-cut-off try 2 only when that gate can open, i.e. try 1 failed too);
641
+ the machine keeps at most
642
+ `MAX_QUESTIONS` (2) — each on a job line the previous try's reds named — and logs the rest in `iterations[].droppedQuestions`;
643
+ the drafter is never told a number. It then stops `questions-open` (`questions: [{line, question}]`, nothing validated).
644
+ `authorCloseForJob({ answerQuestions })` is the seam that asks the person (required answer, no skip), takes back the job lines
645
+ with each answer added word for word as a `~` rule, and runs the draft once more over the same survey, listing and confirmed
646
+ plan with the first leg's spend carried as prior spend and `offerQuestions: false`. The confirm turn's own `questions` (the
647
+ plan's open questions) are a separate, unchanged mechanism: they are asked before drafting, about the plan; try-3 questions
648
+ are asked after two failed drafts, about a job line.
649
+
650
+ **Drafter thinking knob (hamr 2026-10-10; DEFAULT OFF, hamr 2026-10-11; via bare-agent's switch since v0.51).** `draftThinking: 'on' | 'off'` (any other
651
+ value throws, an operator param, never read from env inside the library, not in the panel UI) is owned by `draftCallOptions(provider, mode)`
652
+ (`src/providers.js`). bareloop keeps NO per-model thinking list: bare-agent >= 0.51 owns the request shapes (`thinkingMode: 'on' | 'off'`, per provider
653
+ and model) and `thinkingSupport(kind, model)` (pure lookup, no network) says whether a model can be switched. The default is `'off'`. The frozen ON/OFF contrast
654
+ (`docs/logs/2026-10-10-drafter-thinking-contrast-prereg.md`) measured deepseek-flash only; the default applies to every switchable model, drafting and
655
+ judge alike, by hamr's 2026-10-11 ruling (no per-model exceptions), and is not measured live on other providers (Anthropic, Gemini, OpenAI). With mode `'off'`, a call carries the per-CALL option
656
+ `thinkingMode: 'off'` only when `thinkingSupport` answers `switchable: true` (e.g. deepseek-flash, claude-sonnet-5, gpt-5); a model measured NOT switchable
657
+ (claude-opus-5-5) gets nothing (asking would throw before send), and an `'unverified'` model (not measured by bare-agent) also gets nothing on this
658
+ default path, so an unknown or local model's drafting never breaks by default. `'on'` sends nothing (the model's own thinking, byte-identical to a call with
659
+ no knob). The raw `thinking` option is never sent. It is threaded explicitly: `makeLoopGenerate(provider, { draftThinking })` for the author, revise,
660
+ structure-retry and confirm calls; `authorCloseForJob({ draftThinking })` for the scout (it hands `runAuthorScout` a `callOptions`);
661
+ `createSession(card, { draftThinking })` in the panel does both. The CLI drafter (`src/authorrun.js`) passes no value, so it gets the same default
662
+ through the same two seams. The rubric judge uses the same rule (`judgeCallOptions`: `thinkingMode: 'off'` only where switchable; a judge on
663
+ claude-opus-5-5 does not throw). Worker calls are unchanged. Each panel `draft-log.jsonl` `call` entry records `thinking` = what was asked and sent
664
+ (`draftThinkingSent`): `'off'` (the switch was sent), `'on'` (mode on, nothing sent), `'not-switchable'` (measured not switchable, nothing sent) or
665
+ `'unverified'` (not measured, nothing sent); plus `thinkingApplied` (bare-agent's own verdict for that call's run: `true` / `false` = reasoning seen
666
+ although `'off'` was sent / `null` = unreadable) when thinkingMode was sent, absent otherwise. The panel's drafting list opens with a code-owned
667
+ `thinking` row (`thinkingRowText`, shown with a check, before "checking setup"): `thinking off`, `thinking on`, `thinking on (this model can't turn it off)`,
668
+ `thinking on (not verified for this model)`, or, once any call reports `thinkingApplied: false` after `'off'` was sent, `thinking on (the model thought
669
+ anyway)` (a `step-label` record in the draft log carries the change). A Reuse workflow session drafts nothing and has no row.
670
+
671
+
628
672
  **D5 — the mandatory guards are SHOWN and FIXED, and the battery keys off the VERDICT
629
673
  CLASS** (PRD v1.57 §2 — what a battery is FOR is the class of dishonesty that class of
630
674
  verdict admits). `CLASS_BATTERIES` is the attachment point (structure, un-removability,
@@ -677,6 +721,16 @@ attempt PLUS its close. Unpriced is never free: a null cost is a `pricing-red` s
677
721
  never retried, and the ladder is otherwise ONE retry (`JUDGE_ATTEMPTS`) — after that a broken
678
722
  judge is an instrument stop, never a red about the tree.
679
723
 
724
+ **The rulebook, and what each rule can raise** (`JUDGE_RULES` in `src/judged.js`; every rule carries a plain-words `means` the case-proposal prompt renders, so a proposed practice case never expects a red the rule cannot raise). The judge only POINTS (quotes lines verbatim); code does every check; unsure is red; the judge never says pass/fail. Quotes are matched by bareguard's `quoteIn(..., {wholeLines:true})` (bareguard `^0.21.0`): a contiguous run of whole artifact lines, so a bare fragment cannot pass by hiding in a longer line, while a dropped leading `* ` of a JSDoc tag line still matches. `has-doc` checks only that a JSDoc block sits directly above the function (read off the artifact by `docBlockAbove`; the judge's quote is a pointer that must lie inside that block), so a documented function never breaks it. **`says-what`** (the doc's description must say more than the function's own name) reds when the described prose, minus a fixed list of function words and the name's own words (camel/snake/kebab, light inflections, and a prefix echo such as `init`/`initialize` when the shorter word is at least 4 letters), leaves nothing; no description, or an unlocatable quote, reds. It catches a pure name echo only ("Returns the total." for `getTotal` passes), and with NO doc block at all it stays silent and leaves the red to `has-doc` (the card is not required to carry both). `params` reds a declared parameter with no tag, an `@param` naming no parameter (each destructured slot absorbs one unmatched root tag), and, when the doc names any field of a simple destructured parameter, a missing field; `@param options` alone for `{ x, y }` passes. `returns` reds a returning function with no `@returns`. A practice case that expects a `has-doc` red on a function that has a block is refused as a legality red before any call (`validateCalibrationSet`).
725
+
726
+ **A judged stage may only judge files inside the job's write fence** (`judgedOutsideFence`, `src/authoring.js`, called from `validateDeclaration`, which every door reaches: the authoring revise loop, `validateCloseDecl` at signing and runner pre-flight, and `validateJob` for hand-written or reused specs). A path outside `writeScope` reds `judged-outside-write-scope` at draft time, because a finding on a file the run is fenced out of can never be fixed. With no fence to compare against (the plain-folder deferral) the rule is not run.
727
+
728
+ **One red count.** A judged stage's red COUNT is the number of distinct failing functions it itemizes (`redCount`/`redSet` on the stage detail, `src/kinds.js`; an unsure entry counts as one). The same number is the gap headline ("N red(s) across M of K artifact(s)") and the trend value the close-trend governor compares to best-so-far, direction always down (`trendValueOf`, `src/declaredclose.js`), so a repeated or growing judged failure strikes like any numeric stage instead of running to the wall. Spines written before this change carry the old headline (the card-item count).
729
+
730
+ **A refused draft or signing says WHY in plain words.** `proposalStopText` (a refused rubric proposal: `proposal-invalid` / `rubric-invalid`) and `signingStopText` (refused signing gates) in `src/authorreadout.js` are the one owner of the wording, printed by the panel's failed step and the CLI alike: code-written sentences per red kind naming the case and function, the real spend so far, and what to do; a practice-case/check mismatch is not claimed to be anyone's fault; an unknown red code is quoted, never dropped. The full scrubbed reds go into the session's `draft-log.jsonl` as a `step-reds` record, and the calibrating line shows the gate's real result (`calibrationSummary`: "N of 10 practice cases graded right, M of 5 attack tests resisted").
731
+
732
+ **The worker is warned about the `any` suppression guard.** A job whose actual close stages carry the js `any` suppression pattern gets one extra genre-owned line in the worker system prompt (`ANY_SUPPRESSION_WARNING`, `anySuppressionWarning`, `src/authoring.js`): the guard scans added comment lines too, so prose like `, any` trips it. The guard regex itself is unchanged.
733
+
680
734
  **The two SIGNED artifacts of a judged close** (softgreen module 4, design §4.3/§4.4). The
681
735
  softgreen interview's Judge Examples answer (PRD item 33 M3 piece 3 — one example to pass, one
682
736
  to fail, and why; formerly two questions, Q6+Q7) compiles into the RUBRIC CARD and the FROZEN
@@ -745,12 +799,13 @@ signing record keeps WHAT was certified — `cardHash`, `casesHash`, `setHash` (
745
799
  resolved judge identity), the graded rows and the battery — beside `judgeModel` itself.
746
800
  `runCalibration` takes `judgeModel` as a REQUIRED argument and throws without one.
747
801
 
748
- **D9.3 for a judged-ONLY close** (ruling 3, hamr: *"fix it now, we are delivering softgreen"*).
749
- A judged stage skips the seed read (ruling 8), so a close whose only work stage is judged has no
750
- seed red and used to be unsignable — the one job shape softgreen exists for. For that shape only,
751
- a PASSED calibration gate plays the seed-red role (the polarity law makes *this close can fail* a
752
- demonstrated fact), and the record says so: `gates.seedVerdict.satisfiedBy === 'calibration'`. A
753
- close with any mechanical work stage is UNCHANGED and still needs its seed red.
802
+ **D9.3 for a close with a judged stage** (ruling 3, hamr: *"fix it now, we are delivering softgreen"*;
803
+ widened 2026-10-09: *"a rubric job should check if judge can judge (calibration) ... we can't close
804
+ everything upfront"*). A judged stage skips the seed read (ruling 8), so it can never be red at seed. For
805
+ ANY close carrying a judged stage, pure or mixed with mechanical stages, a PASSED calibration gate plays the
806
+ seed-red role (`seedProofIsCalibration`), and the record says so when no mechanical stage is red itself:
807
+ `gates.seedVerdict.satisfiedBy === 'calibration'`. A failed or missing calibration never satisfies it. A close
808
+ with NO judged stage is UNCHANGED and still needs a work stage red at the seed.
754
809
 
755
810
  **THE JUDGE TIER IS INSIDE THE SIGNATURE, and the stage refuses a mismatch.** *"A judge-model
756
811
  bump forces a full recalibration"* used to be an operator-side rule with nothing to fire on,
@@ -1296,7 +1351,7 @@ arm replaces the CLI-display cap rather than stacking on it; a non-capping arm l
1296
1351
  installed *inside* the shim, so native never loses its bound. Default-flip pending a paid contrast;
1297
1352
  see CHANGELOG for the replay numbers behind it.
1298
1353
 
1299
- **`scout` (default `true`) — the operator-only off switch** (`docs/product/SCOUT-CONTRAST.md`),
1354
+ **`scout` (default `true`) — the operator-only off switch** (`docs/logs/SCOUT-CONTRAST.md`),
1300
1355
  threaded verbatim from `runJob` to `runPlan`. `false` on a FRESH run (no `resumeSeed`) skips the
1301
1356
  read-only survey entirely — `scout-start`/`scout-result`/`scout-empty` never fire, and a single
1302
1357
  `scout-skipped { reason: 'operator-off', meaning }` records it (a RECORD, never silence — the
@@ -1863,7 +1918,7 @@ hand them to the one `Loop` that drives worker, scout and planner rounds; the ru
1863
1918
  them once. No price on the row = no `rates` key anywhere = the guesstimate above, byte-identical to
1864
1919
  before. bareloop keeps no price list of its own, ever.
1865
1920
 
1866
- **The provenance is on the record, per round (`rateSource`).** The pinned bare-agent (`^0.43.0`)
1921
+ **The provenance is on the record, per round (`rateSource`).** The pinned bare-agent (`^0.51.0`)
1867
1922
  carries it on every metering payload, and bareloop forwards it VERBATIM (`rateSourceFields`,
1868
1923
  `src/planrun.js`): every API `worker-round` (worker, scout, planner, fix loop) carries it beside
1869
1924
  `pricing` today — `'caller'` when a customer price is on the key's row, else `'tier'`/`'default'`
@@ -3291,7 +3346,7 @@ key you want first (the panel picks the row by the Model menu).
3291
3346
  close scripts need at run time. The correct flow is **`cd <bundle> && npm install`** (reads
3292
3347
  the bundle's own `package.json`), then **`./node_modules/.bin/bareloop run . --repo <path>
3293
3348
  …`**. `checkBundleDeps` (above) is the fail-safe that catches the wrong-shape install
3294
- before any close ever runs; see `docs/logs/FINDINGS.md` F128 for the full account
3349
+ before any close ever runs; see `docs/wiki/FINDINGS.md` F128 for the full account
3295
3350
  (grounded in the real crashed spine) and `docs/product/EXPORT-BUILD.md`'s dated addendum.
3296
3351
 
3297
3352
  **Minting-run line, corrected (F128).** The first-run notice used to print the shipped
@@ -3453,13 +3508,13 @@ key you want first (the panel picks the row by the Model menu).
3453
3508
  re-attaches to it from the server's session map (nothing is stored page-side). A panel restart mints a new token and
3454
3509
  loses the in-memory sessions: the page then shows one "reload this page" line and stops the Chat poll (no auto-reload;
3455
3510
  drafting money already spent stays booked via `draft-spend.json`, below). `GET /api/author/:id` polls the session's state
3456
- (phase, chat messages, cost, `revisesLeft`, `specHash` once prepared). `POST /api/author/
3511
+ (phase, chat messages, cost, `specHash` once prepared). `POST /api/author/
3457
3512
  :id/send {text}` answers whatever the confirm turn is currently asking (the `language` pick or a plan's
3458
3513
  own follow-up question — the old `worseThanBefore` ask is retired, 2026-09-28) — refused outright when the pending ask is the
3459
- plan MENU itself (`{ok:false}`, no route from chat text to a plan decision, ever). `POST
3460
- /api/author/:id/revise {text}` is the menu's own `fix` pick, with the chat text as the
3461
- correction (D3: max 2 rounds, `revisesLeft` derived from the confirm turn's own round
3462
- number, never a second hardcoded cap). `POST /api/author/:id/sign-prepare` is the menu's
3514
+ plan MENU itself (`{ok:false}`, no route from chat text to a plan decision, ever). There is no
3515
+ typed-revise route (2026-10-09): the panel's **Revise** button, shown only while a plan waits for the OK,
3516
+ abandons the draft (`/abandon`; spend already booked stays booked) and reopens the New job card with the same
3517
+ values in every box, editable, for a fresh Start drafting; no change request is ever sent to the model. `POST /api/author/:id/sign-prepare` is the menu's
3463
3518
  own `confirm` pick — it runs gates 1–4 and reaches `phase:'prepared'` with a `specHash`; it
3464
3519
  NEVER signs. `POST /api/author/:id/sign {specHash}` is the ONLY route that spawns a run —
3465
3520
  it refuses unless `phase==='prepared'` and the posted hash matches the session's own
@@ -3471,7 +3526,7 @@ key you want first (the panel picks the row by the Model menu).
3471
3526
  server's environment with `~/.config/bareloop/.env` merged in per request (a key is never read
3472
3527
  into or sent to the page — a missing one refuses the session at $0, naming only the env var). `src/panel/authorsession.js` takes
3473
3528
  test-only DI seams (`scout`/`generate`/`confirmGenerate`/`authorFn`/`prepareSigningFn`) so
3474
- a test can drive the real ask()-channel/revise/hash wiring without a live provider call;
3529
+ a test can drive the real ask()-channel/hash wiring without a live provider call;
3475
3530
  none of them are reachable from `authorroutes.js`'s real construction path. Sessions live
3476
3531
  under `~/.config/bareloop/panel-sessions/<id>/` (each one's own `resolved-spec.json` and
3477
3532
  `signing.json`, the two files `bareloop author` itself already writes). Edit/re-sign, the
@@ -3516,7 +3571,7 @@ key you want first (the panel picks the row by the Model menu).
3516
3571
  died, else failed. A run that DIED (no `job-end`, runner gone) leaves its last part in flight: when that part has no outcome
3517
3572
  and no cut-off, the server marks it `died` (`[?] died`), never the "happened, so passed" default — replay alone cannot tell
3518
3573
  died from still running, so `getRunDetail` decides it from the same `death` verdict as the run's status. A live run's open
3519
- part stays running; a part that finished before the death keeps its own status. The page's map box reads `[sign] word` (the live part reads `[▶] running`); the step cards and Audit rows
3574
+ part stays running; a part that finished before the death keeps its own status. The page's step map is a row of fit-to-text chips that flow and wrap (no truncation, no horizontal scroll), `[sign title]` each, in plan order, with an arrow between neighbours and a leading arrow on a wrapped line; the status word (from the same table) rides in the chip's tooltip, a retried step has a dashed border and a `↻N` tries mark, a leg boundary a dotted right edge, and a click jumps to that part's Audit row (the live part reads `[▶]`); the step cards and Audit rows
3520
3575
  show the word only; no surface says "done".
3521
3576
  **Resume (item 2)** → `POST /api/runs/:runid/resume` (`src/panel/runroutes.js`), behind
3522
3577
  `checkHumanGuard` (token + own address, like `/api/author/*`); body `{budgetUsd?, maxWallMin?}`
@@ -3574,8 +3629,7 @@ key you want first (the panel picks the row by the Model menu).
3574
3629
  fills the card through this same path (a pick, then `change` returns to the empty search; Deterministic or Rubric leaves reuse).
3575
3630
  It opens the Chat tab's New job card
3576
3631
  filled from the SIGNED job: a NEW run (new runid), never a rerun, nothing drafted ($0). **Only five boxes are
3577
- open — Source, Destination, Model, $ cap, Time cap**; every other box (check type, model, job name, goal, success,
3578
- guardrails, judge examples) is the signed workflow, greyed, and the SERVER refuses a start that changed one
3632
+ open — Inputs, Destination, Model, $ cap, Time cap**; every other box (check type, job name, The job) is the signed workflow, greyed, and the SERVER refuses a start that changed one
3579
3633
  (`400 "<Field> is locked on a reused workflow — Clear the card to change it"`; the page is never trusted).
3580
3634
  Changing a locked box is Clear (the Chat card's top-right button), which drafts. Routes, behind `checkHumanGuard`:
3581
3635
  `GET /api/author/start-from?runid=<id>` or `?import=<id>` → `{ok, origin, card, from:'signed job'|'imported job',
@@ -3666,7 +3720,7 @@ key you want first (the panel picks the row by the Model menu).
3666
3720
  live card reads "$X so far · running Ym" exactly like the right pane.
3667
3721
 
3668
3722
  - **`bareloop run-u <flags…>`** (PANEL-BUILD.md P0 task 2/4) → the person-path run flow
3669
- (the JOBS-table/`--spec` runner, resume, the review door — `docs/logs/FINDINGS.md`'s
3723
+ (the JOBS-table/`--spec` runner, resume, the review door — `docs/wiki/FINDINGS.md`'s
3670
3724
  U-mode). `src/cli.js`'s `run-u` dispatch hands `rest` straight to
3671
3725
  **`src/userrun.js`'s own `main(argv, deps)`**, unparsed — that function is the flag
3672
3726
  grammar's ONE owner, and `scripts/run-u.mjs` calls the exact same function directly, so
@@ -3688,7 +3742,7 @@ key you want first (the panel picks the row by the Model menu).
3688
3742
  fed by each flow's `deps.invokedAs`.
3689
3743
 
3690
3744
  - **`bareloop interview <flags…>`** (PANEL-BUILD.md P0 task 3/4) → the close-authoring
3691
- interview, at the terminal, one question at a time (D10; `docs/logs/FINDINGS.md`'s
3745
+ interview, at the terminal, one question at a time (D10; `docs/wiki/FINDINGS.md`'s
3692
3746
  interview surface). `src/cli.js`'s `interview` dispatch hands `rest` straight to
3693
3747
  **`src/interviewrun.js`'s own `main(argv, deps)`**, unparsed — the same one-owner shape
3694
3748
  `run-u` uses, and `scripts/run-interview.mjs` calls the exact same function directly.
@@ -4277,7 +4331,7 @@ blast radius.
4277
4331
 
4278
4332
  ## Gotchas
4279
4333
 
4280
- *Recorded here as they're found (repo-side friction goes to `docs/logs/FINDINGS.md`).*
4334
+ *Recorded here as they're found (repo-side friction goes to `docs/wiki/FINDINGS.md`).*
4281
4335
 
4282
4336
  - **`non-green-verdict` is a misleading refusal verb, and it is kept on purpose.** When
4283
4337
  `runInterview` refuses a job for having no git repository (`repoPath` absent), the refusal
@@ -4298,3 +4352,12 @@ blast radius.
4298
4352
  an append-only record that captures a key captures it forever.
4299
4353
  - **The spine is append-only** and the single source of truth; every UI (panel included)
4300
4354
  is a pure observer of it.
4355
+
4356
+ ## The job block (P7) — "The job" + "Inputs"
4357
+
4358
+ Goal, Success, Guardrails and Judge examples are one box, **The job**, and Source is **Inputs** (CLI interview and panel card alike).
4359
+
4360
+ - **The job:** each plain line is numbered 1, 2, 3 (the number sits in a gutter outside the box). A `~` line has no number and belongs to the numbered line above; consecutive `~` lines join into ONE rule with `'; '`. A `~` line starting `PASS:` / `FAIL:` is a judge example (a rubric check needs at least one of each; a deterministic check refuses them); any other `~` line is a guardrail. A `~` before line 1 is a $0 red. Empty lines are ignored. `parseJobBlock(text)` → `{ ok, lines: [{ n, text, rule, examples }] }` or `{ ok:false, error }`; `jobBlockFitsCheck(lines, rubric)` is the Check-type cross-check.
4361
+ - **Inputs:** one `label: value` per line, numbered. Line 1 = the repo (absolute path). Lines 2+ = files or folders inside that repo, repo-relative; `proveInputFiles(inputs)` (`src/source.js`) proves them at $0 before any token — exists, inside the repo, tracked, and (files) not an environment file by name, not a symlink, text, under the size ceiling, a known secret shape in a file's content is MASKED wherever bareloop records it, never refused (the P6 worktree door ruling; `.env` by name and symlinks stay refused). A miss is `Inputs, line N: ...`. Search lines and files outside the repo are `only files inside the repo for now`. The panel proves them in `POST /api/author/start` (400 naming the line); the CLI re-asks.
4362
+ - **Drafter:** every stage carries `fromLine`, or the line is listed in `closeDecl.refused` with a reason (see `closeDecl` above); the confirm turn's checks are `{ text, fromLine: [N, ...] }`; the plan draws a multi-line check under EACH line it serves.
4363
+ - **Panel:** the card is `{ jobName, checkType, model, jobText, inputs, destination, capUsd, maxWallMs }`. The confirm plan is a structured chat message `{ role: 'bot', kind: 'plan', plan }` and the Job tab reads the SIGNED spec through the same model (`src/panel/jobplan.js`: `planFromConfirm`, `planFromSpec`); the page draws both with one `renderJobPlan()`. `GET /api/runs/:id/job` returns `plan`, `jobLines`, `inputs`, `specHash`, `signedAt` (the run's FIRST `job-start`) and `signed` (`YYYY-MM-DD HH:MM · <first 6 of specHash>`, local time). An older spec (no `jobLines`) renders the signed goal as line 1 with its stage names as checks and the note `older job — numbered lines were not saved`; Reuse / Edit / Resume prefill the same fallback.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bareloop",
3
- "version": "0.38.0",
3
+ "version": "0.40.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",
@@ -27,8 +27,8 @@
27
27
  "check:lockfile": "node scripts/check-lockfile.mjs"
28
28
  },
29
29
  "dependencies": {
30
- "bare-agent": "^0.43.0",
31
- "bareguard": "^0.13.0",
30
+ "bare-agent": "^0.51.0",
31
+ "bareguard": "^0.21.0",
32
32
  "litectx": "^0.32.0"
33
33
  },
34
34
  "devDependencies": {