@tangle-network/agent-eval 0.133.0 → 0.133.2

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 (100) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +1 -1
  3. package/dist/agent-profile-cell-OhuTee9n.js +335 -0
  4. package/dist/agent-profile-cell-OhuTee9n.js.map +1 -0
  5. package/dist/{analyze-runs-BjPn_fOS.js → analyze-runs-B-afTpCv.js} +6 -19
  6. package/dist/analyze-runs-B-afTpCv.js.map +1 -0
  7. package/dist/{analyze-runs-DZr7JW-m.d.ts → analyze-runs-BmX-h_yn.d.ts} +3 -3
  8. package/dist/{analyze-runs-DZr7JW-m.d.ts.map → analyze-runs-BmX-h_yn.d.ts.map} +1 -1
  9. package/dist/{baseline-HsBvw_dk.js → baseline-DcX5hQDv.js} +2 -70
  10. package/dist/{baseline-HsBvw_dk.js.map → baseline-DcX5hQDv.js.map} +1 -1
  11. package/dist/baseline-hG3K85h4.d.ts.map +1 -1
  12. package/dist/benchmarks/index.d.ts +1 -1
  13. package/dist/benchmarks/index.js +1 -1
  14. package/dist/{benchmarks-DncrVrSr.js → benchmarks-CJr1H1_a.js} +3 -3
  15. package/dist/{benchmarks-DncrVrSr.js.map → benchmarks-CJr1H1_a.js.map} +1 -1
  16. package/dist/builder-eval/index.js +1 -1
  17. package/dist/campaign/index.d.ts +3 -3
  18. package/dist/campaign/index.js +2 -2
  19. package/dist/{campaign-CyQMCCEU.js → campaign-BJjn1rhw.js} +19 -13
  20. package/dist/{campaign-CyQMCCEU.js.map → campaign-BJjn1rhw.js.map} +1 -1
  21. package/dist/{client-D4F9hdzR.d.ts → client-COvaLoQG.d.ts} +2 -2
  22. package/dist/{client-D4F9hdzR.d.ts.map → client-COvaLoQG.d.ts.map} +1 -1
  23. package/dist/contract/index.d.ts +3 -3
  24. package/dist/contract/index.js +5 -5
  25. package/dist/control.js +1 -1
  26. package/dist/{eval-campaign-CYkUME2T.js → eval-campaign-DXhpZghy.js} +4 -5
  27. package/dist/{eval-campaign-CYkUME2T.js.map → eval-campaign-DXhpZghy.js.map} +1 -1
  28. package/dist/hosted/index.d.ts +1 -1
  29. package/dist/index-3cdlURSk.d.ts.map +1 -1
  30. package/dist/{index-CiP2DRCj.d.ts → index-BREtv3ZZ.d.ts} +3 -3
  31. package/dist/{index-CiP2DRCj.d.ts.map → index-BREtv3ZZ.d.ts.map} +1 -1
  32. package/dist/{index-B_eUhU9K.d.ts → index-C7Wue8R6.d.ts} +28 -5
  33. package/dist/{index-B_eUhU9K.d.ts.map → index-C7Wue8R6.d.ts.map} +1 -1
  34. package/dist/{index-BAvgST_9.d.ts → index-nhIYz9hn.d.ts} +67 -9
  35. package/dist/index-nhIYz9hn.d.ts.map +1 -0
  36. package/dist/index.d.ts +7 -7
  37. package/dist/index.js +25 -18
  38. package/dist/index.js.map +1 -1
  39. package/dist/ledger-core/index.d.ts +2 -2
  40. package/dist/ledger-core/index.js +2 -2
  41. package/dist/ledger-core-CPZfcrC2.js +620 -0
  42. package/dist/ledger-core-CPZfcrC2.js.map +1 -0
  43. package/dist/meta-eval/index.js +2 -2
  44. package/dist/{mint-D5_87M5L.js → mint-BvkwcYZU.js} +2 -2
  45. package/dist/{mint-D5_87M5L.js.map → mint-BvkwcYZU.js.map} +1 -1
  46. package/dist/openapi.json +1 -1
  47. package/dist/{opencode-sqlite-BGrHeDu3.js → opencode-sqlite-8r6WUfHc.js} +2 -3
  48. package/dist/opencode-sqlite-8r6WUfHc.js.map +1 -0
  49. package/dist/{paired-arms-D9D0wXj2.js → paired-arms-6XItKzd1.js} +2 -2
  50. package/dist/{paired-arms-D9D0wXj2.js.map → paired-arms-6XItKzd1.js.map} +1 -1
  51. package/dist/pipelines/index.js +2 -2
  52. package/dist/profile-cell.js +1 -242
  53. package/dist/{propose-review-control-Bqb7daEJ.js → propose-review-control-SQ-n9-We.js} +2 -2
  54. package/dist/{propose-review-control-Bqb7daEJ.js.map → propose-review-control-SQ-n9-We.js.map} +1 -1
  55. package/dist/{release-report-DfmKSIEE.d.ts → release-report-CjHWa8Ia.d.ts} +2 -2
  56. package/dist/{release-report-DfmKSIEE.d.ts.map → release-report-CjHWa8Ia.d.ts.map} +1 -1
  57. package/dist/{release-report-oWt9f2k-.js → release-report-wuilQkvK.js} +3 -3
  58. package/dist/{release-report-oWt9f2k-.js.map → release-report-wuilQkvK.js.map} +1 -1
  59. package/dist/{replay-BMR4TEYY.js → replay-CJfGLdx4.js} +3 -3
  60. package/dist/{replay-BMR4TEYY.js.map → replay-CJfGLdx4.js.map} +1 -1
  61. package/dist/reporting.d.ts +2 -2
  62. package/dist/reporting.js +4 -4
  63. package/dist/{researcher-DMimgHtN.d.ts → researcher-CbSKhK8z.d.ts} +2 -2
  64. package/dist/{researcher-DMimgHtN.d.ts.map → researcher-CbSKhK8z.d.ts.map} +1 -1
  65. package/dist/{reward-hacking-BEvjdUtD.js → reward-hacking-Dl2UBzej.js} +3 -3
  66. package/dist/{reward-hacking-BEvjdUtD.js.map → reward-hacking-Dl2UBzej.js.map} +1 -1
  67. package/dist/rl.d.ts +1 -1
  68. package/dist/rl.js +6 -6
  69. package/dist/rollout/index.js +3 -3
  70. package/dist/{rollout-VEo41J0N.js → rollout-CreDz__7.js} +3 -3
  71. package/dist/{rollout-VEo41J0N.js.map → rollout-CreDz__7.js.map} +1 -1
  72. package/dist/{rubric-predictive-validity-B3xmbmS1.js → rubric-predictive-validity-QG7ydk0s.js} +2 -2
  73. package/dist/{rubric-predictive-validity-B3xmbmS1.js.map → rubric-predictive-validity-QG7ydk0s.js.map} +1 -1
  74. package/dist/{run-record-CN8Zd21B.js → run-record-BIwU2wdV.js} +2 -2
  75. package/dist/{run-record-CN8Zd21B.js.map → run-record-BIwU2wdV.js.map} +1 -1
  76. package/dist/{skillopt-optimization-method-DgN7U9iR.js → skillopt-optimization-method-CF6a327Q.js} +8 -4
  77. package/dist/{skillopt-optimization-method-DgN7U9iR.js.map → skillopt-optimization-method-CF6a327Q.js.map} +1 -1
  78. package/dist/{skillopt-optimization-method-CAASpcS3.d.ts → skillopt-optimization-method-wHF5xsUv.d.ts} +2 -2
  79. package/dist/{skillopt-optimization-method-CAASpcS3.d.ts.map → skillopt-optimization-method-wHF5xsUv.d.ts.map} +1 -1
  80. package/dist/{statistics-CnnxdpOg.js → statistics-DWM_AyLe.js} +90 -76
  81. package/dist/statistics-DWM_AyLe.js.map +1 -0
  82. package/dist/statistics-DbvkkDPa.d.ts.map +1 -1
  83. package/dist/{summary-report-DnUcjVpV.d.ts → summary-report-CFnQgNfg.d.ts} +2 -2
  84. package/dist/{summary-report-DnUcjVpV.d.ts.map → summary-report-CFnQgNfg.d.ts.map} +1 -1
  85. package/dist/{summary-report-BNs5nmXI.js → summary-report-Ci17nIdU.js} +4 -4
  86. package/dist/{summary-report-BNs5nmXI.js.map → summary-report-Ci17nIdU.js.map} +1 -1
  87. package/dist/supervisor-run/index.js +1 -1
  88. package/dist/{supervisor-run-_lnTLM3z.js → supervisor-run-B7lUGoyZ.js} +2 -2
  89. package/dist/{supervisor-run-_lnTLM3z.js.map → supervisor-run-B7lUGoyZ.js.map} +1 -1
  90. package/dist/traces.js +1 -1
  91. package/package.json +1 -1
  92. package/dist/analyze-runs-BjPn_fOS.js.map +0 -1
  93. package/dist/index-BAvgST_9.d.ts.map +0 -1
  94. package/dist/ledger-core-eqaI3PCD.js +0 -388
  95. package/dist/ledger-core-eqaI3PCD.js.map +0 -1
  96. package/dist/opencode-sqlite-BGrHeDu3.js.map +0 -1
  97. package/dist/pre-registration-DakwTRXk.js +0 -96
  98. package/dist/pre-registration-DakwTRXk.js.map +0 -1
  99. package/dist/profile-cell.js.map +0 -1
  100. package/dist/statistics-CnnxdpOg.js.map +0 -1
