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 subjectscripts-first-workflow-architecturereachescomplete, AND driving the subject surfaces engine bugs whose fixes land back inscripts-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-planGraph.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_byDAG 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 §05serve_next_with_cross_planmerge 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-planGraph.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, bothSeverity.HIGH) —CROSS_PLAN_CYCLECROSS_PLAN_DEAD_REFmirror 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_needsproperty lands here) + [ori:scripts/plan_corpus/schema_loader.py]. - Summary (≤500 chars): the hard-defer wraps the existing §05
serve_next_with_cross_planmerge (per_plan_route.py) without touching the per-plangraph.py:Graph.serve_next(the negative pin — intra-plan hints stay advisory per INV-6). Thecross_plan_needsschema field is additive on the v7plan-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_needstoscripts/plan_corpus/schemas/v7/plan-routing.schema.jsonon BOTHsections[]items ANDwork_items[]items: an OPTIONAL array (default[]) of objects{ "plan": "<canonical-relative-plan-dir-key>", "id": "<s- | w- node id>" }, whereplanis the canonical relative plan-dir key resolved acrossplans/+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_needsis intra-plan, advisory, never a gate (INV-6).cross_plan_needsis cross-plan, STRUCTURAL, a hard merge-layer gate. The two never alias; an intra-plan dependency is expressed by lexorank placement (+ optional advisoryhint_needs), a cross-plan dependency bycross_plan_needs. Across_plan_needswhoseplanequals the owning plan is a schema error (CROSS_PLAN_SELF_REF— use lexorank ordering intra-plan). -
plan_corpus checkrejects malformed entries: non-array, missingplan/idkey,idnot matching^(s|w)-[0-9a-z]{4,}$(base36, matching the actual node-id alphabet — NOT hex), orplannot 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-planserve_nextyields a candidate, resolve thecross_plan_needsof the served work_item AND its owning section. For each{plan, id}target, status-lookup the target node in that plan’splan.json; if ANY target is notcompleted, 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 (advisoryhint_needs, in-progress precedence, single-branch) are unchanged. The hard-defer is purely additive at the merge tier (defense: a unit test assertsgraph.serve_nextignorescross_plan_needsentirely — 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_needstarget is NOT a done-status (work remains; the deferral is recoverable), the §06 route-walk emits a NEWcross_plan_all_deferredexit_reason withcaller_action: log_and_skip,exit_code: 0,retry_policy: retry, registered inscripts/plan_corpus/exit_reasons.pyCANONICAL_EXIT_REASONSEXIT_REASON_ROUTING(mirroring theno_cross_plan_ready_noderegistration shape atexit_reasons.py:496+:1031). Theretrypolicy keeps the autopilot loop re-engaging on the next tick — under an acycliccross_plan_needsgraph (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-completedcorpus only — the terminal case where no work remains. Routing a transient all-deferred state toretry_policy: neveris theSTRUCTURE:autopilot-pause-leakfailure mode (a recoverable pause masquerading as a permanent halt). - The
cross_plan_all_deferredexit_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).
- When every candidate is cross-plan-deferred AND at least one
- 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]; thecross_plan_all_deferredexit_reason is registered withretry_policy: retryand its next_action mapping;deferred_reasons[]populated;status: complete.
09B.3 — Combined-graph cross-plan acyclicity + referential validators
- Author
CROSS_PLAN_DEAD_REF(HIGH): everycross_plan_needs{plan, id}MUST resolve to an existing node in the named plan’splan.json; a dangling target fires the finding (mirrorsBUG_BLOCKER_DEAD_REF).planresolves to a CANONICAL relative plan-dir key — the resolver spans activeplans/, archivedplans/completed/, ANDbug-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_needstarget 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 (eachcross_plan_needstarget → the dependent node); (c) the EXISTINGblocked_bybug-blocker DAG edges (eachblocked_bytarget → 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 formA.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, across_plan_needscycle would make the all-deferred state non-transient, and routing it tono_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-planauthoring/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_bybug-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 theblocks_section_close/subsection_depends_onordering 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 + §10depends_on§09B, NOT a schema field) with a realcross_plan_needsedge on §12’s node. CARRIER NOTE (Decision-05-compatible, does NOT depend on §10): underdecisions/05-three-plan-types.md§10 was RE-SCOPED — there is NO corpus-wide JSON conversion, so the §12cross_plan_needsedge MUST NOT be gated onscripts-first-restructurebecoming v7 via §10. Two carriers are Decision-05-compatible: (a) the explicit/opt-in JSON conversion ofscripts-first-restructure(a user-initiatedconvert_plan_dir_v7, independent of §10’s re-scoped migration), after which §12’swork_items[]/sections[]node carries thecross_plan_needsedge; OR (b) the up-chain mechanical gate already in §12 (§09.4 dogfood gate blocks §09 close until the subject iscomplete; §10depends_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 dependencyframing: the relationship is NOT merely “one-directional.” The COMPLETION gate is one-directional (subjectcompletegates 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_needsmechanism (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]andstatus: complete. - All success_criteria have
[x]. -
cross_plan_needsschema property additive — every existing v7plan.jsonvalidates 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) vsno_cross_plan_ready_node(terminal, never) exit-reason partition +deferred_reasons[]diagnostic shape.
- dead-ref + v6+v7 done-status acceptance + canonical-plan-key resolution (duplicate-basename +
completed-target) +
- 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-reviewpassed (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 madehint_needsadvisory-only, leaving cross-plan dependency with no structural enforcement (the §12 blocker was prose-declared). This section adds thecross_plan_needsschema 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_needsv7 schema field) IMPLEMENTED: added to$defs.Section+$defs.WorkIteminschemas/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 intovalidate_v7_plan_json;route_check._check_cross_plan_needs_resolve(corpus-gate plan-resolves,STRUCTURE:route-cross-plan-needs-unresolved); 14 pytest intests/test_cross_plan_needs.pygreen + 78-test route suite green. Genuine engine WORK remains in 09B.2 (merge-layer hard-defer +cross_plan_all_deferredexit_reason inper_plan_route.py), 09B.3 (combined-graphCROSS_PLAN_CYCLEover intra-order + cross-plan + bug-blocker edges + fullCROSS_PLAN_DEAD_REF+ v6/v7 done-status SSOT helper), 09B.4 (§12cross_plan_needsmigration + dogfood) — orchestrator section-work-incomplete cure recordedroute: executepinning 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 haltsextended_check_failfrom 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_graph—CROSS_PLAN_CYCLEHIGH over intra-plan completion-order +cross_plan_needs+ bug-blockerblocked_byedges; node-levelCROSS_PLAN_DEAD_REFHIGH); done-status SSOT helperroute_v7.is_cross_plan_target_satisfied(v6 complete/done + v7 completed/superseded); canonical-key resolverroute_check.resolve_cross_plan_dir;types.FindingSubtypeCROSS_PLAN_CYCLE + CROSS_PLAN_DEAD_REF; wired corpus-wide intoplan_corpus check(__main__). Surfaced+fixed two latent defects per ZERO-DEFERRAL:bug_validators._find_canonical_cyclerecursive→iterative DFS (real-corpus recursion-limit overflow; SSOT shared by bug-blocker + cross-plan), and a circular import (lazy-importresolve_cross_plan_dirinsidebuild_combined_graph).atomic_io._atomic_write_textextracted fromplan_orchestrator/markers. 5-case reconciliation testtests/test_cross_plan_cycle.py(12 tests) green; fullscripts/plan_corpus/tests/suite 2855 passed;plan_corpus check plans/scripts-first-restructureexit 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-messagesclean — 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).