Tasks: Org-Tier Doctrine Reaches Its Consumers

Mission: up-org-doctrine-consumers-01M05YAB · Branch: pr/up-org-doctrine-consumers-01M05YAB Input: plan.md (IC-01…IC-05 Implementation Concern Map, two mandatory lockstep pairs), spec.md (FR-001–FR-008 incl. FR-006a, NFR-001–006, C-001–006, SC-001–007).

> Reference rows below are not checkboxes. Subtask completion is event-sourced — > record with spec-kitty agent tasks mark-status T0xx --status done. The reduced > event-log snapshot is the sole authority.

> Verification Bar (binding on every WP, NFR-001): every FR fix must be proven by a > before/after measurement — a count or boolean that changes in a committed regression test — > never merely "no exception was raised." The DRG probe for FR-002 is 347 → 348 nodes in > this checkout (verified live during spec/plan authoring, D-000(4)); the issue's own > "347 → 350" figure came from a different org pack and is not reproducible here — do not > quote it.

Lockstep pairs (hard constraints — do not split across packages)

(_build_repository) must land in the same work package as src/specify_cli/mission_step_contracts/executor.py:160 (StepContractExecutor.__init__). Splitting them leaves an org-tier contract's delegations resolving while its gates: block silently never fires — review gates pass while verifying nothing. Both in WP02.

(_resolve_contract_refs) must land in the same work package as src/runtime/next/runtime_bridge_composition.py:252 (_resolve_runtime_contract_for_step). Splitting them converts a consistent "always invisible" org-tier contract_ref into an inconsistent "accepted at runtime, rejected at load-time" (or vice versa). Both in WP03.

  • Lockstep Pair A (User Story 3 / IC-02): src/specify_cli/review/gate_bindings.py:168
  • Lockstep Pair B (User Story 2 / IC-03): src/specify_cli/mission_loader/command.py:237

Requirement → Work-Package coverage

FRWPFRWP
FR-001WP02FR-006WP03
FR-002WP02FR-006aWP03
FR-003WP01FR-007WP03
FR-004WP04FR-008WP05
FR-005WP02

Subtask Index

