rcf-lite 0.18.0 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/blueprints/application-api-rest/blueprint.json +12 -4
  3. package/blueprints/application-api-rest/contributions/adrs/adr-304-application-api-rest-logging.json +3 -3
  4. package/blueprints/application-api-rest/docs/topics.md +4 -2
  5. package/blueprints/application-error-handling/README.md +42 -0
  6. package/blueprints/application-error-handling/assets/schemas/error-record.schema.json +17 -0
  7. package/blueprints/application-error-handling/blueprint.json +27 -0
  8. package/blueprints/application-error-handling/contributions/adrs/adr-1701-application-error-handling-record-shape.json +25 -0
  9. package/blueprints/application-error-handling/contributions/adrs/adr-1702-application-error-handling-classification-vocabulary.json +20 -0
  10. package/blueprints/application-error-handling/contributions/adrs/adr-1703-application-error-handling-transport-mapping.json +20 -0
  11. package/blueprints/application-error-handling/contributions/requirements/application-error-handling-req-001.json +15 -0
  12. package/blueprints/application-error-handling/contributions/requirements/application-error-handling-req-002.json +15 -0
  13. package/blueprints/application-error-handling/contributions/requirements/application-error-handling-req-003.json +15 -0
  14. package/blueprints/application-error-handling/contributions/requirements/application-error-handling-req-004.json +15 -0
  15. package/blueprints/application-error-handling/contributions/tacs/tac-1701-application-error-handling-boundary.json +45 -0
  16. package/blueprints/application-error-handling/contributions/tacs/tac-1702-application-error-handling-record-factory.json +40 -0
  17. package/blueprints/application-error-handling/contributions/user-stories/application-error-handling-us-16101.json +25 -0
  18. package/blueprints/application-error-handling/contributions/user-stories/application-error-handling-us-16102.json +25 -0
  19. package/blueprints/application-error-handling/contributions/user-stories/application-error-handling-us-16103.json +34 -0
  20. package/blueprints/application-error-handling/contributions/user-stories/application-error-handling-us-16104.json +25 -0
  21. package/blueprints/application-error-handling/contributions/user-stories/application-error-handling-us-16105.json +25 -0
  22. package/blueprints/application-error-handling/contributions/user-stories/application-error-handling-us-16106.json +25 -0
  23. package/blueprints/application-error-handling/contributions/user-stories/application-error-handling-us-16107.json +25 -0
  24. package/blueprints/application-error-handling/docs/topics.md +24 -0
  25. package/blueprints/application-error-handling/guide/application-error-handling.md +35 -0
  26. package/blueprints/application-spa/blueprint.json +12 -2
  27. package/blueprints/application-spa/docs/topics.md +2 -0
  28. package/blueprints/observability-logging/README.md +44 -0
  29. package/blueprints/observability-logging/assets/samples/log-line.json +13 -0
  30. package/blueprints/observability-logging/blueprint.json +27 -0
  31. package/blueprints/observability-logging/contributions/adrs/adr-1601-observability-logging-line-shape.json +25 -0
  32. package/blueprints/observability-logging/contributions/adrs/adr-1602-observability-logging-correlation-id-header.json +25 -0
  33. package/blueprints/observability-logging/contributions/adrs/adr-1603-observability-logging-redaction-categories.json +25 -0
  34. package/blueprints/observability-logging/contributions/adrs/adr-1604-observability-logging-level-vocabulary.json +20 -0
  35. package/blueprints/observability-logging/contributions/requirements/observability-logging-req-001.json +15 -0
  36. package/blueprints/observability-logging/contributions/requirements/observability-logging-req-002.json +15 -0
  37. package/blueprints/observability-logging/contributions/requirements/observability-logging-req-003.json +15 -0
  38. package/blueprints/observability-logging/contributions/requirements/observability-logging-req-004.json +15 -0
  39. package/blueprints/observability-logging/contributions/requirements/observability-logging-req-005.json +15 -0
  40. package/blueprints/observability-logging/contributions/tacs/tac-1601-observability-logging-logger-factory.json +46 -0
  41. package/blueprints/observability-logging/contributions/tacs/tac-1602-observability-logging-redaction-boundary.json +27 -0
  42. package/blueprints/observability-logging/contributions/user-stories/observability-logging-us-15101.json +34 -0
  43. package/blueprints/observability-logging/contributions/user-stories/observability-logging-us-15102.json +43 -0
  44. package/blueprints/observability-logging/contributions/user-stories/observability-logging-us-15103.json +34 -0
  45. package/blueprints/observability-logging/contributions/user-stories/observability-logging-us-15104.json +25 -0
  46. package/blueprints/observability-logging/contributions/user-stories/observability-logging-us-15105.json +34 -0
  47. package/blueprints/observability-logging/contributions/user-stories/observability-logging-us-15106.json +25 -0
  48. package/blueprints/observability-logging/contributions/user-stories/observability-logging-us-15107.json +25 -0
  49. package/blueprints/observability-logging/contributions/user-stories/observability-logging-us-15108.json +25 -0
  50. package/blueprints/observability-logging/docs/topics.md +21 -0
  51. package/blueprints/observability-logging/guide/observability-logging.md +36 -0
  52. package/fixtures/canary-manifest.json +6 -6
  53. package/guidance/harness-template.md +8 -0
  54. package/guidance/managed/agent-instructions-block.hash +1 -1
  55. package/guidance/managed/agent-instructions-block.md +8 -0
  56. package/package.json +1 -1
  57. package/rcf/code-nodes/cn-077.json +14 -0
  58. package/rcf/code-nodes/cn-078.json +15 -0
  59. package/rcf/code-nodes/cn-079.json +14 -0
  60. package/rcf/code-nodes/cn-080.json +15 -0
  61. package/rcf/code-nodes/cn-081.json +15 -0
  62. package/rcf/code-nodes/cn-082.json +15 -0
  63. package/rcf/code-nodes/cn-083.json +14 -0
  64. package/rcf/code-nodes/cn-084.json +15 -0
  65. package/rcf/code-nodes/cn-085.json +14 -0
  66. package/rcf/code-nodes/cn-086.json +16 -0
  67. package/rcf/code-nodes/cn-087.json +14 -0
  68. package/rcf/code-nodes/cn-088.json +14 -0
  69. package/rcf/code-nodes/cn-089.json +14 -0
  70. package/rcf/code-nodes/cn-090.json +14 -0
  71. package/rcf/code-nodes/cn-091.json +14 -0
  72. package/rcf/code-nodes/cn-092.json +15 -0
  73. package/rcf/code-nodes/cn-093.json +15 -0
  74. package/rcf/code-nodes/cn-094.json +14 -0
  75. package/rcf/code-nodes/cn-095.json +14 -0
  76. package/rcf/code-nodes/cn-096.json +14 -0
  77. package/rcf/code-nodes/cn-097.json +14 -0
  78. package/rcf/fbs/fbs-028.json +27 -0
  79. package/rcf/fbs/fbs-029.json +27 -0
  80. package/rcf/fbs/fbs-030.json +27 -0
  81. package/rcf/fbs/fbs-031.json +27 -0
  82. package/rcf/fbs/fbs-032.json +27 -0
  83. package/rcf/fbs/fbs-033.json +27 -0
  84. package/rcf/fbs/fbs-034.json +27 -0
  85. package/rcf/requirements/req-013.json +22 -0
  86. package/rcf/requirements/req-014.json +22 -0
  87. package/rcf/requirements/req-015.json +21 -0
  88. package/rcf/test-suites/ts-038.json +41 -0
  89. package/rcf/test-suites/ts-039.json +41 -0
  90. package/rcf/test-suites/ts-040.json +57 -0
  91. package/rcf/test-suites/ts-041.json +57 -0
  92. package/rcf/test-suites/ts-042.json +57 -0
  93. package/rcf/test-suites/ts-043.json +49 -0
  94. package/rcf/test-suites/ts-044.json +41 -0
  95. package/rcf/user-stories/us-1301.json +43 -0
  96. package/rcf/user-stories/us-1302.json +43 -0
  97. package/rcf/user-stories/us-1401.json +43 -0
  98. package/rcf/user-stories/us-1402.json +43 -0
  99. package/rcf/user-stories/us-1403.json +43 -0
  100. package/rcf/user-stories/us-1404.json +43 -0
  101. package/rcf/user-stories/us-1501.json +43 -0
  102. package/releases/releases.yaml +11 -1
  103. package/src/blueprint/apply.js +8 -0
  104. package/src/blueprint/companions.js +485 -0
  105. package/src/blueprint/index.js +16 -0
  106. package/src/blueprint/loader.js +243 -1
  107. package/src/cli/blueprint.js +309 -0
  108. package/src/cli/validate.js +7 -0
