Context and Problem Statement
Mission up-mission-type-seam-01KZY1JB gives the mission-type roster a layered lookup so a
non-built-in mission type declared in an org-tier doctrine pack resolves through mission create, charter activate, and action-sequence projection end-to-end — not merely "loads
without crashing." MissionTypeRepository.default() (src/doctrine/missions/mission_type_repository.py:48-50,
a @classmethod decorated with @functools.cache, keyed on cls only) stays built-in-only and
unchanged; a new, separate, pack-aware factory is added alongside it.
That work sits immediately downstream of a live sequencing decision recorded in
docs/adr/3.x/2026-08-05-1-mission-type-availability-before-kind-promotion.md,
which deliberately decoupled two independent mission-type threads and sequenced the
charter-activation-driven availability thread ahead of the mission-type-as-ArtifactKind
promotion thread (issue #2468, blocked
on the keystone pack-split #2467). That
ADR is explicit that the promotion thread "reverses a deliberate, tested 'no silent fallback'
contract (R-009/CL-1, FR-032, pinned by tests/doctrine/test_org_pack_augmentation.py) and must
carry its own decision record; it must not be smuggled into an availability slice." It also
explicitly parks the org-pack mission-type directory layout (nested vs. flat) as an
undecided sub-decision belonging to the promotion slice: "This ADR does not bind it."
This mission's roster-layering work is exactly the availability/resolution slice that
sequencing ADR anticipated. The risk this ADR closes is conflation risk, not implementation
risk: a future reader auditing either ADR must be able to tell, without re-deriving it from the
diff, that this mission (1) does not do the promotion work, (2) is not a silent reversal of the
"no silent fallback" contract the promotion thread must reckon with, and (3) makes its own,
narrower, self-contained call on the one layout sub-decision the sequencing ADR left open for a
directory shape this mission actually needs today (the org/project roster location), without
pretending to settle the promotion thread's broader nested-vs-flat question for #2468.
Decision Drivers
- No silent contract reversal. The sequencing ADR requires the promotion thread (
#2468) to carry its own decision record rather than be smuggled into an availability slice; symmetrically, this availability slice must not be misread as doing that thread's work by omission. - Honor a parked decision instead of silently resolving it. The sequencing ADR left nested-vs-flat mission-type layout open for the promotion slice. This mission needs an org/project roster layout now, for a narrower purpose (where does a layered lookup scan for non-built-in mission-type YAML), and must record that choice explicitly rather than let a directory decision made here be mistaken for having settled the promotion thread's question.
ArtifactKind/CHARTER_KIND_TOKENSboundary stays untouched. Confirmed live (see below): the enum has noMISSION_TYPEmember, and the exception that keeps"mission-type"out of the charter-activatableArtifactKindvocabulary still exists and is unchanged by this mission (spec C-002/C-003).- Correctness over convenience in the seam's own shape (CL-001). The layered lookup must not
be built by making the existing built-in-only
default()project-dependent, even though that would look like the smaller diff — see "Seam Shape" below.
Decision
(a) This mission does not promote mission-type to a first-class ArtifactKind
Confirmed live at this HEAD: src/doctrine/artifact_kinds.py:124-143 defines
class ArtifactKind(StrEnum) with twelve members (DIRECTIVE, TACTIC, STYLEGUIDE,
TOOLGUIDE, PARADIGM, PROCEDURE, AGENT_PROFILE, MISSION_STEP_CONTRACT, TEMPLATE,
ASSET, GLOSSARY_PACK, ANTI_PATTERN) — no MISSION_TYPE member exists today.
MissionTypeNotAnArtifactKind is defined at src/doctrine/artifact_kinds.py:40 and is
re-exported by src/charter/kind_vocabulary.py (import at line 51, __all__ entry at line 78).
Its purpose, per the module's own kind-normalization contract, is to keep the operator-facing
token "mission-type" out of the charter-activatable ArtifactKind vocabulary while it remains
a CHARTER_KIND_TOKENS member — i.e. mission types are activatable but are deliberately not
a doctrine artifact kind. This mission does not add a MISSION_TYPE enum member, does not touch
MissionTypeNotAnArtifactKind, and does not widen ALLOWED_MISSION_TYPES
(src/charter/activations.py:95, an import-time-constrained frozenset — spec C-003). Promoting
mission-type to a first-class ArtifactKind is the separate, larger, currently-unstarted
upstream effort tracked by the sequencing ADR: issue
#2468, blocked on the keystone pack-split
#2467.
(b) Relation to the sequencing ADR's "no silent contract reversal" driver
docs/adr/3.x/2026-08-05-1-mission-type-availability-before-kind-promotion.md
names #2468's promotion as reversing a deliberate, tested "no silent fallback" contract
(R-009/CL-1, FR-032, pinned by tests/doctrine/test_org_pack_augmentation.py) and requires that
reversal to carry its own decision record rather than be smuggled into an availability slice.
This mission is that availability/resolution slice, and states plainly that it is not the
contract-reversing type-promotion slice. Concretely: this mission adds a layered lookup +
PackContext projection + CLI-surface fixes for a mission type that has already been activated
(charter-eligible) — it never widens what can be activated in the first place (C-003, above).
It touches no code path guarded by tests/doctrine/test_org_pack_augmentation.py, adds no
ArtifactKind member, and leaves MissionTypeNotAnArtifactKind and CHARTER_KIND_TOKENS
unchanged. A future reader auditing either ADR should read this section and the sequencing ADR's
"No silent contract reversal" driver together as confirming, not conflicting: the sequencing ADR
sequenced this slice first because it does not touch the contract the promotion slice must
reckon with; this ADR confirms that the slice landed as scoped.
(c) The flat org-pack layout (CL-005) — its own short decision record
The sequencing ADR's "Explicitly out of scope" section names the nested-vs-flat mission-type
path as an open sub-decision it deliberately parks for the #2468 promotion slice: "This ADR
does not bind it." This mission needs an org/project mission-type roster location for its own,
narrower purpose (where the new layered lookup scans for non-built-in mission-type definitions),
so it makes and records that call here, distinct from (a) and (b) above and without claiming to
settle the promotion slice's broader question.
Decision: the layout is flat.
- Org layer:
<pack_root>/mission_types/*.yaml. This matches the siblingmission-steps/convention already used at the org/project pack tier — confirmed live atsrc/doctrine/missions/mission_step_repository.py:411, whose docstring states the pattern{pack_root}/mission-steps/{mission_type_id}/{step_id}/step.yaml. - Project layer:
.kittify/missions/mission_types/*.yaml, scanned non-recursively.
Rejected alternative: .kittify/doctrine/mission_types/. That directory shape, scanned
recursively, would descend into a per-type subdirectory (e.g.
.kittify/doctrine/mission_types/<type>/governance-profile.yaml) and mint a bogus available
mission type literally named governance-profile. This is a real trap in the live scanning code,
not a hypothetical one: CharterPackManager.list_available_detailed
(src/charter/pack_manager.py:756) uses scan_dir.rglob(glob) universally for every
charter-activatable kind (the rglob call itself is at src/charter/pack_manager.py:809), so
any subdirectory content matching the glob resolves as a top-level available artifact id.
This mission's flat CL-005 shape structurally avoids the trap rather than fixing the
underlying rglob behavior — .kittify/missions/mission_types/ holds only flat *.yaml files
with no per-type subdirectory, so rglob("*.yaml") and a hypothetical non-recursive glob are
behaviorally identical there. Changing list_available_detailed from rglob to glob would be
an unrelated, broader-blast-radius change affecting every other charter-activatable kind that
scans through the same method, and is explicitly out of scope for this mission (tracked as
follow-up scope in WP05's own work-package prompt,
kitty-specs/up-mission-type-seam-01KZY1JB/tasks/WP05-activate-layer-scan.md).
Seam Shape Backing These Decisions: CL-001's Rejected Alternatives
The layered lookup itself (the mechanism (a)/(b)/(c) above describe as "the availability slice") was not the only shape considered. Recording the rejected alternatives here, not just the chosen shape, is part of what makes this a decision record rather than a design summary:
- Option (a) — make
MissionTypeRepository.default()itself project-dependent, by threading aPackContextinto the existing, process-wide,cls-keyed@functools.cache. Rejected: a project-dependent value behind a project-blind cache is a correctness bug — the first project resolved in a process would poison the cache for every later one resolved in that same process.default()(src/doctrine/missions/mission_type_repository.py:48-50) therefore stays built-in-only and untouched; the new lookup is a separate, module-level@functools.cache-wrapped factory keyed on(mission_types_dirs, pack_context), never a classmethod cache. - Option (b) — move the
action_sequenceprojection out ofsrc/doctrine/missions/mission_type_repository.pyinto the charter layer. Rejected, measured, not guessed: doing so would remove the only producer of theaction_sequenceslot from the one directory tree (src/doctrine/+packs/built-in/) thattests/architectural/test_no_inert_schema_slots.py's producer-scan walks, which reds the one live assertion in that architectural gate —test_live_tree_has_no_new_inert_slots'sassert new == [](tests/architectural/test_no_inert_schema_slots.py:62-75, confirmed live at this HEAD).
The new lookup instead imports the existing structural _PackContextLike Protocol
(src/doctrine/missions/mission_step_repository.py:41-61, confirmed live) from its sibling
module in the same package (doctrine.missions) — an intra-package import, not a new
cross-layer one; doctrine still never imports charter. This is why the mechanism qualifies as
"availability/resolution," per (b) above: it is additive plumbing inside the existing
built-in/org/project layering, not a change to what counts as a doctrine artifact kind.
Consequences
Positive
- A future reader of either this ADR or the sequencing ADR can resolve, from either document alone, whether a given piece of mission-type work is the availability slice or the promotion slice — the conflation risk this ADR exists to close.
- The one org-pack layout question this mission actually needs answered (org/project roster
location) is decided and recorded, without overreaching into the promotion slice's broader
nested-vs-flat question, which stays open for
#2468to answer when it is scoped. - The rejected
MissionTypeRepository.default()project-dependent-cache option is on record, so a future contributor tempted by the smaller-looking diff can see why it was rejected instead of rediscovering the cache-poisoning bug by shipping it.
Negative / accepted trade-offs
- This ADR does not resolve the promotion slice's own nested-vs-flat decision for
#2468— that remains genuinely open, and#2468's own future ADR must still make that call for the doctrine artifact-kind path, independent of the roster-layout call made here. - The
rglob-vs-globbehavior inCharterPackManager.list_available_detailedis not fixed by this mission; it is neutralized only for the flat directories this mission introduces. A future per-type-subdirectory addition to a different charter-activatable kind under a recursively scanned root would still hit the same class of trap.
Neutral
MissionTypeRepository.default()and itscls-keyed cache are unchanged; all built-in mission-type resolution behavior is unaffected by this mission.
References
- Sequencing ADR this relates to:
docs/adr/3.x/2026-08-05-1-mission-type-availability-before-kind-promotion.md - Promotion thread (deferred, separate ADR required): issue
#2468 ("mission-type as a first-class
ArtifactKind"), blocked on keystone issue #2467 (pack-split). - Mission spec (binding decision records CL-001, CL-002, CL-005; constraints C-002, C-003):
kitty-specs/up-mission-type-seam-01KZY1JB/spec.md - Mission plan (Implementation Concern Map preamble stating WP01's required contents):
kitty-specs/up-mission-type-seam-01KZY1JB/plan.md - Follow-up scope for the underlying
rglob/globbehavior:kitty-specs/up-mission-type-seam-01KZY1JB/tasks/WP05-activate-layer-scan.md