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: