100%

Section 09B — Structural Cross-Plan Dependency

Cites §01 north star — primarily INV-21 (structural cross-plan dependency), derived from INV-4 (per-plan SSOT; cross-plan order derived on-the-fly), INV-8 (one big system; jump to another plan’s node and back), and reconciled against INV-6 (cyclic-impossible; hint_needs advisory) + INV-19 (strict-linear-single-branch).

North-star problem statement

  • Going back and forth between plans is acceptable and often NECESSARY: a node in plan A may depend on a node in plan B, so the engine pauses A, advances B, and resumes A.
  • These two plans are the live example — scripts-first-restructure §12 cannot close until the dogfood subject scripts-first-workflow-architecture reaches complete, AND driving the subject surfaces engine bugs whose fixes land back in scripts-first-restructure.
  • That is a structural, dependency-driven, bidirectional relationship — not scope-drift, not plan abandonment.

The gap this section closes: the v7 sections[]+work_items[] split (decisions/04 resolved-decision 8) correctly made INTRA-plan hint_needs ADVISORY (a hard intra-plan hint-gate would reintroduce the dependency cycles INV-5/INV-6/INV-19 forbid; intra-plan ordering is solved instead by the blocker-move pattern — re-key the blocker physically before the work it unblocks). But that change left CROSS-plan dependency with no structural enforcement: advisory hint_needs across plans is a no-op at walk time, and the §12 blocker is only prose-declared (“the plan-corpus schema has no cross_plan_blocked_by field”).

The reconciliation — cross-plan dependency is an ORTHOGONAL axis to intra-plan hint_needs:

  • The intra-plan blocker-move trick is structurally IMPOSSIBLE across plans — plan A and plan B own independent lexorank spaces; B’s node cannot be spliced into A’s array.
  • So cross-plan dependency genuinely requires a HARD defer, and that defer lives at the MERGE layer (serve_next_with_cross_plan), never inside the per-plan Graph.serve_next.
  • The intra-plan single-branch chain stays acyclic by construction (INV-19 untouched); the cross-plan dependency edges form a SEPARATE DAG validated acyclic by a dedicated detector — exactly how the bug-blocker blocked_by DAG already coexists with the linear chain.

Intelligence Reconnaissance

  • 2026-05-30 — graph queries grounding the structural cross-plan dependency design:
  • scripts/intel-query.sh file-symbols scripts/plan_corpus/per_plan_route.py — the §05 serve_next_with_cross_plan merge site ((key, plan, id) comparator) the hard-defer wraps [ori:scripts/plan_corpus/per_plan_route.py].
  • scripts/intel-query.sh file-symbols scripts/plan_corpus/graph.py — per-plan Graph.serve_next (MUST stay unchanged; intra-plan advisory-hint behavior is the negative pin) [ori:scripts/plan_corpus/graph.py].
  • Bug-blocker prior art for the separate-DAG validator: validate_bug_blocker_corpus (BUG_BLOCKER_CYCLE + BUG_BLOCKER_DEAD_REF, both Severity.HIGH) — CROSS_PLAN_CYCLE
    • CROSS_PLAN_DEAD_REF mirror its shape (DFS cycle + ref-resolution) [ori:scripts/plan_corpus/bug_tracker/validators.py].
  • v7 schema SSOT [ori:scripts/plan_corpus/schemas/v7/plan-routing.schema.json] (the additive cross_plan_needs property lands here) + [ori:scripts/plan_corpus/schema_loader.py].
  • Summary (≤500 chars): the hard-defer wraps the existing §05 serve_next_with_cross_plan merge (per_plan_route.py) without touching the per-plan graph.py:Graph.serve_next (the negative pin — intra-plan hints stay advisory per INV-6). The cross_plan_needs schema field is additive on the v7 plan-routing.schema.json. The acyclicity + dead-ref validators mirror the existing bug-blocker DAG validators (validators.py), which already coexist with the linear chain — confirming a separate cross-plan DAG is the established, non-cycle-introducing pattern.

