> For the complete documentation index, see [llms.txt](https://docs.thecolliery.org/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.thecolliery.org/tools/coalboard/changelog.md).

# Changelog

All notable changes to CoalBoard are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versions follow SemVer (the canonical version lives in `.claude-plugin/plugin.json`).

## \[2.6.0] - 2026-10-01

Unknown consent values are clamped and never echoed; unreadable configs are reported.

### Security

* **An unrecognised project `updateMode` or `coalboardMode` value bypassed the config-cascade clamp, and v2.5.1 also echoed it into the session (CB-R1, severity HIGH: the echo (B) is HIGH, and the clamp bypass (A) alone is MEDIUM).** Found by reading the v2.5.1 diff, not by the SkillSpector scanner. Two defects, one root: a value outside the known list was neither clamped nor validated. **(A) The clamp failed open** (since v1.9.0, when the clamp was introduced, `3f68bb5`): the safer-value-wins merge skipped any pair with an unknown value, so a cloned repository's `.coalboard.json` carrying an unrecognised `updateMode` overrode the user's global `off` or `remind`, and the SessionStart conductor emitted the update web-check directive anyway; `coalboardMode` shared the same path. **(B) The raw string was echoed** (new in v2.5.1, when the `remind` fix put the mode in the directive header): attacker-chosen text, newlines included, reached the line the agent reads as context. The conductor now reads an unknown value, or a non-string, as absent (a project's falls back to the global value, a global's to the schema default) and prints only the canonical literal. A known louder project value under a global `off` stays clamped, as before. **Two halves, two strengths:** the conductor's own read is code-enforced and tested; the agent's own read of the merged config (the `SKILL.md` Step 0 instruction that decides ask-versus-auto) is prose, and now carries the same rule — a value that is not one of the key's listed values counts as absent, never as a synonym.
* **A home directory spelled as its Windows 8.3 short name let the project-config walk escape above home (CWK-125).** The stop-at-home compare resolved paths with plain `realpathSync`, which does not expand an 8.3 alias, so a working directory and a `HOME` that spell one directory two ways never compared equal and the walk read a foreign `.coalboard.json` above home. In the conductor that was a read; in `scripts/configure.mjs` (same compare, same resolver) it could write through that foreign config. Both sides now resolve through `realpathSync.native`.

### Added

* **A config file that exists but cannot be read is now reported instead of silently skipped (UMB-174 (b)).** The SessionStart line carries `UNREADABLE: <path> exists but is not a readable config (<reason>); it was skipped — canonical = <canonical>`, with four reasons: `malformed JSON`, `a directory`, `unreadable` (a permission denial, `EACCES` or `EPERM`) and `not a JSON object`. The file is still skipped, as before; only the silence is gone. A failed legacy-path file is reported as unreadable only, never also as a legacy read. The global tier names the global file's own path as its canonical (CWK-135 (a)); the project tier keeps `.claude/coal/coalboard.json`.
* **A config file with a leading UTF-8 byte-order mark is now read.** Windows PowerShell 5.1 writes one whenever asked for UTF-8, and a valid config carrying it used to read as malformed JSON.

### Changed

* **The auto-trigger cue's arbitration sentence now names CoalTipple only conditionally (CWK-135 (b)).** It read as if CoalTipple were always present ("CoalTipple = tier-lever"); it now says that if CoalBoard is present this session it leads, and CoalTipple, if present, is its tier-lever. An undecidable Layer-2 verdict is treated as stakes, so the board still halts and asks. The class-label sentence before it is unchanged.

### Fixed

* **`scripts/` git spawns inherited the ambient `GIT_*` environment (CWK-133).** Inside a linked worktree a git hook exports an absolute `GIT_DIR`, and a test fixture or gate that spread `process.env` into `git` could re-initialise or read the wrong repository. Every git spawn in `scripts/` now takes its environment from one `gitEnv()` helper that deletes the whole `GIT_*` family. Maintainer-side; reaches no install.
* **A new gate refuses a git spawn that does not use `gitEnv()` alone (CWK-136).** `verify.mjs` now runs a git-spawn census over `scripts/` and fails on a spawn with no `env:`, one that mentions `process.env`, or one whose env is anything but `gitEnv(...)`. Maintainer-side; reaches no install.

## \[2.5.1] - 2026-09-22

### Fixed

* **`commands/update.md`'s `git ls-remote` call had no `--refs` filter, so a peeled annotated-tag ref (`vX.Y.Z^{}`) could be read alongside the real tag (CWK-120 row 3, CodeRabbit PR 19).** Added `--refs 'v*'`, verified live against the real remote.
* **`hooks/coalboard-conductor.js`'s emitted self-update directive named no `updateMode`, so the agent following it could not tell whether the user had set `ask`/`auto`/`off` (CWK-120 row 4).** The directive now embeds `cfg.updateMode`.
* **`updateMode: remind` instructed the same web-check spend as `auto`, contradicting the config template's own documented semantics ("a free periodic reminder, you run it") — CodeRabbit's row-4 comment was right for a stronger reason than first credited (CWK-120 findings-back MEDIUM-1/MEDIUM-2).** `remind` now emits a spend-free reminder with no web-check instruction and no `/coalboard:update` offer; `auto` and `ask` are unchanged. `ask`'s "ask once then persist the answer" half is a separate, unbuilt product question (needs a config writer this hook is not, per Phoenix #10) and is not resolved by this fix.
* **`scripts/lib/link-check.mjs`'s HTML-entity decoder crashed on a numeric entity above `0x10FFFF` (`String.fromCodePoint`** **`RangeError`), so one malformed link entity could take down the whole gate (CWK-120 row 5).** Bounded with `Number.isInteger(code) && code >= 0 && code <= 0x10ffff`; RED-FIRST proven.
* **`scripts/lib/link-check.mjs`'s tracked-markdown listing inherited `GIT_DIR`/`GIT_WORK_TREE`/`GIT_INDEX_FILE` from the calling process's environment, so a `git` hook context (which sets all three) could point the listing at the wrong tree (CWK-120 row 6).** Scrubbed from the child's env before spawn; RED-FIRST proven with a real polluted-env spawn test.
* **`scripts/lib/rigor.mjs`'s `rigorPreset()` did a plain bracket lookup against the preset table, reachable via the object prototype chain (CWK-120 row 7).** Guarded with `Object.hasOwn`.
* **`scripts/lib/secrets.mjs`'s scrub regex (`[^\n"']+`) truncated redaction at an embedded quote inside a secret value (CWK-120 row 8).** Widened to `[^\n]+`.
* **`scripts/lib/trigger.mjs`'s `isExcluded()` only matched directory containment, so an exact excluded FILE path never matched (CWK-120 row 9).** Added an exact-file-path branch alongside the directory check.
* **`platform-configs/.coalboard.json`'s comment claimed a legacy path took priority "over global" — the legacy-path half was already moot as of `v2.5.0`'s UMB-133 fix; the "overrides global" half was never true (CWK-120 row 12).** The moot half removed; the remaining claim corrected with the safer-value-wins caveat this room's `mergeSafety` clamp actually enforces.
* **`skills/coalboard/references/audit.md`'s whole-repository report-location line sent an audit to `<repo>/reports/`, missing the `.coalboard/` namespace `SKILL.md`'s On-disk ledger requires everywhere (CWK-120 row 10, CodeRabbit PR 19).** Corrected to `<repo>/.coalboard/reports/`, matching both the single-subproject case already on that line and `MEMORY.md`'s dogfood-mirror rule.
* **`skills/coalboard/references/failure-modes.md`'s SendMessage-availability claim carried no version or date, and measurement found it stale rather than merely unscoped (CWK-120 row 11, CodeRabbit PR 19).** `subagent-safety.md` rule 4 records cross-session `SendMessage`/`ListAgents` as available since CC v2.1.236 (2026-08-19); the line now states that and flags whether it closes CB's own Agent-tool-spawned-lens resume gap as UNVERIFIED, owed to a BUILD-station re-test.
* **`scripts/verify.mjs`'s factory-config schema check parsed with no object guard, so a config body that was an array or a bare number passed as "valid" while every per-key check silently ran zero times (CWK-120 findings-back MEDIUM-3, ride-along a).** Guarded with the same object-shape check the other three `JSON.parse` sites already carry.
* **`scripts/lib/link-check.test.mjs`'s clean-fixture spawn test asserted only the exit code, so a gate rewritten as a silent no-op still passed it (CWK-120 findings-back MEDIUM-4, ride-along d).** Now also asserts the gate's printed finding count.

## \[2.5.0] - 2026-09-22

### Added

* **The repo-root project config `<project>/.coalboard.json` is now read (UMB-133).** Before this it was silently dead: the conductor's per-level walk stepped past it while nothing said so. The candidate list per level is now the canonical three (`.claude/coal/coalboard.json`, `.agents/coal/coalboard.json`, `.gemini/coal/coalboard.json`) then both legacy shapes, `.claude/.coalboard.json` first and `.coalboard.json` last; first existing file wins, nearest level wins, 40-level cap, stops at home — all unchanged, and there is still no repository-root resolve. The SessionStart line now also reports a legacy file it read (naming the canonical path to migrate to) and a near-miss config path it ignored (a fixed list of seven shapes of this skill's own config name, at the levels the walk already visits, at most three named plus a count). `scripts/configure.mjs` carries the same candidate list and now migrates the repo-root legacy file on write too.

### Deprecated

* **Both legacy project-config paths — `<project>/.claude/.coalboard.json` and `<project>/.coalboard.json` — are deprecated in favour of `.claude/coal/coalboard.json` (UMB-133).** Both are still read; nothing breaks. **Window:** deprecated in the release that carries this entry, removable no earlier than the next MAJOR release. **Owner:** this room. **Channel:** this section and the README Configure note only — not a runtime warning (Phoenix #13 bars a hook from emitting a deprecation notice); the SessionStart report of a legacy hit is a report of what was read, not the deprecation itself. To migrate, rename the file, or run `node scripts/configure.mjs` from a repo checkout with any key. The repo-root shape is deprecated in the same release that starts honouring it, so it was never a supported path before this. **Not deprecated:** the global `~/.claude/.coalboard.json`, a separate tier.

## \[2.4.2] - 2026-09-10

### Fixed

* **`references/wizard.md`'s Call-3 line claimed DISPATCH defaults were "config-overridable" — no dispatch key exists anywhere in the 31-key schema.** The false half is removed; the true half (defaults dispatch all-at-once, ask only if raised) is unchanged. This correction landed in `516c52c` (CWK-075 findings-back) as a change to the file inside the shipped `plugin/` tree, but `.claude-plugin/plugin.json`'s `version` stayed at `2.4.1` — and per UMB-049, `claude plugin update` resolves a plugin's version from that string on the default branch, keeping the cached copy when it hasn't moved. So a user already on 2.4.1 kept the false sentence while a fresh install got the corrected tree under the identical version number — one string naming two different trees, this room's own CWK-063 hazard, live. This bump is what actually delivers the correction to an existing install.

### Notes

* **Everything else since `2.4.1` is maintainer-side and reaches no install — stated so this release isn't read as bigger than it is.** The pointer/config-drift gate work this sitting (including the CWK-079 port) and the `.github/` workflow work both touch only `scripts/` and `.github/`, neither of which `build-plugin.mjs` copies into `plugin/` (`for (const d of ['skills', 'hooks', 'commands', 'agents'])`). No skill behaviour, no config key, no command, and no hook changed in this release.

## \[2.4.1] - 2026-08-31

### Changed

* **The conductor's CRITICAL-signal halt-ask now carries a one-line class reason (CWK-022, owner-signed ใบ C).** `hooks/coalboard-conductor.js`'s injected instruction previously told the agent to "HALT and ask the user (question-box) whether to convene the board" with no label naming why. Now the instruction tells the agent to OPEN that question-box ask with a one-line class label naming which of the four auto-trigger classes fired — `security/crypto` · `DB/financial migrations` · `high-precision math` · another catastrophic-on-error class (three verbatim from `SKILL.md`'s own frontmatter, the catch-all compressed for the pick-one slot) — before asking whether to convene. **Not a Phoenix #13 violation:** asking the user is already user-visible surfacing; a class label on a question already being asked is not silent-arbitration narration (#13 bans narrating the arbitration's internal reasoning, never labelling the question itself). The label names ONLY the class — the confidence/reasons/grade detail earlier in the same instruction is explicitly excluded from ever reaching the label, so no mechanism-internal detail leaks into what the user sees. No change to Layer 2's judgment instructions or the CT↔CB triage/arbitration rules — those sentences are untouched.

## \[2.4.0] - 2026-08-31

### Added

* **Per-seat custom agent defs make leaf-ness PLATFORM-ENFORCED for 3 of 5 lens seats, narrowed to Bash-only for the other 2 (CWK-040, the follow-through on CWK-035).** CWK-035 found leaf-ness CONTRACT-only for `feeling`/`adversary` because CB spawned every seat via `subagent_type: Explore`, which has no per-spawn tool-restriction parameter and passes `Bash` through untouched. Fix: five new custom agent defs, one per seat (`agents/cb-data.md`, `cb-truth.md`, `cb-feeling.md`, `cb-adversary.md`, `cb-observer.md`), each carrying a `tools:` frontmatter list that REPLACES the platform's default tool set entirely — a mechanism this room already verified once (2026-07-29 LEAST-PRIVILEGE UNIT) and re-confirms live here. `cb-data`: `Read, Grep, Glob, WebFetch, WebSearch`. `cb-truth`/`cb-observer`: `Read, Grep, Glob`. `cb-feeling`/`cb-adversary`: `Read, Grep, Glob, Bash` — Bash kept because it's load-bearing for both (running tests/proofs, executing a counterexample), everything else (`Agent`/`Task`/`Workflow`/`Write`/`Edit`) now structurally absent. Net: `data`/`truth`/`observer` go from contract-only to FULLY structural, including the run/fetch minimum (P19) that was previously unexpressible on `Explore`; `feeling`/`adversary` close every channel except `Bash`, which stays CONTRACT-governed exactly as CWK-035 described (P6's never-spawn instruction + the Backstop's self-report), unchanged by this ticket. `scripts/build-plugin.mjs` now ships `agents/` as a 4th copied directory alongside `skills/hooks/commands` — the flock-canonical shipping mechanism (matches how CoalMine ships `agents/coalmine-scanner.md`). Ship-text updated to match: `SKILL.md`'s Bounds paragraph names the five `subagent_type` values directly and states the 3-fully-structural/2-structural-except-Bash split; `lens-prompts.md`'s "Grant vs guarantee" paragraph and LEAF-rule bullet updated to the same split; `references/platform-antigravity.md`'s read-only+leaf and per-seat-run/fetch-minimum rows corrected (CC column no longer says "contract-only, Explore passes everything through" — now "expressible for 3 of 5"); `references/failure-modes.md`'s CWK-035 paragraph updated to "closed for 3, narrowed for 2," with the original gap description kept verbatim underneath for the two seats it still applies to. **Test coverage, stated honestly — 2 of 5 individually spawn-tested with real evidence, not 5:** `cb-feeling` was spawned fresh (un-resumed, temp-staged at `.claude/agents/`) and reported exactly `Read, Grep, Glob, Bash` as its full tool schema — `Agent`/`Task` were absent from the schema, not merely refused, and `Bash` executed a real command. `cb-data` was spawned the same way and reported `WebFetch`/`WebSearch` both directly callable (no ToolSearch deferral) and completed a real live fetch to `https://example.com`. `cb-adversary` was NOT individually spawn-tested but is byte-identical to the tested `cb-feeling` in its `tools:` line — same coverage by construction. `cb-truth`/`cb-observer` were NOT individually spawn-tested and are untested strict subsets of the tested lists (no line that isn't already proven absent-or-present in a tested def). No new config key, gate, or step — this is a containment-mechanism upgrade on an already-declared behavior (leaf-ness), which is why it lands `### Added`/MINOR rather than `### Fixed`/PATCH: three seats can now do something they genuinely could not before (platform-enforced containment on the run/fetch axis, not just spawn/write).

## \[2.3.2] - 2026-08-31

### Fixed