package/CHANGELOG.md CHANGED
@@ -6,6 +6,44 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.19.0] - 2026-09-04
10
+
11
+ Core companions train. Two new general-enterprise-practice blueprints land on the shipped core shelf, an additive companion-suggestion mechanism baked into the manifest, and the standards-derived-blueprint discipline written into the authoring standard as section 8a.
12
+
13
+ ### Added
14
+
15
+ - **`observability-logging` v1.0.0 on the shelf.** New core blueprint (`blueprints/observability-logging/`), category `observability`, US band `15101-15899`, ADR/TAC suffix block `16xx`. Contributes five REQs, eight USs, two TACs (`TAC-1601-observability-logging-logger-factory`, `TAC-1602-observability-logging-redaction-boundary`), four ADRs. Declares `providesRoles: ["logging"]`. `ADR-1601-observability-logging-line-shape` is the shelf-canonical `scope: global` claimant on the `logging` global topic (transferred from `application-api-rest-ADR-304`, which is retained on api-rest as historical context without `scope: global` from api-rest v2.1.0). Ships the ECS-neutral single-line JSON emission contract, correlation identifier acceptance / propagation / reflection, PII redaction at the emission boundary by named category, operator-elicited minimum level per environment, and a hermetic in-memory capture mode for tests.
16
+
17
+ - **`application-error-handling` v1.0.0 on the shelf.** New core blueprint (`blueprints/application-error-handling/`), category `application`, US band `16101-16899`, ADR/TAC suffix block `17xx`. Contributes four REQs, seven USs, two TACs (`TAC-1701-application-error-handling-boundary`, `TAC-1702-application-error-handling-record-factory`), three ADRs. Declares `providesRoles: ["errorHandling"]` and `suggestedCompanions: [{role: "logging", ...}]`. `ADR-1701-application-error-handling-record-shape` mints the `errorHandling` global topic (distinct from `errorEnvelope`, which stays with `application-api-rest` as the REST wire shape). Ships process-level and framework-level boundaries, the shared internal error record shape (code, category, message, correlationId, cause chain, redacted context), the transient / permanent / unknown classification vocabulary, and delegation of the wire mapping through a substitutable `transportWriter` interface.
18
+
19
+ - **Companion-suggestion mechanism (spec section 2).** Two additive optional top-level fields on `blueprint.json`: `providesRoles: string[]` (lower camelCase; paired scope:global ADR on the topic string is required) and `suggestedCompanions: [{role, reason}]` (reason has em-dash and emoji refusals to keep operator-facing prose plain). A blueprint declaring neither still loads clean (every shelf blueprint outside this train). Deterministic tier ladder resolver (applied provider > registered library provider > core-shelf provider) with a project-level pin file `rcf/companions.json` (schemaVersion 1, current pin per role, `pinnedAt` ISO-8601, rides git) overriding the library + shelf tiers. Two library candidates for one role with no pin refuse exit 3 with a three-path resolution message (adopt at apply via `--companion`, pin project-wide via `rcf define blueprint companions set`, or `rcf define blueprint library remove` one of the libraries). Backward-compatible: a service blueprint that does not declare `suggestedCompanions[]` produces an empty resolution and prints no block.
20
+
21
+ - **New CLI verbs and flags on `rcf define blueprint`.** `--companion <role>=<slug>` on `add` (repeatable) accepts `<libraryPrefix>:<slug>` for a library provider or a bare kebab slug for a shelf provider, preflight-refuses exit 2 when the named provider does not declare `providesRoles` containing the role (`--companion <role>=<slug>: blueprint '<slug>' does not declare providesRoles containing '<role>'.`), and writes the accepted pins to `rcf/companions.json`. `--no-companion-suggestions` on `add` suppresses both the preflight pin phase and the post-apply resolved suggestion block. `rcf define blueprint companions <slug>` prints the resolved companion set for an applied service blueprint with origin annotations (`already applied` / `pinned via rcf/companions.json` / `registered library '<prefix>' (overrides shelf provider <slug>)` / `shelf fallback (no registered library provides this role)`); refuses exit 3 on ambiguous libraries. `rcf define blueprint companions set <role> <slug>` writes a project-level pin; `unset <role>` removes it. `--json` on all three sub-verbs emits a machine-readable envelope preserving `suggestedCompanions[]` order.
22
+
23
+ - **Apply-time suggestion block.** After a successful `rcf define blueprint add` on a service blueprint that declares `suggestedCompanions[]`, stdout carries a `Suggested companions this blueprint recommends alongside it:` header followed by one line per role in the shape ` <role> -> <resolved-provider> (<origin>)`, then a `Apply either with: rcf define blueprint add <slug>` footer. The apply itself never adopts a companion (that is the operator's decision). Suppressed by `--no-companion-suggestions`.
24
+
25
+ - **`rcf define validate` refuses exit 3 on an unresolvable pin (spec section 5).** When `rcf/companions.json` pins a role to a slug that no applied blueprint, no registered library and no shelf blueprint provides, validate exits 3 with the ratified message shape `rcf/companions.json pins role '<role>' to '<slug>' but no such provider is applied, registered or on the shelf.`. An absent companions.json is not itself an error.
26
+
27
+ - **Standards-derived-blueprint discipline as authoring standard section 8a.** The rule is documented in `packages/rcf-lite/docs/blueprint-authoring.md` section 8a: MUST clauses become ACs where the clause binds a testable runtime or artefact behaviour; a MUST that binds an operator choice (choice-shaped) may land as a `recommendedDefault: true` ADR carrying the clause id on `standardsTraceClause`; SHOULD clauses become recommended ADR defaults; MAY clauses become elicited ADR choices; not-carried clauses stay allowed with a named reason. Three additive optional per-ADR-contribution fields on `blueprint.json`: `recommendedDefault: boolean`, `elicited: boolean`, `standardsTraceClause: string` (the standard clause identifier verbatim, or the sentinel `"generic enterprise practice"`). One additive optional blueprint-level field: `standardsTrace: [{id, version}]`. When `standardsTrace[]` is declared, every ADR contribution MUST carry a non-null `standardsTraceClause` (loader refusal shape: `blueprint '<slug>' declares standardsTrace but ADR contribution '<id>' has no standardsTraceClause; every ADR must reference a standard clause or the sentinel 'generic enterprise practice'.`, exit 2 at the CLI edge). No cross-check on clause severity to kind mapping (amendment A2 Baz 2026-09-04T12:20:31Z): the discipline is prose in section 8a, not code.
28
+
29
+ - **Roles registry (section 6a).** `logging` (shelf provider `observability-logging`), `errorHandling` (shelf provider `application-error-handling`). New roles land in the registry with a chunk-zero-style edit at ratification time, no loader change.
30
+
31
+ - **Companion-suggestion paragraph on the managed agent-instructions block.** `guidance/managed/agent-instructions-block.md` gains one paragraph under `How to talk to your operator` telling the agent to surface resolved companions verbally, prefer the library provider over the shelf when a library is registered, and never apply a companion without the operator's explicit go. `packages/rcf-lite/guidance/managed/agent-instructions-block.hash` regenerated to `129f4018f526ececaef7ae5bea19ecd72be03a957e2cf2ee3323e85f117f6cba`.
32
+
33
+ ### Changed
34
+
35
+ - **`application-api-rest`: 2.0.0 -> 2.1.0** (minor bump: additive `suggestedCompanions[{role: "logging", ...}, {role: "errorHandling", ...}]`; `ADR-304-application-api-rest-logging` retained on the blueprint without `scope: global` and re-titled `Historical: ... (superseded by observability-logging-ADR-1601 as the shelf-canonical logging global-topic owner from api-rest v2.1.0)`; `docs/topics.md` records the transfer and adds the two new blueprints to the shelf-wide band registry).
36
+ - **`application-spa`: 1.4.0 -> 1.5.0** (minor bump: additive `suggestedCompanions[{role: "logging", ...}, {role: "errorHandling", ...}]`; `docs/topics.md` shelf-wide band registry adds the two new blueprints).
37
+
38
+ ### Migration notes
39
+
40
+ - A project on `application-api-rest` v2.0.0 re-applies at v2.1.0 with `rcf define blueprint add <path-to>/blueprints/application-api-rest`; the apply is a minor bump (new suggestedCompanions field, ADR-304 retained without scope:global). Existing chains that referenced `ADR-304-application-api-rest-logging` in their own docs keep the reference intact (the contribution is retained, only its global-topic claim was dropped).
41
+ - A project that had superseded the `logging` topic at project level under v1.x keeps the supersede as historical context; `observability-logging` is the shelf-canonical claimant from this train onward, and a project that wants to align on the shelf provider re-applies both blueprints and drops any redundant historical resolution with `rcf define blueprint remove-resolution <adr-id>`.
42
+ - A project on `application-spa` v1.4.0 re-applies at v1.5.0 for the additive `suggestedCompanions` field.
43
+ - A project that wants the shelf `logging` role: `rcf define blueprint add <path-to>/blueprints/observability-logging`.
44
+ - A project that wants the shelf `errorHandling` role: `rcf define blueprint add <path-to>/blueprints/application-error-handling`.
45
+ - Dex's WSD library adopts the mechanism as a minor bump (0.4.0 -> 0.5.0 named as the adoption target in spec section 7): `wsd-logging` adds `providesRoles: ["logging"]`; `wsd-error-handling` adds `providesRoles: ["errorHandling"]`; `wsd-api-rest` adds `suggestedCompanions: [{role: "logging", ...}, {role: "errorHandling", ...}]`. Optional `standardsTrace[]` adoption formalises the prose standards-trace tables the library already ships.
46
+
9
47
  ## [0.18.0] - 2026-09-04
10
48
 
11
49
  Core shelf probe-path alignment. One shelf blueprint owns the probe-path binding across the whole shelf: `observability-probe-endpoints` v1.1.0 is the sole `scope: global` claimant on `healthProbes` and `readinessSemantics` and gains an optional Kubernetes-profile `/startup` path (off by default, enabled per project by `probeInterface.options.kubernetes.startup.enabled: true`). `observability-essentials` v2.0.0 drops its two `scope: global` topic claims, drops the shipped `/healthz` and `/readyz` defaults from TAC-801/802 (`path` becomes required config with no default), and adds the stable-coded `PROBE_INTERFACE_PATHS_MISSING` boot refusal for essentials-alone projects that forget the config. `application-api-rest` v2.0.0 drops the literal `/healthz/live`, `/healthz/ready`, and `/healthz/startup` bindings from REQ-006, US-2108, TAC-306, and the US-2103 versioning exemption, deferring to the resolved probe path set the probe-endpoints blueprint emits via TAC-1501 `getExemptPathSet` (or to the project-supplied `probeInterface.paths` in the essentials-alone case). `rcf doctor` grows a `probe-path-owner` check that fires when more than one applied blueprint teaches probe paths, and `rcf define blueprint remove-resolution <adr-id>` is the paired remedy verb that drops a single `manifest.resolutions[]` entry by its `resolvedByAdrId`. Spec: `projects/rcf-lite-wsd/specs/rcf-lite-probe-path-alignment-spec-2026-09-04.md` (amendments A1 to A3). Chain: REQ-012 with US-1201/1202/1203/1204 and TS-034/035/036/037 on the rcf-lite own tree.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "slug": "application-api-rest",
3
- "version": "2.0.0",
3
+ "version": "2.1.0",
4
4
  "category": "application",
5
5
  "contributions": [
6
6
  {
@@ -237,9 +237,7 @@
237
237
  {
238
238
  "id": "ADR-304-application-api-rest-logging",
239
239
  "kind": "adr",
240
- "path": "adrs/adr-304-application-api-rest-logging.json",
241
- "scope": "global",
242
- "topic": "logging"
240
+ "path": "adrs/adr-304-application-api-rest-logging.json"
243
241
  },
244
242
  {
245
243
  "id": "ADR-305-application-api-rest-pagination",
@@ -261,5 +259,15 @@
261
259
  "kind": "adr",
262
260
  "path": "adrs/adr-308-application-api-rest-rate-limiting.json"
263
261
  }
262
+ ],
263
+ "suggestedCompanions": [
264
+ {
265
+ "role": "logging",
266
+ "reason": "Every request pipeline pass writes a request-scoped log line; a logging companion supplies the factory the pipeline calls, and the shelf logging blueprint (observability-logging) is the fallback when no library provider is registered."
267
+ },
268
+ {
269
+ "role": "errorHandling",
270
+ "reason": "The error translator writes the wire envelope from the internal record; an error-handling companion supplies the record factory and the boundary that constructs it, and the shelf application-error-handling blueprint is the fallback when no library provider is registered."
271
+ }
264
272
  ]
265
273
  }
@@ -2,12 +2,12 @@
2
2
  "adrId": "ADR-304-application-api-rest-logging",
3
3
  "prdId": "PRD-001",
4
4
  "tadId": "TAD-001",
5
- "version": "1.0.0",
5
+ "version": "2.1.0",
6
6
  "status": "accepted",
7
- "title": "Structured JSON lines with request-scoped correlation as the project's primary logging shape",
7
+ "title": "Historical: structured JSON lines with request-scoped correlation as the project's primary logging shape (superseded by observability-logging-ADR-1601 as the shelf-canonical logging global-topic owner from api-rest v2.1.0)",
8
8
  "context": "A project has one primary logging shape; aggregation, alerting, retention, and redaction tooling are all built against it, and mixed shapes halve the value of every query. The decision covers the line format, the mandatory fields, and where correlation ids live.",
9
9
  "decision": "Logs are single-line JSON objects. Mandatory base fields: timestamp, level, message, service name. Every request-scoped line additionally carries the request id, and the trace id when a trace context exists. Request completion lines carry method, path template, status, duration, and auth class at the class's declared level. Redaction (application-api-rest-REQ-016) applies before emission. Prose, multi-line, and printf-style lines are not emitted by application code (application-api-rest-REQ-012, application-api-rest-US-2114).",
10
- "consequences": "Every operational question that logs can answer becomes a structured query keyed on the same ids the metrics and traces carry. The cost is log discipline in code review (no stray console prose) and marginally larger lines; both are prices the observability requirements already commit to. Any blueprint contributing log-emitting components to the same project joins this shape or the operator supersedes it project-wide.",
10
+ "consequences": "Every operational question that logs can answer becomes a structured query keyed on the same ids the metrics and traces carry. The cost is log discipline in code review (no stray console prose) and marginally larger lines; both are prices the observability requirements already commit to. Any blueprint contributing log-emitting components to the same project joins this shape or the operator supersedes it project-wide. From api-rest v2.1.0 the shelf-wide `logging` global topic is claimed only by `observability-logging` (ADR-1601-observability-logging-line-shape). The structured-JSON-with-correlation intent recorded here remains the intent for api-rest's server-side emission; the shelf-canonical topic owner is elsewhere. Projects that had superseded `logging` at project level in v1.x retain their supersede as historical context (see rcf define blueprint remove-resolution for the drop path when the resolution is genuinely redundant).",
11
11
  "alternativesConsidered": [
12
12
  {
13
13
  "name": "Human-readable prose lines",
@@ -9,7 +9,7 @@ This file is the application-api-rest half of the cross-blueprint contract; the
9
9
  | `errorEnvelope` | ADR-301-application-api-rest-error-envelope | Reused verbatim from the application-spa vocabulary | DELIBERATE pairing: application-spa contributes ADR-204-application-spa-error-envelope on this topic. Applying both blueprints to one project surfaces the conflict for operator resolution. Expected resolution: one project-level ADR adopting RFC 7807 end to end, superseding both halves |
10
10
  | `authModel` | ADR-302-application-api-rest-auth-model | Reused verbatim from the application-spa vocabulary | DELIBERATE pairing with SPA's ADR-205-application-spa-auth-model (cookie-based sessions, client half). Expected resolution: one project-level ADR fixing the credential transport and mapping the client session model onto the four server classes |
11
11
  | `apiVersioning` | ADR-303-application-api-rest-api-versioning | Minted here; pre-cleared as unclaimed in the application-spa vocabulary | The one versioning strategy for the project's wire contract. Any blueprint contributing a versioned API surface must join or supersede this decision |
12
- | `logging` | ADR-304-application-api-rest-logging | Minted here; pre-cleared as unclaimed in the application-spa vocabulary | The project's primary logging shape. Any blueprint contributing log-emitting server components conflicts here by design |
12
+ | `logging` (retained historical) | ADR-304-application-api-rest-logging (no scope:global from v2.1.0) | Transferred to observability-logging-ADR-1601 as the shelf-canonical owner from v2.1.0 | The project's primary logging shape now sits on the shelf logging blueprint; the api-rest ADR-304 is retained as historical context and the api-rest blueprint no longer claims the global topic. Apply observability-logging 1.0.0 or later (or a registered library-side provider) to satisfy the `logging` role api-rest suggests |
13
13
 
14
14
  Rules for new topics (inherited from the application-spa vocabulary, restated as law): lower camel case, one concept per topic, no version suffixes. A topic names the decision area, not the chosen answer. Do not mint variants of existing strings (`errorShape`, `auth`, `apiVersion`, `logShape` are all wrong).
15
15
 
@@ -22,7 +22,7 @@ This table is maintained shelf-wide across every blueprint's `docs/topics.md`. R
22
22
  | Blueprint | US band | ADR/TAC suffix block | Status | Global topics |
23
23
  |---|---|---|---|---|
24
24
  | application-spa | 1101-1899 | 2xx | shipped v1.3.0 | `clientRouting`, `theming`, `clientState`, `errorEnvelope`, `authModel` |
25
- | application-api-rest | 2101-2899 | 3xx | shipped v1.0.0 | `errorEnvelope`, `authModel`, `apiVersioning`, `logging` |
25
+ | application-api-rest | 2101-2899 | 3xx | shipped v2.1.0 (suggestedCompanions added) | `errorEnvelope`, `authModel`, `apiVersioning` |
26
26
  | security-auth-magic-link | 3101-3899 | 5xx | shipped v1.0.0 | `authModel` |
27
27
  | email-smtp-resend | 4101-4899 | 4xx | shipped v1.0.0 | none |
28
28
  | hello-panel (walkthrough exemplar) | 4101-4899 | 4xx | doc-reserved; teaching exemplar in `packages/rcf-lite/docs/blueprint-authoring-walkthrough.md`, not shipped as a blueprint directory | `operatorPanel` |
@@ -36,6 +36,8 @@ This table is maintained shelf-wide across every blueprint's `docs/topics.md`. R
36
36
  | deploy-cloudflare-workers | 12101-12899 | 13xx | shipped v1.0.0 | `deploymentTarget` |
37
37
  | persistence-data-d1 | 13101-13899 | 14xx | shipped v1.0.0 | `persistenceStore`, `migrationDiscipline` |
38
38
  | observability-probe-endpoints | 14101-14899 | 15xx | shipped v1.1.0 | `healthProbes`, `readinessSemantics` |
39
+ | observability-logging | 15101-15899 | 16xx | shipped v1.0.0 | `logging` |
40
+ | application-error-handling | 16101-16899 | 17xx | shipped v1.0.0 | `errorHandling` |
39
41
 
40
42
  Suffix-family ids (ADR, TAC): SPA numbers in the 2xx range, this package takes 3xx (ADR-301 to ADR-308, TAC-301 to TAC-306); the next blueprint should take 4xx.
41
43
 
@@ -0,0 +1,42 @@
1
+ # Error-handling blueprint (v1.0.0)
2
+
3
+ Transport-agnostic error-handling contract for a rcf-lite application. General enterprise practice, no vendor lock-in. Mints the `errorHandling` global topic on `ADR-1701-application-error-handling-record-shape` (distinct from `errorEnvelope`, which stays with application-api-rest as the REST wire shape). Provides the `errorHandling` role for the companion-suggestion mechanism. Suggests the `logging` companion so emission threads through the applied logging factory.
4
+
5
+ ## Apply
6
+
7
+ ```
8
+ rcf define blueprint add <path-to>/blueprints/application-error-handling
9
+ ```
10
+
11
+ Applies namespaced contributions into the project tree and records `manifest.blueprints[]`.
12
+
13
+ ## Anatomy
14
+
15
+ | Piece | Where | What |
16
+ |---|---|---|
17
+ | Metadata | `blueprint.json` | Slug, version 1.0.0, category `application`, `providesRoles: ["errorHandling"]`, `suggestedCompanions: [{role: "logging", ...}]`, and the 16 contributions with scope/topic on `ADR-1701` |
18
+ | Doc set | `contributions/` | 4 REQs, 7 USs (9 ACs), 2 TACs, 3 ADRs, schema-valid and namespaced |
19
+ | Record schema | `assets/schemas/error-record.schema.json` | JSON Schema for the internal record shape |
20
+ | Guide | `guide/application-error-handling.md` | Operator-facing: when to reach, when not, mechanism-reach gaps |
21
+ | Coordination vocabulary | `docs/topics.md` | The `errorHandling` global topic distinct from `errorEnvelope`, the shelf-wide id band registry update |
22
+
23
+ ## What it contributes, and what it deliberately does not
24
+
25
+ Contributed: REQ, US (with inline ACs), TAC, ADR. Adherence is expressed as ACs; the blueprint ships no test files.
26
+
27
+ Deliberately not contributed: a transport wire envelope (application-api-rest owns REST via `errorEnvelope`; a future gRPC or message-consumer blueprint claims its own); a retry policy implementation (the record carries category; the applying platform's retry surface reads it); a substitute for the logging companion's own field-level redaction (the record's context field is redacted through the applied logging companion's redaction boundary, per AC-16104-1).
28
+
29
+ ## The one global decision
30
+
31
+ `ADR-1701-application-error-handling-record-shape` ships `scope: global` on topic `errorHandling`. This is the project's internal error record shape (six fields: code, category, message, correlationId, cause, context). A composing blueprint that wants a different internal record shape conflicts here by design. `errorHandling` is distinct from `errorEnvelope` (owned by application-api-rest for REST); the internal record is one project decision above every transport-specific wire envelope.
32
+
33
+ See `docs/topics.md` for the exact strings, the distinction from `errorEnvelope`, and the AC id band allocation (16101-16899, ADR/TAC suffix block 17xx).
34
+
35
+ ## Quality bar
36
+
37
+ Process-level uncaught-exception boundary and framework-level pipeline boundary both registered; every record constructed through TAC-1702's factory with code and category required; context redacted at construction time using the applied logging companion's redaction categories; cause chain preserved across nested wrappings; classification vocabulary transient / permanent / unknown (elicited additions per project); transport response mapping delegated through the substitutable transportWriter interface (ADR-1703); emission routed through the applied logging companion factory with a documented stderr fallback when no companion is applied. Every bar is carried by ACs in the doc set.
38
+
39
+ ## Known mechanism-reach gaps
40
+
41
+ - **Boundary coverage.** The blueprint cannot prove every code path routes through the boundary; a bare `console.error` or a `process.exit` from any code path bypasses the boundary. Project-side workaround: a grep gate as a project-authored TC bound to `AC-16106-1`.
42
+ - **Classification integrity.** The vocabulary is enforced at the factory (code + category required), but the correctness of the category the caller chose is not machine-checkable. A caller who classifies every failure as `unknown` still passes the shape gate; the blueprint's guide names the diagnostic pattern.
@@ -0,0 +1,17 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://rcf-lite/blueprints/application-error-handling/schemas/error-record.schema.json",
4
+ "title": "Internal error record",
5
+ "description": "The shape TAC-1702's factory returns and every downstream consumer reads. Frozen; see ADR-1701.",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "required": ["code", "category", "message", "correlationId", "context"],
9
+ "properties": {
10
+ "code": { "type": "string", "minLength": 1, "description": "Project-elicited machine-readable identifier." },
11
+ "category": { "type": "string", "minLength": 1, "description": "One of the ADR-1702 classification vocabulary." },
12
+ "message": { "type": "string", "minLength": 1, "description": "Human-readable message; no PII." },
13
+ "correlationId": { "type": ["string", "null"], "description": "Stamped from the ambient logging context at construction." },
14
+ "cause": { "anyOf": [ { "type": "null" }, { "$ref": "#" } ], "description": "Nested record for the wrapped cause, or null." },
15
+ "context": { "type": "object", "description": "Redacted at construction using the applied logging companion's redaction categories." }
16
+ }
17
+ }
@@ -0,0 +1,27 @@
1
+ {
2
+ "slug": "application-error-handling",
3
+ "version": "1.0.0",
4
+ "category": "application",
5
+ "providesRoles": ["errorHandling"],
6
+ "suggestedCompanions": [
7
+ { "role": "logging", "reason": "The internal error record emits through the logging companion, not direct stdout or stderr writes; a logging companion supplies the factory the boundary calls." }
8
+ ],
9
+ "contributions": [
10
+ { "id": "application-error-handling-REQ-001", "kind": "req", "path": "requirements/application-error-handling-req-001.json" },
11
+ { "id": "application-error-handling-REQ-002", "kind": "req", "path": "requirements/application-error-handling-req-002.json" },
12
+ { "id": "application-error-handling-REQ-003", "kind": "req", "path": "requirements/application-error-handling-req-003.json" },
13
+ { "id": "application-error-handling-REQ-004", "kind": "req", "path": "requirements/application-error-handling-req-004.json" },
14
+ { "id": "application-error-handling-US-16101", "kind": "us", "path": "user-stories/application-error-handling-us-16101.json" },
15
+ { "id": "application-error-handling-US-16102", "kind": "us", "path": "user-stories/application-error-handling-us-16102.json" },
16
+ { "id": "application-error-handling-US-16103", "kind": "us", "path": "user-stories/application-error-handling-us-16103.json" },
17
+ { "id": "application-error-handling-US-16104", "kind": "us", "path": "user-stories/application-error-handling-us-16104.json" },
18
+ { "id": "application-error-handling-US-16105", "kind": "us", "path": "user-stories/application-error-handling-us-16105.json" },
19
+ { "id": "application-error-handling-US-16106", "kind": "us", "path": "user-stories/application-error-handling-us-16106.json" },
20
+ { "id": "application-error-handling-US-16107", "kind": "us", "path": "user-stories/application-error-handling-us-16107.json" },
21
+ { "id": "TAC-1701-application-error-handling-boundary", "kind": "tac", "path": "tacs/tac-1701-application-error-handling-boundary.json" },
22
+ { "id": "TAC-1702-application-error-handling-record-factory", "kind": "tac", "path": "tacs/tac-1702-application-error-handling-record-factory.json" },
23
+ { "id": "ADR-1701-application-error-handling-record-shape", "kind": "adr", "path": "adrs/adr-1701-application-error-handling-record-shape.json", "scope": "global", "topic": "errorHandling" },
24
+ { "id": "ADR-1702-application-error-handling-classification-vocabulary", "kind": "adr", "path": "adrs/adr-1702-application-error-handling-classification-vocabulary.json", "recommendedDefault": true, "elicited": true, "standardsTraceClause": "generic enterprise practice" },
25
+ { "id": "ADR-1703-application-error-handling-transport-mapping", "kind": "adr", "path": "adrs/adr-1703-application-error-handling-transport-mapping.json", "recommendedDefault": true, "elicited": false, "standardsTraceClause": "generic enterprise practice" }
26
+ ]
27
+ }
@@ -0,0 +1,25 @@
1
+ {
2
+ "adrId": "ADR-1701-application-error-handling-record-shape",
3
+ "prdId": "PRD-001",
4
+ "tadId": "TAD-001",
5
+ "version": "1.0.0",
6
+ "status": "accepted",
7
+ "title": "Internal error record shape: code, category, message, correlationId, cause chain, redacted context",
8
+ "context": "Without a shared record shape, every consumer of error data (log ingestion pipelines, alerting rules, wire-response mappers, retry policies) invents its own view. The record loses information at each hop and downstream tools reconstruct it from string parsing. The 'errorHandling' global topic is minted here (distinct from 'errorEnvelope', which stays with application-api-rest as the REST wire shape): errorHandling is the internal record shape and the uncaught-exception boundary; errorEnvelope is the wire format one transport chooses to translate the record into.",
9
+ "decision": "The internal error record has exactly six fields: code (string, machine-readable identifier the project elicits at record creation; the vocabulary is project-side), category (one of the ADR-1702 classification vocabulary), message (human-readable string, no PII), correlationId (stamped from the ambient logging context at construction time; may be null), cause (nested record for the wrapped cause, or null), context (redacted object per the applied logging companion's redaction categories). Records are constructed only through TAC-1702's factory; the factory refuses code or category missing.",
10
+ "consequences": "Log-side queries by code, category or correlationId all work off the shared field names. The wire-response mapper (ADR-1703) writes one envelope from the record without a per-project mapping table. The retry policy reads category at one place. The cause chain is preserved across wrappings without any consumer's per-layer knowledge. A future transport blueprint (gRPC, message consumer) claims its OWN transport-specific errorEnvelope topic; the internal record shape stays one project decision above.",
11
+ "alternativesConsidered": [
12
+ {
13
+ "name": "Free-form record with a message-plus-metadata bag",
14
+ "summary": "The record has message and a metadata object; every consumer looks up its own fields.",
15
+ "reasonNotChosen": "Loses the shared vocabulary and forces every consumer to invent field names; the failure mode is a per-consumer view that drifts silently."
16
+ },
17
+ {
18
+ "name": "Merge errorHandling and errorEnvelope into one global topic",
19
+ "summary": "One topic governs both the internal record and the wire envelope.",
20
+ "reasonNotChosen": "Conflates two decisions the mechanism gets to make separately: the internal record shape is one decision (this blueprint owns it); the wire envelope is a transport-specific decision (application-api-rest owns errorEnvelope for REST). Merging them forces a project to change the wire when it changes the internal record and vice versa."
21
+ }
22
+ ],
23
+ "createdAt": "2026-09-04T12:20:00Z",
24
+ "updatedAt": "2026-09-04T12:20:00Z"
25
+ }
@@ -0,0 +1,20 @@
1
+ {
2
+ "adrId": "ADR-1702-application-error-handling-classification-vocabulary",
3
+ "prdId": "PRD-001",
4
+ "tadId": "TAD-001",
5
+ "version": "1.0.0",
6
+ "status": "accepted",
7
+ "title": "Classification vocabulary: transient, permanent, unknown by recommended default; operator elicits additions",
8
+ "context": "Retry policies are the difference between a self-healing system and a burning one. Classification at the source (where the code knows whether the failure was a transient network blip or a permanent authorisation refusal) puts the decision where the information is; classifying downstream on the wire is the source of most retry-storm outages. The recommended default set is deliberately small; per-domain additions land through elicited additions.",
9
+ "decision": "The recommended default classification vocabulary is transient, permanent, unknown. Every constructed record carries one string from this set. The applying operator elicits additional categories at apply (kebab or dotted-namespace grammar; refused with a boot error otherwise); elicited additions apply alongside the defaults. The transport response mapper reads the category and applies the per-class contract.",
10
+ "consequences": "Retry policies wire once against the shared vocabulary; per-project additions (rate-limited, quota-exceeded, dependency-degraded) fit in the same mechanism. The three defaults cover the majority of real-world classification decisions; a project that finds itself using unknown for everything has a diagnostic gap the classification is meant to catch.",
11
+ "alternativesConsidered": [
12
+ {
13
+ "name": "Enumerate an exhaustive vocabulary",
14
+ "summary": "The blueprint ships every possible category (auth, timeout, dependency, quota, ...) as recommended defaults.",
15
+ "reasonNotChosen": "Reliably fights every project's domain vocabulary; the three defaults plus elicited additions gives a working floor and a project-side extension path in the same round."
16
+ }
17
+ ],
18
+ "createdAt": "2026-09-04T12:20:00Z",
19
+ "updatedAt": "2026-09-04T12:20:00Z"
20
+ }
@@ -0,0 +1,20 @@
1
+ {
2
+ "adrId": "ADR-1703-application-error-handling-transport-mapping",
3
+ "prdId": "PRD-001",
4
+ "tadId": "TAD-001",
5
+ "version": "1.0.0",
6
+ "status": "accepted",
7
+ "title": "The transport blueprint's error-envelope ADR writes the wire shape from the internal record; delegation via TAC-1701 writeErrorResponse",
8
+ "context": "The internal record shape (ADR-1701) is transport-agnostic; the wire envelope is a transport-specific decision. For REST, application-api-rest owns 'errorEnvelope' with an RFC 7807-style body plus code/correlationId fields. For gRPC or message-consumer transports, a future transport blueprint claims its own errorEnvelope-family topic. The boundary must delegate the mapping so this blueprint does not have to know every transport.",
9
+ "decision": "TAC-1701 exposes a substitutable transportWriter interface the framework binds. The transport blueprint's errorEnvelope ADR governs the mapping from the internal record to the wire body/headers; the transport writer reads record.code, record.category, record.correlationId and record.context and produces the wire response the transport ADR specifies. This blueprint contributes no writer of its own; it only names the delegation contract.",
10
+ "consequences": "A REST project applies application-api-rest 2.1.0 or later and gets the RFC 7807-shaped response body from the record. A gRPC project supplies its own writer against the same interface. This blueprint remains a companion to whichever transport the project applies; the classification and cause chain travel cleanly across every transport that respects the interface.",
11
+ "alternativesConsidered": [
12
+ {
13
+ "name": "This blueprint ships a REST-shaped default writer",
14
+ "summary": "Provide a default writer that produces a REST envelope; projects override for other transports.",
15
+ "reasonNotChosen": "The blueprint would then have to declare a REST-specific dependency and claim (or conflict on) the errorEnvelope global topic, which application-api-rest already owns. Delegation via a substitutable interface keeps this blueprint transport-agnostic."
16
+ }
17
+ ],
18
+ "createdAt": "2026-09-04T12:20:00Z",
19
+ "updatedAt": "2026-09-04T12:20:00Z"
20
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "reqId": "application-error-handling-REQ-001",
3
+ "prdId": "PRD-001",
4
+ "title": "The application registers an uncaught-exception boundary at process AND framework level",
5
+ "description": "The application registers TWO boundaries: (1) a process-level handler on uncaughtException / unhandledRejection (Node runtime) or equivalent (browser runtime) that constructs the internal error record via TAC-1702, emits it through the logging companion, and terminates the process cleanly (exit 1); (2) a framework-level handler on the request pipeline (Express-style middleware, Fastify hook, or transport-layer equivalent) that catches exceptions thrown from handler code, constructs the internal error record, and hands it to the transport's response writer (ADR-1703). No exception escapes either boundary silently.",
6
+ "category": "functional",
7
+ "domain": "application",
8
+ "priority": "must",
9
+ "rationale": "The most common source of production outages in unchecked applications is an uncaught exception that terminates the process with no diagnostic, or a framework-level exception that produces a mis-shaped response the caller cannot interpret. The two boundaries close both classes at the earliest possible surface.",
10
+ "tags": ["blueprint:application-error-handling", "category:01-boundary"],
11
+ "version": "1.0.0",
12
+ "status": "approved",
13
+ "createdAt": "2026-09-04T12:20:00Z",
14
+ "updatedAt": "2026-09-04T12:20:00Z"
15
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "reqId": "application-error-handling-REQ-002",
3
+ "prdId": "PRD-001",
4
+ "title": "The internal error record has a stable shape: code, category, message, correlationId, cause chain, redacted context",
5
+ "description": "Every constructed error record carries the fields governed by ADR-1701: code (string, project-elicited machine-readable identifier), category (one of the ADR-1702 classification vocabulary), message (human-readable string, no PII), correlationId (from the ambient logging context), cause (nested record for the wrapped cause, may be null), context (redacted object per the logging blueprint's redaction categories). Records are constructed only through TAC-1702's factory; a construction that omits code or category is refused at the factory boundary.",
6
+ "category": "functional",
7
+ "domain": "application",
8
+ "priority": "must",
9
+ "rationale": "A stable record shape lets one log-side query find every error by category, one transport-side handler write the wire envelope by category, and one alerting rule filter by code. Without a shared shape, every consumer invents its own view, and the record becomes lossy at each hop.",
10
+ "tags": ["blueprint:application-error-handling", "category:02-record-shape"],
11
+ "version": "1.0.0",
12
+ "status": "approved",
13
+ "createdAt": "2026-09-04T12:20:00Z",
14
+ "updatedAt": "2026-09-04T12:20:00Z"
15
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "reqId": "application-error-handling-REQ-003",
3
+ "prdId": "PRD-001",
4
+ "title": "Every error class is classified transient, permanent or unknown; the boundary honours the class in its handling",
5
+ "description": "Every constructed record carries a category from the ADR-1702 vocabulary (recommended defaults: transient, permanent, unknown; elicited additions per project). The transport handler (ADR-1703) reads the category and applies the per-class contract: transient errors may include a Retry-After hint if the origin knows one; permanent errors do not; unknown errors are logged at error level and treated as permanent by the wire response. The classification is a property the constructing code declares at record creation, not a property inferred from the exception message.",
6
+ "category": "functional",
7
+ "domain": "application",
8
+ "priority": "must",
9
+ "rationale": "Retry policies are the difference between a self-healing system and a burning one. Classifying errors at the source (where the code knows whether the failure was a transient network blip or a permanent authorisation refusal) puts the decision where the information is; classifying downstream on the wire message is the source of most retry-storm outages.",
10
+ "tags": ["blueprint:application-error-handling", "category:03-classification"],
11
+ "version": "1.0.0",
12
+ "status": "approved",
13
+ "createdAt": "2026-09-04T12:20:00Z",
14
+ "updatedAt": "2026-09-04T12:20:00Z"
15
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "reqId": "application-error-handling-REQ-004",
3
+ "prdId": "PRD-001",
4
+ "title": "Error emission goes through the logging companion factory, never direct stdout or stderr writes",
5
+ "description": "The uncaught-exception boundary and the framework-level handler both emit the constructed record through the logging companion's factory (the logging role provider the project has applied). Direct process.stderr.write or console.error calls for error emission are refused by the boundary contract. When no logging companion is applied the boundary falls back to a one-line JSON emission on stderr with a documented warning; the applying project's guide names the gap.",
6
+ "category": "functional",
7
+ "domain": "application",
8
+ "priority": "must",
9
+ "rationale": "The logging companion owns the redaction boundary, the correlation identifier propagation, the environment/service/version stamping and the level filter. Bypassing it for error emission produces lines that ingestion pipelines cannot parse alongside the rest of the logs and PII the redaction pass would have caught. The fallback stderr path exists so a project that has not applied a logging companion still gets a diagnostic.",
10
+ "tags": ["blueprint:application-error-handling", "category:04-emission-through-logging"],
11
+ "version": "1.0.0",
12
+ "status": "approved",
13
+ "createdAt": "2026-09-04T12:20:00Z",
14
+ "updatedAt": "2026-09-04T12:20:00Z"
15
+ }
@@ -0,0 +1,45 @@
1
+ {
2
+ "tacId": "TAC-1701-application-error-handling-boundary",
3
+ "prdId": "PRD-001",
4
+ "tadId": "TAD-001",
5
+ "version": "1.0.0",
6
+ "status": "approved",
7
+ "name": "Error-handling boundary",
8
+ "purpose": "Owns the two boundaries a application error-handling contract requires: the process-level uncaught-exception boundary and the framework-level request-pipeline boundary. Both accept a runtime exception, invoke the record factory (TAC-1702), emit the record through the applied logging companion factory, and hand the record to the transport response writer per ADR-1703. Substitutable interface for hermetic tests.",
9
+ "responsibilities": [
10
+ "Register the process-level handler on uncaughtException / unhandledRejection (Node) or equivalent (browser) that constructs the record, emits it and exits the process with code 1 (AC-16101-1).",
11
+ "Register a framework-level handler on the request pipeline that catches thrown exceptions, constructs the record, hands it to the transport writer, and never lets a raw stack trace or truncated connection surface (AC-16102-1).",
12
+ "Read the record's classification per ADR-1702 and honour the per-class contract in the transport response (AC-16105-1).",
13
+ "Route error emission through the applied logging companion factory when one is present; fall back to a documented single-line stderr JSON when no companion is applied (AC-16106-1)."
14
+ ],
15
+ "internalStructure": "One module exporting boundaryFor({ loggerFactory, transportWriter }) -> { handle(err, context) }, plus a registerProcess({ loggerFactory }) function the boot code calls once. The handler is a stateless async function; the transport writer is a substitutable interface the framework binds (Express: res.status().json(); Fastify: reply.send(); other transports supply their own writer). The boundary owns the classification-to-wire-envelope mapping delegated to ADR-1703.",
16
+ "interfaces": [
17
+ {
18
+ "name": "boundaryFor",
19
+ "kind": "factory",
20
+ "description": "boundaryFor({ loggerFactory, transportWriter }) -> { handle(err, context) }. The factory constructs the framework-level boundary; the framework calls handle on every caught exception. Tests substitute stub loggerFactory and transportWriter."
21
+ },
22
+ {
23
+ "name": "registerProcess",
24
+ "kind": "hook",
25
+ "description": "registerProcess({ loggerFactory }): void. Called once at boot; wires the process-level uncaughtException / unhandledRejection handlers to route through the same record factory."
26
+ },
27
+ {
28
+ "name": "transportWriter",
29
+ "kind": "interface",
30
+ "description": "The framework-supplied writer that turns a record + wire envelope into the on-the-wire response. Substitutable per transport blueprint (REST envelope, gRPC status, message-consumer nack)."
31
+ }
32
+ ],
33
+ "dependencies": [
34
+ {
35
+ "name": "Record factory",
36
+ "kind": "tac",
37
+ "tacId": "TAC-1702-application-error-handling-record-factory",
38
+ "description": "The boundary constructs every record through the factory; the factory owns the record shape gate and the context redaction pass."
39
+ }
40
+ ],
41
+ "tradeoffs": "The boundary owns the runtime interception surface; a project that wants a different framework's hook (a custom router, an application-code-first middleware chain) supplies its own transportWriter and reuses everything else. The v1 boundary does not attempt to recover from an uncaught exception; termination with exit 1 is the intentional contract (recovery is the supervisor's job). The optional logging-companion fallback exists so a project without a logging companion still sees the record on stderr; the guide names the fallback as documented behaviour and points at the observability-logging companion as the recommended fix.",
42
+ "notes": "Known mechanism-reach gap: the blueprint cannot prove every code path routes through the boundary. A project-side grep gate ('no bare console.error, no bare process.exit outside boundary') closes the class.",
43
+ "createdAt": "2026-09-04T12:20:00Z",
44
+ "updatedAt": "2026-09-04T12:20:00Z"
45
+ }
@@ -0,0 +1,40 @@
1
+ {
2
+ "tacId": "TAC-1702-application-error-handling-record-factory",
3
+ "prdId": "PRD-001",
4
+ "tadId": "TAD-001",
5
+ "version": "1.0.0",
6
+ "status": "approved",
7
+ "name": "Error record factory",
8
+ "purpose": "Owns the construction of every internal error record. Threads the operator-elicited redaction categories (from the applied logging companion) into a shared factory that gates the record shape (code + category required per ADR-1701), redacts the context field at construction (AC-16104-1), preserves the cause chain across nested wrappings (AC-16103-2), and returns an immutable record every downstream consumer can trust.",
9
+ "responsibilities": [
10
+ "Refuse to construct a record without code or category (AC-16103-1); throw a controlled error naming the missing field.",
11
+ "Redact the context object at construction using the redaction category set the applied logging companion supplied at boot (AC-16104-1); the caller's original context object is not mutated.",
12
+ "Preserve the cause chain by accepting a nested record as cause and threading it verbatim onto the returned record (AC-16103-2).",
13
+ "Stamp the record with the ambient correlationId at construction time so the record carries the identifier even if the emitter loses the ambient context between construction and emission."
14
+ ],
15
+ "internalStructure": "One module exporting createErrorRecordFactory({ redactionBoundary, correlationContext }) -> { createErrorRecord({code, category, message, cause, context}) -> Record }. The factory closes over the redaction boundary (from TAC-1602 of the logging companion, when applied) and the correlation-context reader (from the logging companion or a project fallback). The returned record is Object.freeze'd so downstream consumers cannot mutate it.",
16
+ "interfaces": [
17
+ {
18
+ "name": "createErrorRecordFactory",
19
+ "kind": "factory",
20
+ "description": "createErrorRecordFactory({ redactionBoundary, correlationContext }) -> { createErrorRecord({code, category, message, cause, context}) }. Constructed once at boot alongside the logger factory."
21
+ },
22
+ {
23
+ "name": "Record",
24
+ "kind": "interface",
25
+ "description": "Frozen object with fields code (string), category (string), message (string), correlationId (string or null), cause (Record | null), context (redacted object)."
26
+ }
27
+ ],
28
+ "dependencies": [
29
+ {
30
+ "name": "Redaction boundary",
31
+ "kind": "tac",
32
+ "tacId": "TAC-1602-observability-logging-redaction-boundary",
33
+ "description": "The factory calls the redaction boundary on the context object at construction; when the logging companion is not applied, the factory uses a pass-through boundary (documented in the guide as the fallback)."
34
+ }
35
+ ],
36
+ "tradeoffs": "The factory owns the shape gate; a project that wants an additional record field (severity, retryHint) authors it as part of context rather than at the top level. Freezing the record has a small runtime cost; it is intentional so downstream mutation cannot silently drift the emitted line from the record the caller reasoned about. The v1 factory does not attempt to normalise message strings; the caller supplies the message verbatim.",
37
+ "notes": "",
38
+ "createdAt": "2026-09-04T12:20:00Z",
39
+ "updatedAt": "2026-09-04T12:20:00Z"
40
+ }
@@ -0,0 +1,25 @@
1
+ {
2
+ "usId": "application-error-handling-US-16101",
3
+ "prdId": "PRD-001",
4
+ "reqId": "application-error-handling-REQ-001",
5
+ "version": "1.0.0",
6
+ "status": "approved",
7
+ "title": "A process-level uncaught exception constructs the record, emits it, and terminates cleanly with exit 1",
8
+ "asA": "operator running the application under a supervising process",
9
+ "iWant": "an uncaught exception to construct the internal record via TAC-1702, emit it through the logging companion at error level, and terminate with exit 1",
10
+ "soThat": "the crash is diagnosable from one log line and the supervisor knows to restart the process",
11
+ "acceptanceCriteria": [
12
+ {
13
+ "id": "AC-16101-1",
14
+ "description": "An uncaughtException on the Node runtime (or an equivalent unhandled error on another runtime) causes the process-level boundary to construct the internal record with category unknown when no code path set one, emit it as one JSON line at error level with the stack trace on cause, and exit the process with code 1.",
15
+ "given": "a running application with the process-level boundary registered",
16
+ "when": "an uncaught exception is thrown from any code path",
17
+ "then": "one log line is emitted at error level with the record shape (code, category unknown, message, correlationId, cause, context), and process.exitCode equals 1",
18
+ "testable": true,
19
+ "scope": "runtime"
20
+ }
21
+ ],
22
+ "tacIds": ["TAC-1701-application-error-handling-boundary"],
23
+ "createdAt": "2026-09-04T12:20:00Z",
24
+ "updatedAt": "2026-09-04T12:20:00Z"
25
+ }
@@ -0,0 +1,25 @@
1
+ {
2
+ "usId": "application-error-handling-US-16102",
3
+ "prdId": "PRD-001",
4
+ "reqId": "application-error-handling-REQ-001",
5
+ "version": "1.0.0",
6
+ "status": "approved",
7
+ "title": "A framework-level exception thrown from handler code is caught, mapped to the wire, and never leaks a raw stack trace",
8
+ "asA": "consumer of an application endpoint",
9
+ "iWant": "a framework-level exception to produce a mapped wire response, not a raw stack trace or a truncated connection",
10
+ "soThat": "callers can act on the error without inspecting server logs and no internal detail leaks",
11
+ "acceptanceCriteria": [
12
+ {
13
+ "id": "AC-16102-1",
14
+ "description": "A handler that throws produces a wire response written by ADR-1703's transport mapping; the response body carries the fields the transport blueprint's errorEnvelope ADR requires (no raw stack, no PII, code present, message present); the server does not truncate the connection.",
15
+ "given": "an application handler that throws",
16
+ "when": "a request hits the handler",
17
+ "then": "the response arrives cleanly with the mapped wire body, and no raw stack trace or internal path appears in the body",
18
+ "testable": true,
19
+ "scope": "runtime"
20
+ }
21
+ ],
22
+ "tacIds": ["TAC-1701-application-error-handling-boundary"],
23
+ "createdAt": "2026-09-04T12:20:00Z",
24
+ "updatedAt": "2026-09-04T12:20:00Z"
25
+ }
@@ -0,0 +1,34 @@
1
+ {
2
+ "usId": "application-error-handling-US-16103",
3
+ "prdId": "PRD-001",
4
+ "reqId": "application-error-handling-REQ-002",
5
+ "version": "1.0.0",
6
+ "status": "approved",
7
+ "title": "The record factory refuses to construct a record without code and category, and preserves the cause chain across nested wrappings",
8
+ "asA": "developer wrapping a low-level exception into a higher-level record",
9
+ "iWant": "TAC-1702's factory to refuse a construction without code or category and to preserve the wrapped cause chain across every level",
10
+ "soThat": "every record has the minimum debug information and the chain of causes is intact when the record is emitted",
11
+ "acceptanceCriteria": [
12
+ {
13
+ "id": "AC-16103-1",
14
+ "description": "createErrorRecord({code, category, message, cause, context}) refuses (throws a controlled error) when code is missing or blank OR category is missing or blank; the thrown error names the missing field.",
15
+ "given": "a caller invoking the factory without code or without category",
16
+ "when": "the factory is called",
17
+ "then": "the factory throws with a message naming the missing field",
18
+ "testable": true,
19
+ "scope": "runtime"
20
+ },
21
+ {
22
+ "id": "AC-16103-2",
23
+ "description": "A record whose cause is itself a record has both records' code, category and message reachable through record.cause.cause chain traversal.",
24
+ "given": "a record wrapping a record wrapping a low-level exception",
25
+ "when": "the outermost record is emitted",
26
+ "then": "the emitted JSON carries the full cause chain in the same shape as the top-level record fields, nested under cause",
27
+ "testable": true,
28
+ "scope": "runtime"
29
+ }
30
+ ],
31
+ "tacIds": ["TAC-1702-application-error-handling-record-factory"],
32
+ "createdAt": "2026-09-04T12:20:00Z",
33
+ "updatedAt": "2026-09-04T12:20:00Z"
34
+ }
@@ -0,0 +1,25 @@
1
+ {
2
+ "usId": "application-error-handling-US-16104",
3
+ "prdId": "PRD-001",
4
+ "reqId": "application-error-handling-REQ-002",
5
+ "version": "1.0.0",
6
+ "status": "approved",
7
+ "title": "The record's context field is redacted at record construction, not at emission",
8
+ "asA": "operator concerned about PII in error records",
9
+ "iWant": "the context field on the record to be redacted at construction time by the logging companion's redaction categories",
10
+ "soThat": "the record itself never carries un-redacted PII even if the emitter fails to redact",
11
+ "acceptanceCriteria": [
12
+ {
13
+ "id": "AC-16104-1",
14
+ "description": "createErrorRecord({..., context: {email: 'x@y', ip: '1.2.3.4'}}) with pii.email in the redaction category set returns a record whose context.email equals [REDACTED:pii.email]; the caller's original context object is not mutated.",
15
+ "given": "a call to the factory with a context object carrying a pii.email field",
16
+ "when": "the factory returns",
17
+ "then": "the returned record's context.email equals [REDACTED:pii.email] and the caller's context object still holds the original value",
18
+ "testable": true,
19
+ "scope": "runtime"
20
+ }
21
+ ],
22
+ "tacIds": ["TAC-1702-application-error-handling-record-factory"],
23
+ "createdAt": "2026-09-04T12:20:00Z",
24
+ "updatedAt": "2026-09-04T12:20:00Z"
25
+ }