From 61be100a02b2604deb7295f965f9c29ab8416b78 Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Tue, 23 Jun 2026 21:42:16 -0400 Subject: [PATCH] =?UTF-8?q?docs(#1642):=20amend=20ADR-0174=20=C2=A75=20?= =?UTF-8?q?=E2=80=94=20reconcile=20Result=20type=20+=20add=20exitReason=3F?= =?UTF-8?q?=20(#1643)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two changes to ADR-0174 §5 (Sync dispatch with tight-typed Result): 1. Reconcile the documented Result type to the as-built code. The original §5 text planned 'Unknown' / 'BadArgs' / 'ValidationFailed' / 'NotImplemented' / 'HandlerFailed'; the SDK retirement migration kept the ADR-0012 names (UnknownCommand / InvalidArgs / HandlerFailure) and never added the planned ValidationFailed or NotImplemented variants. HandlerRefusal was added during implementation but never back-filled into this ADR. The ADR now documents what consumers actually depend on. 2. Add the optional exitReason?: string field on the InvalidArgs variant (and update the makeInvalidArgs factory signature). This carries an ERROR_REASON enum value separately from the existing reason explanation text, so capability routers migrating from direct error(msg, ERROR_REASON.USAGE) calls to makeInvalidArgs(...) Results preserve ERROR_REASON granularity through the Hub Result → error(msg, exitReason) translation. The field is additive and backward-compatible. Also tightens the amendment requirement: 'Adding a new variant OR adding a field to an existing variant requires amending this ADR.' Phase 0 of parent #1641. No code changes; pure ADR amendment. --- .../0174-retire-gsd-sdk-package-boundary.md | 30 ++++++++++++++----- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/docs/adr/0174-retire-gsd-sdk-package-boundary.md b/docs/adr/0174-retire-gsd-sdk-package-boundary.md index 93d9fee1a..6c4a7117c 100644 --- a/docs/adr/0174-retire-gsd-sdk-package-boundary.md +++ b/docs/adr/0174-retire-gsd-sdk-package-boundary.md @@ -1,6 +1,6 @@ # ADR-0174: Retire @opengsd/gsd-sdk package boundary — single-runtime collapse -- **Status:** Accepted (2026-05-23) +- **Status:** Accepted (2026-05-23); amended #1642 (2026-06-23) — §5 reconciled to as-built Result type + `exitReason?` field added on `InvalidArgs` - **Date:** 2026-05-23 - **Tracking issue:** [#174](https://github.com/open-gsd/get-shit-done-redux/issues/174) — sub-issues #175–#197 @@ -71,19 +71,33 @@ Dispatch is synchronous: `dispatch(req: DispatchRequest): Result`. Rationale: continuous stack traces, no async-boundary races in the logger, no orphaned side effects, SIGINT shows what is actually running. `synckit` dependency is removed. -The `Result` type is a discriminated union per `errorKind` variant, not a flat string field: +The `Result` type is a discriminated union per `errorKind` variant, not a flat string field. The as-built type (in `src/command-routing-hub.cts`) is: ```ts type Result = | { ok: true; data: T } - | { ok: false; kind: 'Unknown'; command: string } - | { ok: false; kind: 'BadArgs'; arg: string; reason: string } - | { ok: false; kind: 'ValidationFailed'; field: string; expected: string; actual: unknown } - | { ok: false; kind: 'HandlerFailed'; message: string; cause?: Error } - | { ok: false; kind: 'NotImplemented'; command: string }; + | { ok: false; kind: 'UnknownCommand'; command: string } + | { ok: false; kind: 'InvalidArgs'; arg: string; reason: string; exitReason?: string } + | { ok: false; kind: 'HandlerRefusal'; reason: string } + | { ok: false; kind: 'HandlerFailure'; message: string; cause?: Error }; ``` -Adding a new variant requires amending this ADR (preserving the drift-prevention property from ADR-0012). +> **Drift note (amendment #1642, 2026-06-23):** the original §5 text specified a different planned shape — `'Unknown'` / `'BadArgs'` / `'ValidationFailed'` / `'NotImplemented'` / `'HandlerFailed'`. The SDK retirement migration kept the ADR-0012 names (`UnknownCommand` / `InvalidArgs` / `HandlerFailure`) and never added the planned `ValidationFailed` or `NotImplemented` variants; `HandlerRefusal` was added during implementation but never back-filled into this ADR. This amendment reconciles the ADR to the as-built code so the contract documented here matches what consumers actually depend on. The drift was caught during architecture review (parent #1641). + +**Factories** (`src/command-routing-hub.cts`): + +```ts +makeUnknownCommand(command: string) → Readonly +makeInvalidArgs(arg: string, reason: string, exitReason?: string) → Readonly +makeHandlerRefusal(reason: string) → Readonly +makeHandlerFailure(message: string, cause?: unknown) → HandlerFailureResult +``` + +**The `exitReason?` field on `InvalidArgs`** (added by this amendment) carries an `ERROR_REASON` enum value (e.g. `ERROR_REASON.USAGE`) separately from the existing `reason` explanation text. This lets routers that today call `error(msg, ERROR_REASON.USAGE)` directly — bypassing the Hub — preserve `ERROR_REASON` granularity when they migrate to returning `makeInvalidArgs(...)` Results through the Hub. The field is optional and additive; existing callers are unaffected. + +**Dispatcher translation contract:** when an adapter translates an `InvalidArgs` Result whose `exitReason` is present, it passes `exitReason` as the second argument to `error(message, exitReason)` so the JSON-error envelope (`GSD_JSON_ERRORS=1`) preserves the typed reason for downstream consumers (CLI tests, integration harnesses). + +Adding a new variant **or adding a field to an existing variant** requires amending this ADR (preserving the drift-prevention property from ADR-0012). ### 6. Observability seam — silent on success, structured JSON on error, opt-in audit