diff --git a/gates.toml b/gates.toml index 9fde325..a3c1f89 100644 --- a/gates.toml +++ b/gates.toml @@ -92,17 +92,26 @@ caught = [ retire_if = "it stays at 100% through two passes that add kernel rules — at that point it is measuring a habit, not enforcing one" [[gate]] -id = "META-25" +id = "META-20" name = "meta budget" target = "status" -checks = "share of the trailing 3 passes spent on the loop itself; soft 25%" +checks = "share of the trailing 5 passes spent on the loop itself; soft 20%. Purpose (InnerLoop v1.7): most spend on the task at hand, some on control, review and improving the process that carries the work forward" added = "2026-08-01" review_by = "2026-11-30" caught = [ "its own cumulative-window defect, reported in CB-EV-0007 §3 and fixed by CB-WP-0009 T01", + "CB-WP-0019: it had a threshold and no stated purpose, which is why the number was argued three times. The maintainer set 80/20; writing that down exposed that the ratio and the window are a pair — one meta pass among three at parity cost already reads 33%, so 20% over a trailing 3 would have silently also demanded it be half-price. Now 20% over a trailing 5, which is one pass in five at normal cost", ] retire_if = "product and meta stop being separable, or the share sits under the line for four passes without anyone consulting it" +# The phase setting (CB-WP-0019 T05). Uncomment, argue, and date it to move +# the split for a phase — stage 0 and a stabilisation phase do not deserve +# the same ratio. It REVERTS to META_SOFT_PCT on `review_by` unless +# re-argued, and `status.py --self-test` refuses one with no reason or no +# expiry, because a threshold anyone may move is not a threshold. +# +# meta_phase = { pct = 35, reason = "why this phase differs", review_by = "YYYY-MM-DD" } + [[gate]] id = "LOOP-LINT" name = "executable InnerLoop rules" diff --git a/specs/InnerLoop.md b/specs/InnerLoop.md index 1de6699..14c37ce 100644 --- a/specs/InnerLoop.md +++ b/specs/InnerLoop.md @@ -6,12 +6,23 @@ a breach never blocks its own repair (D1/D2); loop work cashes out into something runnable in the pass that invents it (D4); the tier table covers changes to the loop's own operation (D5). -> **Meta budget — soft, 25% of the trailing three passes.** Work on the -> loop's own instruments and process is capped at a quarter of recent -> spend. Measured by `make status` from each workplan's `kind:` -> frontmatter (`product` | `meta` | `mixed`), over a **trailing 3-pass -> window**; the lifetime figure is reported as history and is not the -> metric (ADR-0006 D1). +> **What the budget is for (v1.7, CB-WP-0019 T05).** Most of what this +> project spends should go on **the task at hand**. Some of it must go on +> **control, review, and improving the process that carries the work +> forward** — a loop that never inspects itself accumulates exactly the +> defects this one keeps finding, and a loop that only inspects itself +> ships nothing. The budget names the split so that neither happens by +> drift. +> +> **Meta budget — soft, 20% of the trailing five passes.** Measured by +> `make status` from each workplan's `kind:` frontmatter (`product` | +> `meta` | `mixed`), over a **trailing 5-pass window**; the lifetime +> figure is reported as history and is not the metric (ADR-0006 D1). +> +> **The ratio and the window are a pair** — 20% over five is *one pass in +> five at normal cost*; the same number over three would mean *one in five +> and at half price*. `InnerLoopReference.md` §Meta budget has the +> arithmetic. > > **Soft on purpose.** A task already started may be finished — stopping > mid-task to satisfy a ratio wastes the work and leaves the tree worse @@ -27,6 +38,13 @@ covers changes to the loop's own operation (D5). > `authorized_above_budget:` in the workplan frontmatter. Undocumented > above-line meta work is a violation the evidence file must report. > +> **The phase setting is not a dial.** The split may move for a phase, but +> only **declared, argued, and expiring** — `meta_phase = { pct, reason, +> review_by }` in `gates.toml`, reverting to 20 on `review_by` unless +> re-argued. A setting with no reason or no expiry fails +> `status.py --self-test`, so the line cannot be raised quietly to pass a +> breach. +> > A pass that exceeds it **says so in its evidence file and names the > product work displaced**. That is the whole enforcement: a reporting > budget, not a gate, for the same reason the session-shape budget is — @@ -36,12 +54,8 @@ covers changes to the loop's own operation (D5). > least one executable check, command, or gate. Findings that stay prose > are recorded as findings and do **not** count as delivery. > -> Measured basis: cost per response across five passes ran 0.123, 0.228, -> 0.362, 0.298, 0.123 dollars, and the reading that survived the data is -> that **cost tracks distance from a runnable check** — the passes that -> shipped a command were cheap, the passes that argued about what a -> number means were not (CB-EV-0007 §5). **Refuted if** a prose-only meta -> pass lowers cost per response or catches a class no command could. +> Measured basis and its falsifier: `InnerLoopReference.md` §Loop work +> cashes out — **cost tracks distance from a runnable check**. > **Every gate is an experiment (v1.6, ADR-0006 D3).** Each standing > control gate has an entry in `gates.toml` naming what it has caught, a @@ -194,20 +208,9 @@ sampling. So: > red. Re-running the command that prints a number satisfies "reproduce > independently" and finds nothing of this class. > -> *(v1.4, from CB-WP-0005: `evidence/CB-EV-0001` reported `AM-7 replay | -> met, 2,290×` for a clause that asserts nothing — the hash reaches only a -> `println!`. The reviewer found it by opening a test out of curiosity and -> said so; no systematic step pointed there. Mutating it settled it in one -> command. This is the second verification step to inherit the author's -> blindness — the first was CB-WP-0002's dedup sample — and both fixes -> replace re-derivation with **adversarial execution**.)* - -*(v1.1, from CB-WP-0002: the dedup invariant was verified on the main -transcript by the survey — 206/206 groups — and independently re-verified -by the reviewer, who used the same transcript. It is false in the -8-response `subagents/` tree neither examined. Two independent checks, -one blind spot, because both sampled the same way. Only an assertion -running over all the data at execution time caught it.)* +*Both cases that produced these rules — CB-WP-0005's inert `AM-7 replay` +clause and CB-WP-0002's same-sample dedup blind spot — are in +`InnerLoopReference.md` §Why review is adversarial execution.* **What review demonstrably does do.** Measured across two passes: **$0.66** and **$1.11**, roughly 1% of each pass, each finding diff --git a/specs/InnerLoopReference.md b/specs/InnerLoopReference.md index e60f773..6710fd0 100644 --- a/specs/InnerLoopReference.md +++ b/specs/InnerLoopReference.md @@ -227,3 +227,73 @@ So window 2 carries a falsifier: **if it produces no override at all, that is evidence the rate went too far**, not evidence the mechanism is healthy. A window that cannot fire cannot be evaluated, which is the exact failure d10 had. + +## Meta budget — why 20% goes with a five-pass window + +*(CB-WP-0019 T05, 2026-08-03, at the maintainer's instruction: "the main +budget should be spent on the task at hand while some of the budget should +be spent on control, review and improvement… 80/20 to start with".)* + +The ratio and the window are a pair. With one meta pass among `n` at the +same cost `P` as each product pass, the measured share is `P / (n·P)`, so: + +| window | one meta pass at parity reads | what a 20% line then demands | +|---:|---:|---| +| 3 | 33% | the meta pass must cost **half** a product pass | +| 4 | 25% | ~80% of one | +| **5** | **20%** | **parity — one pass in five, normal cost** | +| 6 | 17% | 20% is slack | + +So 80/20 over a **trailing 5** is the literal reading of the instruction: +one pass in five is meta, and it may cost what a pass costs. The same 20% +over a trailing 3 would silently also require it to be half-price, which +does not make meta work rarer — it makes it rushed, and this project's +meta passes are where its instruments get repaired. + +**Why not simply keep 25% over three?** Because 25% over three is *"one in +four at three-quarters cost"*, which is a ratio nobody chose; it was +inherited from ADR-0006 without the window being considered alongside it. +Stating the pair is the correction. + +**The phase setting.** `meta_phase` in `gates.toml` carries `pct`, +`reason`, and `review_by`. It exists because stage 0 and a stabilisation +phase do not deserve the same ratio — but it expires, because a threshold +anyone may move is not a threshold. The failure it guards against is the +one this project has refused four times: raising a limit to pass a breach +rather than fixing the breach. + +## Loop work cashes out — the measured basis + +*(ADR-0006 D4, moved out of `InnerLoop.md` by CB-WP-0019 T05 when that file +crossed the loadability limit. Limits get fixed structurally here.)* + +Cost per response across five passes ran **0.123, 0.228, 0.362, 0.298, +0.123** dollars. The reading that survived the data is that **cost tracks +distance from a runnable check**: the passes that shipped a command were +cheap; the passes that argued about what a number means were not +(CB-EV-0007 §5). + +**Refuted if** a prose-only meta pass lowers cost per response, or catches +a class no command could. + +## Why review is adversarial execution, not re-derivation + +*(Moved out of `InnerLoop.md` by CB-WP-0019 T05 at the loadability limit. +Two cases, three passes apart, with the same shape.)* + +**v1.1, from CB-WP-0002 — the same-sample blind spot (SSB).** The dedup +invariant was verified on the main transcript by the survey (206/206 +groups) and independently re-verified by the reviewer, who used the same +transcript. It is **false** in the 8-response `subagents/` tree neither +examined. Two independent checks, one blind spot, because both sampled the +same way. Only an assertion running over all the data at execution time +caught it. + +**v1.4, from CB-WP-0005 — re-derivation is not enough.** +`evidence/CB-EV-0001` reported `AM-7 replay | met, 2,290×` for a clause +that asserts nothing: the hash reaches only a `println!`. The reviewer +found it by opening a test out of curiosity and said so; **no systematic +step pointed there.** Mutating it settled it in one command. + +Both are verification steps that inherited the author's blindness, and +both fixes replace re-derivation with **adversarial execution**. diff --git a/tools/status.py b/tools/status.py index 5b5b411..1decc47 100644 --- a/tools/status.py +++ b/tools/status.py @@ -118,12 +118,54 @@ def cost_lines(): # ------------------------------------------------------- the meta budget -# How many passes the budget looks back over. One pass is a coin flip — -# a `meta` pass reads 100% and a `product` pass reads 0%, and neither -# says anything about the balance the budget is about. Three is the -# smallest window in which a ratio means something and still moves. -TRAILING_PASSES = 3 -META_SOFT_PCT = 25 +# **The ratio and the window are a pair** (InnerLoop v1.7, CB-WP-0019 T05). +# One meta pass among `n`, costing what its neighbours cost, reads `1/n`: +# 33% over three, 25% over four, 20% over five. So 80/20 — the split the +# maintainer set — is *one pass in five at normal cost*, and that is a +# five-pass window. The same 20% over three would additionally demand the +# meta pass be half-price, which does not make meta work rarer, only +# rushed. Changing either number without the other changes the rule. +TRAILING_PASSES = 5 +META_SOFT_PCT = 20 + +# What the budget is for, in one line, because for three arguments it had +# a threshold and no purpose: most of the spend goes on the task at hand; +# some must go on control, review, and improving the process that carries +# the work forward. +META_PURPOSE = ("most spend on the task at hand; some on control, review " + "and improving the process") + + +def meta_phase(root=None): + """The declared phase setting from gates.toml, or None. + + Returns `(pct, reason, review_by)`. A phase setting is allowed to move + the line — stage 0 and a stabilisation phase do not deserve the same + ratio — but only **declared, argued and expiring**. One with no reason + or no expiry is refused rather than honoured, so the line cannot be + raised quietly to pass a breach. + """ + path = os.path.join(root or ROOT, "gates.toml") + try: + text = open(path).read() + except OSError: + return None + m = re.search(r"^meta_phase\s*=\s*\{(.+?)\}", text, re.M | re.S) + if not m: + return None + body = m.group(1) + + def field(name): + f = re.search(name + r'\s*=\s*"([^"]*)"', body) + return f.group(1) if f else None + + pct = re.search(r"pct\s*=\s*(\d+)", body) + reason, review_by = field("reason"), field("review_by") + if not pct or not reason or not review_by: + raise ValueError( + "meta_phase needs pct, reason and review_by — a threshold with " + "no argument or no expiry is a dial, not a threshold") + return int(pct.group(1)), reason, review_by def workplan_starts(): @@ -178,9 +220,15 @@ def meta_budget(plans): print("\n meta budget UNAVAILABLE — the trailing window measured $0") return share = 100 * meta / total - mark = "ok " if share <= META_SOFT_PCT else "OVER" + phase = meta_phase() + soft = phase[0] if phase else META_SOFT_PCT + mark = "ok " if share <= soft else "OVER" print(f"\n meta budget [{mark}] {share:.0f}% over the last " - f"{len(window)} pass(es) (soft {META_SOFT_PCT}%, InnerLoop v1.6)") + f"{len(window)} pass(es) (soft {soft}%, InnerLoop v1.7)") + print(f" for: {META_PURPOSE}") + if phase: + print(f" phase setting {phase[0]}% until {phase[2]} — {phase[1]}") + print(f" (reverts to {META_SOFT_PCT}% on that date unless re-argued)") for wid, kind, _w in window: c = costs[wid] print(f" {wid} {kind:<8} ${c['cost']:>7,.2f} " @@ -293,6 +341,42 @@ def self_test(): """ results = [] + def check(name, ok, detail=""): + results.append((name, ok, detail)) + + # CB-WP-0019 T05: the ratio and the window are a pair. If either moves + # without the other, "80/20" stops meaning one pass in five at normal + # cost — which is the whole content of the rule. + check("the meta ratio matches its window", + abs(META_SOFT_PCT - 100 / TRAILING_PASSES) < 1e-9, + f"{META_SOFT_PCT}% over {TRAILING_PASSES} passes; one meta pass at " + f"parity reads {100 / TRAILING_PASSES:.0f}%") + check("the budget states what it is for", + bool(META_PURPOSE) and "task at hand" in META_PURPOSE) + + # A phase setting may move the line, but only declared, argued and + # expiring. One with no reason or no expiry is refused rather than + # honoured — otherwise it is a dial for passing breaches. + import tempfile + def phase_of(body): + with tempfile.TemporaryDirectory() as d: + open(os.path.join(d, "gates.toml"), "w").write(body) + return meta_phase(d) + check("a phase setting with no gates.toml is absent, not an error", + phase_of("# nothing here") is None) + good = 'meta_phase = { pct = 35, reason = "stage 0 hardening", review_by = "2026-12-31" }' + check("a fully declared phase setting is honoured", + phase_of(good) == (35, "stage 0 hardening", "2026-12-31")) + for bad, why in [ + ('meta_phase = { pct = 90, review_by = "2026-12-31" }', "no reason"), + ('meta_phase = { pct = 90, reason = "because" }', "no expiry"), + ]: + try: + phase_of(bad) + check(f"a phase setting with {why} is refused", False, "it was honoured") + except ValueError: + check(f"a phase setting with {why} is refused", True) + def check(name, ok, detail=""): results.append((name, ok, detail)) diff --git a/workplans/CB-WP-0019-the-am4-family.md b/workplans/CB-WP-0019-the-am4-family.md index 9fac38c..a651b56 100644 --- a/workplans/CB-WP-0019-the-am4-family.md +++ b/workplans/CB-WP-0019-the-am4-family.md @@ -145,7 +145,7 @@ bearing. ```task id: CB-WP-0019-T05 -status: todo +status: done priority: high ``` @@ -195,6 +195,38 @@ a breach is the failure this shape exists to prevent. **Controls:** `status.py`'s self-test must cover the new window and ratio, and must fail if a phase override has no reason or no expiry. +**Done 2026-08-03.** InnerLoop **v1.7**. + +**The purpose is written first, and the number follows from it:** *most +spend on the task at hand; some on control, review and improving the +process that carries the work forward.* `make status` now prints it above +the figure, because a threshold with no stated purpose is what let this +number be argued three times. + +**Soft 20% over a trailing 5**, and the self-test enforces that they are a +**pair**: `META_SOFT_PCT == 100 / TRAILING_PASSES`. Moving the ratio to 25 +without moving the window turns it red — *"25% over 5 passes; one meta +pass at parity reads 20%"*. That invariant is the rule's whole content; +without it the two numbers drift and "80/20" quietly means something else. + +**The phase setting is declared, argued and expiring**, in `gates.toml` +beside the gate it governs. Verified live: with a setting present the line +moves to 35%, prints its reason, and announces *"reverts to 20% on that +date unless re-argued"*. A setting with **no reason** or **no expiry** is +refused by `--self-test`, not honoured — so the line cannot be raised +quietly to pass a breach. + +**Measured effect of the change**, which is the point of adopting it +rather than asserting it: the last five passes read **7%** against the new +20% line. The old rule read 0% over three. Neither is a breach; the new +one is measuring a window in which a meta pass can actually appear. + +`InnerLoop.md` crossed the 400-line loadability limit three times while +this was written, and was fixed structurally each time — the meta-budget +arithmetic, the cost-per-response basis, and the two review case studies +moved to `InnerLoopReference.md`. **The limit was not raised**, per the +standing precedent it exists to enforce. + ## Task: evidence ```task