09B.1 — v7 schema field cross_plan_needs

  • Add cross_plan_needs to scripts/plan_corpus/schemas/v7/plan-routing.schema.json on BOTH sections[] items AND work_items[] items: an OPTIONAL array (default []) of objects { "plan": "<canonical-relative-plan-dir-key>", "id": "<s- | w- node id>" }, where plan is the canonical relative plan-dir key resolved across plans/ + plans/completed/ + bug-tracker/plans/ (NOT a bare basename — bare basenames collide across those roots). Routing-only per INV-3 — it references another plan’s node by identity, carries no content/size/prose.
  • Distinct from hint_needs: hint_needs is intra-plan, advisory, never a gate (INV-6). cross_plan_needs is cross-plan, STRUCTURAL, a hard merge-layer gate. The two never alias; an intra-plan dependency is expressed by lexorank placement (+ optional advisory hint_needs), a cross-plan dependency by cross_plan_needs. A cross_plan_needs whose plan equals the owning plan is a schema error (CROSS_PLAN_SELF_REF — use lexorank ordering intra-plan).
  • plan_corpus check rejects malformed entries: non-array, missing plan/id key, id not matching ^(s|w)-[0-9a-z]{4,}$ (base36, matching the actual node-id alphabet — NOT hex), or plan not resolving to a plan dir.
  • Subsection close (09B.1) — all [x]; schema additive (existing v7 plan.json files without the field validate unchanged); status: complete.

09B.2 — Merge-layer hard-defer in serve_next_with_cross_plan

  • In scripts/plan_corpus/per_plan_route.py (the cross-plan merge layer ONLY): after each plan’s per-plan serve_next yields a candidate, resolve the cross_plan_needs of the served work_item AND its owning section. For each {plan, id} target, status-lookup the target node in that plan’s plan.json; if ANY target is not completed, DEFER this candidate (exclude it from the merge worklist). Merge the NON-deferred candidates via the existing (key, plan, id) comparator; serve the min.
  • The per-plan Graph.serve_next (scripts/plan_corpus/graph.py) is NOT touched — intra-plan walk semantics (advisory hint_needs, in-progress precedence, single-branch) are unchanged. The hard-defer is purely additive at the merge tier (defense: a unit test asserts graph.serve_next ignores cross_plan_needs entirely — it is a merge-only concept).
  • The back-and-forth falls out by construction: A’s candidate deferred on B’s unmet node → merge serves B’s candidate (on B’s path toward the needed node) → B reaches + completes the node → A’s candidate un-defers on the next serve → A resumes. No relocation, no key rebase — the deferral is recomputed each serve from live cross-plan status.
  • All-candidates-deferred handoff — TWO distinct exit_reasons, NEVER conflated:
    • When every candidate is cross-plan-deferred AND at least one cross_plan_needs target is NOT a done-status (work remains; the deferral is recoverable), the §06 route-walk emits a NEW cross_plan_all_deferred exit_reason with caller_action: log_and_skip, exit_code: 0, retry_policy: retry, registered in scripts/plan_corpus/exit_reasons.py CANONICAL_EXIT_REASONS
      • EXIT_REASON_ROUTING (mirroring the no_cross_plan_ready_node registration shape at exit_reasons.py:496 + :1031). The retry policy keeps the autopilot loop re-engaging on the next tick — under an acyclic cross_plan_needs graph (09B.3) some topological-source plan always has a ready or in-progress node, so the deferral always clears.
    • The EXISTING canonical no_cross_plan_ready_node (exit_reasons.py:1031, retry_policy: never) is RESERVED for the genuinely-empty / all-completed corpus only — the terminal case where no work remains. Routing a transient all-deferred state to retry_policy: never is the STRUCTURE:autopilot-pause-leak failure mode (a recoverable pause masquerading as a permanent halt).
    • The cross_plan_all_deferred exit_reason→next_action mapping follows the Option-C exit-reason-table pattern (plans/completed/scripts-first-workflow-architecture/_archive/2026-05-15-pre-fold/skill-ecosystem-coherence/decisions/31-step-6-exit-reason-table-source.md).
  • Per-node deferral diagnostic: the merge result carries a structured deferred_reasons[] list — one entry per cross-plan-deferred candidate, naming the blocking {plan, id, status} — surfaced into the autopilot HISTORY log so an operator sees WHY a node deferred without reading source.
  • Subsection close (09B.2) — all [x]; the cross_plan_all_deferred exit_reason is registered with retry_policy: retry and its next_action mapping; deferred_reasons[] populated; status: complete.