* **"Leaf-ness is ENFORCED for every seat... STRUCTURALLY" was false for the Bash channel specifically, for the two seats holding it (CWK-035, U10 CB round HIGH-1).** `SKILL.md`'s Bounds paragraph and `references/lens-prompts.md`'s "Grant vs guarantee" both claimed the `Explore` agent type structurally enforces leaf-ness across the board. True for spawn+write (`Explore` genuinely removes `Agent`/`Task`/`Write`/`Edit`/etc.) — **false for `Bash`, which `Explore` passes through untouched, and which `feeling`+`adversary` both hold for their own function** (running tests/proofs, executing a counterexample — the seats can't do their job without it). A Bash-holding seat could in principle invoke `claude -p` itself, a SECOND spawn channel Explore's tool-removal was never built to see (`subagent-safety.md` rule 5's Bash-is-a-second-spawn-channel class). Fixed: both claims corrected to say containment on that one channel is CONTRACT-only — resting on P6's never-spawn instruction (the lens prompt's own LEAF rule) and the Backstop's self-report duty, never a platform block — pointed at from `SKILL.md`, stated once in `lens-prompts.md` (§Seat permissions), with the mechanism explanation itself living in `references/failure-modes.md` under its existing coverage of the same grandchild-runaway incident (the body was already at its declared bound, so new mechanism text went to the reference per this room's regrowth-ratchet discipline; only the corrected rail-level claim stays inline). Also corrected: the Backstop was credited with "holding" leaf-ness on that channel — it can only SURFACE a self-reported spawn, never REAP a `claude -p` process main never launched, matching this file's own existing HONEST CC LIMIT idiom two sentences later. **No behavior change** — `feeling` and `adversary`'s actual tool grants are unchanged; this corrects what the ship-text claims those grants guarantee. INSPECT (opus/xhigh) caught 1 HIGH (a body-ratchet breach at 46,561 vs the declared 45,959 bound, from an earlier draft of this fix) + 2 MEDIUM (a twin overclaim left standing at `lens-prompts.md`'s LEAF-rule bullet; the Backstop overcredit) — all three closed in this diff. **Ratchet check: body re-lands at exactly 45,959 chars (body-only, frontmatter excluded, LF-normalized) — unchanged from campaign #6 unit 1's declared baseline. RATCHET HELD, not widened.**

## \[2.3.1] - 2026-08-22

### Changed

* **SKILL.md body lean pass — the flock-wide regrowth-ratchet campaign, unit 1 (campaign #6; per-candidate carve list and rail-checklist in commit `b448750`).** Measured with the platform's own instrument (`claude plugin details coalboard`, never `chars/4`, which under-counts \~1.5x): **\~19.8k tokens on-invoke against `skill-authoring.md` §3b's \~5,000-token bar — 4.0x over**, this room the worst-offending whole-product skill in the flock. Duplication removed wherever a MANDATORY-read reference already owned the content (P8 makes `references/lens-prompts.md` mandatory before any spawn): the Step-1 Tiers paragraph's full ladder table + rationale (kept in body: the resolve order, GATE 2's firing condition + fable count + all three consent options, the CoalTipple-optional branch) · the per-lens table's "its prompt, in one line" column · the Adversary paragraph (kept: the activation condition) · the spawn-failure/dead-lens block, condensed to point at F1-F4/P9-P12 · a cache-shaping parenthetical, a `docs ≠ reality` echo, and a zero-breakage elaboration duplicating Step 4 point 5. Also a rot fix: a stale example ("Fable 5's 2026-06 access-gating") that `MEMORY.md`'s own release arc records as superseded 2026-07-02. **Three files ship: `SKILL.md`, `references/lens-prompts.md`, `references/platform-cc.md`.** INSPECT (opus/xhigh) caught 1 HIGH + 2 MEDIUM before ship, all fixed in this diff: **HIGH-1** — the sub1 cell's "if sources conflict, report the conflict" existed nowhere else after the carve (grep-verified both directions); restored to `references/lens-prompts.md`'s sub1 role line, the correct single home, keeping the carve's saving. **MEDIUM-1** — GATE 1 (Step 0) must render per-seat tiers as part of its pre-consent config block, but the References ledger only made `lens-prompts.md` mandatory before a Step-1 spawn; before the carve the body carried a redundant full ladder table so this never mattered, but the carve removed that table, creating a real load-path gap. The ledger's "when" cell now reads "MANDATORY before GATE 1's config block (the per-seat tiers) AND before ANY lens spawn." **MEDIUM-2** — `references/platform-cc.md` cited a phrase ("COMPLETED ≠ ANSWERED") the carve removed from the body; re-pointed at P9 and Step 2, where that semantics actually lives now. A reviewer LOW claiming `source-grounding` had 0 hits left in `skills/` was independently re-checked and refuted — `SKILL.md`'s own capability-hack area still carries it; no fix needed, nothing shipped for it.

### Notes

* **DECLARED BOUND (skill-authoring.md §3b/§5) — the body cannot reach the \~5,000-token bar without cutting a rail, and this is the second independent carve to confirm it.** Board #6 (2026-08-16) declared the prior bound at \~45.8k chars after its own carve; this pass re-confirms the same wall one level down: body **50,252 → 45,959 (BODY-ONLY, frontmatter EXCLUDED, LF-normalized) chars, 201 → 197 lines (−4,293 chars, \~8.4%)** — `node -e "const s=require('fs').readFileSync('skills/coalboard/SKILL.md','utf8').replace(/\r\n/g,'\n');console.log(s.replace(/^---[\s\S]*?\n---\n/,'').length)"`, independently re-derived, matches 45,959 exactly. **This entry pins body-only, frontmatter-excluded, LF-normalized as the series convention going forward — two conventions (this one and a full-file count) were found side-by-side in this file's own prior Notes history, and that ambiguity is closed HERE rather than left for the next reader to re-discover; the full-file figure, for reference only, is 51,311 → 47,018, the same −4,293 delta (frontmatter is 1,059 ch and unchanged, so it cancels out of the delta but not out of the baseline — only the body-only number is comparable against `44,343` / `45,817` / `44,923` / `45,416` above).** Against the \~19.8k-token on-invoke measurement (4.0x the \~5,000 bar), the reviewer's own residue accounting found only \~400-600 chars (\~1%) of remaining explanation-class content in the whole body — the other \~99% of the gap to the bar is RAIL (consent gates, step order, safety interlocks, least-privilege/degrade branches, the 11 named never-move items both carves have separately confirmed present), not fat left uncut. **This is the new ratchet baseline: 45,959 chars (body-only) / 197 lines.** Per §3b, the bound RATCHETS — a future measurement above 45,959 is a regression requiring a re-carve, never a new normal to shrug at. **Not claimed here: an after-number in on-invoke tokens** — that requires the platform's own instrument against a synced local install, which only the department head can re-run after this ships.

## \[2.3.0] - 2026-08-22

### Added

* **Lens findings now tag (E) exploitable vs (C) conformance; a `(C)`-tagged CRITICAL/HIGH is a re-grade prompt, not an automatic downgrade (board #132).** Validated live on 2026-06-23 external-audit work and never shipped until now (`grep -rn "exploitable" skills/coalboard/` returned 0 hits before this change). Live-proven case: sub1 inflated an RFC-6455 fragmentation gap to CRITICAL because it measured spec-deviation, not attacker-gain; sub4 — whose prompt already carried the E/C split — rated the same gap LOW correctly; the judge confirmed LOW by ground-truth. `references/lens-prompts.md` gains a new `**E/C tagged:**` FIXED-rule bullet (every finding carries `(E)` — someone/something actually gains or breaks — or `(C)` — deviates from a stated standard with no demonstrated gain/break; domain-general, not security-only) plus `(E|C)` in the Output line's FINDINGS schema: `[CRITICAL|HIGH|MEDIUM|LOW] (E|C) path:line | finding | evidence | conf=NN | FALSIFIER`. `SKILL.md` Step 2 gains one new judge rail: a `(C)`-tagged CRITICAL/HIGH finding is a RE-GRADE PROMPT — the judge re-runs the same ground-truth check on it and states its reason if it re-grades, never a silent automatic downgrade. The tag does not itself set severity; it gives the judge a signal a conformance gap was never carrying before. Cross-checked for shipped drift: no other live surface (`SKILL.md`, `README.md`, `COALBOARD_BLUEPRINT.md`, `references/audit.md`, `references/wizard.md`) restates the FINDINGS schema or a lens-output field list — `lens-prompts.md` is the sole canonical home, and `CHANGELOG.md`'s own historical `[x.y.z]` entries describing the pre-tag schema are left as history, not amended. **Ruled: the opinion lane does not inherit this tag** — `references/opinion-board.md` says so explicitly, because that lane has no TARGET and emits no `path:line` FINDINGS at all (its seats return refutations and walked scenes, not severity-rated findings).

## \[2.2.1] - 2026-08-21

### Fixed

* **Two Bash/PowerShell grants missing from the CLASSIFY-BLOCK section shipped in v2.2.0 (board #141, found by CM/CL's own INSPECTs during F22).** (A) The write row's on-denial branch already named a delete sub-case (P13's `stop`-deletes-staged), but its grant column named only `Write`·`Edit` — Claude Code has no native file-delete tool, so the delete actually needs `Bash`/`PowerShell`; the grant column now names it, and the on-denial branch states the delete can be denied ALONE (Write/Edit still granted) and must be reported as denied too, never a silent stale `proposed/`. (B) Step 4.2's verify-run (compile/test/SAST/ground-truth/formal gates, run by main, never delegated to a seat) had NO covering row or branch at all — not `write` (verify writes nothing), not `spawn` (Step 4.2's own text explicitly excludes delegating verify to a worker), not F6 (F6 presupposes verify already RAN and produced a result; a Bash-denied verify never runs at all). New `execute` row, grant `Bash`/`PowerShell`, routes a denial through the EXISTING F10 mechanism ("could not verify — human decision," carried into GATE 3's digest, never a fake green) — the same reuse-an-existing-mechanism idiom every other row in this section already follows (write reuses P13, spawn reuses F7, network reuses the data seat's honesty rule). CoalWash is the flock exemplar for naming an execute-tool explicitly in a write-class row; this room's own v2.2.0 section missed it on first pass. Both findings verified independently at source (own available-tool list confirms no native delete tool exists; `SKILL.md`'s F6/F10 text read directly, not assumed) before the fix. No change to the section's promise — main still declares grants/denials for its own steps exactly as v2.2.0 shipped; one row added (4 -> 5), one corrected, both making an existing declaration complete rather than adding a new promise.

## \[2.2.0] - 2026-08-21

### Added

* **Grants & denials (CLASSIFY-BLOCK) — main's own read/write/spawn/network steps now declare what a denial looks like (gold-standard F22).** The Seat-permissions ledger only ever governed what a SEAT is granted; nothing stated what MAIN itself needs to run the numbered Steps, or what happens when one of its own grants is denied — a blocked staging write read identically to "the board found nothing to apply," and a blocked `proposed/` delete read identically to a `stop`'s own P13 disposition. New `## Grants & denials (CLASSIFY-BLOCK — declared)` section in `skills/coalboard/SKILL.md`, four rows: **read** (`Read`·`Grep`·`Glob` — a denial refuses before scanning, never proceeds on an unread target, never reports a scan that didn't happen) · **write** (`Write`·`Edit` — a denial is reported as DENIED, not absent; a blocked stage never reads as "nothing to apply," a blocked delete never reads as `stop`'s own P13 disposition — courier the intended content to the human instead) · **spawn** (`Agent`/`Task` on Claude Code, `define_subagent` on Antigravity — a denial degrades to the EXISTING F7 inline-self collapse, its trigger widened to fire on the denial itself and not only a budget/quota limit, naming the denial + which seats never ran, carried into GATE 3's digest as NOT-CHECKED) · **network** (`WebFetch`/`WebSearch`, the `data`/sub1 seat only — main itself never fetches directly; a denial reuses the seat's existing never-training-memory / NOT-CHECKED honesty rule). Retrofits `skill-authoring.md` §5b's authoring requirement onto an already-live skill (a `prefer/should` retrofit per that section's own FORCE line, not a MUST). Reuses existing mechanisms (F7, P13, the data seat's honesty rule) as the DEGRADE PATH. The write row is genuinely new — no prior `SKILL.md` text said what main does on a blocked write, so an agent following the contract now has defined behavior it previously lacked. The read row generalizes a branch F12 already carried for the unreadable-TARGET case specifically; the new row states the wider read-denial rule and names F12 as the specific route for that one case (fixed at INSPECT, per its own MEDIUM-3). Cross-checked `references/lens-prompts.md`, `references/opinion-board.md`, `references/failure-modes.md` for an existing home for this: all of their grant/denial-adjacent language is SEAT-facing (a seat's own tool rights, or a seat-level tool absence) — a different axis from main's own grant being denied — so the new section is correctly self-contained, no reference points added. Survey + full spec: `goldstd-2026-08-18/f22-classify-block-survey.md`.

## \[2.1.1] - 2026-08-16

### Fixed

* **Antigravity ship-text corrected to the platform's actual model-pick and hooks shape (board #13); seat-permission ledger and lens-prompt rail now name the MCP ceiling honestly (board #11).** `references/platform-antigravity.md`, `README.md`: AG's per-worker model-pick is not absent — it is a **define-time** field (`inherit`/`flash`/`pro`, default `inherit`) set on a subagent's own definition, not passed at `invoke_subagent` call time; CB does not set it today, so lenses still run at `inherit` (the parent model), and cross-vendor decorrelation likely still needs a human switching AG's model picker — the field's cross-vendor reach is undocumented on the current primary page, left honestly unverified rather than asserted either way. AG **does** ship hooks (a 5-event engine with a blocking contract); CoalBoard simply ships no wire into them, so the board stays manual-invoke there — the prior wording read as AG having no hooks at all. The read-only-enforcement caveat and the tool-mapping table's spawn row both gained a 2026-08-15 evidence/unconfirmed note pointing at the 2026-06-22 live validation, since the current primary docs page does not confirm either `enable_write_tools`/`enable_subagent_tools`. `skills/coalboard/SKILL.md` (Seat-permissions ledger) and `skills/coalboard/references/lens-prompts.md` (lens-prompt template) both now state that a seat's permission row governs the platform's BUILT-IN tools only — a session-attached MCP server (browser, spawn-chip, any server-provided tool) sits outside every row's reach, so on such a session a seat's real footprint can exceed its row; the guard is CONTRACT-bound (the lens must still refuse and self-report use of an ungranted MCP tool), never claimed as platform-enforced beyond what the platform itself enforces. Sources: board #13's census and board #11's finding, both closed in commit `d715b3d`. Dist rebuilt (all three touched skill files ship: `skills/coalboard/SKILL.md`, `skills/coalboard/references/platform-antigravity.md`, `skills/coalboard/references/lens-prompts.md` — `README.md` and `CHANGELOG.md` are not shipped). No version bump/tag/push — rides the next release station pass.
* **Step 0's auto-trigger enumeration walk-confirmed at 77.8% divergence, carved into a numbered ledger, re-walked to 0% (board #6 item 1).** The room's §3b variance-walk method (`skill-authoring.md`) measured `skills/coalboard/SKILL.md`'s Step-0 activation-signal rail ("what triggers the board") splitting across three non-reconciled textual homes — the frontmatter description's domain list, Step 0's own unnumbered "any one:" prose, and P4's Layer-1 signal — with only 4/18 walkers reaching the correct count of 3, identically across two independent 9-walker draws (a baseline + a null-control on the same unedited blob, run 44 — the null-control also publishes this instrument's OWN noise floor for the first time, \~0 movement between the two draws). Fixed: Step 0's activation signals restructured into an explicit "**exactly 3, any one fires Step 0**" numbered ledger matching the file's own house style for every other countable rail; manual `/coalboard` pulled out of the count entirely (it arrives via a different door and was being miscounted as a 4th item, and its own render-the-box claim corrected to respect `coalboardMode:off`, an INSPECT finding, not part of the original defect); the domain examples folded in as signal 3's content, never a parallel list. Re-walked twice: run 45 (0/9 divergence on the intermediate blob) and run 46 (0/9 divergence on the actual shipping blob, after the INSPECT findings-back below landed — the two are not the same text, see run 45's own correction note). The S1-S4 lane-selection rails stayed perfect throughout, no regression. **7 duplicate/rationale passages removed across the body, honestly split by kind (an INSPECT correction — an earlier draft of this entry undercounted them and mischaracterized 2 of the 7):** 5 are genuine duplicates of content the MANDATORY `references/lens-prompts.md` reference already owns at the exact point it is read (P8) — the seat-permissions closing clause, three tiers-paragraph parentheticals (final-arbiter robustness, the adversary's fable-eligibility rationale, the fable-safeguard re-route detail), the Step-1 per-domain known-failure checklist (now points at the `{work-type-checklist}` placeholder instead of re-deriving the taxonomy inline), and the Step-1 "grant vs guarantee" enforcement nuance (now points at `lens-prompts.md` §Seat permissions); 2 are pure rationale with no match anywhere else (two tiers-paragraph clauses — "max rigor never skimps the lenses," "the judge verifies cheaper lenses so low-rigor lenses stay safe") — legitimate cuts under §5's own test (removing them changes nothing the agent does), just not deduplications. All 11 named never-move rails (3 consent gates, P1-P19, 5 seat rows, 5 on-disk writes, F1-F12, step order, fable ladder, 2 human gates, P3, P6, collect-then-release) confirmed present and byte-identical outside the touched spots, independently re-derived by INSPECT, not only the builders' own checklists. Platform on-invoke stays \~18.7k tokens (unchanged, still \~3.7x the \~5,000-token §3b budget) — the file is genuinely rail-dense at this size, not padded (corroborated against this room's own 2026-08-03 finding at a smaller size); no further cut was forced, per skill-authoring.md §5's explicit permission not to buy determinism it doesn't need. Full evidence trail: commit `b4b67bc` (the per-candidate carve list, the walk numbers runs 44-46, and INSPECT's findings). No version bump/tag/push here either — rides the same unreleased pile as the board #13/#11 entry above.

## \[2.1.0] - 2026-08-09

### Added

* **Namespace campaign #69+#39: per-project config gains more valid homes, fully backward-compatible (`afeeca7`).** A project's `.coalboard.json` is now found via a 4-candidate read order, first found wins: your own agent's dir (`.claude/coal/coalboard.json` on Claude Code) → other known agent dirs, fixed order `.agents` → `.gemini` → the **legacy** `.claude/.coalboard.json` (CoalBoard's actual pre-migration shape — still read normally, no breakage for an existing project). Write target = wherever the config was found; if nothing exists yet, the running agent's own dir is the default. The self-update throttle stamp moved the same way: `~/.claude/.coalboard-update-check` → `~/.claude/coal/coalboard/update-check` (read-new-fallback-old, write-new-drop-old, fail-silent). `fableConsent: "always"` persistence (the board's only project-config write) now targets wherever the config lives per this same read order. `hooks/coalboard-conductor.js`, `skills/coalboard/SKILL.md`, and `references/platform-cc.md` all updated and dist rebuilt. 8 new tests (precedence · clamp-unchanged regression · move-on-write grep-proof · update-stamp read/write).
* **Two small ponytail cuts folded into the same commit** (same file, adjacent lines — splitting would have fragmented one coherent review): the conductor hook's stale wave-by-wave arbitration-cue history comment replaced with a pointer to `MEMORY.md`'s own dated entries (content preserved there, not duplicated); the dead `debateTimeoutSeconds` config key (zero consumers anywhere) removed from the schema, the factory config template, and its citers. Neither changes runtime behavior for any user.

### Fixed

* **LAYMAN wizard box's change affordance restated as an unconditional property, not an optional mention (board #57, `66813a7`).** `references/wizard.md` step 3 already specified the four-option LAYMAN box (`Go ✓ · cheaper · more thorough · cancel`) and that `cheaper`/`more thorough` map to the RIGOR and DEPTH levers — the prose was not silent — but this is a Fork-A prose-only skill with no code enforcement, and the wording described the box's change options as a fact stated ABOUT the box rather than a property the box unconditionally IS. A live manual `/coalboard` run auto-picked defaults and convened with no visible way to change them first. Reworded: "the box IS `[Go ✓ · cheaper · more thorough · cancel]` — four options, every LAYMAN render, never `[Go · cancel]` alone" — both levers (RIGOR, DEPTH) named explicitly behind the same plain-language labels the user still sees (no opaque jargon reaches the box itself), and cross-referenced against the PROGRAMMER path's own Call 3 change→recompute→re-render loop so both paths read as equally mandatory. PROGRAMMER path's mechanics were already sufficient, left untouched. Dist rebuilt (`plugin/skills/coalboard/references/wizard.md`); verify.mjs 12/12, test.mjs 64/64 at the time this commit landed (a sibling commit in the same batch, `dcc6cc4`, later added a 65th test unrelated to this fix — re-derive the live count from `node scripts/test.mjs`, per this room's own Notes convention, rather than trusting either number here).

## \[2.0.0] - 2026-08-04

**MAJOR — two things break for an existing user on update, read this before you update:**

1. **The self-fence is GONE.** A `.coalboard/` dir the board plants (staging, reports, memory) no longer gets its own `.gitignore` created for it. If you relied on that to keep board output out of your commits, it is now your own repo's `.gitignore` that has to do it — the board will not do it for you. (Full reasoning in `### Removed` below: it was your repo hygiene, not ours to enforce.)
2. **The opinion lane ("ask CB") now ACTS on its own verdict instead of re-asking you.** Previously, picking "ask CB" always came back as the same question, restated, for you to answer. Now it presents the board's disposition in chat and continues — it only comes back as a question-box if the verdict would spend real money, change what the product promises you, or the seats failed to converge / converged at low confidence. If your workflow expected a re-ask every time, it no longer happens for ordinary technical calls.

Everything else below was already unreleased and rides this same MAJOR: the SKILL.md enumerability carve (GATE 1-3, P1-P19, on-disk output, F1-F12, seat permissions), least-privilege per seat, the v1.9.0/v1.10.0 station-3 security findings-back rounds, task #34's baseline-walk fix, and the opinion-lane disposition findings-back (F1-F4 below, plus one more LOW closed in this release: the non-override sentence named a rule the reader "already holds" without saying where it lives — now reads *"...you already hold (your platform's own confirm-before-destructive rail)"*, six words, no new bullet, per RE-INSPECT's own suggested wording).

### Changed

* **SKILL.md enumerability carve (the variance-walk lab, station 2 — no version until the walk passes; the walk is this change's real gate).** Every counting-shaped rail rehoused into a numbered home so a reader TRANSCRIBES counts instead of interpreting prose — the new `## The ledgers` section: **consent gates numbered GATE 1/2/3** (GATE 1 = the convene bill in each lane's form; GATE 2 = the conditional fable money-gate; GATE 3 = the disposition; an explicit NOT-gates line for the transient-clone ask + the sub4/post-mortem escalations) · **absolute prohibitions P1–P18** (the Step-0 "Hard rules" block absorbed as P1–P5 verbatim; the scattered absolutes indexed P6–P18 with their owning step; a closing rule makes un-listed in-step "never"s sequencing, not additional absolutes) · **the on-disk output set** (6 writes; `<name>` defined, `references/audit.md`'s more-specific filename shape declared the winner) · **fail routes F1–F12** · **a references table: 4 MANDATORY-at-their-moment, 3 on-demand** (each also labeled inline at its mention).
* **Step 0 consent is decided as ONE box:** the "THEN a 2nd safety gate, a pre-flight CHECKPOINT" sentence is gone — three chat blocks, one CONFIRM/CHANGE/CANCEL box, the CHANGE loop re-fires the SAME gate on a fresh bill. The sign-off IS the CONFIRM; no rail content removed.
* **The gaps the lab measured walkers inventing answers for are now stated:** `rigor` def `standard` (the factory config's own shipped value, now visible in the body) + a preset table (relaxed/standard/high/nasa × `adversaryLens` · `contestedRound` · `tier2Verify` · `qaStrictness` · `observerOnMaxStakes` · `diversifyModels` · `applyConsent`) + inline defs for `consensusThreshold` (80) · `fuzzTimeboxSeconds` (60) · `sastCommand`/`formalCommand` (empty) · the AUTO-bar grade rubric inline (sensitive-path OUTRANKS size; a domain hit grades ≥4 regardless of size) + below-the-bar behavior (no auto-convene, NO unsolicited offer; P4 still binds a fired stakes signal) · the auto lane derives the wizard's outputs (`{target}`/`{scope}`/work-type/`{depth}` L2/`{rigor}`) instead of orphaning them · the report FILE is the user's language.
* **Least privilege per seat — the permission footprint is now a MINIMUM, not a uniform grant (USER ruling; no version until the walk passes, same gate as the carve above).** Every seat in every lane previously spawned with one identical grant, so `truth` (whose own contract forbids fetching) and `outdim` (whose value depends on touching nothing) held a shell and a fetch tool they must never use. **New 6th ledger, `Seat permissions`,** owns the per-seat minimum for the board lanes — `data` Read-class+fetch · `truth` Read-class · `feeling` Read-class+shell · `adversary` Read-class+shell · `sub4/observer` Read-class — and **`P19`** makes "grant exactly the row, never the union" an absolute (18 prohibitions, numbered P1–P19; P5 stays retired). The opinion lane's existing rights column became that lane's own ledger and now names concrete tool sets, including `outdim`'s **zero** — a minimum the platform cannot express, so it is declared contract-only rather than implied to be enforced. Each seat's row is written into its prompt via a new `{seat-rights}` placeholder, with a FIXED rule that an ungranted-but-present tool must still not be used and must be reported. **Rationale per row lives in `references/lens-prompts.md` §Seat permissions**, not in the body (§5: rails in, explanations out).
* **The enforced/contract split is stated instead of implied.** Claude Code's `Explore` type removes exactly `Agent`·`Artifact`·`ExitPlanMode`·`Edit`·`Write`·`NotebookEdit` and passes **everything else through** (verified against the shipped CLI, 2.1.220), so: leaf-ness is platform-ENFORCED for every seat · **no-write is structural ONLY where the seat also lacks a shell** — a seat holding `Bash` can reach the filesystem whatever the Edit/Write removal says, which is why denying a shell is worth more than it looks · run/fetch limits are CONTRACT everywhere on CC. `references/platform-antigravity.md`'s mapping gains a per-seat row and corrects its stale "CC is by-instruction" cell; whether AG's `define_subagent` can toggle shell/fetch is marked **UNVERIFIED** rather than assumed.

### Removed

* **The self-fence is GONE — the board no longer plants a `.gitignore` in any `.coalboard/` dir it creates (USER ruling, supersedes v1.10.0's Added entry below, which stays as history because it shipped).** Removed from every surface: `SKILL.md`'s **P5** and its three write-moment pointer rails (Step-4 stage · the report write · the memory checkpoint), the `.coalboard/.gitignore` line in the On-disk output ledger, P13's fence parenthetical, and the two "fenced" qualifiers on the `report-only` disposition (ledger + Modes/Audit) · `references/audit.md`'s report-location fence clause · `references/opinion-board.md`'s P5 classification · the `self-fence rail present` gate in `scripts/verify.mjs` · the README Permissions row · the PRIVACY.md state list.
* **Why it was wrong to ship, not merely unnecessary.** The fence was built off an incident measured on ONE machine — three public forks that had been cloned onto the maintainer's dev box carried unfenced `.coalboard/` report dirs. **That is the maintainer's own repo hygiene, and it belongs in the maintainer's own `.gitignore`.** As shipped, the rail bound *"whatever repo it lands in (the user's own, a fork, a stranger's)"* — deciding, for every user of the skill, that their board output must be machine-local and un-committable. **Whether a user commits their own board reports is the user's call, not ours**; a user who wants them in version control (a shared audit trail, a review artifact in a PR) had that silently taken away. A dev-machine workflow reaching shipped surfaces is the same class as the Rule-5 removal of 2026-07-09.
* **Ledger counts change; the numbering does NOT.** Absolute prohibitions **18 → 17**, still numbered P1–P18 — **P5 is retired and its number is never reused**, because P-numbers are stable identifiers and a renumber would invalidate every variance-walk reading on record. The ledger heading now states the count and the gap so a reader still transcribes rather than infers. On-disk output **6 writes → 5**. `references/opinion-board.md`'s per-lane classification: "the remaining ten" → **nine**. Fail routes F1–F12 unchanged. `verify.mjs` **13 checks → 12**.
* **No replacement.** No config key, no softer default, no scoped variant, no warning line — the ruling is that the fence is not ours to ship, and a lighter version is the same overreach at a smaller size.

### Fixed

* **`coalboardMode:auto` no longer skips the TARGET cheap-catch:** the Step-0 chat blocks render on EVERY lane — `auto` skips the QUESTION, never the render; a TARGET that will not resolve under `auto` falls back to `ask` for that run (fail toward the human, never spend on an unverified target).
* **A `stop` now covers `proposed/`:** staging happens before GATE 3 by pipeline necessity (verify runs on the staged copies), so `stop` DELETES anything the run staged — "leave no file behind" no longer silently excludes the staged patches. Full `proposed/` disposition stated: apply → consumed · report-only → persists beside the report · stop → deleted. Audit runs declared to stage the same way (staging = the proposal medium, never an apply intent).
* A re-routed/re-spawned lens takes the dead lens's freed slot — `maxConcurrentSubagents` counts LIVE workers, so a replacement never queues behind the wave.
* **Station-3 findings-back on the carve (same \[Unreleased], pre-wave-2):** P13 re-headed to the literal truth — **the REPORT exists only after GATE 3** (staging, `memory/` checkpoints, and GATE 2's consent persist all legitimately precede it; the old "No file before GATE 3" headline contradicted the ledger's own disposition lines) · the NOT-gates line covers the self-error-report OFFER · Layer-1 defined in-file at P4 (the conductor's deterministic static detector; Layer 2 = the semantic intent read) · CANCEL routed — declines the BOARD, not the task · GATE 1's form list restructured lane-first (`ask`/`auto` are `coalboardMode` values inside the auto lane, not lanes) · the on-disk set corrected to **6 writes** (GATE 2's `fableConsent: "always"` project-config write was missing from a list claiming completeness) · the F-ledger gains its boundary rule + **F11** (the Step-1 spawn backstop) + **F12** (unresolvable-target→`ask`; composed with headless → do NOT convene) · the three placeholder homes now point at the template's set instead of enumerating different subsets · P6 covers sub4 · P7 restores work-kind · the ledger's `stop` bullet scoped to "this run's" staged files.

### Fixed (opinion lane — the Run-3 walk seams; central ledgers untouched, byte-proven)

* **The opinion lane's cross-file seams with SKILL.md, enumerated by the Run-3 variance walk (weak tier landed in three different lanes), closed on BOTH sides.** SKILL.md (4 opinion-scoped touches only): the Entry bullet now FENCES the lane — runs from `references/opinion-board.md`, NOT from the Steps (no Step-0 bar/box/TARGET, no rigor/lens config read, no Steps 2–4, no memory net), the pick named GATE 1's form, and the verdict aligned to the shipped USER rule (DEFAULT the user overrides per item — replaces the stale "re-asks and the user decides" tail) · "renders on EVERY lane" scoped to every lane that HAS a target, with the opinion lane's no-TARGET/no-Step-0 stated · "unsolicited" defined at the below-bar site (the "ask CB" ADD rides a question already being asked — never unsolicited) · the Memory section's ARM-when-convened carries the opinion-lane exception. `references/opinion-board.md`: the header's "never a verdict" reconciled (an OPINION summary whose VERDICT is the user's DEFAULT) · the BARE FRAME declared a CONTEXT reduction, not a P7 scope split · the Step-1 inheritance list made COMPLETE (P6/F1/F3/F4/F11 + the freed-slot rule + P11 — a board that cannot fill its seats or run within budget DECLINES rather than self-voicing + P12 = re-spawn the dead seat fresh) · a "routes that CANNOT arise here" map (F2/F5/F6/F8/F9/F10/F12, each with its reason) · `consensusThreshold`/`contestedRound`/`observerOnMaxStakes` added to the not-read list · **the memory net is NOT armed in this lane** (decided — a single-turn 4-seat wave has nothing worth checkpointing; "writes NO file" is now absolute, the "if armed" hedge is gone) · the lens-session disclosure gets its home (the judge's summary ENDS with it — no report exists to carry it).

### Fixed (carve round 4 — the confirmation-wave REGRESSION, `1e66b4e`'s own 4 lines)

* **A haiku×3 confirmation wave on the convene lane (re-tested against the wave-2 baseline above) found the opinion-lane seam round broke enumerability it did not touch.** Ledgers (P1–P18, the on-disk writes, F1–F12) stayed byte-identical, but 3 of 4 rails came apart anyway (prohibitions 13/18/9, fail-routes 9/12/12, writes 3/6/6 — only consent-gates held 3/3/3) because the seam round's 4 changed lines sat in `Entry`/`Step 0`/`Memory` — sections the convene lane reads — as INLINE lane-exception parentheticals ("… except the opinion lane, which arms nothing", "… the opinion lane has no TARGET", etc.). An inline exception turns a ledger a reader TRANSCRIBES back into one they must JUDGE per item: is a P-item on a path this lane never reaches "bound" or "N/A"? Nothing answered it, and every answer was defensible. Root cause + the two data tables (Run 4) → commit `51176b7`.
* **Fixed by relocation, not reversion — the true facts survive, only the injection SITE moved.** All 4 SKILL.md hunks reverted to their pre-seam-round wording (`Entry`/`Step 0`/`Memory` now byte-differ from the last PROVEN-stable commit by only 2 lines, both pointers, neither a per-item exception). The facts those parentheticals carried moved to homes that already existed and already read stable: `references/opinion-board.md` gained an explicit **"No TARGET, no Step 0"** line and a **complete P1–P18 classification** (the 8 items the file's own Step-1 inheritance list already named, plus the 10 it had not: P1/P17/P18 bound-here-unchanged, P2/P3/P4/P5/P13/P14/P16 cannot-arise-with-a-stated-reason) — extending the SAME two-bucket shape the file's fail-route partition already used and station 3 already verified 12/12 correct. `SKILL.md`'s Entry and P-ledger header each gained ONE pointer sentence to that classification — never a restatement.
* **Remaining, NOT done this round (handed off):** scope items 3–4 from the Run-4 record — the 3 cross-file placeholder/instantiation seams (`{rigor}`/`{depth}`/etc. undefined in a lane with no rigor; P8's lens-prompts.md instantiation vs the 4 opinion role lines living elsewhere; the "COMPLETE inherited set" omitting W1/neutral-cwd) and the 5 items named by ≥2 walkers ("materially exceed" with no threshold · `coalboardMode` read in a lane declared to read no config · the `off`→"pressed anyway" branch's self-contradiction · `sonnet` default with no config key · P17's UNVERIFIED line with no home in the opinion lane's output shape).

### Fixed (task #6 + the CB half of #10 — the lane line, in BOTH texts that carry it; Run-12 + Run-13 variance walks, both FAIL)

* **SKILL.md lane selection (Run 12, headline 67%).** (1) The honest-frame thrash sentence — *"a solo agent THRASHES a hard bug … CONVERGES"* — argued FOR a lane its own triggers exclude; 6/6 upper-tier walkers named it unprompted as their near-miss, and it is the measured mechanism of main's live mis-route. **Deleted from the body** (an explanation read as a rail, skill-authoring §5); the why moved to `references/failure-modes.md` with its own boundary — an economics note about a board already convened, NEVER a trigger; the body keeps a one-clause pointer. (2) The Opinion lane gains its missing **triviality floor** — a pick too small to be worth its \~4-seat bill gets no option (manual had a bar, AUTO had two knobs, ask-CB had nothing; as written it opened a board for a typo). (3) The walker-donated **discriminator now stated plainly**: the trigger RIDES an ask ALREADY being made — deciding WHETHER to ask is not this lane; only the three named shapes enter with no ask pending. (4) One-witness seam: Step 0's activation list now marks `manual /coalboard` as the Entry manual lane arriving at that gate, not a fifth path.
* **The CT↔CB arbitration cue (Run 13, headline 55.6% — the modal answer was WRONG) rewritten; CB authors, CoalTipple copies the Triage sentence byte-verbatim in its own dispatch.** The one undefined noun is now defined: **STAKES = the Layer-2 VERDICT that the task itself is stakes-domain work, and a Layer-2 acquittal STANDS — no keyword re-arms it** (the old `in doubt WITH any stakes signal` let a Layer-1 keyword hit overturn Layer 2's own acquittal — 6/9 walkers named it; strong tier unanimously wrong on S1). The triage **binds on single-hook turns too** (the old sibling-scoped form made `trivial -> neither` dead text exactly where trivial is most likely — 17 of 23 live fires were CB-only). Detector-vs-rule vocabulary reconciled generically: fired keywords of ANY vocabulary (`auth` included) are Layer-1 evidence, never the verdict. The stakes set keeps its bounded 4-token parenthetical (rail D measured healthy at 11.1%) with middots replacing the double-duty slash. `triggerConfidence`/`triggerGradeFloor` are no longer named-without-values: the hook now emits the RESOLVED merged-config numbers (def 90 · 4) in the bar clause — the hooks-safety §9 emit-the-computed-value pattern.
* Live fixture recorded with the walk: 23 hook fires in one session, ZERO error-not-allowed tasks, 74% on `<task-notification>` plumbing — Layer 2 held 23/23 live but held by luck of reader, not construction; this carve is the construction. (The Layer-1 seed set itself is WAI recall-max and was NOT tightened.)

### Fixed (carve round 5 — Run-10 EN variance walk, two convene-lane seams)

* **Two haiku-tier misreads closed at their source, both structurally nameable, not wording polish.** (1) The Entry's Auto bullet named the LANE "Auto" beside the mode value `coalboardMode:auto` with nothing distinguishing them — a walker landing on Entry first fused the two and concluded the GATE-1 box never fires; now states the lane name is not the mode value and that the mode (def `ask`) still decides GATE 1's form. (2) The Seat-permissions ledger showed all five rows with no statement that the set is FIXED — a walker scenario-filtered to the rigor's active seats and answered a 3-row table; now states all five rows exist at every rigor, a preset only leaves a row unseated. Opinion lane untouched (already PASS at s+o tier) — re-walk scope is the convene lane only.

### Fixed (task #34 — first baseline lane-selection walk since Run 12; S4 regression closed)

* **CoalBoard's `SKILL.md` had never been re-walked since Run 12 (67% variance, top tier split against itself) despite eight subsequent carve waves on the arbitration cue.** A fresh 9-walker baseline (weak/medium/strong ×3, the lane-selection instrument) found S1–S3 stable and correct, but **S4 (a low-stakes preference fork) at 33.3% divergence with the WRONG modal answer** (6/9 said NEITHER; ground truth is OPINION) — a regression introduced by the task-#6 triviality floor, which was meant to exclude manufactured questions (a typo) but also excluded genuine low-stakes asks (a library pick) as an unintended side effect. Fixed: the triviality clause now carries a positive test — "a decision you would ask about ANYWAY, not one invented to justify asking" — with both example shapes named inline. Re-walked after the fix: **9/9 correct, 0% divergence on S4.** Also fixed at source: the On-disk output ledger's heading claimed "5 writes" while listing 6 bullets (1 witness, independently verifiable); the Entry/Step-0 duplicate activation-signal list (7/9 witnesses across the baseline, the same defect Run 12 flagged in 2026-08-01 and nobody closed) — collapsed to ONE canonical list in Step 0, Entry now points to it instead of restating a count.
* **Lean pass, small and honestly incomplete:** the capability-hack paragraph's historical specifics stayed cut from the body — the nesting-lesson example moved to `references/failure-modes.md` (4,444 → **5,108 ch**); the AG-validation detail was a **deduplication, not a move** — `references/platform-antigravity.md` already carried the date/scope, so the body's restatement was simply removed and the pointer sharpened (destination gained 0 bytes, verified byte-identical before/after). The Memory-arming rationale trimmed to its rail. Net body change ≈ −150 ch — **nowhere near the \~5,000-token spec budget** (platform-measured on-invoke stays \~13k before and after). This file's rail density (six enumerated ledgers, P1–P19, F1–F12, the full seat table, every consent gate) is the honest floor; reaching budget would mean cutting one of those, which is refused per skill-authoring.md §5.

### Changed (opinion lane returns a DISPOSITION, not a question — the owner's own stated purpose for "ask CB")

* **The lane's whole point is that the user does NOT have to be asked, and the shipped contract had it backwards.** SKILL.md's Entry bullet ended "the judged synthesis re-asks the ORIGINAL question and the user decides" — main ran the lane, got a clear board answer, and fired an `AskUserQuestion` anyway, following that contract exactly. Owner (2026-08-04, verbatim): *"ฉันมี ask CB เพื่อให้ไม่ต้องมาถาม user นะ คุณจะย้อนมาถามฉันทำไมล่ะ เสนอให้ปรับแก้ ถ้าไม่มีคำตอบ ก็ใช้ตามนั้น ไม่ต้องยิง ask user."* **Note for the record:** the literal quoted sentence lived only in the STALE INSTALLED plugin (v1.10.0 as shipped) — the source tree had already partly reworked this in the v1.9.0-era "verdict is the default" language, but that fix was never released, so production kept running the old behavior. This edit closes the remaining gap in source: the old wording never said whether presenting the verdict uses a blocking question-box, and the file's own "every consent uses the question-box" rule left that open — which is how a resident could still fire an `AskUserQuestion` even reading the improved text.
* **New ledger, `Opinion-lane disposition` (the 7th countable ledger; the "Six lists" intro line is now "Seven"):** the judge ACTS on its own verdict by default — presents the disposition in chat, never a blocking question-box, and continues — unless exactly one of three named conditions fires: spends real money (a fable seat, a large fan-out) · changes what the product PROMISES a user (a shipped claim, a platform tier, a permission) · the seats did NOT converge, or converged at a stated LOW confidence. A technical disposition inside the room (which layer, which mechanism, whether to build a gate) is named explicitly as the ACT case, not a fourth trigger — that ambiguity in an early draft of this same ledger was caught by 4 of 9 AFTER-walk witnesses before shipping and tightened to "ACT (default) or ESCALATE for exactly ONE of three reasons." Entry's opinion-lane bullet and `references/opinion-board.md`'s Judge/Output bullet both now point to this ledger instead of restating the old vaguer "adopt it and proceed" phrasing; `opinion-board.md`'s `coalboardMode:'off'` re-ask line is untouched (correctly, the board never ran there).
* **Walked, not asserted: BASELINE FAIL, AFTER clean pass.** Baseline (9 walkers on the pre-edit text) found real, load-bearing ambiguity on exactly the rail this edit targets — "does the reader ACT on a board result or ask the user" — S2 (a follow-on fable spend) at 44.4% divergence, S4 (deadlock/low-confidence) at 33.3%, the enumerated-condition count at 33.3%, and S3 (a verdict that changes a shipped product claim) UNANIMOUSLY answered ACT because nothing in the old text named it as an escalation case at all — a real gap the new ledger closes. AFTER (9 fresh walkers, same instrument): **0% divergence on every rail, every modal correct, first pass** — S1 ACT, S2/S3/S4 ESCALATE with the correct named reason each, count=3 unanimous.
* **Ratchet:** body 45,817 → **46,762 ch (LF-normalized)**, +945 (the new ledger + both pointer updates — a RAIL, kept in the body per skill-authoring.md §5; no rationale prose added, so no reference gained size for this change). Platform on-invoke stays \~13k. **Re-derive, never quote.**
* **Findings-back (INSPECT, closed in commit `65e4d78`): FIX-NEEDED, 3 MEDIUM + 1 LOW, all closed same commit.** F1 — Entry's pointer said the ledger was "above" it; the ledger is BELOW. Not imprecise, inverted — the same class this room already paid for at `57957c8`. Fixed by dropping the direction word entirely (name the thing, never point at it). **Structural finding, not just a fix:** Run 26 could not have caught this — its own stamp records the load layer as the whole body with the ledger already present, so a walker never had to navigate a pointer to find it; a walk whose load layer pre-resolves a pointer cannot measure whether the pointer works. F2 — the ledger heading said "exactly ONE of three," followed by FOUR bullets, the fourth an explicit non-member — the same shape this room already refused once (the `P5 — RETIRED` tombstone, rejected for putting a non-prohibition into a ledger whose whole property is that every bullet is one). Fixed: the non-trigger moved OUT of the bulleted list into the ACT clause itself; the list stays exactly three items. F3 — a real completeness gap: an IRREVERSIBLE or OUTWARD-FACING verdict (drop a table, force-push, send to a customer) fires none of the three triggers, so the ledger reads as the closed enumeration of when a human is consulted. **Ruled: a non-override line, not a fourth trigger** (a fourth reopens the count rail F2 is about and owes its own re-walk; the protection already exists as a standing rail the ledger never had authority over) — added: "This ledger decides whether the VERDICT is re-asked; it never relaxes the confirm-before-irreversible-or-outward-facing rule you already hold." F4 — README said "unless **the pick** would spend real money"; the trigger is about what ACTING ON THE VERDICT spends, the pick is the \~4-lens bill already consented at GATE 1. Fixed at both citing lines (`:37` named by the reviewer, `:52` found by sweeping the same phrase).
* **Re-walked with the SAME frozen instrument (Run 26 → Run 27), load layer fixed this time:** 0% divergence on every rail held (S1 ACT, S2/S3/S4 ESCALATE with the correct named reason, count=3), AND the "one of four" heading tension F2 also incidentally closed — 4/9 Run-26 walkers had flagged it unprompted; **0/9 flag it in Run 27.** Version: `71acd56`'s own CHANGELOG section relabeled `### Fixed` → `### Changed` per the reviewer's upheld nudge (it alters documented, user-visible behaviour — README said re-ask, it now acts); the SemVer number itself does not move (MAX-across-`[Unreleased]` already reads MAJOR from the self-fence removal).

### Added

* **Opinion-lane self-trigger: three named fork shapes surface the "ask CB" offer even with no question pending (`9ac5945`, findings-back in the same commit).** A fix-loop re-meeting the SAME class in a new guise across rounds · a round count past the project's OWN declared ceiling (if any) · a fix that visibly seeds the next round's rework — each is "which layer does the defect live in", the fork this lane exists for. A **does-not-fire boundary** names the negatives: one finding however severe · a clean round · a class seen for the first time. Offer-only, same per-instance pick-is-consent form — **never auto-convene**. USER incident: a CoalWash fix-loop re-hit the same class 6 rounds, past its own \~4-round ceiling to \~10-11, before anyone offered the board by hand.

### Notes

* Regrowth-ratchet (skill-authoring §5): body 44,343 → **44,073 ch (LF-normalized)** — net DOWN 270 despite adding the classification pointer + the opinion-board.md additions, because the 4 reverted parentheticals were larger than what replaced them. `references/opinion-board.md`: 9,934 → **10,924** (the classification + no-TARGET line live there now, on purpose — that reference is read only by the lane that needs it, never the convene lane's resident cost). This is the new baseline the next release diffs against.
* **Ratchet, after the self-fence removal:** body 44,073 → **43,362 ch (LF-normalized)**, −711 · `references/opinion-board.md` 10,924 → **10,854** · `references/audit.md` 7,314 → **6,967**. A removal, not a carve — no explanation prose moved to a reference, so nothing offsets it.
* **Ratchet, after the least-privilege pass — 43,362 is NO LONGER the figure to diff against.** Body measured **44,923 ch (LF-normalized)**, +1,561, all of it rails: a 5-row ledger table, `P19`, and the enforced-vs-contract clause at the spawn site. Every rationale went to a reference, which is where the offset went — `references/lens-prompts.md` 10,094 → **13,551** · `references/opinion-board.md` 10,854 → **11,406** · `references/platform-antigravity.md` 2,376 → **2,822**. **Whether the carve BASELINE resets to this number is main's call and is pending** (the enumerability carve already moved the body far past the recorded 31,936 for the variance walk); this entry exists so nobody diffs the next release against a figure two changes stale. **Re-derive, never quote:** LF-normalize, strip `/^---\n[\s\S]*?\n---\n/`, `.length` the remainder.
* **Ratchet, after the opinion-lane self-trigger addition — 44,923 is NO LONGER the figure to diff against.** Body measured **45,416 ch (LF-normalized)**, +493: the Entry bullet named three self-recognized fork shapes (+440, `9ac5945`) then gained a does-not-fire boundary clause and dropped a redundant trailing WHY clause in the same findings-back pass (net +53 across both edits) — rail-only, no new reference. **Re-derive, never quote:** LF-normalize, strip `/^---\n[\s\S]*?\n---\n/`, `.length` the remainder.
* **Ratchet, carve round 5 + the lane-line carve — 45,416 is NO LONGER the figure to diff against.** Round 5 (`611556c`, recorded late — this bullet is the record): 45,416 → **45,734** (+318, the Auto-lane/mode disambiguation + seat-ledger fixed-cardinality clauses, rail-only). The Run-12 lane-line carve then: 45,734 → **45,965** (+231 net = −185 the thrash-sentence deletion + +416 the three lane-line rails: opinion floor · rides-an-ask discriminator · manual-lane disambiguation). `references/failure-modes.md` 3,729 → **4,444** LF (the relocated thrash why + its never-a-trigger boundary). **Re-derive, never quote.**
* **Ratchet, task #34's baseline-walk carve — 45,965 is NO LONGER the figure to diff against.** Body measured **45,817 ch (LF-normalized)**, net ≈ −150 (small explanation moves out + the S4/ledger/list rail fixes in). `references/failure-modes.md` 4,444 → **5,108 ch**. Platform on-invoke (`claude plugin details coalboard`) stays **\~13k tokens before and after** — this carve did not move the needle on the \~5,000-token budget; see the task #34 Fixed entry above for why. **Re-derive, never quote.**
* **Ship-text accuracy (station-3 findings-back, not in dist — `README.md` and `PRIVACY.md` are not copied into `plugin/`):** both enumerate what the board writes and both omitted the project-config write — the single `fableConsent: "always"` key GATE 2 persists into `.claude/.coalboard.json` when you answer "always, this project" at the Fable money gate. README's sentence read *"writes only to its own scratch"*, which that write contradicts; PRIVACY also omitted `.coalboard/memory/`. Both now match the On-disk ledger.
* **SemVer reading, FLAGGED not applied — no bump, no tag, no push in this commit.** The shipped dist DOES change (`SKILL.md` + both references are in `plugin/`), so the no-version-for-a-doc-only-change rule does not apply here. `scripts-quality.md` §3's mechanical map reads a breaking `### Removed` as **MAJOR**, and the judgement test agrees on the axis that matters: a user relying on the fence loses it, silently, on update. The nuance a reviewer should weigh: no public API, config key or CLI is removed — the fence never had a config key — so nothing a user *configured* breaks; what breaks is an implicit behaviour. Both readings land above PATCH. **The version moves ONCE when the variance walk passes** (currently RED), per the USER-locked walk-loop — this commit deliberately spends no number.

## \[1.10.0] - 2026-07-27

**MINOR** — self-fence: every `.coalboard/` dir the board plants now carries its own `.gitignore`, created before the first file, so a planted report/staging dir can never ride a host repo's commit; plus the v1.9.0 station-3 findings-back (below), previously unreleased.

### Added

* **Self-fence — every `.coalboard/` dir the board plants carries its own `.gitignore`, planted BEFORE the first file.** New hard rule in the SKILL contract: on the run's first write into any `.coalboard/` dir (staging · report · memory), ensure `.coalboard/.gitignore` exists — missing → create it first, exactly two lines: `# CoalBoard reports are machine-local - never committed (self-fence)` then `*`. The fence rides the planted dir itself, works in ANY host repo (the user's own, a fork, a stranger's), never edits the host repo's own `.gitignore`, and leaves an existing fence as-is (so an operator-planted `*`-only fence survives). Why now: the report-location rule (correct, unchanged) writes audit reports INSIDE the scanned repo — measured 2026-07-27, three public forks carried an unfenced `.coalboard/` whose internal audit reports + upstream-vulnerability notes sat one `git add -A` from publication (confirmed still unpushed = latent, not realized). Pointer rails ride each write moment (Step-4 stage, the report write, the memory checkpoint) and `references/audit.md`'s report-location rule (the foreign-repo path — the incident's exact shape).
* **Honest enforcement split (hooks-safety.md §9 vocabulary — which half is prose, which half is code):** the runtime PLANTING is a PROSE rail, probability <1 — CoalBoard is Fork-A (no CoalBoard code executes in the target repo at write time, and Phoenix #10 bars the conductor hook from writing into any project), so the write-moment rail is agent-followed like every other write rail in this contract. The CODE half is a new `verify.mjs` gate (`self-fence rail present`) pinning the rail TEXT — the exact fence line in `SKILL.md` + the pointer in `references/audit.md` — on every commit (`.githooks`) and CI: the docketed body carve can no longer drop the rail silently. Probability 1 for the rail's PRESENCE in the shipped contract; the write itself stays prose-enforced. The gate was watched RED (rail absent) before the rail landed.
* **Proof (scenario re-run — the runtime half is prose, so the write moment was executed once, verbatim, against a scratch git repo):** fence planted first, report second → `git check-ignore` passes for the report AND the fence itself (self-ignoring `*`) · `git add -A` stages ZERO entries from the planted dir · the host repo's own `.gitignore` never created/touched · a pre-existing `*`-only fence preserved un-rewritten. Fence body asserted byte-exact to the two-line spec.

### Fixed

* **Opinion-seat run/fetch rights are now declared EXPLICITLY in all four rows of the `references/opinion-board.md` seat table — silence no longer carries meaning** (station-3 finding on v1.9.0: `feeling` had an explicit denial and `realtime` an explicit grant, while `reality` and `outdim` were silent — and the README Permissions row read that silence as "no", claiming the run/fetch spike sits on `realtime` alone). Ruled at the SOURCE: **`reality` MAY run** — to DEMONSTRATE what breaks (it inherits the show-me role, whose audit-lane contract already says "you MAY run: tests/build/parse", and a live board that day had its show-me build + run a prototype; a demand-only "show me" seat degrades to rhetoric) — run-only, no network reach (`realtime` stays the lane's only fetch); **`outdim` explicitly may NOT run, fetch, or read the workspace** (ANY environment contact leaks the shared frame its blindness exists outside of — previously implied by "BARE FRAME only", now stated, since a lens spawned with a project cwd could otherwise `ls` its way into the house context). The table gains a Run/fetch column + an anti-silence rail ("a seat added later must declare too"); the README Permissions row corrected to match the table (two seats run; feeling and outdim never). The judge's run-claim-needs-repro rule was already seat-agnostic — unchanged.

### Changed

* **The opinion lane's closing step: summarize for the USER, verdict = the default** (USER rule 2026-07-27, verbatim: "หลังจากที่ ask CB ไปแล้ว สรุปให้ user อ่านด้วยแล้วกัน ถ้าไม่มีเสริมอะไร ก็ยึดตามนั้น"). The judge's output is now a READABLE summary — the verdict on the original question + per-item dispositions — instead of a bare re-ask; the user adding/correcting overrides PER ITEM, the user adding nothing = adopt the verdict and proceed. NEW NAMED divergence in `references/opinion-board.md`: the user's voice is an OVERRIDE here, not a hard gate — unlike the error-not-allowed lane's mandatory human sign-off; safe because this lane changes no files and gates no apply. (Live shape exemplar same day: a 5-disposition summary, accepted as a set, adopted immediately.) SKILL.md body untouched — its "re-asks the ORIGINAL question and the user decides" line stays true under the refinement (accepting the default IS deciding); README untouched for the same reason ("you decide" makes no gate claim).

### Notes

* **Regrowth-ratchet (skill-authoring §5): SKILL.md body = 32,889 ch (LF-normalized) — the new baseline the next release diffs against** (prior baseline 32,300 at v1.9.0). The +589 rise is RAILS only — the self-fence hard rule + its three write-moment pointers; no explanation prose entered the body (the why lives here and in `references/audit.md`'s one-clause parenthetical).
* README Permissions row + PRIVACY.md state list updated to carry the self-fence claim (and the Permissions row now names `reports/` among the writes — it enumerated `proposed/` + `memory/` only).

## \[1.9.0] - 2026-07-27

**MINOR** — the OPINION lane ("ask CB"): an uncertain-decision question gains a third option that convenes a 4-seat opinion board (USER spec 2026-07-27); plus the config-cascade clamp security rounds (previously unreleased, below).

### Added

* **The OPINION lane ("ask CB").** About to ask the user to settle a decision the agent cannot settle itself → the SAME question gains an **"ask CB"** option, cost labeled (\~4 lenses + judge). The user's PICK is the consent — per-instance only, never auto-convene, nothing persisted; `coalboardMode:'off'` suppresses the offer. Distinct from the error-not-allowed slice: this lane is for genuine forks worth outside opinions, and its output is an OPINION re-asked into the user's original question — the user still decides; no staging, no report file (Step 4 never runs).
* **4 OPINION seats — equal knowledge, locked perspective as the ONLY difference** (USER: "รู้เท่า ๆ กัน สิ่งที่แตกต่างคือมุมมองที่ล็อกเอาไว้"): `realtime` (trusts only what is measured/run THIS session, every measurement ships its reproduction, out-of-reach = declared "RAN BLIND") · `reality`/show-me (undemonstrated = not yet real; builds breaking cases in BOTH directions) · `feeling` (the human-experience seat — walked tangible scenes, NO running; NOT the audit lane's show-me skeptic despite sharing the Thai name ความรู้สึก) · `outdim` (receives the BARE problem only — no proposal, no house context; designs its own answer, then hunts its OWN design's failure modes). All seats one EQUAL tier (default `sonnet`), **never `fable`** (standing USER rule: fable is never an opinion sub). Seats are FIXED by design — not read from `lenses`/`rigorLensTiers`/rigor presets (those belong to the error-not-allowed lane, untouched).
* **REFUTE-not-grade:** when the asker supplies their own leaning/proposal, every seat that sees it is instructed to REFUTE it (a failed refutation is the strongest support); `outdim` never sees the proposal.
* **Judge = main, synthesize-not-vote:** verifies before weighing (a run-claim needs its attached reproduction; without one it weighs as opinion), never counts votes, applies the Step-2 theatrical-consensus guard, and re-asks the ORIGINAL question with the board's view attached.
* **`references/opinion-board.md`** — the lane's full procedure (frame/bare-frame, seat contracts, refute rule, judge rules) + the NAMED divergences from the error-not-allowed lane, loaded on-demand at the pick. The error-not-allowed lane — its lens set, tier ladder, fable consent-gate, benchmark — is UNTOUCHED.
* The frontmatter `description` now carries the opinion trigger (the always-loaded surface is what lets the agent offer "ask CB" at question time); enumerative filler trimmed to hold the 1024 cap (1016/1024, unchanged headroom). **No conductor-hook change**: the lane has no auto path, the offer moment (the agent composing its own question) is not hook-observable on any sanctioned channel (hooks-safety §13 — SessionStart/UserPromptSubmit only), and the description is already the always-loaded activation surface; a SessionStart contract line would double-pay every session for a lane that fires rarely.

### Changed

* **Front doors now speak the whole product (docs, same release):** README gains the three-doors framing (auto · manual · opinion "ask CB"), the opinion-lane seats/refute/no-files paragraph, a Permissions note scoping the lane's run/fetch spike to `realtime` alone, and names **generate** + **audit/review** on the manual door; `plugin.json` + `marketplace.json` descriptions now carry the opinion lane and the audit mode (measured 990 · 953 · 323 chars, all under the 1024 cap) — closing the long-open "front doors speak Generate mode only" gap alongside the new lane.
* **SKILL.md body re-carve to offset the new rails (regrowth ratchet, skill-authoring §5) — net −72 ch vs pre-work despite the added Entry rails.** Six EXPLANATION fragments moved out or deduped, every rail kept verbatim: the blind/anchor why + the cache-shaping cost why + both sub4 whys (reviewer-in-frame · why-the-ruling-stands) → `references/failure-modes.md`; the tier-ladder decorrelation rationale + the R2-6 dev-framing why — both already carried verbatim-equivalent in `references/lens-prompts.md` — deduped (say-once); the worker-label chip fact → `references/lens-prompts.md` §Model assignment; one within-paragraph duplicate rationale ("parallel solos") deleted where the same sentence says it two lines up.

### Security

* **The project `.coalboard.json` layer could ESCALATE a consent-bearing key past an explicit global choice** (`coalboardMode`/`updateMode`: `off`/`ask` → `auto`) on TWO read paths — the conductor hook's own `readCfg()`, and `SKILL.md`'s own "project wins per key" merge instruction, which the AGENT follows to decide ask-vs-auto at Step 0 (the real consent decision, reachable via 3 of 4 activation paths — manual `/coalboard`, the agent's own judgment, a CoalTipple hand-off — that never touch the hook). **The two rounds below are NOT the same strength — do not describe them with the same word (hooks-safety.md §9):**
  * **Round 1 (`3f68bb5`) FIXED the hook's `readCfg()`/`mergeSafety()`** — a code-level guard, enforced with probability 1 on every invocation. (Its CHANGELOG line overclaimed full closure of the whole gap — corrected here, not erased: see the history below.)
  * **Round 2 (`1598a2e`) MITIGATES, does not close, the SKILL.md half** — `SKILL.md:87`'s merge instruction is now clamp-aware for the same two keys, but this is a PROSE carve-out: it holds only if the agent reads and follows it. This room's own doctrine already draws this line ("any CB invariant that MUST hold gets a receipt-checkable position; SKILL prose = signpost only" — `MEMORY.md`, 2026-07-10), and this room's own benchmark measured un-primed prose compliance at **\~65%** against the hook's probability-1 (`.github/benchmarks/CoalBoard/RESULTS.md`, CC reliability line). Call this "narrows the second read path," never "closed."
  * Round 2 also fixed a second, genuinely code-level gap: `mergeSafety()` skipped the clamp entirely when global had no explicit value, leaving an unconfigured user (the common case) unprotected — an absent global now anchors on the schema default (`ask`) instead of "anything goes."
  * `fableConsent`/`applyConsent` remain correctly out of reach in both rounds (the hook never reads either key at all) — a named gap, not a regression.
  * Mirrors CoalMine `updateMode` (v3.9.3) / CoalWash `mergeSafety` for the hook shape; the SKILL.md mitigation and the absent-global fix are new here, not yet ported to those two exemplars. Case-fold was correct but untested — a mixed-case regression fixture added.

### Notes

* **Regrowth-ratchet (skill-authoring §5): SKILL.md body = 32,300 ch (LF-normalized) — the new baseline the next release diffs against** (prior recorded baseline 31,936 at v1.8.0; v1.8.1 measured 31,963). The rise since 31,936 is RAILS only — the §9 config-clamp prose mitigation (+409, the Security entry below) and the opinion-lane Entry rails — while this release's own carve moved six explanation fragments OUT (net −72 vs the pre-release 32,372).
* **CB-specific honesty note on the absent-global fix:** it has NO hook-observable effect for this repo — `boardOff()`/`updateDue()` both branch only on `off` vs non-off, so a value clamped to `ask` instead of left at an escalated `auto` cannot be told apart by a hermetic hook-spawn test. Shipped for `mergeSafety()`'s own correctness (and because the SKILL.md mitigation makes the SAME distinction observable to the agent reading it) — not cited as closing a hook-level escalation for CB.
* **Vocabulary rule for future edits to this entry (hooks-safety.md §9, amended 2026-07-27):** a hook-side clamp = FIX (code-enforced, probability 1); a SKILL.md/prose clamp = MITIGATION (compliance-dependent, \~65% un-primed per this room's own benchmark). "Closed"/"fixed" is reserved for the first; the second only "narrows"/"reduces."

## \[1.8.1] - 2026-07-24

**PATCH** — doc-truth fix in the shipped skill body; no behavior change.

### Fixed

* **The Memory & resume auto-trigger line still called CoalHearth "design-only."** CoalHearth graduated to a LIVE sibling; the auto-trigger note in SKILL.md (source + `plugin/` dist) hadn't caught up. Reworded: "CoalHearth is LIVE — lean on it when installed; absent → CB's light net / best-effort." The auto-trigger's actual degrade-safe behavior (light net when CoalHearth is absent) is unchanged — only the resident rail's own description of it was stale.

## \[1.8.0] - 2026-07-23

**MINOR** — Fable 5 gains a first-class identity as CoalBoard's top lens tier: a mixed per-seat rigor ladder, a consent-gate before any fable seat, and AUP-safe prompt templates.

### Added

* **`fable` is the explicit TOP rung of the alias floor** (`haiku < sonnet < opus < fable`). New `scripts/lib/ladder.mjs` derives `FAMILY_RANK` with fable at top (was UNKNOWN→strong); the board stands alone on this floor, and a CoalTipple `ranking.json` — if installed — stays a bonus.
* **A RANK/RANGE per-seat tier LADDER** replaces the old one-tier-per-rigor default (which ran 4 identical models per rigor — zero model-decorrelation). The code stores RANKS + a 2-tier RANGE per rigor and derives the model names from the alias floor at resolve-time (no model-name literals to rot — mirrors CoalTipple's FAMILY\_RANK): support seats (data, feeling) take the range FLOOR, reasoning seats (truth, then adversary) take the ceil FABLE-FIRST. `rigorLensTiers[rigor]` now accepts a per-seat object `{ data, truth, feeling, adversary }` (or, unchanged, a single string/chain = every seat — the pre-fable form). Factory ladder (seats data·truth·feeling·adversary): relaxed `haiku·sonnet·haiku·sonnet` · standard `sonnet·opus·sonnet·opus` · high `opus·fable·opus·opus` · nasa `opus·fable·opus·fable` — each rigor uses a 2-tier range + fable at the top rung (high/nasa), a light model-decorrelation gain (secondary to the structural diversity; high/nasa opus-heavy by design). Locked invariants: fable count 0/0/1/2 · data (fetch-bound) NEVER fable · monotonic per seat. A tier-MIX per rigor is the only actuatable model-decorrelation on Claude Code; `lensTiers` per-role pins still override.
* **`degradeSeats` — the §2 robustness fallbacks.** A resolved seat whose model is UNAVAILABLE this run — or EVERY seat on a platform with NO per-sub model-pick — collapses to `main` (the parent model is the one guaranteed present, and the judge already runs on main). Distinct from the fable safeguard-BLOCK re-route (a content refusal on an available model → highest non-fable); this is true unavailability / no-pick-capability → main.
* **A Fable consent-gate (`fableConsent`, default `ask`).** At `high`/`nasa` — the only rigors that seat fable — a consent box fires BEFORE the fable seats spawn, showing the exact fable count (1 at high, 2 at nasa) + a \~est token/credit cost + a both-plan rate note (Max/Team-Premium = within the weekly Fable cap · lower plans = real metered credit \~$X at $10/$50 per Mtok, labeled \~est). Options: `once` · `always this project` (persists `fableConsent:"always"` to the project `.coalboard.json`) · `no` (falls every fable seat to the highest non-fable tier — opus, derived from the floor, never hardcoded). `always`/`never` skip the box.
* **`references/platform-cc.md`** — Claude-Code adapter facts: fable is exposed safe-or-block (a block is pre-generation, 0 tokens, and lands as an EMPTY return), so a block rides the EXISTING subagent-safety empty-return re-route (re-spawn on opus) — **no new recovery code**; plus the consent-box cost/rate note. CB core stays platform-generic.

### Changed

* **The adversary lens is now FABLE-ELIGIBLE; ONLY the sub4/observer tiebreaker stays always-non-fable.** The adversary is a domain-general logical-falsification lens (find where the work fails its OWN contract/invariant — logic, not exploit; CB reviews code/docs/math/research/translation/legal, security being ONE domain), so it is fable-eligible and seated fable at nasa (the nasa reasoning pair = truth + adversary); `resolveSeatTiers` no longer strips it. Only the sub4/observer tiebreaker is hard-stripped — `observerTier()` resolves + fable-strips it at ANY chain position (final-arbiter robustness, enforced in code). `stripFable` (used by `observerTier` + the `no`/`never` consent fallback) removes fable at ANY chain position (a `['opus','fable']` fallback no longer evades it — the earlier head-only check did). Honest residual (no new code): a fable adversary on a security-CODE target may trip Fable's safeguard → the EXISTING empty-return re-route falls that spawn to opus, uniform with any fable lens on security content.
* **All lens / launch / judge prompt templates rewritten AUP-safe** (SKILL.md adversary line + `references/lens-prompts.md`): defensive-QA-neutral vocabulary ("find a counterexample" · "where the work fails its own spec" · "search for the falsifying case") with the adversarial INTENT and epistemic pressure fully preserved — the teeth kept, the safeguard trigger words dropped ("red-team", "BREAK IT", "HUNTS"). The `adversaryLens` config-doc help (`config-schema.mjs`, `.coalboard.json`) is neutralized to match (one-flock consistency — these are main/user-facing docs, never lens-received). The adversary lens prompt's FOCUS is also DOMAIN-NEUTRALIZED ("concentrate where falsification bites hardest in the target's OWN domain … security is ONE such surface, never the frame") — the old "executable + security-sensitive surfaces" tail is gone, matching the fable-eligible, domain-general framing.
* **`references/wizard.md`** now routes the manual `/coalboard` path to the fable consent-gate when the resolved models seat fable (high/nasa).
* **The decorrelation frame updated:** nasa now MIXES fable+opus (2 tiers, a light model-decorrelation) rather than all-opus — but both are Claude, so the correlated-blind-spot ceiling persists; re-stated in `references/lens-prompts.md` §Model assignment.

### Notes

* **The ladder's tier choices are a self-contained config decision** — justified purely by rigor-scaling (stronger rigor → stronger seats, monotonic) + tier-mix decorrelation (a spread of distinct tiers per rigor beats a repeated model). No shipped text (code, config, SKILL, references) cites any external benchmark or model-comparison; none is needed.
* **Regrowth-ratchet (skill-authoring.md §5):** SKILL.md body = **31,936 ch (LF-normalized** — the canonical measurement for the ratchet going forward; the v1.7.6 baseline was 31,510). The growth is RAILS (the per-seat rank/range ladder, the consent-gate flow, the ONE security-seat non-fable rail \[sub4/observer] + the adversary-fable-eligible rail, the fable-block re-route target); the explanations (cost/rate note, the seat-derivation + decorrelation rationale, the security-content residual) live in `references/platform-cc.md` + `references/lens-prompts.md`, not the body. New baseline the next release diffs against = **31,936 ch (LF)**.

## \[1.7.6] - 2026-07-16

**PATCH** — SKILL.md body re-carve (the regrowth-ratchet, skill-authoring.md §5). No behavior change: every RAIL stays in the body verbatim/intact; only EXPLANATION prose (war-stories · rationale · a duplicated lens prompt) moves to references.

### Changed

* **SKILL.md body carved 33,831 → 31,510 ch (−2,321).** The v1.0.13 carve (32,372→19,433, −40%) had re-grown to 33,831 (+74%, past pre-carve) as dogfood added WHY/war-story prose INLINE around real rails, release-over-release. Four explanation blocks moved out, rails kept: (1) the Step-1 Bounds grandchild-runaway war-story (\~213k-token runaway) + the flatten-limit mechanics → new `references/failure-modes.md`; (2) the Step-1 Tiers decorrelation essay (`diversifyModels`-INERT · nasa=all-opus MAX-correlation · sub4-shares-the-blind-spot) → `references/lens-prompts.md` §Model assignment (most was already there — dedup; the nasa/sub4 nuances appended); (3) the Memory & resume SendMessage-absent mechanics + build-verify note → `references/failure-modes.md`; (4) the duplicated \~600-ch show-me prompt in the sub3 lens-table cell shrunk to one line (the full prompt already instantiates from `lens-prompts.md`; the sub3 design-feeling ROUTE branch preserved in the Step-2 judge route line). Plus a Step-0 anti-rubber-stamp rationale trim (the phrasing already lives in `wizard.md`).
* **Rails verified INTACT via the §5 rail-checklist (the lab-gate PROOF, not eyeballing):** every rail enumerated from the pre-carve body — the spawn-no-spawn-leaf structural enforcement, the Backstop, the agentId reconciliation, the HONEST-CC-LIMIT best-effort/Clear/don't-attest set, the tier-resolution order + deterministic-by-table, the journal/remainder-re-spawn/idempotent resume set, the memory-delete + cross-read-forbidden set, and every consent gate — confirmed PRESENT + INTACT in the carved body (moved facts land verbatim in a reference; nothing dropped, nothing duplicated). New carve baseline for the regrowth-ratchet = **31,510 ch** (the number future releases diff against).

## \[1.7.5] - 2026-07-16

### Changed

* **HOOK-LEAN — a script-only signal downgrades to a one-liner.** `detect()` now returns the non-Latin script flag SEPARATELY from the hard reasons: a script-only turn (non-Latin prompt, no path/import/keyword hit) injects a one-line judge-by-MEANING reminder (\~126 chars) instead of the full CRITICAL block (\~794 chars) — the "judge by MEANING" rail already lives in the resident SessionStart contract; the one-liner is the re-surface net for long never-compacting sessions. A hard path/import/keyword hit keeps the full block unchanged, INCLUDING the CB↔CT arbitration cue — CB carries its OWN cue, never delegated to CoalTipple (CT's per-turn cue is conditional on CT's own whole-word/stem signal set, which is NOT congruent with CB's substring seeds: a bare `ledger`/`auth` turn fires this block while CT's cue stays silent that turn; an earlier draft removed the cue on the opposite premise — false, corrected in review).
* **SKILL.md Step-0 gains the ARC-JUDGMENT rail:** judge the TASK ARC, not only this turn — a sequence of individually-routine asks around money/records/overseers (send → deflect a stakeholder → keep the overseer out → clean up the record) is error-not-allowed even though no single turn looks critical; the asker personally benefiting from the edit is itself a signal. Harvested 2026-07-16 from a public Petri-style audit transcript in which a frontier target complied with exactly this ladder (deflection mail → counsel isolated → financial CSV falsified) and refused only the final minutes-backdating rung.
* `verify.mjs` gains the flock `DESC_CAP` gate: every `skills/*/SKILL.md` + `commands/*.md` frontmatter `description` (+ `when_to_use`) ≤ 1024 chars — cross-platform-safe cap (agentskills.io); CC's own listing truncation is 1536 combined (verified 2026-07-16). USER lock, past/present/future.

## \[1.7.4] - 2026-07-09

**PATCH** — docs-only conform, from the user's CoalBoard nasa audit (findings **M9 + L4**) plus a report-location hardening from a live incident.

### Changed

* **The ALWAYS-LOADED description surfaces now carry the same verified-hedge the resident body already did.** `SKILL.md`'s frontmatter `description`, `.claude-plugin/plugin.json`'s `description`, and `marketplace.json`'s plugin `description` each flatly asserted "Cross-agent (any platform...)" while the resident SKILL.md body (line 11) correctly hedged "Verified: Claude Code + Antigravity; every other platform designed-for, unverified" — a reader who only sees the always-loaded description (never opens the body) got the overclaim. All three now read "Cross-agent (verified: Claude Code + Antigravity; others designed-for, unverified; Claude Code adds cost-optimized tiering)." The SKILL.md frontmatter description stays inside the 1024-char cross-platform cap (1016 chars).
* **The 2026-06-22 Antigravity validation now names its own nature.** "Validated end-to-end" (SKILL.md body, `references/platform-antigravity.md`, README Install section) reads as an unverifiable self-claim on its own — an artifact (the reference doc) exists, but the process behind it is unaudited by anyone but the model that ran it. Each of the 3 spots gained one clause: a self-run validation, not third-party-audited. No rewrite, no change to the underlying record.
* **The top-line "zero-breakage" claim is now files-scoped (L4).** The Step-4 verify-run EXECUTES staged code before the human approves (no OS sandbox — a pre-run lint plus judgment), so a staged side-effect the lint misses fires pre-consent; the SIDE-EFFECTS ≠ FILES ceiling was disclosed deep in the docs while the headline guarantee stayed unqualified. The SKILL.md body top-line and the README guarantees section now carry the one-clause qualifier: zero-breakage is a FILES guarantee (staging rollback) — an executed verify-run side-effect is *prevented* (pre-run lint + propose-not-execute), never undone. The frontmatter description is unchanged (8 chars of headroom under the 1024 cap — the clause cannot fit; its "(staging)" parenthetical already names the mechanism).
* **`references/audit.md` TRANSIENT-clone bullet can no longer be stretched into silently picking the report location.** "Write the report to a PERMANENT path the user picks" was stretched live (2026-07-09) by an operator into silently choosing the umbrella PARENT for the user — the exact location the bullet above it forbids. The bullet now binds: ASK the user to pick the permanent path explicitly (never choose silently); a parent / umbrella / catch-all root is never a legal pick even if offered; user absent or no answer → the factory location inside the scanned part, telling the user to copy the report out before the next update wipes it.

### Notes

* M9's own meta-point: a board auditing itself shares the very platform blind spot it exists to catch — this repo IS the board's own home platform (Claude Code), so a self-audit cannot see a Claude-Code-only framing bias. The human (or an agent) on another platform is the check the board cannot perform on itself.

## \[1.7.3] - 2026-07-09

### Fixed

* Removed the now-dead `cfgList` function from the conductor hook. The v1.7.2 seed-union refactor moved `criticalPaths`/`criticalImports` to `seedList`, leaving `cfgList` unreferenced in `coalboard-conductor.js` (the conductor detects on prompts only — it has no `excludePaths` file-scan path; `trigger.mjs` keeps its own `cfgList` for the exclude default). Closes a CodeQL `js/unused-local-variable` note surfaced by the post-push code-scanning check. No behavior change.

## \[1.7.2] - 2026-07-09

### Security

* **`criticalPaths` + `criticalImports` are now ADDITIVE (union), not REPLACE — customizing the Layer-1 seed can no longer silently DROP the built-in security detection.** A project `.coalboard.json` setting `criticalPaths` (or `criticalImports`) REPLACED the built-in seed (auth/payment/migration/security/crypto · crypto/bcrypt/jsonwebtoken/child\_process), so a user adding one domain path silently disabled the board's AND-gate on the built-in security paths/imports. Now additive like `criticalKeywords` (a config EXTENDS the seed, never drops a default); a legitimately-named-but-non-critical dir is handled by `excludePaths`, not seed removal. Fixed in both `trigger.mjs` and the conductor's inlined copy; schema help updated. Same class as CoalTipple's v1.0.18 REPLACE→UNION `sensitivePaths` fix. +3 assertions. (Board-2 dogfood finding.)

### Notes

* Board-2 also flagged the cross-platform scope WORDING; on review it is already adequate — the README + SKILL body carry the honest "verified on Claude Code + Antigravity; every other named platform is design-supported, unverified" split (product of the v1.4.0 CB-14 + v1.6.1 cleanups). No change.

## \[1.7.1] - 2026-07-09

### Changed

* SKILL.md frontmatter description trimmed 1123 -> under 1024 chars (the cross-platform-safe cap; always-loaded per session on every platform, so shorter = a per-session token saving). No behavior change.

## \[1.7.0] - 2026-07-09

**MINOR** — measurement + cache-shaping + the double-hook arbitration (the CB side of `DOUBLE-HOOK-FIX.md`).

### Added

* **`/coalboard:stats`** (`commands/stats.md`) — the measurement standard-system command (series system #5): boards convened (auto vs manual), lenses + tiers, verdicts, staged-vs-applied, approximate spend; honest empty state; read-only.
* **Double-hook CB-bias (SKILL.md Step 0 hard rule):** a Layer-1 stakes signal fired → lean CB; the agent may downgrade only a CLEAR false-positive (a comment merely mentioning "crypto"), never silently skip a real one — a false convene wastes recoverable tokens, a missed board is unrecoverable. + the arbitration cue on the conductor's CRITICAL-signal message (same decision table as CoalTipple's — one flock). **3-tier regression PASS** (the stakes-borderline rate-limiter = CB on every tier).
* **Cache-shaping (SKILL.md Step 1):** the judge reads the target ONCE and EMBEDS the content into each lens contract (lenses stay blind to EACH OTHER — decorrelation intact; grounded 2026-07-08: cache is per-prefix, N blind lenses re-reading the same target pay full input N×); lens contracts say "emit ALL findings in ONE generation" (limit-robust); show-me/adversary keep their tool rounds; very large targets fall back to scoped lens-reads.
* **Theatrical-consensus guard (SKILL.md Step 2):** before counting votes the judge compares the lenses' REASONING FOOTPRINTS (evidence cited, checks run, argument path); substantially-overlapping lenses count as ONE voice, and the collapse is NAMED in the report — no invented numeric threshold (judgment-guided, honestly).

## \[1.6.1] - 2026-07-08

Same-day withdrawal of the v1.6.0 key + a text trim. Board behavior is identical to v1.5.5.

### Removed

* **`callFable` — withdrawn (shipped prematurely).** A SKILL.md feature flag cannot hard-block a lens seat the way commented-out code blocks execution — the owner's requirement is a gate that stays dead no matter what the config says. Tombstoned in the schema; returns as the redesigned real-money gate WHEN Fable billing actually leaves the subscription plan (it has not yet). With it go two texts that overstepped: the "never the security-focused lens" clause (a temporary platform state hardcoded as a standing rule — availability is DISCOVERED at spawn, the v1.5.3 lesson) and the repeated "never the judge / main never switched" declarations (an agent cannot switch the main model on any platform; stating the impossible is noise). A leftover `callFable` in a user's `.coalboard.json` is harmless (unknown keys are ignored).

### Changed

* The judge-model line keeps the v1.6.0 reconcile, trimmed: "the JUDGE runs on MAIN" (the old "always the top tier (opus)" stays gone).

## \[1.6.0] - 2026-07-08

**MINOR** — a new user-facing capability (factory-off) + two flock-conform doc fixes. Board behavior is unchanged until the user opts in.

### Added

* **`callFable` config key (factory `false`)** — a hard feature-gate for seating Fable on the board, independent of `rigor`. Fable is leaving the subscription plan: every call now bills real usage credits outside the plan's quota, so the gate defaults off and the user alone opts in. Off: Fable is never seated on any lens, and a `lensTiers`/`rigorLensTiers` entry naming it is inert (the gate overrides pins). On: Fable may be seated only on the highest-value seats — the sub4 observer (episodic; deadlock/max-stakes only) and optionally the truth/formal lens at `rigor:nasa` — never the security-focused lens (the platform safeguard reroutes Fable off security content) and never the judge, which always runs on main.

### Changed

* **README Configure section** converted from a prose paragraph to the flock table shape (the shape CoalMine/CoalTipple/CoalHearth/CoalFace already use) — a `| Key | Default | What it does |` table of the high-impact keys, including the new `callFable`.
* **Self-update nudge wording (`hooks/coalboard-conductor.js`)** aligned to the CoalMine/CoalTipple gold phrasing: web-check the latest tag vs the installed `plugin.json` version; offer `claude plugin update coalboard@coalboard` if newer; say "up to date" if current; say so and suggest updating manually later if git/network is unavailable (never assume).
* **Judge-model text reconciled (3 files).** The pre-existing "the JUDGE is ALWAYS the top tier (opus)" claim contradicted the design truth the new `callFable` text states: the judge runs on MAIN — the user's own model, never switched by the skill (convened as CoalTipple's top escalation rung it typically IS the strongest tier). SKILL.md, the `rigorLensTiers` schema help, and the factory-config comments now all say the latter.
* Relicensed from MIT to Apache-2.0. `LICENSE` is now the Apache License 2.0 (verbatim); a new `NOTICE` carries the attribution; the `plugin.json` `license` field is `Apache-2.0`. No code or behavior change.

## \[1.5.5] — 2026-07-02

**PATCH** — symlink-correct stop-at-home config walk (the series one-flock sweep; same class as CoalFace v0.1.0-beta.2, whose macOS CI proved the bug live).

### Fixed

* **`findProjectCfg`'s stop-at-home compare is now SYMLINK-CORRECT — realpath BOTH sides (`hooks/coalboard-conductor.js`).** On macOS `process.cwd()` returns the physical `/private/var/...` path while `os.homedir()` returns the raw `/var/...` symlink, so the lexical `dir === home` NEVER matched — the walk escaped above home and a `.claude/.coalboard.json` above home was read as PROJECT config, defeating the v1.5.1 stop-at-home guard on macOS (any symlinked HOME hits the same class). Both sides now resolve through `physical()` (`fs.realpathSync`, falling back to `path.resolve` when realpath throws — an absent dir has no realpath) before the compare; the walk stays lexical after that. Phoenix-13 unchanged: `physical()` is read-only and fail-open, inside the existing fail-silent try/catch.

Gate: build + 38 node tests + verify PASS.

## \[1.5.4] — 2026-07-02

**MINOR** — four fixes surfaced by a fable-nasa dogfood board auditing the Colliery mirror (its findings + its own process telemetry): a run-confirmed secret-scrub leak, two wizard-UX corrections, and an honesty fix to the no-zombie claim.

### Fixed

* **Secret-scrub URL-userinfo password leak (`scripts/lib/secrets.mjs`).** The URL-userinfo password class `[^\s/]+` stopped at the first `/`, so a path-shaped / base64 DB password in a connection URL (`postgres://u:pa/ss@host`) leaked **verbatim** into anything scrubbed before a log/consent display — the exact class the scrubber's own comment claims to catch. The password now runs greedily up to the LAST `@` that is followed by a host char or end-of-string (`[^\s]+(@)(?=[^\s@]|$)`), so a password containing `/` OR `@` is fully redacted (incl. the degenerate trailing-`@` `postgres://u:pw@`); a no-`/` password still redacts (control test). Single greedy run + a lookahead = linear (no catastrophic backtracking; the ReDoS guard test still passes). Regression test added (`lib.test.mjs`): the `/`-in-password + base64-DB-URL cases leak without the fix, redact with it. The scrubber's BEST-EFFORT-not-a-guarantee frame is unchanged — no new speculative patterns.

### Changed

* **Manual `/coalboard` routes by SIGNAL, not a fixed layman default (`references/wizard.md` + SKILL.md Entry).** A `/coalboard` with a TECHNICAL target (a repo/subproject/path, a rigor/depth/lens word, stated prefs) is a PROGRAMMER signal → OFFER the picks (order→bill→pay), never silently auto-pick-then-bill. The layman path keeps auto-picks but now MARKS every auto-picked knob as CHANGEABLE in the bill (the `cheaper`/`more thorough` levers map to depth/rigor), so the user knows the config was chosen for them and is theirs to change.
* **The bill's detail renders as chat text BEFORE the question box; the box carries only a 1-2 line decision summary (`~cost + headline config`) + options (`references/wizard.md` both paths + SKILL.md Step 0 checkpoint).** Applies the board's own anti-rubber-stamp rule to its own bill — cramming the full config/per-lens/cost breakdown INTO the question defeats the read.
* **Honest no-zombie framing on Claude Code (SKILL.md Step 1 + Step 4).** A depth-≥2 lens FLATTENS into an independent top-level session main holds no handle to → main can neither see nor `TaskStop` a flattened lens; the "confirm all terminated" barrier is best-effort agentId-reconciliation, NOT an enforced reap, and only the human's top-level UI (Clear) reaps it. The claim no longer over-promises a reap the board cannot perform; the end-of-run report now tells the user to Clear any lingering lens sessions (listing the launched lens agentIds).

Gate: build + 38 node tests + verify PASS. Resident SKILL.md +1350 ch (three behavior additions; say-it-once preserved).

## \[1.5.3] — 2026-07-02

**PATCH** — de-rot: the shipped text asserted a model's CURRENT availability as a standing fact ("Fable is access-gated → drop it", "Never assign an access-gated model (Fable)"), which rots as access shifts (Fable 5 unlocked 2026-07-02; its access model may shift again). The skill now never asserts any model's availability either way — availability is DISCOVERED at spawn time.

### Changed

* **Runtime-discovery wording (SKILL.md Step 1 · `references/lens-prompts.md` model-assignment · the `diversifyModels` config help):** `fable` joins the spawnable-alias enumeration (`haiku/sonnet/opus/fable`) with the honest note that alias availability itself SHIFTS (fable's access is episodic) and is discovered at SPAWN, never assumed from the shipped text — a spawn-fail on ANY alias falls down the tier list. The standing Fable ban is dropped; the GONE-classification rule is unchanged (spawn-fail → re-route immediately; never re-pick a model known-unavailable this run), with Fable kept only as a historical example ("Fable 5's 2026-06 access-gating"). No behavior change; the correlated-blind-spot / Knight-Leveson frame untouched.

Gate: build + 38 node tests + verify PASS.

## \[1.5.2] — 2026-07-01

**PATCH** — prototype-pollution guard on the config parser. The conductor's `parseJsonc` merged an untrusted PROJECT `.coalboard.json` into the config via `Object.assign` (`readCfg`), so a malicious cloned-repo config could inject settings through a `__proto__` key (e.g. inherit `coalboardMode:"off"` to silently suppress the board). The parse now drops `__proto__` / `constructor` / `prototype` (OWASP prototype-pollution; the series' `ecc` TypeScript security rule). Low severity (a fail-silent, short-lived hook process); fixed for defense-in-depth and consistency with CoalHearth's identical guard.

### Fixed

* **`parseJsonc` drops `__proto__` / `constructor` / `prototype`** via a `JSON.parse` reviver, so an untrusted project config cannot pollute the merged config's prototype through the `Object.assign` merge in `readCfg`. A hermetic regression test (a `{"__proto__":{"coalboardMode":"off"}}` project config) asserts the injected setting is NOT honored — the board contract still fires on SessionStart.

Gate: build + 38 node tests + verify PASS.

## \[1.5.1] — 2026-07-01

**PATCH** — structural LEAF enforcement (issue #2). On Claude Code at rigor `nasa`, a show-me lens **spawned its own background subagent**; the orphan grandchild was **unreapable by `main`** once the lens returned (\~27 min / \~213k tokens / 80 Bash uses, manual stop). The LEAF rule was prompt-only — nothing structurally stopped a spawn-capable lens. A second fix, surfaced while gating this release, hardens config resolution (the conductor no longer walks above the home dir) and closes a hermetic-test leak it caused.

### Fixed

* **LEAF is now STRUCTURAL, not prose (issue #2).** `SKILL.md` Step 1 + the depth-0 intro + `references/lens-prompts.md` mandate spawning every lens with an agent type that LACKS the spawn tool — Claude Code: the `Explore` agent type (no Agent/Task tool; keeps Read/Grep/Bash/web for show-me + adversary); Antigravity: `define_subagent(enable_subagent_tools=false)`. A grandchild a lens spawns is UNREAPABLE by `main` on CC once the lens returns (main holds no handle; `TaskStop`/`TaskList` find nothing) — so the fix PREVENTS the grandchild rather than relying on reaping it.
* **Backstop:** if a lens's returned text reports it spawned a subagent, `main` must surface + stop it immediately, before the judge step (a slipped grandchild is the one escape from the no-zombie guarantee).
* **Config resolution no longer escapes above the home dir (latent fix + test hermeticity).** `findProjectCfg` walked UP from cwd to the filesystem ROOT (skipping home but continuing ABOVE it) → it could read a `.coalboard.json` above the home dir, which is not "this project"; it now STOPS at home (Phoenix #10). This also closed a hermetic-test leak: the test sandbox sits under the real home (`os.tmpdir()`), so the walk-up was reading the real `~/.claude/.coalboard.json` (`{updateMode:off}`) and suppressing the self-update directive the 3 self-update tests assert — a real-machine-state leak that surfaced only once the real config gained `updateMode:off` (the tests passed at v1.5.0, before it existed). A new hermetic test asserts a config above the sandboxed home is ignored.

Gate: build + 37 node tests + verify PASS.

## \[1.5.0] — 2026-06-22

**MINOR** — Antigravity is now a VERIFIED supported platform for the board (the capability-hack gate flips AG from UNVERIFIED to VERIFIED — new cross-agent support). The board was validated end-to-end on AG (Claude Opus 4.6): 3 read-only-leaf lenses via `define_subagent` → `invoke_subagent` parallel, the decontamination clause clean on all lenses, reaped via `manage_subagents`.

### Added

* **`references/platform-antigravity.md`** — the Claude-Code→Antigravity tool-mapping + verified caveats: read-only is TOOL-level (the write/spawn tools are absent from the sub — stronger than CC's by-instruction); AG auto-injects `AGENTS.md` → the decontam clause is required + was confirmed clean; subs are zombies → `manage_subagents kill_all` mandatory; no model-pick → lenses inherit the parent model (cross-vendor = human-manual); \~16 concurrent, depth-10 nesting; no hooks → manual-invoke.

### Changed

* **SKILL.md capability-hack** now lists "Verified live: Claude Code + Antigravity" (with a pointer to the new reference). Every other platform stays designed-for / unverified.

Gate: build + 36 node tests + verify PASS.

## \[1.4.2] — 2026-06-21

**PATCH** — wizard / Step-0 consent CHANGE-path correctness. A user found that picking **change** at the cost checkpoint did not specify the recompute-and-re-consent loop — an implementer could spawn on a stale bill (a change to nasa multiplies cost, un-reconsented).

### Fixed

* **CHANGE recomputes the bill + re-consents (`bill → change → bill → pay`).** `references/wizard.md` (programmer Call 3 + the layman bill) and `SKILL.md` Step 0 now spell out: on `change`, loop back to re-pick → RECOMPUTE the bill → re-present the confirm as a FRESH box; never spawn on a stale bill, never fold the recompute into the change step. Spawn only on a confirm of the CURRENT bill.

Gate: build + 36 node tests + verify PASS.

## \[1.4.1] — 2026-06-21

**PATCH** — lens decontamination hardened (W1/W2/W3). A dogfood audit run from inside the dev tree caught two "blind" lenses citing the umbrella `MEMORY.md` (outside the target): on Claude Code a spawned lens auto-loads the project `CLAUDE.md → MEMORY.md/AGENTS.md` (the up-tree walk) + SessionStart hooks, defeating the R2-6 "no dev-governance" rule. No new capability — a correctness fix to existing decontamination.

### Fixed

* **Lens decontamination defeated on Claude Code (W1).** `references/lens-prompts.md` now carries an explicit "IGNORE auto-loaded governance" FIXED rule — a lens must never cite or be primed by an ancestor `CLAUDE.md`/`MEMORY.md`/`AGENTS.md` the platform injects, and re-grounds in the target's own files if it catches itself. `excludePaths` only kept governance out of the *scan*, never the platform-loaded *context*; this closes the lens side. Proven live: an out-of-frame solver spawned WITH the clause did not leak.
* **Neutral-cwd guidance + honest independence flag (W2/W3).** `SKILL.md` Step 1 + `references/audit.md` now direct main to spawn lenses from a NEUTRAL cwd when the target sits inside a governed tree, and to flag any inside-the-tree pass NOT independence-clean — main + the judge auto-load the governance too, so their dismissals are dev-informed, not out-of-frame.

Gate: build + 36 node tests + verify PASS.

## \[1.4.0] — 2026-06-21

**MINOR** — the manual `/coalboard` wizard brought to CM-parity (flow-correctness + max token-min), a non-Latin critical-prompt trigger, and a gateless-auto-apply guard. Wizard built via the same two-stage loop CM's gold-standard wizard got (correctness loop → token-min loop), each adversarially re-verified at the commit-gate.

### Added

* **Non-Latin critical-prompt trigger (CB-7).** The per-prompt conductor was English-keyword-only — a pure-Thai/CJK critical prompt (e.g. `แก้บั๊กการเข้ารหัส…`) produced zero reasons and the board stayed silent. `trigger.mjs` now emits a `script:non-Latin` reason (the grade-by-intent nudge) when a prompt carries a meaningful non-Latin run, so a non-English critical prompt still routes to the board's judge-by-intent. Off for file scans (non-Latin code comments don't false-fire). Phoenix-pure (zero-dep, fail-silent, deterministic).

### Fixed

* **Gateless auto-apply now mechanically guarded (CB-4).** `{coalboardMode:"auto", applyConsent:false}` (explicit, or inherited from `rigor:relaxed`) removed both human gates with only a prose rule stopping it. `verify.mjs` now rejects the combo loudly, and `applyRigor()` forces the apply-gate back on at runtime (on the *effective* value, covering the relaxed-inheritance path). The human gate is never config-able off under `auto`.
* **Manual-wizard flow-correctness (CB-W1).** The resident `SKILL.md` described the superseded pre-v1.3.0 "2-call, stale-cost" flow, contradicting the on-demand wizard's corrected 3-call order→bill→pay (bill computed AFTER the picks). Resident contract + wizard now agree; `SKILL.md` delegates the step detail to the wizard (lean resident body).
* **Cross-platform README claim scoped (CB-14).** The named-platform list (Cursor/Codex/Copilot/Amp/Goose) is now explicitly *design-supported, unverified* — every actuatable artifact (installer, hook, cost-tiering) is Claude-Code-specific; the debate structure is cross-agent by design but verified on Claude Code only.

### Changed

* **Wizard token-minimized \~33%** — the on-demand wizard squeezed to the leanest text passing all bars; every cut re-verified against the flow / no-double-ask / honesty / no-dup bars, and the box-counts, the bill-after-picks anti-drift rule, the layman honesty triad, and the dev-contamination exclude-floor were all rejected-from-cutting (= load-bearing, the maximality proof).

Gate: build + 36 node tests + verify PASS.

## \[1.3.3] — 2026-06-21

**PATCH** — board-audit round-2 fix (sub4-reproduced); bugfix only.

### Fixed

* **conductor `updateCheckDays` clamped at read (#3).** It was `Number.isInteger(v) ? v : 14` with NO upper/lower clamp — `{updateCheckDays:0}` (or negative) made the throttle window ≤0 → `now-last < 0` never true → the self-update nudge fired EVERY session. Now clamped to `[1,365]` (mirrors CoalTipple); out-of-bound → the 14-day default. + a regression test (two consecutive SessionStart with `{updateCheckDays:0}` → the 2nd is throttled). CoalBoard was the lone unguarded sibling (CT fixed in v1.0.20, CM guards in its conductor).

Gate: build + verify + tests PASS.

## \[1.3.2] — 2026-06-21

**PATCH** — board-audit fix (verify-triaged from the whole-Colliery nasa board); bugfix only.

### Fixed

* **`coalboardMode:off` no longer silences self-update.** The conductor returned early on `boardOff`, which also suppressed the SessionStart self-update check — but the board AND-gate (`coalboardMode`) and self-update (`updateMode`) are ORTHOGONAL (independent off-switches). Now the board's UserPromptSubmit path is skipped when off while the SessionStart self-update still fires per its own `updateMode`. (+ the companion test, which had encoded the bug.)

Gate: build + verify + 28 tests PASS.

## \[1.3.1] — 2026-06-21

**PATCH**: report-context sharpen + doc sibling-consistency; no change to the board flow or config.

### Changed

* **R3B-8 — the report carries the FULL sub4 picture in BOTH cases.** On a sub4-broken deadlock (resolved OR escalated), the report always carries the contested claim · each camp's position · sub4's verdict (which camp it matched + why) OR its inability (the 3-way split that escalated) — so at the apply gate the human can RECONSTRUCT sub4's judgment, never rubber-stamp it. The consent digest surfaces enough of it to BE the out-of-frame check (full detail in the report).
* **Docs sibling-consistency (#20):** README leads with the badge + links rows (the series pattern); SECURITY.md reordered to the family section order (Reporting → Signatures → Dist integrity → SkillSpector → Structural safety → board-specific) + a `version-transition` marker on the scan section; workflow action-version comments normalized to the major-only style (`# v7`, SHAs untouched). The shared public-doc pattern is now documented at `TheColliery/.github/DOC-PATTERN.md`.

Gate: build + verify + 28 tests PASS.

## \[1.3.0] — 2026-06-21

Round-3 deep dogfood (the user ran the real published wizard + board as a customer and surfaced flow / honesty gaps the build gate cannot). **MINOR**: a new layman-default UX path + a holistic wizard/gate flow rework + model-diversity honesty corrections; no new config keys.

### Added

* **Dual-audience wizard — a LAYMAN-DEFAULT path:** `/coalboard` now defaults to AI-handles-everything — smart safe defaults (cwd · auto-work-type · L2 · standard) + ONE plain-language bill+confirm ("3 reviewers + a judge check X for \~Y tokens — go / cheaper / more thorough / cancel"; no opaque jargon — "cheaper/more thorough" map to depth/rigor, a universally-known word like "nasa" may stay). A programmer opts into the full restaurant wizard ("advanced" / stating prefs). The layman is kept safe by staging + the human-apply gate (no rigor-knowledge needed); the result carries the honest ceiling in plain language and never says "definitely safe".

### Changed

* **Wizard = the "restaurant" flow order → bill → pay** (programmer path): TARGET → silent scan → the 3 settings (work-type/depth/rigor) → the ACCURATE bill computed FROM the picks → ONE confirm. Fixes the v1.2.1 stale-cost consent (the bill used to precede the picks). DISPATCH defaults all-at-once.
* **Step 4 exit re-ordered + leaned:** DIGEST → ONE consent question (apply-all / let-me-pick / report-only / stop; "which fixes" only on "let-me-pick") → THEN write, CONDITIONAL on the choice (stop = write NOTHING). The report file is never written before consent. (Was: write-then-"consent gate", two questions.)
* **Surfaced output = decisions + results only** — internal mechanics (reading the lens-prompt template, arming/cleaning the memory net, the contract steps) run SILENTLY, never narrated.
* **Model-diversity honesty:** `diversifyModels` is INERT on Claude Code (the spawn tool takes only aliases — it cannot pin a model generation, so "spread across generations" is a no-op; kept degrade-safe for a platform that can). The only actuatable model-decorrelation on CC is a tier-mix (partial, at a lens-strength cost); the real decorrelation is the diverse lens prompts + adversary + sub4, never the model.
* **NASA honesty (the correlated-blind-spot ceiling):** all-opus at nasa = MAX model-correlation at MAX stakes — the escape is the non-model ground-truth gates (`tier2Verify` — fuzz/property/differential) + the human, NOT model-diversity and NOT sub4 (sub4 is the same model → shares the blind spot; it breaks deadlocks only). The skill no longer implies "nasa is safe because diverse models".
* **Warm-resume corrected (verified live):** the standard CC session has no callable SendMessage tool, so a stopped/dead lens is recovered by re-spawning a FRESH lens on the un-done REMAINDER tracked in main's journal (re-does only the in-flight partial); SendMessage-resume is a bonus only where the tool exists. `TaskStop` is available (main reaps a runaway / zombie sub).

### Fixed

* The org `.github` repo had no `dependabot.yml` — its workflows' pinned actions never auto-bumped (the same `.github` skip-bias, one layer down); added it (github-actions, weekly), matching the plugin repos. (R3A-3)

Gate: build + verify + 28 tests PASS.

## \[1.2.1] — 2026-06-21

Wizard token-economy pass (dogfood: a sub RAN the manual `/coalboard` wizard while main watched, then trimmed the waste). **PATCH**: leaner manual-wizard UX, no new capability/config.

### Changed

* **Manual `/coalboard` wizard restructured to 2 question-box calls** (was up to 4 round-trips across 8 steps): Call 1 = TARGET → a silent enumerate-only scan (1-line summary) → Call 2 = WORK-TYPE + DEPTH + RIGOR + PROCEED (4 questions). 2 calls is the dependency FLOOR (TARGET → SCAN → scan-derived WORK-TYPE forces ≥2 round-trips). The cost-confirm folds into Call 2's PROCEED slot — a cost line precedes the call (informed consent); "change" recomputes the precise per-config estimate on demand.
* **DISPATCH is no longer a wizard question** — defaults to all-at-once (a speed-only choice, rarely changed; `maxConcurrentSubagents` caps it, ask only if the user raises it), freeing the slot for the confirm.
* **Terser throughout** — one-line option text, a single scan-summary line, no decorative filler. Every load-bearing ask is KEPT (target-first, ask-work-type, exclude ∪ the dev-contamination floor, `.github`/workflows security, units-from-scan, cost consent, the lens-prompt fill-flow). `references/wizard.md` prose \~−38%.

Gate: build + verify + 28 tests PASS.

## \[1.2.0] — 2026-06-21

Round-3 dogfood (the user ran the board as a customer again + reported each finding) — deterministic rigor-scaled model tiering, a wired scan-exclude config key, `.github`/workflows treated as a security unit, and platform warm-resume. **MINOR**: new config keys (`rigorLensTiers`, a now-functional `excludePaths`) + new deterministic behavior.

### Added

* **`rigorLensTiers` — a deterministic rigor→lens-tier map** (factory `relaxed/standard → haiku · high → sonnet · nasa → opus`). The lens model now SCALES with rigor and the agent READS the table verbatim, so the assignment is identical every run — fixing both the all-haiku-at-nasa under-powering and the run-to-run non-determinism ("deterministic" is now EARNED by the table, not printed over interpreted prose). The judge stays top-tier; the adversary always takes the rigor tier (≥ sonnet), never undetermined.
* **`excludePaths` is now a FUNCTIONAL scan/audit exclude** (was reserved/inert). The factory default unions the build/vcs dirs with the always-hard dev-contamination floor (`CLAUDE.md`/`MEMORY.md`/`AGENTS.md`/`.claude`/`.agents`); config ADDS to the floor, never weakens it (a lens must never read the dev governance). The scan READS it from config instead of a hardcoded prose list.

### Changed

* **Adopt CoalTipple's ranking pattern (OPTIONAL, series-interop):** if CT is installed, inherit its `ranking.json` (alias-floor authority + stable tier-structure + `modelTiers` pins + validity-lock + spawn-fail-fall); else the alias floor + `rigorLensTiers` suffice — CB stands alone. CB adds only the rigor→tier map + the adversary bump.
* **`.github` / workflows = a SECURITY unit, never "just CI"** — the scan classifies `workflows/*.yml` as CI-security; the lens checklist + audit reference now flag action SHA-pins, scanned/action version correctness, `pull_request_target` + untrusted checkout, `${{ github.event.* }}` injection, and over-broad `permissions`. Boarding-scope enumerates UNITS from the scan (`.github` counted as a first-class unit), never re-derives "the tools" (the recurring `.github` skip-bias).
* **Pre-spawn scan is ENUMERATE-ONLY** — classify by extension/path with NO content read (content-reading is the lens phase); avoids burning 200k+ tokens and bloating main's context before any lens runs.
* **Warm-resume PREFERS platform resume over re-spawn-fresh** — capture each lens's `agentId` and SendMessage-resume a dead/limit-hit lens (keeps its accumulated work) instead of re-spawning from scratch (which re-does the lost work). Trigger the resume on budget-RETURN (quota reset OR a user refill, whichever first), never a hardcoded clock; any scheduled resume is idempotent.
* **Judge narration** — the board states "judge running ground-truth to settle the conflict, not acting as a lens" so a watcher is not alarmed when main works post-collapse; after a budget-collapse to an inline judge, the dead lens's domain is flagged NOT-CHECKED, never inline-generated.

### Fixed

* The CT/CB issue-template version placeholders were stale (`v1.0.0`) and ungated → replaced with a number-free `vX.Y.Z` format hint that cannot rot.

Gate: build + verify + 28 tests PASS.

## \[1.1.0] — 2026-06-21

Round-2 dogfood (the user ran the board as a customer + reported each finding) — the manual `/coalboard` wizard + lens-spawning hardened. **MINOR**: new capability (a canonical lens-prompt template + the wizard's target-first / ask-work-type flow + manual-board memory arming + deterministic model assignment + deadlock handling).

### Added

* **`references/lens-prompts.md` — the canonical lens-prompt TEMPLATE.** main fills `{target}` / `{scope}` / `{work-type-checklist}` / `{version}` placeholders, NEVER free-writes a prompt. Fixed rules: ground every lens in the TARGET's OWN files + FORBID injecting loaded dev-governance (the independence break the board exists to avoid); "seeds, not exhaustive" on EVERY lens (not just show-me/adversary); calibrated + FALSIFIER + NOT-CHECKED honesty; the deterministic model rule.

### Changed

* **Wizard (`references/wizard.md`) rewritten to the authoritative 8 steps:** Step 1 TARGET is asked FIRST, before any scan (no more "cheap-scan cwd before asking") — offers cwd / the clean mirror / a subproject / path / diff. WORK-TYPE is ASKED (never auto-set) and is the ONLY scope-narrowing. DISPATCH is a speed choice (BLIND = the independence; parallel just faster; one-at-a-time equally independent). The fill-flow (wizard values → template) is explicit.
* **Lenses NEVER sub-divide the scope** by file-type / name / category / work-kind (SKILL.md Step 1 sharpened) — all lenses examine all in-scope files together.
* **Model assignment is DETERMINISTIC + identical across every unit** (cost-bonus haiku-lenses + opus judge; `diversifyModels` spreads GENERATIONS not TIERS; Fable excluded) — never an arbitrary per-unit spread.
* **Memory & resume:** ARM whenever a board is convened (manual = the full per-agent net; auto = CoalHearth-light, degrade-safe) — supersedes the "long-runs-only" gate; per-agent private + cross-read-forbidden; a sub self-resumes (or main re-spawns the remainder on a read-only platform); **EPHEMERAL** — `.coalboard/memory/` is deleted on completion (Phoenix #1 zero-garbage).
* **Deadlock (sub4):** a sub4-resolved tie is USED directly (no extra blocking gate — a layman can't adjudicate a deep deadlock) but FLAGGED lower-confidence at the apply gate; the full deadlock detail goes in the report; a 3-way split still escalates to the human.

### Fixed

* README status was stale (`v1.0.12`) vs `plugin.json`.

Gate: build + verify (the new reference is in the SHIP list) + 27 tests PASS.

## \[1.0.13] — 2026-06-20

The load-path **carve** — the series token-economy pilot ([skill-authoring](https://github.com/TheColliery/.github) §4): the SKILL.md body is now the LEAN always-loaded core (auto-trigger AND manual both pay only this); the heavy manual-only / deep detail moved to `references/*.md`, loaded ON-DEMAND. **Core body −40% (19433 ← 32372 chars)** — a board that auto-convenes no longer pays the manual wizard's tokens. Plus the Phase-3 robustness items folded in. (CB is the pilot; the carve rolls to CoalTipple + CoalMine next.)

### Added

* **`references/wizard.md`** — the manual `/coalboard` 8-step setup wizard (target · scan/classify · work-type · depth · rigor · dispatch · cost-confirm · result + interlinked boarding-scope), loaded only on a manual convene.
* **`references/audit.md`** — the deep repo/release audit detail (every-file enumeration · `.github` inclusion · scope-containment · interlinked-whole boarding · no-stale-prior-audit · scope-aware report-location), loaded only on an audit run.
* **Platform/version gate** — CoalBoard is a capability-hack tightly coupled to the exact platform + version (docs ≠ reality); on any platform/version not actually run, it now announces UNVERIFIED, degrades conservatively, and surfaces the risk to the human before spending.
* **Durable per-agent memory & warm-resume — LONG runs only** — for a deep/L3 or whole-interlinked-repo run that can overflow: per-agent PRIVATE `.coalboard/memory/<agent>.md` (cross-reading forbidden), incremental checkpoint, warm-resume on overflow/compaction/503 (a sub resumes from pending, never restart). A short single board skips it (no-overkill).

### Changed

* **SKILL.md carved to a lean core** — every behavior preserved; manual/deep detail relocated to `references/` (loaded on demand, not resident every convene).
* **Lens-failure recovery hardened** — a tripped lens is re-spawned on an available model or resumed from pending; main NEVER does a lens's own work (that collapses the decorrelation that IS the board's value); never restart-from-scratch.
* **Never assign an unavailable model** — a spawn-failure / 0-token "Completed" (COMPLETED ≠ ANSWERED) is classified + re-routed; an access-gated model (Fable) is dropped from the pool, never re-picked.
* **Tool-error fail-fast** — a lens hitting an unavailable tool returns immediately (no workaround loops burning tokens); lens contracts are kept tool-agnostic.

### Fixed

* **`/coalboard` "Unknown command" — the redundant `commands/coalboard.md` removed.** It and the skill both claimed `/coalboard:coalboard` (a command and a same-named skill are merged by the platform); the skill is now the single entry — auto via its description, manual via `/coalboard:coalboard` (which reads the wizard). `/coalboard:update` is unaffected.

Gate: build + verify (9/9, incl. dist-sync of the two new references) + 27 tests PASS.

## \[1.0.12] — 2026-06-20

The session-end rot-canary caught two LOW issues in the v1.0.11 scrubber additions (the scrubber's own rot).

### Fixed

* **scrubber over-length leak (same class as the google-key fix):** the v1.0.11 npm / GitLab / SendGrid / Square patterns used exact `{N}` + trailing `\b`, so an OVER-LENGTH token-shaped run fails the boundary and leaks the WHOLE token. Switched to `{N,}` (every pre-existing pattern already used it). +test.
* **AccountKey re-casing:** the Azure pattern hardcoded `AccountKey=` in the replacement, so a lowercase `accountkey=` was re-cased in the output. Now captures the key (`$1[REDACTED]`) to preserve the original casing. +test.

(Both LOW, in the DEV-only `secrets.mjs`; caught by rot-canary at session end, not the gate.) Gate: build + verify PASS + 25 tests.

## \[1.0.11] — 2026-06-20

Consolidated pass — executes the board's v1.0.9 re-audit findings (top open = scrubber gaps + SECURITY scan honesty) as ONE release, plus the report-location scope fix. No catastrophic findings; core safety held throughout.

### Fixed

* **secret-scrubber (`secrets.mjs`) format gaps:** added npm (`npm_`), GitLab (`glpat-`), SendGrid (`SG.`), Square (`sq0atp-`), and Azure storage `AccountKey=`; the Google-key pattern is now open-ended (`{35,}`) so an over-length run is FULLY redacted (was `{35}` → leaked the tail); the PEM block match is case-insensitive. (This is the DEV reference scrubber — defense-in-depth, not shipped in the runtime.)
* **`diversifyModels` example still named "Fable 5" in `config-schema.mjs`** — v1.0.5 dropped it from the SKILL + factory but MISSED the schema SOT → generalized to "available generations".
* **conductor over-fired on non-SessionStart events:** the SessionStart branch now gates on `event === 'SessionStart'`; any other non-prompt event stays silent (Phoenix #13). +test.
* **SECURITY.md scan note clarified:** separates the actual scanned version (v1.0.1), the re-scan policy, and an explicit "later versions not re-scanned" honest scope (no all-versions guarantee).
* **`excludePaths` schema help marked RESERVED** (it feeds only the optional, unwired PreToolUse backstop — the factory already said so; the schema text now matches).
* **CONTRIBUTING.md** headers de-emojified (series doc-standard).

### Changed

* **Report location is now scope-aware:** the report goes INTO the part that was scanned (`X/.coalboard/reports/` for subproject X), never a parent / umbrella / catch-all root — scope decides location.

Tests: 24 (+scrubber formats, +conductor over-fire guard).

## \[1.0.10] — 2026-06-20

Dogfood — the apply decision was asked TWICE: the Step-0 convene gate surfaced "report-only", and the Step-4 post-audit gate asked "fix what?" again. Deciding what to fix before any findings exist is premature; report-only belongs after the findings.

### Fixed

* **The apply / fix / report-only decision is now a SINGLE gate at Step 4 (after findings).** Step 0 decides convene + config + cost + report-LOCATION (source vs clone) only — it no longer asks the apply mode (no findings yet → premature + duplicated). If the user pre-declares report-only (config / at convene), Step 4 honors it without re-asking.

Deferred: sub4 per-rigor doc · heading-order check · language re-flag · CONTRIBUTING emoji-strip.

## \[1.0.9] — 2026-06-20

Dogfood — two issues: the SKILL leaned on CoalTipple as if always present (CoalBoard is standalone — a user may install only it), and a run announced "Step 1" twice (the pre-spawn prep, then the actual spawn).

### Fixed

* **CoalTipple is now explicitly OPTIONAL (no-external-assumption).** The board tiers models on the Claude Code alias floor (`haiku < sonnet < opus`) it knows directly, plus its own introspection + `lensTiers`; if CoalTipple is ALSO installed, its richer availability ranking is inherited as a BONUS. CoalBoard installed alone no longer reads as broken. (Fixed in Tiers, Bounds, and the grade-rubric reference.)
* **Step 1 is announced ONCE.** The target enumeration (file-list for the scope count) + the report's version/commit/timestamp stamp are the Step-0 pre-flight (the checkpoint), NOT a second "Step 1"; Step 1 is purely the spawn.

Deferred: sub4 per-rigor doc · heading-order check · language re-flag · CONTRIBUTING emoji-strip.

## \[1.0.8] — 2026-06-20

Dogfood — the per-lens model display regressed: a run condensed it to a bare slash-list ("haiku/sonnet/sonnet/opus") in the narration, and the spawn chips showed only "empirical lens" etc., so you could not tell which model each lens was running on.

### Changed

* **Each lens's model is now mandated VISIBLE on its spawn.** Lead the worker's label / description with the model (+ effort) — `[haiku] empirical lens`, `[opus] adversary` — so the platform chip shows it (the chip shows the description, not the model). The per-lens → model mapping must be explicit and named, never a bare slash-list divorced from the lens names. (Mirrors the CoalTipple spawn-label rule.)

Deferred: sub4 per-rigor doc · heading-order check · language re-flag · CONTRIBUTING emoji-strip.

## \[1.0.7] — 2026-06-20

Dogfood — the 2nd cost-gate reads like a pre-flight checkpoint but showed only the CONFIG (workers, models, gates, cost), never the TARGET. Added it.

### Changed

* **The cost-gate is now a TARGET + config + cost checkpoint.** Before spawning, it shows WHAT is under audit — repo/path, version read from the target's own `plugin.json` (not memory), source-repo vs transient install-clone (decides where the report lands), and any stale prior audit found there — alongside the final config and recomputed estimate. A wrong target (the clone not the source, a stale version) is caught AT the checkpoint, before the spend.
* **Prior-audit handling:** a prior audit found in the target is de-dup context ONLY — re-verify every prior finding against the CURRENT source, version-check it (a report older than the target's version is suspect), never inherit its verdict; a root-level audit not under `reports/` is a pre-`reports/` leftover.

Deferred: sub4 per-rigor doc · heading-order check · language re-flag · CONTRIBUTING emoji-strip.

## \[1.0.6] — 2026-06-20

Continued dogfood — two errors found by READING the shipped artifacts (the report save-path, the README install block), both the class "the board audited SOUND but missed its own inconsistency". Fixed + sharpened so the board catches the class.

### Fixed

* **Staging-path inconsistency — the report location was undefined.** Docs said `.coalboard/proposed/` everywhere, but reports actually landed in `.coalboard/` root with no rule. Now explicit: `proposed/` holds ONLY changes-to-apply; the human-readable REPORT (audit / post-mortem) goes to `.coalboard/reports/<name>-<timestamp>.md` (a sibling of `proposed/`, never inside it). Synced across SKILL (Step 4.1 / 4.6 / audit-independence), PRIVACY, README.
* **README install was Claude-Code-only under a cross-agent claim.** The Install section now splits **Claude Code** (one command; hook + cost-tiering are CC-only) vs **other platforms** (point the agent at the platform-neutral `skills/coalboard/SKILL.md`) and states plainly that cross-agent operation is by design but VERIFIED on Claude Code only.

### Changed (audit sharpness)

* **Claim-vs-docs mismatch is now an explicit audit finding:** docs that CLAIM "cross-platform / runs anywhere / works on X" while the install, examples, or config cover only ONE platform = a defect; the docs must document the others or scope the claim. (CoalBoard's own README was exactly this.)

Deferred: sub4 per-rigor doc · heading-order check · language re-flag · CONTRIBUTING emoji-strip.

## \[1.0.5] — 2026-06-20

Round-4 self-audit (nasa, whole repo; verdict SOUND, 0 code defect) + queued dogfood points. Headline catches: a dead lens marked "Completed" (0 tokens — model unavailable) was silently counted as a voice; and the cross-platform "works everywhere" claim is a correlated blind spot a single-platform board cannot see.

### Fixed

* **doc-vs-code residuals:** PRIVACY.md scrub claim softened (`secrets.mjs` is dev-only, not shipped) + config path corrected to `.claude/.coalboard.json`; `commands/coalboard.md` "sandboxed" → "staged + contract-isolated"; factory rigor comments aligned to `rigor.mjs` (no phantom "failed verify" trigger; relaxed = `active:false`).
* **conductor read project config via raw `process.cwd()`** → now walks UP from cwd to the nearest `.claude/.coalboard.json` (Phoenix #10 — a hook cwd may be a subdir; per-project config no longer silently ignored).
* **`diversifyModels` no longer names "Fable 5"** (proven access-gated / unavailable) — spread across spawnable generations only; example + CHANGELOG softened.
* **SECURITY SkillSpector framing** → re-scan is event-driven on a NEW SkillSpector version (the static rules are stable), not a per-CoalBoard-release "pending".

### Changed (safety + sharpness)

* **Dead-lens detection:** a lens that returns EMPTY / 0-tokens / "Completed in \~0s" is a FAILURE, not an empty vote (COMPLETED ≠ ANSWERED) — re-route, never silently count it (a phantom lens skews consensus + decorrelation).
* **Consent — per-instance override is ephemeral** (never written to config) + a **2nd cost-gate**: recompute the token estimate for the FINAL (post-override) config and re-confirm BEFORE spawning the first worker (an override to nasa multiplies the cost).
* **Cross-platform honesty:** cross-agent BY DESIGN, VERIFIED on Claude Code only; never state "works on X" unverified. Auditing CoalBoard itself, that is an unverified self-claim sub1/sub3 must challenge (single-platform blind spot; the human on another platform is the out-of-frame check).
* **Audit lens-angle:** sub1 follows the work's OWN links/citations + reads facts from the TARGET files, never from loaded MEMORY.
* **Audit-independence + report:** run a self-audit from a neutral context; write the report to a PERMANENT path (not the transient install clone) with a TIMESTAMPED unique filename (no same-day collision).

Deferred: sub4 per-rigor doc table · objective heading-order check · language re-flag · CONTRIBUTING emoji-strip.

## \[1.0.4] — 2026-06-20

Round-3 self-audit (nasa, whole repo) — the board again caught a bug its green gate + 20 tests missed (the non-English nudge false-firing on emoji) and REFUTED its own "Fable 5 is fictional" lens by ground truth (the re-route failure proved the model is real but access-gated — known ≠ usable; data + feeling shared a training-cutoff blind spot — the Knight-Leveson case the README itself describes).

### Fixed

* **conductor `hasNonLatin` false-fired on emoji** (and any supplementary / symbol char) → the "non-English" nudge fired on plain English. Rewritten to flag only a non-LATIN LETTER (Unicode property escapes; strips Latin + every non-letter incl emoji + C0). Regression test added.
* **`updateDue` reported "due" every session when `~/.claude` was absent** — the stamp write failed silently. Now `mkdirSync` first.
* **`lenses: []` passed the schema → empty board.** A value-constrained `strArr` (lenses) must now be non-empty. Test added.
* **docs vs code (SECURITY.md / README):** "verify is sandboxed" → **contract-isolated, NOT OS-sandboxed**; "secrets scrubbed (`secrets.mjs`)" clarified — `secrets.mjs` is a DEV reference file, not shipped runtime; both guarantees labeled contract-enforced, not OS-enforced.
* **`excludePaths` documented as RESERVED** — it feeds only the optional PreToolUse backstop, which the default `hooks.json` does not wire.
* **`verify.mjs` now cross-checks** that the `plugin.json` version has a matching CHANGELOG entry (a doc-transition gate).

### Changed

* **Worker spawn-failure is now CLASSIFIED:** a GONE / unavailable model → re-route immediately; a rate-limit / quota → wait for the reset then retry (bounded), else re-route — never let a lens silently die, never re-pick a known-unavailable model (the Fable-5 lesson). diversifyModels falls back to an available generation.
* **Cross-key safety rule:** `applyConsent:false` under `coalboardMode:auto` (no human gate) is refused; `rigor:relaxed` wins over auto for auto-engagement.
* **Step 4 wording:** run reviewed checks from the staging / temp dir; the fail-escape climbs ONCE.
* factory config comment clarity (`excludePaths` scope; `criticalImports` AND-in-the-backstop vs OR-in-a-prompt).

Honest: SkillSpector re-scan still pending (v1.0.1 baseline). 4 README claims (a model name, two links, the install syntax, the org-landing version) flagged unverified — need a live fetch.

## \[1.0.3] — 2026-06-19

Round-2 self-audit (the board on its own repo) + the queued dogfood points. The board surfaced a real **HIGH** bug its own green gate + 19 tests missed — three lenses disagreed on the direction, the judge RAN it to adjudicate (and refuted a lens that mis-claimed the opposite).

### Fixed

* **conductor `hasNonLatin` false-fired on C0 control chars** — `\n`/`\t`/`\r` sit below the excluded U+0020, so a multi-line ENGLISH prompt wrongly received the "non-English" nudge (eroding the signal for genuinely non-English critical tasks). Now strips C0 controls before the test (a code-point filter — no control-char literal, no NUL byte). Regression test added.
* **CONTRIBUTING** listed a removed `eval/` dir → now points at the org benchmarks (clean-clone).
* **SECURITY** told users the `node --test <glob>` form the repo's own AGENTS.md records as broken on Node 24 → `node scripts/test.mjs` (the canonical runner).
* **CI** (`ci.yml` / `codeql.yml`) blanket-ignored `**.md`, so a change to the shipped `SKILL.md` skipped CI and its `plugin/` dist-sync check → whitelist only the NON-shipped root docs.
* **config-schema** `lenses` had no value constraint → a typo'd lens name passed verify and reached runtime; now enum-checked (`data`/`truth`/`feeling`) via a value-constrained `strArr`.
* **factory config comment** overstated `relaxed` ("board off") and "locks nothing" → clarified (`coalboardMode:"off"` silences the conductor; `active`/`allDomainGates` are preset-semantic, not config keys).

### Changed

* **Convene consent now offers a per-run OVERRIDE via the question-box** — the `rigor` master dial (+ Audit scope) — tune a run without editing a config file; fine keys stay in `.coalboard.json`.
* **CC cost bonus is now the DEFAULT, not a maybe** — `lensTiers` unset → cheap lenses (haiku) + premium judge (opus), not all-mid (sonnet).
* **Audit scope = "every file, any extension"** — enumerate the actual files, not a fragile type list that silently misses `LICENSE` / dotfiles.
* **Each lens keeps its OWN angle in Audit** — sub1 grounds claims in LIVE sources (not just reading files); never flatten the lenses to a generic "audit every surface".
* **sub3 (feeling) is OPEN-ENDED** — the example feelings are SEEDS, not a closed menu; surface ANY feeling (incl OVERKILL / YAGNI), then make it concrete (uncertainty generates candidates; routing + verify resolve them).
* **README** clarifies the manual command is `/coalboard:coalboard` (plugin-namespaced) or the "convene the board" chat phrase — bare `/coalboard` is not a registered slash command.

## \[1.0.2] — 2026-06-19

Self-audit (the board at nasa rigor, deepest scope) + the user's dogfood findings — all fixed and gated. The board found real bugs in its own repo (dogfood working); the judge caught one lens fabricating a finding (suspect-input-verified).

### Fixed

* **`rigor` preset is no longer silently neutered by the factory template** — the shipped `platform-configs/.coalboard.json` now leaves the rigor-controlled keys COMMENTED OUT, so copying it and setting `rigor:nasa` actually enables the levers (it previously forced them all back to standard — a user believed they had max paranoia on a crypto migration while running standard). Regression test added.
* **Secret scrubber** now catches the JSON `"key": "value"` form (the dominant diff/config format) and a multi-word unquoted passphrase (it leaked after word 1); `hasSecret` no longer reports a JSON secret as clean. Tests added.
* **`verify.mjs`** walks the whole `plugin/` tree (both-direction / dist-orphan check, per `scripts-quality.md §1`) — a new shipped file can no longer ship unverified.
* **README** version drift corrected to match the manifest.

### Changed

* **sub3 (`feeling`) is now a FULL-SPECTRUM skeptic** — every form of gut-feeling, not only correctness demands: design/taste intuition too (OVERKILL / over-engineered / redundant / too-clever / YAGNI / smells-wrong), each routed to a verifier; an unprovable feeling is a flagged gap, never dropped.
* **Audit mode is general** (not fit to one repo layout) — internal-consistency / live-findings / showcase-completeness checks are conditional on the project HAVING those surfaces; no assumption of sibling repos or a specific platform.
* **Language** directive moved up-front and made explicit for EVERY user-facing surface (the consent box, the running narration, the synthesis) — not a footnote.
* **same-target** marked INTERNAL board mechanics — never surfaced to the user as a choosable option; the user picks BREADTH (which files / how deep), never how the lenses divide.
* A deepest / "Everything" audit scope now covers all file types (docs / config / prose) with the same rigor as code.
* `SECURITY.md` SkillSpector provenance clarified (self-reported version, pinned by commit).

## \[1.0.1] — 2026-06-19

### Added

* **Audit mode — repo/release readiness.** Auditing a repo or a release (not just a diff) now also checks **cross-sibling parity** (CI workflows · security toggles like secret-scanning/push-protection/Dependabot · hygiene files), **live findings** (open code-scanning / Dependabot / Scorecard / secret-scanning alerts — triage each, never leave one silently open; query them, don't assume), and **showcase completeness** (the org landing / README / benchmarks index). Plus a **scope-honesty rule**: "NO ERROR FOUND" states *what* was audited — an un-audited dimension is "not checked", never "clean". Closes a real blind spot: a skill can be correct while its repo/org is not.

## \[1.0.0] — 2026-06-19

First **stable** release — the board's contract, config, and honest frame are settled, and it is benchmarked on two platforms.

### Added

* **Max-sharpness levers** (rigor-gated; bounded-cost preserved): `adversaryLens` (a red-team falsification lens), `tier2Verify` (property / fuzz / differential / metamorphic / mutation ground-truth gates) with `fuzzTimeboxSeconds`, `formalCommand` (optional TLA+/Alloy/SPARK), `contestedRound` (one surgical contested-point cross-exam on deadlock), and `diversifyModels` (spread lenses across model generations). The `rigor` preset tiers them (standard off · high/nasa on). Plus always-on judge discipline: calibrated lens output, a domain failure-taxonomy checklist, a disconfirmation + pre-mortem judge, a completeness critic, and honest-ceiling routing.
* **Factory config** (`platform-configs/.coalboard.json`): a fully-commented template for every key; `verify.mjs` validates it against the schema.
* **Benchmark** (`eval/`): with-the-board vs without on both Claude Code (reliability) and Antigravity (cross-vendor), with an honest method + per-task scoring.
* **SkillSpector scan** recorded in `SECURITY.md` (every finding verified false-positive).

### Changed

* **Scope reframed** — a general diverse-lens consensus board: the error-not-allowed slice is the primary **auto-trigger** (cost-disciplined, \~90% asleep); a **manual `/coalboard`** convenes on any hard problem worth several diverse perspectives.
* **No-zombie hardening** — each lens is collected then explicitly released; a returned-but-still-running worker is a zombie and is stopped, and the judge confirms none is alive before proceeding (a worker legitimately awaiting a permission is not reaped).
* Dropped the "governance layer" self-label.

## \[0.1.0-beta.1] — 2026-06-19

First public **beta** of the consensus & debate board.

### Added

* **The board** (`skills/coalboard/SKILL.md`): on an error-not-allowed task, with consent, convene three parallel blind epistemic lenses (empirical/source-grounded, formal, show-me skeptic) → a judge that synthesizes on VERIFIED inputs → an independent out-of-frame solver that breaks deadlocks blind → staging → human sign-off. Generate and Audit modes.
* **Phoenix-13 conductor** (`hooks/coalboard-conductor.js`): SessionStart contract + a UserPromptSubmit AND-gate Layer-1 static scan that injects a halt-and-consent directive on a critical signal; self-update scheduling (kind-1). Fail-silent, zero-dependency, no network, no spawn.
* **Config** (`scripts/lib/config-schema.mjs`): a 24-key source-of-truth with a `rigor` preset dial (`relaxed|standard|high|nasa`), validators, and series-standard self-update keys.
* **Core libs**: `trigger.mjs` (AND-gate detection), `rigor.mjs` (preset → bundle), `secrets.mjs` (credential scrubber).
* **Commands**: `/coalboard` (manual convene) and `/coalboard:update` (self-update).
* **Build + gate**: `build-plugin.mjs` (clean `plugin/` dist), `verify.mjs`, and 13 tests (6 hermetic conductor + 7 lib unit).

### Honest frame

* Guarantees **bounded cost** + **zero-breakage**; improves correctness without claiming a defect/reliability number. NASA-inspired in structure, not in numbers (`0.01/KLOC` and `10⁻⁹` are dropped — unverifiable by any LLM). Model-resilient: it scales with the underlying model.

### Known (pre-1.0)

* No independent SkillSpector scan yet (structural assurance only — see `SECURITY.md`). Cross-platform parallel support is verified as of 2026-06 but churns; re-verify per platform.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.thecolliery.org/tools/coalboard/changelog.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
