@hasna/infinity 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/LICENSE +3 -0
  2. package/README.md +331 -0
  3. package/dist/cli.js +2965 -0
  4. package/dist/http-api.js +6448 -0
  5. package/dist/index.js +28536 -0
  6. package/dist/migrate.js +346 -0
  7. package/dist/self-hosted-migrate.js +2064 -0
  8. package/dist/self-hosted.js +13180 -0
  9. package/dist/types/authcapsules/codewith-local.d.ts +109 -0
  10. package/dist/types/authcapsules/infinity-agent-policy.d.ts +57 -0
  11. package/dist/types/authcapsules/os-isolated-runner.d.ts +87 -0
  12. package/dist/types/authority/authority.d.ts +342 -0
  13. package/dist/types/authority/effect-journal.d.ts +205 -0
  14. package/dist/types/authority/mutation-journal.d.ts +163 -0
  15. package/dist/types/authority/mutation-tuple.d.ts +26 -0
  16. package/dist/types/authority/state.d.ts +75 -0
  17. package/dist/types/authority/store.d.ts +15 -0
  18. package/dist/types/brokers/checkpoint/archive.d.ts +21 -0
  19. package/dist/types/brokers/checkpoint/bundle.d.ts +22 -0
  20. package/dist/types/brokers/checkpoint/bytes.d.ts +4 -0
  21. package/dist/types/brokers/checkpoint/clean-verifier-finalization.d.ts +62 -0
  22. package/dist/types/brokers/checkpoint/clean-verifier.d.ts +145 -0
  23. package/dist/types/brokers/checkpoint/final-trust-authority.d.ts +55 -0
  24. package/dist/types/brokers/checkpoint/idempotency.d.ts +25 -0
  25. package/dist/types/brokers/checkpoint/journaled-store.d.ts +9 -0
  26. package/dist/types/brokers/checkpoint/manifest-signature.d.ts +135 -0
  27. package/dist/types/brokers/checkpoint/manifest.d.ts +57 -0
  28. package/dist/types/brokers/checkpoint/s3-store.d.ts +215 -0
  29. package/dist/types/brokers/checkpoint/seal-authority.d.ts +86 -0
  30. package/dist/types/brokers/checkpoint/service.d.ts +198 -0
  31. package/dist/types/brokers/checkpoint/signing-message.d.ts +22 -0
  32. package/dist/types/brokers/checkpoint/store.d.ts +76 -0
  33. package/dist/types/brokers/checkpoint/verifier-cleanup-authority.d.ts +108 -0
  34. package/dist/types/brokers/checkpoint/verifier-cleanup.d.ts +391 -0
  35. package/dist/types/brokers/checkpoint/verifier.d.ts +225 -0
  36. package/dist/types/brokers/composition.d.ts +86 -0
  37. package/dist/types/brokers/git/github-app.d.ts +216 -0
  38. package/dist/types/brokers/git/promotion.d.ts +418 -0
  39. package/dist/types/brokers/persistence/contracts.d.ts +77 -0
  40. package/dist/types/brokers/persistence/memory.d.ts +5 -0
  41. package/dist/types/brokers/persistence/migration.d.ts +6 -0
  42. package/dist/types/brokers/persistence/postgres.d.ts +74 -0
  43. package/dist/types/cli/main.d.ts +2 -0
  44. package/dist/types/cli/operation-journal.d.ts +47 -0
  45. package/dist/types/cli/run.d.ts +15 -0
  46. package/dist/types/client/operator-client.d.ts +100 -0
  47. package/dist/types/contracts/dtos.d.ts +198 -0
  48. package/dist/types/contracts/events.d.ts +45 -0
  49. package/dist/types/contracts/fence.d.ts +39 -0
  50. package/dist/types/contracts/operations.d.ts +91 -0
  51. package/dist/types/contracts/runspec.d.ts +75 -0
  52. package/dist/types/contracts/validators.d.ts +8 -0
  53. package/dist/types/core/clock.d.ts +12 -0
  54. package/dist/types/core/errors.d.ts +18 -0
  55. package/dist/types/core/ids.d.ts +10 -0
  56. package/dist/types/core/int64.d.ts +7 -0
  57. package/dist/types/core/strict-json.d.ts +8 -0
  58. package/dist/types/core/validation.d.ts +25 -0
  59. package/dist/types/golden/disposable-task.d.ts +108 -0
  60. package/dist/types/golden/source-pins.d.ts +77 -0
  61. package/dist/types/http/api.d.ts +52 -0
  62. package/dist/types/http/bearer-auth.d.ts +8 -0
  63. package/dist/types/http/main.d.ts +3 -0
  64. package/dist/types/http/self-hosted-main.d.ts +17 -0
  65. package/dist/types/index.d.ts +65 -0
  66. package/dist/types/mcp/schema.d.ts +7 -0
  67. package/dist/types/node-agent/agent.d.ts +174 -0
  68. package/dist/types/node-agent/contracts.d.ts +119 -0
  69. package/dist/types/node-agent/macos-service.d.ts +33 -0
  70. package/dist/types/node-agent/service-cli.d.ts +26 -0
  71. package/dist/types/persistence/composition.d.ts +49 -0
  72. package/dist/types/persistence/migrate-main.d.ts +2 -0
  73. package/dist/types/persistence/migrations.d.ts +30 -0
  74. package/dist/types/persistence/postgres-accounts-operation.d.ts +26 -0
  75. package/dist/types/persistence/postgres-authority.d.ts +69 -0
  76. package/dist/types/persistence/postgres-external-mutation-journal.d.ts +22 -0
  77. package/dist/types/persistence/postgres-json.d.ts +1 -0
  78. package/dist/types/persistence/postgres-mutation-journal.d.ts +31 -0
  79. package/dist/types/persistence/postgres-reconciler.d.ts +34 -0
  80. package/dist/types/persistence/postgres-sandbox-dispatch-authority.d.ts +9 -0
  81. package/dist/types/persistence/self-hosted-migrate-main.d.ts +2 -0
  82. package/dist/types/ports/accounts-operation.d.ts +336 -0
  83. package/dist/types/ports/accounts.d.ts +293 -0
  84. package/dist/types/ports/sandbox-dispatch-authority.d.ts +154 -0
  85. package/dist/types/ports/sandboxes.d.ts +40 -0
  86. package/dist/types/providers/sandbox-dispatch.d.ts +143 -0
  87. package/dist/types/runtime/config.d.ts +61 -0
  88. package/dist/types/runtime/postgres.d.ts +18 -0
  89. package/dist/types/runtime/provider-activation.d.ts +45 -0
  90. package/dist/types/runtime/server.d.ts +68 -0
  91. package/dist/types/testing/accounts-fake.d.ts +31 -0
  92. package/dist/types/testing/fakes.d.ts +2 -0
  93. package/dist/types/testing/node-control-plane-fake.d.ts +20 -0
  94. package/dist/types/testing/reference.d.ts +27 -0
  95. package/dist/types/testing/sandboxes-fake.d.ts +22 -0
  96. package/evidence/provider-live-daytona-v3.json +1 -0
  97. package/evidence/provider-live-e2b-v3.json +1 -0
  98. package/evidence/provider-production-route-daytona-v1.json +1 -0
  99. package/evidence/provider-production-route-e2b-v1.json +1 -0
  100. package/migrations/0001_authority.sql +689 -0
  101. package/migrations/0002_effect_step_global_owner.sql +2 -0
  102. package/migrations/0003_effect_journal_observed_head.sql +34 -0
  103. package/migrations/0004_authority_mutation_journal_binding.sql +15 -0
  104. package/migrations/20260710_checkpoint_broker_v1.sql +163 -0
  105. package/migrations/20260711_accounts_operation_v1.sql +137 -0
  106. package/migrations/20260711_checkpoint_trust_v2.sql +17 -0
  107. package/migrations/20260711_verifier_cleanup_v2.sql +169 -0
  108. package/migrations/20260711_z_accounts_claim_authority_v2.sql +172 -0
  109. package/migrations/20260711_zz_sandbox_dispatch_authority_v1.sql +143 -0
  110. package/migrations/20260712_sandbox_dispatch_authority_v2.sql +58 -0
  111. package/migrations/external-journal/0001_external_authority_mutation_journal.sql +175 -0
  112. package/package.json +47 -0