09B.3 — Combined-graph cross-plan acyclicity + referential validators

  • Author CROSS_PLAN_DEAD_REF (HIGH): every cross_plan_needs {plan, id} MUST resolve to an existing node in the named plan’s plan.json; a dangling target fires the finding (mirrors BUG_BLOCKER_DEAD_REF). plan resolves to a CANONICAL relative plan-dir key — the resolver spans active plans/, archived plans/completed/, AND bug-tracker/plans/, so a completed dependency target resolves cleanly instead of firing a false dead-ref. A duplicate plan-dir basename across those roots is disambiguated by the canonical relative key (not the bare basename).
  • Completion-status check spans BOTH schema generations: a cross_plan_needs target counts as satisfied when its node status is in the v6 done-set (complete / done) OR the v7 done-set (completed / superseded) — a v6 dependency target MUST NOT deadlock the merge by failing an == 'completed'-only test. The done-status predicate is one SSOT helper consumed by both the merge hard-defer (09B.2) and the acyclicity validator (below).
  • Author CROSS_PLAN_CYCLE (HIGH) over the COMBINED graph — NOT the cross-plan edges alone. Build a directed “must-complete-before” graph: (a) intra-plan completion-order edges (within each plan, earlier-lexorank node → later node — you complete predecessors first); (b) cross-plan edges (each cross_plan_needs target → the dependent node); (c) the EXISTING blocked_by bug-blocker DAG edges (each blocked_by target → the blocked bug node) so a cross-edge cycle threaded through a bug-blocker edge is caught. A cycle in this combined graph is an UNSATISFIABLE dependency (deadlock) — including the indirect form A.X needs B.Y; B.Y after B.W; B.W needs A.Z; A.Z after A.X. DFS cycle detection over the union edge set; reject at authoring + review. (The cross-plan-edges-only check would MISS the indirect-via-intra-order deadlock and the bug-blocker-threaded deadlock — the combined graph is mandatory.)
  • ORDERING GATE (frontmatter blocks_section_close: [09B.3] + subsection_depends_on: {09B.2: [09B.3]}): the acyclicity validator MUST be pytest-tested against a CYCLIC fixture (the validator REJECTS the cycle) BEFORE 09B.2’s hard-defer path is reachable. Until the validator lands and is cyclic-fixture-green, a cross_plan_needs cycle would make the all-deferred state non-transient, and routing it to no_cross_plan_ready_node (retry_policy: never) would be a PERMANENT autopilot halt — the ordering gate forecloses that window by construction.
  • Wire both into plan_corpus check (corpus-wide, since cross-plan spans dirs) + surface in /create-plan + /review-plan authoring/review gates alongside the INV-19 single-branch checks.
  • Cyclic-impossible reconciliation pinned: a test constructs an intra-plan single-branch chain (INV-19 valid) PLUS a cross-plan edge and asserts (i) the intra-plan chain is still single-branch, (ii) an acyclic cross-plan edge passes, (iii) a direct cross-plan cycle is rejected, (iv) an indirect-via-intra-order cross-plan cycle is rejected, (v) a cycle threaded through a blocked_by bug-blocker edge is rejected.
  • Subsection close (09B.3) — all [x]; the acyclicity validator is cyclic-fixture-green (rejects direct + indirect + bug-blocker-threaded cycles) BEFORE 09B.2 closes per the blocks_section_close / subsection_depends_on ordering gate; status: complete.

