Skip to content

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).

LocationRole today
apps/sdlc/lib/model/entities/capability/schema.ts#kindz.enum(["feature", "technical"]), default technical; doubles as the audience axis and the index-section selector
apps/sdlc/lib/model/entities/capability/schema.tsno audience or locations field; SCHEMA_VERSION is "1"
apps/sdlc/lib/services/docs/generate.tsderives the index’s feature/technical sections from kind
docs/planning/capabilities/20 capability instances — 8 feature, 12 technical
  • kind: optional structural grain — system | subsystem | service | workflow | component | datastore | external | module | adapter | generated | page; absent = ungraded. The donor’s feature kind was audience masquerading as structure; it maps to audience: user, not a grain.
  • audience: user | system, default system — carries the old feature/technical split; index sectioning switches to it.
  • locations: optional string[], default [], five-form Location grammar (file, file#symbol, file:line, dir/, glob) — one grammar shared with task touchpoints, not the donor’s path:line:col. Overlap matching (later, T-U3NR) normalizes these forms onto @sksizer/intersect patterns, so no form may be introduced that cannot degrade to a path/glob.
  • v1→v2 migration: kind: featureaudience: user; kind: technicalaudience: system; old kind value removed; structural kind starts unset.
  • Anchor-rot check (decided 2026-07-18): entities audit warns when a stored location no longer resolves in the tree — file/dir/glob existence only; file#symbol and file:line degrade to the file check. This is locations[]’s minimal consumer per the activation rule; the rich overlap matcher stays with T-U3NR.
  • Populate locations[] now (decided 2026-07-18): seed each of the 20 instances from the code paths in its Underlying implementation prose. The prose paths are ALL stale (plugin/… predates the T-1RLU move to apps/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.
LocationKindChange
apps/sdlc/lib/model/entities/capability/schema.tsmodifykind → optional 11-grain enum; add audience, locations; SCHEMA_VERSION 2; HISTORICAL_KEY_ORDER placement
apps/sdlc/lib/model/entities/capability/migrations/newv1→v2 transform (pattern: entities/task/migrations/) + tests
apps/sdlc/lib/model/entities/capability/definition.mdmodify11 grain definitions (incl. the donor-undocumented module, adapter, generated, page), audience rubric + its falsification signal, locations grammar
apps/sdlc/lib/model/ops/audit.tsmodifyanchor-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.etamodifyreflect new frontmatter fields where referenced
apps/sdlc/lib/services/docs/generate.tsmodifyindex sectioning selector: kindaudience
apps/sdlc/lib/services/docs/index_template.etamodifyaudit feature/technical section wording
apps/sdlc/lib/services/docs/site_data_model_template.etamodifyrender locations (and audience) on capability pages
docs/planning/capabilities/modify20 instances migrated via sdlc entities migrate; locations[] populated from Underlying implementation prose — stale plugin/… paths remapped to apps/sdlc/…, retired targets pointed at successors
  1. Coordinate with T-SJH1: land as ONE v2 bump — this task hosts the combined migration; T-SJH1’s contains drop (with its consistency-verification rule) folds into the same v1→v2 transform.
  2. Edit capability/schema.ts: fields per Proposed; move the index-section language from the kind docstring to audience.
  3. Write the v1→v2 migration following the task-migrations pattern; MigrationError on unknown kind values.
  4. Switch index sectioning in docs/generate.ts to audience; regenerate docs artifacts.
  5. Update definition.md: grain definitions; audience rubric — pick user when the capability’s direct consumer is an end user, else system — and its falsification signal: an audience: user capability whose locations[] contain no user-facing surface, or an audience: system capability cited from user-facing docs, is mis-audienced.
  6. Add the anchor-rot warning to entities audit: each capability location must resolve (file/dir/glob existence; #symbol/:line degrade to the file check). Warning, not error — rot flags drift, it doesn’t block.
  7. Run sdlc entities migrate over docs/planning/capabilities/; entities audit green.
  8. Populate locations[] per instance from Underlying implementation prose: extract the code paths, remap plugin/…apps/sdlc/…, replace retired targets with their living successors; re-run entities audit — zero anchor-rot warnings across the corpus.
  9. The location-overlap matcher does NOT land here (see Out of scope).
  • AC-1: schema v2 — kind optional 11-grain structural enum; audience user | system default system; SCHEMA_VERSION 2; HISTORICAL_KEY_ORDER places audience and locations deliberately.
  • AC-2: the v1→v2 migration maps featureaudience: user and technicalaudience: system; every instance matched by docs/planning/capabilities/*.md (20 today) migrates clean.
  • AC-3: locations accepts the five-form grammar and defaults to [].
  • AC-4: index sections derive from audience with unchanged grouping (8 user / 12 system).
  • AC-5: capability/definition.md documents the 11 grains, the audience rubric with its falsification signal, and the locations grammar; check_entities passes.
  • AC-6: the site capability page renders locations when present.
  • AC-7: entities audit warns 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 implementation section carries a non-empty locations[]; the full corpus audits with zero anchor-rot warnings (all stale plugin/… pointers remapped or replaced).
  • 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-U3NR as a lib service normalizing the grammar onto @sksizer/intersect. The anchor-rot check here is existence-only and must not grow matching semantics.
  • capability_type and state fields (rejected in D-3PDM-cross-project-data-model-adoption; the audience split is not capability_type — see the D-3PDM amendment of 2026-07-17).
  • T-SJH1 — same capability/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.
  • D-3PDM-cross-project-data-model-adoption P3/P4 / finding F6: donor kind set on 76/76 capabilities (component 22, module 20, subsystem 13, …); locations on 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.

Captured by /sdlc:task-work on 2026-07-18. PR: #843.

  • 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 capability migrated all 20 clean (8 audience: user / 12 audience: system), audit reports no drift
  • AC-3: auto — 16 Location-grammar tests (@lib/util/location); agent-manual — entities validate rejects 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_rot warning, then reverted
  • AC-8: agent-manual — all 10 instances with ## Underlying implementation carry non-empty locations[], every anchor existence-checked before writing; auto — full corpus audit: zero drift, zero anchor-rot
  • 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
  • Step 5a/5b machinery (ensure_ready_mutate --commit-on main, start_task.ts) is broken on this host — commitToMainViaWorktree creates 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 a commit_worktree_init verb 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:mainB-AAQ5
  • dumpFrontmatter crashed on null leaves (parent_key: null tree 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/**/*.ts whose */ 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 parseLocation export vs parse-touchpoints’ private cell parser — renamed the cell parser to parseLocationCell rather than baselining
  • check_entities gates 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.eta but the real seam is the site projection framework (capability/site_projection.ts) — file-to-touch drift; the entity-code-placement convention decided it

← Back to Tasks