@mcpherson-ai/observa-local-node 0.1.3

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 (34) hide show
  1. package/README.md +103 -0
  2. package/SHA256SUMS +33 -0
  3. package/artifacts/mcphersonai-observa-adapter-n8n-0.1.0.tgz +0 -0
  4. package/artifacts/mcphersonai-observa-domain-generic-0.1.0.tgz +0 -0
  5. package/artifacts/mcphersonai-observa-hosted-transport-0.1.0.tgz +0 -0
  6. package/artifacts/mcphersonai-observa-n8n-h1-binding-0.1.0.tgz +0 -0
  7. package/artifacts/mcphersonai-observa-node-0.1.0.tgz +0 -0
  8. package/distribution/observa-cli/bin/observa-n8n-hosted.mjs +114 -0
  9. package/distribution/observa-cli/bin/observa.mjs +331 -0
  10. package/distribution/observa-cli/integrations/n8n/observa-external-hook.cjs +132 -0
  11. package/distribution/observa-cli/keys/observa-beta-1.public.json +7 -0
  12. package/distribution/observa-cli/src/artifact.mjs +143 -0
  13. package/distribution/observa-cli/src/config.mjs +162 -0
  14. package/distribution/observa-cli/src/errors.mjs +21 -0
  15. package/distribution/observa-cli/src/hosted-delivery.mjs +148 -0
  16. package/distribution/observa-cli/src/index.mjs +39 -0
  17. package/distribution/observa-cli/src/install.mjs +443 -0
  18. package/distribution/observa-cli/src/lock.mjs +57 -0
  19. package/distribution/observa-cli/src/manifest-schema.mjs +208 -0
  20. package/distribution/observa-cli/src/manifest-verify.mjs +122 -0
  21. package/distribution/observa-cli/src/n8n-hook.mjs +70 -0
  22. package/distribution/observa-cli/src/pair.mjs +149 -0
  23. package/distribution/observa-cli/src/re-pair.mjs +171 -0
  24. package/distribution/observa-cli/src/service.mjs +218 -0
  25. package/distribution/observa-cli/src/state.mjs +87 -0
  26. package/distribution/observa-cli/src/status.mjs +191 -0
  27. package/distribution/observa-cli/src/vocabulary.mjs +47 -0
  28. package/npm-distribution-provenance.json +1 -0
  29. package/package.json +36 -0
  30. package/runtime-adapters/n8n/src/strict-json.mjs +138 -0
  31. package/sdk/contracts/canonical.mjs +289 -0
  32. package/sdk/contracts/entry-boundary.mjs +417 -0
  33. package/sdk/contracts/errors.mjs +334 -0
  34. package/sdk/contracts/stable-primitives.mjs +141 -0
