Research — pack-level-declared-in-pack-yaml
Phase 0. Consolidates the pre-spec grounding (three CodeGraph reconnaissance passes), the adversarial architect review, and operator rulings. Every decision below was resolved before spec; this file is the rationale record.
R-1 — pack_level type: reuse kernel.pack_tiers.PackTier
- Decision: type
pack_levelas the existingkernel.pack_tiers.PackTierLiteral; introduce no new enum. - Rationale:
kernel.pack_tiersalready owns the three level tokens (built-in/org/project) under an empty-allowlist AST gate (test_pack_tier_token_single_authority.py). A second enum incharter.offering.packswould duplicate naming authority and could trip that gate.kernel < charter, sochartermay import it. Operator ruling (Q1). - Alternatives considered: a new
PackLevel(StrEnum)deriving members by reference from kernel (gate-safe but a naming duplication the architect flagged); rejected per operator.
R-2 — Fold the governing ADR into this PR
- Decision: carry
docs/adr/4.x/2026-10-11-2-a-pack-declares-its-level-in-pack-yaml.mdin this PR; close #6049; align its Decision §2 to "kernel owns the tokens; the descriptor reuses the Literal; no second enum." - Rationale: the ADR is docs-only in unmerged PR #6049; folding it makes this PR cite a file present in its own diff (single authority). Operator ruling (Q2). Filename is
-2-(the-1-slot that day is the archive-freeze ADR). - Alternatives: depend on #6049 merging first (cleaner provenance but a merge-order coupling); land independently citing pending (fragile). Rejected per operator.
R-3 — Fold #5992 (provenance respell), no data migration
- Decision: respell the 3-tier artifact provenance/layer value
"builtin"→"built-in", unify onkernel.pack_tiers; no consumer data migration. - Rationale: verified that no read-back code compares a persisted layer value to
"builtin"— every live comparison runs on freshly computedget_provenance()output, so emitter + comparisons move together. The one persisted carrier (.kittify/agent_profiles_manifest.jsonsource_layer) is validated as a non-empty string and regenerated each projection → self-heals. Operator ruling (Q3). - Scope fence (C-002): only the 3-tier provenance vocabulary. NOT the
builtin|packskill-origin vocabulary (skills/manifest.py,registry.py) nor the"builtin:"command-target prefix (pack_skills/models.py). - Test surface: ~7 serialized-contract files (
test_manifest.py,test_portable_provenance.py,test_heal_provenance.py,test_charter_rendering.py,test_context.py,test_doctrine_asset.py,test_profiles_activation.py) + ~30 in-memory assertion files. - Alternatives: defer #5992 to a follow-up (architect's and my original recommendation, safe but leaves the split); rejected per operator in favour of unification now.
R-4 — packs/internal/ gets its own pack.yaml; descriptor declares layout
- Decision: add
packs/internal/pack.yaml(pack_level: org, pinnedpack_id,active_charter: org-charter.yaml,pack_drg: drg/fragment.yaml).org-charter.yamlremains the pack's active-charter content. - Rationale: internal is an org-tier pack (ADR 2026-08-16-3, Option B). Operator ruling:
pack.yamlis the identity/level descriptor;org-charter.yamlis content. The descriptor'sactive_charter/pack_drgpointers mean layout is declared, not located by hard-coded filename. Operator ruling (follow-ups). - Consequences: the org-pack load path must gain a
pack.yamlread;pack_versionbecomes required only for built-in (FR-005) so internal need not author one; internal'spack_idis drift-pinned (mirrortest_pack_id_identity).
R-5 — Clean-tree digest: enumerated-known-files / LF-normalized
- Decision: add a NEW digest reusing the
builtin_manifestenumerated-known-files, LF-normalized approach; leavesnapshot_sha256and its two remote-snapshot callers byte-unchanged. - Rationale: must be identical across a
git archiveexport, a working checkout (with__pycache__), and an installed copy.git ls-filesfails on a.git-less export; an rglob-minus-ignore-list is fail-open. Enumerating declared artifact kinds ignores noise by construction and reuses tested code.snapshot_sha256correctly hashes an installed exact copy (raw bytes, full tree) for the install-record path — repurposing it would corrupt that semantics (NFR-004). - Alternatives:
git ls-files(rejected: no index on export); rglob-minus-ignore (rejected: brittle, fail-open).
R-6 — PACK_LEVEL_MISSING warn-then-refuse behind a tracked switch
- Decision: WARN now when
pack_levelis absent; gate the hard refuse behind a single module-level constant (default = warn), flipped by a tracked follow-up. - Rationale: the ADR grants org packs a one-release deprecation window; this mission invents no release anchor (C-006). The constant makes both the warn and refuse paths testable now (flip the constant in a test).
R-7 — Gate collisions are deliverables, not regressions
- Decision: update
test_pack_manifest_no_author_edit(built-inpack.yamlkey set +=pack_level) and extendtest_pack_tier_token_single_authority(cover the unified spelling, drop the NFR-003 carve-out, keep empty allowlist); add a newtest_pack_level_single_authoritygate. - Rationale: both existing gates explicitly freeze today's shape (the exact
{pack_id,pack_version,parent_pack,name}key set; the"builtin"provenance carve-out). The mission deliberately shifts those contracts, so the gate edits are in-scope deliverables; an implementer must not treat the resulting red as a pre-existing failure.
R-8 — Supply-chain posture
- Decision: no new third-party dependency is added beyond
packaging(already available, used for version specifiers). No install-time supply-chain control is triggered. - Rationale:
requires_corevalidation uses the version-specifier machinery already present; no new package install. DIRECTIVE_051 posture: nothing to surface beyond this note. No adversarial supply-chain challenge required (no dependency decision of consequence).