Skip to content

T-1ODN-note-read-api

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

M-YX34’s Notes deliverable is “Routes for the note kind and the N prefix, with wikilinks resolved and task lists rendered”. This task is the read-API half: the note kind must be listable and readable over HTTP so the SDF note pages (T-KQ7W) read a real repository through the real API, with no fake service. It follows step 2 of “How each milestone runs” in N-6DUQ-sdlc-1-0-release-plan (read API before screen). Notes stay read-only here.

  • Note schema: solutions/ontological/lib/model/entities/note/schema.ts. Id prefix N. It has genre (enum NOTE_GENRES: plan, research, analysis, strategy, proposal, survey, retro, checklist), optional parent (a note wikilink), state, related. Notes live in docs/planning/notes/ (more than 50 files).
  • The generic routes exist in solutions/ontological/lib/services/dashboard/server.ts: app.get('/api/entities/:type') (L1983) and app.get('/api/entities/:type/:id') (L2002).
  • The list route calls readEntitySummaries, which returns null (HTTP 404) unless :type is a key of BROWSABLE_ENTITY_TYPES (L418-433: decisions, standards, principles, terms, references, backlog, products, drivers, capabilities). notes is not in it, so GET /api/entities/notes is a 404 today.
  • The detail route calls readEntityDetail, which already resolves ANY safe directory name, so GET /api/entities/notes/N-YQVC works today. It returns { id, type, frontmatter, body, body_etag }: raw YAML mapping, raw markdown body, and the body write token.
  • A list row is EntitySummary (contract.ts L241): id, title, state, state_group. It has no genre and no parent, so a client cannot group notes by genre or build a hub tree from the list alone; it would need one detail call per note.
  • readSimpleEntities (server.ts L396) builds the rows and returns MilestoneRow[] (contract.ts L100-107, four fields). It also feeds milestones in /api/state (L987), so adding fields there would change the milestone wire shape. readEntitySummaries reuses it and its comment says EntitySummary and MilestoneRow are structurally identical; that stops being true once entitySummary gains fields.
  • The typed client’s types are generated: lib/services/dashboard/gen-client.ts builds packages/ts/dashboard-client/src/types.ts from contract.ts (c: z.array(entitySummary)). just gen-dashboard-client regenerates it and moon run dashboard-client:check is the drift gate. Changing entitySummary therefore needs a regeneration.
  • Client: packages/ts/dashboard-client/src/index.ts has getEntities(type, project) and getEntity(type, id, project) taking a plain string type, so the call already type-checks for notes. Its JSDoc on getEntities names the allowed types, and packages/ts/dashboard-client/test/entity-roster.test.ts pins that list to the server’s BROWSABLE_ENTITY_TYPES keys. Adding notes to the server without the JSDoc fails that test.
  • The desktop mirror BROWSABLE_ENTITY_TYPES in solutions/ontological/desktop/src-nuxt/app/utils/entityRegistry.ts (L25-35) also lacks notes; that is T-KQ7W’s file.
  • Tests: solutions/ontological/lib/services/dashboard/entities.test.ts (bun:test) covers readEntitySummaries, readEntityDetail, and the HTTP routes with a temp corpus (makeCorpus) and contract parsing (entitySummary, entityDetail).

GET /api/entities/notes lists every note, each row carrying genre and parent besides the existing fields. GET /api/entities/notes/<id> already works and keeps working. No second route family. The client JSDoc and its roster test name notes. Notes are read-only through this surface (the existing PUT .../body is untouched and not used by the note pages).

  1. Add notes: { prefix: 'N' } to BROWSABLE_ENTITY_TYPES in server.ts.
  2. Extend entitySummary in contract.ts with optional nullable genre and parent strings. milestoneRow and readSimpleEntities stay untouched, so the milestone wire shape in /api/state does not change. Add a separate row builder readEntityRows in server.ts that returns EntitySummary[]: it takes the same four fields as readSimpleEntities and adds genre and parent from frontmatter for the notes type only; every other type gets null for both. readEntitySummaries calls it, and its “structurally identical” comment is rewritten to say that EntitySummary extends the milestone row. Keep genre and parent as two named fields; the wire contract is schema-agnostic on purpose, so do not import the note schema.
  3. Update the getEntities JSDoc in packages/ts/dashboard-client/src/index.ts to list notes.
  4. Regenerate the client types with just gen-dashboard-client, which rewrites packages/ts/dashboard-client/src/types.ts from the changed entitySummary.
  5. Extend entities.test.ts: a notes corpus (a hub note, a child with parent, two genres); assert list rows carry genre and parent, detail returns frontmatter, body and body_etag, notes is browsable, and the rows parse against entitySummary.
  6. Run a smoke read over the real docs/planning/notes/ corpus, like the existing smoke test.
AreaNote
solutions/ontological/lib/services/dashboard/server.tsRoster entry, new readEntityRows
solutions/ontological/lib/services/dashboard/contract.tsentitySummary gains genre, parent
solutions/ontological/lib/services/dashboard/entities.test.tsRoute and reader tests
packages/ts/dashboard-client/src/index.tsgetEntities JSDoc
packages/ts/dashboard-client/src/types.tsGenerated; regenerated, not hand edited
packages/ts/dashboard-client/test/entity-roster.test.tsRoster guard, must stay green
  • AC-1: isBrowsableEntityType('notes') is true and GET /api/entities/notes returns 200 with one row per note file, in a temp-corpus test.
  • AC-2: Each row has genre and parent (null when the note lacks them), and parses against entitySummary.
  • AC-3: Existing kinds’ rows still parse; their genre and parent are null. The milestones rows in /api/state still have exactly their four fields.
  • AC-4: GET /api/entities/notes/N-YQVC returns id, frontmatter, body, body_etag over the real corpus, and parses against entityDetail.
  • AC-5: No new /api/notes route exists; the diff adds no second route family.
  • AC-6: packages/ts/dashboard-client/test/entity-roster.test.ts passes with notes in the server roster and in the client JSDoc.
  • AC-7: moon run dashboard-client:check reports no drift.
  • AC-8: AC-1 to AC-6 run against the dashboard server directly and need no token. Against sdf-serve, GET /api/entities/notes returns 200 with the x-platform-token header and is refused without it. The token is enforced by sdf-serve (token_guard in serve.rs, #2546 branch), not by server.ts, so only the live run can check it.
  • bun test solutions/ontological/lib/services/dashboard/entities.test.ts
  • bun test packages/ts/dashboard-client/test
  • moon run dashboard-client:check
  • Start sdf-serve with moon run sdf:serve (listens on 127.0.0.1:16175, SDF_HTTP_PORT). The token is the file path it prints at startup (<durable>/com.sizerllc.ontological/token) or SDF_PLATFORM_TOKEN, per the desktop README on that branch. Then curl -H "x-platform-token: $(cat <token file>)" http://127.0.0.1:16175/api/entities/notes.

CI is the typecheck; vue-tsc is never run locally. This task touches no Vue code, so there is no nuxt generate step; the run against sdf-serve (open PR #2546) is the live check. Every /api/* request through sdf-serve carries the platform token; there is no tokenless read there.

  • The note pages and link rendering (T-KQ7W).
  • Writing notes through the API; PUT /api/entities/:type/:id/body is unchanged.
  • A tree or children endpoint; the client builds hubs from parent.
  • Capability, milestone and task read-API additions (other tasks).
  • None. T-KQ7W depends on this task.

Found while scoping M-YX34’s Notes deliverable. readEntityDetail is already generic; only the list roster and the summary fields block it.


← Back to Tasks