* chore(#2143): fail-loud Result + per-surface write-set contract — Phase 3 Phase 3 of epic #2143 (ADR-2143 §5/§6). The three target bugs (#2140, #2112, #2118) were already fixed tactically on next; this introduces the reusable structural contracts and rewires the primary #2140 site onto them. - src/write-set.cts (new): the parse `Result<T> = {ok,value|reason}` (§5) and the per-surface write-set (`WriteOutcome {surface, applied, requirement?}`, `WriteSet`, `writeSetComplete`) (§6). markdown-table.cts now imports + re-exports `Result` from here (single source; distinct from command-routing-hub's Result). - requirements mark-complete (src/milestone.cts): returns a PER-REQUIREMENT, per-surface write-set; `write_set_complete` is true only if every surface of every requirement applied — structurally forbidding the #2140 OR-into-one-flag masking, including across a multi-ID batch (adversarial-review regression). Pre-existing output fields unchanged (behaviour-preserving; #2140 already fixed). - deriveProgressFromRoadmap (src/phase-lifecycle.cts): removed the vestigial null-swallowing try/catch (findTableWithColumns never throws) — ADR §5 no-swallow; RoadmapProgress return contract unchanged. - commit --files (#2112) and milestone complete --dry-run (#2118) left as-is (single-surface commit / pre-mutation preview — not genuine multi-surface writes). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * chore(#2244): backfill changeset PR number (#2251) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
55
src/write-set.cts
Normal file
55
src/write-set.cts
Normal file
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* Write-Set — shared fail-loud parse `Result` and per-surface write-set
|
||||
* contracts (ADR-2143, epic #2143). Pure, Node built-ins only, no I/O.
|
||||
* Compiled by tsc to gsd-core/bin/lib/write-set.cjs.
|
||||
*
|
||||
* ADR-2143 §5 (fail-loud parsing, no null-swallow): seam parse operations
|
||||
* and document-model accessors return a typed `Result<T>` — never a bare
|
||||
* `null` a caller can mistake for "empty but fine." This is the same
|
||||
* `{ ok: true; value: T } | { ok: false; reason: string }` shape
|
||||
* `markdown-table.cts` already defined for `parseMarkdownTable` /
|
||||
* `appendQuickTaskRow`; this module is now the single source of truth for
|
||||
* it and `markdown-table.cjs` re-exports the type so existing importers of
|
||||
* `Result` from that module keep working unchanged.
|
||||
*
|
||||
* NOTE: deliberately distinct from command-routing-hub's dispatch `Result`
|
||||
* (`{ok,data}|{ok:false,kind}`) — the two never mix (different modules,
|
||||
* different shapes, different purposes).
|
||||
*
|
||||
* ADR-2143 §6 (write-set results for multi-surface commands, no
|
||||
* OR-into-one-flag): a command that mutates more than one surface returns
|
||||
* an explicit per-surface write-set — `{ surface, applied }` outcomes — and
|
||||
* its top-level "did this fully succeed" signal is true only if EVERY
|
||||
* surface in the set applied. ORing independent surfaces into a single
|
||||
* boolean is the direct anti-pattern that let a checkbox-only partial
|
||||
* write (#2140) report full success.
|
||||
*/
|
||||
|
||||
export type Result<T> = { ok: true; value: T } | { ok: false; reason: string };
|
||||
|
||||
/**
|
||||
* One surface's outcome within a multi-surface write (ADR-2143 §6).
|
||||
*
|
||||
* `requirement` identifies which entity/ID this surface outcome belongs to,
|
||||
* so a multi-entity command's write-set does not collapse independent
|
||||
* entities into one aggregate — the §6 "no OR into one flag" rule applies
|
||||
* per entity too, not just per surface.
|
||||
*/
|
||||
export interface WriteOutcome {
|
||||
surface: string;
|
||||
applied: boolean;
|
||||
requirement?: string;
|
||||
}
|
||||
|
||||
/** The full set of per-surface outcomes for one multi-surface write. */
|
||||
export type WriteSet = WriteOutcome[];
|
||||
|
||||
/**
|
||||
* True only if the write-set is non-empty AND every surface in it applied.
|
||||
* An empty write-set is never "complete" — there is nothing to be complete
|
||||
* about, so treating it as vacuously true would let a no-op masquerade as
|
||||
* a full success (the same OR-into-one-flag class ADR-2143 §6 prohibits).
|
||||
*/
|
||||
export function writeSetComplete(ws: WriteSet): boolean {
|
||||
return ws.length > 0 && ws.every((o) => o.applied);
|
||||
}
|
||||
Reference in New Issue
Block a user