@unifyplane/logsdk 1.0.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 (48) hide show
  1. package/.github/copilot-instructions.md +48 -0
  2. package/README.md +8 -0
  3. package/contracts/specs/LogSDKFuntionalSpec.md +394 -0
  4. package/contracts/specs/fanout-semantics.v1.md +244 -0
  5. package/contracts/specs/sink-contract.v1.md +223 -0
  6. package/contracts/specs/step-record.v1.md +292 -0
  7. package/contracts/specs/validation-rules.v1.md +324 -0
  8. package/docs/LogSDK-Unified-Execution-Logging-Framework.md +93 -0
  9. package/docs/log_sdk_test_cases_traceability_plan.md +197 -0
  10. package/docs/log_sdk_test_coverage_report.md +198 -0
  11. package/docs/prompts/AuditorSDK.txt +214 -0
  12. package/package.json +29 -0
  13. package/src/core/clock.ts +25 -0
  14. package/src/core/context.ts +142 -0
  15. package/src/core/fanout.ts +38 -0
  16. package/src/core/ids.ts +35 -0
  17. package/src/core/message_constraints.ts +66 -0
  18. package/src/core/outcomes.ts +5 -0
  19. package/src/core/record_builder.ts +269 -0
  20. package/src/core/spool.ts +41 -0
  21. package/src/core/types.ts +56 -0
  22. package/src/crypto-shim.d.ts +9 -0
  23. package/src/fs-shim.d.ts +15 -0
  24. package/src/index.ts +107 -0
  25. package/src/node-test-shim.d.ts +1 -0
  26. package/src/perf_hooks-shim.d.ts +7 -0
  27. package/src/process-shim.d.ts +1 -0
  28. package/src/sinks/file_ndjson.ts +42 -0
  29. package/src/sinks/file_ndjson_sink.ts +45 -0
  30. package/src/sinks/sink_types.ts +15 -0
  31. package/src/sinks/stdout_sink.ts +20 -0
  32. package/src/validate/api_surface_guard.ts +106 -0
  33. package/src/validate/noncompliance.ts +33 -0
  34. package/src/validate/schema_guard.ts +238 -0
  35. package/tests/fanout.test.ts +51 -0
  36. package/tests/fanout_spool.test.ts +96 -0
  37. package/tests/message_constraints.test.ts +7 -0
  38. package/tests/node-shim.d.ts +1 -0
  39. package/tests/record_builder.test.ts +32 -0
  40. package/tests/sequence_monotonic.test.ts +62 -0
  41. package/tests/sinks_file_ndjson.test.ts +53 -0
  42. package/tests/step1_compliance.test.ts +192 -0
  43. package/tools/test_results/generate-test-traceability.js +60 -0
  44. package/tools/test_results/normalize-test-results.js +57 -0
  45. package/tools/test_results/run-tests-then-prebuild.js +103 -0
  46. package/tools/test_results/test-case-map.json +9 -0
  47. package/tsconfig.json +31 -0
  48. package/validators/bootstrap/validate-repo-structure.ts +590 -0
