Claim Phase Provenance and Dependency-Driven Reclassification
Status: design proposal + landed checker, 2026-06-09 Motivating defect: the inter-governance-workset generator’s ready classifier is phase-blind (scripts/generate_inter_governance_workset.py never consults implementation_phase), and nothing mechanically enforces the project’s “phase label follows dependency” principle. Claims surface in the wrong phase lane. Checker: scripts/check_claim_phase_consistency.py (report-only; does not mutate claims.yaml). Canon: memory/feedback_phase_label_follows_dependency.md, evidence/planning/commitment_closure_plan.md GAP-9, docs/architecture/invariant_types.md.
1. The principle this enforces
implementation_phase (v3 / v4 / v5 …) is a prediction about when a claim is expected to be built. It is not a permission gate. Two corollaries:
-
Dependency forces phase earlier. If a V3-scope claim genuinely depends on a claim labelled
v4+, that dependency reclassifies the dependency to V3 by definition. A thing needed to build a V3 commitment is, definitionally, V3 work. Only the sequencing / readiness is open – never the scope-permission. Av4label on a V3-necessary prerequisite is a bookkeeping error, not a deferral decision. -
Phase does not leak upward through instantiation. A
v4claim whose only reverse-dependents are themselvesv4, and which merely instantiates av3parent cluster, staysv4. The canonical case is SD-033e (frontopolar parallel-goal-deliberation module): its reverse-dependents MECH-264 / MECH-265 are bothv4; itinstantiates: SD-033(av3parent) but nothingv3depends on it. It staysv4. Its V3 obligation – registering existing mechanisms under the subdivision – is graph-registration, a separate axis from implementation phase (commitment_closure_plan.md GAP-9).
The defect is that nothing checks (1), and the GAP-9 case shows (2) is easy to conflate with a phase obligation. This document specifies a provenance schema to record why a claim carries its phase, and a checker that finds violations of (1) without committing violations of (2).
2. Phase-provenance schema (proposed claims.yaml fields)
These are proposed additions for human governance review. The checker does not require them to run – it computes derivation from the dependency graph live. The fields make a governance decision durable and auditable once a human accepts a reclassification, and let the checker distinguish “already adjudicated” from “newly leaking”.
- id: MECH-xxx
implementation_phase: v3 # the EFFECTIVE phase (unchanged field)
phase_provenance: assigned # assigned | derived (default: assigned)
phase_derived_from: ARC-036 # set iff provenance == derived; the v3 build
# commitment whose dependency forced this phase
phase_locked: false # default false; true = author asserts the phase
# is intrinsic and must NOT be auto-derived earlier
Field semantics:
| field | values | meaning |
|---|---|---|
phase_provenance | assigned | derived | assigned = author chose this phase as the claim’s intrinsic design scope. derived = the phase was forced earlier than its intrinsic scope by a dependency from a lower-phase build commitment. Absent => assigned. |
phase_derived_from | claim id | Required when derived; the V3 build commitment (or nearest root) whose prerequisite edge forced the phase. Provides the audit trail “this is V3 because X needs it.” |
phase_locked | bool | true = the author asserts the phase is intrinsic (e.g. a genuinely V4 frontopolar module). The checker will NOT propose silent reclassification; if a V3 build commitment nonetheless depends on it, that is surfaced as a CONFLICT for human adjudication, not an auto-derive. Default false. |
Key property: implementation_phase continues to hold the effective phase (what the rest of the pipeline – the V3-pending gate, the indexer – reads). The provenance fields annotate how that value was arrived at. A reclassified claim ends up with implementation_phase: v3, phase_provenance: derived, phase_derived_from: <root> – so a reader sees both that it is V3 work and that its intrinsic design scope was later, deferred only by sequencing.
This is deliberately parallel to the existing invariant_type / emergent_from pattern (invariant_types.md): a structural field (phase_provenance) plus a provenance pointer (phase_derived_from) mirroring emergent_from.
Registration-obligation axis (kept distinct from phase)
GAP-9 (SD-033c / SD-033d / SD-033e) shows a claim can be V4 implementation yet carry a V3 graph-completeness obligation (register the existing mechanisms that sit under the subdivision; finish the claim-graph wiring). These must not be merged into implementation_phase. The registration obligation is tracked where it already lives – the closure plan’s gap nodes (commitment_closure:GAP-9) – and, optionally, as a dedicated boolean that does not affect phase:
graph_registration_pending: true # OPTIONAL: a V3-scope claim-graph-completeness
# task exists, INDEPENDENT of implementation_phase.
# SD-033e: implementation_phase v4 AND this true.
The checker treats graph_registration_pending as orthogonal: it never reads it as a phase signal and never proposes changing implementation_phase because of it. This is the explicit guard against the GAP-9 conflation.
3. The dependency-closure checker
scripts/check_claim_phase_consistency.py walks the claim graph transitively and reports reclassification candidates. It is report-only: it prints / emits JSON, and (in --strict) sets a non-zero exit. It never edits claims.yaml.
3.1 Phase parsing
implementation_phase is free-text-tolerant in the live registry (v3, v4, v4), v4)., v5, and long records that begin with the phase token). The checker extracts the leading v?(\d+). A claim’s effective phase is the explicit implementation_phase if present, else 3 when v3_pending: true, else unphased.
3.2 Edge semantics – what propagates phase pressure
A “needer -> need” prerequisite edge propagates phase pressure (the needer cannot be built before the need):
| edge | propagates? | rationale |
|---|---|---|
depends_on | yes | a build prerequisite. |
emergent_from | yes, STRONGER | the dependent invariant’s subject is ill-formed without the substrate (invariant_types.md). A v3 emergent invariant whose emergent_from substrate is v4 is a hard leak. emergent_from is a subset of depends_on; the checker de-duplicates and marks the candidate strength: strong. |
instantiates | no | a parent-cluster registration relation, not a build prerequisite, in either direction. The instantiates target is removed from the depends_on edge set. This is what keeps SD-033e from being over-pulled and keeps a v3 child that instantiates a v3 parent from spuriously “needing the parent built first.” |
3.3 Driver gating – which claims may force a reclassification
Only a V3-scope build commitment drives reclassification. A claim qualifies as a driver iff all of:
- effective phase == 3,
statusnot in {resolved, superseded, deprecated, retracted, withdrawn},claim_typeis notopen_question,polaritynot in {asks, open, open_question}.
Open questions and pure answer-state claims do not force their prerequisites to be built: being “V3” for a question means “we want to answer it in V3,” which a deferred-substrate answer (“the answer is: wait for V4”) satisfies. These are reported in a separate informational bucket, never as candidates. (polarity: denies / records claims remain drivers – they are still V3 commitments – but rarely have v4 deps.)
3.4 Two-phase model: ROOT vs CASCADE
Naively unioning drivers across a transitive walk conflates “directly needs X” with “needs something three hops past X,” producing useless 200-id driver lists. The checker instead separates:
- ROOT – a V3 build commitment depends directly (one prerequisite edge) on a
v4+claim. This is the actual leak; itsdirect_driversare the v3 claims responsible and are what a human fixes first. - CASCADE – a
v4+claim not directly needed by any v3 build commitment, but which becomes V3-necessary once a ROOT is reclassified (it is a prerequisite of a root, reached through other to-be-reclassifiedv4nodes).follows_fromnames the root(s). Cascade closure stops atphase_lockednodes – a contested node must not silently cascade.
3.5 Verdicts
- RECLASSIFY (
phase_lockedfalse): proposeimplementation_phase: v3,phase_provenance: derived,phase_derived_from: <root driver>. - CONFLICT (
phase_lockedtrue on a claim a V3 build commitment needs): the lock and the dependency contradict. A human must either sever / replace the dependency or clear the lock. Never auto-reclassified; never cascaded through.
3.6 Edge cases handled
- instantiates vs depends_on – instantiation edge dropped (3.2); a child instantiating a
v3parent is not av3dependency, and SD-033e is not over-pulled. - polarity asserts vs questions – questions excluded from drivers (3.3), routed to informational.
- emergent / universal / grey_zone invariants –
emergent_fromis a strong pulling edge; universal invariants carry noemergent_fromand so never pull; grey_zone invariants pull only via whateverdepends_onthey declare (no special casing – their uncertainty is about classification, not dependency). - cycles – each walk carries a per-seed
visitedset; cyclicdepends_onterminates. - dangling deps – a
depends_onid with no matching claim is reported under “Dangling dependency references” (hygiene), never silently dropped. - registration obligation – never read as a phase signal (Section 2).
- idempotency – a claim already at
implementation_phase: v3withphase_provenance: derivedis simply a V3 claim and never re-flagged; the schema is stable under re-running the checker after a governance decision lands.
3.7 CLI
python scripts/check_claim_phase_consistency.py # human report, exit 0
python scripts/check_claim_phase_consistency.py --json # machine report, exit 0
python scripts/check_claim_phase_consistency.py --warn # one WARN line per ROOT, exit 0
python scripts/check_claim_phase_consistency.py --strict # exit 1 if any candidate
Importable API (for the generator / governance.sh):
from check_claim_phase_consistency import load_claims, reclassification_candidates
report = reclassification_candidates(load_claims())
leak_ids = set(report["candidates"]) # {claim_id: {verdict, kind, ...}}
4. Integration
4.1 governance.sh (recommended: warn-only first, mirroring validate_claims)
Add a non-blocking step after the strict claims validation, so every governance cycle surfaces phase leaks in its log without blocking the pipeline:
echo "--- Step 0b: Claim phase-consistency (warn-only) ---"
"$PYTHON" scripts/check_claim_phase_consistency.py --warn || true
Promote to --strict (blocking) once the current backlog of leaks (Section 5) is adjudicated – exactly the trajectory validate_claims.py took (warn-only -> strict gate at the top of governance.sh). Until then, blocking would wedge every cycle on a pre-existing backlog.
4.2 inter-governance-workset generator (the leak point)
generate_inter_governance_workset.py decides ready without consulting implementation_phase. Recommended minimal patch (documented here rather than applied inline, because the generator is high-contention and was last edited by a concurrent session): import the checker’s candidate set once in build_workset() and annotate / down-rank any item whose claim_ids intersect a RECLASSIFY/CONFLICT candidate:
# near the top of build_workset()
try:
from check_claim_phase_consistency import (
load_claims as _load_claims_full,
reclassification_candidates,
)
_phase_leaks = set(reclassification_candidates(_load_claims_full())["candidates"])
except Exception:
_phase_leaks = set()
# in add(...) or the post-build enrichment loop, for each item:
leaked = [c for c in (item.get("claim_ids") or []) if c in _phase_leaks]
if leaked:
item["phase_leak"] = sorted(leaked)
# a phase-leaking claim is not cleanly 'ready' -- its phase lane is contested
if item.get("status") == "ready":
item["status"] = "blocked"
item.setdefault("blocked_by", []).append(
f"phase-consistency: depends on phase-leaking claim(s) {', '.join(leaked)} "
f"-- run check_claim_phase_consistency.py"
)
This closes the leak going forward: a work package that touches a claim whose phase lane is contested no longer surfaces as cleanly ready – it is marked blocked with a pointer to the checker, exactly as substrate-blocked retests are today. The annotation is keyed on the checker’s authoritative candidate set, so the two never drift.
5. Live results (against docs/claims/claims.yaml, 2026-06-09)
python scripts/check_claim_phase_consistency.py over 685 claims / 249 V3 build-commitment seeds finds 12 ROOT leaks, 3 CASCADE candidates, 0 CONFLICTs (plus 10 dangling MECH-057 references – a deleted/renamed claim still named in depends_on). SD-033e is correctly absent (closed v4 triangle with MECH-264 / MECH-265; over-pull guard holds).
ROOT leaks (act on these first)
| candidate (now) | directly needed by V3 build commitment(s) | proposed |
|---|---|---|
| ARC-031 (v4) | MECH-205, MECH-208 | -> v3 derived from MECH-205 |
| ARC-039 (v4) | MECH-261 | -> v3 derived from MECH-261 |
| ARC-053 (v4) | ARC-054 | -> v3 derived from ARC-054 |
| ARC-072 (v4) | SD-055 | -> v3 derived from SD-055 |
| INV-062 (v4) | MECH-213 | -> v3 derived from MECH-213 |
| MECH-121 (v4) | MECH-165, MECH-203, MECH-205, MECH-212 | -> v3 derived from MECH-165 |
| MECH-123 (v4) | MECH-204, MECH-213 | -> v3 derived from MECH-204 |
| MECH-124 (v4) | ARC-036, MECH-208 | -> v3 derived from ARC-036 |
| MECH-210 (v4) | MECH-213 | -> v3 derived from MECH-213 |
| MECH-274 (v4) | ARC-059 | -> v3 derived from ARC-059 |
| MECH-308 (v4) | MECH-330 | -> v3 derived from MECH-330 |
| MECH-326 (v4) | SD-055 | -> v3 derived from SD-055 |
CASCADE candidates (become V3-necessary once the roots above are reclassified)
| candidate (now) | follows from root(s) | example path |
|---|---|---|
| MECH-122 (v4) [strong: emergent_from] | ARC-031, ARC-039, MECH-121, MECH-123, MECH-124, … | MECH-121 -> MECH-122 |
| MECH-209 (v4) | INV-062, MECH-210 | INV-062 -> MECH-209 |
| MECH-325 (v4) | ARC-072, MECH-326 | ARC-072 -> MECH-325 |
These are candidates for human governance review, not automatic edits. Each proposed reclassification should be confirmed in a /governance cycle (is the dependency real, or should the edge be cut?). The checker’s job is to make every such leak visible and attributable; the adjudication – reclassify the dependency, or sever the dependency, or phase_locked the prerequisite and re-scope the needer – stays with governance.
The MECH-121..124 cluster (the “epistemic / consolidation” mechanism block) and ARC-039 are foundational emergent-substrate references depended on widely; they are the highest-value items to adjudicate first because their resolution determines the cascade.
6. What this is not
- Not an auto-migrator. It proposes; governance disposes. No
claims.yamlfield is written by the checker. - Not a change to confidence scoring, the V3-pending gate, or substrate promotion.
- Not a merge of implementation phase with the graph-registration obligation – Section 2 keeps them on separate axes by construction (the GAP-9 guard).
- Not a claim that
v4is “wrong.” Av4label is right until av3build commitment depends on it; then, by the principle, only the label was a prediction that the dependency has overtaken.