Tasks: First-Class TEMPLATE + ASSET Doctrine Kinds

Mission: doctrine-template-asset-kinds-01KX2YQ7 · #2495 (P0) + #2469 · part of #2466 Branch: feat/doctrine-template-asset-kinds-2495 (planning base = merge target; PR later targets Priivacy-ai/spec-kitty:main) Plan: plan.md · Spec: spec.md · Research: research.md · Data model: data-model.md · Contract: contracts/asset-kind.md

Two doctrine-kind changes threaded through the DRG kind universe: (A #2495) make TEMPLATE a first-class, edge-wireable org-pack DRG node; (B #2469) add a new loose-contract ASSET kind (sidecar *.asset.yaml manifest, no blob schema, hard uniqueness + containment + mime). Decomposed against the plan's 6 Implementation Concerns, hardened by the post-plan squad (bare <kind>:<id> URNs; one global uniqueness scan at merge_three_layers; _OrgDRGNode unchanged / sidecar-only metadata; context.py:500 into the canonical set; totality guard exempts documented .get-partials; reuse effective_root containment).

Work Package Dependency Graph

WP01 (enum core + canonical set)  ── root
  ├─ WP02 (loader universe + lockstep mirrors)
  │     └─ WP03 (merge compose + global uniqueness scan)
  ├─ WP04 (ASSET sidecar validator + safety)
  ├─ WP05 (extractor + doctrine-CLI suffix)
  └─ WP06 (charter-cascade exhaustiveness + context.py:500)
        WP07 (core exhaustiveness + totality guard)  ← depends WP01..WP06
          WP08 (e2e fixture + no-regression)         ← depends WP01..WP07

Parallel after WP01: WP02, WP04, WP05, WP06 (disjoint surfaces). WP03 after WP02. WP07 gathers the sweep; WP08 is the terminal no-regression gate. MVP: WP01→WP02→WP03 (templates first-class) is the #2495 spine; WP04 is the #2469 spine.

Requirement → Work Package Coverage

FRWP(s)
FR-001 (templates node-declarable + lockstep mirrors)WP02
FR-002 (templates in _PLURAL_TO_SINGULAR)WP03
FR-003 (templates NOT augmentation/charter via canonical set)WP02
FR-004 (edges to bare template:<id> validate; clash caught)WP02, WP08
FR-005 (ArtifactKind.ASSET + NodeKind.ASSET + on-disk tree)WP01, WP04
FR-006 (sidecar AssetManifest; _OrgDRGNode unchanged)WP04
FR-007 (extractor + loader/plural registration)WP02, WP03, WP05
FR-008 (global URN-uniqueness scan at merge_three_layers)WP03
FR-009 (mime validation)WP04
FR-010 (path-containment via reused effective_root)WP04
FR-011 (_NON_AUGMENTATION_ELIGIBLE_KINDS drives augmentation + CHARTER_KIND_TOKENS)WP01, WP02
FR-012 (exhaustiveness sweep + totality guard)WP05, WP06, WP07

NFRs: NFR-001/004/005 land in WP08 (no-regression + negative cases); NFR-002 (ruff/mypy/complexity) is a per-WP DoD; NFR-003 (forward-compat #2467) is honoured by WP04's convention + confirmed in WP08.

Subtask Index

IDDescriptionWPParallel
T001ArtifactKind.ASSET member + glob *.asset.yamlWP01
T002NodeKind.ASSET + URN-prefix==kind ruleWP01[D]
T003_NON_AUGMENTATION_ELIGIBLE_KINDS = {TEMPLATE, ASSET}WP01
T004Drive CHARTER_KIND_TOKENS off the canonical setWP01
T005Update member-set + glob testsWP01
T006_ORG_DRG_KIND_ALIASES/_ORG_DRG_CANONICAL_KINDS += templates, assetsWP02
T007Drive AUGMENTATION_ELIGIBLE_KINDS/_AUGMENTATION_GLOBS off the canonical setWP02
T008Move lockstep mirrors _ALLOWED_KINDS + _BUILTIN_ARTIFACT_KINDSWP02
T009Update augmentation + lockstep drift-guard + pack_context testsWP02
T010_PLURAL_TO_SINGULAR += templates, assetsWP03
T011_check_node_urn_unique(prefix, nodes) helperWP03
T012Wire the scan at merge_three_layers (asset:+template:, all layers)WP03
T013Red-first dup-URN tests (asset + template, cross-layer)WP03
T014AssetManifest Pydantic model + register in _artifact_schema_registryWP04
T015_validate_asset_manifests separate pass (invoked alongside _validate_drg)WP04
T016Path-containment via reused effective_root/OrgPackSubdirEscapeErrorWP04
T017mime validation (type/subtype + extension consistency)WP04
T018assets/built-in/ convention + red-first manifest testsWP04
T019Extractor scan_dirs += assets; _KIND_MAP via .getWP05
T020doctrine.py::_SUFFIX_TO_KIND += *.asset.yamlWP05[D]
T021Extractor/suffix testsWP05
T022context.py:500 bare-probe filter → canonical set (excludes ASSET)WP06
T023pack_manager (YAML_KEY_MAP + .get-partials) + kind_vocabulary partialsWP06
T024project_drg::_KIND_TO_NODE_KIND (.get, not raise) + consistency_check + _activation_renderWP06
T025list_cmd::_KIND_ORDER + test_drg_filtering member coverageWP06
T026Charter-cascade exhaustiveness testWP06
T027ResolveTransitiveRefsResult += assets field + return lineWP07
T028executor::_ARTIFACT_TO_NODE_KIND += asset,template; template_catalog reconcileWP07
T029Totality guard test (every kind-keyed dict total OR documented .get-partial)WP07
T030Prove the guard exempts the 4 pre-existing partials (no Day-1 false-fail)WP07
T031E2E org-pack fixture: template node + edge + asset (blob + sidecar)WP08
T032Negative cases: dup-id cross-layer, path-escape, malformed mime fail loudWP08
T033Full doctrine/DRG/charter/pack-validator suites green; 9 kinds unchangedWP08

WP01 — Enum core + canonical exclusion set

  • Goal: add both new enum members and the one canonical exclusion set that closes the silent-leak class.
  • Priority: P0 (root — everything depends on it). Independent test: pytest tests/doctrine/test_artifact_kinds.py tests/doctrine/drg/test_nodekind_artifactkind.py.
  • Requirements: FR-005 (enum), FR-011, C-001.
  • Subtasks: T001–T005.
  • Dependencies: None
  • Prompt: tasks/WP01-enum-core-canonical-set.md

WP02 — Loader universe + lockstep charter mirrors

  • Goal: make templates + assets node-declarable; drive augmentation exclusion off the canonical set; move the locked charter mirrors in lockstep.
  • Priority: P0. Independent test: pytest tests/doctrine/test_org_pack_augmentation.py tests/charter/test_pack_context.py.
  • Requirements: FR-001, FR-003, FR-004 (template edge-wireable), FR-007 (loader aliases), FR-011 (consumption).
  • Subtasks: T006–T009.
  • Dependencies: WP01
  • Risk: the lockstep drift-guard is guaranteed-red until all mirrors move together.
  • Prompt: tasks/WP02-loader-universe-lockstep.md

WP03 — Merge compose + global URN-uniqueness scan

  • Goal: compose template/asset nodes into the merged DRG and enforce global URN-uniqueness with one post-merge scan.
  • Priority: P0. Independent test: pytest tests/doctrine/test_drg_merge.py.
  • Requirements: FR-002, FR-007 (plural), FR-008.
  • Subtasks: T010–T013.
  • Dependencies: WP01, WP02
  • Risk: NEW merge behavior — scope by URN prefix; the scan runs once at merge_three_layers, not per-fragment.
  • Prompt: tasks/WP03-merge-global-uniqueness.md

WP04 — ASSET sidecar validator + safety contract

  • Goal: AssetManifest validation + a separate _validate_asset_manifests pass (containment reuse + mime); the assets/ convention.
  • Priority: P0 (the #2469 spine). Independent test: pytest tests/specify_cli/doctrine/test_pack_validator.py.
  • Requirements: FR-005 (on-disk), FR-006, FR-009, FR-010, NFR-005.
  • Subtasks: T014–T018.
  • Dependencies: WP01
  • Risk: keep pack_validator.py complexity ≤15 — the containment/mime pass is a separate helper, not inline in the branchy scan loop.
  • Prompt: tasks/WP04-asset-sidecar-validator.md

WP05 — Extractor + doctrine-CLI suffix

  • Goal: register ASSET in the migration extractor and the *.asset.yaml suffix→kind map.
  • Priority: P1. Independent test: pytest tests/doctrine/drg/test_extractor_asset.py.
  • Requirements: FR-007 (extractor), FR-012 (_SUFFIX_TO_KIND).
  • Subtasks: T019–T021.
  • Dependencies: WP01
  • Prompt: tasks/WP05-extractor-suffix.md

WP06 — Charter-cascade exhaustiveness + context.py:500

  • Goal: cover every charter-layer kind-keyed map/filter for both new members, including the 4th TEMPLATE-exclusion filter and the .get-partials.
  • Priority: P1. Independent test: pytest tests/charter/test_kind_cascade_exhaustive.py tests/charter/test_drg_filtering.py.
  • Requirements: FR-012 (charter cascade).
  • Subtasks: T022–T026.
  • Dependencies: WP01
  • Risk: context.py:500 is a comprehension the totality guard won't catch — WP06 owns it explicitly.
  • Prompt: tasks/WP06-charter-cascade-exhaustiveness.md

WP07 — Core exhaustiveness + totality guard

  • Goal: fix the dropped-asset-node return + the remaining core maps; add the C-005 totality guard with the .get-partial exemption.
  • Priority: P1. Independent test: pytest tests/doctrine/drg/test_kind_mapping_totality.py tests/doctrine/test_drg_relations.py.
  • Requirements: FR-012 (query/executor/template_catalog + guard).
  • Subtasks: T027–T030.
  • Dependencies: WP01, WP02, WP03, WP04, WP05, WP06
  • Risk: the guard requires every mapping site already total; a naive "every dict must be total" guard false-fails on 4 pre-existing partials — the exemption must be proven.
  • Prompt: tasks/WP07-core-exhaustiveness-totality-guard.md

WP08 — E2E fixture + no-regression

  • Goal: a Regnology-shaped org-pack fixture (template node + edge + asset) loads/graphs/validates; negative cases fail loud; the 9 existing kinds are unchanged and the full suites are green.
  • Priority: P0 (the acceptance gate). Independent test: pytest tests/doctrine/test_template_asset_e2e.py then the full doctrine/DRG/charter/pack-validator suites.
  • Requirements: NFR-001, NFR-004 (+ exercises FR-004/FR-008 end-to-end).
  • Subtasks: T031–T033.
  • Dependencies: WP01, WP02, WP03, WP04, WP05, WP06, WP07
  • Prompt: tasks/WP08-e2e-fixture-no-regression.md