@@ -1,2 +1,2 @@
1
- import { C as AtomicFileLockUnavailable, S as AtomicFileLockOwner, T as tryAcquireAtomicFileLock, _ as withLedgerFileLock, a as LedgerEventBase, b as AtomicFileLockError, c as LedgerLineContext, d as canonicalString, f as hashCanonical, g as tryWithLedgerFileLock, h as appendLedgerLine, i as LedgerEntryOf, l as LedgerProjector, m as LedgerFileContext, n as LedgerAppendResult, o as LedgerHash, p as FileLockResult, r as LedgerChainFields, s as LedgerJournalCodec, t as FileLedgerJournal, u as LedgerReplayResult, v as AtomicFileLock, w as probeAtomicFileLock, x as AtomicFileLockOptions, y as AtomicFileLockAcquisition } from "../index-BAvgST_9.js";
2
- export { type AtomicFileLock, type AtomicFileLockAcquisition, AtomicFileLockError, type AtomicFileLockOptions, type AtomicFileLockOwner, type AtomicFileLockUnavailable, FileLedgerJournal, type FileLockResult, type LedgerAppendResult, type LedgerChainFields, type LedgerEntryOf, type LedgerEventBase, type LedgerFileContext, type LedgerHash, type LedgerJournalCodec, type LedgerLineContext, type LedgerProjector, type LedgerReplayResult, appendLedgerLine, canonicalString, hashCanonical, probeAtomicFileLock, tryAcquireAtomicFileLock, tryWithLedgerFileLock, withLedgerFileLock };
1
+ import { A as AtomicFileLockOptions, C as LedgerCanonicalizationError, D as AtomicFileLock, E as hashCanonical, M as AtomicFileLockUnavailable, N as probeAtomicFileLock, O as AtomicFileLockAcquisition, P as tryAcquireAtomicFileLock, S as LEDGER_HASH_PATTERN, T as canonicalString, _ as LedgerFileContext, a as LedgerChainFields, b as withLedgerFileLock, c as LedgerJournalCodec, d as LedgerReplayResult, f as LedgerAnchoredEntry, g as FileLockResult, h as verifyEntriesAgainstTrustedHead, i as LedgerAppendResult, j as AtomicFileLockOwner, k as AtomicFileLockError, l as LedgerLineContext, m as trustedHeadPathFor, n as FileLedgerJournalOptions, o as LedgerEntryOf, p as LedgerTrustedHead, r as LedgerAppendOptions, s as LedgerEventBase, t as FileLedgerJournal, u as LedgerProjector, v as appendLedgerLine, w as LedgerHash, x as writeLedgerFileAtomically, y as tryWithLedgerFileLock } from "../index-nhIYz9hn.js";
2
+ export { type AtomicFileLock, type AtomicFileLockAcquisition, AtomicFileLockError, type AtomicFileLockOptions, type AtomicFileLockOwner, type AtomicFileLockUnavailable, FileLedgerJournal, type FileLedgerJournalOptions, type FileLockResult, LEDGER_HASH_PATTERN, type LedgerAnchoredEntry, type LedgerAppendOptions, type LedgerAppendResult, LedgerCanonicalizationError, type LedgerChainFields, type LedgerEntryOf, type LedgerEventBase, type LedgerFileContext, type LedgerHash, type LedgerJournalCodec, type LedgerLineContext, type LedgerProjector, type LedgerReplayResult, type LedgerTrustedHead, appendLedgerLine, canonicalString, hashCanonical, probeAtomicFileLock, trustedHeadPathFor, tryAcquireAtomicFileLock, tryWithLedgerFileLock, verifyEntriesAgainstTrustedHead, withLedgerFileLock, writeLedgerFileAtomically };
@@ -1,2 +1,2 @@
1
- import { a as tryWithLedgerFileLock, c as probeAtomicFileLock, i as appendLedgerLine, l as tryAcquireAtomicFileLock, n as canonicalString, o as withLedgerFileLock, r as hashCanonical, s as AtomicFileLockError, t as FileLedgerJournal } from "../ledger-core-eqaI3PCD.js";
2
- export { AtomicFileLockError, FileLedgerJournal, appendLedgerLine, canonicalString, hashCanonical, probeAtomicFileLock, tryAcquireAtomicFileLock, tryWithLedgerFileLock, withLedgerFileLock };
1
+ import { a as tryWithLedgerFileLock, c as LEDGER_HASH_PATTERN, d as hashCanonical, f as AtomicFileLockError, i as appendLedgerLine, l as LedgerCanonicalizationError, m as tryAcquireAtomicFileLock, n as trustedHeadPathFor, o as withLedgerFileLock, p as probeAtomicFileLock, r as verifyEntriesAgainstTrustedHead, s as writeLedgerFileAtomically, t as FileLedgerJournal, u as canonicalString } from "../ledger-core-CPZfcrC2.js";
2
+ export { AtomicFileLockError, FileLedgerJournal, LEDGER_HASH_PATTERN, LedgerCanonicalizationError, appendLedgerLine, canonicalString, hashCanonical, probeAtomicFileLock, trustedHeadPathFor, tryAcquireAtomicFileLock, tryWithLedgerFileLock, verifyEntriesAgainstTrustedHead, withLedgerFileLock, writeLedgerFileAtomically };
@@ -0,0 +1,620 @@
1
+ import { s as ValidationError } from "./errors-8YnH8WlF.js";
2
+ import { t as Mutex } from "./concurrency-MUjT7VjM.js";
3
+ import { closeSync, constants, existsSync, fsyncSync, linkSync, mkdirSync, openSync, readFileSync, renameSync, unlinkSync, writeFileSync, writeSync } from "node:fs";
4
+ import { dirname, resolve } from "node:path";
5
+ import { randomUUID } from "node:crypto";
6
+ import { hostname } from "node:os";
7
+ import { canonicalCandidateBytes, canonicalCandidateJson, sha256Bytes } from "@tangle-network/agent-interface";
8
+ //#region src/ledger-core/atomic-file-lock.ts
9
+ /** Crash-safe filesystem lock shared by campaign persistence and run exclusion. */
10
+ var AtomicFileLockError = class extends Error {
11
+ name = "AtomicFileLockError";
12
+ };
13
+ /** Report whether another lock path prevents acquisition without modifying it. */
14
+ function probeAtomicFileLock(options) {
15
+ if (existsSync(recoveryPath(options.lockPath))) return {
16
+ acquired: false,
17
+ reason: "recovery"
18
+ };
19
+ const state = ownerState(options.lockPath);
20
+ if (state.state === "held") return {
21
+ acquired: false,
22
+ reason: "held",
23
+ holder: state.owner
24
+ };
25
+ return null;
26
+ }
27
+ /**
28
+ * Try to acquire a complete, uniquely-owned lock inode.
29
+ *
30
+ * A hard link publishes fully-written owner metadata atomically. Stale-owner
31
+ * removal is serialized so one reclaimer cannot delete a new owner's lock.
32
+ */
33
+ function tryAcquireAtomicFileLock(options) {
34
+ const pid = options.pid ?? process.pid;
35
+ if (!Number.isSafeInteger(pid) || pid <= 0) throw new AtomicFileLockError("atomic file lock pid must be a positive integer");
36
+ const owner = {
37
+ pid,
38
+ host: hostname(),
39
+ nonce: randomUUID()
40
+ };
41
+ for (let attempt = 0; attempt < 8; attempt += 1) {
42
+ if (existsSync(recoveryPath(options.lockPath))) return {
43
+ acquired: false,
44
+ reason: "recovery"
45
+ };
46
+ if (tryLinkOwner(options.lockPath, owner, `acquire.${attempt}`)) return acquired(options.lockPath, owner);
47
+ const holder = ownerState(options.lockPath);
48
+ if (holder.state === "missing") continue;
49
+ if (holder.state === "held") return {
50
+ acquired: false,
51
+ reason: "held",
52
+ holder: holder.owner
53
+ };
54
+ const reclaimPath = recoveryPath(options.lockPath);
55
+ if (!tryLinkOwner(reclaimPath, owner, `reclaim.${attempt}`)) return {
56
+ acquired: false,
57
+ reason: "recovery"
58
+ };
59
+ try {
60
+ const current = ownerState(options.lockPath);
61
+ if (current.state === "held") return {
62
+ acquired: false,
63
+ reason: "held",
64
+ holder: current.owner
65
+ };
66
+ if (current.state === "stale") {
67
+ const tombstone = `${options.lockPath}.stale.${owner.nonce}.${attempt}`;
68
+ try {
69
+ renameSync(options.lockPath, tombstone);
70
+ unlinkSync(tombstone);
71
+ } catch (error) {
72
+ if (!isMissing(error)) throw error;
73
+ }
74
+ }
75
+ if (tryLinkOwner(options.lockPath, owner, `recovered.${attempt}`)) return acquired(options.lockPath, owner);
76
+ } finally {
77
+ releaseOwnedPath(reclaimPath, owner);
78
+ }
79
+ }
80
+ throw new AtomicFileLockError(`could not acquire atomic file lock ${options.lockPath}`);
81
+ }
82
+ function acquired(lockPath, owner) {
83
+ return {
84
+ acquired: true,
85
+ lock: {
86
+ owner,
87
+ release: () => releaseOwnedPath(lockPath, owner)
88
+ }
89
+ };
90
+ }
91
+ function tryLinkOwner(lockPath, owner, suffix) {
92
+ const ownerPath = `${lockPath}.${owner.pid}.${owner.nonce}.${suffix}.owner`;
93
+ const descriptor = openSync(ownerPath, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY, 384);
94
+ try {
95
+ writeFileSync(descriptor, `${canonicalOwner(owner)}\n`, "utf8");
96
+ fsyncSync(descriptor);
97
+ } catch (error) {
98
+ closeSync(descriptor);
99
+ unlinkIfExists(ownerPath);
100
+ throw error;
101
+ }
102
+ closeSync(descriptor);
103
+ try {
104
+ linkSync(ownerPath, lockPath);
105
+ return true;
106
+ } catch (error) {
107
+ if (!isAlreadyExists(error)) throw error;
108
+ return false;
109
+ } finally {
110
+ unlinkIfExists(ownerPath);
111
+ }
112
+ }
113
+ function ownerState(lockPath) {
114
+ const owner = readOwner(lockPath);
115
+ if (!owner) return { state: "missing" };
116
+ if (owner.host !== hostname()) return {
117
+ state: "held",
118
+ owner
119
+ };
120
+ try {
121
+ process.kill(owner.pid, 0);
122
+ return {
123
+ state: "held",
124
+ owner
125
+ };
126
+ } catch (error) {
127
+ if (isNoSuchProcess(error)) return {
128
+ state: "stale",
129
+ owner
130
+ };
131
+ return {
132
+ state: "held",
133
+ owner
134
+ };
135
+ }
136
+ }
137
+ function readOwner(lockPath) {
138
+ let contents;
139
+ try {
140
+ contents = readFileSync(lockPath, "utf8").trim();
141
+ } catch (error) {
142
+ if (isMissing(error)) return void 0;
143
+ throw error;
144
+ }
145
+ let value;
146
+ try {
147
+ value = JSON.parse(contents);
148
+ } catch (error) {
149
+ throw invalidOwner(lockPath, error);
150
+ }
151
+ if (!isOwner(value)) throw invalidOwner(lockPath);
152
+ return value;
153
+ }
154
+ function isOwner(value) {
155
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
156
+ const record = value;
157
+ return Object.keys(record).length === 3 && Number.isSafeInteger(record.pid) && Number(record.pid) > 0 && typeof record.host === "string" && record.host.trim().length > 0 && typeof record.nonce === "string" && record.nonce.trim().length > 0;
158
+ }
159
+ function invalidOwner(lockPath, cause) {
160
+ const error = new AtomicFileLockError(`atomic file lock has an invalid owner (${lockPath}); refusing unsafe recovery`);
161
+ if (cause !== void 0) Object.defineProperty(error, "cause", { value: cause });
162
+ return error;
163
+ }
164
+ function releaseOwnedPath(lockPath, owner) {
165
+ const current = readOwner(lockPath);
166
+ if (!current) return;
167
+ if (canonicalOwner(current) !== canonicalOwner(owner)) throw new AtomicFileLockError(`atomic file lock owner changed before release (${lockPath})`);
168
+ try {
169
+ unlinkSync(lockPath);
170
+ } catch (error) {
171
+ if (!isMissing(error)) throw error;
172
+ }
173
+ }
174
+ function canonicalOwner(owner) {
175
+ return JSON.stringify({
176
+ host: owner.host,
177
+ nonce: owner.nonce,
178
+ pid: owner.pid
179
+ });
180
+ }
181
+ function recoveryPath(lockPath) {
182
+ return `${lockPath}.reclaim`;
183
+ }
184
+ function isAlreadyExists(error) {
185
+ return error instanceof Error && "code" in error && error.code === "EEXIST";
186
+ }
187
+ function isMissing(error) {
188
+ return error instanceof Error && "code" in error && error.code === "ENOENT";
189
+ }
190
+ function isNoSuchProcess(error) {
191
+ return error instanceof Error && "code" in error && error.code === "ESRCH";
192
+ }
193
+ function unlinkIfExists(path) {
194
+ try {
195
+ unlinkSync(path);
196
+ } catch (error) {
197
+ if (!isMissing(error)) throw error;
198
+ }
199
+ }
200
+ //#endregion
201
+ //#region src/ledger-core/canonical.ts
202
+ /**
203
+ * Canonical bytes and digests for durable journal rows.
204
+ *
205
+ * Serialization is RFC 8785 through `@tangle-network/agent-interface` — the
206
+ * stack's single identity scheme — so a value carries the same digest in every
207
+ * package that produces or verifies one.
208
+ *
209
+ * Values canonical JSON cannot represent faithfully are refused, never coerced.
210
+ * `NaN`, `Infinity`, `undefined`, class instances, cycles, and unpaired
211
+ * surrogates each have a lossy encoding that maps distinct records onto one
212
+ * digest, and in a hash chain the digest IS the record's identity: a coercion
213
+ * there is a collision, not a convenience.
214
+ */
215
+ /** Shape of every ledger digest: `sha256:` followed by 64 lowercase hex chars. */
216
+ const LEDGER_HASH_PATTERN = /^sha256:[a-f0-9]{64}$/;
217
+ /** A value has no faithful canonical-JSON form, so it cannot be stored or hashed. */
218
+ var LedgerCanonicalizationError = class extends ValidationError {};
219
+ /** Canonical JSON encoding (RFC 8785): object keys sorted by UTF-16 code unit,
220
+ * recursively. This is the byte form that is hashed and stored, so equal values
221
+ * always encode identically. */
222
+ function canonicalString(value) {
223
+ try {
224
+ return canonicalCandidateJson(value);
225
+ } catch (error) {
226
+ throw canonicalizationError(value, error);
227
+ }
228
+ }
229
+ function hashCanonical(value) {
230
+ try {
231
+ return sha256Bytes(canonicalCandidateBytes(value));
232
+ } catch (error) {
233
+ throw canonicalizationError(value, error);
234
+ }
235
+ }
236
+ /** Name the offending value and path so a refusal is actionable. The RFC 8785
237
+ * encoder above is the sole authority on acceptance; this walk only explains a
238
+ * rejection it already made. */
239
+ function canonicalizationError(value, cause) {
240
+ const defect = firstNonCanonicalDefect(value, "$", /* @__PURE__ */ new Set());
241
+ return new LedgerCanonicalizationError(`value has no canonical JSON form: ${defect !== "" ? defect : cause instanceof Error ? cause.message : String(cause)}`, { cause });
242
+ }
243
+ function firstNonCanonicalDefect(value, path, ancestors) {
244
+ if (value === null) return "";
245
+ if (value === void 0) return `${path} is undefined`;
246
+ const type = typeof value;
247
+ if (type === "number") return Number.isFinite(value) ? "" : `${path} is ${String(value)}`;
248
+ if (type === "string") return isWellFormedUtf16(value) ? "" : `${path} contains an unpaired surrogate`;
249
+ if (type === "boolean") return "";
250
+ if (type !== "object") return `${path} is a ${type}`;
251
+ const object = value;
252
+ if (ancestors.has(object)) return `${path} closes a reference cycle`;
253
+ const prototype = Object.getPrototypeOf(object);
254
+ if (!Array.isArray(object) && prototype !== Object.prototype && prototype !== null) return `${path} is a ${object.constructor?.name ?? "non-plain"} instance, not plain JSON data`;
255
+ const nested = new Set(ancestors).add(object);
256
+ if (Array.isArray(object)) {
257
+ for (let index = 0; index < object.length; index += 1) {
258
+ const defect = firstNonCanonicalDefect(object[index], `${path}[${index}]`, nested);
259
+ if (defect !== "") return defect;
260
+ }
261
+ return "";
262
+ }
263
+ for (const [key, entry] of Object.entries(object)) {
264
+ if (!isWellFormedUtf16(key)) return `${path}.${key} has a key with an unpaired surrogate`;
265
+ const defect = firstNonCanonicalDefect(entry, `${path}.${key}`, nested);
266
+ if (defect !== "") return defect;
267
+ }
268
+ return "";
269
+ }
270
+ function isWellFormedUtf16(value) {
271
+ for (let index = 0; index < value.length; index += 1) {
272
+ const code = value.charCodeAt(index);
273
+ if (code >= 55296 && code <= 56319) {
274
+ const next = value.charCodeAt(index + 1);
275
+ if (!(next >= 56320 && next <= 57343)) return false;
276
+ index += 1;
277
+ } else if (code >= 56320 && code <= 57343) return false;
278
+ }
279
+ return true;
280
+ }
281
+ //#endregion
282
+ //#region src/ledger-core/journal-file.ts
283
+ /** Filesystem durability and cross-process exclusion for append-only journal files. */
284
+ /** Append one already-serialized line; the write is fsynced (file and directory)
285
+ * before returning so an acknowledged append survives a crash. */
286
+ function appendLedgerLine(path, line, context) {
287
+ mkdirSync(dirname(path), { recursive: true });
288
+ const fd = openSync(path, constants.O_CREAT | constants.O_WRONLY | constants.O_APPEND, 384);
289
+ try {
290
+ writeAll(fd, Buffer.from(line, "utf8"), context);
291
+ fsyncSync(fd);
292
+ } finally {
293
+ closeSync(fd);
294
+ }
295
+ fsyncDirectory(dirname(path));
296
+ }
297
+ /** Replace a small sidecar file whole: write a temporary sibling, fsync it,
298
+ * then rename over the target. A reader therefore sees the previous contents
299
+ * or the new ones, never a partial file. */
300
+ function writeLedgerFileAtomically(path, contents, context) {
301
+ const directory = dirname(path);
302
+ mkdirSync(directory, { recursive: true });
303
+ const temporaryPath = `${path}.tmp`;
304
+ const fd = openSync(temporaryPath, constants.O_CREAT | constants.O_WRONLY | constants.O_TRUNC, 384);
305
+ try {
306
+ writeAll(fd, Buffer.from(contents, "utf8"), context);
307
+ fsyncSync(fd);
308
+ } finally {
309
+ closeSync(fd);
310
+ }
311
+ renameSync(temporaryPath, path);
312
+ fsyncDirectory(directory);
313
+ }
314
+ function withLedgerFileLock(path, context, run) {
315
+ const result = tryWithLedgerFileLock(path, context, run);
316
+ if (!result.acquired) throw context.integrityError(`${context.subject} lock is held (${path})`);
317
+ return result.value;
318
+ }
319
+ function tryWithLedgerFileLock(path, context, run) {
320
+ mkdirSync(dirname(path), { recursive: true });
321
+ const lockPath = `${path}.lock`;
322
+ try {
323
+ const acquisition = tryAcquireAtomicFileLock({ lockPath });
324
+ if (!acquisition.acquired) return { acquired: false };
325
+ try {
326
+ return {
327
+ acquired: true,
328
+ value: run()
329
+ };
330
+ } finally {
331
+ acquisition.lock.release();
332
+ }
333
+ } catch (error) {
334
+ if (error instanceof AtomicFileLockError) throw context.integrityError(error.message, { cause: error });
335
+ throw error;
336
+ }
337
+ }
338
+ function writeAll(fd, bytes, context) {
339
+ let offset = 0;
340
+ while (offset < bytes.byteLength) {
341
+ const written = writeSync(fd, bytes, offset, bytes.byteLength - offset);
342
+ if (written <= 0) throw context.integrityError("filesystem wrote zero bytes");
343
+ offset += written;
344
+ }
345
+ }
346
+ function fsyncDirectory(path) {
347
+ const fd = openSync(path, constants.O_RDONLY);
348
+ try {
349
+ fsyncSync(fd);
350
+ } finally {
351
+ closeSync(fd);
352
+ }
353
+ }
354
+ //#endregion
355
+ //#region src/ledger-core/trusted-head.ts
356
+ /**
357
+ * Trusted head — the pin that makes deletion detectable.
358
+ *
359
+ * ## What the chain binds
360
+ *
361
+ * Every entry hashes the journal header, its sequence, the previous entry's
362
+ * hash, and its event, so editing, reordering, or removing an entry in the
363
+ * middle of a journal invalidates every hash after it. What a hash chain
364
+ * cannot bind is its own length. Dropping entries off the END leaves a shorter
365
+ * journal that is internally perfect, and rebuilding a journal from sequence 0
366
+ * with recomputed hashes produces a different, equally perfect chain. For an
367
+ * audit log of candidate registrations and promotion decisions that is the
368
+ * difference between a record of a search that happened and a record of one
369
+ * that did not.
370
+ *
371
+ * ## What the pin binds
372
+ *
373
+ * `<journal>.head` holds `{sequence, entryHash}` for one entry a writer
374
+ * actually appended, in a file separate from the journal it attests. Each
375
+ * entry hash commits to the entire prefix before it, so checking that the
376
+ * entry at exactly `sequence` still carries `entryHash` proves two things at
377
+ * once: the journal is at least `sequence + 1` entries long, and those entries
378
+ * are byte-for-byte the ones the writer saw. A journal that merely grew past
379
+ * the pin still verifies; a truncated, rolled-back, or wholesale-rewritten one
380
+ * cannot.
381
+ *
382
+ * ## What the pin does NOT defend against
383
+ *
384
+ * The pin is a second file, not a signature. A writer with access to BOTH the
385
+ * journal and its pin can rewrite both consistently, and the result verifies.
386
+ * Pinning raises the cost of undetected tampering from one file to two and
387
+ * nothing more; defending against a writer who holds both requires an anchor
388
+ * outside this store entirely — a signed head, or a digest published to an
389
+ * append-only service the tamperer does not control.
390
+ *
391
+ * Deleting the pin file downgrades a pinned journal to an unpinned one, which
392
+ * cannot be distinguished from a journal written before pinning existed. That
393
+ * is what `requireTrustedHead` is for: a journal opened under it refuses to
394
+ * read at all once a non-empty journal has no pin.
395
+ */
396
+ /** Sibling pin file for a journal — deliberately NOT the journal, so rewriting
397
+ * the log is not enough to rewrite the trust recorded about it. */
398
+ function trustedHeadPathFor(journalPath) {
399
+ return `${journalPath}.head`;
400
+ }
401
+ /** The pin, or null when this journal has never been pinned. A pin that exists
402
+ * but does not parse is corruption or tamper of the trust record itself and
403
+ * fails loudly: reporting it as "no pin" would silently downgrade what the
404
+ * journal can prove. */
405
+ function readTrustedHeadFile(path, context) {
406
+ if (!existsSync(path)) return null;
407
+ const text = readFileSync(path, "utf8");
408
+ let raw;
409
+ try {
410
+ raw = JSON.parse(text);
411
+ } catch (error) {
412
+ throw context.integrityError(`${context.subject} trusted head ${path} is not valid JSON`, { cause: error });
413
+ }
414
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) throw context.integrityError(`${context.subject} trusted head ${path} is not an object`);
415
+ const record = raw;
416
+ const keys = Object.keys(record).sort();
417
+ if (keys.length !== 2 || keys[0] !== "entryHash" || keys[1] !== "sequence") throw context.integrityError(`${context.subject} trusted head ${path} has keys [${keys.join(", ")}], expected [entryHash, sequence]`);
418
+ const { sequence, entryHash } = record;
419
+ if (typeof sequence !== "number" || !Number.isSafeInteger(sequence) || sequence < 0) throw context.integrityError(`${context.subject} trusted head ${path} sequence is not a non-negative integer`);
420
+ if (typeof entryHash !== "string" || !LEDGER_HASH_PATTERN.test(entryHash)) throw context.integrityError(`${context.subject} trusted head ${path} entryHash is not a sha256 digest`);
421
+ return {
422
+ sequence,
423
+ entryHash
424
+ };
425
+ }
426
+ /** Publish a pin by write-then-rename, so a crash leaves either the previous
427
+ * pin or the new one and never a torn file that would lock a journal out of
428
+ * its own trust record. */
429
+ function writeTrustedHeadFile(path, head, context) {
430
+ writeLedgerFileAtomically(path, `${canonicalString(head)}\n`, context);
431
+ }
432
+ /** The entry at exactly `head.sequence` must still carry `head.entryHash`.
433
+ * Entries must already have passed chain verification, which is what makes
434
+ * index and sequence interchangeable here. */
435
+ function verifyEntriesAgainstTrustedHead(entries, head, context, subject) {
436
+ const pinned = entries[head.sequence];
437
+ if (pinned === void 0) throw context.integrityError(`${subject} trusted head pins sequence ${head.sequence} (${head.entryHash}) but the journal has ${entries.length} entries — pinned history is missing (truncation, rollback, or rewrite)`);
438
+ if (pinned.sequence !== head.sequence) throw context.integrityError(`${subject} entry at index ${head.sequence} carries sequence ${pinned.sequence}; the chain was not verified before the trusted head`);
439
+ if (pinned.entryHash !== head.entryHash) throw context.integrityError(`${subject} entry at pinned sequence ${head.sequence} carries ${pinned.entryHash} but the trusted head recorded ${head.entryHash} — the journal was rewritten`);
440
+ }
441
+ //#endregion
442
+ //#region src/ledger-core/journal.ts
443
+ /**
444
+ * Generic durable append-only journal with a SHA-256 hash chain.
445
+ *
446
+ * The file format is canonical JSONL: every row is the canonical JSON encoding
447
+ * (RFC 8785, object keys sorted recursively) of one entry, and every entry
448
+ * hashes the previous entry's hash so the log is tamper-evident. Appends are
449
+ * serialized in-process per path and across processes via a lock file, fsynced
450
+ * before acknowledgement, and idempotent by `eventId`. A malformed,
451
+ * non-canonical, truncated, reordered, or conflicting log fails loudly; a bad
452
+ * row is never skipped.
453
+ *
454
+ * A hash chain binds each entry to its predecessor, which cannot prove that no
455
+ * entry was removed from the end: a valid shorter prefix is still a valid
456
+ * chain. `pinHead` records the appended entry in a sibling `<journal>.head`
457
+ * file and every read re-verifies the journal against a pin that is present,
458
+ * so truncation and wholesale rewrite are refused. `trusted-head.ts` carries
459
+ * the full threat model, including what a pin does not defend against.
460
+ *
461
+ * Domain vocabulary lives entirely in the consumer's codec: entry schema
462
+ * validation, the constant header fields stamped into every entry, the error
463
+ * taxonomy, and the state machine replayed over verified entries.
464
+ */
465
+ const journalMutexes = /* @__PURE__ */ new Map();
466
+ function mutexFor(path) {
467
+ const existing = journalMutexes.get(path);
468
+ if (existing) return existing;
469
+ const mutex = new Mutex();
470
+ journalMutexes.set(path, mutex);
471
+ return mutex;
472
+ }
473
+ /** Durable filesystem journal. Construction performs no I/O; `append` and
474
+ * `replay` validate the complete existing file under the locks. */
475
+ var FileLedgerJournal = class {
476
+ path;
477
+ /** Sibling file holding this journal's trusted head. */
478
+ trustedHeadPath;
479
+ codec;
480
+ mutex;
481
+ requireTrustedHead;
482
+ constructor(path, codec, options = {}) {
483
+ this.path = resolve(path);
484
+ this.trustedHeadPath = trustedHeadPathFor(this.path);
485
+ this.codec = codec;
486
+ this.mutex = mutexFor(this.path);
487
+ this.requireTrustedHead = options.requireTrustedHead === true;
488
+ }
489
+ async replay() {
490
+ return this.mutex.runExclusive(() => withLedgerFileLock(this.path, this.codec, () => this.replayLocked()));
491
+ }
492
+ async append(event, options = {}) {
493
+ return this.mutex.runExclusive(() => withLedgerFileLock(this.path, this.codec, () => {
494
+ const before = this.replayLocked();
495
+ const existing = before.entries.find((entry) => entry.event.eventId === event.eventId);
496
+ if (existing) {
497
+ if (canonicalString(existing.event) !== canonicalString(event)) throw this.codec.conflictError(`eventId ${event.eventId} already exists with different content`);
498
+ return {
499
+ entry: existing,
500
+ appended: false,
501
+ projection: before.projection
502
+ };
503
+ }
504
+ const material = {
505
+ ...this.codec.header,
506
+ sequence: before.entries.length,
507
+ previousHash: before.entries.at(-1)?.entryHash ?? null,
508
+ event
509
+ };
510
+ const entry = {
511
+ ...material,
512
+ entryHash: hashCanonical(material)
513
+ };
514
+ const projection = this.project([...before.entries, entry]);
515
+ appendLedgerLine(this.path, `${canonicalString(entry)}\n`, this.codec);
516
+ if (options.pinHead === true) writeTrustedHeadFile(this.trustedHeadPath, {
517
+ sequence: entry.sequence,
518
+ entryHash: entry.entryHash
519
+ }, this.codec);
520
+ return {
521
+ entry,
522
+ appended: true,
523
+ projection
524
+ };
525
+ }));
526
+ }
527
+ /** The pinned head, or null when this journal has never been pinned. */
528
+ async trustedHead() {
529
+ return this.mutex.runExclusive(() => withLedgerFileLock(this.path, this.codec, () => readTrustedHeadFile(this.trustedHeadPath, this.codec)));
530
+ }
531
+ /** Pin the current verified head. The chain and any existing pin are checked
532
+ * first, so the pin only ever moves forward along a journal that still
533
+ * carries the history it already recorded. */
534
+ async pinTrustedHead() {
535
+ return this.mutex.runExclusive(() => withLedgerFileLock(this.path, this.codec, () => {
536
+ const entries = this.readEntries();
537
+ this.project(entries);
538
+ this.verifyTrustedHead(entries, false);
539
+ const last = entries.at(-1);
540
+ if (last === void 0) throw this.codec.integrityError(`${this.codec.subject} ${this.path} is empty; there is no head to pin`);
541
+ const head = {
542
+ sequence: last.sequence,
543
+ entryHash: last.entryHash
544
+ };
545
+ writeTrustedHeadFile(this.trustedHeadPath, head, this.codec);
546
+ return head;
547
+ }));
548
+ }
549
+ replayLocked() {
550
+ const entries = this.readEntries();
551
+ const projection = this.project(entries);
552
+ this.verifyTrustedHead(entries, this.requireTrustedHead);
553
+ return {
554
+ entries,
555
+ projection
556
+ };
557
+ }
558
+ verifyTrustedHead(entries, requirePin) {
559
+ const pinned = readTrustedHeadFile(this.trustedHeadPath, this.codec);
560
+ if (pinned === null) {
561
+ if (requirePin && entries.length > 0) throw this.codec.integrityError(`${this.codec.subject} ${this.path} has ${entries.length} entries but no trusted head at ${this.trustedHeadPath} — without its pin the journal cannot prove nothing was deleted`);
562
+ return;
563
+ }
564
+ verifyEntriesAgainstTrustedHead(entries, pinned, this.codec, `${this.codec.subject} ${this.path}`);
565
+ }
566
+ readEntries() {
567
+ if (!existsSync(this.path)) return [];
568
+ const text = readFileSync(this.path, "utf8");
569
+ if (text.length === 0) return [];
570
+ if (!text.endsWith("\n")) throw this.codec.integrityError(`${this.codec.subject} ${this.path} has a truncated final record (missing newline)`);
571
+ const lines = text.slice(0, -1).split("\n");
572
+ const entries = [];
573
+ for (let index = 0; index < lines.length; index += 1) {
574
+ const line = lines[index];
575
+ if (line.length === 0) throw this.codec.integrityError(`${this.codec.subject} ${this.path} has a blank row at line ${index + 1}`);
576
+ let raw;
577
+ try {
578
+ raw = JSON.parse(line);
579
+ } catch (error) {
580
+ throw this.codec.integrityError(`${this.codec.subject} ${this.path} has invalid JSON at line ${index + 1}`, { cause: error });
581
+ }
582
+ const entry = this.codec.parseEntry(raw, {
583
+ path: this.path,
584
+ line: index + 1
585
+ });
586
+ let canonical;
587
+ try {
588
+ canonical = canonicalString(entry);
589
+ } catch (error) {
590
+ throw this.codec.integrityError(`${this.codec.subject} ${this.path} has a row with no canonical JSON form at line ${index + 1}`, { cause: error });
591
+ }
592
+ if (line !== canonical) throw this.codec.integrityError(`${this.codec.subject} ${this.path} has non-canonical bytes at line ${index + 1}`);
593
+ entries.push(entry);
594
+ }
595
+ return entries;
596
+ }
597
+ project(entries) {
598
+ const projector = this.codec.createProjector();
599
+ const eventIds = /* @__PURE__ */ new Set();
600
+ let expectedPrevious = null;
601
+ for (let index = 0; index < entries.length; index += 1) {
602
+ const entry = entries[index];
603
+ this.codec.checkEntryHeader(entry, index);
604
+ if (entry.sequence !== index) throw this.codec.integrityError(`entry ${entry.event.eventId} has sequence ${entry.sequence}, expected ${index}`);
605
+ if (entry.previousHash !== expectedPrevious) throw this.codec.integrityError(`entry ${entry.event.eventId} does not extend the previous hash`);
606
+ const { entryHash: _entryHash, ...material } = entry;
607
+ const expectedHash = hashCanonical(material);
608
+ if (entry.entryHash !== expectedHash) throw this.codec.integrityError(`entry ${entry.event.eventId} hash mismatch: expected ${expectedHash}, got ${entry.entryHash}`);
609
+ expectedPrevious = entry.entryHash;
610
+ if (eventIds.has(entry.event.eventId)) throw this.codec.integrityError(`duplicate eventId ${entry.event.eventId} in durable ledger`);
611
+ eventIds.add(entry.event.eventId);
612
+ projector.apply(entry, index);
613
+ }
614
+ return projector.finish(entries);
615
+ }
616
+ };
617
+ //#endregion
618
+ export { tryWithLedgerFileLock as a, LEDGER_HASH_PATTERN as c, hashCanonical as d, AtomicFileLockError as f, appendLedgerLine as i, LedgerCanonicalizationError as l, tryAcquireAtomicFileLock as m, trustedHeadPathFor as n, withLedgerFileLock as o, probeAtomicFileLock as p, verifyEntriesAgainstTrustedHead as r, writeLedgerFileAtomically as s, FileLedgerJournal as t, canonicalString as u };
619
+
620
+ //# sourceMappingURL=ledger-core-CPZfcrC2.js.map