@runuai/host 0.9.148 → 0.9.150

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 (93) hide show
  1. package/db/migrations/0024_task_env_mode.sql +9 -0
  2. package/db/migrations/0025_project_env_authority.sql +147 -0
  3. package/db/migrations/0026_project_env_replica.sql +150 -0
  4. package/db/migrations/meta/_journal.json +21 -0
  5. package/db/schema.ts +312 -2
  6. package/images/standard/Dockerfile +44 -6
  7. package/images/standard/container/uai-init +85 -2
  8. package/images/standard/container/uai-task-forwarder.mjs +303 -0
  9. package/lib/agent-cli.ts +18 -1
  10. package/lib/agent.ts +99 -34
  11. package/lib/agents/codex.ts +9 -1
  12. package/lib/agents/cursor.ts +15 -1
  13. package/lib/agents/grok.ts +8 -0
  14. package/lib/agents/kimi.ts +1 -0
  15. package/lib/agents/machine-durable.ts +2 -0
  16. package/lib/agents/opencode.ts +1 -0
  17. package/lib/agents/proc.ts +6 -1
  18. package/lib/agents/transport.ts +9 -5
  19. package/lib/browser-testing.ts +6 -0
  20. package/lib/codex-auth.ts +2 -0
  21. package/lib/credential-store.ts +141 -6
  22. package/lib/engine-accounts.ts +1 -0
  23. package/lib/env-file.ts +7 -0
  24. package/lib/env-schema.ts +841 -51
  25. package/lib/git-diff.ts +1 -0
  26. package/lib/git-identity.ts +2 -0
  27. package/lib/github-tokens.ts +1 -0
  28. package/lib/host-env.ts +98 -18
  29. package/lib/lease.ts +11 -3
  30. package/lib/machine-gateway-tunnel.ts +195 -17
  31. package/lib/machine-project-env-loader.ts +152 -0
  32. package/lib/managed-runtime.ts +270 -51
  33. package/lib/mcp-gateway.ts +777 -26
  34. package/lib/orchestrator.ts +408 -81
  35. package/lib/project-env-authority.ts +1319 -0
  36. package/lib/project-env-feature.ts +74 -0
  37. package/lib/project-env-loader.ts +237 -0
  38. package/lib/project-env-replica.ts +2094 -0
  39. package/lib/project-env-schema-capture.ts +342 -0
  40. package/lib/project-env-staging.ts +148 -0
  41. package/lib/project-env-task-policy.ts +176 -0
  42. package/lib/runtime-archive.ts +79 -16
  43. package/lib/runtime-authority.ts +44 -0
  44. package/lib/runtime-state.ts +175 -10
  45. package/lib/secrets-proxy/address.ts +72 -0
  46. package/lib/secrets-proxy/audit.ts +30 -0
  47. package/lib/secrets-proxy/ca-store.ts +200 -0
  48. package/lib/secrets-proxy/ca.ts +470 -0
  49. package/lib/secrets-proxy/container-steps.ts +138 -0
  50. package/lib/secrets-proxy/der.ts +273 -0
  51. package/lib/secrets-proxy/engine.ts +1633 -0
  52. package/lib/secrets-proxy/host.ts +93 -0
  53. package/lib/secrets-proxy/hostname.ts +60 -0
  54. package/lib/secrets-proxy/placeholders.ts +196 -0
  55. package/lib/secrets-proxy/policy.ts +141 -0
  56. package/lib/secrets-proxy/readiness.ts +148 -0
  57. package/lib/secrets-proxy/runtime.ts +582 -0
  58. package/lib/secrets-proxy/scrub.ts +559 -0
  59. package/lib/secrets-proxy/sidecar-view.ts +139 -0
  60. package/lib/secrets-proxy/task-scripts.ts +262 -0
  61. package/lib/secrets-proxy/task-trust.ts +574 -0
  62. package/lib/secrets-proxy/trust-watch.ts +80 -0
  63. package/lib/shared-files-s3.ts +3 -0
  64. package/lib/sidecar-guard.ts +58 -0
  65. package/lib/sidecar-resume.ts +51 -3
  66. package/lib/standard-image.ts +50 -12
  67. package/lib/task-env-forwarder.ts +228 -0
  68. package/lib/task-env-source.ts +193 -0
  69. package/lib/task-environment/apple-container.ts +45 -11
  70. package/lib/task-environment/docker.ts +51 -12
  71. package/lib/task-environment/index.ts +6 -2
  72. package/lib/task-environment/machine-task-up.ts +258 -25
  73. package/lib/task-environment/machine.ts +80 -22
  74. package/lib/task-environment/registry.ts +3 -2
  75. package/lib/task-environment/self-identity.ts +20 -0
  76. package/lib/task-environment/self.ts +92 -16
  77. package/lib/task-environment/types.ts +63 -0
  78. package/lib/task-gateway-port.ts +22 -0
  79. package/lib/task-gateway-protocol.ts +190 -0
  80. package/lib/task-gateway-recovery.ts +44 -0
  81. package/lib/task-gateway-state.ts +497 -0
  82. package/lib/transcript.ts +1 -0
  83. package/package.json +13 -1
  84. package/scripts/agent/env-hook.sh +626 -0
  85. package/scripts/agent/task-up.sh +476 -27
  86. package/scripts/agent/uai-env-exec.sh +26 -0
  87. package/src/cli.ts +26 -3
  88. package/src/index.ts +273 -19
  89. package/src/main.ts +939 -48
  90. package/src/project-env-latest.ts +130 -0
  91. package/src/project-env-policy.ts +281 -0
  92. package/src/project-env-wording.ts +47 -0
  93. package/src/protocol.ts +2219 -15