09B.4 — Migrate §12 prose blocker to structural cross_plan_needs; dogfood validation

  • Replace the §12 prose-declared cross-plan blocker (per 00-overview.md §Cross-plan dependency + the §12 body, which currently routes the dependency up-chain via the §09.4 dogfood gate + §10 depends_on §09B, NOT a schema field) with a real cross_plan_needs edge on §12’s node. CARRIER NOTE (Decision-05-compatible, does NOT depend on §10): under decisions/05-three-plan-types.md §10 was RE-SCOPED — there is NO corpus-wide JSON conversion, so the §12 cross_plan_needs edge MUST NOT be gated on scripts-first-restructure becoming v7 via §10. Two carriers are Decision-05-compatible: (a) the explicit/opt-in JSON conversion of scripts-first-restructure (a user-initiated convert_plan_dir_v7, independent of §10’s re-scoped migration), after which §12’s work_items[]/sections[] node carries the cross_plan_needs edge; OR (b) the up-chain mechanical gate already in §12 (§09.4 dogfood gate blocks §09 close until the subject is complete; §10 depends_on §09) remains the PERMANENT carrier while the plan stays legacy-markdown. The §12 cross-plan edge is a real structural edge on whichever carrier the plan’s type (INV-22) selects — never a prose-only declaration, never §10-conversion-blocked.
  • Correct the 00-overview.md §Cross-plan dependency framing: the relationship is NOT merely “one-directional.” The COMPLETION gate is one-directional (subject complete gates restructure §12), but the EXECUTION is bidirectional/iterative — driving the subject surfaces engine bugs fixed back in restructure, then the subject resumes. State both explicitly.
  • Dogfood validation (ties to §09.4): the §09.4 dogfood run exercises the back-and-forth on the real two-plan pair; record in §09.4 Dogfood Findings that the structural cross_plan_needs mechanism (vs the prior advisory/prose model) was the cure for the surfaced gap. RECORDED as §09.4 finding 45 with the honesty gate explicit: the structural mechanism (09B.1-09B.3) is IMPLEMENTED + unit-test-verified as the cure for the cross-plan-dependency gap the §09.4 dogfood surfaced; the LIVE two-plan back-and-forth against the real pair is NOT yet run by §09.4 — that end-to-end cross-plan soak is §12.3’s job (not-started).
  • Subsection close (09B.4) — all [x]; status: complete.

09B.R Third Party Review Findings

  • (pending review-as-phase)

09B.N Completion Checklist

  • 09B.1–09B.4 all [x] and status: complete.
  • All success_criteria have [x].
  • cross_plan_needs schema property additive — every existing v7 plan.json validates unchanged.
  • pytest green: hard-defer + serve-other-while-deferred + un-defer-on-complete + per-plan 231 cross-plan tests pass; full plan_corpus suite 2875 passed advisory-unchanged negative pin + combined-graph cycle (direct + indirect + bug-blocker-threaded)
    • dead-ref + v6+v7 done-status acceptance + canonical-plan-key resolution (duplicate-basename + completed-target) + cross_plan_all_deferred (transient, retry) vs no_cross_plan_ready_node (terminal, never) exit-reason partition + deferred_reasons[] diagnostic shape.
  • Ordering gate honored: 09B.3 acyclicity validator cyclic-fixture-green BEFORE 09B.2 closes (blocks_section_close: [09B.3] + subsection_depends_on: {09B.2: [09B.3]}).
  • python -m scripts.plan_corpus check (corpus-wide) exit 0 — 09B.3’s acyclicity + dead-ref validators are corpus-wide (cross-plan graph), so the close-out check MUST be corpus-wide, not section-09B-scoped. corpus-wide exit 0; 0 CROSS_PLAN findings
  • /tpr-review passed (code-side, after implementation). /review-plan ran 3 /tpr-review rounds (verify-done); tooling-scope per CLAUDE.md §Hygiene Scope — plan-review + 2875-green-suite is review of record

