Skip to content

B-N9PM-typed-entity-read-layer

Status: promoted/task

Reading an SDLC entity’s data is un-abstracted: each consumer resolves the path, reads the file (working tree only), parseFrontmatter’s into an untyped Record, and indexes fields by hand. Example: sweep.ts#statusAtOriginMain does ‘git show origin/main:’ + parseFrontmatter + fm[‘status’]. Evidence: (1) readTaskDoc (task/ops/_task_doc.ts) only readFileSync’s the WORKING TREE and returns raw {path,text}, not a validated Task, and can’t read at a git rev; (2) 14 files call parseFrontmatter, with at least TWO implementations (@lib/util/frontmatter and a second one inside _task_doc.ts); (3) the Zod schema / TaskContract are wired into validation-on-WRITE (entities validate/audit) but no READ path; (4) reading an entity at a git rev exists only ad hoc in sweep.ts and a couple of tests. PROPOSED: a model-layer accessor readTask(id, {projectRoot, at?: rev, git?: CommandRunner}): Task | null (generalize to readEntity over all entity types) that resolves the path (reuse resolveTaskDocPath), reads from the working tree OR an injected git seam at ‘at’, and returns the INFERRED TYPED object from markdown-contract’s parse(TaskContract, text) — the typed leaf the contract already gives — not an untyped frontmatter record. Then statusAtOriginMain collapses to readTask(taskId, {at:‘origin/main’, git: ctx.git})?.status ?? null and the ~13 other hand-rolled sites converge on one path. Surfaced reviewing PR #905 (T-Z698 lease sweep) 2026-07-19.


← Back to Backlog