T-2KK8-capability-kind-grains-and-locations
Status: closed/done · Impact: high · Complexity: medium
Capability’s two-value kind (feature | technical) conflates audience with
structure, and capabilities carry no code anchors. Split the axes — structural
kind (11 grains) + audience (user | system) — and add optional
locations[] (D-3PDM-cross-project-data-model-adoption P3/P4, P3 as
amended 2026-07-17; finding F6).
| Location | Role today |
|---|---|
| apps/sdlc/lib/model/entities/capability/schema.ts#kind | z.enum(["feature", "technical"]), default technical; doubles as the audience axis and the index-section selector |
| apps/sdlc/lib/model/entities/capability/schema.ts | no audience or locations field; SCHEMA_VERSION is "1" |
| apps/sdlc/lib/services/docs/generate.ts | derives the index’s feature/technical sections from kind |
| docs/planning/capabilities/ | 20 capability instances — 8 feature, 12 technical |
Proposed
Section titled “Proposed”kind: optional structural grain —system | subsystem | service | workflow | component | datastore | external | module | adapter | generated | page; absent = ungraded. The donor’sfeaturekind was audience masquerading as structure; it maps toaudience: user, not a grain.audience:user | system, defaultsystem— carries the old feature/technical split; index sectioning switches to it.locations: optionalstring[], default[], five-form Location grammar (file,file#symbol,file:line,dir/, glob) — one grammar shared with task touchpoints, not the donor’spath:line:col. Overlap matching (later,T-U3NR) normalizes these forms onto@sksizer/intersectpatterns, so no form may be introduced that cannot degrade to a path/glob.- v1→v2 migration:
kind: feature→audience: user;kind: technical→audience: system; oldkindvalue removed; structuralkindstarts unset. - Anchor-rot check (decided 2026-07-18):
entities auditwarns when a stored location no longer resolves in the tree — file/dir/glob existence only;file#symbolandfile:linedegrade to the file check. This islocations[]’s minimal consumer per the activation rule; the rich overlap matcher stays withT-U3NR. - Populate
locations[]now (decided 2026-07-18): seed each of the 20 instances from the code paths in itsUnderlying implementationprose. The prose paths are ALL stale (plugin/…predates the T-1RLU move toapps/sdlc/…) — remap to current paths; where the referenced implementation was retired (e.g.new_task.py), point at its living successor and note the drift in the body. Every populated location must pass the anchor-rot check. A judgment-assisted pass over 20 files, not part of the deterministic v1→v2 transform.
Files to touch
Section titled “Files to touch”| Location | Kind | Change |
|---|---|---|
| apps/sdlc/lib/model/entities/capability/schema.ts | modify | kind → optional 11-grain enum; add audience, locations; SCHEMA_VERSION 2; HISTORICAL_KEY_ORDER placement |
| apps/sdlc/lib/model/entities/capability/migrations/ | new | v1→v2 transform (pattern: entities/task/migrations/) + tests |
| apps/sdlc/lib/model/entities/capability/definition.md | modify | 11 grain definitions (incl. the donor-undocumented module, adapter, generated, page), audience rubric + its falsification signal, locations grammar |
| apps/sdlc/lib/model/ops/audit.ts | modify | anchor-rot warning: capability location that fails to resolve (existence only; symbol/line forms degrade to the file check) |
| apps/sdlc/lib/model/entities/capability/body-template.eta | modify | reflect new frontmatter fields where referenced |
| apps/sdlc/lib/services/docs/generate.ts | modify | index sectioning selector: kind → audience |
| apps/sdlc/lib/services/docs/index_template.eta | modify | audit feature/technical section wording |
| apps/sdlc/lib/services/docs/site_data_model_template.eta | modify | render locations (and audience) on capability pages |
| docs/planning/capabilities/ | modify | 20 instances migrated via sdlc entities migrate; locations[] populated from Underlying implementation prose — stale plugin/… paths remapped to apps/sdlc/…, retired targets pointed at successors |
Approach
Section titled “Approach”- Coordinate with
T-SJH1: land as ONE v2 bump — this task hosts the combined migration; T-SJH1’scontainsdrop (with its consistency-verification rule) folds into the same v1→v2 transform. - Edit
capability/schema.ts: fields per Proposed; move the index-section language from thekinddocstring toaudience. - Write the v1→v2 migration following the task-migrations pattern;
MigrationError on unknown
kindvalues. - Switch index sectioning in
docs/generate.tstoaudience; regenerate docs artifacts. - Update
definition.md: grain definitions; audience rubric — pickuserwhen the capability’s direct consumer is an end user, elsesystem— and its falsification signal: anaudience: usercapability whoselocations[]contain no user-facing surface, or anaudience: systemcapability cited from user-facing docs, is mis-audienced. - Add the anchor-rot warning to
entities audit: each capability location must resolve (file/dir/glob existence;#symbol/:linedegrade to the file check). Warning, not error — rot flags drift, it doesn’t block. - Run
sdlc entities migrateover docs/planning/capabilities/;entities auditgreen. - Populate
locations[]per instance fromUnderlying implementationprose: extract the code paths, remapplugin/…→apps/sdlc/…, replace retired targets with their living successors; re-runentities audit— zero anchor-rot warnings across the corpus. - The location-overlap matcher does NOT land here (see Out of scope).
Acceptance criteria
Section titled “Acceptance criteria”- AC-1: schema v2 —
kindoptional 11-grain structural enum;audienceuser | systemdefaultsystem; SCHEMA_VERSION2; HISTORICAL_KEY_ORDER placesaudienceandlocationsdeliberately. - AC-2: the v1→v2 migration maps
feature→audience: userandtechnical→audience: system; every instance matched bydocs/planning/capabilities/*.md(20 today) migrates clean. - AC-3:
locationsaccepts the five-form grammar and defaults to[]. - AC-4: index sections derive from
audiencewith unchanged grouping (8 user / 12 system). - AC-5:
capability/definition.mddocuments the 11 grains, the audience rubric with its falsification signal, and the locations grammar; check_entities passes. - AC-6: the site capability page renders
locationswhen present. - AC-7:
entities auditwarns on a capability location that fails to resolve and stays quiet when all locations resolve (fixture-tested both ways). - AC-8: every capability instance with an
Underlying implementationsection carries a non-emptylocations[]; the full corpus audits with zero anchor-rot warnings (all staleplugin/…pointers remapped or replaced).
Out of scope
Section titled “Out of scope”- Re-grading instances to structural grains (follow-up triage; prerequisite for the Tier B graph milestone, see apps/sdlc/desktop/docs/ui-consolidation-plan.md).
- The location-overlap matcher itself — lands with
T-U3NRas a lib service normalizing the grammar onto@sksizer/intersect. The anchor-rot check here is existence-only and must not grow matching semantics. capability_typeandstatefields (rejected inD-3PDM-cross-project-data-model-adoption; the audience split is notcapability_type— see the D-3PDM amendment of 2026-07-17).
Dependencies
Section titled “Dependencies”T-SJH1— samecapability/schema.ts; resolved 2026-07-17: single v2 bump, this task hosts the combined migration.- Grammar alignment with
T-U3NR— one Location grammar, one future matcher on@sksizer/intersect.
Discovery context
Section titled “Discovery context”D-3PDM-cross-project-data-model-adoptionP3/P4 / finding F6: donorkindset on 76/76 capabilities (component 22, module 20, subsystem 13, …);locationson 75/76 — the two most-adopted fields in that corpus.- 2026-07-17 session (UI-consolidation analysis): the audience axis has a
live consumer (index sectioning); structural kind’s consumer is the SDF
capability graph (Tier B). Flattening would erase feature-ness on regrade.
SDLC corpus: 8/20
feature.
Post-mortem
Section titled “Post-mortem”Captured by /sdlc:task-work on 2026-07-18. PR: #843.
Acceptance criteria coverage
Section titled “Acceptance criteria coverage”- AC-1: auto —
bun test apps/sdlc/lib/model(363 pass) +tsc --noEmit; schema v2 fields with deliberate HISTORICAL_KEY_ORDER placement - AC-2: auto — 17 migration unit/fixture tests; agent-manual —
entities migrate --types capabilitymigrated all 20 clean (8audience: user/ 12audience: system), audit reports no drift - AC-3: auto — 16 Location-grammar tests (
@lib/util/location); agent-manual —entities validaterejects a symbol-on-glob fixture (exit 1) - AC-4: agent-manual — regenerated docs/index.md shows zero table-row
changes; sections now derive from
audience, 8/12 grouping preserved - AC-5: auto — check_entities passes in every pre-commit; definition.md carries the 11 grains, audience rubric + falsification signal, and the locations grammar
- AC-6: agent-manual — generated capability page renders Audience and
Locations (C-0003 inspected); landed in
capability/site_projection.ts, not the template the spec named (see friction) - AC-7: auto — anchor-rot fixtures test both directions; agent-manual — a
temporary bogus location produced the
anchor_rotwarning, then reverted - AC-8: agent-manual — all 10 instances with
## Underlying implementationcarry non-emptylocations[], every anchor existence-checked before writing; auto — full corpus audit: zero drift, zero anchor-rot
What worked
Section titled “What worked”- The task-migrations pattern transplanted cleanly to capability (v1-to-v2 + errors + fixtures + tests); the migrate CLI took the new chain with one registry entry
- Fast worktree arming:
bun install --frozen-lockfile --filter '@sksizer/sdlc'(~0.7s) arms all pre-commit hooks — reused from the B-AAQ5 investigation for the manual state commits - Baseline-gated quality run: OK 10/10 with pre-existing drift subtracted
Friction and automation gaps
Section titled “Friction and automation gaps”- Step 5a/5b machinery (
ensure_ready_mutate --commit-on main,start_task.ts) is broken on this host —commitToMainViaWorktreecreates bare ephemeral worktrees whose pre-commit hooks cannot resolve workspace deps under bun’s isolated linker; the parent replicated both steps manually through a detached, armed commit-worktree — fix designed and captured as B-AAQ5 (PR #837): arm ephemeral commit-worktrees via acommit_worktree_initverb list →B-AAQ5 - The parent’s first manual 5b attempt committed state on the task branch;
the task-state-origin guard correctly rejected it — manual replication
must commit from detached HEAD off origin/main and push
HEAD:main→B-AAQ5 dumpFrontmattercrashed on null leaves (parent_key: nulltree roots) — latent PyYAML-parity gap in the migration write path, fixed in-branch (e5ea985c); forced one restore-and-rerun of the corpus migration- The fresh worktree inherited a broken uncommitted block comment
(
path/**/*.tswhose*/terminated the comment) that failed typecheck — absorbed; origin unclear, worth a fresh-worktree cleanliness check in worktree_init → T-SW5E-worktree-init-verifies-clean-checkout - util-redeclaration gate collision: the new
parseLocationexport vs parse-touchpoints’ private cell parser — renamed the cell parser toparseLocationCellrather than baselining check_entitiesgates the working tree, not the index — definition.md had to be schema-consistent before ANY commit could land, forcing the definition rewrite ahead of the task’s step order → T-SGIN-check-entities-gates-staged-content- AC-6 named
site_data_model_template.etabut the real seam is the site projection framework (capability/site_projection.ts) — file-to-touch drift; the entity-code-placement convention decided it
Spawned follow-up tasks
Section titled “Spawned follow-up tasks”- T-SW5E-worktree-init-verifies-clean-checkout (https://github.com/sksizer/dev/pull/840) [open/ready] — task-work Step 4 verifies fresh-worktree cleanliness after init, spawned
- T-SGIN-check-entities-gates-staged-content (https://github.com/sksizer/dev/pull/842) [open/ready] — check_entities gates staged content instead of the working tree, spawned
B-AAQ5— ephemeral commit-worktree arming viacommit_worktree_init, already designed on the backlog-capture branch (PR #837); covers both the Step 5a/5b breakage and its task-state-origin guard facet, linked