Skip to content

T-G5E8-capability-read-api-milestones-tasks

State: planning/draft · Impact: medium · Complexity: medium

Nothing can answer “which milestones deliver this capability, and what is still open in them?”. The links exist only as forward references in milestone frontmatter. Add one read that answers it for a capability, as a CLI op and an HTTP route with a typed client method, so the capability page (T-14JA) reads real data and not a copy.

  • A milestone lists capabilities in its capabilities frontmatter (lib/model/entities/milestone/schema.ts, an array of C- wikilinks). A milestone lists its tasks in tasks. A task has no milestone field, so tasks reach a capability only through a milestone’s tasks list.
  • Those links are only validated, never indexed in reverse: checkPlanningLinks in lib/model/ops/audit.ts (dangling capabilities targets) and checkMissingCapabilities in lib/model/entities/roadmap/ops/check.ts (missing_capability). No file builds capability to milestones.
  • lib/model/corpus/graph.ts buildEdges already returns reverseEdges (target to sources), and loadCorpus (lib/model/corpus/loader.ts) gives { type, state } per basename. It has no title, so a title needs readRawFrontmatter from lib/model/read.ts.
  • lib/model/entities/capability/ops/graph.ts exports buildCapabilityGraph(root) (nodes with parent, title, kind, state, state_group) and the op capability graph. Other capability ops: coverage.ts, create.ts, update.ts.
  • The dashboard server (lib/services/dashboard/server.ts) exposes one capability route, GET /api/capabilities/graph (about L2022), which resolves its project with scopeOf(c) (?project=, L1563) and calls buildCapabilityGraph(scope.root). Its response schema is the op’s own output, re-exported in lib/services/dashboard/contract.ts as capabilityGraph. The typed client is generated from that contract (packages/ts/dashboard-client/src/types.ts says GENERATED; regenerate with just gen-dashboard-client; drift gate moon run dashboard-client:check) and exposes getCapabilityGraph. The endpoint list that drives that generation is lib/services/dashboard/gen-client.ts (each schema is a key of its wrapper object, for example i: capabilityGraph); a new endpoint’s schema has to be added there or its type is not emitted.
  • server.ts has no platform-token guard. The token (x-platform-token) is enforced by sdf-serve (token_guard in serve.rs, #2546 branch) before it forwards /api/* to this server. This server’s own guards are the loopback Host check and the CSRF token on writes.
  • Task state values (TASK_STATE in lib/model/entities/task/schema.ts): planning/*, open/ready, in-progress, in-progress/blocked, and closed/*. State group is the prefix before /.

One read, computed once per request from the files on disk.

Op sdlc capability relations <id> (read-only, mutating: false) and route GET /api/capabilities/:id/relations. It resolves the project the way the graph route does (scopeOf(c), ?project=), so T-14JA can pass a project. There is no collision with /api/capabilities/graph (three segments against four). The route-order hazard in server.ts is /api/entities/:type/:id, which is why the capability routes have their own prefix. Response:

CapabilityRelations {
capability: CapabilityRef // the asked-for capability
contains: CapabilityRef[] // direct children by parent_key, sorted by id
milestones: MilestoneRelation[] // milestones whose `capabilities` lists it
}
CapabilityRef { id, basename, title, kind, state, state_group }
MilestoneRelation { id, basename, title, state, state_group,
task_total, open_task_total, open_tasks: TaskRef[] }
TaskRef { id, basename, title, state, state_group }
  • “Open task” means a task listed in the milestone’s tasks whose state_group is not closed. That counts planning/*, open/ready and in-progress*, so drafts are included on purpose.
  • milestones carries every milestone that lists the capability, whatever its own state, sorted by id, so a closed milestone still shows with its state.
  • Only direct containment and direct listing count. A milestone that lists a child capability is not rolled up into the parent.
  • An id that is not a capability is an error: op INVALID_INPUT (the code runRoadmapProgress uses), HTTP 404 with { error } that does not echo a filesystem path. An unknown ?project= is refused by scopeOf the same way as on the graph route.
  1. Add lib/model/entities/capability/ops/relations.ts with a pure buildCapabilityRelations(root, id). Load the corpus, build a milestone to capabilities buildEdges over milestone basenames (targetsOf reads capabilities, resolve is resolveTarget), and read reverseEdges for the capability.
  2. For each listed milestone, resolve its tasks (dedupe by resolved basename, as roadmap/ops/progress.ts does), keep type task entries, and split by state_group.
  3. Derive contains from buildCapabilityGraph nodes whose parent is the id.
  4. Export zod schemas with .meta({ id }) and defineOp({ path: ['capability', 'relations'], ... }) with a text renderer. Regenerate the op manifest with solutions/ontological/scripts/gen-op-manifest.ts.
  5. Add the route in server.ts and the schema capabilityRelations in contract.ts. Add capabilityRelations to the wrapper in lib/services/dashboard/gen-client.ts and a getCapabilityRelations(id, project?) method to packages/ts/dashboard-client/src/index.ts, then run just gen-dashboard-client so the types exist.
  6. Tests below.
AreaNote
solutions/ontological/lib/model/entities/capability/ops/New relations.ts and its test
solutions/ontological/lib/model/corpus/buildEdges reverse edges, reused as is
solutions/ontological/lib/services/dashboard/server.tsNew route after /api/capabilities/graph
solutions/ontological/lib/services/dashboard/contract.tsRoute schema from the op output
solutions/ontological/lib/services/dashboard/gen-client.tsAdd the schema to the wrapper
solutions/ontological/lib/_generated/op-manifest.tsRegenerated, not hand edited
packages/ts/dashboard-client/src/Generated types.ts; getCapabilityRelations in index.ts
  • AC-1: A test with a temp corpus (one parent, one child capability, two milestones listing the parent, one listing only the child) asserts contains, and that milestones has only the two milestones that list the parent.
  • AC-2: A test asserts open_tasks excludes closed/* tasks and includes planning/draft, open/ready and in-progress ones, with task_total and open_task_total matching.
  • AC-3: A test asserts an unknown id and a non-capability id fail with INVALID_INPUT.
  • AC-4: GET /api/capabilities/:id/relations returns 200 and parses against the capabilityRelations contract schema, returns 404 for an unknown id, reads the project named by ?project= (and refuses an unknown project), and /api/capabilities/graph still returns the graph.
  • AC-5: The route adds no bypass and no auth of its own: it sits beside /api/capabilities/graph and relies on the same Host gate. The token behaviour is asserted only in the live run against sdf-serve: 200 with x-platform-token, refused without it.
  • AC-6: sdlc capability relations C-0063 prints the milestones and open tasks for that capability in this repository.
  • AC-7: moon run dashboard-client:check reports no drift.
  • bun test solutions/ontological/lib/model/entities/capability
  • bun test solutions/ontological/lib/services/dashboard/entities.test.ts
  • moon run dashboard-client:check
  • sdlc capability relations C-0063 from the repository root.
  • Plain bun test <path> is the check for the dashboard service, as in the commands above.
  • Live run: moon run sdf:serve (127.0.0.1:16175), then curl the route with -H "x-platform-token: <token>" (200) and without it (refused). The token is the file path sdf-serve prints at startup or SDF_PLATFORM_TOKEN.
  • Rolling milestones up from descendant capabilities.
  • Any write route.
  • The UI (T-14JA).
  • Adding a milestone field to tasks.
  • none

← Back to Tasks