From a305eaffe5178f83777c10d02632218808f0df4f Mon Sep 17 00:00:00 2001 From: klappy <118073+klappy@users.noreply.github.com> Date: Mon, 21 Sep 2026 04:16:44 +0000 Subject: [PATCH 1/7] gate: three presence prereqs on exploration-to-planning MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit world_prior_cited, house_install_cited, house_canon_cited. Data-only — Worker still evaluates each id independently. Official nulls count. CF/vodka is not a house-canon cite. Fixture: scripts/tests/test_gate_explore_plan_fixture.py Kitchen: 2026-09-20-gate-explore-plan-prior-art Dish A. --- odd/gate/prerequisites.md | 11 +- odd/gate/transitions.md | 4 +- .../tests/test_gate_explore_plan_fixture.py | 171 ++++++++++++++++++ 3 files changed, 182 insertions(+), 4 deletions(-) create mode 100644 scripts/tests/test_gate_explore_plan_fixture.py diff --git a/odd/gate/prerequisites.md b/odd/gate/prerequisites.md index 1e9f352..fc7d852 100644 --- a/odd/gate/prerequisites.md +++ b/odd/gate/prerequisites.md @@ -23,9 +23,9 @@ target_repo: "outcomes-driven-development" --- -## Summary — Eleven Prerequisites, Referenced by ID from Transitions +## Summary — Fourteen Prerequisites, Referenced by ID from Transitions -Eleven prerequisite ids are defined here. They are grouped roughly by what transition they tend to gate, though the same id can be referenced by multiple transitions if future canon adds such references. `problem_defined` and `constraints_reviewed` gate opening planning after exploration. `decisions_locked`, `dod_defined`, `irreversibility_assessed`, and `constraints_satisfied` gate opening execution after planning. `dod_met` and `artifacts_present` gate declaring completion. `delegation_attempted` and `human_only_class_named` gate the `dispatch-to-captain-handoff` transition — the offload trip-wire of `klappy://canon/constraints/dispatcher-dispatches-never-executes`: no ask that assigns the captain operational work passes unless delegation to a subagent was attempted and a human-only class is named. `open_fork_cited` gates the `settled-direction-ratification` transition — the let-the-gates-govern clause of the same constraint: no approval-shaped escalation passes unless it affirmatively names the genuinely-open fork the gates cannot resolve. The prereq is deliberately framed as proof-of-openness rather than absence-of-settledness, because presence-based set-intersection matching can verify that an open fork was named but cannot verify that a settled decision was not — the burden sits on the escalation to cite what is open, and an ask ratifying an already-recorded decision has nothing to cite. Check vocabularies are deliberately broad word lists that cover the most common ways an operator signals the prerequisite in natural language. Gap messages name the missing prerequisite in a single sentence phrased to point the operator at what to surface next. +Fourteen prerequisite ids are defined here. They are grouped roughly by what transition they tend to gate, though the same id can be referenced by multiple transitions if future canon adds such references. `problem_defined`, `constraints_reviewed`, `world_prior_cited`, `house_install_cited`, and `house_canon_cited` gate opening planning after exploration. The last three are independent presence-proofs — kitchen CHECKLIST 10 (observed world reference), CHECKLIST 11 (installation inspected), and a house-canon cite — because stemmed set intersection is per-id OR of stems; one id cannot enforce three-band AND, and a Worker AND would be Flavored Vodka. Official nulls count (`no world prior`, `no house install`, `no house canon`). `decisions_locked`, `dod_defined`, `irreversibility_assessed`, and `constraints_satisfied` gate opening execution after planning. `dod_met` and `artifacts_present` gate declaring completion. `delegation_attempted` and `human_only_class_named` gate the `dispatch-to-captain-handoff` transition — the offload trip-wire of `klappy://canon/constraints/dispatcher-dispatches-never-executes`: no ask that assigns the captain operational work passes unless delegation to a subagent was attempted and a human-only class is named. `open_fork_cited` gates the `settled-direction-ratification` transition — the let-the-gates-govern clause of the same constraint: no approval-shaped escalation passes unless it affirmatively names the genuinely-open fork the gates cannot resolve. The prereq is deliberately framed as proof-of-openness rather than absence-of-settledness, because presence-based set-intersection matching can verify that an open fork was named but cannot verify that a settled decision was not — the burden sits on the escalation to cite what is open, and an ask ratifying an already-recorded decision has nothing to cite. Check vocabularies are deliberately broad word lists that cover the most common ways an operator signals the prerequisite in natural language. Gap messages name the missing prerequisite in a single sentence phrased to point the operator at what to surface next. --- @@ -44,6 +44,9 @@ Eleven prerequisite ids are defined here. They are grouped roughly by what trans | delegation_attempted | delegated, dispatched, subagent, flight, spawned, routed, crew | "No delegation attempt named — operational work (launch/commit/push/run/deploy) goes to a subagent/flight, which holds full rights by intent; a 'blocked' subagent is a misconfigured hook to fix, never a reason to route the task to the captain" | | human_only_class_named | human-only, secret, credential, authorial, voice, irreversible, approval, spend, ruling | "No human-only class named — only HUMAN-ONLY(secret / voice / irreversible / approval) asks may go to the captain; anything else is a dispatch wearing a question's clothes" | | open_fork_cited | open fork, open question, genuinely open, undecided, unresolved, unsettled, options, fork, cannot resolve | "No open fork cited — this is settled: let the gates govern, proceed. The gates are the approval; escalate only a genuinely-open fork the gates cannot resolve, and name it: OPEN-FORK(the fork), with the open options stated" | +| world_prior_cited | world-prior, prior-art, observed-reference | "world_prior_cited: World prior not cited — name the observed external reference (CHECKLIST 10), or the official null (no world prior)" | +| house_install_cited | install, installation, repo-path, inspected-repo | "house_install_cited: House install not cited — name the installation repo/path inspected (CHECKLIST 11), or the official null (no house install)" | +| house_canon_cited | klappy, canon, canon-cited | "house_canon_cited: House canon not cited — name a klappy:// URI (or the word canon), or the official null (no house canon); stack folklore (CF, vodka) is not a cite" | --- @@ -53,10 +56,12 @@ Check terms in the table are comma-separated, case-insensitive, and parsed at ca Stemming consequence is significant and intentional. `problems identified` satisfies `problem_defined` because `problems` stems to `problem`. `constraints addressed` satisfies `constraints_satisfied` because `addressed` stems to `address` — matching the canonical `addressed` entry in the check column. `deployed it` satisfies `dod_met` via `deploy` → stems that match the check vocabulary of done-adjacent completion terms. The shift from word-boundary regex to stemmed matching is the session's functional improvement and applies to every prereq row. Canon revisions can tighten or broaden the check vocabulary at any time without requiring a code release. -Set intersection is used rather than BM25 here because prereq evaluation is not a ranking problem. Each prereq independently returns gap-or-not; there is no winner to pick across prereqs. BM25 would compute per-prereq scores only to threshold at zero and discard the ranking — wasted work, and on a small 8-prereq corpus with any shared vocabulary across rows, BM25's IDF term can flip negative and produce zero-scores on valid matches. Set intersection returns the semantically correct "any stem in common = prereq applies" with no scoring pass. The matcher choice reflects fit to the problem shape rather than symmetry with transition detection, which does need ranking. +Set intersection is used rather than BM25 here because prereq evaluation is not a ranking problem. Each prereq independently returns gap-or-not; there is no winner to pick across prereqs. BM25 would compute per-prereq scores only to threshold at zero and discard the ranking — wasted work, and on a small prereq corpus with any shared vocabulary across rows, BM25's IDF term can flip negative and produce zero-scores on valid matches. Set intersection returns the semantically correct "any stem in common = prereq applies" with no scoring pass. The matcher choice reflects fit to the problem shape rather than symmetry with transition detection, which does need ranking. Some check columns contain multi-word phrases (e.g., `definition of done`, `can't undo`, `one-way`, `point of no return`). The server tokenizes these at parse time the same way it tokenizes the user input: split on whitespace and word-separators, stem each token, drop short tokens and stop words. The resulting stemmed tokens are added to the prereq's set just like single-word entries. A user input containing `definition of done` contributes the same stemmed tokens as the canon entry and matches; a user input containing only `dod` matches via the `dod` token directly (acronyms pass through the stemmer unchanged when under the minimum-length threshold). The check vocabularies in this file are deliberately broad rather than narrow, because prereq evaluation is meant to catch a reasonable signal that the prerequisite has been addressed, not to demand specific phrasing. A narrower vocabulary would produce false-negative gaps — the operator has addressed the constraint but did not happen to use the exact word canon expects. Broader vocabulary produces occasional false-positive passes — an input mentioning `problem` in passing satisfies `problem_defined` whether or not the problem was actually articulated — but that failure mode is preferable to false-negative gaps because the operator remains responsible for the transition regardless of what the gate reports. Gate is a discipline support, not a correctness enforcer. When the server cannot reach this file at runtime, `oddkit_gate` falls back to a hardcoded minimal vocabulary that mirrors the check terms above. The minimal tier is identified in the response envelope via `governance_source: "minimal"`; the canon tier via `governance_source: "knowledge_base"`. Both tiers use the same set-intersection matcher; they differ only in whether the vocabulary is editable by updating this file or locked to the deployed worker version. + +The three exploration-to-planning presence ids (2026-09-21, kitchen ticket `2026-09-20-gate-explore-plan-prior-art`) omit shared tokens such as `house` from their check columns so hyphen-split cannot leak a band: `no-house-install` in a check column would add `house`, and then `no house canon` would false-pass `house_install_cited`. Official nulls work from the input side (`no world prior` contributes `world`+`prior`). Presence is not proof a tool ran; a dummy `klappy://` paste is the new cowboy — spawned audit, not this matcher. diff --git a/odd/gate/transitions.md b/odd/gate/transitions.md index d69c60d..aa8e025 100644 --- a/odd/gate/transitions.md +++ b/odd/gate/transitions.md @@ -34,7 +34,7 @@ Six declared transitions cover the mode-discipline cycle and its two guarded esc | Transition Key | From | To | Prerequisites | Detection Terms | |---|---|---|---|---| | `planning-to-execution` | planning | execution | decisions_locked, dod_defined, irreversibility_assessed, constraints_satisfied | ready to build, ready to implement, start building, let's code, start coding, moving to execution, moving to build | -| `exploration-to-planning` | exploration | planning | problem_defined, constraints_reviewed | ready to plan, start planning, let's plan, time to plan, move to planning, moving to planning, ready, let's go, proceed, move forward, next step | +| `exploration-to-planning` | exploration | planning | problem_defined, constraints_reviewed, world_prior_cited, house_install_cited, house_canon_cited | ready to plan, start planning, let's plan, time to plan, move to planning, moving to planning, ready, let's go, proceed, move forward, next step | | `execution-to-exploration` | execution | exploration | | back to exploration, need to rethink, step back, stepped back, stepping back, reconsider | | `execution-to-completion` | execution | completion | dod_met, artifacts_present | ship, shipping, shipped, deploy, release, go live, push to prod | | `dispatch-to-captain-handoff` | execution | captain-escalation | delegation_attempted, human_only_class_named | you run it, you push, you commit, you deploy, you launch, you merge, can you run, please run this, over to you, your turn, on your machine, run this yourself, hand off to the captain, captain to run, for you to merge, leave the push to you | @@ -57,3 +57,5 @@ When no row's detection terms score above zero against the user input, the serve Prereq-ids listed in the Prerequisites column are resolved against `odd/gate/prerequisites.md`. An id that appears in this table but not in that file is a governance error; the server's minimal fallback tier carries a hardcoded vocabulary snapshot that keeps gate functional in this case, but the canon-level fix is to update one file or the other. When the server cannot reach this file at runtime, `oddkit_gate` falls back to a hardcoded minimal vocabulary that mirrors the row order and detection terms above. The minimal tier is identified in the response envelope via `governance_source: "minimal"`; the canon tier via `governance_source: "knowledge_base"`. Both tiers run the same BM25 matcher; they differ only in whether the vocabulary is editable by updating this file or locked to the deployed worker version. + +The `exploration-to-planning` row (2026-09-21, kitchen ticket `2026-09-20-gate-explore-plan-prior-art`) lists five prerequisite ids: the original pair plus `world_prior_cited`, `house_install_cited`, `house_canon_cited`. Those ids are defined in `odd/gate/prerequisites.md`. Planning-to-execution is unchanged. diff --git a/scripts/tests/test_gate_explore_plan_fixture.py b/scripts/tests/test_gate_explore_plan_fixture.py new file mode 100644 index 0000000..5d807db --- /dev/null +++ b/scripts/tests/test_gate_explore_plan_fixture.py @@ -0,0 +1,171 @@ +#!/usr/bin/env python3 +"""Fixture for oddkit_gate exploration-to-planning presence-prereqs. + +Mirrors workers/src/bm25.ts tokenize/stem and workers/src/orchestrate.ts +table parse + stemmed set-intersection (oddkit 0.30.1). Data-only: no +Worker AND. Kitchen ticket 2026-09-20-gate-explore-plan-prior-art Dish A. + +Run from repo root: + python3 scripts/tests/test_gate_explore_plan_fixture.py +""" +from __future__ import annotations + +import re +import sys +from pathlib import Path + +REPO = Path(__file__).resolve().parents[2] +TRANSITIONS = REPO / "odd" / "gate" / "transitions.md" +PREREQS = REPO / "odd" / "gate" / "prerequisites.md" + +STOP = { + "a", "an", "the", "is", "are", "was", "were", "be", "been", "being", + "have", "has", "had", "do", "does", "did", "will", "would", "shall", + "should", "may", "might", "must", "can", "could", "of", "in", "to", + "for", "with", "on", "at", "by", "from", "as", "into", "through", + "and", "but", "or", "nor", "not", "no", "so", "if", "then", "than", + "that", "this", "it", "its", "we", "you", "he", "she", "they", +} + +NEW_IDS = ( + "world_prior_cited", + "house_install_cited", + "house_canon_cited", +) + + +def stem(word: str) -> str: + if len(word) < 4: + return word + word = re.sub(r"ies$", "y", word) + word = re.sub(r"ied$", "y", word) + word = re.sub(r"([^aeiou])ed$", r"\1", word) + word = re.sub(r"(ing|tion|ment|ness|able|ible)$", "", word) + word = re.sub(r"s$", "", word) + return word + + +def tokenize(text: str) -> list[str]: + cleaned = re.sub(r"[^\w\s-]", " ", text.lower()) + parts = re.split(r"[\s\-_/]+", cleaned) + return [stem(t) for t in parts if len(t) > 1 and t not in STOP] + + +def parse_table(md: str, heading: str) -> list[list[str]]: + marker = f"## {heading}" + start = md.find(marker) + if start < 0: + raise SystemExit(f"missing heading {heading!r}") + chunk = md[start:] + end = chunk.find("\n## ", 3) + if end > 0: + chunk = chunk[:end] + rows = [] + for line in chunk.splitlines(): + if "|" not in line or re.match(r"^\s*\|[-|\s]+\|\s*$", line): + continue + if "Prerequisite" in line and "Check" in line: + continue + if "Transition Key" in line: + continue + parts = [c.strip() for c in line.strip().strip("|").split("|")] + rows.append(parts) + return rows + + +def prereq_map(md: str) -> dict[str, set[str]]: + out = {} + for cols in parse_table(md, "Prerequisite Overlays"): + if len(cols) < 2: + continue + out[cols[0]] = set(tokenize(cols[1])) + return out + + +def exploration_prereq_ids(md: str) -> list[str]: + for cols in parse_table(md, "Transitions"): + key = cols[0].replace("`", "").strip() + if key == "exploration-to-planning" and len(cols) >= 4: + return [p.strip() for p in cols[3].split(",") if p.strip()] + raise SystemExit("exploration-to-planning row missing") + + +def evaluate(input_text: str, ids: list[str], vocab: dict[str, set[str]]) -> tuple[list[str], list[str]]: + stems = set(tokenize(input_text)) + met, unmet = [], [] + for pid in ids: + check = vocab[pid] + if any(s in stems for s in check): + met.append(pid) + else: + unmet.append(pid) + return met, unmet + + +NOT_READY = "ready to plan. the problem is a thin gate. constraints reviewed." +READY = ( + "ready to plan. the problem is a thin gate. constraints reviewed. " + "no world prior. no house install. no house canon." +) +CF_VODKA = ( + "ready to plan. the problem is a thin gate. constraints reviewed. " + "no world prior. no house install. we use CF and vodka" +) + + +def main() -> None: + trans = TRANSITIONS.read_text() + prereqs = PREREQS.read_text() + vocab = prereq_map(prereqs) + ids = exploration_prereq_ids(trans) + + failed = 0 + + def ok(label: str, cond: bool, hint: str = "") -> None: + nonlocal failed + if cond: + print(f" OK: {label}") + else: + print(f" FAIL: {label}{(' — ' + hint) if hint else ''}") + failed += 1 + + ok( + "exploration-to-planning lists the three new ids after problem+constraints", + ids[:2] == ["problem_defined", "constraints_reviewed"] + and ids[2:5] == list(NEW_IDS), + f"got {ids}", + ) + for pid in NEW_IDS: + ok(f"prerequisites.md defines {pid}", pid in vocab) + + met, unmet = evaluate(NOT_READY, ids, vocab) + ok( + "ready-to-plan + problem + constraints only → NOT_READY naming new ids", + set(NEW_IDS) <= set(unmet) and "problem_defined" in met and "constraints_reviewed" in met, + f"met={met} unmet={unmet}", + ) + + met, unmet = evaluate(READY, ids, vocab) + ok( + "three official nulls → all exploration-to-planning prereqs met", + unmet == [] and set(ids) <= set(met), + f"met={met} unmet={unmet}", + ) + + met, unmet = evaluate(CF_VODKA, ids, vocab) + ok( + "we use CF and vodka → house_canon_cited fails (other new ids may pass)", + "house_canon_cited" in unmet + and "world_prior_cited" in met + and "house_install_cited" in met, + f"met={met} unmet={unmet}", + ) + + if failed: + print(f"\n{failed} fixture(s) failed") + sys.exit(1) + print("\nall fixtures passed") + + +if __name__ == "__main__": + main() From 634341594dda9fce2a90914e90e3cb1633681c7d Mon Sep 17 00:00:00 2001 From: klappy <118073+klappy@users.noreply.github.com> Date: Mon, 21 Sep 2026 04:17:41 +0000 Subject: [PATCH 2/7] debrief: Dish A at the pass Live overlay: NOT_READY 2/5, PASS 5/5, CF-vodka house_canon_cited fail. PR klappy/klappy.dev#323. Kitchen rail 9c0aa12. --- DEBRIEF.md | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 DEBRIEF.md diff --git a/DEBRIEF.md b/DEBRIEF.md new file mode 100644 index 0000000..6b03d39 --- /dev/null +++ b/DEBRIEF.md @@ -0,0 +1,35 @@ +# DEBRIEF — Dish A, 2026-09-20-gate-explore-plan-prior-art + +**Seat:** grok-build (cook). **Clock:** 2026-09-21T04:17Z / 00:17 America/New_York. +**Product:** [klappy/klappy.dev#323](https://github.com/klappy/klappy.dev/pull/323) @ `a305eaf`. +**Kitchen rail:** `9c0aa12` fired 1-ordered → 2-cooking (this file rides the dish branch, not kitchen main). + +## Plate + +`oddkit_gate` `exploration-to-planning` now lists five prereqs. Three new ids, data-only: + +| id | Check (stems) | Official null | +|---|---|---| +| `world_prior_cited` | world-prior, prior-art, observed-reference | no world prior | +| `house_install_cited` | install, installation, repo-path, inspected-repo | no house install | +| `house_canon_cited` | klappy, canon, canon-cited | no house canon | + +Hyphen-split leak closed: `house` is not in any of those check columns. + +## Live overlay (Worker fetch of this branch) + +`knowledge_base_url=https://raw.githubusercontent.com/klappy/klappy.dev/dish/2026-09-20-gate-explore-plan-prior-art` + +1. ready-to-plan + problem + constraints → **NOT_READY 2/5**, unmet names the three new ids. +2. official nulls → **PASS 5/5**. +3. we use CF and vodka → **NOT_READY 4/5**, only `house_canon_cited` unmet. + +Local fixture: `python3 scripts/tests/test_gate_explore_plan_fixture.py` — all passed. + +## Not this plate + +Worker AND. `jev_layer_considered`. Jev API/SDK. P0009. CHECKLIST 10/11 deletion. planning-to-execution. Dishes B–D. + +## Pass ask + +Merge [klappy/klappy.dev#323](https://github.com/klappy/klappy.dev/pull/323) when tasted. Do not merge kitchen main for product. `jev: no-table`. From 535d05f3cbedc48c3f0a61f4b5610b4d5a83b85a Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 21 Sep 2026 04:22:15 +0000 Subject: [PATCH 3/7] fix(gate): drop canon-cited so hyphen-split cannot leak house_canon_cited canon already covers that term; the hyphenated synonym only added cited, so ordinary cite language for the other two bands false-passed the AND. --- odd/gate/prerequisites.md | 4 ++-- .../tests/test_gate_explore_plan_fixture.py | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/odd/gate/prerequisites.md b/odd/gate/prerequisites.md index fc7d852..15f63c6 100644 --- a/odd/gate/prerequisites.md +++ b/odd/gate/prerequisites.md @@ -46,7 +46,7 @@ Fourteen prerequisite ids are defined here. They are grouped roughly by what tra | open_fork_cited | open fork, open question, genuinely open, undecided, unresolved, unsettled, options, fork, cannot resolve | "No open fork cited — this is settled: let the gates govern, proceed. The gates are the approval; escalate only a genuinely-open fork the gates cannot resolve, and name it: OPEN-FORK(the fork), with the open options stated" | | world_prior_cited | world-prior, prior-art, observed-reference | "world_prior_cited: World prior not cited — name the observed external reference (CHECKLIST 10), or the official null (no world prior)" | | house_install_cited | install, installation, repo-path, inspected-repo | "house_install_cited: House install not cited — name the installation repo/path inspected (CHECKLIST 11), or the official null (no house install)" | -| house_canon_cited | klappy, canon, canon-cited | "house_canon_cited: House canon not cited — name a klappy:// URI (or the word canon), or the official null (no house canon); stack folklore (CF, vodka) is not a cite" | +| house_canon_cited | klappy, canon | "house_canon_cited: House canon not cited — name a klappy:// URI (or the word canon), or the official null (no house canon); stack folklore (CF, vodka) is not a cite" | --- @@ -64,4 +64,4 @@ The check vocabularies in this file are deliberately broad rather than narrow, b When the server cannot reach this file at runtime, `oddkit_gate` falls back to a hardcoded minimal vocabulary that mirrors the check terms above. The minimal tier is identified in the response envelope via `governance_source: "minimal"`; the canon tier via `governance_source: "knowledge_base"`. Both tiers use the same set-intersection matcher; they differ only in whether the vocabulary is editable by updating this file or locked to the deployed worker version. -The three exploration-to-planning presence ids (2026-09-21, kitchen ticket `2026-09-20-gate-explore-plan-prior-art`) omit shared tokens such as `house` from their check columns so hyphen-split cannot leak a band: `no-house-install` in a check column would add `house`, and then `no house canon` would false-pass `house_install_cited`. Official nulls work from the input side (`no world prior` contributes `world`+`prior`). Presence is not proof a tool ran; a dummy `klappy://` paste is the new cowboy — spawned audit, not this matcher. +The three exploration-to-planning presence ids (2026-09-21, kitchen ticket `2026-09-20-gate-explore-plan-prior-art`) omit shared tokens such as `house` and `cited` from their check columns so hyphen-split cannot leak a band: `no-house-install` in a check column would add `house`, and then `no house canon` would false-pass `house_install_cited`; `canon-cited` would add `cited`, and then ordinary cite language for the other two bands (`world prior cited`, `house install cited`) would false-pass `house_canon_cited`. Official nulls work from the input side (`no world prior` contributes `world`+`prior`). Presence is not proof a tool ran; a dummy `klappy://` paste is the new cowboy — spawned audit, not this matcher. diff --git a/scripts/tests/test_gate_explore_plan_fixture.py b/scripts/tests/test_gate_explore_plan_fixture.py index 5d807db..3e4583e 100644 --- a/scripts/tests/test_gate_explore_plan_fixture.py +++ b/scripts/tests/test_gate_explore_plan_fixture.py @@ -111,6 +111,10 @@ def evaluate(input_text: str, ids: list[str], vocab: dict[str, set[str]]) -> tup "ready to plan. the problem is a thin gate. constraints reviewed. " "no world prior. no house install. we use CF and vodka" ) +CITE_LEAK = ( + "ready to plan. the problem is a thin gate. constraints reviewed. " + "world prior cited. house install cited." +) def main() -> None: @@ -161,6 +165,21 @@ def ok(label: str, cond: bool, hint: str = "") -> None: f"met={met} unmet={unmet}", ) + met, unmet = evaluate(CITE_LEAK, ids, vocab) + ok( + "cite verbs for the other two bands do not leak house_canon_cited", + "house_canon_cited" in unmet + and "world_prior_cited" in met + and "house_install_cited" in met, + f"met={met} unmet={unmet}", + ) + ok( + "house_canon_cited check stems omit cited (hyphen-split leak)", + "cit" not in vocab["house_canon_cited"] + and "cited" not in vocab["house_canon_cited"], + f"stems={sorted(vocab['house_canon_cited'])}", + ) + if failed: print(f"\n{failed} fixture(s) failed") sys.exit(1) From b31db3084ae1bab2369ccf43e13fc74e8ee5678e Mon Sep 17 00:00:00 2001 From: klappy <118073+klappy@users.noreply.github.com> Date: Mon, 21 Sep 2026 04:26:39 +0000 Subject: [PATCH 4/7] chore: drop DEBRIEF and gate fixture from canon repo DEBRIEF belongs on the kitchen ticket. Gate matcher tests belong in oddkit. Living canon here is the two markdown files. Live Worker overlay remains the fixture. --- DEBRIEF.md | 35 ---- .../tests/test_gate_explore_plan_fixture.py | 190 ------------------ 2 files changed, 225 deletions(-) delete mode 100644 DEBRIEF.md delete mode 100644 scripts/tests/test_gate_explore_plan_fixture.py diff --git a/DEBRIEF.md b/DEBRIEF.md deleted file mode 100644 index 6b03d39..0000000 --- a/DEBRIEF.md +++ /dev/null @@ -1,35 +0,0 @@ -# DEBRIEF — Dish A, 2026-09-20-gate-explore-plan-prior-art - -**Seat:** grok-build (cook). **Clock:** 2026-09-21T04:17Z / 00:17 America/New_York. -**Product:** [klappy/klappy.dev#323](https://github.com/klappy/klappy.dev/pull/323) @ `a305eaf`. -**Kitchen rail:** `9c0aa12` fired 1-ordered → 2-cooking (this file rides the dish branch, not kitchen main). - -## Plate - -`oddkit_gate` `exploration-to-planning` now lists five prereqs. Three new ids, data-only: - -| id | Check (stems) | Official null | -|---|---|---| -| `world_prior_cited` | world-prior, prior-art, observed-reference | no world prior | -| `house_install_cited` | install, installation, repo-path, inspected-repo | no house install | -| `house_canon_cited` | klappy, canon, canon-cited | no house canon | - -Hyphen-split leak closed: `house` is not in any of those check columns. - -## Live overlay (Worker fetch of this branch) - -`knowledge_base_url=https://raw.githubusercontent.com/klappy/klappy.dev/dish/2026-09-20-gate-explore-plan-prior-art` - -1. ready-to-plan + problem + constraints → **NOT_READY 2/5**, unmet names the three new ids. -2. official nulls → **PASS 5/5**. -3. we use CF and vodka → **NOT_READY 4/5**, only `house_canon_cited` unmet. - -Local fixture: `python3 scripts/tests/test_gate_explore_plan_fixture.py` — all passed. - -## Not this plate - -Worker AND. `jev_layer_considered`. Jev API/SDK. P0009. CHECKLIST 10/11 deletion. planning-to-execution. Dishes B–D. - -## Pass ask - -Merge [klappy/klappy.dev#323](https://github.com/klappy/klappy.dev/pull/323) when tasted. Do not merge kitchen main for product. `jev: no-table`. diff --git a/scripts/tests/test_gate_explore_plan_fixture.py b/scripts/tests/test_gate_explore_plan_fixture.py deleted file mode 100644 index 3e4583e..0000000 --- a/scripts/tests/test_gate_explore_plan_fixture.py +++ /dev/null @@ -1,190 +0,0 @@ -#!/usr/bin/env python3 -"""Fixture for oddkit_gate exploration-to-planning presence-prereqs. - -Mirrors workers/src/bm25.ts tokenize/stem and workers/src/orchestrate.ts -table parse + stemmed set-intersection (oddkit 0.30.1). Data-only: no -Worker AND. Kitchen ticket 2026-09-20-gate-explore-plan-prior-art Dish A. - -Run from repo root: - python3 scripts/tests/test_gate_explore_plan_fixture.py -""" -from __future__ import annotations - -import re -import sys -from pathlib import Path - -REPO = Path(__file__).resolve().parents[2] -TRANSITIONS = REPO / "odd" / "gate" / "transitions.md" -PREREQS = REPO / "odd" / "gate" / "prerequisites.md" - -STOP = { - "a", "an", "the", "is", "are", "was", "were", "be", "been", "being", - "have", "has", "had", "do", "does", "did", "will", "would", "shall", - "should", "may", "might", "must", "can", "could", "of", "in", "to", - "for", "with", "on", "at", "by", "from", "as", "into", "through", - "and", "but", "or", "nor", "not", "no", "so", "if", "then", "than", - "that", "this", "it", "its", "we", "you", "he", "she", "they", -} - -NEW_IDS = ( - "world_prior_cited", - "house_install_cited", - "house_canon_cited", -) - - -def stem(word: str) -> str: - if len(word) < 4: - return word - word = re.sub(r"ies$", "y", word) - word = re.sub(r"ied$", "y", word) - word = re.sub(r"([^aeiou])ed$", r"\1", word) - word = re.sub(r"(ing|tion|ment|ness|able|ible)$", "", word) - word = re.sub(r"s$", "", word) - return word - - -def tokenize(text: str) -> list[str]: - cleaned = re.sub(r"[^\w\s-]", " ", text.lower()) - parts = re.split(r"[\s\-_/]+", cleaned) - return [stem(t) for t in parts if len(t) > 1 and t not in STOP] - - -def parse_table(md: str, heading: str) -> list[list[str]]: - marker = f"## {heading}" - start = md.find(marker) - if start < 0: - raise SystemExit(f"missing heading {heading!r}") - chunk = md[start:] - end = chunk.find("\n## ", 3) - if end > 0: - chunk = chunk[:end] - rows = [] - for line in chunk.splitlines(): - if "|" not in line or re.match(r"^\s*\|[-|\s]+\|\s*$", line): - continue - if "Prerequisite" in line and "Check" in line: - continue - if "Transition Key" in line: - continue - parts = [c.strip() for c in line.strip().strip("|").split("|")] - rows.append(parts) - return rows - - -def prereq_map(md: str) -> dict[str, set[str]]: - out = {} - for cols in parse_table(md, "Prerequisite Overlays"): - if len(cols) < 2: - continue - out[cols[0]] = set(tokenize(cols[1])) - return out - - -def exploration_prereq_ids(md: str) -> list[str]: - for cols in parse_table(md, "Transitions"): - key = cols[0].replace("`", "").strip() - if key == "exploration-to-planning" and len(cols) >= 4: - return [p.strip() for p in cols[3].split(",") if p.strip()] - raise SystemExit("exploration-to-planning row missing") - - -def evaluate(input_text: str, ids: list[str], vocab: dict[str, set[str]]) -> tuple[list[str], list[str]]: - stems = set(tokenize(input_text)) - met, unmet = [], [] - for pid in ids: - check = vocab[pid] - if any(s in stems for s in check): - met.append(pid) - else: - unmet.append(pid) - return met, unmet - - -NOT_READY = "ready to plan. the problem is a thin gate. constraints reviewed." -READY = ( - "ready to plan. the problem is a thin gate. constraints reviewed. " - "no world prior. no house install. no house canon." -) -CF_VODKA = ( - "ready to plan. the problem is a thin gate. constraints reviewed. " - "no world prior. no house install. we use CF and vodka" -) -CITE_LEAK = ( - "ready to plan. the problem is a thin gate. constraints reviewed. " - "world prior cited. house install cited." -) - - -def main() -> None: - trans = TRANSITIONS.read_text() - prereqs = PREREQS.read_text() - vocab = prereq_map(prereqs) - ids = exploration_prereq_ids(trans) - - failed = 0 - - def ok(label: str, cond: bool, hint: str = "") -> None: - nonlocal failed - if cond: - print(f" OK: {label}") - else: - print(f" FAIL: {label}{(' — ' + hint) if hint else ''}") - failed += 1 - - ok( - "exploration-to-planning lists the three new ids after problem+constraints", - ids[:2] == ["problem_defined", "constraints_reviewed"] - and ids[2:5] == list(NEW_IDS), - f"got {ids}", - ) - for pid in NEW_IDS: - ok(f"prerequisites.md defines {pid}", pid in vocab) - - met, unmet = evaluate(NOT_READY, ids, vocab) - ok( - "ready-to-plan + problem + constraints only → NOT_READY naming new ids", - set(NEW_IDS) <= set(unmet) and "problem_defined" in met and "constraints_reviewed" in met, - f"met={met} unmet={unmet}", - ) - - met, unmet = evaluate(READY, ids, vocab) - ok( - "three official nulls → all exploration-to-planning prereqs met", - unmet == [] and set(ids) <= set(met), - f"met={met} unmet={unmet}", - ) - - met, unmet = evaluate(CF_VODKA, ids, vocab) - ok( - "we use CF and vodka → house_canon_cited fails (other new ids may pass)", - "house_canon_cited" in unmet - and "world_prior_cited" in met - and "house_install_cited" in met, - f"met={met} unmet={unmet}", - ) - - met, unmet = evaluate(CITE_LEAK, ids, vocab) - ok( - "cite verbs for the other two bands do not leak house_canon_cited", - "house_canon_cited" in unmet - and "world_prior_cited" in met - and "house_install_cited" in met, - f"met={met} unmet={unmet}", - ) - ok( - "house_canon_cited check stems omit cited (hyphen-split leak)", - "cit" not in vocab["house_canon_cited"] - and "cited" not in vocab["house_canon_cited"], - f"stems={sorted(vocab['house_canon_cited'])}", - ) - - if failed: - print(f"\n{failed} fixture(s) failed") - sys.exit(1) - print("\nall fixtures passed") - - -if __name__ == "__main__": - main() From e6fe5cdc2629e0a26e7efd89ead1033b2b273e86 Mon Sep 17 00:00:00 2001 From: Chris Klapp Date: Mon, 21 Sep 2026 00:39:38 -0400 Subject: [PATCH 5/7] fix(gate): drop prior-art from world_prior_cited check column Hyphen-split added prior; a problem statement that says prior art false-passed the band. Official null is now no world analogue / literature only / bide waiting. --- odd/gate/prerequisites.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/odd/gate/prerequisites.md b/odd/gate/prerequisites.md index 15f63c6..ad8adf9 100644 --- a/odd/gate/prerequisites.md +++ b/odd/gate/prerequisites.md @@ -25,7 +25,7 @@ target_repo: "outcomes-driven-development" ## Summary — Fourteen Prerequisites, Referenced by ID from Transitions -Fourteen prerequisite ids are defined here. They are grouped roughly by what transition they tend to gate, though the same id can be referenced by multiple transitions if future canon adds such references. `problem_defined`, `constraints_reviewed`, `world_prior_cited`, `house_install_cited`, and `house_canon_cited` gate opening planning after exploration. The last three are independent presence-proofs — kitchen CHECKLIST 10 (observed world reference), CHECKLIST 11 (installation inspected), and a house-canon cite — because stemmed set intersection is per-id OR of stems; one id cannot enforce three-band AND, and a Worker AND would be Flavored Vodka. Official nulls count (`no world prior`, `no house install`, `no house canon`). `decisions_locked`, `dod_defined`, `irreversibility_assessed`, and `constraints_satisfied` gate opening execution after planning. `dod_met` and `artifacts_present` gate declaring completion. `delegation_attempted` and `human_only_class_named` gate the `dispatch-to-captain-handoff` transition — the offload trip-wire of `klappy://canon/constraints/dispatcher-dispatches-never-executes`: no ask that assigns the captain operational work passes unless delegation to a subagent was attempted and a human-only class is named. `open_fork_cited` gates the `settled-direction-ratification` transition — the let-the-gates-govern clause of the same constraint: no approval-shaped escalation passes unless it affirmatively names the genuinely-open fork the gates cannot resolve. The prereq is deliberately framed as proof-of-openness rather than absence-of-settledness, because presence-based set-intersection matching can verify that an open fork was named but cannot verify that a settled decision was not — the burden sits on the escalation to cite what is open, and an ask ratifying an already-recorded decision has nothing to cite. Check vocabularies are deliberately broad word lists that cover the most common ways an operator signals the prerequisite in natural language. Gap messages name the missing prerequisite in a single sentence phrased to point the operator at what to surface next. +Fourteen prerequisite ids are defined here. They are grouped roughly by what transition they tend to gate, though the same id can be referenced by multiple transitions if future canon adds such references. `problem_defined`, `constraints_reviewed`, `world_prior_cited`, `house_install_cited`, and `house_canon_cited` gate opening planning after exploration. The last three are independent presence-proofs — kitchen CHECKLIST 10 (observed world reference), CHECKLIST 11 (installation inspected), and a house-canon cite — because stemmed set intersection is per-id OR of stems; one id cannot enforce three-band AND, and a Worker AND would be Flavored Vodka. Official nulls count (`no world analogue` / `literature only` / `bide waiting`, `no house install`, `no house canon`). `decisions_locked`, `dod_defined`, `irreversibility_assessed`, and `constraints_satisfied` gate opening execution after planning. `dod_met` and `artifacts_present` gate declaring completion. `delegation_attempted` and `human_only_class_named` gate the `dispatch-to-captain-handoff` transition — the offload trip-wire of `klappy://canon/constraints/dispatcher-dispatches-never-executes`: no ask that assigns the captain operational work passes unless delegation to a subagent was attempted and a human-only class is named. `open_fork_cited` gates the `settled-direction-ratification` transition — the let-the-gates-govern clause of the same constraint: no approval-shaped escalation passes unless it affirmatively names the genuinely-open fork the gates cannot resolve. The prereq is deliberately framed as proof-of-openness rather than absence-of-settledness, because presence-based set-intersection matching can verify that an open fork was named but cannot verify that a settled decision was not — the burden sits on the escalation to cite what is open, and an ask ratifying an already-recorded decision has nothing to cite. Check vocabularies are deliberately broad word lists that cover the most common ways an operator signals the prerequisite in natural language. Gap messages name the missing prerequisite in a single sentence phrased to point the operator at what to surface next. --- @@ -44,7 +44,7 @@ Fourteen prerequisite ids are defined here. They are grouped roughly by what tra | delegation_attempted | delegated, dispatched, subagent, flight, spawned, routed, crew | "No delegation attempt named — operational work (launch/commit/push/run/deploy) goes to a subagent/flight, which holds full rights by intent; a 'blocked' subagent is a misconfigured hook to fix, never a reason to route the task to the captain" | | human_only_class_named | human-only, secret, credential, authorial, voice, irreversible, approval, spend, ruling | "No human-only class named — only HUMAN-ONLY(secret / voice / irreversible / approval) asks may go to the captain; anything else is a dispatch wearing a question's clothes" | | open_fork_cited | open fork, open question, genuinely open, undecided, unresolved, unsettled, options, fork, cannot resolve | "No open fork cited — this is settled: let the gates govern, proceed. The gates are the approval; escalate only a genuinely-open fork the gates cannot resolve, and name it: OPEN-FORK(the fork), with the open options stated" | -| world_prior_cited | world-prior, prior-art, observed-reference | "world_prior_cited: World prior not cited — name the observed external reference (CHECKLIST 10), or the official null (no world prior)" | +| world_prior_cited | analogue, literature, bide | "world_prior_cited: World analogue not cited — name an observed world analogue, or official null (no world analogue / literature only / bide waiting)" | | house_install_cited | install, installation, repo-path, inspected-repo | "house_install_cited: House install not cited — name the installation repo/path inspected (CHECKLIST 11), or the official null (no house install)" | | house_canon_cited | klappy, canon | "house_canon_cited: House canon not cited — name a klappy:// URI (or the word canon), or the official null (no house canon); stack folklore (CF, vodka) is not a cite" | @@ -64,4 +64,4 @@ The check vocabularies in this file are deliberately broad rather than narrow, b When the server cannot reach this file at runtime, `oddkit_gate` falls back to a hardcoded minimal vocabulary that mirrors the check terms above. The minimal tier is identified in the response envelope via `governance_source: "minimal"`; the canon tier via `governance_source: "knowledge_base"`. Both tiers use the same set-intersection matcher; they differ only in whether the vocabulary is editable by updating this file or locked to the deployed worker version. -The three exploration-to-planning presence ids (2026-09-21, kitchen ticket `2026-09-20-gate-explore-plan-prior-art`) omit shared tokens such as `house` and `cited` from their check columns so hyphen-split cannot leak a band: `no-house-install` in a check column would add `house`, and then `no house canon` would false-pass `house_install_cited`; `canon-cited` would add `cited`, and then ordinary cite language for the other two bands (`world prior cited`, `house install cited`) would false-pass `house_canon_cited`. Official nulls work from the input side (`no world prior` contributes `world`+`prior`). Presence is not proof a tool ran; a dummy `klappy://` paste is the new cowboy — spawned audit, not this matcher. +The three exploration-to-planning presence ids (2026-09-21, kitchen ticket `2026-09-20-gate-explore-plan-prior-art`) omit shared tokens such as `house` and `cited` from their check columns so hyphen-split cannot leak a band: `no-house-install` in a check column would add `house`, and then `no house canon` would false-pass `house_install_cited`; `canon-cited` would add `cited`, and then ordinary cite language for the other two bands (`world prior cited`, `house install cited`) would false-pass `house_canon_cited`. Official nulls work from the input side (`no world analogue` contributes `analogue`). Do not put `prior-art` or `world-prior` in the check column: hyphen-split adds `prior`, and a problem statement that says prior art false-passes the band. Presence is not proof a tool ran; a dummy `klappy://` paste is the new cowboy — spawned audit, not this matcher. From ff8dffc5cd1793fdc8577bc7bdc226e9c69440e1 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 21 Sep 2026 04:45:57 +0000 Subject: [PATCH 6/7] fix(gate): add analog to world_prior_cited check column The suffix-only stemmer does not unify analogue with analog, so American spelling cites (analog/analogs/analogous) left the prereq unmet. --- odd/gate/prerequisites.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/odd/gate/prerequisites.md b/odd/gate/prerequisites.md index ad8adf9..e36658a 100644 --- a/odd/gate/prerequisites.md +++ b/odd/gate/prerequisites.md @@ -44,7 +44,7 @@ Fourteen prerequisite ids are defined here. They are grouped roughly by what tra | delegation_attempted | delegated, dispatched, subagent, flight, spawned, routed, crew | "No delegation attempt named — operational work (launch/commit/push/run/deploy) goes to a subagent/flight, which holds full rights by intent; a 'blocked' subagent is a misconfigured hook to fix, never a reason to route the task to the captain" | | human_only_class_named | human-only, secret, credential, authorial, voice, irreversible, approval, spend, ruling | "No human-only class named — only HUMAN-ONLY(secret / voice / irreversible / approval) asks may go to the captain; anything else is a dispatch wearing a question's clothes" | | open_fork_cited | open fork, open question, genuinely open, undecided, unresolved, unsettled, options, fork, cannot resolve | "No open fork cited — this is settled: let the gates govern, proceed. The gates are the approval; escalate only a genuinely-open fork the gates cannot resolve, and name it: OPEN-FORK(the fork), with the open options stated" | -| world_prior_cited | analogue, literature, bide | "world_prior_cited: World analogue not cited — name an observed world analogue, or official null (no world analogue / literature only / bide waiting)" | +| world_prior_cited | analogue, analog, analogous, literature, bide | "world_prior_cited: World analogue not cited — name an observed world analogue, or official null (no world analogue / literature only / bide waiting)" | | house_install_cited | install, installation, repo-path, inspected-repo | "house_install_cited: House install not cited — name the installation repo/path inspected (CHECKLIST 11), or the official null (no house install)" | | house_canon_cited | klappy, canon | "house_canon_cited: House canon not cited — name a klappy:// URI (or the word canon), or the official null (no house canon); stack folklore (CF, vodka) is not a cite" | From 58daa99092657e877b9066aefb534bc9f044827e Mon Sep 17 00:00:00 2001 From: klappy <118073+klappy@users.noreply.github.com> Date: Mon, 21 Sep 2026 13:35:11 +0000 Subject: [PATCH 7/7] fix(gate): close path/repo/inspect and klappy-slug leaks on the two house bands - house_install_cited: drop repo-path, inspected-repo (hyphen-split leaked repo, path, inspect; 'the path forward' false-passed) - house_canon_cited: drop klappy (every house repo slug carried it, so an install cite paid the canon band); gap message now teaches 'canon: klappy://...' - world_prior_cited: add analogy (same word family; 'by analogy with' was a false gap) - note: three ids are canon-tier only; minimal fallback still carries the original pair (observed oddkit 94c98ad) Found by independent taste with live oddkit_gate on the branch overlay. Data-only. Worker unchanged. --- odd/gate/prerequisites.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/odd/gate/prerequisites.md b/odd/gate/prerequisites.md index e36658a..ce61f27 100644 --- a/odd/gate/prerequisites.md +++ b/odd/gate/prerequisites.md @@ -44,9 +44,9 @@ Fourteen prerequisite ids are defined here. They are grouped roughly by what tra | delegation_attempted | delegated, dispatched, subagent, flight, spawned, routed, crew | "No delegation attempt named — operational work (launch/commit/push/run/deploy) goes to a subagent/flight, which holds full rights by intent; a 'blocked' subagent is a misconfigured hook to fix, never a reason to route the task to the captain" | | human_only_class_named | human-only, secret, credential, authorial, voice, irreversible, approval, spend, ruling | "No human-only class named — only HUMAN-ONLY(secret / voice / irreversible / approval) asks may go to the captain; anything else is a dispatch wearing a question's clothes" | | open_fork_cited | open fork, open question, genuinely open, undecided, unresolved, unsettled, options, fork, cannot resolve | "No open fork cited — this is settled: let the gates govern, proceed. The gates are the approval; escalate only a genuinely-open fork the gates cannot resolve, and name it: OPEN-FORK(the fork), with the open options stated" | -| world_prior_cited | analogue, analog, analogous, literature, bide | "world_prior_cited: World analogue not cited — name an observed world analogue, or official null (no world analogue / literature only / bide waiting)" | -| house_install_cited | install, installation, repo-path, inspected-repo | "house_install_cited: House install not cited — name the installation repo/path inspected (CHECKLIST 11), or the official null (no house install)" | -| house_canon_cited | klappy, canon | "house_canon_cited: House canon not cited — name a klappy:// URI (or the word canon), or the official null (no house canon); stack folklore (CF, vodka) is not a cite" | +| world_prior_cited | analogue, analog, analogous, analogy, literature, bide | "world_prior_cited: World analogue not cited — name an observed world analogue, or official null (no world analogue / literature only / bide waiting)" | +| house_install_cited | install, installation | "house_install_cited: House install not cited — name the installation repo/path inspected (CHECKLIST 11), or the official null (no house install)" | +| house_canon_cited | canon | "house_canon_cited: House canon not cited — write the word canon with the klappy:// URI you read (canon: klappy://...), or the official null (no house canon); a repo slug or stack folklore (CF, vodka) is not a cite" | --- @@ -64,4 +64,4 @@ The check vocabularies in this file are deliberately broad rather than narrow, b When the server cannot reach this file at runtime, `oddkit_gate` falls back to a hardcoded minimal vocabulary that mirrors the check terms above. The minimal tier is identified in the response envelope via `governance_source: "minimal"`; the canon tier via `governance_source: "knowledge_base"`. Both tiers use the same set-intersection matcher; they differ only in whether the vocabulary is editable by updating this file or locked to the deployed worker version. -The three exploration-to-planning presence ids (2026-09-21, kitchen ticket `2026-09-20-gate-explore-plan-prior-art`) omit shared tokens such as `house` and `cited` from their check columns so hyphen-split cannot leak a band: `no-house-install` in a check column would add `house`, and then `no house canon` would false-pass `house_install_cited`; `canon-cited` would add `cited`, and then ordinary cite language for the other two bands (`world prior cited`, `house install cited`) would false-pass `house_canon_cited`. Official nulls work from the input side (`no world analogue` contributes `analogue`). Do not put `prior-art` or `world-prior` in the check column: hyphen-split adds `prior`, and a problem statement that says prior art false-passes the band. Presence is not proof a tool ran; a dummy `klappy://` paste is the new cowboy — spawned audit, not this matcher. +The three exploration-to-planning presence ids (2026-09-21, kitchen ticket `2026-09-20-gate-explore-plan-prior-art`) omit shared tokens such as `house` and `cited` from their check columns so hyphen-split cannot leak a band: `no-house-install` in a check column would add `house`, and then `no house canon` would false-pass `house_install_cited`; `canon-cited` would add `cited`, and then ordinary cite language for the other two bands (`world prior cited`, `house install cited`) would false-pass `house_canon_cited`. Official nulls work from the input side (`no world analogue` contributes `analogue`). Do not put `prior-art` or `world-prior` in the check column: hyphen-split adds `prior`, and a problem statement that says prior art false-passes the band. No hyphenated entry belongs in any of these three check columns: `repo-path` and `inspected-repo` were removed from `house_install_cited` because hyphen-split added `repo`, `path`, and `inspect`, and `the path forward` false-passed the band. `klappy` was removed from `house_canon_cited` because every house repo slug (`klappy/oddkit`, `klappy.dev`) contains it, so an honest install cite paid the canon band too and the bands stopped being independent; the word `canon` is the only key, and the gap message says so. These three ids exist only in the canon tier: the Worker's minimal fallback still carries the original pair for this transition, so a run reporting `governance_source: "minimal"` can pass 2/2 — read the source field before trusting a PASS. Presence is not proof a tool ran, and negation passes (`did not read canon` contains `canon`); a dummy `klappy://` paste is the new cowboy — spawned audit, not this matcher.