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
capabilitiesfrontmatter (lib/model/entities/milestone/schema.ts, an array ofC-wikilinks). A milestone lists its tasks intasks. A task has no milestone field, so tasks reach a capability only through a milestone’staskslist. - Those links are only validated, never indexed in reverse:
checkPlanningLinksinlib/model/ops/audit.ts(danglingcapabilitiestargets) andcheckMissingCapabilitiesinlib/model/entities/roadmap/ops/check.ts(missing_capability). No file builds capability to milestones. lib/model/corpus/graph.tsbuildEdgesalready returnsreverseEdges(target to sources), andloadCorpus(lib/model/corpus/loader.ts) gives{ type, state }per basename. It has no title, so a title needsreadRawFrontmatterfromlib/model/read.ts.lib/model/entities/capability/ops/graph.tsexportsbuildCapabilityGraph(root)(nodes withparent,title,kind,state,state_group) and the opcapability 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 withscopeOf(c)(?project=, L1563) and callsbuildCapabilityGraph(scope.root). Its response schema is the op’s own output, re-exported inlib/services/dashboard/contract.tsascapabilityGraph. The typed client is generated from that contract (packages/ts/dashboard-client/src/types.tssays GENERATED; regenerate withjust gen-dashboard-client; drift gatemoon run dashboard-client:check) and exposesgetCapabilityGraph. The endpoint list that drives that generation islib/services/dashboard/gen-client.ts(each schema is a key of its wrapper object, for examplei: capabilityGraph); a new endpoint’s schema has to be added there or its type is not emitted. server.tshas no platform-token guard. The token (x-platform-token) is enforced bysdf-serve(token_guardinserve.rs, #2546 branch) before it forwards/api/*to this server. This server’s own guards are the loopbackHostcheck and the CSRF token on writes.- Task state values (
TASK_STATEinlib/model/entities/task/schema.ts):planning/*,open/ready,in-progress,in-progress/blocked, andclosed/*. State group is the prefix before/.
Proposed
Section titled “Proposed”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
taskswhosestate_groupis notclosed. That countsplanning/*,open/readyandin-progress*, so drafts are included on purpose. milestonescarries 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 coderunRoadmapProgressuses), HTTP 404 with{ error }that does not echo a filesystem path. An unknown?project=is refused byscopeOfthe same way as on the graph route.
Approach
Section titled “Approach”- Add
lib/model/entities/capability/ops/relations.tswith a purebuildCapabilityRelations(root, id). Load the corpus, build a milestone to capabilitiesbuildEdgesover milestone basenames (targetsOfreadscapabilities,resolveisresolveTarget), and readreverseEdgesfor the capability. - For each listed milestone, resolve its
tasks(dedupe by resolved basename, asroadmap/ops/progress.tsdoes), keep typetaskentries, and split bystate_group. - Derive
containsfrombuildCapabilityGraphnodes whoseparentis the id. - Export zod schemas with
.meta({ id })anddefineOp({ path: ['capability', 'relations'], ... })with a text renderer. Regenerate the op manifest withsolutions/ontological/scripts/gen-op-manifest.ts. - Add the route in
server.tsand the schemacapabilityRelationsincontract.ts. AddcapabilityRelationsto the wrapper inlib/services/dashboard/gen-client.tsand agetCapabilityRelations(id, project?)method topackages/ts/dashboard-client/src/index.ts, then runjust gen-dashboard-clientso the types exist. - Tests below.
| Area | Note |
|---|---|
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.ts | New route after /api/capabilities/graph |
solutions/ontological/lib/services/dashboard/contract.ts | Route schema from the op output |
solutions/ontological/lib/services/dashboard/gen-client.ts | Add the schema to the wrapper |
solutions/ontological/lib/_generated/op-manifest.ts | Regenerated, not hand edited |
packages/ts/dashboard-client/src/ | Generated types.ts; getCapabilityRelations in index.ts |
Acceptance criteria
Section titled “Acceptance criteria”- 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 thatmilestoneshas only the two milestones that list the parent. - AC-2: A test asserts
open_tasksexcludesclosed/*tasks and includesplanning/draft,open/readyandin-progressones, withtask_totalandopen_task_totalmatching. - AC-3: A test asserts an unknown id and a non-capability id fail with
INVALID_INPUT. - AC-4:
GET /api/capabilities/:id/relationsreturns 200 and parses against thecapabilityRelationscontract schema, returns 404 for an unknown id, reads the project named by?project=(and refuses an unknown project), and/api/capabilities/graphstill returns the graph. - AC-5: The route adds no bypass and no auth of its own: it sits beside
/api/capabilities/graphand relies on the sameHostgate. The token behaviour is asserted only in the live run againstsdf-serve: 200 withx-platform-token, refused without it. - AC-6:
sdlc capability relations C-0063prints the milestones and open tasks for that capability in this repository. - AC-7:
moon run dashboard-client:checkreports no drift.
Verification
Section titled “Verification”bun test solutions/ontological/lib/model/entities/capabilitybun test solutions/ontological/lib/services/dashboard/entities.test.tsmoon run dashboard-client:checksdlc capability relations C-0063from 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), thencurlthe route with-H "x-platform-token: <token>"(200) and without it (refused). The token is the file pathsdf-serveprints at startup orSDF_PLATFORM_TOKEN.
Out of scope
Section titled “Out of scope”- Rolling milestones up from descendant capabilities.
- Any write route.
- The UI (
T-14JA). - Adding a milestone field to tasks.
Dependencies
Section titled “Dependencies”- none