From e3d0df66902b8060ed5dbe852ad5f7f43756f394 Mon Sep 17 00:00:00 2001 From: tegwick Date: Fri, 31 Jul 2026 09:29:40 +0200 Subject: [PATCH] T08: give provisional defaults an owner, a date, and a visible age Five scenarios encoded U-item defaults with no owner and no review date, so they could shape the kernel indefinitely while looking handled. Each now carries provisional_owner and provisional_raised, and the runtime's ScenarioFile learned both fields (deny_unknown_fields meant adding them to YAML alone would have failed every scenario -- the parser had to agree). make coverage reports every provisional item with its owner and age in days, warns on any with no owner, and warns past 30 days. It WARNS rather than breaking the build, on purpose: the ruling is ground-game's to make and the kernel cannot make it for them. What the loop can enforce is that evidence files list them, which is now stated in GroundRules. rule-coverage --self-test gained an assertion that every provisional item has both fields, so the next one added without them fails a command rather than passing quietly. Co-Authored-By: Claude Opus 5 --- crates/cb-game-runtime/src/scenario.rs | 10 ++++ scenarios/ground/gr-a04-bond-support.yaml | 2 + scenarios/ground/gr-d01-darvo-trigger.yaml | 2 + scenarios/ground/gr-d05-darvo-reverse.yaml | 2 + scenarios/ground/gr-e02-shared-ground.yaml | 2 + scenarios/ground/gr-e04-coalitions.yaml | 2 + specs/GroundRules.md | 9 ++++ tools/rule-coverage.py | 53 ++++++++++++++++++++++ workplans/CB-WP-0003-loop-hardening.md | 2 +- 9 files changed, 83 insertions(+), 1 deletion(-) diff --git a/crates/cb-game-runtime/src/scenario.rs b/crates/cb-game-runtime/src/scenario.rs index 1a6aa08..ed47391 100644 --- a/crates/cb-game-runtime/src/scenario.rs +++ b/crates/cb-game-runtime/src/scenario.rs @@ -21,6 +21,16 @@ pub struct ScenarioFile { /// scenario, not the kernel. #[serde(default)] pub provisional: bool, + /// Who must rule on the U-item this scenario encodes. Required when + /// `provisional` is true: a provisional default with no owner can + /// shape the kernel indefinitely while looking handled + /// (CB-WP-0003 T08). + #[serde(default)] + pub provisional_owner: String, + /// ISO date the provisional default was raised, so its age is + /// reportable by `make coverage`. + #[serde(default)] + pub provisional_raised: String, pub seed: u64, pub setup: Setup, pub commands: Vec, diff --git a/scenarios/ground/gr-a04-bond-support.yaml b/scenarios/ground/gr-a04-bond-support.yaml index d70d957..f315220 100644 --- a/scenarios/ground/gr-a04-bond-support.yaml +++ b/scenarios/ground/gr-a04-bond-support.yaml @@ -5,6 +5,8 @@ description: > Bond, because GR-L02 requires the target's consent. covers: [GR-A04, GR-F04, GR-L02, GR-L05] provisional: true +provisional_owner: ground-game +provisional_raised: 2026-07-31 seed: 42 setup: players: 3 diff --git a/scenarios/ground/gr-d01-darvo-trigger.yaml b/scenarios/ground/gr-d01-darvo-trigger.yaml index 19ed4ab..fe99a86 100644 --- a/scenarios/ground/gr-d01-darvo-trigger.yaml +++ b/scenarios/ground/gr-d01-darvo-trigger.yaml @@ -5,6 +5,8 @@ description: > application (GR-F01 under the U2 default). covers: [GR-D01, GR-R08, GR-F01] provisional: true +provisional_owner: ground-game +provisional_raised: 2026-07-31 seed: 42 setup: players: 3 diff --git a/scenarios/ground/gr-d05-darvo-reverse.yaml b/scenarios/ground/gr-d05-darvo-reverse.yaml index 77efb18..f1e6d89 100644 --- a/scenarios/ground/gr-d05-darvo-reverse.yaml +++ b/scenarios/ground/gr-d05-darvo-reverse.yaml @@ -5,6 +5,8 @@ description: > owner takes −2 Stress and the sequence ends (GR-D05, GR-D07). covers: [GR-D05, GR-D07, GR-T01] provisional: true +provisional_owner: ground-game +provisional_raised: 2026-07-31 seed: 42 setup: players: 3 diff --git a/scenarios/ground/gr-e02-shared-ground.yaml b/scenarios/ground/gr-e02-shared-ground.yaml index 8182ef2..04e9aa7 100644 --- a/scenarios/ground/gr-e02-shared-ground.yaml +++ b/scenarios/ground/gr-e02-shared-ground.yaml @@ -5,6 +5,8 @@ description: > rating reduced by each Blame token and each Denied Problem. covers: [GR-R09, GR-E01, GR-E02, GR-P03] provisional: true +provisional_owner: ground-game +provisional_raised: 2026-07-31 seed: 42 setup: players: 3 diff --git a/scenarios/ground/gr-e04-coalitions.yaml b/scenarios/ground/gr-e04-coalitions.yaml index d9771ec..dd9f16d 100644 --- a/scenarios/ground/gr-e04-coalitions.yaml +++ b/scenarios/ground/gr-e04-coalitions.yaml @@ -5,6 +5,8 @@ description: > one. Personal scores subtract Blame (GR-E03, GR-T02). covers: [GR-E03, GR-E04, GR-O03] provisional: true +provisional_owner: ground-game +provisional_raised: 2026-07-31 seed: 42 setup: players: 3 diff --git a/specs/GroundRules.md b/specs/GroundRules.md index 78a1284..620b120 100644 --- a/specs/GroundRules.md +++ b/specs/GroundRules.md @@ -210,6 +210,15 @@ terms. ## Underdetermined in dataset 0.1 — PROVISIONAL defaults (flag to ground-game) +**Owner: ground-game. Raised: 2026-07-31.** Every U-item carries an owner +and a raise date so its age is visible; `make coverage` reports both and +warns past 30 days. CI **warns rather than breaks** — the ruling belongs +to ground-game and the kernel cannot make it for them — but every evidence +file that depends on one must list it (CB-WP-0003 T08). + +A ruling flips the *scenario*, not the kernel: the default is encoded in a +scenario tagged `provisional: true`, so a correction is a data change. + Formalization exposed points the dataset does not decide. Simulation uses the stated default; each is tagged in scenarios that depend on it and must be confirmed or corrected by ground-game. diff --git a/tools/rule-coverage.py b/tools/rule-coverage.py index 73ef5ae..109f9ed 100755 --- a/tools/rule-coverage.py +++ b/tools/rule-coverage.py @@ -27,6 +27,10 @@ import sys RULE_RE = r"\*\*(GR-[A-Z]+\d+)" COVERS_RE = r"covers: \[(.*?)\]" AGGREGATE = "games/ground/src/lib.rs" +# CB-WP-0003 T08: a provisional default with no expiry can shape the kernel +# indefinitely while looking handled. CI warns; it does not break the build, +# because the ruling is a ground-game decision we cannot make for them. +PROVISIONAL_WARN_DAYS = 30 ID_RE = r"GR-[A-Z]+\d+" @@ -39,6 +43,21 @@ def parse_code_ids(text): return set(re.findall(ID_RE, text)) +def provisional_items(paths): + """(path, owner, raised) for every scenario encoding a U-item default.""" + out = [] + for path in paths: + text = open(path).read() + if not re.search(r"^provisional:\s*true", text, re.M): + continue + owner = re.search(r"^provisional_owner:\s*(\S+)", text, re.M) + raised = re.search(r"^provisional_raised:\s*(\S+)", text, re.M) + out.append((path, + owner.group(1) if owner else None, + raised.group(1) if raised else None)) + return out + + def parse_covers(text): match = re.search(COVERS_RE, text, re.S) if not match: @@ -71,6 +90,12 @@ def self_test(): == {"GR-R06", "GR-A12"}) check("code-id matcher finds none in unmarked source", parse_code_ids("fn f() { let x = 1; }") == set()) + # T08: every provisional scenario must carry an owner and a date. + import glob as _g + prov = provisional_items(sorted(_g.glob("scenarios/ground/*.yaml"))) + check("every provisional item has an owner and a raised date", + bool(prov) and all(o and r for _, o, r in prov), + f"{len(prov)} provisional item(s)") print("rule-coverage self-test (positive control)") ok = True @@ -128,6 +153,34 @@ def main(): print(" ERROR — rule id in code that the spec does not define:", " ".join(phantom), file=sys.stderr) return 1 + # T08: provisional items are reported with an owner and an age. + prov = provisional_items(paths) + if prov: + import datetime + today = datetime.date.today() + print(f"\nprovisional U-item defaults: {len(prov)}") + unowned, stale = [], [] + for path, owner, raised in prov: + age = "?" + if raised: + try: + age = (today - datetime.date.fromisoformat(raised)).days + except ValueError: + age = "?" + name = path.split("/")[-1] + print(f" {name:<34} owner={owner or 'NONE':<12} age={age}d") + if not owner: + unowned.append(name) + if isinstance(age, int) and age > PROVISIONAL_WARN_DAYS: + stale.append(f"{name} ({age}d)") + if unowned: + print(" WARN — provisional with no owner:", " ".join(unowned)) + if stale: + print(f" WARN — provisional for over {PROVISIONAL_WARN_DAYS} days:", + " ".join(stale)) + print(" NOTE: evidence files must list these; a ruling flips the " + "scenario, not the kernel") + if missing: print(" uncovered:", " ".join(missing)) if invented: diff --git a/workplans/CB-WP-0003-loop-hardening.md b/workplans/CB-WP-0003-loop-hardening.md index 33bdc57..fef7c1b 100644 --- a/workplans/CB-WP-0003-loop-hardening.md +++ b/workplans/CB-WP-0003-loop-hardening.md @@ -286,7 +286,7 @@ self-report of which one it was. ```task id: CB-WP-0003-T08 -status: todo +status: done priority: low state_hub_task_id: "c2ee91ee-e551-48a7-b1a9-477c34c0690c" ```