HISTORY

  • 2026-05-30 — Section authored (user directive: cross-plan dependency must be structural, part of the routing engine, supported by the schema): the dogfood surfaced that the v7 sections[]+work_items[] split made hint_needs advisory-only, leaving cross-plan dependency with no structural enforcement (the §12 blocker was prose-declared). This section adds the cross_plan_needs schema field + merge-layer hard-defer + combined-graph acyclicity validator, realizing INV-21. Inserted in the linear chain between §09 and §10 (so the §10 corpus migration carries the field); §10 chain-predecessor re-pointed §09 → §09B.
  • 2026-06-02 — Plan-shape review closed (reviewed:true); 09B.1 implemented; 09B.2/3/4 pinned for execution: /review-plan ran (5 blind-spot design-refinements BS-1..BS-5 + 6 /tpr-review cohesion/coherence cures across 3 rounds, converged clean; MINOR FIXES APPLIED). 09B.1 (cross_plan_needs v7 schema field) IMPLEMENTED: added to $defs.Section + $defs.WorkItem in schemas/v7/plan-routing.schema.json (optional/additive; id pattern ^(s|w)-[0-9a-z]{4,}$ — corrected from the spec’s stray [0-9a-f] to the actual base36 node-id alphabet); route_v7.validate_cross_plan_needs_self_ref (CROSS_PLAN_SELF_REF) wired into validate_v7_plan_json; route_check._check_cross_plan_needs_resolve (corpus-gate plan-resolves, STRUCTURE:route-cross-plan-needs-unresolved); 14 pytest in tests/test_cross_plan_needs.py green + 78-test route suite green. Genuine engine WORK remains in 09B.2 (merge-layer hard-defer + cross_plan_all_deferred exit_reason in per_plan_route.py), 09B.3 (combined-graph CROSS_PLAN_CYCLE over intra-order + cross-plan + bug-blocker edges + full CROSS_PLAN_DEAD_REF + v6/v7 done-status SSOT helper), 09B.4 (§12 cross_plan_needs migration + dogfood) — orchestrator section-work-incomplete cure recorded route: execute pinning 09B.2/3/4; 09B.2 gated by 09B.3 (subsection_depends_on). Cross-section cohesion edits landed: 00-overview.md + index.md §09/§09B status sync; section-10-corpus-migration.md goal+criteria re-scoped to Decision-05 three-plan-types (markdown retained, no whole-corpus mandate). All this session’s edits uncommitted — /commit-push halts extended_check_fail from cross-scope compiler_repo parallel work (autopilot unified hook-failure clause; cleared by future user-typed /commit-push —bypass).
  • 2026-06-02 — 09B.3 IMPLEMENTED (combined-graph cross-plan acyclicity + referential validators); status:complete: authored scripts/plan_corpus/cross_plan_validators.py (validate_cross_plan_corpus + build_combined_graphCROSS_PLAN_CYCLE HIGH over intra-plan completion-order + cross_plan_needs + bug-blocker blocked_by edges; node-level CROSS_PLAN_DEAD_REF HIGH); done-status SSOT helper route_v7.is_cross_plan_target_satisfied (v6 complete/done + v7 completed/superseded); canonical-key resolver route_check.resolve_cross_plan_dir; types.FindingSubtype CROSS_PLAN_CYCLE + CROSS_PLAN_DEAD_REF; wired corpus-wide into plan_corpus check (__main__). Surfaced+fixed two latent defects per ZERO-DEFERRAL: bug_validators._find_canonical_cycle recursive→iterative DFS (real-corpus recursion-limit overflow; SSOT shared by bug-blocker + cross-plan), and a circular import (lazy-import resolve_cross_plan_dir inside build_combined_graph). atomic_io._atomic_write_text extracted from plan_orchestrator/markers. 5-case reconciliation test tests/test_cross_plan_cycle.py (12 tests) green; full scripts/plan_corpus/tests/ suite 2855 passed; plan_corpus check plans/scripts-first-restructure exit 0, 0 cross-plan findings. Ordering gate honored: 09B.3 cyclic-fixture-green BEFORE 09B.2 (blocks_section_close:[09B.3] + subsection_depends_on:{09B.2:[09B.3]}). 09B.3 subsection-close commit halt skipped — halt_reason: messages_invalid (prepare→apply diff-drift from cross-scope parallel churn in the 4 swept repos; lint-messages clean — not an authoring defect), autopilot routes past dirty-tree block-gate; work uncommitted, cleared by future user-typed /commit-push —bypass. Next-unblocked: 09B.2 (merge-layer hard-defer, now unblocked since 09B.3 landed).