Implementation Plan: Tree-wide org-pack chain authority
Branch: feat/org-pack-chain-tree-wide-authority | Date: 2026-10-10 | Spec: spec.md Input: Mission specification from kitty-specs/org-pack-chain-tree-wide-authority-01M4KGZG/spec.md
Summary
Route every remaining non-charter org-pack chain-deriving caller through the single resolve_pack_chain() authority (src/charter/activation/layer_roots.py, landed in #6006), preserving each caller's posture except on the governed decision surfaces, where #4984 requires a fail-closed (strict=True) posture. Then widen the FR-006 empty-allowlist census (tests/architectural/test_org_pack_chain_single_authority.py) from its charter-surface path scope to the whole src/ tree, expressing the two legitimate exemptions (src/charter/offering/** tier; name-paired registry enumeration) as scan rules — the allowlist stays empty.
The brownfield scout (see research.md) corrected the issue's caller list: discovery.py:82 is a docstring (no migration), the three resolve_org_dirs sites are out of scope (not a forbidden primitive), and _retired_activation.py:353 is an additional in-scope caller.
Technical Context
Language/Version: Python 3.11+ Primary Dependencies: internal only — charter.activation.layer_roots.resolve_pack_chain; no new third-party dependency (no supply-chain review needed) Storage: N/A (filesystem org-pack roots) Testing: pytest; the FR-006 architectural census gate (tests/architectural/test_org_pack_chain_single_authority.py); focused per-caller unit tests; red-first regression for the #4984 fail-closed behaviour change Target Platform: Linux/macOS dev + CI Project Type: single (library/CLI) Performance Goals: no regression — the lenient hot paths keep quiet/best-effort semantics; resolve_pack_chain(strict=False) is the same resolve_existing_org_roots body Constraints: resolve_pack_chain is Path-only (name-paired enumeration NOT migrated, C-001); census allowlist stays empty (C-003); src/charter/offering/ cannot import the activation authority (C-001); complexity ≤15, new branches tested in-commit (NFR-003) Scale/Scope**: 13 call sites across 11 modules + 1 architectural gate widen; 3 of the call sites are behaviour-changing (#4984 fail-closed)
Charter Check
GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.
- Single canonical authority (DIRECTIVE_044): this mission is the application of this principle — one authority (
resolve_pack_chain) for the ordered org-pack chain. ✅ aligned. - Architectural alignment / layering (DIRECTIVE_001): migrated callers import
resolve_pack_chaindirectly fromcharter.activation.layer_roots(consistent with all 16 existing importers); the scout verified nooffering→activationimport inversion is introduced andsrc/charter/offering/**stays excluded. ✅ - Test-first / quality gate (DIRECTIVE_030, DIRECTIVE_034): the #4984 fail-closed change lands red-first with an issue-pinned regression; new branches get focused tests in-commit. ✅
- Terminology canon: Mission, not Feature. ✅
- No charter violations → Complexity Tracking empty.
Project Structure
Documentation (this mission)
kitty-specs/org-pack-chain-tree-wide-authority-01M4KGZG/
├── plan.md # This file
├── research.md # Phase 0 — brownfield scout map + decisions
├── spec.md # Mission spec
└── tasks/ # Phase 2 output (/spec-kitty.tasks)
No data-model.md/contracts/ — this is an internal refactor with no new interfaces; meta.json will carry "contracts": "none" + rationale for strict accept.
Source Code (repository root)
src/charter/activation/layer_roots.py # the authority (resolve_pack_chain) — unchanged, the migration target
src/runtime/next/runtime_bridge_io.py # :465 strict=False (hot path)
src/runtime/next/_internal_runtime/discovery.py # :82 docstring only — optional refresh, NOT a call
src/specify_cli/runtime/resolver.py # :356, :818 strict=False (hot path)
src/specify_cli/mission_loader/command.py # :235 strict=False (hot path; :275 resolve_org_dirs OUT OF SCOPE)
src/specify_cli/skills/catalog.py # :154 strict=False (hot path prefilter)
src/specify_cli/invocation/org_profiles.py # :67 strict=False (hot path short-circuit)
src/specify_cli/cli/commands/profiles_cmd.py # :109 strict=False (read-only view)
src/specify_cli/cli/commands/_charter_pack_collect.py # :258,:365,:609,:1213 strict=False (diagnostics)
src/specify_cli/upgrade/migrations/_retired_activation.py # :353 strict=False (keep retired-key splice)
src/specify_cli/review/gate_bindings.py # :286 strict=True [#4984 BEHAVIOUR CHANGE]
src/specify_cli/mission_step_contracts/executor.py # :194 strict=True [#4984 BEHAVIOUR CHANGE] (:173 resolve_org_dirs OUT OF SCOPE)
src/specify_cli/tool_surface/providers/agent_profiles.py # :574 strict=True [#4984 — already manual-raises]
src/charter/drg.py # frozen compat re-exports — leave primitive re-exports; callers import resolve_pack_chain direct
tests/architectural/test_org_pack_chain_single_authority.py # WP05: widen _SCOPE_DIRS to src/, offering exclusion rule, raise floor, allowlist stays empty
Structure Decision: single-project library/CLI; edits are in-place caller migrations plus one architectural-gate widen. No new modules.
Decision record (resolved at plan time)
- #4984 crash-vs-degrade (DEC-6012-1): the three decision surfaces (
gate_bindings:286,executor:194,agent_profiles:574) move existing-filtered →strict=True, i.e. a declared-but-unfetched org pack raises (naming the pack +spec-kitty charter fetchremedy) instead of silently proceeding. This is the explicit intent of issue #6012 / #4984 ("fails closed where it should on the decision surfaces"); the operator-visible impact (refuse vs silent-degrade) is recorded here and gets a CHANGELOG entry + a red-first regression test.agent_profiles:574already manual-raises, so strict=True only swaps its ad-hoc message for the canonical remedy message. - drg.py (DEC-6012-2): leave
charter.drg's primitive re-exports frozen (compat surface forresolve_org_dirsusers + primitive unit tests); migrated callers importresolve_pack_chaindirectly fromcharter.activation.layer_roots, consistent with the 16 existing importers. No offering→activation cycle (scout-verified).
Implementation Concern Map
> Concerns, not WPs. /spec-kitty.tasks translates these into executable work packages.
IC-01 — Runtime-tier lenient hot-path migration
- Purpose: route the runtime/next template-discovery and resolution hot paths onto
resolve_pack_chain(strict=False)without behaviour change. - Relevant requirements: FR-001, FR-002, FR-006, NFR-001.
- Affected surfaces:
runtime_bridge_io.py:465,runtime/resolver.py:356/:818,mission_loader/command.py:235; refreshdiscovery.py:82docstring + the DEC-004/005 comment blocks. - Sequencing/depends-on: none.
- Risks: lazy-import ordering (keep imports lazy); comment blocks still name the old primitive; raw→
strict=Falseadds an existence filter (harmless in template search — note, no behaviour-change test).
IC-02 — specify_cli lenient callers
- Purpose: migrate the best-effort / diagnostic / read-only specify_cli callers to
strict=False. - Relevant requirements: FR-001, FR-002, FR-006, NFR-001.
- Affected surfaces:
skills/catalog.py:154,invocation/org_profiles.py:67,cli/commands/profiles_cmd.py:109,cli/commands/_charter_pack_collect.py:258/:365/:609/:1213. - Sequencing/depends-on: none.
- Risks: verify
CharterOfferingServicetreats absent roots as no-ops before the raw→filtered swap on_charter_pack_collect(else an unfetched pack could be hidden fromdoctor); diagnostic completeness comes from the separateregistry.packsname-capturing loops (not census hits), so those stay.
IC-03 — #4984 fail-closed decision surfaces
- Purpose: move the governed decision surfaces to
strict=Trueso a declared-but-missing pack fails closed (completes #4984). - Relevant requirements: FR-005, FR-002, NFR-002, NFR-003.
- Affected surfaces:
review/gate_bindings.py:286,mission_step_contracts/executor.py:194,tool_surface/providers/agent_profiles.py:574. - Sequencing/depends-on: none (independent of IC-01/02).
- Risks: real behaviour change (refuse vs degrade) — red-first regression per surface (unfetched pack ⇒ raises naming pack + remedy), CHANGELOG entry; update any test monkeypatching the old primitive on these modules / asserting the old
agent_profilesmessage string.
IC-04 — migration-tier caller + drg.py decision
- Purpose: migrate
_retired_activation.py:353(keep the retired-key splice) and settle thecharter.drgre-export decision (DEC-6012-2: leave frozen). - Relevant requirements: FR-001, FR-002.
- Affected surfaces:
src/specify_cli/upgrade/migrations/_retired_activation.py:353,src/charter/drg.py(no change beyond confirming the decision). - Sequencing/depends-on: shares the import-path decision with IC-01.
- Risks:
_retired_activationis a frozen migration — keep behaviour byte-identical; its_retired_key_org_rootshelper is not a census hit (iterates retired-key entries, notregistry.packs).
IC-05 — widen the census gate tree-wide (lands last)
- Purpose: widen
test_org_pack_chain_single_authority.pyto scan all ofsrc/, allowlist empty, with rule-based exemptions. - Relevant requirements: FR-003, FR-004, SC-002.
- Affected surfaces:
tests/architectural/test_org_pack_chain_single_authority.py(_SCOPE_DIRS→src/; addsrc/charter/offering/**scan-exclusion rule; raise_SCANNED_FILE_FLOOR; extend docstring noting name-paired readers rely on existing rule semantics;_ALLOWEDstays empty). - Sequencing/depends-on: MUST follow IC-01..IC-04 — widening before the callers migrate turns the gate red.
- Risks: file-floor must track the (much larger) tree-wide scanned count; the offering exclusion must be a documented rule, not an allowlist entry (C-003, ADR 2026-09-30-1).