IDDescriptionWPParallel
T001Implement resolve_org_dirs(repo_root, subdir) in src/doctrine/drg/org_pack_config.py + register in __all__WP01
T002Unit tests: no org packs → []; one pack → [root/subdir] (C-1 invariant)WP01
T003Unit tests: two-pack declaration-order precedence (NFR-003) + nonexistent org root filtered before join (NFR-002)WP01
T004Verify src/doctrine/* diff-cover critical-path gate and test_no_legacy_terminology.py pass locally (NFR-006)WP01
T005FR-001: executor.py __init__ threads org_dirs via resolve_org_dirs(repo_root, "mission_step_contracts")WP02
T006FR-002: executor.py execute() threads an inline first-match org_root into load_validated_graphWP02
T007FR-005: gate_bindings.py _build_repository threads the same org_dirs helper — lockstep mirror of T005WP02
T008Red-first regression tests for FR-001/FR-002 in test_executor.py (SC-001 DRG count delta 347→348, SC-002 None→contract)WP02
T009Activation-interaction test in test_executor_activation.py proving C-001 (activation filtering unchanged)WP02
T010Shared-fixture FR-005 test in tests/review/test_gate_bindings.py (SC-003, gate-bindings third)WP02
T011FR-006: runtime_bridge_composition.py _resolve_runtime_contract_for_step threads org_dirsWP03
T012FR-006a: mission_loader/command.py _resolve_contract_refs threads the same org_dirs helper — lockstep mirror of T011WP03
T013Shared-fixture SC-003 test (FR-006) in tests/runtime/test_bridge_composition.pyWP03
T014Shared-fixture SC-003 test (FR-006a) in tests/unit/mission_loader/test_command.py + identical-failure-when-org-pack-absent test (User Story 2 AS3)WP03
T015FR-007: _dispatch_via_composition logs one WARNING per step with 1+ unresolved delegation candidatesWP03
T016FR-007 caplog positive/negative test (SC-004) in tests/runtime/test_bridge_composition.pyWP03
T017FR-004: _mission_type_profile_repository threads org_dirs via resolve_org_dirs(repo_root, "mission_types") into MissionTypeProfileRepository.for_projectWP04
T018Regression test: org-tier governance-profile.yaml override reflected in resolve_mission_type_context(...).governance_textWP04
T019Regression guard: action_grain.py:220's deliberately built-in-only call site stays unchanged (D-004 boundary)WP04
T020Verify src/charter/* diff-cover ≥90% and architectural sole-door gates green locally (NFR-005)WP04
T021Implement resolve_org_expected_artifacts(org_roots, mission_type) in new src/charter/org_expected_artifacts.py per contract C-4WP05
T022_resolve_expected_artifacts_slot calls the new helper first, falls back to built-in-only when NoneWP05
T023ManifestRegistry.load_manifest cache-key fix: add `repo_root: Path \None = None, cache key becomes (mission_type, tuple(sorted org roots))`WP05
T024ManifestRegistry.load_manifest calls resolve_org_expected_artifacts when repo_root is given, preserving byte-identical no-override behaviorWP05
T025Unit tests for resolve_org_expected_artifacts (no override→None, one root, later-org-root-wins precedence, no-built-in-baseline custom type)WP05
T026Integration tests: _resolve_expected_artifacts_slot org override (SC-005 count/content delta) + whole-file-precedence testWP05
T027Integration tests: ManifestRegistry.load_manifest org override (SC-005) + cache-key regression (two repo_roots don't shadow each other) + byte-identical Given #2 testWP05

Work Packages

WP01 — Shared org_dirs resolution helper (IC-01) · FR-003

Prompt: tasks/WP01-shared-org-dirs-helper.md Goal: One function resolves the existing-path-filtered, declaration-ordered org_dirs list for a caller-supplied subdirectory name, so FR-001/FR-004/FR-005/FR-006/FR-006a cannot independently drift the way sites 3 and 6 already had before this mission. Priority: High (foundation). Independent test: resolve_org_dirs(repo_root, "mission_step_contracts") returns [] with no org packs configured, one path with one pack, and preserves declared order with two. Subtasks: T001–T004. Depends on: none — must land before WP02/WP03/WP04 (they import it), but is itself inert until they do. Risk: low; the one design decision is existence-filtering at the org-root level, not the joined-subdirectory level. ~150–200 lines.

WP02 — Executor org-tier threading + gate-binding lockstep [LOCKSTEP PAIR A] (IC-02) · FR-001, FR-002, FR-005

Prompt: tasks/WP02-executor-gate-binding-lockstep.md Goal: StepContractExecutor gains org-tier repository construction (FR-001) and org-tier DRG resolution (FR-002); gate_bindings._build_repository (FR-005) moves in the same package because splitting it from the executor leaves review gates silently inert (User Story 3). Priority: High. Independent test: an org-only step contract resolves via StepContractExecutor.execute, its delegation resolves against the org pack's own DRG node, and load_gate_bindings returns that contract's gates for the same fixture. Subtasks: T005–T010. Depends on: WP01. Risk: FR-001/FR-002 are forced into the same file (executor.py) regardless of the lockstep rule. Neither executor.py nor gate_bindings.py sits in the enforced diff-cover critical-path list — this WP's real backstop is the red-first regression tests (NFR-001), not a coverage gate. ~350–430 lines.

WP03 — Runtime dispatch / mission-load lockstep + delegation surfacing [LOCKSTEP PAIR B] (IC-03) · FR-006, FR-006a, FR-007

Prompt: tasks/WP03-runtime-dispatch-mission-load-lockstep.md Goal: _resolve_runtime_contract_for_step (runtime dispatch) and _resolve_contract_refs (mission-load validation) resolve an org-tier contract_ref identically — splitting them is explicitly worse than not fixing either (User Story 2). FR-007 (surface unresolved delegation candidates as a WARNING) is folded in because its edit site shares a file with FR-006. Priority: High. Independent test: a custom mission template step with an org-tier contract_ref validates at load time and resolves at dispatch time against the same fixture; both fail identically when the org pack is absent. Subtasks: T011–T016. Depends on: WP01. Risk: runtime_bridge_composition.py (FR-006) is critical-path (enforced diff-cover); mission_loader/command.py (FR-006a) has its own dedicated --cov-fail-under=90 job (NFR-004). ~380–460 lines.

WP04 — Governance-profile org-tier threading (IC-04) · FR-004

Prompt: tasks/WP04-governance-profile-org-tier.md Goal: _resolve_governance_slot_mission_type_profile_repositoryMissionTypeProfileRepository.for_project threads org_dirs so an org-tier governance-profile.yaml override is not silently invisible in every mission-type context resolution (it runs eagerly). Priority: High. Independent test: resolve_mission_type_context(...).governance_text reflects an org-pack override instead of the built-in baseline. Subtasks: T017–T020. Depends on: WP01. Risk: low in isolation; the only material risk is the file collision with WP05 on mission_type_profiles.py. src/charter/* is critical-path (enforced diff-cover). ~150–200 lines.

WP05 — Org-tier expected-artifacts.yaml override (IC-05) · FR-008

Prompt: tasks/WP05-expected-artifacts-org-tier.md Goal: MissionTemplateRepository/ManifestRegistry have no tiering mechanism today — this is net-new surface. An org pack can ship <org_root>/<mission_type>/expected-artifacts.yaml that fully replaces (not merges with) the built-in manifest for that mission type. Includes the self-identified ManifestRegistry cache-key fix (see prompt for full rationale). Priority: Medium. Independent test: ManifestRegistry.load_manifest("software-dev")'s required_always count/content changes when an org override is added to the fixture, in the same process, before/after. Subtasks: T021–T027. Depends on: WP04 — file-collision only, not functional. Both WPs edit src/charter/mission_type_profiles.py (and its test module); this dependency exists purely so owned_files stays non-overlapping for two concurrent packages (the finalizer exempts dependency-ordered pairs from the overlap check). Do not "optimise" WP04/WP05 into parallel execution. Risk: src/charter/org_expected_artifacts.py and mission_type_profiles.py are critical-path; specify_cli/dossier/manifest.py is not — same red-first-tests-are-the-real-backstop caveat as WP02. ~500–620 lines (largest package; see prompt's Sizing note).