@@ -0,0 +1,2064 @@
1
+ #!/usr/bin/env bun
2
+ // @bun
3
+
4
+ // src/persistence/self-hosted-migrate-main.ts
5
+ var {SQL } = globalThis.Bun;
6
+
7
+ // src/authority/mutation-journal.ts
8
+ import {
9
+ createPublicKey,
10
+ generateKeyPairSync,
11
+ sign as signDetached,
12
+ verify as verifyDetached
13
+ } from "crypto";
14
+
15
+ // src/core/errors.ts
16
+ class InfinityError extends Error {
17
+ code;
18
+ status;
19
+ retryable;
20
+ details;
21
+ constructor(code, message, options = {}) {
22
+ super(message, { cause: options.cause });
23
+ this.name = "InfinityError";
24
+ this.code = code;
25
+ this.status = options.status ?? 400;
26
+ this.retryable = options.retryable ?? false;
27
+ this.details = Object.freeze({ ...options.details ?? {} });
28
+ }
29
+ }
30
+
31
+ // src/core/validation.ts
32
+ function isPlainObject(value) {
33
+ if (value === null || typeof value !== "object" || Array.isArray(value))
34
+ return false;
35
+ const prototype = Object.getPrototypeOf(value);
36
+ return prototype === Object.prototype || prototype === null;
37
+ }
38
+ function closedObject(value, label, required, optional = []) {
39
+ if (!isPlainObject(value))
40
+ throw invalid(`${label} must be an object`);
41
+ const allowed = new Set([...required, ...optional]);
42
+ for (const key of Object.keys(value)) {
43
+ if (!allowed.has(key))
44
+ throw invalid(`${label} contains an unknown field`);
45
+ }
46
+ for (const key of required) {
47
+ if (!Object.hasOwn(value, key))
48
+ throw invalid(`${label}.${key} is required`);
49
+ }
50
+ return value;
51
+ }
52
+ function stringValue(value, label, options = {}) {
53
+ if (typeof value !== "string")
54
+ throw invalid(`${label} must be a string`);
55
+ if (value.normalize("NFC") !== value)
56
+ throw invalid(`${label} must use NFC normalization`);
57
+ if (value.length < (options.min ?? 1))
58
+ throw invalid(`${label} is too short`);
59
+ if (value.length > (options.max ?? 256))
60
+ throw invalid(`${label} is too long`);
61
+ if (options.pattern && !options.pattern.test(value))
62
+ throw invalid(`${label} has an invalid format`);
63
+ return value;
64
+ }
65
+ var IDENTIFIER_PATTERN = /^[A-Za-z0-9][A-Za-z0-9:_-]*$/;
66
+ var DIGEST_PATTERN = /^sha256:[a-f0-9]{64}$/;
67
+ var CREDENTIAL_CARRIER_PATTERN_SOURCE = "^(?:[sS][kK]-[A-Za-z0-9]|[gG][hH][pPoOuUsSrR]_|[gG][iI][tT][hH][uU][bB]_[pP][aA][tT]_|[xX][oO][xX][bBaApPrRsS]-|[aA][kKsS][iI][aA][0-9A-Za-z]|[aA][iI][zZ][aA]|[nN][pP][mM]_|[xX][aA][iI]-|[cC][tT][xX]7[sS][kK]-)";
68
+ var CREDENTIAL_CARRIER_PATTERN = new RegExp(CREDENTIAL_CARRIER_PATTERN_SOURCE);
69
+ function identifier(value, label) {
70
+ const parsed = stringValue(value, label, { max: 96, pattern: IDENTIFIER_PATTERN });
71
+ if (CREDENTIAL_CARRIER_PATTERN.test(parsed)) {
72
+ throw invalid(`${label} resembles credential material and is not a valid public identifier`);
73
+ }
74
+ if (parsed.length > 48 && /^[A-Za-z0-9]+$/.test(parsed)) {
75
+ throw invalid(`${label} is an unstructured high-entropy secret-carrier candidate`);
76
+ }
77
+ return parsed;
78
+ }
79
+ function digest(value, label) {
80
+ return stringValue(value, label, { min: 71, max: 71, pattern: DIGEST_PATTERN });
81
+ }
82
+ function positiveInteger(value, label, maximum = Number.MAX_SAFE_INTEGER) {
83
+ if (!Number.isSafeInteger(value) || value <= 0 || value > maximum) {
84
+ throw invalid(`${label} must be a positive safe integer`);
85
+ }
86
+ return value;
87
+ }
88
+ function enumValue(value, label, values) {
89
+ if (typeof value !== "string" || !values.includes(value)) {
90
+ throw invalid(`${label} must be one of ${values.join(", ")}`);
91
+ }
92
+ return value;
93
+ }
94
+ var RFC3339_MILLIS = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
95
+ function timestamp(value, label) {
96
+ const parsed = stringValue(value, label, { min: 24, max: 24, pattern: RFC3339_MILLIS });
97
+ const millis = Date.parse(parsed);
98
+ if (!Number.isFinite(millis) || new Date(millis).toISOString() !== parsed) {
99
+ throw invalid(`${label} must be a canonical UTC timestamp`);
100
+ }
101
+ return parsed;
102
+ }
103
+ function invalid(message) {
104
+ return new InfinityError("invalid_input", message, { status: 2 });
105
+ }
106
+ function deepFreeze(value) {
107
+ if (value && typeof value === "object" && !Object.isFrozen(value)) {
108
+ Object.freeze(value);
109
+ for (const nested of Object.values(value))
110
+ deepFreeze(nested);
111
+ }
112
+ return value;
113
+ }
114
+
115
+ // src/core/int64.ts
116
+ var INT64_MAX = 9223372036854775807n;
117
+ function positiveInt64(value, label) {
118
+ if (typeof value === "number") {
119
+ if (!Number.isSafeInteger(value) || value <= 0)
120
+ throw invalid(`${label} must be a positive signed 64-bit integer`);
121
+ return value;
122
+ }
123
+ if (typeof value === "bigint" && value > 0n && value <= INT64_MAX) {
124
+ return value <= BigInt(Number.MAX_SAFE_INTEGER) ? Number(value) : value;
125
+ }
126
+ throw invalid(`${label} must be a positive signed 64-bit integer`);
127
+ }
128
+ function nonnegativeInt64(value, label) {
129
+ if (typeof value === "number") {
130
+ if (!Number.isSafeInteger(value) || value < 0)
131
+ throw invalid(`${label} must be a nonnegative signed 64-bit integer`);
132
+ return value;
133
+ }
134
+ if (typeof value === "bigint" && value >= 0n && value <= INT64_MAX) {
135
+ return value <= BigInt(Number.MAX_SAFE_INTEGER) ? Number(value) : value;
136
+ }
137
+ throw invalid(`${label} must be a nonnegative signed 64-bit integer`);
138
+ }
139
+ function incrementInt64(value, label) {
140
+ const incremented = BigInt(value) + 1n;
141
+ if (incremented > INT64_MAX)
142
+ throw invalid(`${label} reached signed 64-bit exhaustion`);
143
+ return incremented <= BigInt(Number.MAX_SAFE_INTEGER) ? Number(incremented) : incremented;
144
+ }
145
+ function int64Equal(left, right) {
146
+ return BigInt(left) === BigInt(right);
147
+ }
148
+
149
+ // src/core/strict-json.ts
150
+ import { createHash } from "crypto";
151
+ var strictDocuments = new WeakSet;
152
+ var strictDocumentDigests = new WeakMap;
153
+
154
+ class StrictJsonParser {
155
+ source;
156
+ index = 0;
157
+ constructor(source) {
158
+ this.source = source;
159
+ }
160
+ parse() {
161
+ this.skipWhitespace();
162
+ const value = this.parseValue();
163
+ this.skipWhitespace();
164
+ if (this.index !== this.source.length)
165
+ throw invalid(`JSON has trailing data at byte ${this.index}`);
166
+ if (value !== null && typeof value === "object")
167
+ strictDocumentDigests.set(value, digestCanonical(value));
168
+ return value;
169
+ }
170
+ parseValue() {
171
+ this.skipWhitespace();
172
+ const char = this.source[this.index];
173
+ if (char === "{")
174
+ return this.parseObject();
175
+ if (char === "[")
176
+ return this.parseArray();
177
+ if (char === '"')
178
+ return this.parseString();
179
+ if (char === "t")
180
+ return this.parseLiteral("true", true);
181
+ if (char === "f")
182
+ return this.parseLiteral("false", false);
183
+ if (char === "n")
184
+ return this.parseLiteral("null", null);
185
+ if (char === "-" || char !== undefined && char >= "0" && char <= "9")
186
+ return this.parseNumber();
187
+ throw invalid(`invalid JSON value at byte ${this.index}`);
188
+ }
189
+ parseObject() {
190
+ this.index += 1;
191
+ const result = Object.create(null);
192
+ const seen = new Set;
193
+ this.skipWhitespace();
194
+ if (this.source[this.index] === "}") {
195
+ this.index += 1;
196
+ strictDocuments.add(result);
197
+ strictDocumentDigests.set(result, digestCanonical(result));
198
+ return result;
199
+ }
200
+ while (true) {
201
+ this.skipWhitespace();
202
+ if (this.source[this.index] !== '"')
203
+ throw invalid(`object key expected at byte ${this.index}`);
204
+ const key = this.parseString();
205
+ if (seen.has(key))
206
+ throw invalid("duplicate JSON key");
207
+ seen.add(key);
208
+ this.skipWhitespace();
209
+ if (this.source[this.index] !== ":")
210
+ throw invalid(`':' expected after object key at byte ${this.index}`);
211
+ this.index += 1;
212
+ result[key] = this.parseValue();
213
+ this.skipWhitespace();
214
+ const delimiter = this.source[this.index];
215
+ if (delimiter === "}") {
216
+ this.index += 1;
217
+ strictDocuments.add(result);
218
+ strictDocumentDigests.set(result, digestCanonical(result));
219
+ return result;
220
+ }
221
+ if (delimiter !== ",")
222
+ throw invalid(`',' or '}' expected at byte ${this.index}`);
223
+ this.index += 1;
224
+ }
225
+ }
226
+ parseArray() {
227
+ this.index += 1;
228
+ const result = [];
229
+ this.skipWhitespace();
230
+ if (this.source[this.index] === "]") {
231
+ this.index += 1;
232
+ strictDocuments.add(result);
233
+ strictDocumentDigests.set(result, digestCanonical(result));
234
+ return result;
235
+ }
236
+ while (true) {
237
+ result.push(this.parseValue());
238
+ this.skipWhitespace();
239
+ const delimiter = this.source[this.index];
240
+ if (delimiter === "]") {
241
+ this.index += 1;
242
+ strictDocuments.add(result);
243
+ strictDocumentDigests.set(result, digestCanonical(result));
244
+ return result;
245
+ }
246
+ if (delimiter !== ",")
247
+ throw invalid(`',' or ']' expected at byte ${this.index}`);
248
+ this.index += 1;
249
+ }
250
+ }
251
+ parseString() {
252
+ const start = this.index;
253
+ this.index += 1;
254
+ let escaped = false;
255
+ while (this.index < this.source.length) {
256
+ const char = this.source[this.index];
257
+ if (escaped) {
258
+ escaped = false;
259
+ this.index += 1;
260
+ continue;
261
+ }
262
+ if (char === "\\") {
263
+ escaped = true;
264
+ this.index += 1;
265
+ continue;
266
+ }
267
+ if (char === '"') {
268
+ this.index += 1;
269
+ let value;
270
+ try {
271
+ value = JSON.parse(this.source.slice(start, this.index));
272
+ } catch (error) {
273
+ throw invalid(`invalid JSON string at byte ${start}`);
274
+ }
275
+ if (value.normalize("NFC") !== value)
276
+ throw invalid(`JSON strings must use NFC normalization at byte ${start}`);
277
+ if (hasUnpairedSurrogate(value))
278
+ throw invalid(`JSON strings must not contain unpaired surrogates at byte ${start}`);
279
+ return value;
280
+ }
281
+ if (char !== undefined && char.charCodeAt(0) < 32)
282
+ throw invalid(`control character in JSON string at byte ${this.index}`);
283
+ this.index += 1;
284
+ }
285
+ throw invalid(`unterminated JSON string at byte ${start}`);
286
+ }
287
+ parseNumber() {
288
+ const rest = this.source.slice(this.index);
289
+ const match = /^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?/.exec(rest);
290
+ if (!match)
291
+ throw invalid(`invalid JSON number at byte ${this.index}`);
292
+ const token = match[0];
293
+ this.index += token.length;
294
+ if (!/[.eE]/.test(token)) {
295
+ if (token === "-0")
296
+ throw invalid("negative zero is not canonical JSON");
297
+ const integer = BigInt(token);
298
+ if (integer >= BigInt(Number.MIN_SAFE_INTEGER) && integer <= BigInt(Number.MAX_SAFE_INTEGER))
299
+ return Number(integer);
300
+ return integer;
301
+ }
302
+ const value = Number(token);
303
+ if (!Number.isFinite(value))
304
+ throw invalid(`JSON number must be finite at byte ${this.index - token.length}`);
305
+ if (Object.is(value, -0))
306
+ throw invalid("negative zero is not canonical JSON");
307
+ const significand = token.split(/[eE]/, 1)[0] ?? token;
308
+ if (value === 0 && /[1-9]/.test(significand))
309
+ throw invalid("JSON number underflow would lose information");
310
+ if (JSON.stringify(value) !== token)
311
+ throw invalid("JSON number is non-canonical or loses information");
312
+ return value;
313
+ }
314
+ parseLiteral(token, value) {
315
+ if (!this.source.startsWith(token, this.index))
316
+ throw invalid(`invalid JSON literal at byte ${this.index}`);
317
+ this.index += token.length;
318
+ return value;
319
+ }
320
+ skipWhitespace() {
321
+ while (this.index < this.source.length && /[\t\n\r ]/.test(this.source[this.index] ?? ""))
322
+ this.index += 1;
323
+ }
324
+ }
325
+ function parseStrictJson(source) {
326
+ return new StrictJsonParser(source).parse();
327
+ }
328
+ function canonicalJson(value) {
329
+ if (value === null)
330
+ return "null";
331
+ if (typeof value === "boolean")
332
+ return value ? "true" : "false";
333
+ if (typeof value === "string") {
334
+ if (value.normalize("NFC") !== value)
335
+ throw invalid("canonical JSON strings must use NFC normalization");
336
+ if (hasUnpairedSurrogate(value))
337
+ throw invalid("canonical JSON strings must not contain unpaired surrogates");
338
+ return JSON.stringify(value);
339
+ }
340
+ if (typeof value === "number") {
341
+ if (!Number.isFinite(value) || Object.is(value, -0))
342
+ throw invalid("canonical JSON numbers must be finite and not negative zero");
343
+ if (Number.isInteger(value) && !Number.isSafeInteger(value)) {
344
+ throw invalid("canonical JSON integer Numbers must be safe integers; use bigint for exact wider values");
345
+ }
346
+ return JSON.stringify(value);
347
+ }
348
+ if (typeof value === "bigint")
349
+ return value.toString(10);
350
+ if (Array.isArray(value)) {
351
+ for (let index = 0;index < value.length; index += 1) {
352
+ if (!Object.hasOwn(value, index))
353
+ throw invalid("canonical JSON arrays must not be sparse");
354
+ }
355
+ return `[${value.map((entry) => canonicalJson(entry)).join(",")}]`;
356
+ }
357
+ if (!isPlainObject(value))
358
+ throw invalid("canonical JSON accepts only JSON-compatible values");
359
+ const entries = Object.keys(value).sort().map((key) => {
360
+ if (key.normalize("NFC") !== key)
361
+ throw invalid("canonical JSON keys must use NFC normalization");
362
+ if (hasUnpairedSurrogate(key))
363
+ throw invalid("canonical JSON keys must not contain unpaired surrogates");
364
+ const nested = value[key];
365
+ if (nested === undefined)
366
+ throw invalid("canonical JSON object contains an undefined field");
367
+ return `${JSON.stringify(key)}:${canonicalJson(nested)}`;
368
+ });
369
+ return `{${entries.join(",")}}`;
370
+ }
371
+ function hasUnpairedSurrogate(value) {
372
+ for (let index = 0;index < value.length; index += 1) {
373
+ const unit = value.charCodeAt(index);
374
+ if (unit >= 55296 && unit <= 56319) {
375
+ const next = value.charCodeAt(index + 1);
376
+ if (!(next >= 56320 && next <= 57343))
377
+ return true;
378
+ index += 1;
379
+ } else if (unit >= 56320 && unit <= 57343) {
380
+ return true;
381
+ }
382
+ }
383
+ return false;
384
+ }
385
+ function digestCanonical(value) {
386
+ return `sha256:${createHash("sha256").update(canonicalJson(value), "utf8").digest("hex")}`;
387
+ }
388
+
389
+ // src/authority/mutation-journal.ts
390
+ var AUTHORITY_MUTATION_ENVELOPE_SCHEMA_VERSION = "infinity.authority-mutation-reconstruction/v1";
391
+ var AUTHORITY_MUTATION_ANCHOR_SCHEMA_VERSION = "infinity.authority-mutation-journal-anchor/v1";
392
+ var AUTHORITY_MUTATION_ACK_SCHEMA_VERSION = "infinity.authority-mutation-journal-ack/v1";
393
+ var AUTHORITY_MUTATION_RANGE_SCHEMA_VERSION = "infinity.authority-mutation-journal-range/v1";
394
+ var AUTHORITY_MUTATION_PROTECTION_SCHEMA_VERSION = "infinity.authority-mutation-journal-protection/v1";
395
+ var MAXIMUM_ENVELOPE_BYTES = 16 * 1024 * 1024;
396
+ var MAXIMUM_CHANGES = 1e5;
397
+ class Ed25519AuthorityMutationJournalVerifier {
398
+ signerPrincipal;
399
+ signingKeyId;
400
+ publicKey;
401
+ verificationKeyDigest;
402
+ constructor(signerPrincipal, signingKeyId, publicKey, publicKeySpki) {
403
+ this.signerPrincipal = signerPrincipal;
404
+ this.signingKeyId = signingKeyId;
405
+ this.publicKey = publicKey;
406
+ this.verificationKeyDigest = digestCanonical({
407
+ algorithm: "Ed25519",
408
+ public_key_spki: publicKeySpki
409
+ });
410
+ }
411
+ static fromSpki(signerPrincipal, signingKeyId, publicKeySpki) {
412
+ const principal = identifier(signerPrincipal, "authority_mutation_journal.signer_principal");
413
+ const keyId = identifier(signingKeyId, "authority_mutation_journal.signing_key_id");
414
+ const encoded = stringValue(publicKeySpki, "authority_mutation_journal.public_key_spki", {
415
+ min: 40,
416
+ max: 256,
417
+ pattern: /^[A-Za-z0-9_-]+$/
418
+ });
419
+ let key;
420
+ try {
421
+ key = createPublicKey({ key: Buffer.from(encoded, "base64url"), format: "der", type: "spki" });
422
+ } catch (error) {
423
+ throw new InfinityError("invalid_input", "authority mutation journal public key is invalid", { cause: error });
424
+ }
425
+ if (key.asymmetricKeyType !== "ed25519")
426
+ throw new InfinityError("invalid_input", "authority mutation journal key must be Ed25519");
427
+ return new Ed25519AuthorityMutationJournalVerifier(principal, keyId, key, encoded);
428
+ }
429
+ verifyAnchor(value) {
430
+ const input = closedObject(value, "authority_mutation_anchor", [
431
+ "anchor_schema_version",
432
+ "journal_sequence",
433
+ "prior_frontier_digest",
434
+ "record_digest",
435
+ "frontier_digest",
436
+ "signer_principal",
437
+ "signing_key_id",
438
+ "signature",
439
+ "record"
440
+ ]);
441
+ const record = parseAuthorityMutationEnvelope(input.record);
442
+ const parsed = {
443
+ anchor_schema_version: enumValue(input.anchor_schema_version, "authority_mutation_anchor.anchor_schema_version", [AUTHORITY_MUTATION_ANCHOR_SCHEMA_VERSION]),
444
+ journal_sequence: positiveInt64(input.journal_sequence, "authority_mutation_anchor.journal_sequence"),
445
+ prior_frontier_digest: nullableDigest(input.prior_frontier_digest, "authority_mutation_anchor.prior_frontier_digest"),
446
+ record_digest: digest(input.record_digest, "authority_mutation_anchor.record_digest"),
447
+ frontier_digest: digest(input.frontier_digest, "authority_mutation_anchor.frontier_digest"),
448
+ signer_principal: identifier(input.signer_principal, "authority_mutation_anchor.signer_principal"),
449
+ signing_key_id: identifier(input.signing_key_id, "authority_mutation_anchor.signing_key_id"),
450
+ signature: stringValue(input.signature, "authority_mutation_anchor.signature", { min: 80, max: 128, pattern: /^[A-Za-z0-9_-]+$/ }),
451
+ record
452
+ };
453
+ if (parsed.signer_principal !== this.signerPrincipal || parsed.signing_key_id !== this.signingKeyId) {
454
+ throw new InfinityError("integrity_failure", "authority mutation journal signer is not trusted");
455
+ }
456
+ if (!int64Equal(parsed.journal_sequence, record.commit_sequence)) {
457
+ throw new InfinityError("integrity_failure", "authority mutation journal sequence differs from commit sequence");
458
+ }
459
+ if (parsed.record_digest !== digestCanonical(record)) {
460
+ throw new InfinityError("integrity_failure", "authority mutation journal record digest mismatch");
461
+ }
462
+ const unsigned = unsignedAnchor(parsed);
463
+ if (parsed.frontier_digest !== digestCanonical(unsigned)) {
464
+ throw new InfinityError("integrity_failure", "authority mutation journal frontier digest mismatch");
465
+ }
466
+ if (!verifySignature(this.publicKey, canonicalJson({ ...unsigned, frontier_digest: parsed.frontier_digest }), parsed.signature)) {
467
+ throw new InfinityError("integrity_failure", "authority mutation journal anchor signature is invalid");
468
+ }
469
+ return deepFreeze(parsed);
470
+ }
471
+ verifyAcknowledgement(value) {
472
+ const input = closedObject(value, "authority_mutation_ack", [
473
+ "ack_schema_version",
474
+ "acknowledged_through",
475
+ "frontier_digest",
476
+ "journal_id",
477
+ "protection_digest",
478
+ "restore_domain_id",
479
+ "encrypted_at_rest",
480
+ "signer_principal",
481
+ "signing_key_id",
482
+ "signature"
483
+ ]);
484
+ const parsed = {
485
+ ack_schema_version: enumValue(input.ack_schema_version, "authority_mutation_ack.ack_schema_version", [AUTHORITY_MUTATION_ACK_SCHEMA_VERSION]),
486
+ acknowledged_through: positiveInt64(input.acknowledged_through, "authority_mutation_ack.acknowledged_through"),
487
+ frontier_digest: digest(input.frontier_digest, "authority_mutation_ack.frontier_digest"),
488
+ journal_id: identifier(input.journal_id, "authority_mutation_ack.journal_id"),
489
+ protection_digest: digest(input.protection_digest, "authority_mutation_ack.protection_digest"),
490
+ restore_domain_id: identifier(input.restore_domain_id, "authority_mutation_ack.restore_domain_id"),
491
+ encrypted_at_rest: input.encrypted_at_rest === true ? true : (() => {
492
+ throw new InfinityError("integrity_failure", "authority mutation journal acknowledgement is not encrypted-at-rest bound");
493
+ })(),
494
+ signer_principal: identifier(input.signer_principal, "authority_mutation_ack.signer_principal"),
495
+ signing_key_id: identifier(input.signing_key_id, "authority_mutation_ack.signing_key_id"),
496
+ signature: stringValue(input.signature, "authority_mutation_ack.signature", { min: 80, max: 128, pattern: /^[A-Za-z0-9_-]+$/ })
497
+ };
498
+ if (parsed.signer_principal !== this.signerPrincipal || parsed.signing_key_id !== this.signingKeyId) {
499
+ throw new InfinityError("integrity_failure", "authority mutation journal acknowledgement signer is not trusted");
500
+ }
501
+ if (!verifySignature(this.publicKey, canonicalJson(unsignedAcknowledgement(parsed)), parsed.signature)) {
502
+ throw new InfinityError("integrity_failure", "authority mutation journal acknowledgement signature is invalid");
503
+ }
504
+ return deepFreeze(parsed);
505
+ }
506
+ verifyAppendReceipt(value) {
507
+ const input = closedObject(value, "authority_mutation_append_receipt", ["anchor", "ack"]);
508
+ const anchor = this.verifyAnchor(input.anchor);
509
+ const ack = this.verifyAcknowledgement(input.ack);
510
+ if (!int64Equal(anchor.journal_sequence, ack.acknowledged_through) || anchor.frontier_digest !== ack.frontier_digest) {
511
+ throw new InfinityError("integrity_failure", "authority mutation acknowledgement does not bind its anchor");
512
+ }
513
+ return deepFreeze({ anchor, ack });
514
+ }
515
+ verifyRange(value) {
516
+ const input = closedObject(value, "authority_mutation_range", [
517
+ "range_schema_version",
518
+ "from_sequence",
519
+ "through_sequence",
520
+ "prior_frontier_digest",
521
+ "receipts",
522
+ "head",
523
+ "journal_id",
524
+ "protection_digest",
525
+ "signer_principal",
526
+ "signing_key_id",
527
+ "signature"
528
+ ]);
529
+ if (!Array.isArray(input.receipts) || input.receipts.length > 1000) {
530
+ throw new InfinityError("invalid_input", "authority mutation journal range receipts must be a bounded array");
531
+ }
532
+ const fromSequence = positiveInt64(input.from_sequence, "authority_mutation_range.from_sequence");
533
+ const receipts = input.receipts.map((receipt) => this.verifyAppendReceipt(receipt));
534
+ const head = input.head === null ? null : this.verifyAcknowledgement(input.head);
535
+ const parsed = {
536
+ range_schema_version: enumValue(input.range_schema_version, "authority_mutation_range.range_schema_version", [AUTHORITY_MUTATION_RANGE_SCHEMA_VERSION]),
537
+ from_sequence: fromSequence,
538
+ through_sequence: nonnegativeInt64(input.through_sequence, "authority_mutation_range.through_sequence"),
539
+ prior_frontier_digest: nullableDigest(input.prior_frontier_digest, "authority_mutation_range.prior_frontier_digest"),
540
+ receipts,
541
+ head,
542
+ journal_id: identifier(input.journal_id, "authority_mutation_range.journal_id"),
543
+ protection_digest: digest(input.protection_digest, "authority_mutation_range.protection_digest"),
544
+ signer_principal: identifier(input.signer_principal, "authority_mutation_range.signer_principal"),
545
+ signing_key_id: identifier(input.signing_key_id, "authority_mutation_range.signing_key_id"),
546
+ signature: stringValue(input.signature, "authority_mutation_range.signature", { min: 80, max: 128, pattern: /^[A-Za-z0-9_-]+$/ })
547
+ };
548
+ if (parsed.signer_principal !== this.signerPrincipal || parsed.signing_key_id !== this.signingKeyId) {
549
+ throw new InfinityError("integrity_failure", "authority mutation journal range signer is not trusted");
550
+ }
551
+ if (head && (head.journal_id !== parsed.journal_id || head.protection_digest !== parsed.protection_digest)) {
552
+ throw new InfinityError("integrity_failure", "authority mutation journal range protection differs from its signed head");
553
+ }
554
+ if (receipts.some((receipt) => receipt.ack.journal_id !== parsed.journal_id || receipt.ack.protection_digest !== parsed.protection_digest)) {
555
+ throw new InfinityError("integrity_failure", "authority mutation journal range members differ from its protection binding");
556
+ }
557
+ let expected = fromSequence;
558
+ let prior = parsed.prior_frontier_digest;
559
+ for (const receipt of receipts) {
560
+ if (!int64Equal(receipt.anchor.journal_sequence, expected) || receipt.anchor.prior_frontier_digest !== prior) {
561
+ throw new InfinityError("integrity_failure", "authority mutation journal signed range is not contiguous");
562
+ }
563
+ prior = receipt.anchor.frontier_digest;
564
+ expected = incrementInt64(expected, "authority_mutation_range.sequence");
565
+ }
566
+ const expectedThrough = receipts.length === 0 ? BigInt(fromSequence) - 1n : receipts.at(-1).anchor.journal_sequence;
567
+ if (!int64Equal(parsed.through_sequence, expectedThrough)) {
568
+ throw new InfinityError("integrity_failure", "authority mutation journal range through sequence differs from its members");
569
+ }
570
+ if (head) {
571
+ if (BigInt(head.acknowledged_through) < BigInt(parsed.through_sequence) || int64Equal(head.acknowledged_through, parsed.through_sequence) && head.frontier_digest !== prior) {
572
+ throw new InfinityError("integrity_failure", "authority mutation journal range head does not cover its members");
573
+ }
574
+ } else if (!int64Equal(parsed.through_sequence, 0)) {
575
+ throw new InfinityError("integrity_failure", "authority mutation journal nonempty range lacks a signed head");
576
+ }
577
+ if (!verifySignature(this.publicKey, canonicalJson(unsignedRange(parsed)), parsed.signature)) {
578
+ throw new InfinityError("integrity_failure", "authority mutation journal range signature is invalid");
579
+ }
580
+ return deepFreeze(parsed);
581
+ }
582
+ }
583
+
584
+ class InMemoryAuthorityMutationJournal {
585
+ signer;
586
+ verifier;
587
+ receipts = new Map;
588
+ frontier = 0;
589
+ frontierDigest = null;
590
+ available = true;
591
+ protection;
592
+ constructor(signer, protection) {
593
+ this.signer = signer;
594
+ const unsignedProtection = {
595
+ protection_schema_version: AUTHORITY_MUTATION_PROTECTION_SCHEMA_VERSION,
596
+ journal_id: identifier(digestCanonical({
597
+ kind: "transient-authority-mutation-journal",
598
+ signer_principal: signer.signerPrincipal,
599
+ signing_key_id: signer.signingKeyId
600
+ }), "authority_mutation_journal.journal_id"),
601
+ restore_domain_id: identifier(protection.restore_domain_id, "authority_mutation_journal.restore_domain_id"),
602
+ encrypted_at_rest: protection.encrypted_at_rest,
603
+ durability: "transient",
604
+ signer_principal: signer.signerPrincipal,
605
+ signing_key_id: signer.signingKeyId
606
+ };
607
+ this.protection = deepFreeze({
608
+ ...unsignedProtection,
609
+ protection_digest: authorityMutationJournalProtectionDigest(unsignedProtection)
610
+ });
611
+ if (!this.protection.encrypted_at_rest)
612
+ throw new InfinityError("capability_denied", "authority mutation journal must be encrypted at rest", { status: 503 });
613
+ this.verifier = Ed25519AuthorityMutationJournalVerifier.fromSpki(signer.signerPrincipal, signer.signingKeyId, signer.publicKeySpki());
614
+ }
615
+ setAvailable(available) {
616
+ this.available = available;
617
+ }
618
+ describe() {
619
+ return this.journalProtection();
620
+ }
621
+ journalProtection() {
622
+ return this.protection;
623
+ }
624
+ append(value) {
625
+ this.requireAvailable();
626
+ const envelope = parseAuthorityMutationEnvelope(value);
627
+ const key = BigInt(envelope.commit_sequence).toString(10);
628
+ const prior = this.receipts.get(key);
629
+ if (prior) {
630
+ this.verifyStoredReceipt(prior);
631
+ if (canonicalJson(prior.anchor.record) !== canonicalJson(envelope)) {
632
+ throw new InfinityError("integrity_failure", "authority mutation journal sequence has conflicting immutable bytes");
633
+ }
634
+ return prior;
635
+ }
636
+ this.verifyContiguousFrontier();
637
+ const expected = incrementInt64(this.frontier, "authority_mutation_journal.frontier");
638
+ if (!int64Equal(envelope.commit_sequence, expected)) {
639
+ throw new InfinityError("state_conflict", "authority mutation journal append must extend the contiguous frontier", { status: 409 });
640
+ }
641
+ const recordDigest = digestCanonical(envelope);
642
+ const unsigned = {
643
+ anchor_schema_version: AUTHORITY_MUTATION_ANCHOR_SCHEMA_VERSION,
644
+ journal_sequence: expected,
645
+ prior_frontier_digest: this.frontierDigest,
646
+ record_digest: recordDigest,
647
+ signer_principal: this.signer.signerPrincipal,
648
+ signing_key_id: this.signer.signingKeyId
649
+ };
650
+ const frontierDigest = digestCanonical(unsigned);
651
+ const anchor = {
652
+ ...unsigned,
653
+ frontier_digest: frontierDigest,
654
+ signature: this.signer.sign(canonicalJson({ ...unsigned, frontier_digest: frontierDigest })),
655
+ record: envelope
656
+ };
657
+ const protection = this.journalProtection();
658
+ const unsignedAck = {
659
+ ack_schema_version: AUTHORITY_MUTATION_ACK_SCHEMA_VERSION,
660
+ acknowledged_through: expected,
661
+ frontier_digest: frontierDigest,
662
+ journal_id: protection.journal_id,
663
+ protection_digest: protection.protection_digest,
664
+ restore_domain_id: protection.restore_domain_id,
665
+ encrypted_at_rest: true,
666
+ signer_principal: this.signer.signerPrincipal,
667
+ signing_key_id: this.signer.signingKeyId
668
+ };
669
+ const ack = {
670
+ ...unsignedAck,
671
+ signature: this.signer.sign(canonicalJson(unsignedAck))
672
+ };
673
+ const receipt = this.verifier.verifyAppendReceipt({ anchor, ack });
674
+ this.receipts.set(key, receipt);
675
+ this.frontier = expected;
676
+ this.frontierDigest = frontierDigest;
677
+ return receipt;
678
+ }
679
+ readRange(fromSequenceInput, limitInput) {
680
+ this.requireAvailable();
681
+ const fromSequence = positiveInt64(fromSequenceInput, "authority_mutation_journal.from_sequence");
682
+ const limit = positiveInteger(limitInput, "authority_mutation_journal.range_limit", 1000);
683
+ this.verifyContiguousFrontier();
684
+ const receipts = [];
685
+ let sequence = fromSequence;
686
+ while (receipts.length < limit && BigInt(sequence) <= BigInt(this.frontier)) {
687
+ const receipt = this.receipts.get(BigInt(sequence).toString(10));
688
+ if (!receipt)
689
+ throw new InfinityError("integrity_failure", "authority mutation journal range contains a gap");
690
+ receipts.push(this.verifyStoredReceipt(receipt));
691
+ sequence = incrementInt64(sequence, "authority_mutation_journal.range_sequence");
692
+ }
693
+ const priorSequence = BigInt(fromSequence) - 1n;
694
+ const priorFrontier = priorSequence === 0n ? null : this.receipts.get(priorSequence.toString())?.anchor.frontier_digest ?? null;
695
+ if (priorSequence > 0n && priorFrontier === null)
696
+ throw new InfinityError("integrity_failure", "authority mutation journal range predecessor is missing");
697
+ const protection = this.journalProtection();
698
+ const unsigned = {
699
+ range_schema_version: AUTHORITY_MUTATION_RANGE_SCHEMA_VERSION,
700
+ from_sequence: fromSequence,
701
+ through_sequence: receipts.length === 0 ? priorSequence : receipts.at(-1).anchor.journal_sequence,
702
+ prior_frontier_digest: priorFrontier,
703
+ receipts,
704
+ head: this.head(),
705
+ journal_id: protection.journal_id,
706
+ protection_digest: protection.protection_digest,
707
+ signer_principal: this.signer.signerPrincipal,
708
+ signing_key_id: this.signer.signingKeyId
709
+ };
710
+ return this.verifier.verifyRange({
711
+ ...unsigned,
712
+ signature: this.signer.sign(canonicalJson(unsigned))
713
+ });
714
+ }
715
+ head() {
716
+ this.requireAvailable();
717
+ if (int64Equal(this.frontier, 0))
718
+ return null;
719
+ return this.receipts.get(BigInt(this.frontier).toString(10))?.ack ?? null;
720
+ }
721
+ verifyStoredReceipt(value) {
722
+ const verified = this.verifier.verifyAppendReceipt(value);
723
+ const stored = this.receipts.get(BigInt(verified.anchor.journal_sequence).toString(10));
724
+ if (!stored || canonicalJson(stored) !== canonicalJson(verified)) {
725
+ throw new InfinityError("integrity_failure", "authority mutation receipt is not a stored external journal member");
726
+ }
727
+ return stored;
728
+ }
729
+ verifyAppendReceipt(value) {
730
+ return this.verifier.verifyAppendReceipt(value);
731
+ }
732
+ verifyAcknowledgement(value) {
733
+ return this.verifier.verifyAcknowledgement(value);
734
+ }
735
+ verifyRange(value) {
736
+ return this.verifier.verifyRange(value);
737
+ }
738
+ verifyContiguousFrontier() {
739
+ let expected = 1;
740
+ let prior = null;
741
+ for (const receipt of this.receipts.values()) {
742
+ const verified = this.verifier.verifyAppendReceipt(receipt);
743
+ if (!int64Equal(verified.anchor.journal_sequence, expected) || verified.anchor.prior_frontier_digest !== prior) {
744
+ throw new InfinityError("integrity_failure", "authority mutation journal signed frontier contains a gap or is not contiguous");
745
+ }
746
+ prior = verified.anchor.frontier_digest;
747
+ expected = incrementInt64(expected, "authority_mutation_journal.frontier");
748
+ }
749
+ if (!int64Equal(this.frontier, BigInt(expected) - 1n) || this.frontierDigest !== prior) {
750
+ throw new InfinityError("integrity_failure", "authority mutation journal head differs from its signed members");
751
+ }
752
+ }
753
+ requireAvailable() {
754
+ if (!this.available)
755
+ throw new InfinityError("state_conflict", "external authority mutation journal is unavailable", { status: 503, retryable: true });
756
+ }
757
+ }
758
+ function parseAuthorityMutationEnvelope(value) {
759
+ const input = closedObject(value, "authority_mutation_envelope", [
760
+ "schema_version",
761
+ "storage_schema_version",
762
+ "authority_epoch",
763
+ "commit_sequence",
764
+ "operation_id",
765
+ "operation_digest",
766
+ "command",
767
+ "actor_principal",
768
+ "database_time",
769
+ "changes"
770
+ ]);
771
+ if (!Array.isArray(input.changes) || input.changes.length === 0 || input.changes.length > MAXIMUM_CHANGES) {
772
+ throw new InfinityError("invalid_input", "authority mutation envelope changes must be a bounded nonempty array");
773
+ }
774
+ const changes = input.changes.map((value2, index) => {
775
+ const change = closedObject(value2, `authority_mutation_envelope.changes[${index}]`, [
776
+ "change_ordinal",
777
+ "table_name",
778
+ "mutation_kind",
779
+ "row_digest",
780
+ "row"
781
+ ]);
782
+ const ordinal = positiveInteger(change.change_ordinal, `authority_mutation_envelope.changes[${index}].change_ordinal`, MAXIMUM_CHANGES);
783
+ if (ordinal !== index + 1)
784
+ throw new InfinityError("invalid_input", "authority mutation change ordinals must be contiguous");
785
+ if (!isPlainObject(change.row))
786
+ throw new InfinityError("invalid_input", "authority mutation row image must be an object");
787
+ canonicalJson(change.row);
788
+ const rowDigest = digest(change.row_digest, `authority_mutation_envelope.changes[${index}].row_digest`);
789
+ if (rowDigest !== digestCanonical(change.row))
790
+ throw new InfinityError("integrity_failure", "authority mutation row image digest mismatch");
791
+ return deepFreeze({
792
+ change_ordinal: ordinal,
793
+ table_name: stringValue(change.table_name, `authority_mutation_envelope.changes[${index}].table_name`, {
794
+ max: 80,
795
+ pattern: /^[a-z][a-z0-9_]*$/
796
+ }),
797
+ mutation_kind: enumValue(change.mutation_kind, `authority_mutation_envelope.changes[${index}].mutation_kind`, ["INSERT", "UPDATE", "DELETE"]),
798
+ row_digest: rowDigest,
799
+ row: deepFreeze({ ...change.row })
800
+ });
801
+ });
802
+ const parsed = {
803
+ schema_version: enumValue(input.schema_version, "authority_mutation_envelope.schema_version", [AUTHORITY_MUTATION_ENVELOPE_SCHEMA_VERSION]),
804
+ storage_schema_version: stringValue(input.storage_schema_version, "authority_mutation_envelope.storage_schema_version", {
805
+ max: 120,
806
+ pattern: /^[a-z][a-z0-9._/-]*$/
807
+ }),
808
+ authority_epoch: positiveInt64(input.authority_epoch, "authority_mutation_envelope.authority_epoch"),
809
+ commit_sequence: positiveInt64(input.commit_sequence, "authority_mutation_envelope.commit_sequence"),
810
+ operation_id: identifier(input.operation_id, "authority_mutation_envelope.operation_id"),
811
+ operation_digest: digest(input.operation_digest, "authority_mutation_envelope.operation_digest"),
812
+ command: stringValue(input.command, "authority_mutation_envelope.command", { max: 80, pattern: /^[a-z][a-z0-9._-]*$/ }),
813
+ actor_principal: identifier(input.actor_principal, "authority_mutation_envelope.actor_principal"),
814
+ database_time: timestamp(input.database_time, "authority_mutation_envelope.database_time"),
815
+ changes
816
+ };
817
+ if (Buffer.byteLength(canonicalJson(parsed), "utf8") > MAXIMUM_ENVELOPE_BYTES) {
818
+ throw new InfinityError("invalid_input", "authority mutation envelope exceeds the byte limit");
819
+ }
820
+ return deepFreeze(parsed);
821
+ }
822
+ function authorityMutationJournalProtectionDigest(protection) {
823
+ return digestCanonical({
824
+ protection_schema_version: protection.protection_schema_version,
825
+ journal_id: protection.journal_id,
826
+ restore_domain_id: protection.restore_domain_id,
827
+ encrypted_at_rest: protection.encrypted_at_rest,
828
+ durability: protection.durability,
829
+ signer_principal: protection.signer_principal,
830
+ signing_key_id: protection.signing_key_id
831
+ });
832
+ }
833
+ function unsignedAnchor(anchor) {
834
+ return {
835
+ anchor_schema_version: anchor.anchor_schema_version,
836
+ journal_sequence: anchor.journal_sequence,
837
+ prior_frontier_digest: anchor.prior_frontier_digest,
838
+ record_digest: anchor.record_digest,
839
+ signer_principal: anchor.signer_principal,
840
+ signing_key_id: anchor.signing_key_id
841
+ };
842
+ }
843
+ function unsignedAcknowledgement(ack) {
844
+ return {
845
+ ack_schema_version: ack.ack_schema_version,
846
+ acknowledged_through: ack.acknowledged_through,
847
+ frontier_digest: ack.frontier_digest,
848
+ journal_id: ack.journal_id,
849
+ protection_digest: ack.protection_digest,
850
+ restore_domain_id: ack.restore_domain_id,
851
+ encrypted_at_rest: ack.encrypted_at_rest,
852
+ signer_principal: ack.signer_principal,
853
+ signing_key_id: ack.signing_key_id
854
+ };
855
+ }
856
+ function unsignedRange(range) {
857
+ return {
858
+ range_schema_version: range.range_schema_version,
859
+ from_sequence: range.from_sequence,
860
+ through_sequence: range.through_sequence,
861
+ prior_frontier_digest: range.prior_frontier_digest,
862
+ receipts: range.receipts,
863
+ head: range.head,
864
+ journal_id: range.journal_id,
865
+ protection_digest: range.protection_digest,
866
+ signer_principal: range.signer_principal,
867
+ signing_key_id: range.signing_key_id
868
+ };
869
+ }
870
+ function nullableDigest(value, label) {
871
+ return value === null ? null : digest(value, label);
872
+ }
873
+ function verifySignature(publicKey, payload, signature) {
874
+ if (!/^[A-Za-z0-9_-]{80,128}$/.test(signature))
875
+ return false;
876
+ try {
877
+ return verifyDetached(null, Buffer.from(payload, "utf8"), publicKey, Buffer.from(signature, "base64url"));
878
+ } catch {
879
+ return false;
880
+ }
881
+ }
882
+
883
+ // src/persistence/postgres-external-mutation-journal.ts
884
+ import { existsSync, readFileSync } from "fs";
885
+ import { dirname, join, parse } from "path";
886
+ import { fileURLToPath } from "url";
887
+
888
+ // src/persistence/postgres-mutation-journal.ts
889
+ async function runSerializableAcknowledgement(operation, attempts = 5) {
890
+ let lastError;
891
+ for (let attempt = 1;attempt <= attempts; attempt += 1) {
892
+ try {
893
+ return await operation();
894
+ } catch (error) {
895
+ lastError = error;
896
+ if (!isRetryableSerializationFailure(error) || attempt === attempts)
897
+ throw error;
898
+ }
899
+ }
900
+ throw lastError;
901
+ }
902
+ function isRetryableSerializationFailure(error) {
903
+ if (!error || typeof error !== "object")
904
+ return false;
905
+ const candidate = error;
906
+ const state = typeof candidate.errno === "string" ? candidate.errno : typeof candidate.code === "string" ? candidate.code : typeof candidate.sqlState === "string" ? candidate.sqlState : undefined;
907
+ return state === "40001" || state === "40P01" || isRetryableSerializationFailure(candidate.cause);
908
+ }
909
+
910
+ // src/persistence/postgres-external-mutation-journal.ts
911
+ var MAX_STARTUP_RECEIPTS = 1e4;
912
+ var POSTGRES_EXTERNAL_MUTATION_JOURNAL_MIGRATION_MANIFEST = Object.freeze({
913
+ "0001_external_authority_mutation_journal.sql": "sha256:ead2509a00dc767b855711be54f7480eaa09e0928cd16b1b86dc5a79b57c9b52"
914
+ });
915
+
916
+ class PostgresExternalMutationJournal {
917
+ sql;
918
+ signer;
919
+ verifier;
920
+ protection;
921
+ configuredJournalId;
922
+ clusterSystemIdentifier;
923
+ databaseName;
924
+ verificationKeyDigest;
925
+ constructor(sql, signer, verifier, protection, configuredJournalId, clusterSystemIdentifier, databaseName, verificationKeyDigest) {
926
+ this.sql = sql;
927
+ this.signer = signer;
928
+ this.verifier = verifier;
929
+ this.protection = protection;
930
+ this.configuredJournalId = configuredJournalId;
931
+ this.clusterSystemIdentifier = clusterSystemIdentifier;
932
+ this.databaseName = databaseName;
933
+ this.verificationKeyDigest = verificationKeyDigest;
934
+ }
935
+ static async open(sql, options, allowInitialize = false) {
936
+ if (options.encryptedAtRest !== true) {
937
+ throw new InfinityError("capability_denied", "external mutation journal requires an explicit encrypted-at-rest deployment assertion", { status: 503 });
938
+ }
939
+ if (options.requiredRole !== undefined) {
940
+ await assertExternalJournalSessionRole(sql, options.requiredRole);
941
+ }
942
+ if (options.signer.signerPrincipal !== options.verifier.signerPrincipal || options.signer.signingKeyId !== options.verifier.signingKeyId) {
943
+ throw new InfinityError("integrity_failure", "external mutation journal signer and verifier identity differ");
944
+ }
945
+ if (!(options.verifier instanceof Ed25519AuthorityMutationJournalVerifier)) {
946
+ throw new InfinityError("capability_denied", "external PostgreSQL mutation journal requires a verifier with internally derived Ed25519 key material", { status: 503 });
947
+ }
948
+ const configuredJournalId = identifier(options.journalId, "external_mutation_journal.configured_journal_id");
949
+ const restoreDomainId = identifier(options.restoreDomainId, "external_mutation_journal.restore_domain_id");
950
+ const verificationKeyDigest = digest(options.verifier.verificationKeyDigest, "external_mutation_journal.verification_key_digest");
951
+ const databaseIdentity = await postgresDatabaseIdentity(sql);
952
+ const unsignedProtection = {
953
+ protection_schema_version: AUTHORITY_MUTATION_PROTECTION_SCHEMA_VERSION,
954
+ journal_id: identifier(digestCanonical({
955
+ kind: "postgres-external-authority-mutation-journal/v1",
956
+ configured_journal_id: configuredJournalId,
957
+ cluster_system_identifier: databaseIdentity.system_identifier,
958
+ database_name: databaseIdentity.database_name,
959
+ verification_key_digest: verificationKeyDigest,
960
+ restore_domain_id: restoreDomainId
961
+ }), "external_mutation_journal.journal_id"),
962
+ restore_domain_id: restoreDomainId,
963
+ encrypted_at_rest: true,
964
+ durability: "durable",
965
+ signer_principal: identifier(options.signer.signerPrincipal, "external_mutation_journal.signer_principal"),
966
+ signing_key_id: identifier(options.signer.signingKeyId, "external_mutation_journal.signing_key_id")
967
+ };
968
+ const protection = deepFreeze({
969
+ ...unsignedProtection,
970
+ protection_digest: authorityMutationJournalProtectionDigest(unsignedProtection)
971
+ });
972
+ const journal = new PostgresExternalMutationJournal(sql, options.signer, options.verifier, protection, configuredJournalId, databaseIdentity.system_identifier, databaseIdentity.database_name, verificationKeyDigest);
973
+ journal.assertSignerPossession();
974
+ await journal.initialize(allowInitialize);
975
+ return journal;
976
+ }
977
+ describe() {
978
+ return this.protection;
979
+ }
980
+ async append(value) {
981
+ const envelope = parseAuthorityMutationEnvelope(value);
982
+ return runSerializableAcknowledgement(() => this.sql.begin("isolation level serializable", async (tx) => {
983
+ await tx`SELECT pg_advisory_xact_lock(hashtextextended('infinity.external-authority-mutation-journal.append', 0))`;
984
+ const store = await this.lockAndVerifyStore(tx);
985
+ await this.assertFastFrontier(tx, store);
986
+ const sequence = BigInt(envelope.commit_sequence);
987
+ const existing = await tx`
988
+ SELECT journal_sequence, prior_frontier_digest, record_digest, frontier_digest,
989
+ envelope_record, envelope_digest, acknowledgement_digest, receipt_digest, receipt_record
990
+ FROM infinity_external_journal.receipts WHERE journal_sequence = ${sequence}
991
+ `;
992
+ if (existing[0]) {
993
+ const receipt2 = await this.verifyPhysicalReceipt(tx, existing[0]);
994
+ if (canonicalJson(receipt2.anchor.record) !== canonicalJson(envelope)) {
995
+ throw new InfinityError("integrity_failure", "external authority mutation journal sequence has conflicting immutable bytes");
996
+ }
997
+ return receipt2;
998
+ }
999
+ const headSequence = dbNonnegativeBigint(store.head_sequence, "external journal head sequence");
1000
+ if (sequence !== headSequence + 1n) {
1001
+ throw new InfinityError("state_conflict", "external authority mutation journal append must extend the contiguous frontier", { status: 409 });
1002
+ }
1003
+ const receipt = this.signReceipt(envelope, store.head_frontier_digest);
1004
+ const envelopeRecord = canonicalJson(receipt.anchor.record);
1005
+ const receiptRecord = canonicalJson(receipt);
1006
+ await tx`
1007
+ SELECT infinity_external_journal.append_receipt(
1008
+ ${sequence}, ${receipt.anchor.prior_frontier_digest}, ${receipt.anchor.record_digest},
1009
+ ${receipt.anchor.frontier_digest}, ${envelopeRecord}, ${digestCanonical(receipt.anchor.record)},
1010
+ ${digestCanonical(receipt.ack)}, ${digestCanonical(receipt)}, ${receiptRecord}
1011
+ )
1012
+ `;
1013
+ return receipt;
1014
+ }));
1015
+ }
1016
+ async readRange(fromSequenceInput, limitInput) {
1017
+ const fromSequence = positiveInt64(fromSequenceInput, "external_mutation_journal.from_sequence");
1018
+ const limit = positiveInteger(limitInput, "external_mutation_journal.range_limit", 1000);
1019
+ return this.sql.begin("isolation level repeatable read", async (tx) => {
1020
+ const store = await this.readAndVerifyStore(tx);
1021
+ await this.assertFastFrontier(tx, store);
1022
+ const rows = await tx`
1023
+ SELECT journal_sequence, prior_frontier_digest, record_digest, frontier_digest,
1024
+ envelope_record, envelope_digest, acknowledgement_digest, receipt_digest, receipt_record
1025
+ FROM infinity_external_journal.receipts
1026
+ WHERE journal_sequence >= ${BigInt(fromSequence)}
1027
+ ORDER BY journal_sequence ASC LIMIT ${limit}
1028
+ `;
1029
+ const receipts = [];
1030
+ let expected = BigInt(fromSequence);
1031
+ for (const row of rows) {
1032
+ if (dbPositiveBigint(row.journal_sequence, "external journal receipt sequence") !== expected) {
1033
+ throw new InfinityError("integrity_failure", "external authority mutation journal range contains a gap");
1034
+ }
1035
+ receipts.push(await this.verifyPhysicalReceipt(tx, row));
1036
+ expected += 1n;
1037
+ }
1038
+ const priorSequence = BigInt(fromSequence) - 1n;
1039
+ const priorFrontier = priorSequence === 0n ? null : (await tx`
1040
+ SELECT frontier_digest FROM infinity_external_journal.receipts
1041
+ WHERE journal_sequence = ${priorSequence}
1042
+ `)[0]?.frontier_digest ?? null;
1043
+ if (priorSequence > 0n && priorFrontier === null) {
1044
+ throw new InfinityError("integrity_failure", "external authority mutation journal range predecessor is missing");
1045
+ }
1046
+ let chainPrior = priorFrontier;
1047
+ for (const receipt of receipts) {
1048
+ if (receipt.anchor.prior_frontier_digest !== chainPrior) {
1049
+ throw new InfinityError("integrity_failure", "external authority mutation journal signed range is not contiguous");
1050
+ }
1051
+ chainPrior = receipt.anchor.frontier_digest;
1052
+ }
1053
+ const head = await this.headFromStore(tx, store);
1054
+ const unsigned = {
1055
+ range_schema_version: AUTHORITY_MUTATION_RANGE_SCHEMA_VERSION,
1056
+ from_sequence: fromSequence,
1057
+ through_sequence: receipts.length === 0 ? priorSequence : receipts.at(-1).anchor.journal_sequence,
1058
+ prior_frontier_digest: priorFrontier,
1059
+ receipts,
1060
+ head,
1061
+ journal_id: this.protection.journal_id,
1062
+ protection_digest: this.protection.protection_digest,
1063
+ signer_principal: this.signer.signerPrincipal,
1064
+ signing_key_id: this.signer.signingKeyId
1065
+ };
1066
+ return this.verifier.verifyRange({
1067
+ ...unsigned,
1068
+ signature: this.signer.sign(canonicalJson(unsigned))
1069
+ });
1070
+ });
1071
+ }
1072
+ async head() {
1073
+ return this.sql.begin("isolation level repeatable read", async (tx) => {
1074
+ const store = await this.readAndVerifyStore(tx);
1075
+ await this.assertFastFrontier(tx, store);
1076
+ return this.headFromStore(tx, store);
1077
+ });
1078
+ }
1079
+ async initialize(allowInitialize) {
1080
+ await runSerializableAcknowledgement(() => this.sql.begin("isolation level serializable", async (tx) => {
1081
+ await tx`SELECT pg_advisory_xact_lock(hashtextextended('infinity.external-authority-mutation-journal.initialize', 0))`;
1082
+ const existing = await this.readStore(tx, allowInitialize);
1083
+ if (!existing) {
1084
+ if (!allowInitialize) {
1085
+ throw new InfinityError("integrity_failure", "external authority mutation journal protection must be initialized by the migration authority");
1086
+ }
1087
+ const rows = await tx`
1088
+ SELECT count(*) AS count FROM infinity_external_journal.receipts
1089
+ `;
1090
+ if (dbNonnegativeBigint(rows[0]?.count, "external journal receipt count") !== 0n) {
1091
+ throw new InfinityError("integrity_failure", "external authority mutation journal receipts exist without protection metadata");
1092
+ }
1093
+ const protectionRecord = canonicalJson(this.protection);
1094
+ await tx`
1095
+ INSERT INTO infinity_external_journal.store
1096
+ (singleton, protection_schema_version, configured_journal_id, journal_id,
1097
+ cluster_system_identifier, database_name, restore_domain_id,
1098
+ encrypted_at_rest, durability, signer_principal, signing_key_id,
1099
+ verification_key_digest,
1100
+ protection_digest, protection_record, head_sequence, head_frontier_digest)
1101
+ VALUES (true, ${this.protection.protection_schema_version}, ${this.configuredJournalId},
1102
+ ${this.protection.journal_id}, ${this.clusterSystemIdentifier}, ${this.databaseName},
1103
+ ${this.protection.restore_domain_id}, true, 'durable', ${this.protection.signer_principal},
1104
+ ${this.protection.signing_key_id}, ${this.verificationKeyDigest},
1105
+ ${this.protection.protection_digest}, ${protectionRecord}, 0, NULL)
1106
+ `;
1107
+ } else {
1108
+ this.verifyStore(existing);
1109
+ }
1110
+ }));
1111
+ await this.sql.begin("isolation level repeatable read", async (tx) => {
1112
+ const store = await this.readAndVerifyStore(tx);
1113
+ const rows = await tx`
1114
+ SELECT journal_sequence, prior_frontier_digest, record_digest, frontier_digest,
1115
+ envelope_record, envelope_digest, acknowledgement_digest, receipt_digest, receipt_record
1116
+ FROM infinity_external_journal.receipts ORDER BY journal_sequence ASC
1117
+ LIMIT ${MAX_STARTUP_RECEIPTS + 1}
1118
+ `;
1119
+ if (rows.length > MAX_STARTUP_RECEIPTS) {
1120
+ throw new InfinityError("state_conflict", "external authority mutation journal exceeds the bounded startup verification limit", { status: 503 });
1121
+ }
1122
+ let expected = 1n;
1123
+ let prior = null;
1124
+ for (const row of rows) {
1125
+ if (dbPositiveBigint(row.journal_sequence, "external journal receipt sequence") !== expected || row.prior_frontier_digest !== prior) {
1126
+ throw new InfinityError("integrity_failure", "external authority mutation journal has a gap or invalid predecessor");
1127
+ }
1128
+ const receipt = await this.verifyPhysicalReceipt(tx, row);
1129
+ prior = receipt.anchor.frontier_digest;
1130
+ expected += 1n;
1131
+ }
1132
+ const headSequence = dbNonnegativeBigint(store.head_sequence, "external journal head sequence");
1133
+ if (headSequence !== expected - 1n || store.head_frontier_digest !== prior) {
1134
+ throw new InfinityError("integrity_failure", "external authority mutation journal metadata is rewound or differs from its signed frontier");
1135
+ }
1136
+ });
1137
+ }
1138
+ signReceipt(envelope, priorFrontierDigest) {
1139
+ const recordDigest = digestCanonical(envelope);
1140
+ const unsignedAnchor2 = {
1141
+ anchor_schema_version: AUTHORITY_MUTATION_ANCHOR_SCHEMA_VERSION,
1142
+ journal_sequence: envelope.commit_sequence,
1143
+ prior_frontier_digest: priorFrontierDigest,
1144
+ record_digest: recordDigest,
1145
+ signer_principal: this.signer.signerPrincipal,
1146
+ signing_key_id: this.signer.signingKeyId
1147
+ };
1148
+ const frontierDigest = digestCanonical(unsignedAnchor2);
1149
+ const anchor = {
1150
+ ...unsignedAnchor2,
1151
+ frontier_digest: frontierDigest,
1152
+ signature: this.signer.sign(canonicalJson({ ...unsignedAnchor2, frontier_digest: frontierDigest })),
1153
+ record: envelope
1154
+ };
1155
+ const unsignedAcknowledgement2 = {
1156
+ ack_schema_version: AUTHORITY_MUTATION_ACK_SCHEMA_VERSION,
1157
+ acknowledged_through: envelope.commit_sequence,
1158
+ frontier_digest: frontierDigest,
1159
+ journal_id: this.protection.journal_id,
1160
+ protection_digest: this.protection.protection_digest,
1161
+ restore_domain_id: this.protection.restore_domain_id,
1162
+ encrypted_at_rest: true,
1163
+ signer_principal: this.signer.signerPrincipal,
1164
+ signing_key_id: this.signer.signingKeyId
1165
+ };
1166
+ return this.verifier.verifyAppendReceipt({
1167
+ anchor,
1168
+ ack: {
1169
+ ...unsignedAcknowledgement2,
1170
+ signature: this.signer.sign(canonicalJson(unsignedAcknowledgement2))
1171
+ }
1172
+ });
1173
+ }
1174
+ assertSignerPossession() {
1175
+ const row = {
1176
+ journal_id: this.protection.journal_id,
1177
+ protection_digest: this.protection.protection_digest,
1178
+ restore_domain_id: this.protection.restore_domain_id
1179
+ };
1180
+ const envelope = parseAuthorityMutationEnvelope({
1181
+ schema_version: "infinity.authority-mutation-reconstruction/v1",
1182
+ storage_schema_version: "infinity.external-mutation-journal-key-possession/v1",
1183
+ authority_epoch: 1,
1184
+ commit_sequence: 1,
1185
+ operation_id: "external-journal-key-possession",
1186
+ operation_digest: digestCanonical({
1187
+ purpose: "external-journal-key-possession",
1188
+ protection_digest: this.protection.protection_digest
1189
+ }),
1190
+ command: "external-journal.key-possession",
1191
+ actor_principal: this.protection.signer_principal,
1192
+ database_time: "2000-01-01T00:00:00.000Z",
1193
+ changes: [{
1194
+ change_ordinal: 1,
1195
+ table_name: "journal_protection",
1196
+ mutation_kind: "INSERT",
1197
+ row_digest: digestCanonical(row),
1198
+ row
1199
+ }]
1200
+ });
1201
+ try {
1202
+ this.signReceipt(envelope, null);
1203
+ } catch (error) {
1204
+ throw new InfinityError("integrity_failure", "external mutation journal signer cannot prove possession of the verifier key", { cause: error });
1205
+ }
1206
+ }
1207
+ async verifyPhysicalReceipt(tx, row) {
1208
+ const parsed = parseStrictJson(row.receipt_record);
1209
+ const receipt = this.verifier.verifyAppendReceipt(parsed);
1210
+ const sequence = dbPositiveBigint(row.journal_sequence, "external journal receipt sequence");
1211
+ if (BigInt(receipt.anchor.journal_sequence) !== sequence || row.prior_frontier_digest !== receipt.anchor.prior_frontier_digest || row.record_digest !== receipt.anchor.record_digest || row.frontier_digest !== receipt.anchor.frontier_digest || row.envelope_record !== canonicalJson(receipt.anchor.record) || row.envelope_digest !== digestCanonical(receipt.anchor.record) || row.acknowledgement_digest !== digestCanonical(receipt.ack) || row.receipt_digest !== digestCanonical(receipt) || row.receipt_record !== canonicalJson(receipt) || receipt.ack.journal_id !== this.protection.journal_id || receipt.ack.restore_domain_id !== this.protection.restore_domain_id || receipt.ack.protection_digest !== this.protection.protection_digest) {
1212
+ throw new InfinityError("integrity_failure", "external authority mutation journal physical row differs from its signed receipt");
1213
+ }
1214
+ const predecessor = sequence === 1n ? null : (await tx`
1215
+ SELECT frontier_digest FROM infinity_external_journal.receipts
1216
+ WHERE journal_sequence = ${sequence - 1n}
1217
+ `)[0]?.frontier_digest ?? null;
1218
+ if (receipt.anchor.prior_frontier_digest !== predecessor) {
1219
+ throw new InfinityError("integrity_failure", "external authority mutation journal signed predecessor is missing or differs");
1220
+ }
1221
+ return receipt;
1222
+ }
1223
+ async headFromStore(tx, store) {
1224
+ const sequence = dbNonnegativeBigint(store.head_sequence, "external journal head sequence");
1225
+ if (sequence === 0n)
1226
+ return null;
1227
+ const rows = await tx`
1228
+ SELECT journal_sequence, prior_frontier_digest, record_digest, frontier_digest,
1229
+ envelope_record, envelope_digest, acknowledgement_digest, receipt_digest, receipt_record
1230
+ FROM infinity_external_journal.receipts WHERE journal_sequence = ${sequence}
1231
+ `;
1232
+ const row = rows[0];
1233
+ if (!row)
1234
+ throw new InfinityError("integrity_failure", "external authority mutation journal head receipt is missing");
1235
+ const receipt = await this.verifyPhysicalReceipt(tx, row);
1236
+ if (receipt.anchor.frontier_digest !== store.head_frontier_digest) {
1237
+ throw new InfinityError("integrity_failure", "external authority mutation journal head digest differs");
1238
+ }
1239
+ return receipt.ack;
1240
+ }
1241
+ async assertFastFrontier(tx, store) {
1242
+ const rows = await tx`
1243
+ SELECT count(*) AS count, min(journal_sequence) AS minimum, max(journal_sequence) AS maximum
1244
+ FROM infinity_external_journal.receipts
1245
+ `;
1246
+ const summary = rows[0];
1247
+ const count = dbNonnegativeBigint(summary?.count, "external journal receipt count");
1248
+ const head = dbNonnegativeBigint(store.head_sequence, "external journal head sequence");
1249
+ if (count !== head || head === 0n && (summary?.minimum !== null || summary?.maximum !== null) || head > 0n && (dbPositiveBigint(summary?.minimum, "external journal minimum") !== 1n || dbPositiveBigint(summary?.maximum, "external journal maximum") !== head)) {
1250
+ throw new InfinityError("integrity_failure", "external authority mutation journal physical frontier has a gap or rewind");
1251
+ }
1252
+ }
1253
+ async lockAndVerifyStore(tx) {
1254
+ const store = await this.readStore(tx, false);
1255
+ if (!store)
1256
+ throw new InfinityError("integrity_failure", "external authority mutation journal protection metadata is missing");
1257
+ this.verifyStore(store);
1258
+ return store;
1259
+ }
1260
+ async readAndVerifyStore(tx) {
1261
+ const store = await this.readStore(tx, false);
1262
+ if (!store)
1263
+ throw new InfinityError("integrity_failure", "external authority mutation journal protection metadata is missing");
1264
+ this.verifyStore(store);
1265
+ return store;
1266
+ }
1267
+ async readStore(tx, forUpdate) {
1268
+ const query = `SELECT protection_schema_version, configured_journal_id, journal_id,
1269
+ cluster_system_identifier, database_name, restore_domain_id,
1270
+ encrypted_at_rest, durability, signer_principal, signing_key_id,
1271
+ verification_key_digest,
1272
+ protection_digest, protection_record, head_sequence, head_frontier_digest
1273
+ FROM infinity_external_journal.store WHERE singleton${forUpdate ? " FOR UPDATE" : ""}`;
1274
+ return (await tx.unsafe(query))[0];
1275
+ }
1276
+ verifyStore(store) {
1277
+ if (store.protection_schema_version !== this.protection.protection_schema_version || store.configured_journal_id !== this.configuredJournalId || store.journal_id !== this.protection.journal_id || store.cluster_system_identifier !== this.clusterSystemIdentifier || store.database_name !== this.databaseName || store.restore_domain_id !== this.protection.restore_domain_id || store.encrypted_at_rest !== true || store.durability !== "durable" || store.signer_principal !== this.protection.signer_principal || store.signing_key_id !== this.protection.signing_key_id || store.verification_key_digest !== this.verificationKeyDigest || store.protection_digest !== this.protection.protection_digest || store.protection_record !== canonicalJson(this.protection)) {
1278
+ throw new InfinityError("integrity_failure", "external authority mutation journal protection identity differs");
1279
+ }
1280
+ }
1281
+ }
1282
+ async function initializePostgresExternalMutationJournalStore(primarySql, journalSql, options) {
1283
+ await assertPostgresExternalMutationJournalDatabaseSeparated(primarySql, journalSql);
1284
+ const journal = await PostgresExternalMutationJournal.open(journalSql, options, true);
1285
+ return journal.describe();
1286
+ }
1287
+ async function applyPostgresExternalMutationJournalMigrations(sql, directory) {
1288
+ const migrationDirectory = directory ?? packagedExternalJournalMigrationDirectory(import.meta.url);
1289
+ const paths = [];
1290
+ for await (const path of new Bun.Glob("*.sql").scan({ cwd: migrationDirectory, absolute: true, onlyFiles: true })) {
1291
+ paths.push(path);
1292
+ }
1293
+ paths.sort((left, right) => left.localeCompare(right));
1294
+ const names = paths.map((path) => path.slice(path.lastIndexOf("/") + 1));
1295
+ if (JSON.stringify(names) !== JSON.stringify(Object.keys(POSTGRES_EXTERNAL_MUTATION_JOURNAL_MIGRATION_MANIFEST).sort())) {
1296
+ throw new InfinityError("integrity_failure", "external mutation journal migrations differ from the compiled immutable manifest");
1297
+ }
1298
+ return sql.begin(async (tx) => {
1299
+ await tx.unsafe(`
1300
+ CREATE SCHEMA IF NOT EXISTS infinity_external_journal;
1301
+ REVOKE ALL ON SCHEMA infinity_external_journal FROM PUBLIC;
1302
+ CREATE TABLE IF NOT EXISTS infinity_external_journal.schema_migrations (
1303
+ migration_name text PRIMARY KEY,
1304
+ checksum text NOT NULL CHECK (checksum ~ '^sha256:[0-9a-f]{64}$'),
1305
+ applied_at timestamptz NOT NULL
1306
+ );
1307
+ REVOKE ALL ON infinity_external_journal.schema_migrations FROM PUBLIC;
1308
+ `).simple();
1309
+ await tx`SELECT pg_advisory_xact_lock(hashtextextended('infinity.external-journal.schema.migrations', 0))`;
1310
+ const applied = [];
1311
+ const alreadyApplied = [];
1312
+ for (const path of paths) {
1313
+ const name = path.slice(path.lastIndexOf("/") + 1);
1314
+ const source = await Bun.file(path).text();
1315
+ const checksum = rawSha256(source);
1316
+ const expected = POSTGRES_EXTERNAL_MUTATION_JOURNAL_MIGRATION_MANIFEST[name];
1317
+ if (checksum !== expected) {
1318
+ throw new InfinityError("integrity_failure", `external mutation journal migration bytes differ: ${name}`);
1319
+ }
1320
+ const existing = await tx`
1321
+ SELECT checksum FROM infinity_external_journal.schema_migrations WHERE migration_name = ${name}
1322
+ `;
1323
+ if (existing[0]) {
1324
+ if (existing[0].checksum !== checksum) {
1325
+ throw new InfinityError("integrity_failure", `applied external mutation journal migration checksum differs: ${name}`);
1326
+ }
1327
+ alreadyApplied.push(name);
1328
+ continue;
1329
+ }
1330
+ await tx.unsafe(source).simple();
1331
+ await tx`
1332
+ INSERT INTO infinity_external_journal.schema_migrations (migration_name, checksum, applied_at)
1333
+ VALUES (${name}, ${checksum}, clock_timestamp())
1334
+ `;
1335
+ applied.push(name);
1336
+ }
1337
+ return { applied, alreadyApplied };
1338
+ });
1339
+ }
1340
+ async function configurePostgresExternalMutationJournalRole(sql, roleName = "infinity_external_journal_runtime") {
1341
+ if (!/^[a-z][a-z0-9_]{0,62}$/.test(roleName)) {
1342
+ throw new InfinityError("invalid_input", "external mutation journal role is not canonical");
1343
+ }
1344
+ const role = quotePostgresIdentifier(roleName);
1345
+ const databaseRows = await sql`
1346
+ SELECT current_user, database.datname AS database_name, owner.rolname AS database_owner
1347
+ FROM pg_database database
1348
+ JOIN pg_roles owner ON owner.oid = database.datdba
1349
+ WHERE database.datname = current_database()
1350
+ `;
1351
+ const database = databaseRows[0];
1352
+ if (!database || database.database_owner !== database.current_user) {
1353
+ throw new InfinityError("capability_denied", "external mutation journal migration role must own the current database", { status: 503 });
1354
+ }
1355
+ if (roleName === database.current_user || roleName === database.database_owner) {
1356
+ throw new InfinityError("capability_denied", "external mutation journal runtime role must be distinct from the database owner and migration role", { status: 503 });
1357
+ }
1358
+ const found = await sql`
1359
+ SELECT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = ${roleName}) AS present
1360
+ `;
1361
+ if (!found[0]?.present)
1362
+ await sql.unsafe(`CREATE ROLE ${role} NOLOGIN`);
1363
+ await sql.unsafe(`ALTER ROLE ${role} NOSUPERUSER NOCREATEDB NOCREATEROLE NOLOGIN NOREPLICATION NOBYPASSRLS`);
1364
+ const parentMemberships = await sql`
1365
+ SELECT parent.rolname AS role_name
1366
+ FROM pg_auth_members membership
1367
+ JOIN pg_roles child ON child.oid = membership.member
1368
+ JOIN pg_roles parent ON parent.oid = membership.roleid
1369
+ WHERE child.rolname = ${roleName}
1370
+ `;
1371
+ if (parentMemberships.length > 0) {
1372
+ throw new InfinityError("capability_denied", "external mutation journal runtime role has a privilege-escalating parent membership", { status: 503 });
1373
+ }
1374
+ const databaseOwnerMembership = await sql`
1375
+ SELECT pg_has_role(${roleName}, 'pg_database_owner', 'MEMBER') AS member,
1376
+ pg_has_role(${roleName}, 'pg_database_owner', 'SET') AS settable
1377
+ `;
1378
+ if (databaseOwnerMembership[0]?.member || databaseOwnerMembership[0]?.settable) {
1379
+ throw new InfinityError("capability_denied", "external mutation journal runtime role must not own the database or inherit pg_database_owner", { status: 503 });
1380
+ }
1381
+ await sql.begin(async (tx) => {
1382
+ const databaseIdentifier = quotePostgresIdentifier(database.database_name);
1383
+ await tx.unsafe(`REVOKE CREATE, TEMPORARY ON DATABASE ${databaseIdentifier} FROM PUBLIC`);
1384
+ await tx.unsafe(`REVOKE ALL PRIVILEGES ON DATABASE ${databaseIdentifier} FROM ${role}`);
1385
+ await tx.unsafe(`GRANT CONNECT ON DATABASE ${databaseIdentifier} TO ${role}`);
1386
+ await tx.unsafe(`REVOKE ALL ON SCHEMA infinity_external_journal FROM ${role}`);
1387
+ await tx.unsafe(`REVOKE ALL PRIVILEGES ON ALL TABLES IN SCHEMA infinity_external_journal FROM ${role}`);
1388
+ await tx.unsafe(`REVOKE ALL PRIVILEGES ON ALL FUNCTIONS IN SCHEMA infinity_external_journal FROM ${role}`);
1389
+ await tx.unsafe(`GRANT USAGE ON SCHEMA infinity_external_journal TO ${role}`);
1390
+ await tx.unsafe(`GRANT SELECT ON infinity_external_journal.store, infinity_external_journal.receipts TO ${role}`);
1391
+ await tx.unsafe(`GRANT EXECUTE ON FUNCTION infinity_external_journal.append_receipt(
1392
+ bigint, text, text, text, text, text, text, text, text) TO ${role}`);
1393
+ await tx.unsafe(`REVOKE CREATE ON SCHEMA infinity_external_journal FROM ${role}`);
1394
+ await tx.unsafe(`REVOKE DELETE, TRUNCATE, REFERENCES, TRIGGER
1395
+ ON ALL TABLES IN SCHEMA infinity_external_journal FROM ${role}`);
1396
+ });
1397
+ }
1398
+ async function assertExternalJournalSessionRole(sql, requiredRole) {
1399
+ if (!/^[a-z][a-z0-9_]{0,62}$/.test(requiredRole)) {
1400
+ throw new InfinityError("invalid_input", "external mutation journal required role is not canonical");
1401
+ }
1402
+ const roles = await sql`
1403
+ SELECT session_user, current_user,
1404
+ pg_has_role(session_user, ${requiredRole}, 'MEMBER') AS required,
1405
+ role.rolsuper, role.rolcreatedb, role.rolcreaterole,
1406
+ role.rolreplication, role.rolbypassrls
1407
+ FROM pg_roles role WHERE role.rolname = session_user
1408
+ `;
1409
+ const role = roles[0];
1410
+ if (!role?.required || role.current_user !== role.session_user || role.rolsuper || role.rolcreatedb || role.rolcreaterole || role.rolreplication || role.rolbypassrls) {
1411
+ throw new InfinityError("capability_denied", "external mutation journal database session is not exclusively least-privilege bound", { status: 503 });
1412
+ }
1413
+ const requiredRoleState = await sql`
1414
+ SELECT rolcanlogin, rolsuper, rolcreatedb, rolcreaterole, rolreplication, rolbypassrls
1415
+ FROM pg_roles WHERE rolname = ${requiredRole}
1416
+ `;
1417
+ const required = requiredRoleState[0];
1418
+ if (!required || required.rolcanlogin || required.rolsuper || required.rolcreatedb || required.rolcreaterole || required.rolreplication || required.rolbypassrls) {
1419
+ throw new InfinityError("capability_denied", "external mutation journal required role is not a narrow NOLOGIN role", { status: 503 });
1420
+ }
1421
+ const databaseOwnership = await sql`
1422
+ SELECT owner.rolname AS database_owner, session_user,
1423
+ pg_has_role(session_user, 'pg_database_owner', 'MEMBER') AS session_owner_member,
1424
+ pg_has_role(session_user, 'pg_database_owner', 'SET') AS session_owner_settable,
1425
+ pg_has_role(${requiredRole}, 'pg_database_owner', 'MEMBER') AS required_owner_member,
1426
+ pg_has_role(${requiredRole}, 'pg_database_owner', 'SET') AS required_owner_settable
1427
+ FROM pg_database database
1428
+ JOIN pg_roles owner ON owner.oid = database.datdba
1429
+ WHERE database.datname = current_database()
1430
+ `;
1431
+ const ownership = databaseOwnership[0];
1432
+ if (!ownership || ownership.database_owner === ownership.session_user || ownership.database_owner === requiredRole || ownership.session_owner_member || ownership.session_owner_settable || ownership.required_owner_member || ownership.required_owner_settable) {
1433
+ throw new InfinityError("capability_denied", "external mutation journal session or required role must not own the database or inherit pg_database_owner", { status: 503 });
1434
+ }
1435
+ const memberships = await sql`
1436
+ SELECT parent.rolname AS role_name, membership.inherit_option,
1437
+ membership.set_option, membership.admin_option
1438
+ FROM pg_auth_members membership
1439
+ JOIN pg_roles member ON member.oid = membership.member
1440
+ JOIN pg_roles parent ON parent.oid = membership.roleid
1441
+ WHERE member.rolname = session_user
1442
+ ORDER BY parent.rolname
1443
+ `;
1444
+ if (memberships.length !== 1 || memberships[0]?.role_name !== requiredRole || memberships[0].inherit_option !== true || memberships[0].set_option !== false || memberships[0].admin_option !== false) {
1445
+ throw new InfinityError("capability_denied", "external mutation journal login role membership graph is not exact or permits SET ROLE", { status: 503 });
1446
+ }
1447
+ const requiredParents = await sql`
1448
+ SELECT parent.rolname AS role_name
1449
+ FROM pg_auth_members membership
1450
+ JOIN pg_roles member ON member.oid = membership.member
1451
+ JOIN pg_roles parent ON parent.oid = membership.roleid
1452
+ WHERE member.rolname = ${requiredRole}
1453
+ `;
1454
+ if (requiredParents.length > 0) {
1455
+ throw new InfinityError("capability_denied", "external mutation journal required role has transitive parent memberships", { status: 503 });
1456
+ }
1457
+ const databasePrivileges = await sql`
1458
+ SELECT has_database_privilege(session_user, current_database(), 'CONNECT') AS can_connect,
1459
+ has_database_privilege(session_user, current_database(), 'CREATE') AS can_create,
1460
+ has_database_privilege(session_user, current_database(), 'TEMPORARY') AS can_temporary
1461
+ `;
1462
+ if (!databasePrivileges[0]?.can_connect || databasePrivileges[0].can_create || databasePrivileges[0].can_temporary) {
1463
+ throw new InfinityError("capability_denied", "external mutation journal database privileges are contaminated", { status: 503 });
1464
+ }
1465
+ const privileges = await sql`
1466
+ SELECT
1467
+ has_schema_privilege(session_user, 'infinity_external_journal', 'USAGE') AS schema_usage,
1468
+ has_schema_privilege(session_user, 'infinity_external_journal', 'CREATE') AS schema_create,
1469
+ has_table_privilege(session_user, 'infinity_external_journal.store', 'SELECT') AS store_select,
1470
+ has_table_privilege(session_user, 'infinity_external_journal.store', 'INSERT') AS store_insert,
1471
+ has_table_privilege(session_user, 'infinity_external_journal.store', 'DELETE') AS store_delete,
1472
+ has_table_privilege(session_user, 'infinity_external_journal.store', 'TRUNCATE') AS store_truncate,
1473
+ has_table_privilege(session_user, 'infinity_external_journal.store', 'REFERENCES') AS store_references,
1474
+ has_table_privilege(session_user, 'infinity_external_journal.store', 'TRIGGER') AS store_trigger,
1475
+ has_table_privilege(session_user, 'infinity_external_journal.receipts', 'SELECT') AS receipts_select,
1476
+ has_table_privilege(session_user, 'infinity_external_journal.receipts', 'INSERT') AS receipts_insert,
1477
+ has_table_privilege(session_user, 'infinity_external_journal.receipts', 'UPDATE') AS receipts_update,
1478
+ has_table_privilege(session_user, 'infinity_external_journal.receipts', 'DELETE') AS receipts_delete,
1479
+ has_table_privilege(session_user, 'infinity_external_journal.receipts', 'TRUNCATE') AS receipts_truncate,
1480
+ has_table_privilege(session_user, 'infinity_external_journal.receipts', 'REFERENCES') AS receipts_references,
1481
+ has_table_privilege(session_user, 'infinity_external_journal.receipts', 'TRIGGER') AS receipts_trigger,
1482
+ has_table_privilege(session_user, 'infinity_external_journal.schema_migrations', 'SELECT') AS migrations_select,
1483
+ has_function_privilege(session_user,
1484
+ 'infinity_external_journal.append_receipt(bigint,text,text,text,text,text,text,text,text)',
1485
+ 'EXECUTE') AS append_execute,
1486
+ has_column_privilege(session_user, 'infinity_external_journal.store', 'head_sequence', 'UPDATE')
1487
+ AND has_column_privilege(session_user, 'infinity_external_journal.store', 'head_frontier_digest', 'UPDATE') AS head_update,
1488
+ has_column_privilege(session_user, 'infinity_external_journal.store', 'journal_id', 'UPDATE')
1489
+ OR has_column_privilege(session_user, 'infinity_external_journal.store', 'singleton', 'UPDATE')
1490
+ OR has_column_privilege(session_user, 'infinity_external_journal.store', 'protection_schema_version', 'UPDATE')
1491
+ OR has_column_privilege(session_user, 'infinity_external_journal.store', 'configured_journal_id', 'UPDATE')
1492
+ OR has_column_privilege(session_user, 'infinity_external_journal.store', 'cluster_system_identifier', 'UPDATE')
1493
+ OR has_column_privilege(session_user, 'infinity_external_journal.store', 'database_name', 'UPDATE')
1494
+ OR has_column_privilege(session_user, 'infinity_external_journal.store', 'restore_domain_id', 'UPDATE')
1495
+ OR has_column_privilege(session_user, 'infinity_external_journal.store', 'encrypted_at_rest', 'UPDATE')
1496
+ OR has_column_privilege(session_user, 'infinity_external_journal.store', 'durability', 'UPDATE')
1497
+ OR has_column_privilege(session_user, 'infinity_external_journal.store', 'signer_principal', 'UPDATE')
1498
+ OR has_column_privilege(session_user, 'infinity_external_journal.store', 'signing_key_id', 'UPDATE')
1499
+ OR has_column_privilege(session_user, 'infinity_external_journal.store', 'verification_key_digest', 'UPDATE')
1500
+ OR has_column_privilege(session_user, 'infinity_external_journal.store', 'protection_digest', 'UPDATE')
1501
+ OR has_column_privilege(session_user, 'infinity_external_journal.store', 'protection_record', 'UPDATE')
1502
+ AS identity_update
1503
+ `;
1504
+ const value = privileges[0];
1505
+ if (!value?.schema_usage || value.schema_create || !value.store_select || value.store_insert || value.store_delete || value.store_truncate || value.store_references || value.store_trigger || !value.receipts_select || value.receipts_insert || value.receipts_update || value.receipts_delete || value.receipts_truncate || value.receipts_references || value.receipts_trigger || value.migrations_select || !value.append_execute || value.head_update || value.identity_update) {
1506
+ throw new InfinityError("capability_denied", "external mutation journal database session privileges are contaminated", { status: 503 });
1507
+ }
1508
+ }
1509
+ async function assertPostgresExternalMutationJournalDatabaseSeparated(primarySql, journalSql) {
1510
+ if (primarySql === journalSql) {
1511
+ throw new InfinityError("capability_denied", "primary authority and external mutation journal require separate SQL pools and databases", { status: 503 });
1512
+ }
1513
+ const [primary, journal] = await Promise.all([
1514
+ postgresDatabaseIdentity(primarySql),
1515
+ postgresDatabaseIdentity(journalSql)
1516
+ ]);
1517
+ if (primary.database_name === journal.database_name && primary.server_address === journal.server_address && primary.server_port === journal.server_port) {
1518
+ throw new InfinityError("capability_denied", "primary authority and external mutation journal must use separate PostgreSQL databases", { status: 503 });
1519
+ }
1520
+ if (primary.system_identifier === journal.system_identifier) {
1521
+ throw new InfinityError("capability_denied", "primary authority and external mutation journal must use independently durable PostgreSQL clusters", { status: 503 });
1522
+ }
1523
+ }
1524
+ async function postgresDatabaseIdentity(sql) {
1525
+ const rows = await sql`
1526
+ SELECT current_database() AS database_name,
1527
+ COALESCE(inet_server_addr()::text, 'local') AS server_address,
1528
+ COALESCE(inet_server_port(), 0)::integer AS server_port,
1529
+ system_identifier::text AS system_identifier
1530
+ FROM pg_control_system()
1531
+ `;
1532
+ const row = rows[0];
1533
+ if (!row)
1534
+ throw new InfinityError("integrity_failure", "PostgreSQL database identity is unavailable");
1535
+ return {
1536
+ database_name: row.database_name,
1537
+ server_address: row.server_address ?? "local",
1538
+ server_port: row.server_port ?? 0,
1539
+ system_identifier: String(row.system_identifier)
1540
+ };
1541
+ }
1542
+ function packagedExternalJournalMigrationDirectory(moduleUrl) {
1543
+ let candidate = dirname(fileURLToPath(moduleUrl));
1544
+ const filesystemRoot = parse(candidate).root;
1545
+ while (candidate !== filesystemRoot) {
1546
+ const packagePath = join(candidate, "package.json");
1547
+ const migrationsPath = join(candidate, "migrations", "external-journal");
1548
+ if (existsSync(packagePath) && existsSync(migrationsPath)) {
1549
+ try {
1550
+ const packageManifest = JSON.parse(readFileSync(packagePath, "utf8"));
1551
+ if (packageManifest.name === "@hasna/infinity")
1552
+ return migrationsPath;
1553
+ } catch {
1554
+ throw new InfinityError("integrity_failure", "Infinity package manifest cannot locate external journal migrations");
1555
+ }
1556
+ }
1557
+ candidate = dirname(candidate);
1558
+ }
1559
+ throw new InfinityError("invalid_input", "no external mutation journal PostgreSQL migrations were found");
1560
+ }
1561
+ function rawSha256(source) {
1562
+ return `sha256:${new Bun.CryptoHasher("sha256").update(source).digest("hex")}`;
1563
+ }
1564
+ function quotePostgresIdentifier(value) {
1565
+ return `"${value.replaceAll('"', '""')}"`;
1566
+ }
1567
+ function dbPositiveBigint(value, label) {
1568
+ return BigInt(positiveInt64(typeof value === "string" ? BigInt(value) : value, label));
1569
+ }
1570
+ function dbNonnegativeBigint(value, label) {
1571
+ if (typeof value === "bigint") {
1572
+ if (value < 0n || value > 9223372036854775807n) {
1573
+ throw new InfinityError("integrity_failure", `${label} is outside the supported range`);
1574
+ }
1575
+ return value;
1576
+ }
1577
+ if (typeof value === "string" && /^\d+$/.test(value)) {
1578
+ const parsed = BigInt(value);
1579
+ if (parsed > 9223372036854775807n) {
1580
+ throw new InfinityError("integrity_failure", `${label} exceeds the supported range`);
1581
+ }
1582
+ return parsed;
1583
+ }
1584
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) {
1585
+ throw new InfinityError("integrity_failure", `${label} is invalid`);
1586
+ }
1587
+ return BigInt(value);
1588
+ }
1589
+
1590
+ // src/persistence/migrations.ts
1591
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
1592
+ import { dirname as dirname2, join as join2, parse as parse2 } from "path";
1593
+ import { fileURLToPath as fileURLToPath2 } from "url";
1594
+ var DEFAULT_POSTGRES_ROLES = {
1595
+ runtimeRole: "infinity_runtime",
1596
+ reconcilerRole: "infinity_reconciler",
1597
+ journalAckerRole: "infinity_journal_acker"
1598
+ };
1599
+ var POSTGRES_MIGRATION_MANIFEST = Object.freeze({
1600
+ "0001_authority.sql": "sha256:bfcff16873cf8664ee46aeea581f9f4d212bac05cf3bbc1817dc495434f7f1d6",
1601
+ "0002_effect_step_global_owner.sql": "sha256:3342801f79c4cf6321bbeaa3928cf7a0d4a3e9a391505274e88931c48eb4632d",
1602
+ "0003_effect_journal_observed_head.sql": "sha256:0cf5388c8665f9727df4f1f7430761086bd2fa03d0e1e2aaddecda9ca7790aba",
1603
+ "0004_authority_mutation_journal_binding.sql": "sha256:ddeb4d190016536f61ea55e3e0c36a5fa28211af5ed2c9a41c752ce3b1a857d0",
1604
+ "20260710_checkpoint_broker_v1.sql": "sha256:3e250bee6358188cf30acb2ab14f31edfdf94c2b73502793b91a7caaf28c3d8f",
1605
+ "20260711_checkpoint_trust_v2.sql": "sha256:5165e08ae32b671838a2ee9349b35cf7007afade70ec19694ad27334bdaac065",
1606
+ "20260711_verifier_cleanup_v2.sql": "sha256:fe06ed0ec0bde18310321e7cce19c885f127e550ac1ea365b159be4effb71e79",
1607
+ "20260711_accounts_operation_v1.sql": "sha256:7d9caa3aa9fbbdab501e81b363ec5258b006a86572211eccb563b000d52d3a62",
1608
+ "20260711_z_accounts_claim_authority_v2.sql": "sha256:1da1ace609a4813472f2637d61b435cf372bd9d0f66694b3c502e17da3b9de38",
1609
+ "20260711_zz_sandbox_dispatch_authority_v1.sql": "sha256:543bb2c9b392c98c2069c437616e3d7d0fbf1d02cb8d1f6e09f1bad83238e69f",
1610
+ "20260712_sandbox_dispatch_authority_v2.sql": "sha256:c93b5b31d4f94549018300c94a568a4b8b5a403f28bf04d6177d14726518b3e3"
1611
+ });
1612
+ async function applyPostgresMigrations(sql, directory) {
1613
+ const migrationDirectory = directory ?? packagedMigrationDirectory(import.meta.url);
1614
+ const paths = [];
1615
+ for await (const path of new Bun.Glob("*.sql").scan({ cwd: migrationDirectory, absolute: true, onlyFiles: true })) {
1616
+ paths.push(path);
1617
+ }
1618
+ paths.sort((left, right) => left.localeCompare(right));
1619
+ if (paths.length === 0)
1620
+ throw new InfinityError("invalid_input", "no PostgreSQL migrations were found");
1621
+ const names = paths.map((path) => path.slice(path.lastIndexOf("/") + 1));
1622
+ if (canonicalStringArray(names) !== canonicalStringArray(Object.keys(POSTGRES_MIGRATION_MANIFEST))) {
1623
+ throw new InfinityError("integrity_failure", "PostgreSQL migration files differ from the compiled immutable manifest");
1624
+ }
1625
+ return sql.begin(async (tx) => {
1626
+ await tx.unsafe(`
1627
+ CREATE SCHEMA IF NOT EXISTS infinity;
1628
+ CREATE TABLE IF NOT EXISTS infinity.schema_migrations (
1629
+ migration_name text PRIMARY KEY,
1630
+ checksum text NOT NULL CHECK (checksum ~ '^sha256:[0-9a-f]{64}$'),
1631
+ applied_at timestamptz NOT NULL
1632
+ );
1633
+ `).simple();
1634
+ await tx`SELECT pg_advisory_xact_lock(hashtextextended('infinity.schema.migrations', 0))`;
1635
+ const applied = [];
1636
+ const alreadyApplied = [];
1637
+ for (const path of paths) {
1638
+ const name = path.slice(path.lastIndexOf("/") + 1);
1639
+ if (!/^(?:\d{4}|\d{8})_[a-z0-9_]+\.sql$/.test(name)) {
1640
+ throw new InfinityError("invalid_input", "migration filename is not canonical");
1641
+ }
1642
+ const source = await Bun.file(path).text();
1643
+ const checksum = rawSha2562(source);
1644
+ const compiledChecksum = POSTGRES_MIGRATION_MANIFEST[name];
1645
+ if (checksum !== compiledChecksum) {
1646
+ throw new InfinityError("integrity_failure", `migration bytes differ from compiled manifest: ${name}`);
1647
+ }
1648
+ const existing = await tx`
1649
+ SELECT checksum FROM infinity.schema_migrations WHERE migration_name = ${name}
1650
+ `;
1651
+ if (existing.length > 0) {
1652
+ if (existing[0].checksum !== checksum) {
1653
+ throw new InfinityError("integrity_failure", `applied migration checksum differs: ${name}`);
1654
+ }
1655
+ alreadyApplied.push(name);
1656
+ continue;
1657
+ }
1658
+ await tx.unsafe(source).simple();
1659
+ await tx`
1660
+ INSERT INTO infinity.schema_migrations (migration_name, checksum, applied_at)
1661
+ VALUES (${name}, ${checksum}, clock_timestamp())
1662
+ `;
1663
+ applied.push(name);
1664
+ }
1665
+ return { applied, alreadyApplied };
1666
+ });
1667
+ }
1668
+ function packagedMigrationDirectory(moduleUrl) {
1669
+ let candidate = dirname2(fileURLToPath2(moduleUrl));
1670
+ const filesystemRoot = parse2(candidate).root;
1671
+ while (candidate !== filesystemRoot) {
1672
+ const packagePath = join2(candidate, "package.json");
1673
+ const migrationsPath = join2(candidate, "migrations");
1674
+ if (existsSync2(packagePath) && existsSync2(migrationsPath)) {
1675
+ try {
1676
+ const packageManifest = JSON.parse(readFileSync2(packagePath, "utf8"));
1677
+ if (packageManifest.name === "@hasna/infinity")
1678
+ return migrationsPath;
1679
+ } catch {
1680
+ throw new InfinityError("integrity_failure", "Infinity package manifest cannot locate PostgreSQL migrations");
1681
+ }
1682
+ }
1683
+ candidate = dirname2(candidate);
1684
+ }
1685
+ throw new InfinityError("invalid_input", "no PostgreSQL migrations were found");
1686
+ }
1687
+ async function configurePostgresRoles(sql, roles = DEFAULT_POSTGRES_ROLES) {
1688
+ const runtimeRole = sqlIdentifier(roles.runtimeRole, "runtime role");
1689
+ const reconcilerRole = sqlIdentifier(roles.reconcilerRole, "reconciler role");
1690
+ const journalAckerRole = sqlIdentifier(roles.journalAckerRole, "journal acker role");
1691
+ if (new Set([runtimeRole, reconcilerRole, journalAckerRole]).size !== 3) {
1692
+ throw new InfinityError("invalid_input", "runtime, reconciler, and journal acker roles must differ");
1693
+ }
1694
+ const session = await sql`
1695
+ SELECT current_user, database.datname AS database_name, owner.rolname AS database_owner
1696
+ FROM pg_database database
1697
+ JOIN pg_roles owner ON owner.oid = database.datdba
1698
+ WHERE database.datname = current_database()
1699
+ `;
1700
+ const database = session[0];
1701
+ if ([runtimeRole, reconcilerRole, journalAckerRole].includes(database?.current_user ?? "")) {
1702
+ throw new InfinityError("capability_denied", "migration session must be distinct from authority service roles", { status: 403 });
1703
+ }
1704
+ if (!database || database.database_owner !== database.current_user) {
1705
+ throw new InfinityError("capability_denied", "Infinity migration role must be the dedicated owner of the current database", { status: 503 });
1706
+ }
1707
+ for (const role of [runtimeRole, reconcilerRole, journalAckerRole]) {
1708
+ const found = await sql`SELECT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = ${role}) AS present`;
1709
+ if (!found[0]?.present)
1710
+ await sql.unsafe(`CREATE ROLE ${quoteIdentifier(role)} NOLOGIN`);
1711
+ await sql.unsafe(`ALTER ROLE ${quoteIdentifier(role)} NOSUPERUSER NOCREATEDB NOCREATEROLE NOLOGIN NOREPLICATION NOBYPASSRLS`);
1712
+ const parentMemberships = await sql`
1713
+ SELECT parent.rolname AS role_name
1714
+ FROM pg_auth_members membership
1715
+ JOIN pg_roles child ON child.oid = membership.member
1716
+ JOIN pg_roles parent ON parent.oid = membership.roleid
1717
+ WHERE child.rolname = ${role}
1718
+ `;
1719
+ if (parentMemberships.length > 0) {
1720
+ throw new InfinityError("capability_denied", `Infinity authority role ${role} has a privilege-escalating parent membership`, { status: 503 });
1721
+ }
1722
+ const databaseOwnerMembership = await sql`
1723
+ SELECT pg_has_role(${role}, 'pg_database_owner', 'MEMBER') AS member,
1724
+ pg_has_role(${role}, 'pg_database_owner', 'SET') AS settable
1725
+ `;
1726
+ if (databaseOwnerMembership[0]?.member || databaseOwnerMembership[0]?.settable) {
1727
+ throw new InfinityError("capability_denied", `Infinity authority role ${role} must not inherit or SET pg_database_owner`, { status: 503 });
1728
+ }
1729
+ }
1730
+ const runtime = quoteIdentifier(runtimeRole);
1731
+ const reconciler = quoteIdentifier(reconcilerRole);
1732
+ const journalAcker = quoteIdentifier(journalAckerRole);
1733
+ await sql.begin(async (tx) => {
1734
+ const databaseIdentifier = quoteIdentifier(database.database_name);
1735
+ await tx.unsafe(`REVOKE CREATE, TEMPORARY ON DATABASE ${databaseIdentifier} FROM PUBLIC`);
1736
+ await tx.unsafe(`REVOKE ALL PRIVILEGES ON DATABASE ${databaseIdentifier} FROM ${runtime}, ${reconciler}, ${journalAcker}`);
1737
+ await tx.unsafe(`GRANT CONNECT ON DATABASE ${databaseIdentifier} TO ${runtime}, ${reconciler}, ${journalAcker}`);
1738
+ await tx.unsafe("REVOKE ALL ON SCHEMA infinity FROM PUBLIC");
1739
+ await tx.unsafe(`REVOKE ALL PRIVILEGES ON ALL TABLES IN SCHEMA infinity FROM ${runtime}, ${reconciler}, ${journalAcker}`);
1740
+ await tx.unsafe(`REVOKE ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA infinity FROM ${runtime}, ${reconciler}, ${journalAcker}`);
1741
+ await tx.unsafe(`GRANT USAGE ON SCHEMA infinity TO ${runtime}, ${reconciler}, ${journalAcker}`);
1742
+ await tx.unsafe(`GRANT SELECT ON ALL TABLES IN SCHEMA infinity TO ${runtime}, ${reconciler}`);
1743
+ await tx.unsafe(`GRANT SELECT ON infinity.authority_runtime_meta, infinity.authority_mutation_envelopes,
1744
+ infinity.authority_mutation_acknowledgements, infinity.authority_mutation_journal_binding,
1745
+ infinity.outbox TO ${journalAcker}`);
1746
+ await tx.unsafe(`GRANT INSERT ON infinity.run_specs, infinity.runs, infinity.attempts,
1747
+ infinity.attempt_lease_grants, infinity.attempt_lease_events, infinity.queue_entries,
1748
+ infinity.admission_keys, infinity.idempotency_records, infinity.events, infinity.budget_reservations,
1749
+ infinity.event_stream_heads, infinity.outbox, infinity.external_operations,
1750
+ infinity.authority_mutation_envelopes, infinity.accounts_resource_authority,
1751
+ infinity.accounts_operation_signer_pin, infinity.accounts_serialization_domains,
1752
+ infinity.accounts_serialization_frontier_heads, infinity.accounts_model_call_operations,
1753
+ infinity.accounts_model_effect_journal, infinity.sandbox_dispatch_accounts_consume_evidence,
1754
+ infinity.sandbox_dispatch_authorizations TO ${runtime}`);
1755
+ await tx.unsafe(`GRANT UPDATE ON infinity.runs, infinity.attempts, infinity.queue_entries,
1756
+ infinity.event_stream_heads, infinity.external_operations TO ${runtime}`);
1757
+ await tx.unsafe(`GRANT UPDATE
1758
+ (head_sequence, head_digest, pin_digest, pin_record, updated_at)
1759
+ ON infinity.accounts_operation_signer_pin TO ${runtime}`);
1760
+ await tx.unsafe(`GRANT UPDATE
1761
+ (state, updated_at)
1762
+ ON infinity.accounts_resource_authority TO ${runtime}`);
1763
+ await tx.unsafe(`GRANT UPDATE
1764
+ (active_model_operation_id, revision, state_digest, state_record, updated_at)
1765
+ ON infinity.accounts_serialization_domains TO ${runtime}`);
1766
+ await tx.unsafe(`GRANT UPDATE
1767
+ (state, consume_receipt_digest, use_id, projection_digest, projection_record, updated_at)
1768
+ ON infinity.accounts_model_call_operations TO ${runtime}`);
1769
+ await tx.unsafe(`GRANT UPDATE
1770
+ (state, consume_operation_id, consume_idempotency_key, consume_request_digest,
1771
+ effect_claim_digest, canonical_intent_digest, dispatch_use_id, consumption_receipt_digest,
1772
+ consumption_receipt_record, consumed_at, provider_contact_expires_at, updated_at)
1773
+ ON infinity.sandbox_dispatch_authorizations TO ${runtime}`);
1774
+ await tx.unsafe(`GRANT UPDATE
1775
+ (next_queue_sequence, next_commit_sequence, budget_reserved_units, budget_charged_units, budget_released_units)
1776
+ ON infinity.authority_runtime_meta TO ${runtime}`);
1777
+ await tx.unsafe(`GRANT INSERT ON infinity.effect_operation_steps, infinity.effect_journal_records,
1778
+ infinity.effect_journal_observed_head TO ${reconciler}`);
1779
+ await tx.unsafe(`GRANT UPDATE ON infinity.effect_journal_frontier,
1780
+ infinity.effect_journal_observed_head TO ${reconciler}`);
1781
+ await tx.unsafe(`GRANT UPDATE (state, lease_owner, lease_expires_at) ON infinity.outbox TO ${reconciler}`);
1782
+ await tx.unsafe(`GRANT INSERT ON infinity.authority_mutation_acknowledgements TO ${journalAcker}`);
1783
+ await tx.unsafe(`GRANT INSERT ON infinity.authority_mutation_journal_binding TO ${journalAcker}`);
1784
+ await tx.unsafe(`GRANT UPDATE (journal_acknowledged_through)
1785
+ ON infinity.authority_runtime_meta TO ${journalAcker}`);
1786
+ await tx.unsafe(`GRANT UPDATE (state) ON infinity.outbox TO ${journalAcker}`);
1787
+ await tx.unsafe(`REVOKE CREATE ON SCHEMA infinity FROM ${runtime}, ${reconciler}, ${journalAcker}`);
1788
+ await tx.unsafe(`REVOKE DELETE, TRUNCATE, REFERENCES, TRIGGER ON ALL TABLES IN SCHEMA infinity FROM ${runtime}, ${reconciler}, ${journalAcker}`);
1789
+ await tx.unsafe(`REVOKE ALL ON FUNCTION infinity.capture_authority_mutation_change(),
1790
+ infinity.validate_authority_mutation_envelope(), infinity.validate_authority_mutation_acknowledgement(),
1791
+ infinity.validate_authority_ack_frontier_update(), infinity.validate_outbox_write(),
1792
+ infinity.validate_effect_journal_observed_head_update(),
1793
+ infinity.validate_accounts_operation_signer_pin_update(),
1794
+ infinity.validate_accounts_resource_authority_update(),
1795
+ infinity.validate_sandbox_dispatch_authorization_update()
1796
+ FROM PUBLIC, ${runtime}, ${reconciler}, ${journalAcker}`);
1797
+ });
1798
+ }
1799
+ function rawSha2562(source) {
1800
+ const value = new Bun.CryptoHasher("sha256").update(source).digest("hex");
1801
+ return `sha256:${value}`;
1802
+ }
1803
+ function sqlIdentifier(value, label) {
1804
+ if (!/^[a-z][a-z0-9_]{0,62}$/.test(value))
1805
+ throw new InfinityError("invalid_input", `${label} is not a canonical PostgreSQL identifier`);
1806
+ return value;
1807
+ }
1808
+ function quoteIdentifier(value) {
1809
+ return `"${value.replaceAll('"', '""')}"`;
1810
+ }
1811
+ function canonicalStringArray(values) {
1812
+ return JSON.stringify([...values].sort((left, right) => left.localeCompare(right)));
1813
+ }
1814
+
1815
+ // src/runtime/config.ts
1816
+ import {
1817
+ createPrivateKey,
1818
+ createPublicKey as createPublicKey2,
1819
+ sign as signDetached2,
1820
+ verify as verifyDetached2
1821
+ } from "crypto";
1822
+ import { closeSync, constants, fstatSync, openSync, readFileSync as readFileSync3 } from "fs";
1823
+ import { isAbsolute } from "path";
1824
+ var DIRECT_SECRET_VARIABLES = [
1825
+ "INFINITY_OPERATOR_TOKEN",
1826
+ "INFINITY_RUNTIME_DATABASE_URL",
1827
+ "INFINITY_RECONCILER_DATABASE_URL",
1828
+ "INFINITY_JOURNAL_ACKER_DATABASE_URL",
1829
+ "INFINITY_EXTERNAL_JOURNAL_DATABASE_URL",
1830
+ "INFINITY_MUTATION_SIGNING_KEY",
1831
+ "INFINITY_EFFECT_SIGNING_KEY",
1832
+ "INFINITY_MIGRATOR_DATABASE_URL",
1833
+ "INFINITY_EXTERNAL_JOURNAL_MIGRATOR_DATABASE_URL",
1834
+ "INFINITY_PROVIDER_ACTIVATION_MANIFEST"
1835
+ ];
1836
+ function loadSelfHostedMigrationConfig(environment) {
1837
+ for (const variable of DIRECT_SECRET_VARIABLES) {
1838
+ if (environment[variable] !== undefined) {
1839
+ throw new InfinityError("invalid_input", `${variable} is forbidden; configure its file reference instead`);
1840
+ }
1841
+ }
1842
+ const primaryMigratorDatabaseUrlFile = secretFile(environment, "INFINITY_MIGRATOR_DATABASE_URL_FILE");
1843
+ const externalJournalMigratorDatabaseUrlFile = secretFile(environment, "INFINITY_EXTERNAL_JOURNAL_MIGRATOR_DATABASE_URL_FILE");
1844
+ const mutationSigningKeyFile = secretFile(environment, "INFINITY_MUTATION_SIGNING_KEY_FILE");
1845
+ if (new Set([
1846
+ primaryMigratorDatabaseUrlFile,
1847
+ externalJournalMigratorDatabaseUrlFile,
1848
+ mutationSigningKeyFile
1849
+ ]).size !== 3) {
1850
+ throw new InfinityError("invalid_input", "self-hosted migration secret file references must be distinct");
1851
+ }
1852
+ return deepFreeze({
1853
+ primaryMigratorDatabaseUrlFile,
1854
+ externalJournalMigratorDatabaseUrlFile,
1855
+ mutationSigningKeyFile,
1856
+ externalJournalId: identifier(required(environment, "INFINITY_EXTERNAL_JOURNAL_ID"), "external journal ID"),
1857
+ externalJournalRestoreDomainId: identifier(required(environment, "INFINITY_EXTERNAL_JOURNAL_RESTORE_DOMAIN_ID"), "external journal restore domain"),
1858
+ mutationSignerPrincipal: identifier(required(environment, "INFINITY_MUTATION_SIGNER_PRINCIPAL"), "mutation signer principal"),
1859
+ mutationSigningKeyId: identifier(required(environment, "INFINITY_MUTATION_SIGNING_KEY_ID"), "mutation signing key ID"),
1860
+ runtimeRole: role(environment.INFINITY_RUNTIME_ROLE ?? "infinity_runtime", "runtime role"),
1861
+ reconcilerRole: role(environment.INFINITY_RECONCILER_ROLE ?? "infinity_reconciler", "reconciler role"),
1862
+ journalAckerRole: role(environment.INFINITY_JOURNAL_ACKER_ROLE ?? "infinity_journal_acker", "journal acker role"),
1863
+ externalJournalRole: role(environment.INFINITY_EXTERNAL_JOURNAL_ROLE ?? "infinity_external_journal_runtime", "external journal role")
1864
+ });
1865
+ }
1866
+ class FileEd25519Signer {
1867
+ signerPrincipal;
1868
+ signingKeyId;
1869
+ privateKey;
1870
+ publicKey;
1871
+ constructor(signerPrincipal, signingKeyId, privateKey, publicKey) {
1872
+ this.signerPrincipal = signerPrincipal;
1873
+ this.signingKeyId = signingKeyId;
1874
+ this.privateKey = privateKey;
1875
+ this.publicKey = publicKey;
1876
+ }
1877
+ static open(path, principalInput, keyIdInput) {
1878
+ assertOwnerOnlySecretFile(path, "Ed25519 signing key file");
1879
+ const bytes = readOwnerOnlySecretFile(path, "Ed25519 signing key file");
1880
+ try {
1881
+ let privateKey;
1882
+ try {
1883
+ privateKey = createPrivateKey(bytes);
1884
+ } catch {
1885
+ throw new InfinityError("invalid_input", "Ed25519 signing key file is invalid");
1886
+ }
1887
+ if (privateKey.asymmetricKeyType !== "ed25519") {
1888
+ throw new InfinityError("invalid_input", "signing key must be Ed25519");
1889
+ }
1890
+ return new FileEd25519Signer(identifier(principalInput, "signer principal"), identifier(keyIdInput, "signing key ID"), privateKey, createPublicKey2(privateKey));
1891
+ } finally {
1892
+ bytes.fill(0);
1893
+ }
1894
+ }
1895
+ publicKeySpki() {
1896
+ return this.publicKey.export({ type: "spki", format: "der" }).toString("base64url");
1897
+ }
1898
+ sign(payload) {
1899
+ return signDetached2(null, Buffer.from(payload, "utf8"), this.privateKey).toString("base64url");
1900
+ }
1901
+ verify(payload, signature) {
1902
+ if (!/^[A-Za-z0-9_-]{80,128}$/.test(signature))
1903
+ return false;
1904
+ try {
1905
+ return verifyDetached2(null, Buffer.from(payload, "utf8"), this.publicKey, Buffer.from(signature, "base64url"));
1906
+ } catch {
1907
+ return false;
1908
+ }
1909
+ }
1910
+ }
1911
+ function assertOwnerOnlySecretFile(path, label) {
1912
+ if (!isAbsolute(path))
1913
+ throw new InfinityError("invalid_input", `${label} path must be absolute`);
1914
+ let descriptor;
1915
+ try {
1916
+ descriptor = openSync(path, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
1917
+ } catch {
1918
+ throw new InfinityError("invalid_input", `${label} is unavailable`);
1919
+ }
1920
+ try {
1921
+ assertOwnerOnlyDescriptor(descriptor, label);
1922
+ } finally {
1923
+ closeSync(descriptor);
1924
+ }
1925
+ }
1926
+ function readOwnerOnlySecretFile(path, label) {
1927
+ if (!isAbsolute(path))
1928
+ throw new InfinityError("invalid_input", `${label} path must be absolute`);
1929
+ let descriptor;
1930
+ try {
1931
+ descriptor = openSync(path, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
1932
+ } catch {
1933
+ throw new InfinityError("invalid_input", `${label} is unavailable`);
1934
+ }
1935
+ try {
1936
+ assertOwnerOnlyDescriptor(descriptor, label);
1937
+ return readFileSync3(descriptor);
1938
+ } finally {
1939
+ closeSync(descriptor);
1940
+ }
1941
+ }
1942
+ function readPostgresUrlFile(path, label) {
1943
+ assertOwnerOnlySecretFile(path, label);
1944
+ const bytes = readOwnerOnlySecretFile(path, label);
1945
+ try {
1946
+ let value;
1947
+ try {
1948
+ value = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
1949
+ } catch {
1950
+ throw new InfinityError("invalid_input", `${label} must contain valid UTF-8`);
1951
+ }
1952
+ if (value.endsWith(`
1953
+ `))
1954
+ value = value.slice(0, -1);
1955
+ if (value.endsWith("\r"))
1956
+ value = value.slice(0, -1);
1957
+ if (value.includes(`
1958
+ `) || value.includes("\r")) {
1959
+ throw new InfinityError("invalid_input", `${label} must contain one PostgreSQL URL`);
1960
+ }
1961
+ let url;
1962
+ try {
1963
+ url = new URL(value);
1964
+ } catch {
1965
+ throw new InfinityError("invalid_input", `${label} must contain a PostgreSQL URL`);
1966
+ }
1967
+ if (url.protocol !== "postgresql:" && url.protocol !== "postgres:") {
1968
+ throw new InfinityError("invalid_input", `${label} must contain a PostgreSQL URL`);
1969
+ }
1970
+ if (!url.hostname || !url.pathname || url.pathname === "/") {
1971
+ throw new InfinityError("invalid_input", `${label} PostgreSQL URL is incomplete`);
1972
+ }
1973
+ if (!isDatabaseLoopback(url.hostname) && url.searchParams.get("sslmode") !== "verify-full") {
1974
+ throw new InfinityError("capability_denied", `${label} must require sslmode=verify-full for a non-loopback PostgreSQL endpoint`, { status: 503 });
1975
+ }
1976
+ return value;
1977
+ } finally {
1978
+ bytes.fill(0);
1979
+ }
1980
+ }
1981
+ function secretFile(environment, variable) {
1982
+ const path = required(environment, variable);
1983
+ assertOwnerOnlySecretFile(path, variable);
1984
+ return path;
1985
+ }
1986
+ function assertOwnerOnlyDescriptor(descriptor, label) {
1987
+ const state = fstatSync(descriptor);
1988
+ const effectiveUserId = process.geteuid?.();
1989
+ if (!state.isFile())
1990
+ throw new InfinityError("invalid_input", `${label} must be a regular file`);
1991
+ if (state.size <= 0 || state.size > 65536) {
1992
+ throw new InfinityError("invalid_input", `${label} size is outside the accepted bound`);
1993
+ }
1994
+ if (effectiveUserId === undefined || (state.mode & 63) !== 0 || state.uid !== effectiveUserId) {
1995
+ throw new InfinityError("capability_denied", `${label} must be owner-only`, { status: 503 });
1996
+ }
1997
+ }
1998
+ function required(environment, variable) {
1999
+ const value = environment[variable];
2000
+ if (value === undefined || value.length === 0)
2001
+ throw new InfinityError("invalid_input", `${variable} is required`);
2002
+ return value;
2003
+ }
2004
+ function role(value, label) {
2005
+ if (!/^[a-z][a-z0-9_]{0,62}$/.test(value))
2006
+ throw new InfinityError("invalid_input", `${label} is invalid`);
2007
+ return value;
2008
+ }
2009
+ function isDatabaseLoopback(value) {
2010
+ return value === "localhost" || value === "::1" || value === "[::1]" || value.startsWith("127.");
2011
+ }
2012
+
2013
+ // src/persistence/self-hosted-migrate-main.ts
2014
+ async function runSelfHostedMigrations(environment = process.env) {
2015
+ const config = loadSelfHostedMigrationConfig(environment);
2016
+ const primarySql = new SQL(readPostgresUrlFile(config.primaryMigratorDatabaseUrlFile, "primary migrator database URL file"), { max: 1, connectionTimeout: 5 });
2017
+ const externalSql = new SQL(readPostgresUrlFile(config.externalJournalMigratorDatabaseUrlFile, "external journal migrator database URL file"), { max: 1, connectionTimeout: 5 });
2018
+ try {
2019
+ const primary = await applyPostgresMigrations(primarySql);
2020
+ await configurePostgresRoles(primarySql, {
2021
+ runtimeRole: config.runtimeRole,
2022
+ reconcilerRole: config.reconcilerRole,
2023
+ journalAckerRole: config.journalAckerRole
2024
+ });
2025
+ const external = await applyPostgresExternalMutationJournalMigrations(externalSql);
2026
+ await configurePostgresExternalMutationJournalRole(externalSql, config.externalJournalRole);
2027
+ const signer = FileEd25519Signer.open(config.mutationSigningKeyFile, config.mutationSignerPrincipal, config.mutationSigningKeyId);
2028
+ const verifier = Ed25519AuthorityMutationJournalVerifier.fromSpki(signer.signerPrincipal, signer.signingKeyId, signer.publicKeySpki());
2029
+ const protection = await initializePostgresExternalMutationJournalStore(primarySql, externalSql, {
2030
+ signer,
2031
+ verifier,
2032
+ journalId: config.externalJournalId,
2033
+ restoreDomainId: config.externalJournalRestoreDomainId,
2034
+ encryptedAtRest: true
2035
+ });
2036
+ process.stdout.write(`${canonicalJson({
2037
+ ok: true,
2038
+ primary,
2039
+ external,
2040
+ external_journal_protection_sha256: protection.protection_digest
2041
+ })}
2042
+ `);
2043
+ } finally {
2044
+ await Promise.allSettled([
2045
+ primarySql.close({ timeout: 1 }),
2046
+ externalSql.close({ timeout: 1 })
2047
+ ]);
2048
+ }
2049
+ }
2050
+ if (import.meta.main) {
2051
+ try {
2052
+ await runSelfHostedMigrations();
2053
+ } catch (error) {
2054
+ process.stderr.write(`${canonicalJson({
2055
+ event: "self_hosted_migration_failed",
2056
+ error_class: error instanceof Error ? error.name : "UnknownError"
2057
+ })}
2058
+ `);
2059
+ process.exitCode = 1;
2060
+ }
2061
+ }
2062
+ export {
2063
+ runSelfHostedMigrations
2064
+ };