@@ -0,0 +1,334 @@
1
+ // THE CLOSED REFUSAL BOUNDARY.
2
+ //
3
+ // A public error object cannot safely be a durable provenance token: callers
4
+ // can capture it, decorate it, and replay it through a later callback. Every
5
+ // foreign-code boundary therefore emits a *fresh*, code-only refusal instead
6
+ // of passing any previously thrown object through. Internal validation keeps
7
+ // its specific codes by placing narrow `runUntrusted` calls only around the
8
+ // reflection/callback operation that is actually foreign.
9
+ //
10
+ // The object has no caller-controlled detail channel. Its message and
11
+ // detail fields are code-only and immutable; the object itself is frozen.
12
+ // Native Promise results
13
+ // are observed then refused because this synchronous contract cannot allow a
14
+ // later rejection to leak caller text after its closed result has returned.
15
+
16
+ // EVERY CODE THIS PACKAGE MAY EMIT, in one closed list.
17
+ //
18
+ // Derived mechanically from the source set — every literal in a code-carrying
19
+ // argument position of `refuse`, `refuseOnRawThrow`, `runUntrusted`,
20
+ // `snapshotRecord`, `exactKeys`, `entryRecord`, `entryArray`,
21
+ // `assertVocabularyValue`, `validateEventPayload` and their siblings, plus the
22
+ // node's entry-code table and the critical section's phase table.
23
+ //
24
+ // FINAL CLOSURE, INDEPENDENT REVIEW #1 — THAT CLAIM USED TO BE FALSE IN ALL
25
+ // THREE PARTS. The list was not derived, nothing re-derived it, and it was
26
+ // wrong in BOTH directions: `PROJECTION_EVENT_PAYLOAD_INVALID` was emitted by
27
+ // the public `projectStateAt` and not declared here, while
28
+ // `EVIDENCE_OUTSIDE_PROFILE_VOCABULARY` was declared and emitted nowhere. The
29
+ // undeclared one escaped because it is bound to a module const and passed by
30
+ // NAME, so even a literal-only scan would have missed it. What stood in for the
31
+ // guard was a test that called six hand-picked methods and asserted their codes
32
+ // were members — it could not see either divergence.
33
+ //
34
+ // `error-provenance.test.mjs` now performs the derivation this comment
35
+ // describes, resolving single-assignment string consts, and fails on a set
36
+ // difference in EITHER direction. A code added to the code without being
37
+ // declared here fails the suite rather than silently becoming
38
+ // `REFUSAL_CODE_UNDECLARED` at runtime.
39
+ export const REFUSAL_CODES = Object.freeze([
40
+ "ACTION_ALREADY_ATTEMPTED",
41
+ "ADAPTER_CAPABILITIES_INVALID",
42
+ "ADAPTER_CAPABILITY_UNSATISFIED",
43
+ "ADAPTER_CONTRACT_REQUIRED",
44
+ "ADAPTER_DECLARATION_INVALID",
45
+ "ADAPTER_ID_INVALID",
46
+ "ADAPTER_METHOD_NOT_IMPLEMENTED",
47
+ "ADAPTER_VERSION_INVALID",
48
+ "AUTHORITY_BINDING_FIELDS_INVALID",
49
+ "AUTHORITY_BINDING_ID_INVALID",
50
+ "AUTHORITY_BINDING_NOT_REGISTERED",
51
+ "AUTHORITY_BINDING_NOT_SUCCESSOR",
52
+ "AUTHORITY_BINDING_OPTIONS_INVALID",
53
+ "AUTHORITY_BINDING_SUPERSEDED",
54
+ "AUTHORITY_BINDING_TAMPERED",
55
+ "BINDING_ALREADY_SUPERSEDED",
56
+ "BINDING_OUTSIDE_PROFILE_VOCABULARY",
57
+ "BINDING_PROFILE_MISMATCH",
58
+ "BINDING_REVISION_PROFILE_MISMATCH",
59
+ "BINDING_REVISION_REASON_INVALID",
60
+ "BINDING_REVISION_REASON_UNEXPECTED",
61
+ "BINDING_REVISION_TENANT_MISMATCH",
62
+ "BOUND_INTENT_FIELDS_INVALID",
63
+ "BOUND_INTENT_ID_INVALID",
64
+ "BOUND_INTENT_OPTIONS_INVALID",
65
+ "BOUND_INTENT_TAMPERED",
66
+ "CAPABILITY_NOT_DECLARED",
67
+ "CLOCK_INVALID",
68
+ "CLOCK_REQUIRED",
69
+ "COMPARISON_INPUT_INVALID",
70
+ "CORRECTION_AUTHORITY_MISMATCH",
71
+ "CORRECTION_ERA_MISMATCH",
72
+ "CORRECTION_REVISION_NOT_DISTINCT",
73
+ "CORRECTION_RULE_INVALID",
74
+ "CORRECTION_RULE_NOT_REGISTERED",
75
+ "CORRECTION_SEMANTICS_MISSING",
76
+ "CORRECTION_SOURCE_MISMATCH",
77
+ "CORRECTION_TARGET_REQUIRED",
78
+ "CROSS_INTENT_SUPERSESSION_REFUSED",
79
+ "CROSS_TENANT_REFUSED",
80
+ "ENTRY_POINT_NOT_DECLARED",
81
+ "ENVELOPE_ACCESSOR_FIELD",
82
+ "ENVELOPE_BINDING_MISMATCH",
83
+ "ENVELOPE_CONTEXT_INVALID",
84
+ "ENVELOPE_DESCRIPTOR_INVALID",
85
+ "ENVELOPE_INCONSISTENT",
86
+ "ENVELOPE_INVALID_ENUM",
87
+ "ENVELOPE_INVALID_FORMAT",
88
+ "ENVELOPE_INVALID_TYPE",
89
+ "ENVELOPE_MISSING_FIELD",
90
+ "ENVELOPE_NESTED_OBJECT",
91
+ "ENVELOPE_NOT_PLAIN_OBJECT",
92
+ "ENVELOPE_OUTSIDE_PROFILE_VOCABULARY",
93
+ "ENVELOPE_PROJECTION_INVALID",
94
+ "ENVELOPE_SERIALIZED_INPUT",
95
+ "ENVELOPE_SYMBOL_FIELD",
96
+ "ENVELOPE_UNKNOWN_FIELD",
97
+ "EVIDENCE_APPEND_FIELDS_INVALID",
98
+ "EVIDENCE_APPEND_OPTIONS_INVALID",
99
+ "EVIDENCE_BEFORE_ACTION",
100
+ "EVIDENCE_ERA_INVALID",
101
+ "EVIDENCE_FINGERPRINT_INVALID",
102
+ "EVIDENCE_IDENTITY_CONFLICT",
103
+ "EVIDENCE_LOG_GENERATION_INVALID",
104
+ "EVIDENCE_LOG_OPTIONS_INVALID",
105
+ "EVIDENCE_LOG_STALE",
106
+ "EVIDENCE_PAYLOAD_INVALID",
107
+ "EVIDENCE_PAYLOAD_REQUIRED",
108
+ "EVIDENCE_READ_EPOCH_INVALID",
109
+ "EVIDENCE_RECORDED_AT_INVALID",
110
+ "EVIDENCE_RELATION_INVALID",
111
+ "EVIDENCE_SOURCE_INVALID",
112
+ "EVIDENCE_SOURCE_OUTSIDE_PROFILE_VOCABULARY",
113
+ "EVIDENCE_SOURCE_REQUIRED",
114
+ "INTENT_ALREADY_REVISED",
115
+ "INTENT_OUTSIDE_PROFILE_VOCABULARY",
116
+ "INTENT_PROFILE_MISMATCH",
117
+ "INTENT_REVISION_PROFILE_MISMATCH",
118
+ "INTENT_REVISION_REASON_INVALID",
119
+ "INTENT_REVISION_REASON_UNEXPECTED",
120
+ "INTENT_REVISION_TENANT_MISMATCH",
121
+ "INVALID_LOCAL_TENANT",
122
+ "NODE_APPEND_DURING_EVALUATION",
123
+ "NODE_CRITICAL_STATE_MOVED",
124
+ "NODE_METHOD_NOT_IMPLEMENTED",
125
+ "NODE_QUERY_INVALID",
126
+ "NODE_REENTRANT_APPEND",
127
+ "NODE_REENTRANT_OPERATION",
128
+ "NODE_SHAPE_INVALID",
129
+ "NODE_SURFACE_EMITS_AUTHORITY",
130
+ "NON_EVIDENCE_RELATION_REFUSED",
131
+ "NO_APPLICABLE_EVIDENCE",
132
+ "PRIOR_EVIDENCE_APPLICABILITY_REQUIRED",
133
+ "PRIOR_EVIDENCE_APPLICABILITY_UNEXPECTED",
134
+ "PROFILE_BEHAVIOUR_NOT_INSPECTABLE",
135
+ "PROFILE_CLASSIFICATION_INVALID",
136
+ "PROFILE_CONTRACT_VERSION_MISMATCH",
137
+ "PROFILE_DESCRIPTOR_INVALID",
138
+ "PROFILE_EVALUATION_FAILED",
139
+ "PROFILE_NOT_REGISTERED",
140
+ "PROFILE_OUTCOME_CAUSE_REQUIRED",
141
+ "PROFILE_OUTCOME_INCONSISTENT",
142
+ "PROFILE_OUTCOME_INVALID",
143
+ "PROFILE_OUTCOME_OWNER_REQUIRED",
144
+ "PROFILE_OUTCOME_OWNER_UNKNOWN",
145
+ "PROFILE_OUTCOME_REASON_UNKNOWN",
146
+ "PROFILE_REASON_CODE_COLLISION",
147
+ "PROFILE_SHAPE_INVALID",
148
+ "PROFILE_VERSION_REWRITE_ATTEMPT",
149
+ "PROJECTION_EVENTS_INVALID",
150
+ "PROJECTION_EVENT_INVALID",
151
+ "PROJECTION_EVENT_PAYLOAD_INVALID",
152
+ "PROJECTION_INTENT_MISMATCH",
153
+ "PROJECTION_RECOVERY_INCOMPLETE",
154
+ "PROJECTION_SEQUENCE_GAP",
155
+ "PROJECTION_SEQUENCE_INVALID",
156
+ "PROJECTION_STATE_INVALID",
157
+ "READBACK_DEADLINE_NOT_REACHED",
158
+ "READBACK_NOT_PENDING",
159
+ "READBACK_POLICY_INVALID",
160
+ "READBACK_POLICY_REQUIRED",
161
+ "READBACK_STILL_PENDING",
162
+ "RECOVERY_CAUSE_REQUIRED",
163
+ "RECOVERY_CAUSE_UNEXPECTED",
164
+ "RECOVERY_CAUSE_UNKNOWN",
165
+ "RECOVERY_IN_PROGRESS",
166
+ "RECOVERY_NOT_APPLICABLE",
167
+ "RECOVERY_NOT_OPEN",
168
+ "RECOVERY_OWNER_UNKNOWN",
169
+ // ROUND-2 REVIEW #1, H1R2-04. A verdict may not close a repair that no fresh
170
+ // evidence resolved. Distinct from RECOVERY_IN_PROGRESS, which answers the
171
+ // narrower "somebody is already working on it".
172
+ "RECOVERY_UNRESOLVED",
173
+ // ROUND-2 REVIEW #1, H1R2-01. The fallback for a code this package never
174
+ // declared: the only way a non-member string can leave this file.
175
+ "REFUSAL_CODE_UNDECLARED",
176
+ "RELATION_SUPERSESSION_MISMATCH",
177
+ "RUNTIME_CLASS_INVALID",
178
+ "SUPERSESSION_TARGET_ALREADY_SUPERSEDED",
179
+ "SUPERSESSION_TARGET_NOT_EVIDENCE",
180
+ "SUPERSESSION_TARGET_UNKNOWN",
181
+ "TOLERANCE_SPEC_INVALID",
182
+ "TOLERANCE_SPEC_REQUIRED",
183
+ "UNKNOWN_BOUND_INTENT",
184
+ "UNKNOWN_COMPARISON_RULE",
185
+ "UNKNOWN_EVENT_KIND",
186
+ "UNKNOWN_EVIDENCE_EVENT",
187
+ "VALUE_ACCESSOR_REFUSED",
188
+ "VALUE_ACCESS_REFUSED",
189
+ "VALUE_CYCLE",
190
+ "VALUE_NOT_JSON_COMPATIBLE",
191
+ "VALUE_NOT_PLAIN",
192
+ "VALUE_TOO_DEEP",
193
+ "VALUE_TOO_LARGE",
194
+ "VALUE_UNSAFE_KEY",
195
+ "VERIFICATION_BEFORE_ACTION",
196
+ "VOCABULARY_FIELD_UNDECLARED",
197
+ ]);
198
+
199
+ const REGISTRY = new Set(REFUSAL_CODES);
200
+
201
+ export const UNDECLARED_REFUSAL_CODE = "REFUSAL_CODE_UNDECLARED";
202
+
203
+ // A REFUSAL CODE IS A MACHINE TOKEN, AND THAT IS THE WHOLE OF WHAT IT MAY BE.
204
+ //
205
+ // The codes above are the ones THIS package emits. They are not the only codes
206
+ // that may exist, because several public helpers — `snapshotRecord`,
207
+ // `exactKeys`, `entryRecord`, `entryArray`, `assertExportSafeText`,
208
+ // `assertVocabularyValue` — take the CALLER'S OWN code so that an external
209
+ // consumer's boundary answers in its own vocabulary. That is a deliberate part
210
+ // of the contract and predates this round.
211
+ //
212
+ // What was never part of the contract is a code that is not a token at all. The
213
+ // the review drove sentences, JSON fragments and caller-authored text
214
+ // through that parameter and read them back out of a public method. The
215
+ // alphabet below is the same positive-allowlist decision
216
+ // `vocabulary-charset.mjs` records for every other machine vocabulary: ASCII
217
+ // UPPER_SNAKE, bounded, anchored. It admits `MY_CODE`; it cannot express a
218
+ // sentence, a path, a JSON blob, a lowercase word or any character outside
219
+ // `[A-Z0-9_]`, so free text cannot survive contact with it.
220
+ //
221
+ // `runUntrusted` below is the control-flow half: a foreign callback cannot use
222
+ // even a well-formed token to choose the enclosing boundary's result.
223
+ const MACHINE_CODE = /^[A-Z][A-Z0-9_]{0,63}$/u;
224
+
225
+ // Capture the small set of primordials this boundary depends on. A frozen
226
+ // refusal object is not sufficient when membership or trace construction can
227
+ // be redirected through mutable ambient prototypes after module load.
228
+ const setHas = Function.prototype.call.bind(Set.prototype.has);
229
+ const nativePromiseThen = Function.prototype.call.bind(Promise.prototype.then);
230
+ const regexpTest = Function.prototype.call.bind(RegExp.prototype.test);
231
+ const reflectGet = Reflect.get;
232
+ const defineProperty = Object.defineProperty;
233
+ const freeze = Object.freeze;
234
+
235
+ export class SdkContractError extends Error {
236
+ constructor(code) {
237
+ // NORMALIZED BEFORE `super`, so there is no window in which an undeclared
238
+ // string is the message. A non-string, a template-built string carrying
239
+ // caller text, a subclass's chosen code: all become the one fallback.
240
+ const declared = typeof code === "string" && (setHas(REGISTRY, code) || regexpTest(MACHINE_CODE, code))
241
+ ? code
242
+ : UNDECLARED_REFUSAL_CODE;
243
+ const message = `Observa SDK contract refused (${declared})`;
244
+ super(message);
245
+ // NON-WRITABLE AND NON-CONFIGURABLE. A subclass runs its own constructor
246
+ // body after `super()` returns, and the round-2 review used exactly that to
247
+ // overwrite `.message` with caller-authored text on an object the
248
+ // boundary then rethrew. Defining the three fields as immutable own
249
+ // properties means the shape survives subclassing.
250
+ defineProperty(this, "name", {
251
+ value: "SdkContractError", writable: false, configurable: false, enumerable: false,
252
+ });
253
+ defineProperty(this, "code", {
254
+ value: declared, writable: false, configurable: false, enumerable: true,
255
+ });
256
+ defineProperty(this, "message", {
257
+ value: message, writable: false, configurable: false, enumerable: false,
258
+ });
259
+ // Engine-generated traces and Error.prepareStackTrace are caller-controlled
260
+ // detail channels. The contract therefore fixes the detail fields
261
+ // to code-only values instead of retaining a lazy trace or chained value.
262
+ defineProperty(this, "stack", {
263
+ value: message, writable: false, configurable: false, enumerable: false,
264
+ });
265
+ defineProperty(this, "cause", {
266
+ value: undefined, writable: false, configurable: false, enumerable: false,
267
+ });
268
+ // No later `defineProperty`, subclass body, or captured-reference replay
269
+ // may add an attacker-bearing field to an emitted refusal.
270
+ freeze(this);
271
+ }
272
+ }
273
+
274
+ // Raise a code-only refusal. This public function intentionally carries NO
275
+ // durable "trusted" brand: a caller can capture any public object and replay
276
+ // it later, so object lifetime is not control-flow provenance.
277
+ export function refuse(code) {
278
+ throw new SdkContractError(code);
279
+ }
280
+
281
+ // A synchronous H1 boundary cannot safely admit a native Promise: an async
282
+ // profile/clock can reject later with caller text after the synchronous caller
283
+ // has already received a refusal. Attach a rejection sink first, then refuse
284
+ // synchronously. The private-slot call does not dispatch through a caller's
285
+ // `.then` property.
286
+ function isNativePromiseAndObserve(value) {
287
+ if (value === null || (typeof value !== "object" && typeof value !== "function")) return false;
288
+ try {
289
+ const observed = nativePromiseThen(value, undefined, () => undefined);
290
+ nativePromiseThen(observed, undefined, () => undefined);
291
+ return true;
292
+ } catch {
293
+ return false;
294
+ }
295
+ }
296
+
297
+ // A non-native thenable is also outside this synchronous contract. Reading
298
+ // `then` is fenced here; it is never invoked. Native Promises were handled
299
+ // above so a rejected one is observed before the closed refusal leaves.
300
+ function hasThenableShape(value) {
301
+ if (value === null || (typeof value !== "object" && typeof value !== "function")) return false;
302
+ try {
303
+ return typeof reflectGet(value, "then") === "function";
304
+ } catch {
305
+ return true;
306
+ }
307
+ }
308
+
309
+ // Run foreign code and collapse everything it can do to one closed code.
310
+ export function runUntrusted(code, fn, thenableCode = code) {
311
+ let result;
312
+ try {
313
+ result = fn();
314
+ } catch {
315
+ refuse(code);
316
+ }
317
+ if (isNativePromiseAndObserve(result) || hasThenableShape(result)) refuse(thenableCode);
318
+ return result;
319
+ }
320
+
321
+ // Retained as a compatibility query for callers/tests. No public error
322
+ // object is ever a trusted control-flow token; public callers must receive a
323
+ // freshly closed boundary code instead.
324
+ export function isContractRaised(value) {
325
+ void value;
326
+ return false;
327
+ }
328
+
329
+ // Compatibility wrapper for a public callback boundary. It intentionally
330
+ // recodes *every* thrown value, including a previously emitted SDK error:
331
+ // public object identity cannot prove trustworthy control-flow provenance.
332
+ export function refuseOnRawThrow(code, fn) {
333
+ return runUntrusted(code, fn);
334
+ }
@@ -0,0 +1,141 @@
1
+ // SMALL PRIMITIVES FOR SECURITY-RELEVANT, ALREADY-VALIDATED DATA.
2
+ //
3
+ // A frozen Array protects its own elements; it does not freeze
4
+ // Array.prototype. Capability certification and boundary materialization
5
+ // must therefore not dispatch through mutable inherited helpers such as
6
+ // `.includes`, `Symbol.iterator`, spread, or `.push` after validation.
7
+ //
8
+ // These helpers deliberately use indexed access only. Their callers either
9
+ // hold a contract-owned frozen array or have just materialized a caller array
10
+ // with `entryArray`; they are not general-purpose validators for arbitrary
11
+ // objects.
12
+
13
+ const NativeArray = Array;
14
+ const freeze = Object.freeze;
15
+ const isSafeInteger = Number.isSafeInteger;
16
+
17
+ export function stableArrayHas(values, wanted) {
18
+ const length = values.length;
19
+ for (let index = 0; index < length; index += 1) {
20
+ const value = values[index];
21
+ // SameValueZero is the membership semantics Array#includes supplies,
22
+ // without dynamically dispatching to a mutable prototype method.
23
+ if (value === wanted || (value !== value && wanted !== wanted)) return true;
24
+ }
25
+ return false;
26
+ }
27
+
28
+ // Closed grammar checks sometimes use a short string as their vocabulary.
29
+ // Keep those checks off mutable String.prototype for the same reason closed
30
+ // array vocabularies stay off Array.prototype.includes.
31
+ export function stableStringHas(text, wanted) {
32
+ for (let index = 0; index < text.length; index += 1) {
33
+ if (text[index] === wanted) return true;
34
+ }
35
+ return false;
36
+ }
37
+
38
+ export function stableArrayCopy(values) {
39
+ const length = values.length;
40
+ const copy = new NativeArray(length);
41
+ for (let index = 0; index < length; index += 1) copy[index] = values[index];
42
+ return freeze(copy);
43
+ }
44
+
45
+ // SELECT AND PROJECT WITHOUT DISPATCHING THROUGH `Array.prototype` — FINAL
46
+ // CLOSURE, INDEPENDENT REVIEW #1.
47
+ //
48
+ // `Array.prototype.filter` is ambient mutable behaviour, and this package's own
49
+ // threat model has semi-trusted domain profiles executing in-process (the same
50
+ // class already closed for `GOVERNANCE_WORDS` and for `ID_PATTERNS`, H1R2-06).
51
+ // `EvidenceLog.applicableEvidence()` was `Object.freeze(this.#records.filter(…))`
52
+ // — it FROZE A VALUE IT DID NOT OWN, on the assumption that `filter` returns a
53
+ // fresh array. One assignment inside a profile callback (`Array.prototype.filter
54
+ // = function () { return this; }`) made it return the receiver, so
55
+ // `Object.freeze` froze the log's private `#records`.
56
+ //
57
+ // The result was the worst outcome this contract names: a raw uncoded
58
+ // `TypeError` out of public node methods AND an append-only log destroyed
59
+ // beyond remediation — restoring the prototype does not help, because
60
+ // `#records` stays frozen. Verified: `recordSourceUnavailable` still threw a raw
61
+ // TypeError after `Array.prototype.filter` had been fully restored.
62
+ //
63
+ // The same mutable dispatch sat on the evidence-admissibility decision path and
64
+ // the false-VERIFIED gate in the node. These two helpers are the stable
65
+ // replacements: indexed access only, and the array they freeze is one they
66
+ // constructed themselves.
67
+ export function stableFilter(values, predicate) {
68
+ const length = values.length;
69
+ const out = new NativeArray();
70
+ let count = 0;
71
+ for (let index = 0; index < length; index += 1) {
72
+ const value = values[index];
73
+ if (predicate(value, index)) {
74
+ out[count] = value;
75
+ count += 1;
76
+ }
77
+ }
78
+ out.length = count;
79
+ return out;
80
+ }
81
+
82
+ export function stableMap(values, project) {
83
+ const length = values.length;
84
+ const out = new NativeArray(length);
85
+ for (let index = 0; index < length; index += 1) out[index] = project(values[index], index);
86
+ return out;
87
+ }
88
+
89
+ // Sort a verified, string-valued snapshot without dispatching through the
90
+ // mutable Array prototype. The small insertion sort is intentional: these
91
+ // callers compare declaration keys, not bulk user data, and the important
92
+ // property is that the ordering mechanism cannot be replaced after import.
93
+ export function stableSortedStringCopy(values) {
94
+ const copy = new NativeArray(values.length);
95
+ for (let index = 0; index < values.length; index += 1) copy[index] = values[index];
96
+ for (let index = 1; index < copy.length; index += 1) {
97
+ const value = copy[index];
98
+ let cursor = index - 1;
99
+ while (cursor >= 0 && copy[cursor] > value) {
100
+ copy[cursor + 1] = copy[cursor];
101
+ cursor -= 1;
102
+ }
103
+ copy[cursor + 1] = value;
104
+ }
105
+ return freeze(copy);
106
+ }
107
+
108
+ // Integer millisecond timestamps are a bounded numeric domain, not every
109
+ // Number.isInteger value. JavaScript accepts integers that cannot represent
110
+ // adjacent milliseconds; allowing them into durable history makes ordinary
111
+ // deadline arithmetic non-deterministic at the precision boundary.
112
+ export function isNonNegativeSafeInteger(value) {
113
+ return isSafeInteger(value) && value >= 0;
114
+ }
115
+
116
+ export function isPositiveSafeInteger(value) {
117
+ return isSafeInteger(value) && value > 0;
118
+ }
119
+
120
+ export function assertNonNegativeSafeInteger(value, code, refuse) {
121
+ if (!isNonNegativeSafeInteger(value)) refuse(code);
122
+ return value;
123
+ }
124
+
125
+ export function assertPositiveSafeInteger(value, code, refuse) {
126
+ if (!isPositiveSafeInteger(value)) refuse(code);
127
+ return value;
128
+ }
129
+
130
+ // Checked arithmetic for the millisecond-number API. The equality check is
131
+ // intentional: it detects IEEE-754 rounding even in a case where the rounded
132
+ // result happens to remain numerically greater than the start.
133
+ export function addSafeMilliseconds(start, windowMs, code, refuse) {
134
+ assertNonNegativeSafeInteger(start, code, refuse);
135
+ assertPositiveSafeInteger(windowMs, code, refuse);
136
+ const deadline = start + windowMs;
137
+ if (!isSafeInteger(deadline) || deadline <= start || deadline - start !== windowMs) {
138
+ refuse(code);
139
+ }
140
+ return deadline;
141
+ }