@@ -0,0 +1,48 @@
1
+ # Copilot Instructions — LogSDK
2
+
3
+ Purpose: Help AI agents work productively in this repository by capturing architecture, workflows, and project-specific conventions that are enforced by code and tests.
4
+
5
+ ## Overview
6
+ - System: Execution logging SDK producing immutable, canonical "step" records, delivered to sinks with authoritative failure handling.
7
+ - Entrypoint: `initLogSDK(config)` returns an API with `step(message)` and optional `flush()` in [src/index.ts](src/index.ts).
8
+ - Runtime: Node.js (TypeScript → ESM); tests run via Vitest.
9
+
10
+ ## Architecture & Data Flow
11
+ - Context capsule: Initialize once via `initContext()`; exposes `context_hash`/`context_version` for records. See [src/core/context.ts](src/core/context.ts).
12
+ - IDs & sequencing: `createIdGenerator()` provides monotonic `sequence` and `record_id`. See [src/core/ids.ts](src/core/ids.ts) and [src/core/clock.ts](src/core/clock.ts).
13
+ - Record builder: `buildStepRecord()` validates inputs, sets dual clocks, canonicalizes payload, computes `record_hash` (sha256), and deep-freezes the result. See [src/core/record_builder.ts](src/core/record_builder.ts).
14
+ - Fanout & spool safety: `fanout()` emits to sinks, requires at least one authoritative sink, writes emergency spool if authoritative emit fails; returns `OK | DEGRADED | FAILED`. See [src/core/fanout.ts](src/core/fanout.ts) and [src/core/spool.ts](src/core/spool.ts).
15
+ - Canonical schema & guards: `assertStepRecord()` and `assertApiSurface()` enforce schema and API discipline. See [src/validate/schema_guard.ts](src/validate/schema_guard.ts) and [src/validate/api_surface_guard.ts](src/validate/api_surface_guard.ts).
16
+ - Sinks: NDJSON file sinks (authoritative/observability) and stdout sink. See [src/sinks/file_ndjson.ts](src/sinks/file_ndjson.ts), [src/sinks/file_ndjson_sink.ts](src/sinks/file_ndjson_sink.ts), [src/sinks/stdout_sink.ts](src/sinks/stdout_sink.ts).
17
+ - Types: Canonical `StepRecord` fields and sink interfaces. See [src/core/types.ts](src/core/types.ts) and [src/sinks/sink_types.ts](src/sinks/sink_types.ts).
18
+
19
+ ## Usage Pattern (Examples)
20
+ - Construct sinks: `authoritativeNdjsonSink('...')` for governance-critical emission; `fileNdjsonSink('...')` for observability.
21
+ - Initialize:
22
+ - `const log = initLogSDK({ context: { /* object */ }, system, sinks });`
23
+ - `system` must include `institution, system_name, system_type, environment, system_version` (optional `instance_id`).
24
+ - Emit & flush:
25
+ - `await log.step('...');` → produces one immutable record; sequence increments per instance.
26
+ - `await log.flush?.();` → asks all sinks to flush (if implemented).
27
+
28
+ ## Project-Specific Conventions
29
+ - Message constraints: 512-char max; must be a simple string; reject JSON-shaped or base64-like blobs. See [src/core/message_constraints.ts](src/core/message_constraints.ts).
30
+ - API surface: Only `step(message)` and optional `flush()`; `child()` is reserved and must declare `allowedScopes` if present; forbidden names include `log`, `emit`, `metric`, `span`, etc. See [src/validate/api_surface_guard.ts](src/validate/api_surface_guard.ts).
31
+ - Authoritative sink requirement: At least one sink with `sinkClass: 'authoritative'`; otherwise `NO_AUTHORITATIVE_SINK`. See [src/validate/schema_guard.ts](src/validate/schema_guard.ts).
32
+ - Emergency spool path: Controlled by `LOGSDK_EMERGENCY_SPOOL_PATH`; default is OS temp dir `logsdk-emergency-spool.ndjson`. See [src/core/spool.ts](src/core/spool.ts).
33
+ - Immutability: Records are deep-frozen before emission and validated as frozen. Never mutate records in sinks.
34
+ - Integrity: `record_hash` is computed over an ordered, normalized JSON; do not change field ordering, hashing, or algorithm.
35
+
36
+ ## Developer Workflows
37
+ - Quick tests: `npm test` (Vitest).
38
+ - Full verify pipeline (strict): `npm run verify` → runs repo structure validator, Vitest (`--reporter=json`), normalizes results, generates traceability evidence.
39
+ - Build: `npm run build` → runs `verify` then TypeScript compile via `tsc`.
40
+ - Tooling scripts: See [tools/test_results/run-tests-then-prebuild.js](tools/test_results/run-tests-then-prebuild.js), [tools/test_results/normalize-test-results.js](tools/test_results/normalize-test-results.js), and [tools/test_results/generate-test-traceability.js](tools/test_results/generate-test-traceability.js).
41
+
42
+ ## Implementation Notes for Agents
43
+ - Prefer `authoritativeNdjsonSink()` for governance-grade emission; add `fileNdjsonSink()` or `createStdoutSink()` for observability.
44
+ - Do not embed per-step context; context is initialized once and referenced via hash/version.
45
+ - When adding fields to `StepRecord`, update schema guards and hash field order consistently; otherwise tests may fail.
46
+ - Use `NonComplianceError` codes for violations; do not throw generic errors for governance checks. See [src/validate/noncompliance.ts](src/validate/noncompliance.ts).
47
+
48
+ If any section is unclear or misses a repo-specific nuance, tell us what you need clarified and we’ll refine these instructions.
package/README.md ADDED
@@ -0,0 +1,8 @@
1
+ # LogSDK
2
+
3
+ System Type: pipeline
4
+
5
+ ## Runtime Scope
6
+
7
+ LogSDK core is implemented for the Node.js/CommonJS runtime and depends on the Node standard library (fs, crypto, perf_hooks, etc.). Other runtimes (Python, JVM, browsers) must provide a compatible core that enforces the same canonical contracts, and thin adapters should bridge them into the canonical API surface described in docs/LogSDKFuntionalSpec.md.
8
+
@@ -0,0 +1,394 @@
1
+
2
+ # LogSDK Functional Specification (v1.1)
3
+
4
+ ## Table of Contents
5
+ - [1. Overview](#section-1-overview)
6
+ - [2. Initialization & Configuration](#section-2-initialization-and-configuration)
7
+ - [3. Public API Surface (Closed)](#section-3-public-api-surface-closed)
8
+ - [4. Context Handling (Non-Negotiable)](#section-4-context-handling-non-negotiable)
9
+ - [5. Step Record Construction](#section-5-step-record-construction)
10
+ - [6. Canonical Serialization & Hashing (Normative)](#section-6-canonical-serialization-and-hashing-normative)
11
+ - [7. Sequencing & Identity](#section-7-sequencing-and-identity)
12
+ - [8. Message Constraints (Strict)](#section-8-message-constraints-strict)
13
+ - [9. Sink Model & Fan-Out Semantics](#section-9-sink-model-and-fan-out-semantics)
14
+ - [10. Authoritative Failure Semantics](#section-10-authoritative-failure-semantics)
15
+ - [11. Flush Behavior](#section-11-flush-behavior)
16
+ - [12. Validation & Non-Compliance](#section-12-validation-and-non-compliance)
17
+ - [13. Explicit Constraints & Non-Goals](#section-13-explicit-constraints-and-non-goals)
18
+ - [14. Known Gaps (As Implemented)](#section-14-known-gaps-as-implemented)
19
+ - [15. Final Lock Statement](#section-15-final-lock-statement)
20
+ - [16. Runtime Scope](#section-16-runtime-scope)
21
+ - [Appendix A. Revision Safety Notes (Non-Normative)](#appendix-a-revision-safety-notes-non-normative)
22
+
23
+ <a id="section-1-overview"></a>
24
+ ## 1. Overview
25
+
26
+ LogSDK provides a **minimal, governance-aligned logging facility** that emits **immutable execution step records** to configured sinks.
27
+
28
+ It enforces:
29
+
30
+ * strict message constraints
31
+ * immutable, single-capture context referencing
32
+ * deterministic sequencing and fan-out
33
+ * canonical schema validation at emission
34
+ * cryptographic integrity via canonical hashing
35
+
36
+ LogSDK produces **execution evidence only**.
37
+ It does not interpret meaning, enforce authority, or perform analytics.
38
+
39
+ ---
40
+
41
+ <a id="section-2-initialization-and-configuration"></a>
42
+ ## 2. Initialization & Configuration
43
+
44
+ Initialization occurs **once per process or execution boundary**.
45
+
46
+ ### 2.1 Initialization Inputs
47
+
48
+ LogSDK initialization accepts:
49
+
50
+ * **context**
51
+
52
+ * A single plain object (no arrays, no prototypes)
53
+ * Captured exactly once
54
+ * Normalized and deeply frozen
55
+ * Never embedded in records
56
+ * Referenced only by `context_hash` and `context_version`
57
+
58
+ * **system**
59
+
60
+ * Ownership metadata identifying the emitting system
61
+ * Must be consistent with declared system classification
62
+
63
+ * **sinks**
64
+
65
+ * An ordered list of sink entries
66
+ * Each sink declares a `class`:
67
+
68
+ * `authoritative` — evidence-bearing
69
+ * `observability` — best-effort visibility
70
+ * Each sink implements:
71
+
72
+ * `emit(record)`
73
+ * optional `flush()`
74
+
75
+ ### 2.2 Default Sink Support
76
+
77
+ The SDK may provide a file-based **NDJSON sink** as a default `authoritative` sink.
78
+
79
+ NDJSON sinks must:
80
+
81
+ * append records atomically
82
+ * detect and reject partial writes
83
+ * never mutate records
84
+
85
+ ---
86
+
87
+ <a id="section-3-public-api-surface-closed"></a>
88
+ ## 3. Public API Surface (Closed)
89
+
90
+ LogSDK intentionally exposes a **minimal, closed API**.
91
+
92
+ ### 3.1 Canonical Methods
93
+
94
+ * **`step(message: string): Promise<void>`**
95
+
96
+ * Validates message constraints
97
+ * Constructs a canonical step record
98
+ * Applies canonical serialization
99
+ * Computes integrity hashes
100
+ * Deep-freezes the record
101
+ * Validates record against schema
102
+ * Emits deterministically to all sinks
103
+ * Throws a **non-compliance error** if authoritative emission fails
104
+
105
+ * **`flush(): Promise<void>`** *(optional)*
106
+
107
+ * Invokes `flush()` on sinks that implement it
108
+
109
+ ### 3.2 Forbidden API Patterns
110
+
111
+ The SDK must not expose:
112
+
113
+ * per-step context injection
114
+ * severity levels, metrics, or counters
115
+ * arbitrary metadata setters
116
+ * hooks or interceptors that mutate records
117
+ * payload-carrying APIs
118
+
119
+ The **message string is the only per-step input**.
120
+
121
+ ---
122
+
123
+ <a id="section-4-context-handling-non-negotiable"></a>
124
+ ## 4. Context Handling (Non-Negotiable)
125
+
126
+ * Context is captured **once** at initialization
127
+ * Context must be a plain object
128
+ * Context is normalized and deeply frozen
129
+ * Context is referenced only via:
130
+
131
+ * `context_hash`
132
+ * `context_version`
133
+
134
+ Per-step context mutation or augmentation is **explicitly forbidden**.
135
+
136
+ ---
137
+
138
+ <a id="section-5-step-record-construction"></a>
139
+ ## 5. Step Record Construction
140
+
141
+ Each invocation of `step()` produces **exactly one immutable step record**.
142
+
143
+ A canonical step record includes:
144
+
145
+ * SDK identity and version
146
+ * sequence number (monotonic)
147
+ * dual time:
148
+
149
+ * wall-clock timestamp (UTC)
150
+ * monotonic timestamp
151
+ * system ownership metadata
152
+ * optional execution correlation fields
153
+ * optional source location metadata
154
+ * bounded message string
155
+ * optional message code
156
+ * context references (hash + version)
157
+ * optional evidence references
158
+ * integrity fields:
159
+
160
+ * `record_hash`
161
+ * `hash_algorithm`
162
+
163
+ Records are:
164
+
165
+ * constructed once
166
+ * deep-frozen prior to emission
167
+ * identical across all sinks
168
+
169
+ ---
170
+
171
+ <a id="section-6-canonical-serialization-and-hashing-normative"></a>
172
+ ## 6. Canonical Serialization & Hashing (Normative)
173
+
174
+ ### 6.1 Canonical Serialization
175
+
176
+ Before hashing, records and context **must be serialized canonically** using a deterministic JSON representation.
177
+
178
+ Normative requirements:
179
+
180
+ * UTF-8 encoding
181
+ * stable key ordering
182
+ * normalized numbers and strings
183
+ * no insignificant whitespace
184
+ * no runtime-dependent ordering
185
+
186
+ An RFC-8785–compatible JSON Canonicalization Scheme is recommended.
187
+
188
+ ### 6.2 Hash Scope
189
+
190
+ * `context_hash` is computed over the canonical serialized context object
191
+ * `record_hash` is computed over the canonical serialized record **excluding**:
192
+
193
+ * `record_hash`
194
+ * sink-specific transport metadata
195
+
196
+ Hash algorithm must be explicitly declared via `hash_algorithm`.
197
+
198
+ ---
199
+
200
+ <a id="section-7-sequencing-and-identity"></a>
201
+ ## 7. Sequencing & Identity
202
+
203
+ * Sequence is a **non-negative, monotonically increasing integer**
204
+ * Scoped to a single SDK instance
205
+ * Never resets during process lifetime
206
+
207
+ Record identifiers:
208
+
209
+ * are unique per process and SDK instance
210
+ * incorporate monotonic time and sequence
211
+ * may include an optional sanitized instance tag
212
+
213
+ These guarantees support deterministic replay and auditability.
214
+
215
+ ---
216
+
217
+ <a id="section-8-message-constraints-strict"></a>
218
+ ## 8. Message Constraints (Strict)
219
+
220
+ Message input must:
221
+
222
+ * be a non-empty string
223
+ * respect a hard maximum length
224
+
225
+ Rejected inputs include:
226
+
227
+ * objects or arrays
228
+ * embedded JSON
229
+ * base64-like payloads
230
+ * attempts to smuggle structured data
231
+ * non-string values
232
+
233
+ Messages describe **what step occurred**, not **what data flowed**.
234
+
235
+ ---
236
+
237
+ <a id="section-9-sink-model-and-fan-out-semantics"></a>
238
+ ## 9. Sink Model & Fan-Out Semantics
239
+
240
+ ### 9.1 Sink Classes
241
+
242
+ * **Authoritative sinks**
243
+
244
+ * Evidence-bearing
245
+ * Failure is system-significant
246
+ * Failure raises non-compliance error
247
+
248
+ * **Observability sinks**
249
+
250
+ * Best-effort only
251
+ * Failure does not block execution
252
+
253
+ ### 9.2 Fan-Out Rules
254
+
255
+ * A single frozen record is dispatched
256
+ * Dispatch is **sequential and deterministic**
257
+ * Sink order is configuration-declared
258
+ * No concurrent fan-out
259
+ * No per-sink mutation or enrichment
260
+
261
+ ---
262
+
263
+ <a id="section-10-authoritative-failure-semantics"></a>
264
+ ## 10. Authoritative Failure Semantics
265
+
266
+ If an authoritative sink fails during emission:
267
+
268
+ * The step is considered **non-compliant**
269
+ * A standardized non-compliance error is raised
270
+ * Observability sinks may still receive the record
271
+ * No retries are implied unless explicitly implemented by the sink
272
+
273
+ The SDK does **not** silently degrade evidence guarantees.
274
+
275
+ ---
276
+
277
+ <a id="section-11-flush-behavior"></a>
278
+ ## 11. Flush Behavior
279
+
280
+ If provided:
281
+
282
+ * `flush()` drains buffered records
283
+ * finalizes sink resources
284
+ * behavior is sink-specific
285
+
286
+ Flush is:
287
+
288
+ * explicit
289
+ * never automatic
290
+ * never invoked implicitly by `step()`
291
+
292
+ ---
293
+
294
+ <a id="section-12-validation-and-non-compliance"></a>
295
+ ## 12. Validation & Non-Compliance
296
+
297
+ LogSDK enforces compliance via:
298
+
299
+ * **API guard**
300
+
301
+ * only `step()` and optional `flush()`
302
+
303
+ * **Schema guard**
304
+
305
+ * required fields
306
+ * correct types
307
+ * immutability
308
+ * timestamp validity
309
+
310
+ * **Message guard**
311
+
312
+ * bounded strings only
313
+ * no embedded context or payloads
314
+
315
+ * **Emission guard**
316
+
317
+ * authoritative sink failure surfaces error
318
+
319
+ Violations emit standardized **non-compliance codes**.
320
+
321
+ Non-compliance is classified as a **project defect**, not a governance failure.
322
+
323
+ ---
324
+
325
+ <a id="section-13-explicit-constraints-and-non-goals"></a>
326
+ ## 13. Explicit Constraints & Non-Goals
327
+
328
+ LogSDK intentionally does **not** provide:
329
+
330
+ * severity levels
331
+ * metrics or counters
332
+ * payload embedding
333
+ * per-sink enrichment
334
+ * concurrent fan-out
335
+ * dynamic context mutation
336
+
337
+ It emits **factual execution step evidence only**.
338
+
339
+ ---
340
+
341
+ <a id="section-14-known-gaps-as-implemented"></a>
342
+ ## 14. Known Gaps (As Implemented)
343
+
344
+ The following are explicitly out of scope for v1.x:
345
+
346
+ * emergency spooling or degraded evidence modes
347
+ * concurrent sink dispatch
348
+ * automatic correlation or surface population
349
+ * recovery semantics beyond error signaling
350
+
351
+ These gaps are acknowledged and frozen.
352
+
353
+ ---
354
+
355
+ <a id="section-15-final-lock-statement"></a>
356
+ ## 15. Final Lock Statement
357
+
358
+ This specification defines the **canonical enforcement layer** for UnifyPlane logging.
359
+
360
+ Any change to:
361
+
362
+ * canonical serialization rules
363
+ * hashing scope
364
+ * context immutability
365
+ * message constraints
366
+ * authoritative sink semantics
367
+
368
+ requires a **versioned specification update**, not incremental evolution.
369
+
370
+ ---
371
+
372
+ <a id="section-16-runtime-scope"></a>
373
+ ## 16. Runtime Scope
374
+
375
+ LogSDK core is implemented for the Node.js/CommonJS runtime and relies on the Node standard library (`fs`, `crypto`, `perf_hooks`, etc.). Any other runtime (Python, JVM, embedded, browser) that wants to reuse the canonical contracts must provide its own core implementation that mirrors the structure, determinism, and evidence guarantees described above, exposing only a thin adapter surface.
376
+
377
+ <a id="appendix-a-revision-safety-notes-non-normative"></a>
378
+ ## Appendix A. Revision Safety Notes (Non-Normative)
379
+
380
+ ### Why this revision is safe
381
+
382
+ * No new APIs
383
+ * No new responsibilities
384
+ * No governance leakage
385
+ * Determinism and auditability are now explicit
386
+ * Suitable for long-running pipelines *and* high-integrity processes
387
+
388
+ If you want next, the **correct follow-on artifacts** are:
389
+
390
+ * a **formal JSON Schema** for the step record
391
+ * a **non-compliance code registry**
392
+ * a **migration delta (v1 → v1.1)**
393
+
394
+ Tell me which one you want to generate next.
@@ -0,0 +1,244 @@
1
+ # 🧩 LogSDK Core — Fan-Out Semantics & Failure Rules (v1.0)
2
+
3
+ ## Table of Contents
4
+
5
+ - [0. Purpose (Non-Negotiable)](#section-0-purpose-non-negotiable)
6
+ - [1. Emit-Once Rule (Canonical)](#section-1-emit-once-rule-canonical)
7
+ - [2. Sink Ordering Rule (Deterministic)](#section-2-sink-ordering-rule-deterministic)
8
+ - [3. Delivery Mode (Default & Allowed Modes)](#section-3-delivery-mode-default-and-allowed-modes)
9
+ - [4. Acknowledgement Semantics](#section-4-acknowledgement-semantics)
10
+ - [5. Failure Classification (Locked)](#section-5-failure-classification-locked)
11
+ - [6. Core Failure Handling (Default Policy)](#section-6-core-failure-handling-default-policy)
12
+ - [7. Emergency Spool Semantics (If Enabled)](#section-7-emergency-spool-semantics-if-enabled)
13
+ - [8. Backpressure & Rate Limits (Bounded Behavior)](#section-8-backpressure-and-rate-limits-bounded-behavior)
14
+ - [9. Flush Semantics (If Exposed)](#section-9-flush-semantics-if-exposed)
15
+ - [10. Compliance Outcomes (Deterministic)](#section-10-compliance-outcomes-deterministic)
16
+ - [11. Freeze Statement](#section-11-freeze-statement)
17
+
18
+ <a id="section-0-purpose-non-negotiable"></a>
19
+ ## 0. Purpose (Non-Negotiable)
20
+
21
+ Fan-out exists to allow:
22
+
23
+ * **Authoritative evidence capture** (must succeed)
24
+ * **Observability projection** (may fail without impact)
25
+
26
+ while ensuring the system emits **one canonical Step Record**.
27
+
28
+ ---
29
+
30
+ <a id="section-1-emit-once-rule-canonical"></a>
31
+ ## 1. Emit-Once Rule (Canonical)
32
+
33
+ For each `step(message)` call:
34
+
35
+ 1. Core constructs **exactly one** Step Record
36
+ 2. Core freezes it (immutability)
37
+ 3. Core computes integrity fields (record hash)
38
+ 4. Core submits the same frozen record to **all configured sinks**
39
+
40
+ There is **no per-sink mutation** and **no per-sink context**.
41
+
42
+ ---
43
+
44
+ <a id="section-2-sink-ordering-rule-deterministic"></a>
45
+ ## 2. Sink Ordering Rule (Deterministic)
46
+
47
+ Fan-out order must be deterministic:
48
+
49
+ ### 2.1 Declared Order
50
+
51
+ Sinks are invoked in the **declared configuration order**.
52
+
53
+ This enables:
54
+
55
+ * predictable I/O patterns
56
+ * reproducible behavior in tests
57
+ * stable incident diagnosis
58
+
59
+ ### 2.2 Recommended Ordering (Standard)
60
+
61
+ Configuration should typically be:
62
+
63
+ 1. **Authoritative sinks first**
64
+ 2. **Observability sinks after**
65
+
66
+ But the core rule is “declared order,” not “type order.”
67
+
68
+ ---
69
+
70
+ <a id="section-3-delivery-mode-default-and-allowed-modes"></a>
71
+ ## 3. Delivery Mode (Default & Allowed Modes)
72
+
73
+ ### 3.1 Default Mode: Sequential Dispatch
74
+
75
+ Core sends the record to sinks **sequentially** in order.
76
+
77
+ Reason: deterministic error semantics.
78
+
79
+ ### 3.2 Allowed Future Mode: Concurrent Dispatch (Explicit Only)
80
+
81
+ Concurrent dispatch is permitted only if:
82
+
83
+ * ordering of *sink acknowledgements* does not affect correctness
84
+ * failure classification remains deterministic
85
+ * authoritative sink guarantees remain enforced
86
+
87
+ If concurrent mode exists, it must be **explicitly configured** and must preserve all rules in this document.
88
+
89
+ ---
90
+
91
+ <a id="section-4-acknowledgement-semantics"></a>
92
+ ## 4. Acknowledgement Semantics
93
+
94
+ Each sink invocation results in one of:
95
+
96
+ * **ACK**: sink confirms record accepted/persisted
97
+ * **NACK**: sink rejects record (explicit failure)
98
+ * **TIMEOUT**: sink did not respond in bounded time
99
+
100
+ Core must treat TIMEOUT as failure.
101
+
102
+ ---
103
+
104
+ <a id="section-5-failure-classification-locked"></a>
105
+ ## 5. Failure Classification (Locked)
106
+
107
+ Each sink is configured with a **sink_class**:
108
+
109
+ ### 5.1 `authoritative`
110
+
111
+ Meaning:
112
+
113
+ * required for governance evidence continuity
114
+ * failure must be treated as system-significant
115
+
116
+ ### 5.2 `observability`
117
+
118
+ Meaning:
119
+
120
+ * best-effort visibility only
121
+ * failure must not block execution by default
122
+
123
+ > Sink class is a configuration property, not inferred dynamically.
124
+
125
+ ---
126
+
127
+ <a id="section-6-core-failure-handling-default-policy"></a>
128
+ ## 6. Core Failure Handling (Default Policy)
129
+
130
+ ### 6.1 Authoritative Sink Failure
131
+
132
+ If **any authoritative sink** fails for a record:
133
+
134
+ Core must produce a **local failure signal** in one of two canonical forms:
135
+
136
+ **A) Fail-Closed (Strict)**
137
+
138
+ * `step()` returns failure status to caller boundary (platform code)
139
+ * caller may decide to abort the request/job
140
+
141
+ **B) Fail-Open With Local Spool (Resilient)**
142
+
143
+ * record is written to a **local emergency spool**
144
+ * core reports “degraded evidence capture”
145
+ * execution may continue
146
+
147
+ **Constraint:**
148
+ Fail-open is permitted **only** if emergency spool exists and is itself local/owned.
149
+
150
+ The default policy should be **fail-open with spool** for production services, and **fail-closed** for tests/CI.
151
+
152
+ ---
153
+
154
+ ### 6.2 Observability Sink Failure
155
+
156
+ If an observability sink fails:
157
+
158
+ * core must continue dispatching to remaining sinks
159
+ * core must not mark the overall step as failed
160
+ * core may optionally increment an internal counter (not emitted as logs)
161
+
162
+ Observability failures are never evidence failures.
163
+
164
+ ---
165
+
166
+ <a id="section-7-emergency-spool-semantics-if-enabled"></a>
167
+ ## 7. Emergency Spool Semantics (If Enabled)
168
+
169
+ Emergency spool is the only allowed “fail-open” mechanism.
170
+
171
+ Rules:
172
+
173
+ * append-only
174
+ * local filesystem (or equivalent local owned store)
175
+ * bounded rotation
176
+ * replay-capable (a later process can re-emit)
177
+
178
+ Spool must never:
179
+
180
+ * enrich records
181
+ * re-order within a single execution context
182
+
183
+ It is a safety net, not a datastore.
184
+
185
+ ---
186
+
187
+ <a id="section-8-backpressure-and-rate-limits-bounded-behavior"></a>
188
+ ## 8. Backpressure & Rate Limits (Bounded Behavior)
189
+
190
+ LogSDK must not allow unbounded memory growth.
191
+
192
+ Two mechanisms are permitted:
193
+
194
+ ### 8.1 Bounded Buffer
195
+
196
+ * If sink is slow, core buffers up to a fixed limit
197
+ * When full, apply the failure policy (authoritative vs observability)
198
+
199
+ ### 8.2 Drop Policy (Observability Only)
200
+
201
+ Drops are allowed **only** for observability sinks when buffers overflow.
202
+
203
+ Drops are forbidden for authoritative sinks unless emergency spool is active and successful.
204
+
205
+ ---
206
+
207
+ <a id="section-9-flush-semantics-if-exposed"></a>
208
+ ## 9. Flush Semantics (If Exposed)
209
+
210
+ If `flush()` exists:
211
+
212
+ * it must attempt to drain buffers to all sinks
213
+ * it must return a status indicating whether authoritative sinks are fully flushed
214
+ * it must not block indefinitely (bounded by timeouts)
215
+
216
+ Flush is an operational tool, not a correctness requirement.
217
+
218
+ ---
219
+
220
+ <a id="section-10-compliance-outcomes-deterministic"></a>
221
+ ## 10. Compliance Outcomes (Deterministic)
222
+
223
+ For each `step()` call, the core must be able to classify the outcome:
224
+
225
+ * **OK**: all authoritative sinks ACK’d
226
+ * **DEGRADED**: authoritative sink failed but record spooled
227
+ * **FAILED**: authoritative sink failed and not spooled
228
+
229
+ Observability sink failures do not change these outcomes.
230
+
231
+ ---
232
+
233
+ <a id="section-11-freeze-statement"></a>
234
+ ## 11. Freeze Statement
235
+
236
+ This **Fan-Out Semantics & Failure Rules v1.0** is now locked:
237
+
238
+ * deterministic order (declared)
239
+ * authoritative vs observability classes
240
+ * fail-open allowed only with spool
241
+ * bounded buffers
242
+ * no unbounded memory
243
+ * no per-sink mutation
244
+