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 prefixN. It hasgenre(enumNOTE_GENRES: plan, research, analysis, strategy, proposal, survey, retro, checklist), optionalparent(a note wikilink),state,related. Notes live indocs/planning/notes/(more than 50 files). - The generic routes exist in
solutions/ontological/lib/services/dashboard/server.ts:app.get('/api/entities/:type')(L1983) andapp.get('/api/entities/:type/:id')(L2002). - The list route calls
readEntitySummaries, which returns null (HTTP 404) unless:typeis a key ofBROWSABLE_ENTITY_TYPES(L418-433: decisions, standards, principles, terms, references, backlog, products, drivers, capabilities).notesis not in it, soGET /api/entities/notesis a 404 today. - The detail route calls
readEntityDetail, which already resolves ANY safe directory name, soGET /api/entities/notes/N-YQVCworks 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.tsL241):id,title,state,state_group. It has nogenreand noparent, 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 returnsMilestoneRow[](contract.tsL100-107, four fields). It also feedsmilestonesin/api/state(L987), so adding fields there would change the milestone wire shape.readEntitySummariesreuses it and its comment saysEntitySummaryandMilestoneRoware structurally identical; that stops being true onceentitySummarygains fields.- The typed client’s types are generated:
lib/services/dashboard/gen-client.tsbuildspackages/ts/dashboard-client/src/types.tsfromcontract.ts(c: z.array(entitySummary)).just gen-dashboard-clientregenerates it andmoon run dashboard-client:checkis the drift gate. ChangingentitySummarytherefore needs a regeneration. - Client:
packages/ts/dashboard-client/src/index.tshasgetEntities(type, project)andgetEntity(type, id, project)taking a plain stringtype, so the call already type-checks fornotes. Its JSDoc ongetEntitiesnames the allowed types, andpackages/ts/dashboard-client/test/entity-roster.test.tspins that list to the server’sBROWSABLE_ENTITY_TYPESkeys. Addingnotesto the server without the JSDoc fails that test. - The desktop mirror
BROWSABLE_ENTITY_TYPESinsolutions/ontological/desktop/src-nuxt/app/utils/entityRegistry.ts(L25-35) also lacks notes; that isT-KQ7W’s file. - Tests:
solutions/ontological/lib/services/dashboard/entities.test.ts(bun:test) coversreadEntitySummaries,readEntityDetail, and the HTTP routes with a temp corpus (makeCorpus) and contract parsing (entitySummary,entityDetail).
Proposed
Section titled “Proposed”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).
Approach
Section titled “Approach”- Add
notes: { prefix: 'N' }toBROWSABLE_ENTITY_TYPESinserver.ts. - Extend
entitySummaryincontract.tswith optional nullablegenreandparentstrings.milestoneRowandreadSimpleEntitiesstay untouched, so the milestone wire shape in/api/statedoes not change. Add a separate row builderreadEntityRowsinserver.tsthat returnsEntitySummary[]: it takes the same four fields asreadSimpleEntitiesand addsgenreandparentfrom frontmatter for thenotestype only; every other type gets null for both.readEntitySummariescalls it, and its “structurally identical” comment is rewritten to say thatEntitySummaryextends the milestone row. Keepgenreandparentas two named fields; the wire contract is schema-agnostic on purpose, so do not import the note schema. - Update the
getEntitiesJSDoc inpackages/ts/dashboard-client/src/index.tsto listnotes. - Regenerate the client types with
just gen-dashboard-client, which rewritespackages/ts/dashboard-client/src/types.tsfrom the changedentitySummary. - Extend
entities.test.ts: a notes corpus (a hub note, a child withparent, two genres); assert list rows carrygenreandparent, detail returns frontmatter, body andbody_etag,notesis browsable, and the rows parse againstentitySummary. - Run a smoke read over the real
docs/planning/notes/corpus, like the existing smoke test.
| Area | Note |
|---|---|
solutions/ontological/lib/services/dashboard/server.ts | Roster entry, new readEntityRows |
solutions/ontological/lib/services/dashboard/contract.ts | entitySummary gains genre, parent |
solutions/ontological/lib/services/dashboard/entities.test.ts | Route and reader tests |
packages/ts/dashboard-client/src/index.ts | getEntities JSDoc |
packages/ts/dashboard-client/src/types.ts | Generated; regenerated, not hand edited |
packages/ts/dashboard-client/test/entity-roster.test.ts | Roster guard, must stay green |
Acceptance criteria
Section titled “Acceptance criteria”- AC-1:
isBrowsableEntityType('notes')is true andGET /api/entities/notesreturns 200 with one row per note file, in a temp-corpus test. - AC-2: Each row has
genreandparent(null when the note lacks them), and parses againstentitySummary. - AC-3: Existing kinds’ rows still parse; their
genreandparentare null. Themilestonesrows in/api/statestill have exactly their four fields. - AC-4:
GET /api/entities/notes/N-YQVCreturnsid,frontmatter,body,body_etagover the real corpus, and parses againstentityDetail. - AC-5: No new
/api/notesroute exists; the diff adds no second route family. - AC-6:
packages/ts/dashboard-client/test/entity-roster.test.tspasses withnotesin the server roster and in the client JSDoc. - AC-7:
moon run dashboard-client:checkreports 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/notesreturns 200 with thex-platform-tokenheader and is refused without it. The token is enforced bysdf-serve(token_guardinserve.rs, #2546 branch), not byserver.ts, so only the live run can check it.
Verification
Section titled “Verification”bun test solutions/ontological/lib/services/dashboard/entities.test.tsbun test packages/ts/dashboard-client/testmoon run dashboard-client:check- Start
sdf-servewithmoon 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) orSDF_PLATFORM_TOKEN, per the desktop README on that branch. Thencurl -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.
Out of scope
Section titled “Out of scope”- The note pages and link rendering (
T-KQ7W). - Writing notes through the API;
PUT /api/entities/:type/:id/bodyis unchanged. - A tree or children endpoint; the client builds hubs from
parent. - Capability, milestone and task read-API additions (other tasks).
Dependencies
Section titled “Dependencies”- None.
T-KQ7Wdepends on this task.
Discovery context
Section titled “Discovery context”Found while scoping M-YX34’s Notes deliverable. readEntityDetail is already generic; only the
list roster and the summary fields block it.