@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.
- package/.github/copilot-instructions.md +48 -0
- package/README.md +8 -0
- package/contracts/specs/LogSDKFuntionalSpec.md +394 -0
- package/contracts/specs/fanout-semantics.v1.md +244 -0
- package/contracts/specs/sink-contract.v1.md +223 -0
- package/contracts/specs/step-record.v1.md +292 -0
- package/contracts/specs/validation-rules.v1.md +324 -0
- package/docs/LogSDK-Unified-Execution-Logging-Framework.md +93 -0
- package/docs/log_sdk_test_cases_traceability_plan.md +197 -0
- package/docs/log_sdk_test_coverage_report.md +198 -0
- package/docs/prompts/AuditorSDK.txt +214 -0
- package/package.json +29 -0
- package/src/core/clock.ts +25 -0
- package/src/core/context.ts +142 -0
- package/src/core/fanout.ts +38 -0
- package/src/core/ids.ts +35 -0
- package/src/core/message_constraints.ts +66 -0
- package/src/core/outcomes.ts +5 -0
- package/src/core/record_builder.ts +269 -0
- package/src/core/spool.ts +41 -0
- package/src/core/types.ts +56 -0
- package/src/crypto-shim.d.ts +9 -0
- package/src/fs-shim.d.ts +15 -0
- package/src/index.ts +107 -0
- package/src/node-test-shim.d.ts +1 -0
- package/src/perf_hooks-shim.d.ts +7 -0
- package/src/process-shim.d.ts +1 -0
- package/src/sinks/file_ndjson.ts +42 -0
- package/src/sinks/file_ndjson_sink.ts +45 -0
- package/src/sinks/sink_types.ts +15 -0
- package/src/sinks/stdout_sink.ts +20 -0
- package/src/validate/api_surface_guard.ts +106 -0
- package/src/validate/noncompliance.ts +33 -0
- package/src/validate/schema_guard.ts +238 -0
- package/tests/fanout.test.ts +51 -0
- package/tests/fanout_spool.test.ts +96 -0
- package/tests/message_constraints.test.ts +7 -0
- package/tests/node-shim.d.ts +1 -0
- package/tests/record_builder.test.ts +32 -0
- package/tests/sequence_monotonic.test.ts +62 -0
- package/tests/sinks_file_ndjson.test.ts +53 -0
- package/tests/step1_compliance.test.ts +192 -0
- package/tools/test_results/generate-test-traceability.js +60 -0
- package/tools/test_results/normalize-test-results.js +57 -0
- package/tools/test_results/run-tests-then-prebuild.js +103 -0
- package/tools/test_results/test-case-map.json +9 -0
- package/tsconfig.json +31 -0
- 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
|
+
|