@@ -0,0 +1,1319 @@
1
+ /**
2
+ * Durable owned-runner project-env authority (ADR-153).
3
+ *
4
+ * This module deliberately has no websocket knowledge. It owns the one SQLite
5
+ * transaction that couples a value/head CAS, project record version, ordered
6
+ * cursor and replay receipt. Receipts contain a keyed MAC of a request and a
7
+ * value-free outcome; a plaintext set value is never durable outside its
8
+ * encrypted host_project_env row.
9
+ */
10
+
11
+ import { createHmac, timingSafeEqual } from "node:crypto";
12
+
13
+ import { and, asc, eq, sql } from "drizzle-orm";
14
+
15
+ import {
16
+ INITIAL_PROJECT_ENV_POLICY_EPOCH,
17
+ isHostOpId,
18
+ isProjectEnvBindingState,
19
+ isProjectEnvBindingSetFrame,
20
+ isProjectEnvDeleteFrame,
21
+ isProjectEnvDeliverySkipFrame,
22
+ isProjectEnvPolicyEpochs,
23
+ isProjectEnvReconciledFrame,
24
+ isProjectEnvReconciliationAttempt,
25
+ isProjectEnvRemoveFrame,
26
+ isProjectEnvSetFrame,
27
+ sameProjectEnvHead,
28
+ type ProjectEnvBinding,
29
+ type ProjectEnvBindingSetFrame,
30
+ type ProjectEnvBindingState,
31
+ type ProjectEnvDeleteFrame,
32
+ type ProjectEnvDeliveryCursor,
33
+ type ProjectEnvDeliverySkipFrame,
34
+ type ProjectEnvHead,
35
+ type ProjectEnvReconcileBeginFrame,
36
+ type ProjectEnvReconciledFrame,
37
+ type ProjectEnvRemoveFrame,
38
+ type ProjectEnvSetFrame,
39
+ type ProjectEnvWithholdReason,
40
+ } from "../src/protocol";
41
+ import { getDb, schema, type Db } from "./db";
42
+ import { PROJECT_ENV_BINDINGS_RUNTIME_CAPABILITY } from "./project-env-feature";
43
+ import {
44
+ mirrorProjectEnvDeletionToStore,
45
+ mirrorProjectEnvToStore,
46
+ noteProjectEnvAuthorityChange,
47
+ noteProjectEnvValueMutation,
48
+ openProjectEnvValueFromStorage,
49
+ sealProjectEnvValueForStorage,
50
+ } from "./host-env";
51
+ import { masterKey } from "./secrets";
52
+
53
+ /** Managed runtimes must claim this floor before the first begin transaction. */
54
+ export const PROJECT_ENV_AUTHORITY_RUNTIME_CAPABILITY =
55
+ PROJECT_ENV_BINDINGS_RUNTIME_CAPABILITY;
56
+
57
+ type ModernProjectEnvSetFrame = Extract<
58
+ ProjectEnvSetFrame,
59
+ { delivery: ProjectEnvDeliveryCursor }
60
+ >;
61
+ type ModernProjectEnvDeleteFrame = Extract<
62
+ ProjectEnvDeleteFrame,
63
+ { delivery: ProjectEnvDeliveryCursor }
64
+ >;
65
+
66
+ export type ProjectEnvOwnedDeliveryFrame =
67
+ | ModernProjectEnvSetFrame
68
+ | ModernProjectEnvDeleteFrame
69
+ | ProjectEnvRemoveFrame
70
+ | ProjectEnvBindingSetFrame
71
+ | ProjectEnvDeliverySkipFrame;
72
+
73
+ type ProjectEnvRecordRow = typeof schema.hostProjectEnvRecords.$inferSelect;
74
+ type ProjectEnvAuthorityRow = typeof schema.hostProjectEnvAuthority.$inferSelect;
75
+ type ProjectEnvReceiptRow =
76
+ typeof schema.hostProjectEnvDeliveryReceipts.$inferSelect;
77
+ type ProjectEnvTransaction = Parameters<Parameters<Db["transaction"]>[0]>[0];
78
+
79
+ export class ProjectEnvAuthorityCorruptError extends Error {
80
+ constructor(message: string) {
81
+ super(message);
82
+ this.name = "ProjectEnvAuthorityCorruptError";
83
+ }
84
+ }
85
+
86
+ export interface ProjectEnvAuthorityStatus {
87
+ activated: boolean;
88
+ gateOpen: boolean;
89
+ appliedThrough: ProjectEnvDeliveryCursor | null;
90
+ attemptId: string | null;
91
+ policyEpochs: number[];
92
+ legacyVersion: number;
93
+ }
94
+
95
+ export interface ProjectEnvRecordInventory {
96
+ projectId: string;
97
+ recordVersion: number;
98
+ states: ProjectEnvBindingState[];
99
+ }
100
+
101
+ export interface ProjectEnvAuthorityEntry {
102
+ key: string;
103
+ state: ProjectEnvBindingState | null;
104
+ value: string;
105
+ }
106
+
107
+ export interface ProjectEnvAuthorityProjectSnapshot {
108
+ projectId: string;
109
+ recordVersion: number;
110
+ entries: ProjectEnvAuthorityEntry[];
111
+ }
112
+
113
+ export interface ProjectEnvAuthoritySnapshot {
114
+ mode: "legacy" | "authority";
115
+ projects: ProjectEnvAuthorityProjectSnapshot[];
116
+ }
117
+
118
+ export type ProjectEnvDeliveryApplyResult =
119
+ | {
120
+ ok: true;
121
+ replayed: boolean;
122
+ appliedThrough: ProjectEnvDeliveryCursor;
123
+ state: ProjectEnvBindingState | null;
124
+ recordVersion: number | null;
125
+ }
126
+ | {
127
+ ok: false;
128
+ replayed: boolean;
129
+ consumed: boolean;
130
+ code: ProjectEnvWithholdReason;
131
+ appliedThrough: ProjectEnvDeliveryCursor;
132
+ };
133
+
134
+ export type ProjectEnvReconciliationBeginResult = {
135
+ appliedThrough: ProjectEnvDeliveryCursor;
136
+ activatedNow: boolean;
137
+ foldedLegacyKeys: Array<{ projectId: string; key: string }>;
138
+ };
139
+
140
+ export type ProjectEnvReconciliationCompleteResult =
141
+ | { ok: true; appliedThrough: ProjectEnvDeliveryCursor }
142
+ | {
143
+ ok: false;
144
+ code: ProjectEnvWithholdReason;
145
+ appliedThrough: ProjectEnvDeliveryCursor;
146
+ };
147
+
148
+ const RECEIPT_HMAC_DOMAIN = Buffer.from(
149
+ "uai.project_env.delivery_receipt.v1\0",
150
+ "utf8",
151
+ );
152
+
153
+ function exactJson(value: unknown): string {
154
+ if (value === null || typeof value !== "object") return JSON.stringify(value);
155
+ if (Array.isArray(value)) return `[${value.map(exactJson).join(",")}]`;
156
+ const object = value as Record<string, unknown>;
157
+ return `{${Object.keys(object)
158
+ .sort()
159
+ .map((key) => `${JSON.stringify(key)}:${exactJson(object[key])}`)
160
+ .join(",")}}`;
161
+ }
162
+
163
+ function canonicalDeliveryRequest(frame: ProjectEnvOwnedDeliveryFrame): unknown {
164
+ switch (frame.kind) {
165
+ case "env.var.set":
166
+ return {
167
+ kind: frame.kind,
168
+ projectId: frame.projectId,
169
+ key: frame.key,
170
+ value: frame.value,
171
+ orgId: frame.orgId ?? null,
172
+ expectedHead: frame.expectedHead,
173
+ ...(Object.hasOwn(frame, "initialBinding")
174
+ ? { initialBinding: frame.initialBinding }
175
+ : {}),
176
+ policyEpoch: frame.policyEpoch,
177
+ listingGeneration: frame.listingGeneration,
178
+ delivery: frame.delivery,
179
+ };
180
+ case "env.var.delete":
181
+ return {
182
+ kind: frame.kind,
183
+ projectId: frame.projectId,
184
+ key: frame.key,
185
+ expectedHead: frame.expectedHead,
186
+ policyEpoch: frame.policyEpoch,
187
+ listingGeneration: frame.listingGeneration,
188
+ delivery: frame.delivery,
189
+ };
190
+ case "env.var.remove":
191
+ return {
192
+ kind: frame.kind,
193
+ projectId: frame.projectId,
194
+ key: frame.key,
195
+ listingGeneration: frame.listingGeneration,
196
+ delivery: frame.delivery,
197
+ };
198
+ case "env.binding.set":
199
+ return {
200
+ kind: frame.kind,
201
+ projectId: frame.projectId,
202
+ key: frame.key,
203
+ binding: frame.binding,
204
+ expectedHead: frame.expectedHead,
205
+ policyEpoch: frame.policyEpoch,
206
+ listingGeneration: frame.listingGeneration,
207
+ orgId: frame.orgId ?? null,
208
+ delivery: frame.delivery,
209
+ };
210
+ case "env.delivery.skip":
211
+ return { kind: frame.kind, delivery: frame.delivery };
212
+ }
213
+ }
214
+
215
+ function requestMac(frame: ProjectEnvOwnedDeliveryFrame): string {
216
+ return createHmac("sha256", masterKey())
217
+ .update(RECEIPT_HMAC_DOMAIN)
218
+ .update(exactJson(canonicalDeliveryRequest(frame)))
219
+ .digest("hex");
220
+ }
221
+
222
+ function sameMac(left: string, right: string): boolean {
223
+ if (!/^[0-9a-f]{64}$/.test(left) || !/^[0-9a-f]{64}$/.test(right)) {
224
+ return false;
225
+ }
226
+ return timingSafeEqual(Buffer.from(left, "hex"), Buffer.from(right, "hex"));
227
+ }
228
+
229
+ function parseBinding(bindingJson: string | null): ProjectEnvBinding | null {
230
+ if (bindingJson === null) return null;
231
+ try {
232
+ return JSON.parse(bindingJson) as unknown as ProjectEnvBinding;
233
+ } catch {
234
+ throw new ProjectEnvAuthorityCorruptError(
235
+ "stored project environment binding is malformed",
236
+ );
237
+ }
238
+ }
239
+
240
+ function rowState(row: ProjectEnvRecordRow): ProjectEnvBindingState {
241
+ const state: ProjectEnvBindingState = {
242
+ key: row.key,
243
+ head: row.tombstone
244
+ ? {
245
+ revision: row.revision,
246
+ generation: row.generation,
247
+ tombstone: true,
248
+ valueRef: null,
249
+ }
250
+ : {
251
+ revision: row.revision,
252
+ generation: row.generation,
253
+ tombstone: false,
254
+ valueRef: { kind: "row" },
255
+ },
256
+ binding: parseBinding(row.bindingJson),
257
+ policyEpoch: row.policyEpoch,
258
+ listingGeneration: row.listingGeneration,
259
+ };
260
+ if (!isProjectEnvBindingState(state)) {
261
+ throw new ProjectEnvAuthorityCorruptError(
262
+ `stored project environment record is invalid (${row.projectId}/${row.key})`,
263
+ );
264
+ }
265
+ return state;
266
+ }
267
+
268
+ function storedRecord(
269
+ tx: ProjectEnvTransaction,
270
+ projectId: string,
271
+ key: string,
272
+ ): ProjectEnvRecordRow | null {
273
+ return (
274
+ tx
275
+ .select()
276
+ .from(schema.hostProjectEnvRecords)
277
+ .where(
278
+ and(
279
+ eq(schema.hostProjectEnvRecords.projectId, projectId),
280
+ eq(schema.hostProjectEnvRecords.key, key),
281
+ ),
282
+ )
283
+ .get() ?? null
284
+ );
285
+ }
286
+
287
+ function authorityRow(tx: ProjectEnvTransaction | Db): ProjectEnvAuthorityRow {
288
+ const row = tx
289
+ .select()
290
+ .from(schema.hostProjectEnvAuthority)
291
+ .where(eq(schema.hostProjectEnvAuthority.id, "host"))
292
+ .get();
293
+ if (!row) {
294
+ throw new ProjectEnvAuthorityCorruptError(
295
+ "project environment authority row is missing",
296
+ );
297
+ }
298
+ return row;
299
+ }
300
+
301
+ function parsePolicyEpochs(row: ProjectEnvAuthorityRow): number[] {
302
+ let value: unknown;
303
+ try {
304
+ value = JSON.parse(row.policyEpochsJson);
305
+ } catch {
306
+ throw new ProjectEnvAuthorityCorruptError(
307
+ "stored project environment policy epochs are malformed",
308
+ );
309
+ }
310
+ if (!isProjectEnvPolicyEpochs(value)) {
311
+ throw new ProjectEnvAuthorityCorruptError(
312
+ "stored project environment policy epochs are invalid",
313
+ );
314
+ }
315
+ return value;
316
+ }
317
+
318
+ function cursorFor(row: ProjectEnvAuthorityRow): ProjectEnvDeliveryCursor | null {
319
+ if (!row.activated) return null;
320
+ const cursor = { streamId: row.streamId, sequence: row.appliedSequence };
321
+ if (
322
+ !isHostOpId(row.streamId) ||
323
+ !Number.isSafeInteger(row.appliedSequence) ||
324
+ row.appliedSequence < 0
325
+ ) {
326
+ throw new ProjectEnvAuthorityCorruptError(
327
+ "stored project environment delivery cursor is invalid",
328
+ );
329
+ }
330
+ return cursor as ProjectEnvDeliveryCursor;
331
+ }
332
+
333
+ function validateAuthorityRow(row: ProjectEnvAuthorityRow): void {
334
+ parsePolicyEpochs(row);
335
+ cursorFor(row);
336
+ if (
337
+ !Number.isSafeInteger(row.legacyVersion) ||
338
+ row.legacyVersion < 0 ||
339
+ row.modernWrite ||
340
+ (row.attemptId !== null && !isHostOpId(row.attemptId)) ||
341
+ (row.attemptId === null) !== (row.attemptLegacyVersion === null) ||
342
+ (row.gateOpen && row.attemptId !== null)
343
+ ) {
344
+ throw new ProjectEnvAuthorityCorruptError(
345
+ "stored project environment authority state is invalid",
346
+ );
347
+ }
348
+ }
349
+
350
+ export function readProjectEnvAuthorityStatus(): ProjectEnvAuthorityStatus {
351
+ const row = authorityRow(getDb());
352
+ validateAuthorityRow(row);
353
+ return {
354
+ activated: row.activated,
355
+ gateOpen: row.gateOpen,
356
+ appliedThrough: cursorFor(row),
357
+ attemptId: row.attemptId,
358
+ policyEpochs: parsePolicyEpochs(row),
359
+ legacyVersion: row.legacyVersion,
360
+ };
361
+ }
362
+
363
+ /** A corrupted or closed activated authority never permits a value read. */
364
+ export function projectEnvAuthorityGateAllowsReads(): boolean {
365
+ try {
366
+ const status = readProjectEnvAuthorityStatus();
367
+ return !status.activated || status.gateOpen;
368
+ } catch {
369
+ return false;
370
+ }
371
+ }
372
+
373
+ function projectRecordVersion(
374
+ tx: ProjectEnvTransaction | Db,
375
+ projectId: string,
376
+ ): number {
377
+ const row = tx
378
+ .select({ recordVersion: schema.hostProjectEnvProjects.recordVersion })
379
+ .from(schema.hostProjectEnvProjects)
380
+ .where(eq(schema.hostProjectEnvProjects.projectId, projectId))
381
+ .get();
382
+ const version = row?.recordVersion ?? 0;
383
+ if (!Number.isSafeInteger(version) || version < 0) {
384
+ throw new ProjectEnvAuthorityCorruptError(
385
+ `stored project environment record version is invalid (${projectId})`,
386
+ );
387
+ }
388
+ return version;
389
+ }
390
+
391
+ function bumpProjectRecordVersion(
392
+ tx: ProjectEnvTransaction,
393
+ projectId: string,
394
+ ): number {
395
+ tx.insert(schema.hostProjectEnvProjects)
396
+ .values({ projectId, recordVersion: 1 })
397
+ .onConflictDoUpdate({
398
+ target: schema.hostProjectEnvProjects.projectId,
399
+ set: {
400
+ recordVersion: sql`${schema.hostProjectEnvProjects.recordVersion} + 1`,
401
+ },
402
+ })
403
+ .run();
404
+ return projectRecordVersion(tx, projectId);
405
+ }
406
+
407
+ function putState(
408
+ tx: ProjectEnvTransaction,
409
+ projectId: string,
410
+ state: ProjectEnvBindingState,
411
+ ): void {
412
+ if (!isProjectEnvBindingState(state)) {
413
+ throw new ProjectEnvAuthorityCorruptError(
414
+ "refusing to persist an invalid project environment record",
415
+ );
416
+ }
417
+ tx.insert(schema.hostProjectEnvRecords)
418
+ .values({
419
+ projectId,
420
+ key: state.key,
421
+ revision: state.head.revision,
422
+ generation: state.head.generation,
423
+ tombstone: state.head.tombstone,
424
+ bindingJson: state.binding === null ? null : JSON.stringify(state.binding),
425
+ policyEpoch: state.policyEpoch,
426
+ listingGeneration: state.listingGeneration,
427
+ updatedAt: Date.now(),
428
+ })
429
+ .onConflictDoUpdate({
430
+ target: [
431
+ schema.hostProjectEnvRecords.projectId,
432
+ schema.hostProjectEnvRecords.key,
433
+ ],
434
+ set: {
435
+ revision: state.head.revision,
436
+ generation: state.head.generation,
437
+ tombstone: state.head.tombstone,
438
+ bindingJson:
439
+ state.binding === null ? null : JSON.stringify(state.binding),
440
+ policyEpoch: state.policyEpoch,
441
+ listingGeneration: state.listingGeneration,
442
+ updatedAt: Date.now(),
443
+ },
444
+ })
445
+ .run();
446
+ }
447
+
448
+ function inventoryWith(
449
+ tx: ProjectEnvTransaction | Db,
450
+ projectId: string,
451
+ ): ProjectEnvRecordInventory {
452
+ const rows = tx
453
+ .select()
454
+ .from(schema.hostProjectEnvRecords)
455
+ .where(eq(schema.hostProjectEnvRecords.projectId, projectId))
456
+ .orderBy(asc(schema.hostProjectEnvRecords.key))
457
+ .all();
458
+ const version = projectRecordVersion(tx, projectId);
459
+ if (rows.length > 0 && version === 0) {
460
+ throw new ProjectEnvAuthorityCorruptError(
461
+ `project environment records have no version (${projectId})`,
462
+ );
463
+ }
464
+ return {
465
+ projectId,
466
+ recordVersion: version,
467
+ states: rows.map(rowState),
468
+ };
469
+ }
470
+
471
+ export function readProjectEnvRecordInventory(
472
+ projectId: string,
473
+ ): ProjectEnvRecordInventory {
474
+ return getDb().transaction((tx) => inventoryWith(tx, projectId));
475
+ }
476
+
477
+ /**
478
+ * One consistent, fail-closed value/head snapshot for the loader/proxy. Before
479
+ * activation it preserves the legacy value-only behavior. Once activated it
480
+ * returns null unless the gate is open and every live record has exactly one
481
+ * decryptable value row (and no unrecorded row exists).
482
+ */
483
+ export function readProjectEnvAuthoritySnapshot(
484
+ projectIds: readonly string[],
485
+ ): ProjectEnvAuthoritySnapshot | null {
486
+ try {
487
+ return getDb().transaction((tx) => {
488
+ const authority = authorityRow(tx);
489
+ validateAuthorityRow(authority);
490
+ if (authority.activated && !authority.gateOpen) return null;
491
+
492
+ const projects: ProjectEnvAuthorityProjectSnapshot[] = [];
493
+ for (const projectId of projectIds) {
494
+ const valueRows = tx
495
+ .select()
496
+ .from(schema.hostProjectEnv)
497
+ .where(eq(schema.hostProjectEnv.projectId, projectId))
498
+ .orderBy(asc(schema.hostProjectEnv.key))
499
+ .all();
500
+ if (!authority.activated) {
501
+ projects.push({
502
+ projectId,
503
+ recordVersion: 0,
504
+ entries: valueRows.map((row) => ({
505
+ key: row.key,
506
+ state: null,
507
+ value: openProjectEnvValueFromStorage(row.valueEnc),
508
+ })),
509
+ });
510
+ continue;
511
+ }
512
+
513
+ const inventory = inventoryWith(tx, projectId);
514
+ const values = new Map(valueRows.map((row) => [row.key, row.valueEnc]));
515
+ const entries: ProjectEnvAuthorityEntry[] = [];
516
+ for (const state of inventory.states) {
517
+ if (state.head.tombstone) {
518
+ if (values.has(state.key)) {
519
+ throw new ProjectEnvAuthorityCorruptError(
520
+ `tombstoned project environment record has a value (${projectId}/${state.key})`,
521
+ );
522
+ }
523
+ continue;
524
+ }
525
+ const valueEnc = values.get(state.key);
526
+ if (valueEnc === undefined) {
527
+ throw new ProjectEnvAuthorityCorruptError(
528
+ `live project environment record has no value (${projectId}/${state.key})`,
529
+ );
530
+ }
531
+ entries.push({
532
+ key: state.key,
533
+ state,
534
+ value: openProjectEnvValueFromStorage(valueEnc),
535
+ });
536
+ values.delete(state.key);
537
+ }
538
+ if (values.size > 0) {
539
+ throw new ProjectEnvAuthorityCorruptError(
540
+ `project environment value has no authority record (${projectId})`,
541
+ );
542
+ }
543
+ projects.push({
544
+ projectId,
545
+ recordVersion: inventory.recordVersion,
546
+ entries,
547
+ });
548
+ }
549
+ return { mode: authority.activated ? "authority" : "legacy", projects };
550
+ });
551
+ } catch {
552
+ return null;
553
+ }
554
+ }
555
+
556
+ function setModernWrite(tx: ProjectEnvTransaction, value: boolean): void {
557
+ const changed = tx
558
+ .update(schema.hostProjectEnvAuthority)
559
+ .set({ modernWrite: value, updatedAt: Date.now() })
560
+ .where(eq(schema.hostProjectEnvAuthority.id, "host"))
561
+ .run().changes;
562
+ if (changed !== 1) {
563
+ throw new ProjectEnvAuthorityCorruptError(
564
+ "project environment authority row could not be fenced",
565
+ );
566
+ }
567
+ }
568
+
569
+ function backfillLegacyValues(tx: ProjectEnvTransaction): void {
570
+ const record = tx
571
+ .select({ key: schema.hostProjectEnvRecords.key })
572
+ .from(schema.hostProjectEnvRecords)
573
+ .limit(1)
574
+ .get();
575
+ const project = tx
576
+ .select({ projectId: schema.hostProjectEnvProjects.projectId })
577
+ .from(schema.hostProjectEnvProjects)
578
+ .limit(1)
579
+ .get();
580
+ if (record || project) {
581
+ throw new ProjectEnvAuthorityCorruptError(
582
+ "unactivated project environment authority already has records",
583
+ );
584
+ }
585
+ const rows = tx
586
+ .select({
587
+ projectId: schema.hostProjectEnv.projectId,
588
+ key: schema.hostProjectEnv.key,
589
+ })
590
+ .from(schema.hostProjectEnv)
591
+ .orderBy(
592
+ asc(schema.hostProjectEnv.projectId),
593
+ asc(schema.hostProjectEnv.key),
594
+ )
595
+ .all();
596
+ const projects = new Set<string>();
597
+ for (const row of rows) {
598
+ putState(tx, row.projectId, {
599
+ key: row.key,
600
+ head: {
601
+ revision: 1,
602
+ generation: 1,
603
+ tombstone: false,
604
+ valueRef: { kind: "row" },
605
+ },
606
+ binding: null,
607
+ policyEpoch: INITIAL_PROJECT_ENV_POLICY_EPOCH,
608
+ listingGeneration: 0,
609
+ });
610
+ projects.add(row.projectId);
611
+ }
612
+ for (const projectId of projects) {
613
+ tx.insert(schema.hostProjectEnvProjects)
614
+ .values({ projectId, recordVersion: 1 })
615
+ .run();
616
+ }
617
+ }
618
+
619
+ function foldLegacyJournal(
620
+ tx: ProjectEnvTransaction,
621
+ ): Array<{ projectId: string; key: string }> {
622
+ const rows = tx
623
+ .select({
624
+ projectId: schema.hostProjectEnvLegacyJournal.projectId,
625
+ key: schema.hostProjectEnvLegacyJournal.key,
626
+ })
627
+ .from(schema.hostProjectEnvLegacyJournal)
628
+ .groupBy(
629
+ schema.hostProjectEnvLegacyJournal.projectId,
630
+ schema.hostProjectEnvLegacyJournal.key,
631
+ )
632
+ .orderBy(
633
+ asc(schema.hostProjectEnvLegacyJournal.projectId),
634
+ asc(schema.hostProjectEnvLegacyJournal.key),
635
+ )
636
+ .all();
637
+ for (const dirty of rows) {
638
+ const old = storedRecord(tx, dirty.projectId, dirty.key);
639
+ let state: ProjectEnvBindingState;
640
+ if (!old) {
641
+ // A post-activation legacy insert/delete has no authorized value. The
642
+ // migration tombstone shape makes every delayed legacy save stale.
643
+ state = {
644
+ key: dirty.key,
645
+ head: {
646
+ revision: 2,
647
+ generation: 2,
648
+ tombstone: true,
649
+ valueRef: null,
650
+ },
651
+ binding: null,
652
+ policyEpoch: INITIAL_PROJECT_ENV_POLICY_EPOCH,
653
+ listingGeneration: 0,
654
+ };
655
+ putState(tx, dirty.projectId, state);
656
+ bumpProjectRecordVersion(tx, dirty.projectId);
657
+ } else {
658
+ const current = rowState(old);
659
+ if (!current.head.tombstone) {
660
+ state = {
661
+ ...current,
662
+ head: {
663
+ revision: current.head.revision + 1,
664
+ generation: current.head.generation + 1,
665
+ tombstone: true,
666
+ valueRef: null,
667
+ },
668
+ };
669
+ putState(tx, dirty.projectId, state);
670
+ bumpProjectRecordVersion(tx, dirty.projectId);
671
+ }
672
+ }
673
+ tx.delete(schema.hostProjectEnv)
674
+ .where(
675
+ and(
676
+ eq(schema.hostProjectEnv.projectId, dirty.projectId),
677
+ eq(schema.hostProjectEnv.key, dirty.key),
678
+ ),
679
+ )
680
+ .run();
681
+ }
682
+ tx.delete(schema.hostProjectEnvLegacyJournal).run();
683
+ return rows;
684
+ }
685
+
686
+ /**
687
+ * Activate (if needed), fold every legacy write, close the gate, and persist a
688
+ * fresh attempt. The caller must claim capability floor 4 before calling this
689
+ * on an unactivated managed runtime.
690
+ */
691
+ export function beginProjectEnvReconciliation(
692
+ frame: ProjectEnvReconcileBeginFrame,
693
+ policyEpochs: readonly number[],
694
+ ): ProjectEnvReconciliationBeginResult {
695
+ if (
696
+ frame.kind !== "env.reconcile.begin" ||
697
+ !isHostOpId(frame.opId) ||
698
+ !isProjectEnvReconciliationAttempt(frame.attempt) ||
699
+ !isProjectEnvPolicyEpochs(policyEpochs)
700
+ ) {
701
+ throw new Error("invalid project environment reconciliation begin");
702
+ }
703
+ const result = getDb().transaction((tx) => {
704
+ const before = authorityRow(tx);
705
+ validateAuthorityRow(before);
706
+ setModernWrite(tx, true);
707
+ if (!before.activated) backfillLegacyValues(tx);
708
+ const foldedLegacyKeys = before.activated ? foldLegacyJournal(tx) : [];
709
+ const streamChanged = before.streamId !== frame.attempt.streamId;
710
+ tx.update(schema.hostProjectEnvAuthority)
711
+ .set({
712
+ activated: true,
713
+ gateOpen: false,
714
+ streamId: frame.attempt.streamId,
715
+ appliedSequence: streamChanged ? 0 : before.appliedSequence,
716
+ attemptId: frame.attempt.attemptId,
717
+ attemptLegacyVersion: before.legacyVersion,
718
+ policyEpochsJson: JSON.stringify(policyEpochs),
719
+ modernWrite: false,
720
+ updatedAt: Date.now(),
721
+ })
722
+ .where(eq(schema.hostProjectEnvAuthority.id, "host"))
723
+ .run();
724
+ return {
725
+ appliedThrough: {
726
+ streamId: frame.attempt.streamId,
727
+ sequence: streamChanged ? 0 : before.appliedSequence,
728
+ },
729
+ activatedNow: !before.activated,
730
+ foldedLegacyKeys,
731
+ };
732
+ });
733
+ for (const dirty of result.foldedLegacyKeys) {
734
+ noteProjectEnvValueMutation(dirty.projectId);
735
+ mirrorProjectEnvDeletionToStore(dirty.projectId, dirty.key);
736
+ }
737
+ noteProjectEnvAuthorityChange();
738
+ return result;
739
+ }
740
+
741
+ function headEqual(
742
+ current: ProjectEnvBindingState | null,
743
+ expected: ProjectEnvHead | null,
744
+ ): boolean {
745
+ if (current === null || expected === null) return current === null && expected === null;
746
+ return sameProjectEnvHead(current.head, expected);
747
+ }
748
+
749
+ function stateFor(
750
+ tx: ProjectEnvTransaction,
751
+ projectId: string,
752
+ key: string,
753
+ ): ProjectEnvBindingState | null {
754
+ const row = storedRecord(tx, projectId, key);
755
+ return row ? rowState(row) : null;
756
+ }
757
+
758
+ function receiptResult(
759
+ receipt: ProjectEnvReceiptRow,
760
+ cursor: ProjectEnvDeliveryCursor,
761
+ ): ProjectEnvDeliveryApplyResult {
762
+ if (receipt.ok) {
763
+ let state: ProjectEnvBindingState | null = null;
764
+ if (receipt.stateJson !== null) {
765
+ try {
766
+ const parsed: unknown = JSON.parse(receipt.stateJson);
767
+ if (!isProjectEnvBindingState(parsed)) throw new Error("invalid");
768
+ state = parsed;
769
+ } catch {
770
+ throw new ProjectEnvAuthorityCorruptError(
771
+ "stored project environment delivery outcome is invalid",
772
+ );
773
+ }
774
+ }
775
+ return {
776
+ ok: true,
777
+ replayed: true,
778
+ appliedThrough: cursor,
779
+ state,
780
+ recordVersion: receipt.recordVersion,
781
+ };
782
+ }
783
+ if (receipt.code === null) {
784
+ throw new ProjectEnvAuthorityCorruptError(
785
+ "stored project environment refusal has no code",
786
+ );
787
+ }
788
+ return {
789
+ ok: false,
790
+ replayed: true,
791
+ consumed: true,
792
+ code: receipt.code,
793
+ appliedThrough: cursor,
794
+ };
795
+ }
796
+
797
+ function transientResult(
798
+ row: ProjectEnvAuthorityRow,
799
+ code: ProjectEnvWithholdReason,
800
+ ): ProjectEnvDeliveryApplyResult {
801
+ const cursor = cursorFor(row);
802
+ if (!cursor) {
803
+ throw new ProjectEnvAuthorityCorruptError(
804
+ "project environment delivery arrived before activation",
805
+ );
806
+ }
807
+ return {
808
+ ok: false,
809
+ replayed: false,
810
+ consumed: false,
811
+ code,
812
+ appliedThrough: cursor,
813
+ };
814
+ }
815
+
816
+ function saveReceipt(
817
+ tx: ProjectEnvTransaction,
818
+ frame: ProjectEnvOwnedDeliveryFrame,
819
+ outcome:
820
+ | {
821
+ ok: true;
822
+ state: ProjectEnvBindingState | null;
823
+ recordVersion: number | null;
824
+ }
825
+ | { ok: false; code: ProjectEnvWithholdReason },
826
+ ): void {
827
+ tx.insert(schema.hostProjectEnvDeliveryReceipts)
828
+ .values({
829
+ streamId: frame.delivery.streamId,
830
+ sequence: frame.delivery.sequence,
831
+ requestKind: frame.kind,
832
+ requestMac: requestMac(frame),
833
+ ok: outcome.ok,
834
+ stateJson:
835
+ outcome.ok && outcome.state !== null
836
+ ? JSON.stringify(outcome.state)
837
+ : null,
838
+ recordVersion: outcome.ok ? outcome.recordVersion : null,
839
+ code: outcome.ok ? null : outcome.code,
840
+ createdAt: Date.now(),
841
+ })
842
+ .run();
843
+ tx.update(schema.hostProjectEnvAuthority)
844
+ .set({ appliedSequence: frame.delivery.sequence, updatedAt: Date.now() })
845
+ .where(eq(schema.hostProjectEnvAuthority.id, "host"))
846
+ .run();
847
+ }
848
+
849
+ function terminalRefusal(
850
+ tx: ProjectEnvTransaction,
851
+ frame: ProjectEnvOwnedDeliveryFrame,
852
+ code: ProjectEnvWithholdReason,
853
+ ): ProjectEnvDeliveryApplyResult {
854
+ saveReceipt(tx, frame, { ok: false, code });
855
+ return {
856
+ ok: false,
857
+ replayed: false,
858
+ consumed: true,
859
+ code,
860
+ appliedThrough: frame.delivery,
861
+ };
862
+ }
863
+
864
+ function applySet(
865
+ tx: ProjectEnvTransaction,
866
+ frame: ModernProjectEnvSetFrame,
867
+ policyEpochs: readonly number[],
868
+ ): { state: ProjectEnvBindingState; recordVersion: number } | string {
869
+ const current = stateFor(tx, frame.projectId, frame.key);
870
+ if (!headEqual(current, frame.expectedHead)) return "head_mismatch";
871
+ if (!policyEpochs.includes(frame.policyEpoch)) return "policy_epoch_unknown";
872
+ if (current && frame.listingGeneration < current.listingGeneration) {
873
+ return "listing_generation_stale";
874
+ }
875
+
876
+ let state: ProjectEnvBindingState;
877
+ if (current === null) {
878
+ state = {
879
+ key: frame.key,
880
+ head: {
881
+ revision: 1,
882
+ generation: 1,
883
+ tombstone: false,
884
+ valueRef: { kind: "row" },
885
+ },
886
+ binding: frame.initialBinding ?? null,
887
+ policyEpoch: frame.policyEpoch,
888
+ listingGeneration: frame.listingGeneration,
889
+ };
890
+ } else if (current.head.generation === 0) {
891
+ state = {
892
+ key: frame.key,
893
+ head: {
894
+ revision: 2,
895
+ generation: 1,
896
+ tombstone: false,
897
+ valueRef: { kind: "row" },
898
+ },
899
+ binding: frame.initialBinding ?? null,
900
+ policyEpoch: frame.policyEpoch,
901
+ listingGeneration: frame.listingGeneration,
902
+ };
903
+ } else {
904
+ if (frame.policyEpoch !== current.policyEpoch) return "policy_epoch_mismatch";
905
+ state = {
906
+ ...current,
907
+ head: current.head.tombstone
908
+ ? {
909
+ revision: current.head.revision + 1,
910
+ generation: current.head.generation + 1,
911
+ tombstone: false,
912
+ valueRef: { kind: "row" },
913
+ }
914
+ : {
915
+ revision: current.head.revision,
916
+ generation: current.head.generation + 1,
917
+ tombstone: false,
918
+ valueRef: { kind: "row" },
919
+ },
920
+ listingGeneration: frame.listingGeneration,
921
+ };
922
+ }
923
+ putState(tx, frame.projectId, state);
924
+ tx.insert(schema.hostProjectEnv)
925
+ .values({
926
+ projectId: frame.projectId,
927
+ key: frame.key,
928
+ valueEnc: sealProjectEnvValueForStorage(frame.value),
929
+ updatedAt: Date.now(),
930
+ ...(frame.orgId ? { orgId: frame.orgId } : {}),
931
+ })
932
+ .onConflictDoUpdate({
933
+ target: [schema.hostProjectEnv.projectId, schema.hostProjectEnv.key],
934
+ set: {
935
+ valueEnc: sealProjectEnvValueForStorage(frame.value),
936
+ updatedAt: Date.now(),
937
+ ...(frame.orgId ? { orgId: frame.orgId } : {}),
938
+ },
939
+ })
940
+ .run();
941
+ return { state, recordVersion: bumpProjectRecordVersion(tx, frame.projectId) };
942
+ }
943
+
944
+ function applyDelete(
945
+ tx: ProjectEnvTransaction,
946
+ frame: ModernProjectEnvDeleteFrame,
947
+ policyEpochs: readonly number[],
948
+ ): { state: ProjectEnvBindingState; recordVersion: number } | string {
949
+ const current = stateFor(tx, frame.projectId, frame.key);
950
+ if (!headEqual(current, frame.expectedHead) || current?.head.tombstone !== false) {
951
+ return "head_mismatch";
952
+ }
953
+ if (
954
+ !policyEpochs.includes(frame.policyEpoch) ||
955
+ frame.policyEpoch !== current.policyEpoch
956
+ ) {
957
+ return "policy_epoch_unknown";
958
+ }
959
+ if (frame.listingGeneration < current.listingGeneration) {
960
+ return "listing_generation_stale";
961
+ }
962
+ const state: ProjectEnvBindingState = {
963
+ ...current,
964
+ head: {
965
+ revision: current.head.revision + 1,
966
+ generation: current.head.generation + 1,
967
+ tombstone: true,
968
+ valueRef: null,
969
+ },
970
+ listingGeneration: frame.listingGeneration,
971
+ };
972
+ putState(tx, frame.projectId, state);
973
+ tx.delete(schema.hostProjectEnv)
974
+ .where(
975
+ and(
976
+ eq(schema.hostProjectEnv.projectId, frame.projectId),
977
+ eq(schema.hostProjectEnv.key, frame.key),
978
+ ),
979
+ )
980
+ .run();
981
+ return { state, recordVersion: bumpProjectRecordVersion(tx, frame.projectId) };
982
+ }
983
+
984
+ function applyRemove(
985
+ tx: ProjectEnvTransaction,
986
+ frame: ProjectEnvRemoveFrame,
987
+ ): { state: ProjectEnvBindingState; recordVersion: number } | string {
988
+ const current = stateFor(tx, frame.projectId, frame.key);
989
+ if (current && frame.listingGeneration < current.listingGeneration) {
990
+ return "listing_generation_stale";
991
+ }
992
+ let state: ProjectEnvBindingState;
993
+ if (current === null) {
994
+ state = {
995
+ key: frame.key,
996
+ head: {
997
+ revision: 1,
998
+ generation: 0,
999
+ tombstone: true,
1000
+ valueRef: null,
1001
+ },
1002
+ binding: null,
1003
+ policyEpoch: null,
1004
+ listingGeneration: frame.listingGeneration,
1005
+ };
1006
+ } else if (!current.head.tombstone) {
1007
+ state = {
1008
+ ...current,
1009
+ head: {
1010
+ revision: current.head.revision + 1,
1011
+ generation: current.head.generation + 1,
1012
+ tombstone: true,
1013
+ valueRef: null,
1014
+ },
1015
+ listingGeneration: frame.listingGeneration,
1016
+ };
1017
+ } else {
1018
+ state = { ...current, listingGeneration: frame.listingGeneration };
1019
+ }
1020
+ const changed = exactJson(state) !== exactJson(current);
1021
+ if (changed) putState(tx, frame.projectId, state);
1022
+ tx.delete(schema.hostProjectEnv)
1023
+ .where(
1024
+ and(
1025
+ eq(schema.hostProjectEnv.projectId, frame.projectId),
1026
+ eq(schema.hostProjectEnv.key, frame.key),
1027
+ ),
1028
+ )
1029
+ .run();
1030
+ return {
1031
+ state,
1032
+ recordVersion: changed
1033
+ ? bumpProjectRecordVersion(tx, frame.projectId)
1034
+ : projectRecordVersion(tx, frame.projectId),
1035
+ };
1036
+ }
1037
+
1038
+ function applyBinding(
1039
+ tx: ProjectEnvTransaction,
1040
+ frame: ProjectEnvBindingSetFrame,
1041
+ policyEpochs: readonly number[],
1042
+ ): { state: ProjectEnvBindingState; recordVersion: number } | string {
1043
+ const current = stateFor(tx, frame.projectId, frame.key);
1044
+ if (!headEqual(current, frame.expectedHead) || current?.head.tombstone !== false) {
1045
+ return "head_mismatch";
1046
+ }
1047
+ if (!policyEpochs.includes(frame.policyEpoch)) return "policy_epoch_unknown";
1048
+ if (frame.listingGeneration < current.listingGeneration) {
1049
+ return "listing_generation_stale";
1050
+ }
1051
+ const state: ProjectEnvBindingState = {
1052
+ ...current,
1053
+ head: { ...current.head, revision: current.head.revision + 1 },
1054
+ binding: frame.binding,
1055
+ policyEpoch: frame.policyEpoch,
1056
+ listingGeneration: frame.listingGeneration,
1057
+ };
1058
+ putState(tx, frame.projectId, state);
1059
+ return { state, recordVersion: bumpProjectRecordVersion(tx, frame.projectId) };
1060
+ }
1061
+
1062
+ function validOwnedDeliveryFrame(
1063
+ value: unknown,
1064
+ ): value is ProjectEnvOwnedDeliveryFrame {
1065
+ if (isProjectEnvSetFrame(value)) {
1066
+ return "delivery" in value && value.delivery !== undefined;
1067
+ }
1068
+ if (isProjectEnvDeleteFrame(value)) {
1069
+ return "delivery" in value && value.delivery !== undefined;
1070
+ }
1071
+ return (
1072
+ isProjectEnvRemoveFrame(value) ||
1073
+ isProjectEnvBindingSetFrame(value) ||
1074
+ isProjectEnvDeliverySkipFrame(value)
1075
+ );
1076
+ }
1077
+
1078
+ /**
1079
+ * Apply exactly the next delivery, or replay its value-free receipt. Terminal
1080
+ * CAS/policy refusals consume their sequence. Gaps, stale attempts and stream
1081
+ * mismatches do not. Different content at an already-consumed sequence is
1082
+ * refused; the special late skip keeps the original operation's outcome.
1083
+ */
1084
+ export function applyProjectEnvDelivery(
1085
+ frame: ProjectEnvOwnedDeliveryFrame,
1086
+ ): ProjectEnvDeliveryApplyResult {
1087
+ if (!validOwnedDeliveryFrame(frame)) {
1088
+ throw new Error("invalid modern project environment delivery");
1089
+ }
1090
+ const result = getDb().transaction((tx) => {
1091
+ const authority = authorityRow(tx);
1092
+ validateAuthorityRow(authority);
1093
+ if (!authority.activated) return transientResult(authority, "not_activated");
1094
+ const current = cursorFor(authority)!;
1095
+ if (frame.delivery.streamId !== current.streamId) {
1096
+ return transientResult(authority, "stream_mismatch");
1097
+ }
1098
+ if (frame.delivery.sequence <= current.sequence) {
1099
+ const receipt = tx
1100
+ .select()
1101
+ .from(schema.hostProjectEnvDeliveryReceipts)
1102
+ .where(
1103
+ and(
1104
+ eq(
1105
+ schema.hostProjectEnvDeliveryReceipts.streamId,
1106
+ frame.delivery.streamId,
1107
+ ),
1108
+ eq(
1109
+ schema.hostProjectEnvDeliveryReceipts.sequence,
1110
+ frame.delivery.sequence,
1111
+ ),
1112
+ ),
1113
+ )
1114
+ .get();
1115
+ if (!receipt) return transientResult(authority, "receipt_missing");
1116
+ if (frame.kind === "env.delivery.skip") {
1117
+ return receiptResult(receipt, current);
1118
+ }
1119
+ if (receipt.requestKind === "env.delivery.skip") {
1120
+ return {
1121
+ ok: false,
1122
+ replayed: true,
1123
+ consumed: true,
1124
+ code: "delivery_skipped",
1125
+ appliedThrough: current,
1126
+ } as const;
1127
+ }
1128
+ if (
1129
+ receipt.requestKind !== frame.kind ||
1130
+ !sameMac(receipt.requestMac, requestMac(frame))
1131
+ ) {
1132
+ return {
1133
+ ok: false,
1134
+ replayed: true,
1135
+ consumed: true,
1136
+ code: "delivery_conflict",
1137
+ appliedThrough: current,
1138
+ } as const;
1139
+ }
1140
+ return receiptResult(receipt, current);
1141
+ }
1142
+ if (frame.delivery.sequence !== current.sequence + 1) {
1143
+ return transientResult(authority, "delivery_gap");
1144
+ }
1145
+ if (
1146
+ authority.gateOpen
1147
+ ? frame.reconciliationAttemptId !== undefined
1148
+ : authority.attemptId === null ||
1149
+ frame.reconciliationAttemptId !== authority.attemptId
1150
+ ) {
1151
+ return transientResult(authority, "reconciliation_attempt_mismatch");
1152
+ }
1153
+
1154
+ setModernWrite(tx, true);
1155
+ const policyEpochs = parsePolicyEpochs(authority);
1156
+ let applied:
1157
+ | { state: ProjectEnvBindingState; recordVersion: number }
1158
+ | string
1159
+ | null;
1160
+ switch (frame.kind) {
1161
+ case "env.var.set":
1162
+ applied = applySet(tx, frame, policyEpochs);
1163
+ break;
1164
+ case "env.var.delete":
1165
+ applied = applyDelete(tx, frame, policyEpochs);
1166
+ break;
1167
+ case "env.var.remove":
1168
+ applied = applyRemove(tx, frame);
1169
+ break;
1170
+ case "env.binding.set":
1171
+ applied = applyBinding(tx, frame, policyEpochs);
1172
+ break;
1173
+ case "env.delivery.skip":
1174
+ applied = null;
1175
+ break;
1176
+ }
1177
+ if (typeof applied === "string") {
1178
+ setModernWrite(tx, false);
1179
+ return terminalRefusal(tx, frame, applied);
1180
+ }
1181
+ setModernWrite(tx, false);
1182
+ saveReceipt(tx, frame, {
1183
+ ok: true,
1184
+ state: applied?.state ?? null,
1185
+ recordVersion: applied?.recordVersion ?? null,
1186
+ });
1187
+ return {
1188
+ ok: true,
1189
+ replayed: false,
1190
+ appliedThrough: frame.delivery,
1191
+ state: applied?.state ?? null,
1192
+ recordVersion: applied?.recordVersion ?? null,
1193
+ } as const;
1194
+ });
1195
+ if (result.ok && !result.replayed) noteProjectEnvAuthorityChange();
1196
+ if (result.ok && !result.replayed && frame.kind === "env.var.set") {
1197
+ noteProjectEnvValueMutation(frame.projectId);
1198
+ mirrorProjectEnvToStore(frame.projectId, frame.key);
1199
+ } else if (
1200
+ result.ok &&
1201
+ !result.replayed &&
1202
+ (frame.kind === "env.var.delete" || frame.kind === "env.var.remove")
1203
+ ) {
1204
+ noteProjectEnvValueMutation(frame.projectId);
1205
+ mirrorProjectEnvDeletionToStore(frame.projectId, frame.key);
1206
+ }
1207
+ return result;
1208
+ }
1209
+
1210
+ export const applyProjectEnvSet = (frame: ModernProjectEnvSetFrame) =>
1211
+ applyProjectEnvDelivery(frame);
1212
+ export const applyProjectEnvDelete = (frame: ModernProjectEnvDeleteFrame) =>
1213
+ applyProjectEnvDelivery(frame);
1214
+ export const applyProjectEnvRemove = (frame: ProjectEnvRemoveFrame) =>
1215
+ applyProjectEnvDelivery(frame);
1216
+ export const applyProjectEnvBinding = (frame: ProjectEnvBindingSetFrame) =>
1217
+ applyProjectEnvDelivery(frame);
1218
+ export const applyProjectEnvSkip = (frame: ProjectEnvDeliverySkipFrame) =>
1219
+ applyProjectEnvDelivery(frame);
1220
+
1221
+ /** Open the gate only for the exact current attempt, stream, cursor and record versions. */
1222
+ export function completeProjectEnvReconciliation(
1223
+ frame: ProjectEnvReconciledFrame,
1224
+ ): ProjectEnvReconciliationCompleteResult {
1225
+ if (!isProjectEnvReconciledFrame(frame)) {
1226
+ throw new Error("invalid project environment reconciliation marker");
1227
+ }
1228
+ const result = getDb().transaction((tx): ProjectEnvReconciliationCompleteResult => {
1229
+ const authority = authorityRow(tx);
1230
+ validateAuthorityRow(authority);
1231
+ const cursor = cursorFor(authority);
1232
+ if (!cursor) {
1233
+ throw new ProjectEnvAuthorityCorruptError(
1234
+ "project environment reconciliation completed before activation",
1235
+ );
1236
+ }
1237
+ const refuse = (code: string): ProjectEnvReconciliationCompleteResult => ({
1238
+ ok: false,
1239
+ code,
1240
+ appliedThrough: cursor,
1241
+ });
1242
+ if (authority.attemptId !== frame.attemptId) {
1243
+ return refuse("reconciliation_attempt_mismatch");
1244
+ }
1245
+ if (
1246
+ frame.highWater.streamId !== cursor.streamId ||
1247
+ frame.highWater.sequence > cursor.sequence
1248
+ ) {
1249
+ return refuse("delivery_incomplete");
1250
+ }
1251
+ if (authority.attemptLegacyVersion !== authority.legacyVersion) {
1252
+ return refuse("legacy_write_detected");
1253
+ }
1254
+ for (const version of frame.versions) {
1255
+ if (projectRecordVersion(tx, version.projectId) !== version.recordVersion) {
1256
+ return refuse("record_version_changed");
1257
+ }
1258
+ }
1259
+ tx.update(schema.hostProjectEnvAuthority)
1260
+ .set({
1261
+ gateOpen: true,
1262
+ attemptId: null,
1263
+ attemptLegacyVersion: null,
1264
+ updatedAt: Date.now(),
1265
+ })
1266
+ .where(eq(schema.hostProjectEnvAuthority.id, "host"))
1267
+ .run();
1268
+ return { ok: true, appliedThrough: cursor };
1269
+ });
1270
+ if (result.ok) noteProjectEnvAuthorityChange();
1271
+ return result;
1272
+ }
1273
+
1274
+ /** Explicit fail-closed transition for transport loss or storage diagnostics. */
1275
+ export function closeProjectEnvAuthorityGate(): void {
1276
+ getDb()
1277
+ .update(schema.hostProjectEnvAuthority)
1278
+ .set({ gateOpen: false, updatedAt: Date.now() })
1279
+ .where(eq(schema.hostProjectEnvAuthority.id, "host"))
1280
+ .run();
1281
+ noteProjectEnvAuthorityChange();
1282
+ }
1283
+
1284
+ /** Value-free inspection used by tests and crash recovery diagnostics. */
1285
+ export function listProjectEnvDeliveryReceipts(): Array<{
1286
+ streamId: string;
1287
+ sequence: number;
1288
+ requestKind: string;
1289
+ requestMac: string;
1290
+ ok: boolean;
1291
+ state: ProjectEnvBindingState | null;
1292
+ recordVersion: number | null;
1293
+ code: string | null;
1294
+ }> {
1295
+ return getDb()
1296
+ .select()
1297
+ .from(schema.hostProjectEnvDeliveryReceipts)
1298
+ .orderBy(
1299
+ asc(schema.hostProjectEnvDeliveryReceipts.streamId),
1300
+ asc(schema.hostProjectEnvDeliveryReceipts.sequence),
1301
+ )
1302
+ .all()
1303
+ .map((row) => {
1304
+ const result = receiptResult(row, {
1305
+ streamId: row.streamId,
1306
+ sequence: row.sequence,
1307
+ });
1308
+ return {
1309
+ streamId: row.streamId,
1310
+ sequence: row.sequence,
1311
+ requestKind: row.requestKind,
1312
+ requestMac: row.requestMac,
1313
+ ok: result.ok,
1314
+ state: result.ok ? result.state : null,
1315
+ recordVersion: result.ok ? result.recordVersion : null,
1316
+ code: result.ok ? null : result.code,
1317
+ };
1318
+ });
1319
+ }