From 05d1a9aadafea5dad5473d73ab1c7f9d60e6fc69 Mon Sep 17 00:00:00 2001 From: tegwick Date: Sat, 8 Aug 2026 15:51:52 +0200 Subject: [PATCH] ADR-0021: the chaos roll is retired, and the condition that retired it was wrong MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The condition is met and the tally was verified rather than recalled. ADR-0017 D2 named window 3 as the decider; windows 2 and 3 each produced exactly one override and each changed nothing. Every roll in window 3 was cross-checked against the workplan that made it, because the last time this project tallied chaos rolls from memory it was wrong and asserted the wrong figure four times (F23). All twelve agree. But meeting the condition is not evidence. P(an override changes nothing) is 1/3, and each window had exactly one override, so the condition fires on a 1/9 coincidence. ADR-0017 restated it to be REACHABLE and made it weak in the process; reachability was checked and discriminating power was not. Worse, it measures the wrong subject. It asks whether an override changed the tier; the question is whether changing the tier helped. Under it, a die that always changed the tier could never be retired however useless its changes were. The real ground is stronger. Four overrides across roughly forty declarations, and the mechanism's value has never once been demonstrated. The one substantive intervention dropped CB-WP-0011 from a structural L to S, and that work then needed CB-WP-0016 and CB-WP-0017 to fix defects a human found by playing. Not offered as causation — a tier is process weight, not a guarantee — but it is the only evidence we have about an override's consequences and it points the wrong way. And the purpose has no live evidence of need: 17 M, 11 S, 4 L across every workplan, with the only two structural/declared mismatches being the window-1 overrides themselves. Tier declaration has not ossified. So: retired, with nothing replacing it. Adding a successor to guard against ossification that has not occurred would invent a gate for an instance we do not have. The revival trigger is stated: tiers collapsing toward one value, or a pass declaring below its structural tier to dodge a review. InnerLoop loses the chaos paragraph, loop-lint loses the chaos-recorded check — a check that outlives its rule becomes an obstruction — and its self-test now asserts the opposite: a note with no roll must pass. ChaosRollHistory is closed. Window 4 ends incomplete at four declarations, the last of which is this one, rolled because the rule was still in force. Co-Authored-By: Claude Opus 5 --- .../ADR-0021-the-chaos-roll-is-retired.md | 127 ++++++++++++++++++ specs/ChaosRollHistory.md | 28 +++- specs/InnerLoop.md | 39 ++---- tools/loop-lint.py | 35 ++--- 4 files changed, 183 insertions(+), 46 deletions(-) create mode 100644 decisions/ADR-0021-the-chaos-roll-is-retired.md diff --git a/decisions/ADR-0021-the-chaos-roll-is-retired.md b/decisions/ADR-0021-the-chaos-roll-is-retired.md new file mode 100644 index 0000000..738a7dd --- /dev/null +++ b/decisions/ADR-0021-the-chaos-roll-is-retired.md @@ -0,0 +1,127 @@ +# ADR-0021: the chaos roll is retired — and the condition that retired it was wrong + +status: accepted +date: 2026-08-08 +decided by: agent, under the standing loop authorization +tier: M (changes how the loop constrains its own operation — ADR-0006 D5). +chaos d8 = 7 → no override. **Declaration 4 of chaos window 4, and the +last chaos roll this project will make.** +references: [ADR-0006](ADR-0006-the-inner-loop.md), +[ADR-0017](ADR-0017-chaos-window-2-verdict.md), +[`specs/ChaosRollHistory.md`](../specs/ChaosRollHistory.md), +[`specs/InnerLoop.md`](../specs/InnerLoop.md) §Loop tiers + +## D1 — the condition is met, and the tally was verified rather than recalled + +ADR-0017 D2: *retire if a full window's overrides all change nothing, and +the window contains at least one override* — met in **two consecutive +windows**. D2a: *"Window 2 counts as the first. Window 3 decides."* + +| window | declarations | overrides | all changed nothing? | +|---|---:|---:|---| +| 2 | 12 | 1 (CB-WP-0020, d8=8, drew S over structural S) | **yes** | +| 3 | 12 | 1 (CB-WP-0038, d8=8, drew L over structural L) | **yes** | + +**Two consecutive. The condition is met.** + +**Every roll was cross-checked against the workplan that made it**, not +read off the history table — because the last time this project tallied +chaos rolls from memory it was wrong, and asserted the wrong figure four +times (F23). All twelve of window 3 agree. + +## D2 — and meeting it is not evidence + +**P(an override changes nothing) = 1/3.** Each of windows 2 and 3 had +**exactly one** override. So: + +> **P(the condition fires | one override per window) = 1/3 × 1/3 = 1/9.** + +**An 11% coincidence is not a finding.** The condition, restated in +ADR-0017 D2 to be *reachable*, became reachable by becoming weak — and +that trade was made without computing this number. Reachability was +checked; discriminating power was not. + +## D3 — worse: it measures the wrong subject + +**The condition asks whether an override changed the tier. The question +is whether changing the tier helped.** + +Those are different subjects, which is the family +[`Taxonomy.md`](../specs/Taxonomy.md) §2.2 exists to name. An override that +changes nothing tells you the die landed on the tier you already had; it +says nothing about whether the mechanism is worth running. Under this +condition a die that *always* changed the tier — maximally active — could +never be retired, however useless its changes were. + +## D4 — the real ground for retirement, which is stronger + +Four overrides across roughly forty declarations: + +| override | effect | benefit | +|---|---|---| +| CB-WP-0011 | structural **L → declared S** | **none recorded** | +| CB-WP-0017 | structural **S → declared M** | none recorded | +| CB-WP-0020 | drew S over structural S | changed nothing | +| CB-WP-0038 | drew L over structural L | changed nothing | + +> **The mechanism has acted four times in the project's life and its value +> has never once been demonstrated.** + +**And the one substantive intervention is not encouraging.** CB-WP-0011 +was the inspectable-table pass; the roll dropped a structural **L** to +**S**, and the resulting work needed **two follow-up passes** — CB-WP-0016 +(the drag was broken) and CB-WP-0017 (the page was wrong about which moves +exist) — to fix defects a human found by playing. + +**That is not proof of causation and is not offered as such.** A tier is +process weight, not a guarantee, and both defects were the kind INTENT +says only human verification finds. **But it is the only evidence we have +about an override's consequences, and it points the wrong way.** + +## D5 — the purpose has no live evidence of need + +The roll exists so tier declaration cannot ossify. Measured across every +workplan: + +**17 M · 11 S · 4 L** + +**That is a real spread**, and only two passes in the project's history +declared a tier different from their structural one — **both of them the +window-1 overrides.** Left to itself, tier declaration has varied on the +structure, which is what it is supposed to do. + +## D6 — retired, and what replaces it + +**The chaos roll is retired.** No roll, no window, no `chaos` line in a +workplan's tier block. Existing records keep theirs — they are history. + +**Nothing replaces it.** Adding a successor mechanism to guard against +ossification that has not occurred would be inventing a gate for an +instance we do not have (GameDesign §2). + +**What stays** is the structural derivation and its trigger table, which +is the part that was doing the work. + +**The trigger for revisiting**: if declared tiers collapse toward a single +value, or if a pass is found to have declared a tier below its structural +one to avoid a review, ossification is real and this decision was wrong. + +## Consequences + +- `InnerLoop.md` §Loop tiers loses the chaos paragraph; `loop-lint`'s + `chaos-recorded` check must go with it, or it fails every future + workplan. +- `ChaosRollHistory.md` becomes a **closed log**, and says so. +- **Window 4 ends at four declarations**, incomplete, which is correct: the + condition was met at the end of window 3 and waiting out a window to + satisfy a symmetry nobody requires would be ceremony. + +## What was rejected + +| rejected | why | +|---|---| +| retire on the strength of the condition | it fires on a 1/9 coincidence and measures the wrong subject (D2, D3) | +| keep the roll because the condition is weak | the condition being weak is not an argument for the mechanism; D4 and D5 are, and they point at retirement | +| keep it as a diagnostic, unrolled | a roll nobody acts on is a ritual with a log file | +| replace it with a value-measuring condition | it would need overrides to measure, and retirement stops producing them. **The honest sequence is retire, then watch D6's trigger** | +| wait for window 4 to close | ceremony; the condition was met at the end of window 3 | diff --git a/specs/ChaosRollHistory.md b/specs/ChaosRollHistory.md index 7a08f28..df5ef35 100644 --- a/specs/ChaosRollHistory.md +++ b/specs/ChaosRollHistory.md @@ -1,5 +1,15 @@ # Chaos roll — window records +> **CLOSED 2026-08-08.** The mechanism is retired +> ([ADR-0021](../decisions/ADR-0021-the-chaos-roll-is-retired.md)). This +> file is history and takes no new entries. +> +> **Four windows, four overrides, no demonstrated benefit.** The +> retirement condition was met — and was itself wrong: it fires on a 1/9 +> coincidence, and it measured whether an override *changed the tier* +> rather than whether the change *helped*. The real ground was D4's +> tally, not the condition. + One entry per chaos window. Split out of `InnerLoopReference.md` when that file crossed the loadability limit: this is a **log that grows**, and a log inside a reference eventually crowds out the reference. @@ -99,9 +109,17 @@ not binding on what was actually built. | 1 | CB-WP-0041 | d8 = 5 | — | | 2 | CB-WP-0042 | d8 = 4 | — | | 3 | ADR-0020 | d8 = 7 | — | +| 4 | ADR-0021 | d8 = 7 | — | -**Window 3's verdict remains owed** and is now two windows behind. One -override in twelve, changing nothing, is the second consecutive window -meeting ADR-0017 D2's retirement condition — *"a full window's overrides -all change nothing"* — and the condition asks for two consecutive such -windows. **It should be evaluated, not restated again.** +**Window 4 ends here, incomplete, at four declarations.** The condition +was met at the end of window 3; waiting out a fourth window to satisfy a +symmetry nobody requires would have been ceremony. **Declaration 4 is the +last chaos roll this project made** — the one that declared its +retirement, which was rolled because the rule was still in force. + +**Window 3's verdict, delivered 2026-08-08** in +[ADR-0021](../decisions/ADR-0021-the-chaos-roll-is-retired.md): the +condition was met, and **the condition was defective**. Every roll in this +window was cross-checked against the workplan that made it before the +verdict was written, because the last tally taken from memory was wrong +four times over (F23). diff --git a/specs/InnerLoop.md b/specs/InnerLoop.md index 2d9f157..8f3de9f 100644 --- a/specs/InnerLoop.md +++ b/specs/InnerLoop.md @@ -148,34 +148,21 @@ are never skipped for code-producing work. | **M** | Survey and ADR merged into one document; review optional | Touches a canonical interface, adds/updates an external dependency, **or changes whether or how the loop constrains its own operation** — budgets, gates, review requirements, or these tier rules (v1.6, ADR-0006 D5) | | **S** | One provenance paragraph in the commit message | Everything else (utilities, fixes, refactors inside a boundary) | -**The chaos roll.** After deriving the structural tier, roll **d8** -(`shuf -i 1-8 -n 1`). On an **8**, the tier is instead picked uniformly at -random (`shuf -e S M L -n 1`), overriding the structural derivation — up or -down. +**The chaos roll is retired** ([ADR-0021](../decisions/ADR-0021-the-chaos-roll-is-retired.md), +2026-08-08). The tier is the structural derivation above, and nothing +overrides it. -> **Window 3, opened 2026-08-07 at d8**, running to 12 declarations. +> It ran for four windows and acted four times. Its retirement condition +> was met — and **the condition was wrong**: it fires on a 1/9 coincidence +> and it measured whether an override *changed the tier*, not whether the +> change *helped*. The real ground was that **no override ever +> demonstrated a benefit**, and the one substantive intervention dropped a +> structural L to S on a pass that then needed two follow-ups. > -> **Retirement condition (ADR-0017 D2):** retire the roll if **a full -> window's overrides all change nothing**, met in **two consecutive -> windows**. A window with **no** overrides is *inconclusive* and advances -> nothing. **Window 2 counts as the first.** -> -> Both windows' full records, the rate history, and why the old -> "twice running" condition was replaced: -> [`specs/ChaosRollHistory.md`](ChaosRollHistory.md), and -> `specs/InnerLoopReference.md` §Chaos roll — calibration. -> -> Record the roll every time, including when it changes nothing -> (`tier: L (structural L, chaos 8)`). Why the rate fell, why n=2 makes -> that the weakest part of the decision, and the dead rolls: -> [`specs/ChaosRollHistory.md`](ChaosRollHistory.md), and -> `specs/InnerLoopReference.md` §Chaos roll — calibration. - -Chaos limits: a rolled-down tier relaxes *process* weight only. Invariants -(zero foreign types in canonical interfaces, determinism, passing -conformance suites) bind at every tier, and a rolled-down pass touching a -canonical interface still requires the interface change to be flagged in -the commit for retrospective review. +> The log is closed at [`ChaosRollHistory.md`](ChaosRollHistory.md). +> **The trigger for reviving it** is ossification: declared tiers +> collapsing toward one value, or a pass declaring below its structural +> tier to dodge a review. ### Step 1 — Research diff --git a/tools/loop-lint.py b/tools/loop-lint.py index 66363a0..0c2496e 100644 --- a/tools/loop-lint.py +++ b/tools/loop-lint.py @@ -191,7 +191,13 @@ def check_workplan_lifecycle(root=REPO): def check_survey_tier_and_chaos(root=REPO): - """§Loop tiers — tier declared, and the chaos roll recorded every time.""" + """§Loop tiers — the tier is declared. + + **The chaos half is retired** (ADR-0021, 2026-08-08). Requiring the + roll would fail every future research note: a check that outlives its + rule stops being a control and becomes an obstruction. The name is + kept because `gates.toml` and the self-tests refer to it. + """ out = [] base = os.path.join(root, "research") if not os.path.isdir(base): @@ -203,12 +209,6 @@ def check_survey_tier_and_chaos(root=REPO): text = open(os.path.join(root, rel)).read() if not re.search(r"^tier:\s*[SML]\b", text, re.M): out.append(Finding("tier-declared", rel, "no `tier:` declaration")) - elif "chaos" not in text.lower(): - out.append( - Finding("chaos-recorded", rel, - "tier declared without the chaos roll; the rule requires " - "recording it even when it changes nothing") - ) return out @@ -644,15 +644,20 @@ def self_test(): check("evidence-unmeasured detects a table verdict, not prose", len(f) == 1, f"{len(f)} finding(s), expected exactly 1") - # tier/chaos: missing tier trips; tier without chaos trips. - with open(os.path.join(tmp, "research", "A.md"), "w") as fh: - fh.write("# survey\nno tier here\n") - with open(os.path.join(tmp, "research", "B.md"), "w") as fh: - fh.write("tier: L (structural L)\n") + # tier: a missing declaration trips. The chaos half is retired + # (ADR-0021), so a note without a roll must now PASS — the + # control that used to require one would fail every new note. + os.makedirs(os.path.join(tmp, "research"), exist_ok=True) + with open(os.path.join(tmp, "research", "CB-RES-9999-x.md"), "w") as fh: + fh.write("---\nid: CB-RES-9999\n---\n\nno tier here\n") f = check_survey_tier_and_chaos(tmp) - rules = sorted(x.rule for x in f) - check("tier/chaos detects both omissions", - rules == ["chaos-recorded", "tier-declared"], f"{rules}") + check("tier: a survey with no tier declaration is caught", + len(f) == 1 and f[0].rule == "tier-declared", f"{[x.rule for x in f]}") + with open(os.path.join(tmp, "research", "CB-RES-9999-x.md"), "w") as fh: + fh.write("---\nid: CB-RES-9999\ntier: M\n---\n\nno chaos line\n") + check("tier: a survey with no chaos roll now PASSES", + not check_survey_tier_and_chaos(tmp), + "ADR-0021 retired the roll; requiring it would fail every new note") # self-test: a tool without the flag trips. with open(os.path.join(tmp, "tools", "silent.py"), "w") as fh: