rcf-lite 0.18.0 → 0.20.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 (142) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/bin/rcf.js +5 -0
  3. package/blueprints/application-api-rest/blueprint.json +12 -4
  4. package/blueprints/application-api-rest/contributions/adrs/adr-304-application-api-rest-logging.json +3 -3
  5. package/blueprints/application-api-rest/docs/topics.md +4 -2
  6. package/blueprints/application-error-handling/README.md +42 -0
  7. package/blueprints/application-error-handling/assets/schemas/error-record.schema.json +17 -0
  8. package/blueprints/application-error-handling/blueprint.json +27 -0
  9. package/blueprints/application-error-handling/contributions/adrs/adr-1701-application-error-handling-record-shape.json +25 -0
  10. package/blueprints/application-error-handling/contributions/adrs/adr-1702-application-error-handling-classification-vocabulary.json +20 -0
  11. package/blueprints/application-error-handling/contributions/adrs/adr-1703-application-error-handling-transport-mapping.json +20 -0
  12. package/blueprints/application-error-handling/contributions/requirements/application-error-handling-req-001.json +15 -0
  13. package/blueprints/application-error-handling/contributions/requirements/application-error-handling-req-002.json +15 -0
  14. package/blueprints/application-error-handling/contributions/requirements/application-error-handling-req-003.json +15 -0
  15. package/blueprints/application-error-handling/contributions/requirements/application-error-handling-req-004.json +15 -0
  16. package/blueprints/application-error-handling/contributions/tacs/tac-1701-application-error-handling-boundary.json +45 -0
  17. package/blueprints/application-error-handling/contributions/tacs/tac-1702-application-error-handling-record-factory.json +40 -0
  18. package/blueprints/application-error-handling/contributions/user-stories/application-error-handling-us-16101.json +25 -0
  19. package/blueprints/application-error-handling/contributions/user-stories/application-error-handling-us-16102.json +25 -0
  20. package/blueprints/application-error-handling/contributions/user-stories/application-error-handling-us-16103.json +34 -0
  21. package/blueprints/application-error-handling/contributions/user-stories/application-error-handling-us-16104.json +25 -0
  22. package/blueprints/application-error-handling/contributions/user-stories/application-error-handling-us-16105.json +25 -0
  23. package/blueprints/application-error-handling/contributions/user-stories/application-error-handling-us-16106.json +25 -0
  24. package/blueprints/application-error-handling/contributions/user-stories/application-error-handling-us-16107.json +25 -0
  25. package/blueprints/application-error-handling/docs/topics.md +24 -0
  26. package/blueprints/application-error-handling/guide/application-error-handling.md +35 -0
  27. package/blueprints/application-spa/blueprint.json +12 -2
  28. package/blueprints/application-spa/docs/topics.md +2 -0
  29. package/blueprints/observability-logging/README.md +44 -0
  30. package/blueprints/observability-logging/assets/samples/log-line.json +13 -0
  31. package/blueprints/observability-logging/blueprint.json +27 -0
  32. package/blueprints/observability-logging/contributions/adrs/adr-1601-observability-logging-line-shape.json +25 -0
  33. package/blueprints/observability-logging/contributions/adrs/adr-1602-observability-logging-correlation-id-header.json +25 -0
  34. package/blueprints/observability-logging/contributions/adrs/adr-1603-observability-logging-redaction-categories.json +25 -0
  35. package/blueprints/observability-logging/contributions/adrs/adr-1604-observability-logging-level-vocabulary.json +20 -0
  36. package/blueprints/observability-logging/contributions/requirements/observability-logging-req-001.json +15 -0
  37. package/blueprints/observability-logging/contributions/requirements/observability-logging-req-002.json +15 -0
  38. package/blueprints/observability-logging/contributions/requirements/observability-logging-req-003.json +15 -0
  39. package/blueprints/observability-logging/contributions/requirements/observability-logging-req-004.json +15 -0
  40. package/blueprints/observability-logging/contributions/requirements/observability-logging-req-005.json +15 -0
  41. package/blueprints/observability-logging/contributions/tacs/tac-1601-observability-logging-logger-factory.json +46 -0
  42. package/blueprints/observability-logging/contributions/tacs/tac-1602-observability-logging-redaction-boundary.json +27 -0
  43. package/blueprints/observability-logging/contributions/user-stories/observability-logging-us-15101.json +34 -0
  44. package/blueprints/observability-logging/contributions/user-stories/observability-logging-us-15102.json +43 -0
  45. package/blueprints/observability-logging/contributions/user-stories/observability-logging-us-15103.json +34 -0
  46. package/blueprints/observability-logging/contributions/user-stories/observability-logging-us-15104.json +25 -0
  47. package/blueprints/observability-logging/contributions/user-stories/observability-logging-us-15105.json +34 -0
  48. package/blueprints/observability-logging/contributions/user-stories/observability-logging-us-15106.json +25 -0
  49. package/blueprints/observability-logging/contributions/user-stories/observability-logging-us-15107.json +25 -0
  50. package/blueprints/observability-logging/contributions/user-stories/observability-logging-us-15108.json +25 -0
  51. package/blueprints/observability-logging/docs/topics.md +21 -0
  52. package/blueprints/observability-logging/guide/observability-logging.md +36 -0
  53. package/fixtures/canary-manifest.json +6 -6
  54. package/guidance/harness-template.md +8 -0
  55. package/guidance/managed/agent-instructions-block.hash +1 -1
  56. package/guidance/managed/agent-instructions-block.md +8 -0
  57. package/package.json +2 -2
  58. package/rcf/code-nodes/cn-077.json +14 -0
  59. package/rcf/code-nodes/cn-078.json +15 -0
  60. package/rcf/code-nodes/cn-079.json +14 -0
  61. package/rcf/code-nodes/cn-080.json +15 -0
  62. package/rcf/code-nodes/cn-081.json +15 -0
  63. package/rcf/code-nodes/cn-082.json +15 -0
  64. package/rcf/code-nodes/cn-083.json +14 -0
  65. package/rcf/code-nodes/cn-084.json +15 -0
  66. package/rcf/code-nodes/cn-085.json +14 -0
  67. package/rcf/code-nodes/cn-086.json +16 -0
  68. package/rcf/code-nodes/cn-087.json +14 -0
  69. package/rcf/code-nodes/cn-088.json +14 -0
  70. package/rcf/code-nodes/cn-089.json +14 -0
  71. package/rcf/code-nodes/cn-090.json +14 -0
  72. package/rcf/code-nodes/cn-091.json +14 -0
  73. package/rcf/code-nodes/cn-092.json +15 -0
  74. package/rcf/code-nodes/cn-093.json +15 -0
  75. package/rcf/code-nodes/cn-094.json +14 -0
  76. package/rcf/code-nodes/cn-095.json +14 -0
  77. package/rcf/code-nodes/cn-096.json +14 -0
  78. package/rcf/code-nodes/cn-097.json +14 -0
  79. package/rcf/code-nodes/cn-098.json +13 -0
  80. package/rcf/code-nodes/cn-099.json +13 -0
  81. package/rcf/code-nodes/cn-100.json +13 -0
  82. package/rcf/code-nodes/cn-101.json +13 -0
  83. package/rcf/code-nodes/cn-102.json +13 -0
  84. package/rcf/code-nodes/cn-103.json +13 -0
  85. package/rcf/code-nodes/cn-104.json +13 -0
  86. package/rcf/code-nodes/cn-105.json +13 -0
  87. package/rcf/evals/eval-001.json +55 -0
  88. package/rcf/fbs/fbs-028.json +27 -0
  89. package/rcf/fbs/fbs-029.json +27 -0
  90. package/rcf/fbs/fbs-030.json +27 -0
  91. package/rcf/fbs/fbs-031.json +27 -0
  92. package/rcf/fbs/fbs-032.json +27 -0
  93. package/rcf/fbs/fbs-033.json +27 -0
  94. package/rcf/fbs/fbs-034.json +27 -0
  95. package/rcf/fbs/fbs-035.json +18 -0
  96. package/rcf/requirements/req-013.json +22 -0
  97. package/rcf/requirements/req-014.json +22 -0
  98. package/rcf/requirements/req-015.json +21 -0
  99. package/rcf/requirements/req-016.json +37 -0
  100. package/rcf/test-suites/ts-038.json +41 -0
  101. package/rcf/test-suites/ts-039.json +41 -0
  102. package/rcf/test-suites/ts-040.json +57 -0
  103. package/rcf/test-suites/ts-041.json +57 -0
  104. package/rcf/test-suites/ts-042.json +57 -0
  105. package/rcf/test-suites/ts-043.json +49 -0
  106. package/rcf/test-suites/ts-044.json +41 -0
  107. package/rcf/test-suites/ts-045.json +46 -0
  108. package/rcf/user-stories/us-1301.json +43 -0
  109. package/rcf/user-stories/us-1302.json +43 -0
  110. package/rcf/user-stories/us-1401.json +43 -0
  111. package/rcf/user-stories/us-1402.json +43 -0
  112. package/rcf/user-stories/us-1403.json +43 -0
  113. package/rcf/user-stories/us-1404.json +43 -0
  114. package/rcf/user-stories/us-1501.json +43 -0
  115. package/rcf/user-stories/us-1601.json +37 -0
  116. package/releases/releases.yaml +20 -1
  117. package/src/blueprint/apply.js +8 -0
  118. package/src/blueprint/companions.js +485 -0
  119. package/src/blueprint/index.js +16 -0
  120. package/src/blueprint/loader.js +243 -1
  121. package/src/cli/blueprint.js +309 -0
  122. package/src/cli/create.js +14 -0
  123. package/src/cli/eval-coverage.js +221 -0
  124. package/src/cli/eval.js +43 -0
  125. package/src/cli/finalise.js +64 -0
  126. package/src/cli/help.js +4 -0
  127. package/src/cli/validate.js +7 -0
  128. package/src/core/store/ids.js +5 -1
  129. package/src/core/store/init.js +4 -0
  130. package/src/core/store/loader.js +4 -0
  131. package/src/core/store/validator.js +8 -1
  132. package/src/core/store/walker.js +71 -2
  133. package/src/core/store/writer.js +4 -0
  134. package/src/eval/judge.js +338 -0
  135. package/src/finalise/index.js +8 -0
  136. package/src/finalise/ingest.js +27 -0
  137. package/src/finalise/ship-without-eval.js +123 -0
  138. package/src/query/eval-coverage.js +162 -0
  139. package/src/verify/chain/index.js +67 -0
  140. package/src/verify/cli/run.js +15 -0
  141. package/src/verify/engine/index.js +10 -0
  142. package/src/verify/verdict/index.js +46 -0
@@ -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
+ }
@@ -0,0 +1,25 @@
1
+ {
2
+ "usId": "application-error-handling-US-16105",
3
+ "prdId": "PRD-001",
4
+ "reqId": "application-error-handling-REQ-003",
5
+ "version": "1.0.0",
6
+ "status": "approved",
7
+ "title": "The record's classification drives the transport response's retry hint",
8
+ "asA": "caller of an application endpoint that failed",
9
+ "iWant": "the transport response to reflect whether the failure is retryable",
10
+ "soThat": "my retry policy honours the origin's judgement instead of guessing from the status code alone",
11
+ "acceptanceCriteria": [
12
+ {
13
+ "id": "AC-16105-1",
14
+ "description": "A record with category transient produces a wire response the transport ADR maps to a retryable envelope (HTTP: a 5xx or a 429 with a Retry-After hint when the origin supplied one on the record); a record with category permanent produces a non-retryable envelope with no Retry-After (HTTP: a 4xx); a record with category unknown is treated as permanent by the wire but logged at error level.",
15
+ "given": "three failing handler paths that construct records with category transient, permanent and unknown respectively",
16
+ "when": "each request runs",
17
+ "then": "the transport response bodies and headers reflect the per-class contract above",
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-16106",
3
+ "prdId": "PRD-001",
4
+ "reqId": "application-error-handling-REQ-004",
5
+ "version": "1.0.0",
6
+ "status": "approved",
7
+ "title": "Records emit through the logging companion; the fallback stderr path fires only when no companion is applied",
8
+ "asA": "operator debugging why an error record did not appear in the log ingestion pipeline",
9
+ "iWant": "records to emit exclusively through the applied logging companion's factory (never direct stderr) when a companion is applied",
10
+ "soThat": "records land in the same pipeline as every other log line and inherit the companion's redaction, correlation and stamping discipline",
11
+ "acceptanceCriteria": [
12
+ {
13
+ "id": "AC-16106-1",
14
+ "description": "When a logging companion is applied on the project, the record emission path calls the companion factory's error() method; process.stderr.write and console.error carry no line from the boundary. When no logging companion is applied, the boundary falls back to a single-line JSON emission on stderr with a documented warning line ('no logging companion applied; emitting error record on stderr').",
15
+ "given": "two fixture applications: one with a logging companion applied, one without",
16
+ "when": "an error is emitted from the boundary",
17
+ "then": "the first case emits via the logging companion (nothing on stderr from the boundary itself); the second case emits one JSON line on stderr with the warning",
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-16107",
3
+ "prdId": "PRD-001",
4
+ "reqId": "application-error-handling-REQ-001",
5
+ "version": "1.0.0",
6
+ "status": "approved",
7
+ "title": "The boundary is substitutable via the framework interface so tests exercise it without booting the whole stack",
8
+ "asA": "test author verifying that a specific exception produces the expected record",
9
+ "iWant": "the boundary contract to expose a substitutable interface so a test can exercise the error-handling path without booting the full framework",
10
+ "soThat": "boundary tests are hermetic and fast",
11
+ "acceptanceCriteria": [
12
+ {
13
+ "id": "AC-16107-1",
14
+ "description": "TAC-1701 exposes a boundaryFor({ loggerFactory, transportWriter }) -> { handle(err, context) } function the framework registers as its handler; a test constructs the boundary with a stub loggerFactory and a stub transportWriter, invokes handle(err, context), and asserts the observed loggerFactory calls and the observed transportWriter calls.",
15
+ "given": "a stub loggerFactory and a stub transportWriter",
16
+ "when": "the test invokes handle with a synthetic exception",
17
+ "then": "the stub loggerFactory records one error() call with the constructed record and the stub transportWriter records one write with the mapped wire 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,24 @@
1
+ # application-error-handling: coordination vocabulary
2
+
3
+ ## Global topics
4
+
5
+ | Topic | Owning ADR | Meaning | Composition note |
6
+ |---|---|---|---|
7
+ | `errorHandling` | `ADR-1701-application-error-handling-record-shape` | Internal error record shape: code, category, message, correlationId, cause chain, redacted context. Minted here at v1.0.0. | Transport-agnostic. Composing blueprints that want a different internal record shape conflict by design; the operator resolves at project level. A library-registered errorHandling provider (wsd-error-handling, acme-error-shape) is preferred over this shelf provider by the companion-suggestion mechanism. |
8
+
9
+ ## Distinction from `errorEnvelope`
10
+
11
+ `errorEnvelope` stays with `application-api-rest` and governs the REST wire body (RFC 7807-style, plus code/correlationId fields). `errorHandling` (this blueprint) governs the internal record shape one project decision above every transport-specific envelope. A future transport blueprint (gRPC, message consumer) claims its OWN transport-specific errorEnvelope-family topic and reads records from this blueprint's factory through the substitutable `transportWriter` interface (TAC-1701).
12
+
13
+ ## Deliberately unclaimed topics
14
+
15
+ - `retryPolicy` (the runtime schedule and back-off behaviour). Owned by the applying platform; a future blueprint may claim it.
16
+ - `errorTelemetry` (aggregated error rate metrics, alerting rules). Belongs to a future metrics or alerting companion when the shelf ships one.
17
+
18
+ ## Shelf id band and suffix block
19
+
20
+ | Blueprint | US band | ADR/TAC suffix block | Status | Global topics |
21
+ |---|---|---|---|---|
22
+ | application-error-handling | 16101-16899 | 17xx | shipped v1.0.0 | `errorHandling` |
23
+
24
+ The shelf-wide band registry lives in `packages/rcf-lite/docs/blueprint-authoring.md` section 5.
@@ -0,0 +1,35 @@
1
+ # Guide: application-error-handling (v1.0.0)
2
+
3
+ ## What it is
4
+
5
+ A shipped, org-neutral error-handling contract for a rcf-lite application. Two boundaries (process-level uncaught, framework-level request-pipeline), one shared internal error record shape, one classification vocabulary. Records emit through the applied logging companion so PII redaction, correlation propagation and structured emission all reuse the logging blueprint's discipline. The transport response mapping delegates to the applied transport blueprint's `errorEnvelope` ADR (application-api-rest for REST; a future blueprint for gRPC / message consumer).
6
+
7
+ ## What it deliberately is not
8
+
9
+ - Not a transport wire envelope. application-api-rest owns `errorEnvelope` for REST; a future transport blueprint claims its own transport-specific errorEnvelope topic.
10
+ - Not a retry policy. The record carries `category`; the applying platform's retry surface reads it and decides. The blueprint names the per-class contract (transient => retryable; permanent => not retryable; unknown => permanent by wire, logged at error level) but does not itself schedule retries.
11
+ - Not a substitute for the logging companion's redaction. Context redaction at record construction uses the applied logging companion's redaction boundary; when no logging companion is applied, redaction is a pass-through (documented fallback).
12
+
13
+ ## When to reach for it
14
+
15
+ - Any application that has not yet applied an error-handling companion. Applying this blueprint gives a working boundary and record shape without registering a library.
16
+ - Any project whose applied service blueprints (application-api-rest, application-spa) declare `suggestedCompanions: ["errorHandling"]` and no more specific library provides the role.
17
+
18
+ ## When it does not fit
19
+
20
+ - A project standing on an organisation with a shipped error-handling library (`wsd-error-handling`, `acme-error-shape`) that provides the `errorHandling` role. The companion-suggestion mechanism resolves to the library over this shelf fallback.
21
+ - A project whose transport is not yet served by any transport blueprint the shelf ships. The delegation contract (ADR-1703) still holds; the project supplies its own `transportWriter`.
22
+
23
+ ## What a good outcome looks like
24
+
25
+ - The uncaught boundary catches every process-level exception, emits one record and exits cleanly with code 1; the framework-level boundary maps every thrown exception to a mapped wire response with no raw stack trace; every record has code, category and correlationId; the cause chain is intact across every nested wrapping; the transport response reflects the classification (retryable vs not); `rcf define validate` clean.
26
+
27
+ ## The operator decisions that remain open
28
+
29
+ - **The classification additions** (ADR-1702). Recommended defaults cover the three shared cases; per-domain additions (rate-limited, quota-exceeded, dependency-degraded) fit the same mechanism.
30
+ - **The applied transport blueprint.** REST projects apply application-api-rest 2.1.0 or later. Non-REST projects supply their own `transportWriter` against the substitutable interface.
31
+ - **The applied logging companion.** Recommended: apply observability-logging 1.0.0 or later (or a registered library-side provider). Without a logging companion, the fallback stderr path fires and the guide names the gap.
32
+
33
+ ## Cost-honesty
34
+
35
+ Adds four REQs, seven USs, two TACs, three ADRs to the project's chain. The runtime cost is one boundary registration per boot plus one record construction per error emission (including a context deep-clone for redaction). No runtime dependency added to the project's `package.json`; the boundary is authored against the applying project's own runtime and its applied transport blueprint's writer interface.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "slug": "application-spa",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "category": "application",
5
5
  "contributions": [
6
6
  {
@@ -405,5 +405,15 @@
405
405
  "light",
406
406
  "dark"
407
407
  ]
408
- }
408
+ },
409
+ "suggestedCompanions": [
410
+ {
411
+ "role": "logging",
412
+ "reason": "The client console emits structured lines through the same contract the server side uses; a logging companion supplies the factory and the client tier composes on top through its own boundary."
413
+ },
414
+ {
415
+ "role": "errorHandling",
416
+ "reason": "The browser-facing error boundary constructs the internal record from a client-side exception and hands it to the applied transport writer; an error-handling companion supplies the boundary and the record factory the SPA composes on."
417
+ }
418
+ ]
409
419
  }
@@ -41,6 +41,8 @@ This table is maintained shelf-wide across every blueprint's `docs/topics.md`. R
41
41
  | deploy-cloudflare-workers | 12101-12899 | 13xx | shipped v1.0.0 | `deploymentTarget` |
42
42
  | persistence-data-d1 | 13101-13899 | 14xx | shipped v1.0.0 | `persistenceStore`, `migrationDiscipline` |
43
43
  | observability-probe-endpoints | 14101-14899 | 15xx | shipped v1.1.0 | `healthProbes`, `readinessSemantics` |
44
+ | observability-logging | 15101-15899 | 16xx | shipped v1.0.0 | `logging` |
45
+ | application-error-handling | 16101-16899 | 17xx | shipped v1.0.0 | `errorHandling` |
44
46
 
45
47
  SPA v1.1.0 stays on the LOW end of its band: v1.0.0 occupied US-1101 through US-1128; v1.1.0 adds US-1129 and US-1130, leaving headroom above 1130 for future minor bumps and for project-side stories that mechanically derive to the 11xx numeric range.
46
48
 
@@ -0,0 +1,44 @@
1
+ # Logging blueprint (v1.0.0)
2
+
3
+ Structured single-line JSON emission for a rcf-lite project. General enterprise practice, no vendor lock-in. Ships the shelf-canonical `logging` global topic on `ADR-1601-observability-logging-line-shape` (transferred from `application-api-rest-ADR-304`, retained there as superseded history). Provides the `logging` role for the companion-suggestion mechanism.
4
+
5
+ ## Apply
6
+
7
+ ```
8
+ rcf define blueprint add <path-to>/blueprints/observability-logging
9
+ ```
10
+
11
+ Applies namespaced contributions into the project tree and records `manifest.blueprints[]`. Apply is idempotent; `rcf define blueprint list` shows the applied entry; `rcf define blueprint remove observability-logging` cleanly removes an unreferenced application.
12
+
13
+ ## Anatomy
14
+
15
+ | Piece | Where | What |
16
+ |---|---|---|
17
+ | Metadata | `blueprint.json` | Slug, version 1.0.0, category `observability`, `providesRoles: ["logging"]`, and the 19 contributions with scope/topic on `ADR-1601` |
18
+ | Doc set | `contributions/` | 5 REQs, 8 USs (13 ACs), 2 TACs, 4 ADRs, schema-valid and namespaced (`observability-logging-REQ-001` prefix family; `ADR-1601-observability-logging-line-shape` suffix family) |
19
+ | Log line sample | `assets/samples/log-line.json` | Exact JSON shape of one emitted line carrying the shared minimum field set |
20
+ | Guide | `guide/observability-logging.md` | Operator-facing: when to use it, when not, what stays your call, mechanism-reach gaps |
21
+ | Coordination vocabulary | `docs/topics.md` | The `logging` global-topic string this blueprint claims, plus the shelf-wide id band registry update |
22
+
23
+ The doc set is contributions (copied into the project tree by `rcf define blueprint add`); the guide, assets and docs are package-resident references.
24
+
25
+ ## What it contributes, and what it deliberately does not
26
+
27
+ Contributed kinds: REQ, US (with inline ACs), TAC, ADR. Adherence is expressed as ACs; the blueprint ships no test files (ratified decision 5) and no code.
28
+
29
+ Deliberately not contributed: a specific log-shipping transport (Elastic Common Schema over Logstash, Loki push endpoints, Datadog agent, Splunk HEC); a rotation policy for on-disk log files (the shipped emission target is stdout, and the surrounding platform owns the pipe); a metrics-export surface (out of scope, sits with the metrics-store companion when the shelf ships one); a distributed-tracing surface (spans, propagation, sampling; out of scope for v1). The choice of ECS library (Elastic's, `pino-elastic-common-schema`, a project-authored shim) stays project-side; the blueprint names the ECS-neutral field set the AC binds to.
30
+
31
+ ## The one global decision
32
+
33
+ `ADR-1601-observability-logging-line-shape` ships `scope: global` on topic `logging`. This is the project's structured emission contract: one JSON object per line to stdout with the shared minimum field set (message, level, timestamp, correlationId, environment, serviceName, serviceVersion). A composing blueprint that holds a different opinion (structured multi-line, per-service special format, non-JSON) conflicts here by design; the operator resolves at project level with `rcf define blueprint supersede logging --incoming <source>`.
34
+
35
+ See `docs/topics.md` for the exact string, the expected resolutions, and the AC id band allocation (observability-logging owns 15101-15899, ADR/TAC suffix block 16xx).
36
+
37
+ ## Quality bar
38
+
39
+ One JSON object per line on stdout carrying the shared minimum field set; serialisation failures caught at the boundary with a stderr notice and no service crash; correlation identifier accepted from an inbound header (default `X-Correlation-Id`, elicited alternative), propagated onto every log line, reflected on outbound requests, `null` when no ambient context; PII fields redacted at the emission boundary by named category (recommended defaults: credential, pii.email, pii.name, pii.address, token, bearer; elicited additions); operator-elicited minimum level per environment filtered per line; environment, serviceName and serviceVersion supplied at boot and stamped on every line; an in-memory capture mode for hermetic tests. Every bar is carried by ACs in the doc set, not by this README.
40
+
41
+ ## Known mechanism-reach gaps
42
+
43
+ - **Call-site coverage.** The blueprint cannot prove every log-emitting call site uses the factory. A rogue `console.log` from any code path bypasses the boundary and lands unstructured text on stdout. Turning the discipline into a runtime-observable AC either becomes document-observable ("the source contains no `console.log`") or requires a project-side gate the blueprint does not ship. Named here rather than smuggled in as a v1 requirement. Promotion signal: a lint-rule contribution in a future rcf-lite blueprint or in the `delivery-ci-workflows` blueprint's shipped ruleset. Project-side workaround: a grep gate as a project-authored TC bound to `AC-15101-1` that walks the source tree for bare `console.` calls outside a whitelist.
44
+ - **Text-level redaction.** ADR-1603 redacts at field-name granularity. A payload string that concatenates an email into the `message` field carries the email through the emission. Field-name redaction is fast and predictable; text-level redaction is expensive and prone to false positives. Project-side workaround: wrap the log call sites in a project-authored redaction pass; long-term, a future companion blueprint may claim a text-level `logRedaction` topic.
@@ -0,0 +1,13 @@
1
+ {
2
+ "message": "request completed",
3
+ "level": "info",
4
+ "timestamp": "2026-09-04T12:20:00.123Z",
5
+ "correlationId": "c3b8d0f1-4a2e-4a6f-9c1a-1234567890ab",
6
+ "environment": "production",
7
+ "serviceName": "orders-api",
8
+ "serviceVersion": "1.4.2",
9
+ "httpStatus": 200,
10
+ "requestPath": "/orders/42",
11
+ "durationMs": 37,
12
+ "userId": "[REDACTED:pii.name]"
13
+ }
@@ -0,0 +1,27 @@
1
+ {
2
+ "slug": "observability-logging",
3
+ "version": "1.0.0",
4
+ "category": "observability",
5
+ "providesRoles": ["logging"],
6
+ "contributions": [
7
+ { "id": "observability-logging-REQ-001", "kind": "req", "path": "requirements/observability-logging-req-001.json" },
8
+ { "id": "observability-logging-REQ-002", "kind": "req", "path": "requirements/observability-logging-req-002.json" },
9
+ { "id": "observability-logging-REQ-003", "kind": "req", "path": "requirements/observability-logging-req-003.json" },
10
+ { "id": "observability-logging-REQ-004", "kind": "req", "path": "requirements/observability-logging-req-004.json" },
11
+ { "id": "observability-logging-REQ-005", "kind": "req", "path": "requirements/observability-logging-req-005.json" },
12
+ { "id": "observability-logging-US-15101", "kind": "us", "path": "user-stories/observability-logging-us-15101.json" },
13
+ { "id": "observability-logging-US-15102", "kind": "us", "path": "user-stories/observability-logging-us-15102.json" },
14
+ { "id": "observability-logging-US-15103", "kind": "us", "path": "user-stories/observability-logging-us-15103.json" },
15
+ { "id": "observability-logging-US-15104", "kind": "us", "path": "user-stories/observability-logging-us-15104.json" },
16
+ { "id": "observability-logging-US-15105", "kind": "us", "path": "user-stories/observability-logging-us-15105.json" },
17
+ { "id": "observability-logging-US-15106", "kind": "us", "path": "user-stories/observability-logging-us-15106.json" },
18
+ { "id": "observability-logging-US-15107", "kind": "us", "path": "user-stories/observability-logging-us-15107.json" },
19
+ { "id": "observability-logging-US-15108", "kind": "us", "path": "user-stories/observability-logging-us-15108.json" },
20
+ { "id": "TAC-1601-observability-logging-logger-factory", "kind": "tac", "path": "tacs/tac-1601-observability-logging-logger-factory.json" },
21
+ { "id": "TAC-1602-observability-logging-redaction-boundary", "kind": "tac", "path": "tacs/tac-1602-observability-logging-redaction-boundary.json" },
22
+ { "id": "ADR-1601-observability-logging-line-shape", "kind": "adr", "path": "adrs/adr-1601-observability-logging-line-shape.json", "scope": "global", "topic": "logging" },
23
+ { "id": "ADR-1602-observability-logging-correlation-id-header", "kind": "adr", "path": "adrs/adr-1602-observability-logging-correlation-id-header.json", "recommendedDefault": true, "elicited": true, "standardsTraceClause": "generic enterprise practice" },
24
+ { "id": "ADR-1603-observability-logging-redaction-categories", "kind": "adr", "path": "adrs/adr-1603-observability-logging-redaction-categories.json", "recommendedDefault": true, "elicited": true, "standardsTraceClause": "generic enterprise practice" },
25
+ { "id": "ADR-1604-observability-logging-level-vocabulary", "kind": "adr", "path": "adrs/adr-1604-observability-logging-level-vocabulary.json", "recommendedDefault": true, "elicited": true, "standardsTraceClause": "generic enterprise practice" }
26
+ ]
27
+ }
@@ -0,0 +1,25 @@
1
+ {
2
+ "adrId": "ADR-1601-observability-logging-line-shape",
3
+ "prdId": "PRD-001",
4
+ "tadId": "TAD-001",
5
+ "version": "1.0.0",
6
+ "status": "accepted",
7
+ "title": "Structured single-line JSON to standard output with the shared minimum field set",
8
+ "context": "Every log ingestion pipeline the applying project's operator might reach for (Elastic Common Schema pipelines, Loki, Datadog, Splunk) parses one JSON object per line. Emissions that break this shape (multi-line stack traces, unstructured text, non-JSON) either drop from the pipeline silently or produce a per-service special case the operator maintains forever. The core shelf owned the 'logging' topic on application-api-rest-ADR-304 through the 0.18.0 line; from this blueprint the topic transfers to observability-logging so a shelf project without api-rest still gets a logging contract, and api-rest becomes a consumer.",
9
+ "decision": "Every log emission from the applying application lands as exactly one JSON object per line on process stdout. Every line carries the shared minimum field set: message (string), level (one string from the ADR-1604 vocabulary), timestamp (ISO-8601 with millisecond precision), correlationId (string or null), environment (string), serviceName (string), serviceVersion (string). Payload fields fold onto the top level of the object. A JSON serialisation failure at the boundary drops the emission with one stderr notice and returns to the caller without throwing.",
10
+ "consequences": "Any log ingestion pipeline that parses one JSON object per line consumes the applying application's stdout without a service-specific parser. Message search, level filtering, environment partitioning and service-version debugging all become one-field queries. The shared minimum field set is stable across every applying project; a project cannot silently rename or drop one of the seven without a major version bump. This ADR is the shelf-canonical owner of the 'logging' topic; the previously superseded application-api-rest-ADR-304 defers to it. Composing blueprints that want a different logging surface (structured multi-line, per-service special format, non-JSON) conflict on the 'logging' topic and the operator resolves at project level.",
11
+ "alternativesConsidered": [
12
+ {
13
+ "name": "Free-form text emission",
14
+ "summary": "The application writes plain text log messages with no structural discipline.",
15
+ "reasonNotChosen": "Every ingestion pipeline needs a per-service parser, and every parser breaks the moment a caller changes a phrase. The industry-standard shape for the tier this blueprint targets is one JSON object per line."
16
+ },
17
+ {
18
+ "name": "Multi-line JSON with pretty-printing",
19
+ "summary": "One JSON object per emission but pretty-printed across multiple lines for human readability.",
20
+ "reasonNotChosen": "Breaks the one-line assumption every log pipeline holds. Pretty printing is a rendering concern of the consuming tool, not of the emitter."
21
+ }
22
+ ],
23
+ "createdAt": "2026-09-04T12:20:00Z",
24
+ "updatedAt": "2026-09-04T12:20:00Z"
25
+ }
@@ -0,0 +1,25 @@
1
+ {
2
+ "adrId": "ADR-1602-observability-logging-correlation-id-header",
3
+ "prdId": "PRD-001",
4
+ "tadId": "TAD-001",
5
+ "version": "1.0.0",
6
+ "status": "accepted",
7
+ "title": "Correlation identifier header name is X-Correlation-Id by recommended default; operator elicits an alternative at apply",
8
+ "context": "Inbound requests may arrive with correlation identifiers under different header names depending on the surrounding platform: X-Correlation-Id in generic HTTP tooling, X-Request-Id in reverse proxies, traceparent in W3C Trace Context clients. Picking one at the code level forces every applying project to align; making it operator-elicited lets the applying platform's convention win.",
9
+ "decision": "The recommended default correlation header is X-Correlation-Id. The applying operator elicits an alternative at apply time; the elicited value is recorded on the applied ADR. The application accepts the elicited header on inbound requests and reflects it as the SAME header name on outbound requests.",
10
+ "consequences": "A project sitting behind a reverse proxy that already stamps X-Request-Id sets the elicited value to X-Request-Id and inherits the proxy's identifier without a code change. A project with no upstream stamping keeps the recommended default and mints v4 UUIDs. The header name is fixed for the process lifetime; changing it is a re-apply.",
11
+ "alternativesConsidered": [
12
+ {
13
+ "name": "Hard-code X-Correlation-Id",
14
+ "summary": "The application accepts only X-Correlation-Id and no elicited alternative.",
15
+ "reasonNotChosen": "Projects sitting behind a proxy that stamps a different header lose the upstream identifier and mint a new one on every request; the whole point of the mechanism is to inherit an existing identifier when one is present."
16
+ },
17
+ {
18
+ "name": "Adopt W3C Trace Context traceparent as the sole surface",
19
+ "summary": "The application requires the traceparent header for correlation, following the W3C Trace Context standard.",
20
+ "reasonNotChosen": "The applying tier for this blueprint is small greenfield applications; requiring a Trace Context client on every calling service would force upstream tooling the tier does not otherwise need. A project that operates in a Trace Context ecosystem elicits traceparent as the header name and inherits the standard."
21
+ }
22
+ ],
23
+ "createdAt": "2026-09-04T12:20:00Z",
24
+ "updatedAt": "2026-09-04T12:20:00Z"
25
+ }
@@ -0,0 +1,25 @@
1
+ {
2
+ "adrId": "ADR-1603-observability-logging-redaction-categories",
3
+ "prdId": "PRD-001",
4
+ "tadId": "TAD-001",
5
+ "version": "1.0.0",
6
+ "status": "accepted",
7
+ "title": "Redaction categories: credential, pii.email, pii.name, pii.address, token, bearer by recommended default; operator elicits additions",
8
+ "context": "PII redaction in logs is one of the most common compliance surfaces (GDPR, PCI DSS, HIPAA); a blueprint that ships no default vocabulary forces every applying project to invent its own on day one, which reliably produces a coverage gap. A vocabulary that pretends to be exhaustive fights every project's actual field surface. Splitting recommended defaults from elicited additions gets both.",
9
+ "decision": "The recommended default category set is credential, pii.email, pii.name, pii.address, token, bearer. A field whose name equals or path-tail-matches any of these categories is redacted at the emission boundary. The applying operator elicits additional category names at apply (kebab or dotted-namespace grammar; refused with a boot error otherwise); elicited additions apply alongside the defaults. The applied ADR records the final category list the applying project uses.",
10
+ "consequences": "A project's logs never emit a field named 'credential', 'pii.email', 'pii.name', 'pii.address', 'token' or 'bearer' with a real value out of the box, before the operator has thought about redaction. Additional per-domain categories (ssn, iban, jwt) land through the elicited additions at apply without a code change. Free-text redaction inside the message string (an email pasted into an error message) is out of scope; the mechanism reach is field-level, not text-level.",
11
+ "alternativesConsidered": [
12
+ {
13
+ "name": "Ship no default vocabulary",
14
+ "summary": "The blueprint ships a redaction contract but no default category list; every applying project supplies its own from scratch.",
15
+ "reasonNotChosen": "Reliably produces a coverage gap on day one because the operator has not yet enumerated the field surface. Recommended defaults with elicited additions give a working floor and a project-side extension path in the same round."
16
+ },
17
+ {
18
+ "name": "Redact by field-value pattern rather than field-name category",
19
+ "summary": "The boundary walks every value and redacts against pattern lists (regex for emails, credit cards, tokens).",
20
+ "reasonNotChosen": "Pattern-matching every value on every emission is expensive at scale and produces false positives that make logs unusable. Field-name category matching is fast and predictable; the trade-off is that a field with an unrelated name that holds a PII value is not redacted, which is what the field-name grammar exists to catch."
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-1604-observability-logging-level-vocabulary",
3
+ "prdId": "PRD-001",
4
+ "tadId": "TAD-001",
5
+ "version": "1.0.0",
6
+ "status": "accepted",
7
+ "title": "Level vocabulary is trace, debug, info, warn, error, fatal; operator elicits the minimum level per environment",
8
+ "context": "The industry has settled on a six-level vocabulary (trace, debug, info, warn, error, fatal); most log tooling (structured or otherwise) already understands these strings without translation. Production and development want different minimum levels; hard-coding info-and-above helps neither.",
9
+ "decision": "The recommended default vocabulary is exactly trace, debug, info, warn, error, fatal, in that severity order. The applying operator elicits a minimum level per environment at apply; the applied ADR records the elicited value. The factory refuses to construct with an unknown level string and drops emissions below the minimum before serialisation.",
10
+ "consequences": "The vocabulary is stable across every applying project; log tooling wires its level filter once. Per-environment minimums honour the production-development split without changing call sites. A future project that wants a different vocabulary (Apache-style FATAL/ERROR/WARN/INFO/DEBUG/TRACE) supersedes this ADR at project level; the vocabulary transfer is a mechanical string rename in call sites.",
11
+ "alternativesConsidered": [
12
+ {
13
+ "name": "Adopt Apache log4j-style ALL/TRACE/DEBUG/INFO/WARN/ERROR/FATAL/OFF vocabulary",
14
+ "summary": "The blueprint ships the historically-familiar seven-plus-two vocabulary from log4j.",
15
+ "reasonNotChosen": "ALL and OFF are configuration meta-levels, not emission levels; conflating them with severities has produced enough confusion that modern structured-logging tools omit them. The six emission levels above match what today's tools accept out of the box."
16
+ }
17
+ ],
18
+ "createdAt": "2026-09-04T12:20:00Z",
19
+ "updatedAt": "2026-09-04T12:20:00Z"
20
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "reqId": "observability-logging-REQ-001",
3
+ "prdId": "PRD-001",
4
+ "title": "Every service surface emits structured single-line JSON log lines through the shared factory",
5
+ "description": "Every log emission from the applying application passes through the logger factory contract TAC-1601 produces, writes one JSON object per line to standard output, and includes at minimum the fields message (string), level (one of the level vocabulary in ADR-1604), timestamp (ISO-8601 with millisecond precision), correlationId (string, may be null when no ambient context), environment (string), serviceName (string) and serviceVersion (string). A log emission that fails to serialise (a value that is not JSON-representable, a circular reference) is caught, dropped with a one-time stderr notice, and the calling code path continues; a serialisation failure never crashes the service.",
6
+ "category": "functional",
7
+ "domain": "observability",
8
+ "priority": "must",
9
+ "rationale": "Downstream log ingestion tooling (Elastic Common Schema pipelines, Loki, Datadog, Splunk) parses one JSON object per line. Multi-line stack traces or unstructured text break the pipeline and produce silent data loss. Threading every emission through one factory closes the drift class where a stray console.log lands unstructured text alongside structured lines.",
10
+ "tags": ["blueprint:observability-logging", "category:01-structured-emission"],
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": "observability-logging-REQ-002",
3
+ "prdId": "PRD-001",
4
+ "title": "The application accepts, propagates and reflects a correlation identifier on every log line",
5
+ "description": "The application reads a correlation identifier from an inbound request header (name governed by ADR-1602; default X-Correlation-Id, elicited per project), threads it through the request-scoped context using the mechanism the TAC exposes, includes it on every log line emitted during that request, and reflects it on any outbound request initiated inside the same context. When no inbound identifier is present, the application mints a v4 UUID and treats it as the ambient value. Requests processed with no ambient context (a scheduled task boot, a background worker startup line) log with correlationId equal to null; the field is always present.",
6
+ "category": "functional",
7
+ "domain": "observability",
8
+ "priority": "must",
9
+ "rationale": "Correlation identifiers are the mechanism that stitches together the log lines of a single logical request across multiple services and multiple retries. Without acceptance-and-reflection at every hop, log search cannot follow the chain. Present-but-null is a legitimate ambient state (boot-time logs, cron); refusing to log without an identifier fights the operator when they need the emission most.",
10
+ "tags": ["blueprint:observability-logging", "category:02-correlation-id"],
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": "observability-logging-REQ-003",
3
+ "prdId": "PRD-001",
4
+ "title": "PII fields are redacted by named category at the emission boundary, not at the call site",
5
+ "description": "The logger factory exposes a redaction contract (ADR-1603 recommended-default categories: credential, pii.email, pii.name, pii.address, token, bearer; elicited additions per project). A field named or path-labelled with a category is replaced with the string [REDACTED:<category>] before the line is serialised, whether the log call site remembered to redact or not. Redaction happens at the emission boundary (TAC-1602): a rogue field name in a caller-supplied object is redacted by rule, not by hope. The redaction pass never mutates the caller's original object; the emission pipeline copies the object first.",
6
+ "category": "nonFunctional",
7
+ "domain": "observability",
8
+ "priority": "must",
9
+ "rationale": "PII leaks through logs are one of the most common data-exposure defects in production systems. Placing the redaction responsibility at the call site produces a coverage gap the size of the codebase; placing it at the boundary is the only scalable enforcement. Named categories keep the vocabulary auditable and let a project add its own categories at apply time.",
10
+ "tags": ["blueprint:observability-logging", "category:03-pii-redaction"],
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": "observability-logging-REQ-004",
3
+ "prdId": "PRD-001",
4
+ "title": "Every environment has an operator-elicited minimum log level; the factory refuses to emit below it",
5
+ "description": "The logger factory reads a minimum level per environment (ADR-1604 recommended default vocabulary: trace, debug, info, warn, error, fatal; the minimum level per environment is elicited at apply). Emissions below the minimum are dropped before serialisation; emissions at or above the minimum land on stdout. The environment name is supplied at boot (REQ-005) and the level filter is applied per line, not batched. Changing the minimum level mid-run through a documented hook (SIGHUP re-read, admin endpoint, config reload) is out of scope for v1; the minimum is fixed for the process lifetime.",
6
+ "category": "functional",
7
+ "domain": "observability",
8
+ "priority": "must",
9
+ "rationale": "Production log volume is dominated by debug and trace lines; enabling them everywhere buries the signal. Development wants them; production does not. A per-environment operator-elicited minimum keeps the discipline explicit at apply time and lets the same code path serve both environments with the same call sites.",
10
+ "tags": ["blueprint:observability-logging", "category:04-level-vocabulary"],
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": "observability-logging-REQ-005",
3
+ "prdId": "PRD-001",
4
+ "title": "Every log line carries environment, service name and service version, all operator-elicited at boot",
5
+ "description": "The three fields (environment, serviceName, serviceVersion) are supplied to the logger factory at boot from operator-elicited configuration (environment variables, config file, deployment manifest); the factory refuses to construct a logger when any of the three is missing or blank. Every emitted line carries all three at fixed field names so log ingestion can partition by environment and service without prose parsing.",
6
+ "category": "functional",
7
+ "domain": "observability",
8
+ "priority": "must",
9
+ "rationale": "A shared log destination sees lines from every environment and every service. Without machine-readable partitioning fields, filtering by environment or service requires string search on the message field, which is fragile and prone to false positives. Requiring the three fields at boot puts the discipline at the earliest possible surface.",
10
+ "tags": ["blueprint:observability-logging", "category:05-boot-fields"],
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,46 @@
1
+ {
2
+ "tacId": "TAC-1601-observability-logging-logger-factory",
3
+ "prdId": "PRD-001",
4
+ "tadId": "TAD-001",
5
+ "version": "1.0.0",
6
+ "status": "approved",
7
+ "name": "Logger factory",
8
+ "purpose": "Owns the construction of every logger the application uses. Threads the operator-elicited boot fields (environment, serviceName, serviceVersion, minimum level, correlation header name, redaction categories) into a shared factory contract; every call site takes a logger from the factory and never constructs one directly. Provides a hermetic in-memory capture mode for tests.",
9
+ "responsibilities": [
10
+ "Construct a logger from operator-elicited boot fields; refuse construction when environment, serviceName or serviceVersion is missing or blank (AC-15105-1), and refuse when the minimum level is not one of the recommended-default vocabulary (AC-15106-1).",
11
+ "Emit each log call as one JSON line to standard output carrying the shared minimum field set (AC-15101-1), applying the level filter per line (AC-15104-1).",
12
+ "Expose an in-memory capture mode returning an ordered array of the structured objects the same emission would have written to stdout (AC-15107-1), with zero stdout side effects.",
13
+ "Catch serialisation failures (BigInt, function reference, circular object) at the boundary, drop the emission with one stderr notice naming the offending field, and return without throwing to the caller (AC-15101-2).",
14
+ "Read the ambient correlation context per emission and thread it onto the correlationId field (AC-15102-1), or null when no context is active (AC-15102-3)."
15
+ ],
16
+ "internalStructure": "One module exporting createLoggerFactory({ environment, serviceName, serviceVersion, minimumLevel, correlationHeader, redactionCategories, now, sink }) -> { getLogger(name) -> Logger, capturedLines() }. The factory closes over boot fields, a now() clock (default () -> new Date()) and a pluggable sink (default process.stdout; test-mode sink is an in-memory array). Loggers are lightweight wrappers around the sink. Serialisation is JSON.stringify with a replacer function that folds BigInt to string and short-circuits on circularity via a WeakSet, throwing a controlled SerializationRefusedError caught at the boundary.",
17
+ "interfaces": [
18
+ {
19
+ "name": "createLoggerFactory",
20
+ "kind": "factory",
21
+ "description": "createLoggerFactory({ environment, serviceName, serviceVersion, minimumLevel, correlationHeader, redactionCategories, now, sink }) -> { getLogger(name), capturedLines() }. The factory is constructed exactly once at boot; getLogger returns a per-module logger with .trace/.debug/.info/.warn/.error/.fatal methods each taking (message, payload?)."
22
+ },
23
+ {
24
+ "name": "Logger",
25
+ "kind": "interface",
26
+ "description": "One method per level; each takes (message: string, payload?: object). Emits one JSON line carrying message, level, timestamp, correlationId, environment, serviceName, serviceVersion, and every payload field folded onto the top level."
27
+ },
28
+ {
29
+ "name": "correlationHeader",
30
+ "kind": "config",
31
+ "description": "Operator-elicited inbound header name governing the correlation identifier acceptance and outbound propagation; recommended default X-Correlation-Id per ADR-1602."
32
+ }
33
+ ],
34
+ "dependencies": [
35
+ {
36
+ "name": "Redaction boundary",
37
+ "kind": "tac",
38
+ "tacId": "TAC-1602-observability-logging-redaction-boundary",
39
+ "description": "The factory calls the redaction boundary on every serialised payload before the line is written to the sink; the boundary encapsulates the category vocabulary and the [REDACTED:<category>] rewriting."
40
+ }
41
+ ],
42
+ "tradeoffs": "The factory owns the field vocabulary; a project that wants an additional top-level field beyond the shared minimum authors it as a payload field (which appears on the line) rather than as a factory-side hook. Capture mode is a first-class interface, not a test-only afterthought: the runtime sink is one strategy the same interface serves, so the capture path exercises the same serialisation logic as the runtime path. The v1 blueprint does not ship a dynamic level-change interface; the level is fixed for the process lifetime (REQ-004).",
43
+ "notes": "Known mechanism-reach gap: the blueprint cannot prove every emission call site uses the factory. A project-side grep gate ('every log call is a factory-returned logger method, not a bare console.log') or a lint rule closes the class; the blueprint's guide names this gap.",
44
+ "createdAt": "2026-09-04T12:20:00Z",
45
+ "updatedAt": "2026-09-04T12:20:00Z"
46
+ }