@opengeni/db 0.12.0 → 0.12.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-VUKRIBO5.js → chunk-CYA6BHV6.js} +150 -7
- package/dist/chunk-CYA6BHV6.js.map +1 -0
- package/dist/chunk-XRVNI2GB.js +896 -0
- package/dist/chunk-XRVNI2GB.js.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1166 -265
- package/dist/index.js.map +1 -1
- package/dist/provision-roles.d.ts +324 -38
- package/dist/provision-roles.js +1 -1
- package/dist/{schema-CnpD6BcX.d.ts → schema-CeXWlYcG.d.ts} +441 -12
- package/dist/schema.d.ts +1 -1
- package/dist/schema.js +1 -1
- package/drizzle/0122_codex_capacity_same_turn.sql +59 -0
- package/drizzle/0123_session_tool_policy_version.sql +14 -0
- package/drizzle/0124_session_event_duplicate_lookup.sql +4 -0
- package/drizzle/0125_document_drops_visibility.sql +29 -0
- package/drizzle/0126_document_access_constraints.sql +138 -0
- package/drizzle/0127_document_default_base_index.sql +5 -0
- package/drizzle/0128_github_installation_authority.sql +69 -0
- package/drizzle/0129_retained_process_reconciliation.sql +356 -0
- package/package.json +4 -3
- package/src/event-payload-sanitizer.ts +167 -55
- package/src/index.ts +1494 -258
- package/src/new-session-drafts.ts +127 -6
- package/src/provision-roles.ts +184 -2
- package/src/runtime-posture-cli.ts +57 -0
- package/src/runtime-posture.ts +760 -0
- package/src/schema.ts +156 -3
- package/src/session-control.ts +1 -0
- package/dist/chunk-BMFDXFPA.js +0 -155
- package/dist/chunk-BMFDXFPA.js.map +0 -1
- package/dist/chunk-VUKRIBO5.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -32,9 +32,11 @@ import {
|
|
|
32
32
|
rigChanges,
|
|
33
33
|
rigVersions,
|
|
34
34
|
rigs,
|
|
35
|
+
sandboxLeaseHolders,
|
|
35
36
|
sandboxPtySessions,
|
|
36
37
|
sandboxRetainedProcesses,
|
|
37
38
|
sandboxSessionEnvelopes,
|
|
39
|
+
sandboxWorkspaceMutationAdmissions,
|
|
38
40
|
sandboxes,
|
|
39
41
|
scheduledTaskRuns,
|
|
40
42
|
scheduledTasks,
|
|
@@ -70,14 +72,27 @@ import {
|
|
|
70
72
|
workspaceVariableSetVariables,
|
|
71
73
|
workspaceVariableSets,
|
|
72
74
|
workspaces
|
|
73
|
-
} from "./chunk-
|
|
75
|
+
} from "./chunk-CYA6BHV6.js";
|
|
74
76
|
import {
|
|
75
77
|
migrate,
|
|
76
78
|
runMigrations
|
|
77
79
|
} from "./chunk-Y5WZZVQK.js";
|
|
78
80
|
import {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
+
FORCE_RLS_TABLES,
|
|
82
|
+
NON_RLS_RUNTIME_TABLES,
|
|
83
|
+
PROTECTED_NO_DIRECT_DML_TABLES,
|
|
84
|
+
RUNTIME_DML_TABLES,
|
|
85
|
+
RUNTIME_FULL_DML_TABLES,
|
|
86
|
+
RUNTIME_READ_INSERT_TABLES,
|
|
87
|
+
RUNTIME_READ_ONLY_TABLES,
|
|
88
|
+
RUNTIME_TABLE_PRIVILEGES,
|
|
89
|
+
RuntimeDatabasePostureError,
|
|
90
|
+
assertRuntimeDatabasePosture,
|
|
91
|
+
evaluateRuntimeDatabasePosture,
|
|
92
|
+
inspectRuntimeDatabasePosture,
|
|
93
|
+
provisionRoles,
|
|
94
|
+
runtimeDatabaseReadyCheck
|
|
95
|
+
} from "./chunk-XRVNI2GB.js";
|
|
81
96
|
import "./chunk-PZ5AY32C.js";
|
|
82
97
|
|
|
83
98
|
// src/index.ts
|
|
@@ -284,24 +299,15 @@ function assertKey(key) {
|
|
|
284
299
|
}
|
|
285
300
|
|
|
286
301
|
// src/event-payload-sanitizer.ts
|
|
287
|
-
import {
|
|
302
|
+
import {
|
|
303
|
+
boundSessionEventPayload,
|
|
304
|
+
isCredentialHeaderName,
|
|
305
|
+
isSensitiveFieldName,
|
|
306
|
+
redactSensitiveKey,
|
|
307
|
+
redactSensitiveText
|
|
308
|
+
} from "@opengeni/contracts";
|
|
288
309
|
var REPLACEMENT = "\uFFFD";
|
|
289
310
|
var REDACTED = "[redacted]";
|
|
290
|
-
var SENSITIVE_FIELD_NAMES = /* @__PURE__ */ new Set([
|
|
291
|
-
"authorization",
|
|
292
|
-
"headers",
|
|
293
|
-
"accesstoken",
|
|
294
|
-
"refreshtoken",
|
|
295
|
-
"idtoken",
|
|
296
|
-
"token",
|
|
297
|
-
"apikey",
|
|
298
|
-
"secret",
|
|
299
|
-
"clientsecret",
|
|
300
|
-
"credential",
|
|
301
|
-
"credentialencrypted",
|
|
302
|
-
"encryptedpkceverifier",
|
|
303
|
-
"codeverifier"
|
|
304
|
-
]);
|
|
305
311
|
function sanitizeEventString(value) {
|
|
306
312
|
let needsWork = false;
|
|
307
313
|
for (let i = 0; i < value.length; i++) {
|
|
@@ -343,7 +349,8 @@ function sanitizeEventPayload(payload, options = {}) {
|
|
|
343
349
|
fullEvidence: options.fullEvidence
|
|
344
350
|
});
|
|
345
351
|
return sanitizeEventPayloadDeep(
|
|
346
|
-
bounded === payload ? removeProducerTruncationMetadata(bounded) : bounded
|
|
352
|
+
bounded === payload ? removeProducerTruncationMetadata(bounded) : bounded,
|
|
353
|
+
options.knownSecrets ?? []
|
|
347
354
|
);
|
|
348
355
|
}
|
|
349
356
|
function removeProducerTruncationMetadata(payload) {
|
|
@@ -354,33 +361,38 @@ function removeProducerTruncationMetadata(payload) {
|
|
|
354
361
|
delete cleaned.truncation;
|
|
355
362
|
return cleaned;
|
|
356
363
|
}
|
|
357
|
-
function sanitizeEventPayloadDeep(payload) {
|
|
364
|
+
function sanitizeEventPayloadDeep(payload, knownSecrets = []) {
|
|
358
365
|
if (typeof payload === "string") {
|
|
359
|
-
return sanitizeEventString(payload);
|
|
366
|
+
return sanitizeEventString(redactSensitiveText(payload, knownSecrets));
|
|
360
367
|
}
|
|
361
368
|
if (Array.isArray(payload)) {
|
|
362
|
-
return payload.map((item) => sanitizeEventPayloadDeep(item));
|
|
369
|
+
return payload.map((item) => sanitizeEventPayloadDeep(item, knownSecrets));
|
|
363
370
|
}
|
|
364
371
|
if (payload instanceof Date) {
|
|
365
372
|
return safeDateIso(payload);
|
|
366
373
|
}
|
|
367
374
|
if (payload && typeof payload === "object") {
|
|
368
|
-
const
|
|
369
|
-
|
|
370
|
-
|
|
375
|
+
const usedKeys = /* @__PURE__ */ new Set();
|
|
376
|
+
const entries = Object.entries(payload).map(([key, value]) => {
|
|
377
|
+
const safeKey = nextUniqueKey(
|
|
378
|
+
sanitizeEventString(redactSensitiveKey(key, knownSecrets)),
|
|
379
|
+
usedKeys
|
|
380
|
+
);
|
|
381
|
+
return [safeKey, sanitizeSensitiveEventField(key, value, knownSecrets)];
|
|
382
|
+
});
|
|
371
383
|
return Object.fromEntries(entries);
|
|
372
384
|
}
|
|
373
385
|
return payload;
|
|
374
386
|
}
|
|
375
|
-
function sanitizeModelPayload(payload) {
|
|
376
|
-
return sanitizeModelPayloadDeep(payload, /* @__PURE__ */ new WeakSet(), 0);
|
|
387
|
+
function sanitizeModelPayload(payload, knownSecrets = []) {
|
|
388
|
+
return sanitizeModelPayloadDeep(payload, /* @__PURE__ */ new WeakSet(), 0, knownSecrets);
|
|
377
389
|
}
|
|
378
390
|
var MODEL_PAYLOAD_SANITIZE_MAX_DEPTH = 64;
|
|
379
391
|
var MODEL_PAYLOAD_CYCLE_MARKER = "[OpenGeni omitted cyclic model payload]";
|
|
380
392
|
var MODEL_PAYLOAD_DEPTH_MARKER = "[OpenGeni omitted model payload beyond database-safety depth]";
|
|
381
|
-
function sanitizeModelPayloadDeep(payload, seen, depth) {
|
|
393
|
+
function sanitizeModelPayloadDeep(payload, seen, depth, knownSecrets = []) {
|
|
382
394
|
if (typeof payload === "string") {
|
|
383
|
-
return sanitizeEventString(payload);
|
|
395
|
+
return sanitizeEventString(redactSensitiveText(payload, knownSecrets));
|
|
384
396
|
}
|
|
385
397
|
if (!payload || typeof payload !== "object") return payload;
|
|
386
398
|
if (payload instanceof Date) {
|
|
@@ -393,13 +405,25 @@ function sanitizeModelPayloadDeep(payload, seen, depth) {
|
|
|
393
405
|
seen.add(payload);
|
|
394
406
|
try {
|
|
395
407
|
if (Array.isArray(payload)) {
|
|
396
|
-
return payload.map(
|
|
408
|
+
return payload.map(
|
|
409
|
+
(item) => sanitizeModelPayloadDeep(item, seen, depth + 1, knownSecrets)
|
|
410
|
+
);
|
|
397
411
|
}
|
|
412
|
+
const usedKeys = /* @__PURE__ */ new Set();
|
|
398
413
|
return Object.fromEntries(
|
|
399
|
-
Object.entries(payload).map(([key, value]) =>
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
414
|
+
Object.entries(payload).map(([key, value]) => {
|
|
415
|
+
const safeKey = nextUniqueKey(
|
|
416
|
+
sanitizeEventString(redactSensitiveKey(key, knownSecrets)),
|
|
417
|
+
usedKeys
|
|
418
|
+
);
|
|
419
|
+
if (isSensitiveFieldName(key)) {
|
|
420
|
+
return [safeKey, REDACTED];
|
|
421
|
+
}
|
|
422
|
+
if (normalizeFieldName(key) === "headers") {
|
|
423
|
+
return [safeKey, sanitizeModelHeaders(value, seen, depth + 1, knownSecrets)];
|
|
424
|
+
}
|
|
425
|
+
return [safeKey, sanitizeModelPayloadDeep(value, seen, depth + 1, knownSecrets)];
|
|
426
|
+
})
|
|
403
427
|
);
|
|
404
428
|
} finally {
|
|
405
429
|
seen.delete(payload);
|
|
@@ -413,57 +437,118 @@ function safeDateIso(value) {
|
|
|
413
437
|
return null;
|
|
414
438
|
}
|
|
415
439
|
}
|
|
416
|
-
function sanitizeSensitiveEventField(key, value) {
|
|
440
|
+
function sanitizeSensitiveEventField(key, value, knownSecrets) {
|
|
417
441
|
if (key === "mcpServers") {
|
|
418
|
-
return sanitizeSessionMcpServerList(value);
|
|
442
|
+
return sanitizeSessionMcpServerList(value, knownSecrets);
|
|
419
443
|
}
|
|
420
444
|
if (key === "mcpCredentialUpdates") {
|
|
421
|
-
return sanitizeMcpCredentialUpdateList(value);
|
|
445
|
+
return sanitizeMcpCredentialUpdateList(value, knownSecrets);
|
|
422
446
|
}
|
|
423
|
-
if (
|
|
447
|
+
if (normalizeFieldName(key) === "headers") {
|
|
448
|
+
return sanitizeEventHeaders(value, knownSecrets);
|
|
449
|
+
}
|
|
450
|
+
if (isSensitiveFieldName(key)) {
|
|
424
451
|
return REDACTED;
|
|
425
452
|
}
|
|
426
|
-
return sanitizeEventPayloadDeep(value);
|
|
453
|
+
return sanitizeEventPayloadDeep(value, knownSecrets);
|
|
454
|
+
}
|
|
455
|
+
function sanitizeEventHeaders(value, knownSecrets) {
|
|
456
|
+
if (!isPlainObject(value)) {
|
|
457
|
+
return sanitizeEventPayloadDeep(value, knownSecrets);
|
|
458
|
+
}
|
|
459
|
+
const usedKeys = /* @__PURE__ */ new Set();
|
|
460
|
+
return Object.fromEntries(
|
|
461
|
+
Object.entries(value).map(([key, child]) => {
|
|
462
|
+
const safeKey = nextUniqueKey(
|
|
463
|
+
sanitizeEventString(redactSensitiveKey(key, knownSecrets)),
|
|
464
|
+
usedKeys
|
|
465
|
+
);
|
|
466
|
+
return [
|
|
467
|
+
safeKey,
|
|
468
|
+
isCredentialHeaderName(key) ? REDACTED : sanitizeEventPayloadDeep(child, knownSecrets)
|
|
469
|
+
];
|
|
470
|
+
})
|
|
471
|
+
);
|
|
472
|
+
}
|
|
473
|
+
function sanitizeModelHeaders(value, seen, depth, knownSecrets) {
|
|
474
|
+
if (!isPlainObject(value)) {
|
|
475
|
+
return sanitizeModelPayloadDeep(value, seen, depth, knownSecrets);
|
|
476
|
+
}
|
|
477
|
+
if (depth >= MODEL_PAYLOAD_SANITIZE_MAX_DEPTH) {
|
|
478
|
+
return MODEL_PAYLOAD_DEPTH_MARKER;
|
|
479
|
+
}
|
|
480
|
+
if (seen.has(value)) return MODEL_PAYLOAD_CYCLE_MARKER;
|
|
481
|
+
seen.add(value);
|
|
482
|
+
try {
|
|
483
|
+
const usedKeys = /* @__PURE__ */ new Set();
|
|
484
|
+
return Object.fromEntries(
|
|
485
|
+
Object.entries(value).map(([key, child]) => {
|
|
486
|
+
const safeKey = nextUniqueKey(
|
|
487
|
+
sanitizeEventString(redactSensitiveKey(key, knownSecrets)),
|
|
488
|
+
usedKeys
|
|
489
|
+
);
|
|
490
|
+
return [
|
|
491
|
+
safeKey,
|
|
492
|
+
isCredentialHeaderName(key) ? REDACTED : sanitizeModelPayloadDeep(child, seen, depth + 1, knownSecrets)
|
|
493
|
+
];
|
|
494
|
+
})
|
|
495
|
+
);
|
|
496
|
+
} finally {
|
|
497
|
+
seen.delete(value);
|
|
498
|
+
}
|
|
427
499
|
}
|
|
428
|
-
function sanitizeSessionMcpServerList(value) {
|
|
500
|
+
function sanitizeSessionMcpServerList(value, knownSecrets) {
|
|
429
501
|
if (!Array.isArray(value)) {
|
|
430
|
-
return sanitizeEventPayloadDeep(value);
|
|
502
|
+
return sanitizeEventPayloadDeep(value, knownSecrets);
|
|
431
503
|
}
|
|
432
504
|
return value.map((item) => {
|
|
433
505
|
if (!isPlainObject(item)) {
|
|
434
|
-
return sanitizeEventPayloadDeep(item);
|
|
506
|
+
return sanitizeEventPayloadDeep(item, knownSecrets);
|
|
435
507
|
}
|
|
436
508
|
const { headers, headersEncrypted, ...rest } = item;
|
|
437
|
-
const cleaned = sanitizeEventPayloadDeep(rest);
|
|
438
|
-
const headerNames = safeHeaderNames(headers) ?? safeHeaderNames(headersEncrypted);
|
|
509
|
+
const cleaned = sanitizeEventPayloadDeep(rest, knownSecrets);
|
|
510
|
+
const headerNames = safeHeaderNames(headers, knownSecrets) ?? safeHeaderNames(headersEncrypted, knownSecrets);
|
|
439
511
|
if (headerNames) {
|
|
440
512
|
cleaned.headerNames = headerNames;
|
|
441
513
|
}
|
|
442
514
|
return cleaned;
|
|
443
515
|
});
|
|
444
516
|
}
|
|
445
|
-
function sanitizeMcpCredentialUpdateList(value) {
|
|
517
|
+
function sanitizeMcpCredentialUpdateList(value, knownSecrets) {
|
|
446
518
|
if (!Array.isArray(value)) {
|
|
447
|
-
return sanitizeEventPayloadDeep(value);
|
|
519
|
+
return sanitizeEventPayloadDeep(value, knownSecrets);
|
|
448
520
|
}
|
|
449
521
|
return value.map((item) => {
|
|
450
522
|
if (!isPlainObject(item)) {
|
|
451
|
-
return sanitizeEventPayloadDeep(item);
|
|
523
|
+
return sanitizeEventPayloadDeep(item, knownSecrets);
|
|
452
524
|
}
|
|
453
525
|
const { headers, headersEncrypted, ...rest } = item;
|
|
454
|
-
const cleaned = sanitizeEventPayloadDeep(rest);
|
|
455
|
-
const headerNames = safeHeaderNames(headers) ?? safeHeaderNames(headersEncrypted);
|
|
526
|
+
const cleaned = sanitizeEventPayloadDeep(rest, knownSecrets);
|
|
527
|
+
const headerNames = safeHeaderNames(headers, knownSecrets) ?? safeHeaderNames(headersEncrypted, knownSecrets);
|
|
456
528
|
if (headerNames) {
|
|
457
529
|
cleaned.headerNames = headerNames;
|
|
458
530
|
}
|
|
459
531
|
return cleaned;
|
|
460
532
|
});
|
|
461
533
|
}
|
|
462
|
-
function safeHeaderNames(value) {
|
|
534
|
+
function safeHeaderNames(value, knownSecrets) {
|
|
463
535
|
if (!isPlainObject(value)) {
|
|
464
536
|
return null;
|
|
465
537
|
}
|
|
466
|
-
|
|
538
|
+
const usedKeys = /* @__PURE__ */ new Set();
|
|
539
|
+
return Object.keys(value).map(
|
|
540
|
+
(key) => nextUniqueKey(sanitizeEventString(redactSensitiveKey(key, knownSecrets)), usedKeys)
|
|
541
|
+
).sort();
|
|
542
|
+
}
|
|
543
|
+
function nextUniqueKey(base, usedKeys) {
|
|
544
|
+
let candidate = base;
|
|
545
|
+
let suffix = 2;
|
|
546
|
+
while (usedKeys.has(candidate)) {
|
|
547
|
+
candidate = `${base}#${suffix}`;
|
|
548
|
+
suffix += 1;
|
|
549
|
+
}
|
|
550
|
+
usedKeys.add(candidate);
|
|
551
|
+
return candidate;
|
|
467
552
|
}
|
|
468
553
|
function isPlainObject(value) {
|
|
469
554
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
@@ -487,6 +572,18 @@ var NewSessionDraftAccessError = class extends Error {
|
|
|
487
572
|
super("New-session draft access changed");
|
|
488
573
|
}
|
|
489
574
|
};
|
|
575
|
+
function storedOptions(options, toolsProvided) {
|
|
576
|
+
return { ...options, toolsProvided };
|
|
577
|
+
}
|
|
578
|
+
function newSessionDraftToolsProvided(row) {
|
|
579
|
+
const options = row.sessionOptions;
|
|
580
|
+
return options.toolsProvided === true || !Object.hasOwn(options, "toolsProvided");
|
|
581
|
+
}
|
|
582
|
+
function publicNewSessionDraftOptions(row) {
|
|
583
|
+
const options = { ...row.sessionOptions };
|
|
584
|
+
delete options.toolsProvided;
|
|
585
|
+
return options;
|
|
586
|
+
}
|
|
490
587
|
async function getNewSessionDraftInTransaction(db, input) {
|
|
491
588
|
const query = db.select().from(newSessionDrafts).where(
|
|
492
589
|
and2(
|
|
@@ -523,7 +620,9 @@ async function saveNewSessionDraftInTransaction(db, input) {
|
|
|
523
620
|
tools: input.tools,
|
|
524
621
|
model: input.model,
|
|
525
622
|
reasoningEffort: input.reasoningEffort,
|
|
526
|
-
|
|
623
|
+
// Keep the explicit/omitted policy in the existing JSONB extension point;
|
|
624
|
+
// adding a column here would turn a client preference into a migration.
|
|
625
|
+
sessionOptions: storedOptions(input.options, input.toolsProvided),
|
|
527
626
|
updatedAt: /* @__PURE__ */ new Date()
|
|
528
627
|
};
|
|
529
628
|
if (current) {
|
|
@@ -538,17 +637,69 @@ async function saveNewSessionDraftInTransaction(db, input) {
|
|
|
538
637
|
const raced = await getNewSessionDraftInTransaction(db, { ...input, lock: true });
|
|
539
638
|
throw new NewSessionDraftConflictError(raced?.revision ?? 0);
|
|
540
639
|
}
|
|
541
|
-
|
|
640
|
+
function safeRepositoryResource(resource) {
|
|
641
|
+
return {
|
|
642
|
+
kind: "repository",
|
|
643
|
+
uri: resource.uri,
|
|
644
|
+
ref: resource.ref,
|
|
645
|
+
...resource.mountPath ? { mountPath: resource.mountPath } : {},
|
|
646
|
+
...resource.subpath ? { subpath: resource.subpath } : {},
|
|
647
|
+
...resource.githubInstallationId ? { githubInstallationId: resource.githubInstallationId } : {},
|
|
648
|
+
...resource.githubRepositoryId ? { githubRepositoryId: resource.githubRepositoryId } : {}
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
function safeWorkingDir(value, targetSandboxId) {
|
|
652
|
+
if (!targetSandboxId || typeof value !== "string") return void 0;
|
|
653
|
+
const trimmed = value.trim();
|
|
654
|
+
if (!trimmed || trimmed === "." || trimmed === ".." || trimmed.includes("\0") || trimmed.startsWith("/") || trimmed.startsWith("\\") || /^[A-Za-z]:[\\/]/.test(trimmed) || trimmed.split(/[\\/]+/).some((part) => part === "..")) {
|
|
655
|
+
return void 0;
|
|
656
|
+
}
|
|
657
|
+
return trimmed;
|
|
658
|
+
}
|
|
659
|
+
async function seedNewSessionDraftInTransaction(db, input) {
|
|
542
660
|
if (input.expectedRevision === 0) return false;
|
|
543
|
-
const
|
|
661
|
+
const [current] = await db.select().from(newSessionDrafts).where(
|
|
544
662
|
and2(
|
|
545
663
|
eq2(newSessionDrafts.workspaceId, input.workspaceId),
|
|
546
|
-
eq2(newSessionDrafts.subjectId, input.subjectId)
|
|
664
|
+
eq2(newSessionDrafts.subjectId, input.subjectId)
|
|
665
|
+
)
|
|
666
|
+
).for("update").limit(1);
|
|
667
|
+
if (!current || current.revision !== input.expectedRevision) return false;
|
|
668
|
+
const options = current.sessionOptions;
|
|
669
|
+
const targetSandboxId = typeof options.targetSandboxId === "string" ? options.targetSandboxId : void 0;
|
|
670
|
+
const safeOptions = {
|
|
671
|
+
...options.sandboxBackend ? { sandboxBackend: options.sandboxBackend } : {},
|
|
672
|
+
...targetSandboxId ? { targetSandboxId } : {},
|
|
673
|
+
...safeWorkingDir(options.workingDir, targetSandboxId) ? { workingDir: safeWorkingDir(options.workingDir, targetSandboxId) } : {},
|
|
674
|
+
...options.variableSetId ? { variableSetId: options.variableSetId } : {},
|
|
675
|
+
...options.rigId ? { rigId: options.rigId } : {}
|
|
676
|
+
};
|
|
677
|
+
const resources = (Array.isArray(current.resources) ? current.resources : []).flatMap((raw) => {
|
|
678
|
+
if (!raw || typeof raw !== "object" || raw.kind !== "repository") {
|
|
679
|
+
return [];
|
|
680
|
+
}
|
|
681
|
+
return [safeRepositoryResource(raw)];
|
|
682
|
+
});
|
|
683
|
+
const [seeded] = await db.update(newSessionDrafts).set({
|
|
684
|
+
revision: current.revision + 1,
|
|
685
|
+
text: "",
|
|
686
|
+
resources,
|
|
687
|
+
// The explicit array is retained only when the caller explicitly pinned
|
|
688
|
+
// tools. Omitted workspace-default policy is represented by [] + false.
|
|
689
|
+
tools: newSessionDraftToolsProvided(current) ? current.tools : [],
|
|
690
|
+
model: current.model,
|
|
691
|
+
reasoningEffort: current.reasoningEffort,
|
|
692
|
+
sessionOptions: storedOptions(safeOptions, newSessionDraftToolsProvided(current)),
|
|
693
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
694
|
+
}).where(
|
|
695
|
+
and2(
|
|
696
|
+
eq2(newSessionDrafts.id, current.id),
|
|
547
697
|
eq2(newSessionDrafts.revision, input.expectedRevision)
|
|
548
698
|
)
|
|
549
699
|
).returning({ id: newSessionDrafts.id });
|
|
550
|
-
return
|
|
700
|
+
return Boolean(seeded);
|
|
551
701
|
}
|
|
702
|
+
var consumeNewSessionDraftInTransaction = seedNewSessionDraftInTransaction;
|
|
552
703
|
|
|
553
704
|
// src/persistence-errors.ts
|
|
554
705
|
var SQLSTATE_KEYS = ["sqlState", "sqlstate", "code"];
|
|
@@ -5020,6 +5171,14 @@ async function cancelResponseBody(response) {
|
|
|
5020
5171
|
}
|
|
5021
5172
|
|
|
5022
5173
|
// src/index.ts
|
|
5174
|
+
var SessionToolPolicyVersionConflictError = class extends Error {
|
|
5175
|
+
constructor(currentVersion) {
|
|
5176
|
+
super("The session tool policy changed in another client");
|
|
5177
|
+
this.currentVersion = currentVersion;
|
|
5178
|
+
this.name = "SessionToolPolicyVersionConflictError";
|
|
5179
|
+
}
|
|
5180
|
+
code = "SESSION_TOOL_POLICY_CONFLICT";
|
|
5181
|
+
};
|
|
5023
5182
|
var SessionSpawnDeniedDbError = class extends Error {
|
|
5024
5183
|
constructor(denial) {
|
|
5025
5184
|
super(denial.code);
|
|
@@ -5367,14 +5526,23 @@ async function withRlsContext(db, context, fn, transactionConfig) {
|
|
|
5367
5526
|
const scoped = tx;
|
|
5368
5527
|
await setRlsContext(scoped, context);
|
|
5369
5528
|
const applied = await tx.execute(
|
|
5370
|
-
sql4`select
|
|
5529
|
+
sql4`select
|
|
5530
|
+
current_setting('opengeni.account_id', true) as account_id,
|
|
5531
|
+
current_setting('opengeni.workspace_id', true) as workspace_id`
|
|
5371
5532
|
);
|
|
5372
5533
|
const appliedAccountId = applied[0]?.account_id ?? "";
|
|
5534
|
+
const expectedWorkspaceId = context.workspaceId ?? "";
|
|
5535
|
+
const appliedWorkspaceId = applied[0]?.workspace_id ?? "";
|
|
5373
5536
|
if (appliedAccountId !== context.accountId) {
|
|
5374
5537
|
throw new Error(
|
|
5375
5538
|
`RLS context not applied on the active backend: expected account ${context.accountId}, got "${appliedAccountId}"`
|
|
5376
5539
|
);
|
|
5377
5540
|
}
|
|
5541
|
+
if (appliedWorkspaceId !== expectedWorkspaceId) {
|
|
5542
|
+
throw new Error(
|
|
5543
|
+
`RLS context not applied on the active backend: expected workspace "${expectedWorkspaceId}", got "${appliedWorkspaceId}"`
|
|
5544
|
+
);
|
|
5545
|
+
}
|
|
5378
5546
|
return await fn(scoped);
|
|
5379
5547
|
}, transactionConfig);
|
|
5380
5548
|
}
|
|
@@ -6000,6 +6168,21 @@ async function findActiveApiKeyByHash(db, keyHash) {
|
|
|
6000
6168
|
return mapApiKey({ ...row, lastUsedAt: now });
|
|
6001
6169
|
});
|
|
6002
6170
|
}
|
|
6171
|
+
var GitHubInstallationAuthorityCommitError = class extends Error {
|
|
6172
|
+
constructor() {
|
|
6173
|
+
super("GitHub installation authority expired before the binding transaction completed");
|
|
6174
|
+
}
|
|
6175
|
+
};
|
|
6176
|
+
var githubInstallationAuthorityMaxAgeMs = 10 * 6e4;
|
|
6177
|
+
function hasAuditableGitHubInstallationAuthority(installation) {
|
|
6178
|
+
const checkedAt = installation.authorityCheckedAt ? Date.parse(installation.authorityCheckedAt) : Number.NaN;
|
|
6179
|
+
const expiresAt = installation.authorityExpiresAt ? Date.parse(installation.authorityExpiresAt) : Number.NaN;
|
|
6180
|
+
const common = installation.repositoryScope === "selected" && installation.repositoryIds.length > 0 && installation.repositoryIds.every((id) => Number.isSafeInteger(id) && id > 0) && installation.githubAccountId !== null && Number.isSafeInteger(installation.githubAccountId) && installation.githubAccountId > 0 && Boolean(installation.accountLogin) && installation.githubActorId !== null && Number.isSafeInteger(installation.githubActorId) && installation.githubActorId > 0 && Boolean(installation.githubActorLogin) && Boolean(installation.linkedBySubjectId) && Boolean(installation.authorityNonce) && Number.isFinite(checkedAt) && Number.isFinite(expiresAt) && checkedAt < expiresAt;
|
|
6181
|
+
if (!common) {
|
|
6182
|
+
return false;
|
|
6183
|
+
}
|
|
6184
|
+
return installation.authorityKind === "personal_owner" ? installation.accountType === "User" && installation.githubActorId === installation.githubAccountId : installation.authorityKind === "organization_owner" && installation.accountType === "Organization";
|
|
6185
|
+
}
|
|
6003
6186
|
async function upsertGitHubInstallation(db, input) {
|
|
6004
6187
|
return await withRlsContext(
|
|
6005
6188
|
db,
|
|
@@ -6023,7 +6206,10 @@ async function upsertGitHubInstallation(db, input) {
|
|
|
6023
6206
|
accountType: input.accountType ?? null,
|
|
6024
6207
|
...input.linkedBySubjectId !== void 0 ? { linkedBySubjectId: input.linkedBySubjectId } : {},
|
|
6025
6208
|
updatedAt: /* @__PURE__ */ new Date()
|
|
6026
|
-
}
|
|
6209
|
+
},
|
|
6210
|
+
// This legacy metadata helper cannot mutate an owner-authorized row.
|
|
6211
|
+
// New bindings use bindAuthorizedGitHubInstallationRepositories.
|
|
6212
|
+
setWhere: isNull(githubInstallations.authorityNonce)
|
|
6027
6213
|
}).returning();
|
|
6028
6214
|
if (!row) {
|
|
6029
6215
|
throw new Error("Failed to upsert GitHub installation");
|
|
@@ -6064,6 +6250,114 @@ async function bindGitHubInstallationRepositories(db, input) {
|
|
|
6064
6250
|
repositoryScope: "selected",
|
|
6065
6251
|
linkedBySubjectId: input.linkedBySubjectId,
|
|
6066
6252
|
updatedAt: /* @__PURE__ */ new Date()
|
|
6253
|
+
},
|
|
6254
|
+
// Preserve the immutable authority + allowlist audit boundary.
|
|
6255
|
+
setWhere: isNull(githubInstallations.authorityNonce)
|
|
6256
|
+
}).returning();
|
|
6257
|
+
if (!row) {
|
|
6258
|
+
throw new Error("Failed to bind GitHub installation");
|
|
6259
|
+
}
|
|
6260
|
+
await tx.delete(githubInstallationRepositories).where(
|
|
6261
|
+
and6(
|
|
6262
|
+
eq6(githubInstallationRepositories.workspaceId, input.workspaceId),
|
|
6263
|
+
eq6(githubInstallationRepositories.installationId, input.installationId)
|
|
6264
|
+
)
|
|
6265
|
+
);
|
|
6266
|
+
for (let offset = 0; offset < repositoryIds.length; offset += 1e3) {
|
|
6267
|
+
await tx.insert(githubInstallationRepositories).values(
|
|
6268
|
+
repositoryIds.slice(offset, offset + 1e3).map((repositoryId) => ({
|
|
6269
|
+
accountId: input.accountId,
|
|
6270
|
+
workspaceId: input.workspaceId,
|
|
6271
|
+
installationId: input.installationId,
|
|
6272
|
+
repositoryId
|
|
6273
|
+
}))
|
|
6274
|
+
);
|
|
6275
|
+
}
|
|
6276
|
+
return { ...mapGitHubInstallation(row), repositoryIds };
|
|
6277
|
+
})
|
|
6278
|
+
);
|
|
6279
|
+
}
|
|
6280
|
+
async function bindAuthorizedGitHubInstallationRepositories(db, input) {
|
|
6281
|
+
if (!Number.isSafeInteger(input.installationId) || input.installationId <= 0) {
|
|
6282
|
+
throw new Error("GitHub installation id must be a positive safe integer");
|
|
6283
|
+
}
|
|
6284
|
+
if (!Number.isSafeInteger(input.githubAccountId) || input.githubAccountId <= 0) {
|
|
6285
|
+
throw new Error("GitHub account id must be a positive safe integer");
|
|
6286
|
+
}
|
|
6287
|
+
if (!Number.isSafeInteger(input.githubActorId) || input.githubActorId <= 0) {
|
|
6288
|
+
throw new Error("GitHub actor id must be a positive safe integer");
|
|
6289
|
+
}
|
|
6290
|
+
const authorityCheckedAtMs = input.authorityCheckedAt.getTime();
|
|
6291
|
+
const authorityExpiresAtMs = input.authorityExpiresAt.getTime();
|
|
6292
|
+
if (!Number.isFinite(authorityCheckedAtMs) || !Number.isFinite(authorityExpiresAtMs) || authorityCheckedAtMs >= authorityExpiresAtMs || authorityExpiresAtMs - authorityCheckedAtMs > githubInstallationAuthorityMaxAgeMs || !input.authorityNonce || !input.githubActorLogin.trim() || !input.accountLogin?.trim() || !input.linkedBySubjectId.trim() || input.authorityKind !== "personal_owner" && input.authorityKind !== "organization_owner" || input.authorityKind === "personal_owner" && (input.accountType !== "User" || input.githubActorId !== input.githubAccountId) || input.authorityKind === "organization_owner" && input.accountType !== "Organization") {
|
|
6293
|
+
throw new Error("GitHub installation authority proof is invalid or expired");
|
|
6294
|
+
}
|
|
6295
|
+
const repositoryIds = [...new Set(input.repositoryIds)];
|
|
6296
|
+
if (repositoryIds.length === 0 || repositoryIds.length !== input.repositoryIds.length || repositoryIds.some((id) => !Number.isSafeInteger(id) || id <= 0)) {
|
|
6297
|
+
throw new Error(
|
|
6298
|
+
"GitHub repository ids must be a nonempty, unique list of positive safe integers"
|
|
6299
|
+
);
|
|
6300
|
+
}
|
|
6301
|
+
return await withRlsContext(
|
|
6302
|
+
db,
|
|
6303
|
+
{ accountId: input.accountId, workspaceId: input.workspaceId },
|
|
6304
|
+
async (scopedDb) => await scopedDb.transaction(async (tx) => {
|
|
6305
|
+
await assertGitHubAuthorityWindowOpen(
|
|
6306
|
+
tx,
|
|
6307
|
+
input.authorityCheckedAt,
|
|
6308
|
+
input.authorityExpiresAt
|
|
6309
|
+
);
|
|
6310
|
+
await tx.delete(integrationOauthStateNonces).where(
|
|
6311
|
+
and6(
|
|
6312
|
+
eq6(integrationOauthStateNonces.workspaceId, input.workspaceId),
|
|
6313
|
+
lt(integrationOauthStateNonces.expiresAt, input.authorityCheckedAt)
|
|
6314
|
+
)
|
|
6315
|
+
);
|
|
6316
|
+
const consumed = await tx.insert(integrationOauthStateNonces).values({
|
|
6317
|
+
accountId: input.accountId,
|
|
6318
|
+
workspaceId: input.workspaceId,
|
|
6319
|
+
subjectId: input.linkedBySubjectId,
|
|
6320
|
+
nonce: input.authorityNonce,
|
|
6321
|
+
expiresAt: input.authorityExpiresAt,
|
|
6322
|
+
usedAt: input.authorityCheckedAt
|
|
6323
|
+
}).onConflictDoNothing({ target: integrationOauthStateNonces.nonce }).returning({ nonce: integrationOauthStateNonces.nonce });
|
|
6324
|
+
if (consumed.length === 0) {
|
|
6325
|
+
return null;
|
|
6326
|
+
}
|
|
6327
|
+
const [row] = await tx.insert(githubInstallations).values({
|
|
6328
|
+
accountId: input.accountId,
|
|
6329
|
+
workspaceId: input.workspaceId,
|
|
6330
|
+
installationId: input.installationId,
|
|
6331
|
+
githubAccountId: input.githubAccountId,
|
|
6332
|
+
accountLogin: input.accountLogin,
|
|
6333
|
+
accountType: input.accountType,
|
|
6334
|
+
repositoryScope: "selected",
|
|
6335
|
+
linkedBySubjectId: input.linkedBySubjectId,
|
|
6336
|
+
githubActorId: input.githubActorId,
|
|
6337
|
+
githubActorLogin: input.githubActorLogin,
|
|
6338
|
+
authorityKind: input.authorityKind,
|
|
6339
|
+
authorityCheckedAt: input.authorityCheckedAt,
|
|
6340
|
+
authorityExpiresAt: input.authorityExpiresAt,
|
|
6341
|
+
authorityNonce: input.authorityNonce
|
|
6342
|
+
}).onConflictDoUpdate({
|
|
6343
|
+
target: [
|
|
6344
|
+
githubInstallations.workspaceId,
|
|
6345
|
+
githubInstallations.installationId
|
|
6346
|
+
],
|
|
6347
|
+
set: {
|
|
6348
|
+
accountId: input.accountId,
|
|
6349
|
+
githubAccountId: input.githubAccountId,
|
|
6350
|
+
accountLogin: input.accountLogin,
|
|
6351
|
+
accountType: input.accountType,
|
|
6352
|
+
repositoryScope: "selected",
|
|
6353
|
+
linkedBySubjectId: input.linkedBySubjectId,
|
|
6354
|
+
githubActorId: input.githubActorId,
|
|
6355
|
+
githubActorLogin: input.githubActorLogin,
|
|
6356
|
+
authorityKind: input.authorityKind,
|
|
6357
|
+
authorityCheckedAt: input.authorityCheckedAt,
|
|
6358
|
+
authorityExpiresAt: input.authorityExpiresAt,
|
|
6359
|
+
authorityNonce: input.authorityNonce,
|
|
6360
|
+
updatedAt: input.authorityCheckedAt
|
|
6067
6361
|
}
|
|
6068
6362
|
}).returning();
|
|
6069
6363
|
if (!row) {
|
|
@@ -6085,10 +6379,29 @@ async function bindGitHubInstallationRepositories(db, input) {
|
|
|
6085
6379
|
}))
|
|
6086
6380
|
);
|
|
6087
6381
|
}
|
|
6382
|
+
await assertGitHubAuthorityWindowOpen(
|
|
6383
|
+
tx,
|
|
6384
|
+
input.authorityCheckedAt,
|
|
6385
|
+
input.authorityExpiresAt
|
|
6386
|
+
);
|
|
6088
6387
|
return { ...mapGitHubInstallation(row), repositoryIds };
|
|
6089
6388
|
})
|
|
6090
6389
|
);
|
|
6091
6390
|
}
|
|
6391
|
+
async function assertGitHubAuthorityWindowOpen(tx, checkedAt, expiresAt) {
|
|
6392
|
+
const checkedAtIso = checkedAt.toISOString();
|
|
6393
|
+
const expiresAtIso = expiresAt.toISOString();
|
|
6394
|
+
const result = await tx.execute(sql4`
|
|
6395
|
+
select (
|
|
6396
|
+
${checkedAtIso}::timestamptz <= clock_timestamp()
|
|
6397
|
+
and clock_timestamp() < ${expiresAtIso}::timestamptz
|
|
6398
|
+
and ${expiresAtIso}::timestamptz <= ${checkedAtIso}::timestamptz + interval '10 minutes'
|
|
6399
|
+
) as valid
|
|
6400
|
+
`);
|
|
6401
|
+
if (result[0]?.valid !== true) {
|
|
6402
|
+
throw new GitHubInstallationAuthorityCommitError();
|
|
6403
|
+
}
|
|
6404
|
+
}
|
|
6092
6405
|
async function listGitHubInstallationsForWorkspace(db, workspaceId) {
|
|
6093
6406
|
return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
|
|
6094
6407
|
const rows = await scopedDb.select().from(githubInstallations).where(eq6(githubInstallations.workspaceId, workspaceId)).orderBy(desc(githubInstallations.updatedAt));
|
|
@@ -6128,7 +6441,7 @@ async function areGitHubRepositoriesAllowedForWorkspace(db, workspaceId, install
|
|
|
6128
6441
|
return false;
|
|
6129
6442
|
}
|
|
6130
6443
|
return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
|
|
6131
|
-
const [installation] = await scopedDb.select(
|
|
6444
|
+
const [installation] = await scopedDb.select().from(githubInstallations).where(
|
|
6132
6445
|
and6(
|
|
6133
6446
|
eq6(githubInstallations.workspaceId, workspaceId),
|
|
6134
6447
|
eq6(githubInstallations.installationId, installationId)
|
|
@@ -6137,9 +6450,6 @@ async function areGitHubRepositoriesAllowedForWorkspace(db, workspaceId, install
|
|
|
6137
6450
|
if (!installation) {
|
|
6138
6451
|
return false;
|
|
6139
6452
|
}
|
|
6140
|
-
if (installation.repositoryScope === "all") {
|
|
6141
|
-
return true;
|
|
6142
|
-
}
|
|
6143
6453
|
const allowed = await scopedDb.select({
|
|
6144
6454
|
repositoryId: githubInstallationRepositories.repositoryId
|
|
6145
6455
|
}).from(githubInstallationRepositories).where(
|
|
@@ -6149,7 +6459,13 @@ async function areGitHubRepositoriesAllowedForWorkspace(db, workspaceId, install
|
|
|
6149
6459
|
inArray3(githubInstallationRepositories.repositoryId, requestedIds)
|
|
6150
6460
|
)
|
|
6151
6461
|
);
|
|
6152
|
-
|
|
6462
|
+
if (allowed.length !== requestedIds.length) {
|
|
6463
|
+
return false;
|
|
6464
|
+
}
|
|
6465
|
+
return hasAuditableGitHubInstallationAuthority({
|
|
6466
|
+
...mapGitHubInstallation(installation),
|
|
6467
|
+
repositoryIds: allowed.map((row) => row.repositoryId)
|
|
6468
|
+
});
|
|
6153
6469
|
});
|
|
6154
6470
|
}
|
|
6155
6471
|
async function deleteGitHubInstallationBinding(db, input) {
|
|
@@ -10365,6 +10681,7 @@ function mapCodexCapacityWaiter(row) {
|
|
|
10365
10681
|
sessionId: row.sessionId,
|
|
10366
10682
|
goalId: row.goalId,
|
|
10367
10683
|
blockedTurnId: row.blockedTurnId,
|
|
10684
|
+
blockedTurnGeneration: row.blockedTurnGeneration,
|
|
10368
10685
|
workflowId: row.workflowId,
|
|
10369
10686
|
generation: row.generation,
|
|
10370
10687
|
status: row.status,
|
|
@@ -10411,6 +10728,11 @@ function nextCodexCapacityCheckAt(earliestResetAt, resetKind, refreshAttempt, no
|
|
|
10411
10728
|
}
|
|
10412
10729
|
async function armCodexCapacityWait(db, input) {
|
|
10413
10730
|
const now = input.now ?? /* @__PURE__ */ new Date();
|
|
10731
|
+
const goalId = input.goalId ?? null;
|
|
10732
|
+
const goalVersion = input.goalVersion ?? null;
|
|
10733
|
+
if (goalId === null !== (goalVersion === null) || goalVersion !== null && (!Number.isSafeInteger(goalVersion) || goalVersion < 1)) {
|
|
10734
|
+
throw new Error("Codex capacity goal fence must be absent or contain a positive version");
|
|
10735
|
+
}
|
|
10414
10736
|
return await withRlsContext(
|
|
10415
10737
|
db,
|
|
10416
10738
|
{ accountId: input.accountId, workspaceId: input.workspaceId },
|
|
@@ -10433,13 +10755,13 @@ async function armCodexCapacityWait(db, input) {
|
|
|
10433
10755
|
const effectiveControl = session ? await evaluateSessionControl(tx, input.workspaceId, input.sessionId, {
|
|
10434
10756
|
workspaceControl: locks.control ?? void 0
|
|
10435
10757
|
}) : null;
|
|
10436
|
-
const [goal] = await tx.select().from(sessionGoals).where(
|
|
10758
|
+
const [goal] = goalId ? await tx.select().from(sessionGoals).where(
|
|
10437
10759
|
and6(
|
|
10438
10760
|
eq6(sessionGoals.workspaceId, input.workspaceId),
|
|
10439
|
-
eq6(sessionGoals.id,
|
|
10761
|
+
eq6(sessionGoals.id, goalId),
|
|
10440
10762
|
eq6(sessionGoals.sessionId, input.sessionId)
|
|
10441
10763
|
)
|
|
10442
|
-
).for("update").limit(1);
|
|
10764
|
+
).for("update").limit(1) : [];
|
|
10443
10765
|
const leaseRows = input.leaseFence ? await tx.execute(sql4`
|
|
10444
10766
|
select holder_id, generation
|
|
10445
10767
|
from codex_credential_leases
|
|
@@ -10463,7 +10785,7 @@ async function armCodexCapacityWait(db, input) {
|
|
|
10463
10785
|
events: []
|
|
10464
10786
|
};
|
|
10465
10787
|
}
|
|
10466
|
-
if (existing?.status === "waiting" && existing.blockedTurnId === input.turnId && turn?.status === "
|
|
10788
|
+
if (existing?.status === "waiting" && existing.blockedTurnId === input.turnId && existing.blockedTurnGeneration === turn?.executionGeneration && turn?.status === "waiting_capacity" && session?.status === "waiting_capacity" && session.activeTurnId === input.turnId) {
|
|
10467
10789
|
return {
|
|
10468
10790
|
action: "waiting",
|
|
10469
10791
|
waiter: mapCodexCapacityWaiter(existing),
|
|
@@ -10474,7 +10796,7 @@ async function armCodexCapacityWait(db, input) {
|
|
|
10474
10796
|
const currentRedispatches = Number(turn?.metadata?.workerDeathRedispatches ?? 0);
|
|
10475
10797
|
const lease = leaseRows[0];
|
|
10476
10798
|
const leaseFenceValid = !input.leaseFence || lease?.holder_id === input.leaseFence.holderId && Number(lease.generation) === input.leaseFence.generation && currentRedispatches === (input.expectedRedispatches ?? currentRedispatches);
|
|
10477
|
-
if (!
|
|
10799
|
+
if (!session || !turn || effectiveControl?.state !== "active" || effectiveControl.settlement !== null || session.activeTurnId !== input.turnId || session.status !== "running" || goalId !== null && (!goal || goal.status !== "active" || goal.version !== goalVersion) || turn.status !== "running" || turn.activeAttemptId !== input.attemptId || !leaseFenceValid || codexCapacityPolicyHashFromTurnMetadata(turn.metadata) !== policyHash) {
|
|
10478
10800
|
return {
|
|
10479
10801
|
action: "stale",
|
|
10480
10802
|
waiter: existing ? mapCodexCapacityWaiter(existing) : null,
|
|
@@ -10488,7 +10810,7 @@ async function armCodexCapacityWait(db, input) {
|
|
|
10488
10810
|
sessionId: input.sessionId,
|
|
10489
10811
|
turnId: input.turnId,
|
|
10490
10812
|
executionGeneration: turn.executionGeneration,
|
|
10491
|
-
outcome: "
|
|
10813
|
+
outcome: "waiting_capacity",
|
|
10492
10814
|
closedAt: now
|
|
10493
10815
|
});
|
|
10494
10816
|
const generation = (existing?.generation ?? 0) + 1;
|
|
@@ -10503,12 +10825,13 @@ async function armCodexCapacityWait(db, input) {
|
|
|
10503
10825
|
accountId: input.accountId,
|
|
10504
10826
|
workspaceId: input.workspaceId,
|
|
10505
10827
|
sessionId: input.sessionId,
|
|
10506
|
-
goalId
|
|
10828
|
+
goalId,
|
|
10507
10829
|
blockedTurnId: input.turnId,
|
|
10830
|
+
blockedTurnGeneration: turn.executionGeneration,
|
|
10508
10831
|
workflowId: input.workflowId,
|
|
10509
10832
|
generation,
|
|
10510
10833
|
status: "waiting",
|
|
10511
|
-
goalVersion
|
|
10834
|
+
goalVersion,
|
|
10512
10835
|
policyHash,
|
|
10513
10836
|
earliestResetAt: input.earliestResetAt,
|
|
10514
10837
|
nextCheckAt,
|
|
@@ -10544,32 +10867,17 @@ async function armCodexCapacityWait(db, input) {
|
|
|
10544
10867
|
workspaceId: input.workspaceId,
|
|
10545
10868
|
sessionId: input.sessionId,
|
|
10546
10869
|
sequence: ++sequence,
|
|
10547
|
-
type: "
|
|
10870
|
+
type: "codex.capacity.waiting",
|
|
10548
10871
|
payload: sanitizeEventPayload({
|
|
10549
10872
|
...input.failurePayload,
|
|
10550
10873
|
recovery: "codex_capacity",
|
|
10551
|
-
retryable:
|
|
10874
|
+
retryable: true,
|
|
10552
10875
|
rotated: true,
|
|
10553
|
-
capacityWaiterId: waiterRow.id,
|
|
10554
|
-
capacityWaitGeneration: waiterRow.generation
|
|
10555
|
-
}),
|
|
10556
|
-
turnId: input.turnId,
|
|
10557
|
-
turnGeneration: turn.executionGeneration,
|
|
10558
|
-
turnAttemptId: input.attemptId,
|
|
10559
|
-
turnAssociation: "current",
|
|
10560
|
-
occurredAt: now
|
|
10561
|
-
},
|
|
10562
|
-
{
|
|
10563
|
-
accountId: input.accountId,
|
|
10564
|
-
workspaceId: input.workspaceId,
|
|
10565
|
-
sessionId: input.sessionId,
|
|
10566
|
-
sequence: ++sequence,
|
|
10567
|
-
type: "codex.capacity.waiting",
|
|
10568
|
-
payload: sanitizeEventPayload({
|
|
10569
10876
|
waiterId: waiterRow.id,
|
|
10570
10877
|
generation: waiterRow.generation,
|
|
10571
|
-
goalId
|
|
10572
|
-
goalVersion
|
|
10878
|
+
goalId,
|
|
10879
|
+
goalVersion,
|
|
10880
|
+
blockedTurnGeneration: turn.executionGeneration,
|
|
10573
10881
|
policyHash,
|
|
10574
10882
|
resetKind: input.resetKind,
|
|
10575
10883
|
earliestResetAt: input.earliestResetAt?.toISOString() ?? null,
|
|
@@ -10587,7 +10895,7 @@ async function armCodexCapacityWait(db, input) {
|
|
|
10587
10895
|
sessionId: input.sessionId,
|
|
10588
10896
|
sequence: ++sequence,
|
|
10589
10897
|
type: "session.status.changed",
|
|
10590
|
-
payload: { status: "
|
|
10898
|
+
payload: { status: "waiting_capacity", reason: "codex_capacity" },
|
|
10591
10899
|
turnId: input.turnId,
|
|
10592
10900
|
turnGeneration: turn.executionGeneration,
|
|
10593
10901
|
turnAttemptId: input.attemptId,
|
|
@@ -10595,37 +10903,50 @@ async function armCodexCapacityWait(db, input) {
|
|
|
10595
10903
|
occurredAt: now
|
|
10596
10904
|
}
|
|
10597
10905
|
]).returning();
|
|
10598
|
-
await tx.update(sessionTurns).set({
|
|
10599
|
-
status: "
|
|
10906
|
+
const [waitingTurn] = await tx.update(sessionTurns).set({
|
|
10907
|
+
status: "waiting_capacity",
|
|
10600
10908
|
activeAttemptId: null,
|
|
10909
|
+
metadata: metadataWithoutTurnDispatchAttempt(turn.metadata),
|
|
10601
10910
|
version: turn.version + 1,
|
|
10602
|
-
finishedAt:
|
|
10911
|
+
finishedAt: null,
|
|
10603
10912
|
updatedAt: now
|
|
10604
10913
|
}).where(
|
|
10605
10914
|
and6(
|
|
10606
10915
|
eq6(sessionTurns.workspaceId, input.workspaceId),
|
|
10607
10916
|
eq6(sessionTurns.id, input.turnId),
|
|
10608
|
-
eq6(sessionTurns.status, "running")
|
|
10917
|
+
eq6(sessionTurns.status, "running"),
|
|
10918
|
+
eq6(sessionTurns.activeAttemptId, input.attemptId)
|
|
10609
10919
|
)
|
|
10610
|
-
);
|
|
10611
|
-
|
|
10612
|
-
|
|
10613
|
-
|
|
10920
|
+
).returning({ id: sessionTurns.id });
|
|
10921
|
+
if (!waitingTurn) {
|
|
10922
|
+
throw new Error("Codex capacity blocked turn changed during atomic arm");
|
|
10923
|
+
}
|
|
10924
|
+
const [waitingSession] = await tx.update(sessions).set({
|
|
10925
|
+
status: "waiting_capacity",
|
|
10926
|
+
activeTurnId: input.turnId,
|
|
10614
10927
|
lastSequence: sequence,
|
|
10615
10928
|
updatedAt: now
|
|
10616
10929
|
}).where(
|
|
10617
10930
|
and6(
|
|
10618
10931
|
eq6(sessions.workspaceId, input.workspaceId),
|
|
10619
10932
|
eq6(sessions.id, input.sessionId),
|
|
10933
|
+
eq6(sessions.status, "running"),
|
|
10620
10934
|
eq6(sessions.activeTurnId, input.turnId)
|
|
10621
10935
|
)
|
|
10622
|
-
);
|
|
10623
|
-
|
|
10624
|
-
|
|
10625
|
-
|
|
10626
|
-
|
|
10627
|
-
|
|
10628
|
-
|
|
10936
|
+
).returning({ id: sessions.id });
|
|
10937
|
+
if (!waitingSession) {
|
|
10938
|
+
throw new Error("Codex capacity session changed during atomic arm");
|
|
10939
|
+
}
|
|
10940
|
+
if (input.leaseFence) {
|
|
10941
|
+
await tx.execute(sql4`
|
|
10942
|
+
delete from codex_credential_leases
|
|
10943
|
+
where account_id = ${input.accountId}
|
|
10944
|
+
and workspace_id = ${input.workspaceId}
|
|
10945
|
+
and turn_id = ${input.turnId}
|
|
10946
|
+
and holder_id = ${input.leaseFence.holderId}
|
|
10947
|
+
and generation = ${input.leaseFence.generation}
|
|
10948
|
+
`);
|
|
10949
|
+
}
|
|
10629
10950
|
return {
|
|
10630
10951
|
action: "waiting",
|
|
10631
10952
|
waiter: mapCodexCapacityWaiter(waiterRow),
|
|
@@ -10742,26 +11063,87 @@ async function supersedeCodexCapacityWaitInTransaction(tx, input) {
|
|
|
10742
11063
|
if (!updated) {
|
|
10743
11064
|
return { waiter: mapCodexCapacityWaiter(input.waiter), events: [] };
|
|
10744
11065
|
}
|
|
10745
|
-
const
|
|
10746
|
-
|
|
10747
|
-
|
|
10748
|
-
|
|
10749
|
-
|
|
10750
|
-
|
|
10751
|
-
|
|
10752
|
-
|
|
10753
|
-
|
|
10754
|
-
|
|
10755
|
-
|
|
10756
|
-
|
|
10757
|
-
|
|
10758
|
-
|
|
10759
|
-
|
|
11066
|
+
const turnWasCurrent = input.session.activeTurnId === input.blockedTurn.id;
|
|
11067
|
+
const turnStillWaiting = input.blockedTurn.status === "waiting_capacity";
|
|
11068
|
+
const terminalTurnStatus = input.session.status === "cancelled" ? "cancelled" : "superseded";
|
|
11069
|
+
if (turnStillWaiting) {
|
|
11070
|
+
const [supersededTurn] = await tx.update(sessionTurns).set({
|
|
11071
|
+
status: terminalTurnStatus,
|
|
11072
|
+
activeAttemptId: null,
|
|
11073
|
+
cancelledBy: "codex_capacity_reconcile",
|
|
11074
|
+
cancelReason: input.reason,
|
|
11075
|
+
version: input.blockedTurn.version + 1,
|
|
11076
|
+
finishedAt: input.now,
|
|
11077
|
+
updatedAt: input.now
|
|
11078
|
+
}).where(
|
|
11079
|
+
and6(
|
|
11080
|
+
eq6(sessionTurns.workspaceId, input.session.workspaceId),
|
|
11081
|
+
eq6(sessionTurns.id, input.blockedTurn.id),
|
|
11082
|
+
eq6(sessionTurns.status, "waiting_capacity"),
|
|
11083
|
+
isNull(sessionTurns.activeAttemptId),
|
|
11084
|
+
eq6(sessionTurns.executionGeneration, input.waiter.blockedTurnGeneration)
|
|
11085
|
+
)
|
|
11086
|
+
).returning({ id: sessionTurns.id });
|
|
11087
|
+
if (!supersededTurn) {
|
|
11088
|
+
throw new Error("Codex capacity blocked turn changed during atomic supersession");
|
|
11089
|
+
}
|
|
11090
|
+
}
|
|
11091
|
+
const [queued] = turnWasCurrent ? await tx.select({ id: sessionTurns.id }).from(sessionTurns).where(
|
|
11092
|
+
and6(
|
|
11093
|
+
eq6(sessionTurns.workspaceId, input.session.workspaceId),
|
|
11094
|
+
eq6(sessionTurns.sessionId, input.session.id),
|
|
11095
|
+
eq6(sessionTurns.status, "queued")
|
|
11096
|
+
)
|
|
11097
|
+
).limit(1) : [];
|
|
11098
|
+
const nextSessionStatus = input.session.status === "cancelled" ? "cancelled" : queued ? "queued" : "idle";
|
|
11099
|
+
const eventValues = [
|
|
11100
|
+
{
|
|
11101
|
+
accountId: input.session.accountId,
|
|
11102
|
+
workspaceId: input.session.workspaceId,
|
|
11103
|
+
sessionId: input.session.id,
|
|
11104
|
+
sequence: input.session.lastSequence + 1,
|
|
11105
|
+
type: "codex.capacity.superseded",
|
|
11106
|
+
payload: sanitizeEventPayload({
|
|
11107
|
+
waiterId: updated.id,
|
|
11108
|
+
generation: updated.generation,
|
|
11109
|
+
reason: input.reason
|
|
11110
|
+
}),
|
|
11111
|
+
turnId: updated.blockedTurnId,
|
|
11112
|
+
turnGeneration: input.blockedTurn.executionGeneration,
|
|
11113
|
+
...turnWasCurrent ? { turnAssociation: "current" } : {},
|
|
11114
|
+
occurredAt: input.now
|
|
11115
|
+
}
|
|
11116
|
+
];
|
|
11117
|
+
if (turnWasCurrent && input.session.status !== nextSessionStatus) {
|
|
11118
|
+
eventValues.push({
|
|
11119
|
+
accountId: input.session.accountId,
|
|
11120
|
+
workspaceId: input.session.workspaceId,
|
|
11121
|
+
sessionId: input.session.id,
|
|
11122
|
+
sequence: input.session.lastSequence + 2,
|
|
11123
|
+
type: "session.status.changed",
|
|
11124
|
+
payload: { status: nextSessionStatus, reason: input.reason },
|
|
11125
|
+
turnId: updated.blockedTurnId,
|
|
11126
|
+
turnGeneration: input.blockedTurn.executionGeneration,
|
|
11127
|
+
turnAssociation: "current",
|
|
11128
|
+
occurredAt: input.now
|
|
11129
|
+
});
|
|
11130
|
+
}
|
|
11131
|
+
const inserted = await tx.insert(sessionEvents).values(eventValues).returning();
|
|
11132
|
+
const lastSequence = input.session.lastSequence + inserted.length;
|
|
11133
|
+
const [updatedSession] = await tx.update(sessions).set({
|
|
11134
|
+
...turnWasCurrent ? { status: nextSessionStatus, activeTurnId: null } : {},
|
|
11135
|
+
lastSequence,
|
|
11136
|
+
updatedAt: input.now
|
|
11137
|
+
}).where(
|
|
10760
11138
|
and6(
|
|
10761
11139
|
eq6(sessions.workspaceId, input.session.workspaceId),
|
|
10762
|
-
eq6(sessions.id, input.session.id)
|
|
11140
|
+
eq6(sessions.id, input.session.id),
|
|
11141
|
+
...turnWasCurrent ? [eq6(sessions.activeTurnId, input.blockedTurn.id)] : []
|
|
10763
11142
|
)
|
|
10764
|
-
);
|
|
11143
|
+
).returning({ id: sessions.id });
|
|
11144
|
+
if (!updatedSession) {
|
|
11145
|
+
throw new Error("Codex capacity session changed during atomic supersession");
|
|
11146
|
+
}
|
|
10765
11147
|
return {
|
|
10766
11148
|
waiter: mapCodexCapacityWaiter(updated),
|
|
10767
11149
|
events: inserted.map(mapEvent2)
|
|
@@ -10808,61 +11190,47 @@ async function reconcileCodexCapacityWait(db, input, decide, policy) {
|
|
|
10808
11190
|
const effectiveControl = session ? await evaluateSessionControl(tx, input.workspaceId, input.sessionId, {
|
|
10809
11191
|
workspaceControl: prefix.control ?? void 0
|
|
10810
11192
|
}) : null;
|
|
10811
|
-
const [goal] = await tx.select().from(sessionGoals).where(
|
|
11193
|
+
const [goal] = waiterRead.goalId ? await tx.select().from(sessionGoals).where(
|
|
10812
11194
|
and6(
|
|
10813
11195
|
eq6(sessionGoals.workspaceId, input.workspaceId),
|
|
10814
11196
|
eq6(sessionGoals.id, waiterRead.goalId),
|
|
10815
11197
|
eq6(sessionGoals.sessionId, input.sessionId)
|
|
10816
11198
|
)
|
|
10817
|
-
).for("update").limit(1);
|
|
11199
|
+
).for("update").limit(1) : [];
|
|
10818
11200
|
const [waiter] = await tx.select().from(codexCapacityWaiters).where(eq6(codexCapacityWaiters.id, input.waiterId)).for("update").limit(1);
|
|
10819
|
-
if (!session || !
|
|
11201
|
+
if (!session || !blockedTurn || !waiter || session.accountId !== input.accountId || blockedTurn.accountId !== input.accountId || blockedTurn.sessionId !== input.sessionId || waiter.accountId !== input.accountId || waiter.workspaceId !== input.workspaceId || waiter.sessionId !== input.sessionId || waiter.blockedTurnId !== blockedTurn.id || waiter.generation !== input.generation || waiter.status !== "waiting") {
|
|
10820
11202
|
return {
|
|
10821
11203
|
action: "stale",
|
|
10822
11204
|
waiter: waiter ? mapCodexCapacityWaiter(waiter) : null,
|
|
10823
11205
|
events: []
|
|
10824
11206
|
};
|
|
10825
11207
|
}
|
|
10826
|
-
|
|
10827
|
-
|
|
10828
|
-
|
|
10829
|
-
|
|
10830
|
-
|
|
10831
|
-
|
|
10832
|
-
|
|
10833
|
-
"requires_action",
|
|
10834
|
-
"recovering",
|
|
10835
|
-
"waiting_capacity"
|
|
10836
|
-
])
|
|
10837
|
-
)
|
|
10838
|
-
).limit(1);
|
|
10839
|
-
const [laterTurn] = await tx.select({ id: sessionTurns.id }).from(sessionTurns).where(
|
|
10840
|
-
and6(
|
|
10841
|
-
eq6(sessionTurns.workspaceId, input.workspaceId),
|
|
10842
|
-
eq6(sessionTurns.sessionId, input.sessionId),
|
|
10843
|
-
gt(sessionTurns.position, blockedTurn.position)
|
|
10844
|
-
)
|
|
10845
|
-
).limit(1);
|
|
11208
|
+
if (effectiveControl?.state !== "active" || effectiveControl.settlement !== null) {
|
|
11209
|
+
return {
|
|
11210
|
+
action: "paused",
|
|
11211
|
+
waiter: mapCodexCapacityWaiter(waiter),
|
|
11212
|
+
events: []
|
|
11213
|
+
};
|
|
11214
|
+
}
|
|
10846
11215
|
const currentPolicyHash = codexCapacityPolicyHashFromTurnMetadata(blockedTurn.metadata);
|
|
10847
11216
|
let supersedeReason = null;
|
|
10848
|
-
if (
|
|
10849
|
-
supersedeReason = "
|
|
10850
|
-
} else if (goal.status !== "active" || goal.version !== waiter.goalVersion) {
|
|
11217
|
+
if (session.status === "cancelled") {
|
|
11218
|
+
supersedeReason = "session_cancelled";
|
|
11219
|
+
} else if (waiter.goalId !== null && (!goal || goal.status !== "active" || goal.version !== waiter.goalVersion)) {
|
|
10851
11220
|
supersedeReason = "goal_changed";
|
|
10852
11221
|
} else if (currentPolicyHash !== waiter.policyHash) {
|
|
10853
11222
|
supersedeReason = "credential_policy_changed";
|
|
10854
|
-
} else if (session.
|
|
10855
|
-
supersedeReason = "
|
|
10856
|
-
} else if (
|
|
11223
|
+
} else if (session.activeTurnId !== blockedTurn.id) {
|
|
11224
|
+
supersedeReason = "active_turn_changed";
|
|
11225
|
+
} else if (session.status !== "waiting_capacity") {
|
|
11226
|
+
supersedeReason = "session_not_waiting_capacity";
|
|
11227
|
+
} else if (blockedTurn.status !== "waiting_capacity" || blockedTurn.activeAttemptId !== null || blockedTurn.executionGeneration !== waiter.blockedTurnGeneration) {
|
|
10857
11228
|
supersedeReason = "blocked_turn_changed";
|
|
10858
|
-
} else if (pending) {
|
|
10859
|
-
supersedeReason = "pending_work_exists";
|
|
10860
|
-
} else if (laterTurn) {
|
|
10861
|
-
supersedeReason = "newer_turn_exists";
|
|
10862
11229
|
}
|
|
10863
11230
|
if (supersedeReason) {
|
|
10864
11231
|
const superseded = await supersedeCodexCapacityWaitInTransaction(tx, {
|
|
10865
11232
|
session,
|
|
11233
|
+
blockedTurn,
|
|
10866
11234
|
waiter,
|
|
10867
11235
|
reason: supersedeReason,
|
|
10868
11236
|
now
|
|
@@ -10927,77 +11295,26 @@ async function reconcileCodexCapacityWait(db, input, decide, policy) {
|
|
|
10927
11295
|
events: []
|
|
10928
11296
|
};
|
|
10929
11297
|
}
|
|
10930
|
-
const prompt = [
|
|
10931
|
-
"[CODEX CAPACITY RESUME] Codex subscription capacity is available again.",
|
|
10932
|
-
`Continue the existing active goal from durable conversation history: ${goal.text}`,
|
|
10933
|
-
`Success criteria: ${goal.successCriteria ?? "none specified"}.`,
|
|
10934
|
-
"Do not replay completed tool side effects; verify any ambiguous in-flight effect before repeating it.",
|
|
10935
|
-
"If the goal is complete, call opengeni__goal_complete. If blocked for another reason, call opengeni__goal_pause."
|
|
10936
|
-
].join("\n");
|
|
10937
|
-
const [update] = await tx.insert(sessionSystemUpdates).values({
|
|
10938
|
-
accountId: input.accountId,
|
|
10939
|
-
workspaceId: input.workspaceId,
|
|
10940
|
-
sessionId: input.sessionId,
|
|
10941
|
-
kind: "goal_continuation",
|
|
10942
|
-
classification: "info",
|
|
10943
|
-
sourceId: goal.id,
|
|
10944
|
-
dedupeKey: `codex-capacity-resume:${waiter.id}:${waiter.generation}`,
|
|
10945
|
-
summary: prompt,
|
|
10946
|
-
payload: {
|
|
10947
|
-
type: "goal_continuation",
|
|
10948
|
-
goalId: goal.id,
|
|
10949
|
-
goalVersion: goal.version,
|
|
10950
|
-
prompt,
|
|
10951
|
-
reason: "codex_capacity",
|
|
10952
|
-
capacityWaiterId: waiter.id,
|
|
10953
|
-
capacityWaitGeneration: waiter.generation,
|
|
10954
|
-
policy: {
|
|
10955
|
-
model: blockedTurn.model,
|
|
10956
|
-
reasoningEffort: blockedTurn.reasoningEffort,
|
|
10957
|
-
tools: blockedTurn.tools,
|
|
10958
|
-
sandboxBackend: blockedTurn.sandboxBackend
|
|
10959
|
-
}
|
|
10960
|
-
},
|
|
10961
|
-
lineage: {
|
|
10962
|
-
goalId: goal.id,
|
|
10963
|
-
blockedTurnId: blockedTurn.id,
|
|
10964
|
-
capacityWaiterId: waiter.id
|
|
10965
|
-
},
|
|
10966
|
-
state: "pending"
|
|
10967
|
-
}).returning();
|
|
10968
|
-
if (!update) {
|
|
10969
|
-
throw new Error("Codex capacity resume did not create an internal update");
|
|
10970
|
-
}
|
|
10971
|
-
await tx.insert(usageEvents).values({
|
|
10972
|
-
accountId: input.accountId,
|
|
10973
|
-
workspaceId: input.workspaceId,
|
|
10974
|
-
eventType: "agent_run.created",
|
|
10975
|
-
quantity: 1,
|
|
10976
|
-
unit: "run",
|
|
10977
|
-
sourceResourceType: "session_system_update",
|
|
10978
|
-
sourceResourceId: update.id,
|
|
10979
|
-
sessionId: input.sessionId,
|
|
10980
|
-
initiatorKind: "service",
|
|
10981
|
-
initiatorSubjectId: "goal-continuation",
|
|
10982
|
-
initiatorContext: { goalId: goal.id, reason: "codex_capacity" },
|
|
10983
|
-
origin: "goal",
|
|
10984
|
-
idempotencyKey: `agent_run.created:codex-capacity:${input.workspaceId}:${update.id}`,
|
|
10985
|
-
occurredAt: now
|
|
10986
|
-
}).onConflictDoNothing({ target: usageEvents.idempotencyKey });
|
|
10987
11298
|
const events = await tx.insert(sessionEvents).values([
|
|
10988
11299
|
{
|
|
10989
11300
|
accountId: input.accountId,
|
|
10990
11301
|
workspaceId: input.workspaceId,
|
|
10991
11302
|
sessionId: input.sessionId,
|
|
10992
11303
|
sequence: session.lastSequence + 1,
|
|
10993
|
-
type: "
|
|
11304
|
+
type: "codex.capacity.resumed",
|
|
10994
11305
|
payload: sanitizeEventPayload({
|
|
10995
|
-
|
|
10996
|
-
|
|
10997
|
-
|
|
10998
|
-
|
|
10999
|
-
|
|
11306
|
+
waiterId: waiter.id,
|
|
11307
|
+
generation: waiter.generation,
|
|
11308
|
+
wakeRevision: waiter.wakeRevision,
|
|
11309
|
+
goalId: waiter.goalId,
|
|
11310
|
+
goalVersion: waiter.goalVersion,
|
|
11311
|
+
blockedTurnGeneration: waiter.blockedTurnGeneration,
|
|
11312
|
+
policyHash: waiter.policyHash,
|
|
11313
|
+
diagnostic: decision.diagnostic ?? null
|
|
11000
11314
|
}),
|
|
11315
|
+
turnId: blockedTurn.id,
|
|
11316
|
+
turnGeneration: blockedTurn.executionGeneration,
|
|
11317
|
+
turnAssociation: "current",
|
|
11001
11318
|
occurredAt: now
|
|
11002
11319
|
},
|
|
11003
11320
|
{
|
|
@@ -11005,24 +11322,17 @@ async function reconcileCodexCapacityWait(db, input, decide, policy) {
|
|
|
11005
11322
|
workspaceId: input.workspaceId,
|
|
11006
11323
|
sessionId: input.sessionId,
|
|
11007
11324
|
sequence: session.lastSequence + 2,
|
|
11008
|
-
type: "
|
|
11009
|
-
payload:
|
|
11010
|
-
waiterId: waiter.id,
|
|
11011
|
-
generation: waiter.generation,
|
|
11012
|
-
wakeRevision: waiter.wakeRevision,
|
|
11013
|
-
goalId: goal.id,
|
|
11014
|
-
goalVersion: goal.version,
|
|
11015
|
-
policyHash: waiter.policyHash,
|
|
11016
|
-
diagnostic: decision.diagnostic ?? null,
|
|
11017
|
-
updateId: update.id
|
|
11018
|
-
}),
|
|
11325
|
+
type: "session.status.changed",
|
|
11326
|
+
payload: { status: "recovering", reason: "codex_capacity" },
|
|
11019
11327
|
turnId: blockedTurn.id,
|
|
11328
|
+
turnGeneration: blockedTurn.executionGeneration,
|
|
11329
|
+
turnAssociation: "current",
|
|
11020
11330
|
occurredAt: now
|
|
11021
11331
|
}
|
|
11022
11332
|
]).returning();
|
|
11023
11333
|
const [updatedWaiter] = await tx.update(codexCapacityWaiters).set({
|
|
11024
11334
|
status: "resumed",
|
|
11025
|
-
resumedUpdateId:
|
|
11335
|
+
resumedUpdateId: null,
|
|
11026
11336
|
observedWakeRevision: waiter.wakeRevision,
|
|
11027
11337
|
lastWakeReason: "capacity_available",
|
|
11028
11338
|
updatedAt: now
|
|
@@ -11036,22 +11346,44 @@ async function reconcileCodexCapacityWait(db, input, decide, policy) {
|
|
|
11036
11346
|
if (!updatedWaiter) {
|
|
11037
11347
|
throw new Error("Codex capacity waiter changed during atomic resume");
|
|
11038
11348
|
}
|
|
11039
|
-
await tx.update(
|
|
11040
|
-
status: "
|
|
11041
|
-
|
|
11349
|
+
const [recoveringTurn] = await tx.update(sessionTurns).set({
|
|
11350
|
+
status: "recovering",
|
|
11351
|
+
activeAttemptId: null,
|
|
11352
|
+
metadata: metadataWithoutTurnDispatchAttempt(blockedTurn.metadata),
|
|
11353
|
+
version: blockedTurn.version + 1,
|
|
11354
|
+
finishedAt: null,
|
|
11355
|
+
updatedAt: now
|
|
11356
|
+
}).where(
|
|
11357
|
+
and6(
|
|
11358
|
+
eq6(sessionTurns.workspaceId, input.workspaceId),
|
|
11359
|
+
eq6(sessionTurns.id, blockedTurn.id),
|
|
11360
|
+
eq6(sessionTurns.status, "waiting_capacity"),
|
|
11361
|
+
isNull(sessionTurns.activeAttemptId),
|
|
11362
|
+
eq6(sessionTurns.executionGeneration, waiter.blockedTurnGeneration)
|
|
11363
|
+
)
|
|
11364
|
+
).returning({ id: sessionTurns.id });
|
|
11365
|
+
if (!recoveringTurn) {
|
|
11366
|
+
throw new Error("Codex capacity blocked turn changed during atomic resume");
|
|
11367
|
+
}
|
|
11368
|
+
const [recoveringSession] = await tx.update(sessions).set({
|
|
11369
|
+
status: "recovering",
|
|
11370
|
+
activeTurnId: blockedTurn.id,
|
|
11042
11371
|
lastSequence: session.lastSequence + 2,
|
|
11043
11372
|
updatedAt: now
|
|
11044
11373
|
}).where(
|
|
11045
11374
|
and6(
|
|
11046
11375
|
eq6(sessions.workspaceId, input.workspaceId),
|
|
11047
11376
|
eq6(sessions.id, input.sessionId),
|
|
11048
|
-
|
|
11377
|
+
eq6(sessions.status, "waiting_capacity"),
|
|
11378
|
+
eq6(sessions.activeTurnId, blockedTurn.id)
|
|
11049
11379
|
)
|
|
11050
|
-
);
|
|
11380
|
+
).returning({ id: sessions.id });
|
|
11381
|
+
if (!recoveringSession) {
|
|
11382
|
+
throw new Error("Codex capacity session changed during atomic resume");
|
|
11383
|
+
}
|
|
11051
11384
|
return {
|
|
11052
11385
|
action: "resumed",
|
|
11053
11386
|
waiter: mapCodexCapacityWaiter(updatedWaiter),
|
|
11054
|
-
update: mapSessionSystemUpdate(update),
|
|
11055
11387
|
events: events.map(mapEvent2)
|
|
11056
11388
|
};
|
|
11057
11389
|
})
|
|
@@ -13349,17 +13681,19 @@ async function listSessionsForSubject(db, workspaceId, options) {
|
|
|
13349
13681
|
if (ordinaryIds.length > limit) {
|
|
13350
13682
|
let snapshot = reusableSnapshot;
|
|
13351
13683
|
if (!snapshot) {
|
|
13352
|
-
|
|
13353
|
-
|
|
13354
|
-
|
|
13355
|
-
|
|
13356
|
-
|
|
13357
|
-
|
|
13358
|
-
|
|
13359
|
-
|
|
13360
|
-
|
|
13361
|
-
|
|
13362
|
-
|
|
13684
|
+
await tx.execute(sql4`
|
|
13685
|
+
delete from ${sessionListSnapshots} snapshot
|
|
13686
|
+
where snapshot.workspace_id = ${workspaceId}
|
|
13687
|
+
and snapshot.subject_id = ${options.subjectId}
|
|
13688
|
+
and snapshot.id in (
|
|
13689
|
+
select evicted.id
|
|
13690
|
+
from ${sessionListSnapshots} evicted
|
|
13691
|
+
where evicted.workspace_id = ${workspaceId}
|
|
13692
|
+
and evicted.subject_id = ${options.subjectId}
|
|
13693
|
+
order by evicted.created_at desc, evicted.id desc
|
|
13694
|
+
offset ${SESSION_LIST_SNAPSHOT_MAX_ACTIVE_PER_SUBJECT - 1}
|
|
13695
|
+
)
|
|
13696
|
+
`);
|
|
13363
13697
|
const [workspace] = await tx.select({ accountId: workspaces.accountId }).from(workspaces).where(eq6(workspaces.id, workspaceId)).limit(1);
|
|
13364
13698
|
if (!workspace) {
|
|
13365
13699
|
throw new Error("session list workspace disappeared while creating a snapshot");
|
|
@@ -16163,7 +16497,7 @@ async function acquireLease(db, input) {
|
|
|
16163
16497
|
}
|
|
16164
16498
|
if (liveness === "cold") {
|
|
16165
16499
|
const recovery = recoveryStateFromLeaseRow(row);
|
|
16166
|
-
if (recovery.restore.status === "degraded" || recovery.restore.status === "unrecoverable") {
|
|
16500
|
+
if (recovery.restore.status === "degraded" && recovery.restore.retryable !== true || recovery.restore.status === "unrecoverable") {
|
|
16167
16501
|
return {
|
|
16168
16502
|
role: "blocked",
|
|
16169
16503
|
code: recovery.restore.status === "degraded" ? "restore_degraded" : "restore_unrecoverable",
|
|
@@ -16642,12 +16976,148 @@ async function recordWarmingSandboxCreated(db, input) {
|
|
|
16642
16976
|
})
|
|
16643
16977
|
);
|
|
16644
16978
|
}
|
|
16979
|
+
var LOST_PROVIDER_PROCESS_REASON = "provider_instance_lost";
|
|
16980
|
+
async function lockExactLostProviderWorkspaceBlockersTx(tx, input) {
|
|
16981
|
+
await tx.execute(sql4`
|
|
16982
|
+
select id from sandbox_retained_processes
|
|
16983
|
+
where account_id = ${input.accountId}
|
|
16984
|
+
and workspace_id = ${input.workspaceId}
|
|
16985
|
+
and lease_id = ${input.leaseId}
|
|
16986
|
+
and sandbox_group_id = ${input.sandboxGroupId}
|
|
16987
|
+
and lease_epoch = ${input.lostEpoch}
|
|
16988
|
+
and provider_instance_id = ${input.lostInstanceId}
|
|
16989
|
+
and state = 'active'
|
|
16990
|
+
order by id
|
|
16991
|
+
for update
|
|
16992
|
+
`);
|
|
16993
|
+
await tx.execute(sql4`
|
|
16994
|
+
select id from sandbox_workspace_mutation_admissions
|
|
16995
|
+
where account_id = ${input.accountId}
|
|
16996
|
+
and workspace_id = ${input.workspaceId}
|
|
16997
|
+
and lease_id = ${input.leaseId}
|
|
16998
|
+
and sandbox_group_id = ${input.sandboxGroupId}
|
|
16999
|
+
and lease_epoch = ${input.lostEpoch}
|
|
17000
|
+
and provider_instance_id = ${input.lostInstanceId}
|
|
17001
|
+
and settled_at is null
|
|
17002
|
+
order by id
|
|
17003
|
+
for update
|
|
17004
|
+
`);
|
|
17005
|
+
await tx.execute(sql4`
|
|
17006
|
+
select id from sandbox_pty_sessions
|
|
17007
|
+
where account_id = ${input.accountId}
|
|
17008
|
+
and workspace_id = ${input.workspaceId}
|
|
17009
|
+
and lease_id = ${input.leaseId}
|
|
17010
|
+
and sandbox_group_id = ${input.sandboxGroupId}
|
|
17011
|
+
and lease_epoch = ${input.lostEpoch}
|
|
17012
|
+
and provider_instance_id = ${input.lostInstanceId}
|
|
17013
|
+
and status = 'open'
|
|
17014
|
+
order by id
|
|
17015
|
+
for update
|
|
17016
|
+
`);
|
|
17017
|
+
}
|
|
17018
|
+
async function settleExactLostProviderWorkspaceBlockersTx(tx, input) {
|
|
17019
|
+
const lostProcesses = await tx.update(sandboxRetainedProcesses).set({
|
|
17020
|
+
state: "lost",
|
|
17021
|
+
exitCode: null,
|
|
17022
|
+
settlementReason: LOST_PROVIDER_PROCESS_REASON,
|
|
17023
|
+
settledAt: /* @__PURE__ */ new Date()
|
|
17024
|
+
}).where(
|
|
17025
|
+
and6(
|
|
17026
|
+
eq6(sandboxRetainedProcesses.accountId, input.accountId),
|
|
17027
|
+
eq6(sandboxRetainedProcesses.workspaceId, input.workspaceId),
|
|
17028
|
+
eq6(sandboxRetainedProcesses.leaseId, input.leaseId),
|
|
17029
|
+
eq6(sandboxRetainedProcesses.sandboxGroupId, input.sandboxGroupId),
|
|
17030
|
+
eq6(sandboxRetainedProcesses.leaseEpoch, input.lostEpoch),
|
|
17031
|
+
eq6(sandboxRetainedProcesses.providerInstanceId, input.lostInstanceId),
|
|
17032
|
+
eq6(sandboxRetainedProcesses.state, "active")
|
|
17033
|
+
)
|
|
17034
|
+
).returning({
|
|
17035
|
+
id: sandboxRetainedProcesses.id,
|
|
17036
|
+
holderId: sandboxRetainedProcesses.holderId
|
|
17037
|
+
});
|
|
17038
|
+
const rejectedAdmissions = await tx.update(sandboxWorkspaceMutationAdmissions).set({ providerOutcome: "rejected", settledAt: /* @__PURE__ */ new Date() }).where(
|
|
17039
|
+
and6(
|
|
17040
|
+
eq6(sandboxWorkspaceMutationAdmissions.accountId, input.accountId),
|
|
17041
|
+
eq6(sandboxWorkspaceMutationAdmissions.workspaceId, input.workspaceId),
|
|
17042
|
+
eq6(sandboxWorkspaceMutationAdmissions.leaseId, input.leaseId),
|
|
17043
|
+
eq6(sandboxWorkspaceMutationAdmissions.sandboxGroupId, input.sandboxGroupId),
|
|
17044
|
+
eq6(sandboxWorkspaceMutationAdmissions.leaseEpoch, input.lostEpoch),
|
|
17045
|
+
eq6(sandboxWorkspaceMutationAdmissions.providerInstanceId, input.lostInstanceId),
|
|
17046
|
+
isNull(sandboxWorkspaceMutationAdmissions.settledAt)
|
|
17047
|
+
)
|
|
17048
|
+
).returning({ id: sandboxWorkspaceMutationAdmissions.id });
|
|
17049
|
+
const closedPtys = await tx.update(sandboxPtySessions).set({ status: "closed", closedAt: /* @__PURE__ */ new Date() }).where(
|
|
17050
|
+
and6(
|
|
17051
|
+
eq6(sandboxPtySessions.accountId, input.accountId),
|
|
17052
|
+
eq6(sandboxPtySessions.workspaceId, input.workspaceId),
|
|
17053
|
+
eq6(sandboxPtySessions.leaseId, input.leaseId),
|
|
17054
|
+
eq6(sandboxPtySessions.sandboxGroupId, input.sandboxGroupId),
|
|
17055
|
+
eq6(sandboxPtySessions.leaseEpoch, input.lostEpoch),
|
|
17056
|
+
eq6(sandboxPtySessions.providerInstanceId, input.lostInstanceId),
|
|
17057
|
+
eq6(sandboxPtySessions.status, "open")
|
|
17058
|
+
)
|
|
17059
|
+
).returning({ id: sandboxPtySessions.id });
|
|
17060
|
+
const deletedHolders = lostProcesses.length === 0 ? [] : await tx.delete(sandboxLeaseHolders).where(
|
|
17061
|
+
and6(
|
|
17062
|
+
eq6(sandboxLeaseHolders.accountId, input.accountId),
|
|
17063
|
+
eq6(sandboxLeaseHolders.workspaceId, input.workspaceId),
|
|
17064
|
+
eq6(sandboxLeaseHolders.leaseId, input.leaseId),
|
|
17065
|
+
eq6(sandboxLeaseHolders.kind, "process"),
|
|
17066
|
+
inArray3(
|
|
17067
|
+
sandboxLeaseHolders.holderId,
|
|
17068
|
+
lostProcesses.map((process) => process.holderId)
|
|
17069
|
+
)
|
|
17070
|
+
)
|
|
17071
|
+
).returning({ holderId: sandboxLeaseHolders.holderId });
|
|
17072
|
+
await tx.execute(sql4`
|
|
17073
|
+
update sandbox_leases as lease set
|
|
17074
|
+
refcount = counts.total,
|
|
17075
|
+
turn_holders = counts.turns,
|
|
17076
|
+
viewer_holders = counts.viewers,
|
|
17077
|
+
updated_at = now()
|
|
17078
|
+
from (
|
|
17079
|
+
select count(*)::int as total,
|
|
17080
|
+
count(*) filter (where kind = 'turn')::int as turns,
|
|
17081
|
+
count(*) filter (where kind = 'viewer')::int as viewers
|
|
17082
|
+
from sandbox_lease_holders
|
|
17083
|
+
where lease_id = ${input.leaseId}
|
|
17084
|
+
) as counts
|
|
17085
|
+
where lease.id = ${input.leaseId}
|
|
17086
|
+
`);
|
|
17087
|
+
return {
|
|
17088
|
+
processesLost: lostProcesses.length,
|
|
17089
|
+
admissionsRejected: rejectedAdmissions.length,
|
|
17090
|
+
ptysClosed: closedPtys.length,
|
|
17091
|
+
processHoldersDeleted: deletedHolders.length
|
|
17092
|
+
};
|
|
17093
|
+
}
|
|
16645
17094
|
async function markWarmLeaseInstanceLost(db, input) {
|
|
16646
17095
|
return await withRlsContext(
|
|
16647
17096
|
db,
|
|
16648
17097
|
{ accountId: input.accountId, workspaceId: input.workspaceId },
|
|
16649
17098
|
async (scopedDb) => await scopedDb.transaction(async (txRaw) => {
|
|
16650
17099
|
const tx = txRaw;
|
|
17100
|
+
const observedRows = await tx.execute(sql4`
|
|
17101
|
+
select * from sandbox_leases
|
|
17102
|
+
where workspace_id = ${input.workspaceId}
|
|
17103
|
+
and sandbox_group_id = ${input.sandboxGroupId}
|
|
17104
|
+
`);
|
|
17105
|
+
const observed = observedRows[0];
|
|
17106
|
+
if (!observed || observed.liveness !== "warm" || Number(observed.lease_epoch) !== input.expectedEpoch || observed.instance_id !== input.expectedInstanceId) {
|
|
17107
|
+
return {
|
|
17108
|
+
status: "stale",
|
|
17109
|
+
lease: observed ? mapLeaseRow(observed) : null
|
|
17110
|
+
};
|
|
17111
|
+
}
|
|
17112
|
+
const blockerScope = {
|
|
17113
|
+
accountId: input.accountId,
|
|
17114
|
+
workspaceId: input.workspaceId,
|
|
17115
|
+
leaseId: observed.id,
|
|
17116
|
+
sandboxGroupId: input.sandboxGroupId,
|
|
17117
|
+
lostEpoch: input.expectedEpoch,
|
|
17118
|
+
lostInstanceId: input.expectedInstanceId
|
|
17119
|
+
};
|
|
17120
|
+
await lockExactLostProviderWorkspaceBlockersTx(tx, blockerScope);
|
|
16651
17121
|
const currentRows = await tx.execute(sql4`
|
|
16652
17122
|
select * from sandbox_leases
|
|
16653
17123
|
where workspace_id = ${input.workspaceId}
|
|
@@ -16655,12 +17125,13 @@ async function markWarmLeaseInstanceLost(db, input) {
|
|
|
16655
17125
|
for update
|
|
16656
17126
|
`);
|
|
16657
17127
|
const current = currentRows[0];
|
|
16658
|
-
if (!current || current.liveness !== "warm" || Number(current.lease_epoch) !== input.expectedEpoch || current.instance_id !== input.expectedInstanceId) {
|
|
17128
|
+
if (!current || current.id !== observed.id || current.liveness !== "warm" || Number(current.lease_epoch) !== input.expectedEpoch || current.instance_id !== input.expectedInstanceId) {
|
|
16659
17129
|
return {
|
|
16660
17130
|
status: "stale",
|
|
16661
17131
|
lease: current ? mapLeaseRow(current) : null
|
|
16662
17132
|
};
|
|
16663
17133
|
}
|
|
17134
|
+
const settlement = await settleExactLostProviderWorkspaceBlockersTx(tx, blockerScope);
|
|
16664
17135
|
const observedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
16665
17136
|
const before = recoveryStateFromLeaseRow(current);
|
|
16666
17137
|
const archiveStatus = before.archive.status;
|
|
@@ -16711,7 +17182,66 @@ async function markWarmLeaseInstanceLost(db, input) {
|
|
|
16711
17182
|
if (!updated) {
|
|
16712
17183
|
throw new Error(`Warm sandbox lease vanished while retiring instance ${current.id}`);
|
|
16713
17184
|
}
|
|
16714
|
-
return { status: "marked", lease: mapLeaseRow(updated) };
|
|
17185
|
+
return { status: "marked", lease: mapLeaseRow(updated), settlement };
|
|
17186
|
+
})
|
|
17187
|
+
);
|
|
17188
|
+
}
|
|
17189
|
+
async function reconcileColdLostLeaseInstanceBlockers(db, input) {
|
|
17190
|
+
if (!Number.isSafeInteger(input.expectedCurrentEpoch) || !Number.isSafeInteger(input.expectedLostEpoch) || input.expectedCurrentEpoch !== input.expectedLostEpoch + 1) {
|
|
17191
|
+
throw new Error("Cold lost-provider reconciliation requires currentEpoch = lostEpoch + 1");
|
|
17192
|
+
}
|
|
17193
|
+
return await withRlsContext(
|
|
17194
|
+
db,
|
|
17195
|
+
{ accountId: input.accountId, workspaceId: input.workspaceId },
|
|
17196
|
+
async (scopedDb) => await scopedDb.transaction(async (txRaw) => {
|
|
17197
|
+
const tx = txRaw;
|
|
17198
|
+
const observedRows = await tx.execute(sql4`
|
|
17199
|
+
select * from sandbox_leases
|
|
17200
|
+
where workspace_id = ${input.workspaceId}
|
|
17201
|
+
and sandbox_group_id = ${input.sandboxGroupId}
|
|
17202
|
+
`);
|
|
17203
|
+
const observed = observedRows[0];
|
|
17204
|
+
const observedRecovery = observed ? recoveryStateFromLeaseRow(observed) : null;
|
|
17205
|
+
if (!observed || observed.liveness !== "cold" || observed.instance_id !== null || Number(observed.lease_epoch) !== input.expectedCurrentEpoch || Number(observed.workspace_generation) !== input.expectedWorkspaceGeneration || (observed.archive_generation === null ? null : Number(observed.archive_generation)) !== input.expectedArchiveGeneration || hasCompleteWorkspaceArchive(observed) !== input.expectedArchiveComplete || observedRecovery?.provider.status !== "missing" || observedRecovery.provider.instanceId !== input.expectedLostInstanceId) {
|
|
17206
|
+
return {
|
|
17207
|
+
status: "stale",
|
|
17208
|
+
lease: observed ? mapLeaseRow(observed) : null
|
|
17209
|
+
};
|
|
17210
|
+
}
|
|
17211
|
+
const blockerScope = {
|
|
17212
|
+
accountId: input.accountId,
|
|
17213
|
+
workspaceId: input.workspaceId,
|
|
17214
|
+
leaseId: observed.id,
|
|
17215
|
+
sandboxGroupId: input.sandboxGroupId,
|
|
17216
|
+
lostEpoch: input.expectedLostEpoch,
|
|
17217
|
+
lostInstanceId: input.expectedLostInstanceId
|
|
17218
|
+
};
|
|
17219
|
+
await lockExactLostProviderWorkspaceBlockersTx(tx, blockerScope);
|
|
17220
|
+
const currentRows = await tx.execute(sql4`
|
|
17221
|
+
select * from sandbox_leases
|
|
17222
|
+
where workspace_id = ${input.workspaceId}
|
|
17223
|
+
and sandbox_group_id = ${input.sandboxGroupId}
|
|
17224
|
+
for update
|
|
17225
|
+
`);
|
|
17226
|
+
const current = currentRows[0];
|
|
17227
|
+
const recovery = current ? recoveryStateFromLeaseRow(current) : null;
|
|
17228
|
+
if (!current || current.id !== observed.id || current.liveness !== "cold" || current.instance_id !== null || Number(current.lease_epoch) !== input.expectedCurrentEpoch || Number(current.workspace_generation) !== input.expectedWorkspaceGeneration || (current.archive_generation === null ? null : Number(current.archive_generation)) !== input.expectedArchiveGeneration || hasCompleteWorkspaceArchive(current) !== input.expectedArchiveComplete || recovery?.provider.status !== "missing" || recovery.provider.instanceId !== input.expectedLostInstanceId) {
|
|
17229
|
+
return {
|
|
17230
|
+
status: "stale",
|
|
17231
|
+
lease: current ? mapLeaseRow(current) : null
|
|
17232
|
+
};
|
|
17233
|
+
}
|
|
17234
|
+
const settlement = await settleExactLostProviderWorkspaceBlockersTx(tx, blockerScope);
|
|
17235
|
+
const refreshedRows = await tx.execute(sql4`
|
|
17236
|
+
select * from sandbox_leases where id = ${current.id}
|
|
17237
|
+
`);
|
|
17238
|
+
const refreshed = refreshedRows[0];
|
|
17239
|
+
if (!refreshed) throw new Error("Cold sandbox lease vanished during reconciliation");
|
|
17240
|
+
return {
|
|
17241
|
+
status: "reconciled",
|
|
17242
|
+
lease: mapLeaseRow(refreshed),
|
|
17243
|
+
settlement
|
|
17244
|
+
};
|
|
16715
17245
|
})
|
|
16716
17246
|
);
|
|
16717
17247
|
}
|
|
@@ -17204,6 +17734,45 @@ var SandboxWorkspaceMutationFencedError = class extends Error {
|
|
|
17204
17734
|
}
|
|
17205
17735
|
name = "SandboxWorkspaceMutationFencedError";
|
|
17206
17736
|
};
|
|
17737
|
+
function retainedProcessSettlementIdentity(process) {
|
|
17738
|
+
return {
|
|
17739
|
+
leaseId: process.leaseId,
|
|
17740
|
+
sandboxGroupId: process.sandboxGroupId,
|
|
17741
|
+
parentAdmissionId: process.parentAdmissionId,
|
|
17742
|
+
holderId: process.holderId,
|
|
17743
|
+
leaseEpoch: process.leaseEpoch,
|
|
17744
|
+
providerBackend: process.providerBackend,
|
|
17745
|
+
providerInstanceId: process.providerInstanceId,
|
|
17746
|
+
routeKind: process.routeKind,
|
|
17747
|
+
routeTargetId: process.routeTargetId,
|
|
17748
|
+
routeEpoch: process.routeEpoch,
|
|
17749
|
+
providerSessionId: process.providerSessionId
|
|
17750
|
+
};
|
|
17751
|
+
}
|
|
17752
|
+
function retainedProcessReconciliationProof(process) {
|
|
17753
|
+
if (process.reconcileProofOutcome === "exited" && process.reconcileProofReason === "provider_exit_banner" && process.reconcileProofExitCode !== null) {
|
|
17754
|
+
return {
|
|
17755
|
+
outcome: "exited",
|
|
17756
|
+
exitCode: process.reconcileProofExitCode,
|
|
17757
|
+
reason: "provider_exit_banner"
|
|
17758
|
+
};
|
|
17759
|
+
}
|
|
17760
|
+
if (process.reconcileProofOutcome === "lost" && (process.reconcileProofReason === "provider_session_lost_banner" || process.reconcileProofReason === "provider_instance_not_found")) {
|
|
17761
|
+
return {
|
|
17762
|
+
outcome: "lost",
|
|
17763
|
+
exitCode: null,
|
|
17764
|
+
reason: process.reconcileProofReason
|
|
17765
|
+
};
|
|
17766
|
+
}
|
|
17767
|
+
return null;
|
|
17768
|
+
}
|
|
17769
|
+
var SandboxRetainedProcessPromotionFencedError = class extends SandboxWorkspaceMutationFencedError {
|
|
17770
|
+
constructor(code, message, process) {
|
|
17771
|
+
super(code, message);
|
|
17772
|
+
this.process = process;
|
|
17773
|
+
}
|
|
17774
|
+
name = "SandboxRetainedProcessPromotionFencedError";
|
|
17775
|
+
};
|
|
17207
17776
|
function normalizeWorkspaceMutationOperation(operation) {
|
|
17208
17777
|
const normalized = operation.trim();
|
|
17209
17778
|
const byteLength = new TextEncoder().encode(normalized).byteLength;
|
|
@@ -17233,6 +17802,17 @@ function normalizeRetainedProcessSettlementReason(reason) {
|
|
|
17233
17802
|
}
|
|
17234
17803
|
return bounded;
|
|
17235
17804
|
}
|
|
17805
|
+
function normalizeRetainedProcessReconciliationOutcome(outcome) {
|
|
17806
|
+
const normalized = outcome.trim();
|
|
17807
|
+
const byteLength = Buffer.byteLength(normalized, "utf8");
|
|
17808
|
+
if (byteLength < 1 || byteLength > 64) {
|
|
17809
|
+
throw new SandboxWorkspaceMutationFencedError(
|
|
17810
|
+
"process_fenced",
|
|
17811
|
+
"Retained process reconciliation outcome must contain between 1 and 64 UTF-8 bytes"
|
|
17812
|
+
);
|
|
17813
|
+
}
|
|
17814
|
+
return normalized;
|
|
17815
|
+
}
|
|
17236
17816
|
function mapWorkspaceMutationAdmission(row) {
|
|
17237
17817
|
return {
|
|
17238
17818
|
id: row.id,
|
|
@@ -17278,9 +17858,21 @@ function mapRetainedProcess(row) {
|
|
|
17278
17858
|
exitCode: row.exitCode ?? null,
|
|
17279
17859
|
settlementReason: row.settlementReason ?? null,
|
|
17280
17860
|
startedAt: row.startedAt.toISOString(),
|
|
17281
|
-
settledAt: row.settledAt?.toISOString() ?? null
|
|
17861
|
+
settledAt: row.settledAt?.toISOString() ?? null,
|
|
17862
|
+
reconcileAfter: row.reconcileAfter.toISOString(),
|
|
17863
|
+
reconcileClaimId: row.reconcileClaimId ?? null,
|
|
17864
|
+
reconcileClaimedAt: row.reconcileClaimedAt?.toISOString() ?? null,
|
|
17865
|
+
reconcileAttempts: row.reconcileAttempts,
|
|
17866
|
+
lastReconcileOutcome: row.lastReconcileOutcome ?? null,
|
|
17867
|
+
reconcileProofOutcome: row.reconcileProofOutcome ?? null,
|
|
17868
|
+
reconcileProofExitCode: row.reconcileProofExitCode ?? null,
|
|
17869
|
+
reconcileProofReason: row.reconcileProofReason ?? null,
|
|
17870
|
+
reconcileProofObservedAt: row.reconcileProofObservedAt?.toISOString() ?? null
|
|
17282
17871
|
};
|
|
17283
17872
|
}
|
|
17873
|
+
function retainedProcessMatchesSettlementIdentity(process, expected) {
|
|
17874
|
+
return process.leaseId === expected.leaseId && process.sandboxGroupId === expected.sandboxGroupId && process.parentAdmissionId === expected.parentAdmissionId && process.holderId === expected.holderId && process.leaseEpoch === expected.leaseEpoch && process.providerBackend === expected.providerBackend && process.providerInstanceId === expected.providerInstanceId && process.routeKind === expected.routeKind && (process.routeTargetId ?? null) === expected.routeTargetId && process.routeEpoch === expected.routeEpoch && process.providerSessionId === expected.providerSessionId;
|
|
17875
|
+
}
|
|
17284
17876
|
async function lockWorkspaceMutationSessionTx(tx, workspaceId, sessionId) {
|
|
17285
17877
|
const locks = await lockSessionEventWriteRows(tx, {
|
|
17286
17878
|
workspaceId,
|
|
@@ -17901,7 +18493,11 @@ async function retainWorkspaceMutationProcess(db, input) {
|
|
|
17901
18493
|
})
|
|
17902
18494
|
);
|
|
17903
18495
|
if (result.failure.failure !== null) {
|
|
17904
|
-
throw new
|
|
18496
|
+
throw new SandboxRetainedProcessPromotionFencedError(
|
|
18497
|
+
result.failure.failure,
|
|
18498
|
+
result.failure.detail,
|
|
18499
|
+
result.process
|
|
18500
|
+
);
|
|
17905
18501
|
}
|
|
17906
18502
|
return result.process;
|
|
17907
18503
|
}
|
|
@@ -17917,6 +18513,186 @@ async function getRetainedProcess(db, input) {
|
|
|
17917
18513
|
return row ? mapRetainedProcess(row) : null;
|
|
17918
18514
|
});
|
|
17919
18515
|
}
|
|
18516
|
+
async function claimTerminalRetainedProcesses(db, input) {
|
|
18517
|
+
if (!Number.isSafeInteger(input.limit) || input.limit < 1 || input.limit > 100) {
|
|
18518
|
+
throw new Error("Retained process reconciliation limit must be between 1 and 100");
|
|
18519
|
+
}
|
|
18520
|
+
if (!Number.isSafeInteger(input.claimTtlMs) || input.claimTtlMs < 0 || input.claimTtlMs > 36e5) {
|
|
18521
|
+
throw new Error("Retained process reconciliation claim TTL is invalid");
|
|
18522
|
+
}
|
|
18523
|
+
const rows = await rawRows(
|
|
18524
|
+
db,
|
|
18525
|
+
sql4`
|
|
18526
|
+
select account_id, workspace_id, session_id, process_id, claim_id,
|
|
18527
|
+
owner_state, owner_attempt_outcome
|
|
18528
|
+
from opengeni_private.claim_terminal_retained_processes(
|
|
18529
|
+
${input.claimId}::uuid, ${input.limit}::integer, ${input.claimTtlMs}::bigint
|
|
18530
|
+
)
|
|
18531
|
+
`
|
|
18532
|
+
);
|
|
18533
|
+
const claims = [];
|
|
18534
|
+
for (const row of rows) {
|
|
18535
|
+
const process = await getRetainedProcess(db, {
|
|
18536
|
+
workspaceId: row.workspace_id,
|
|
18537
|
+
sessionId: row.session_id,
|
|
18538
|
+
processId: row.process_id
|
|
18539
|
+
});
|
|
18540
|
+
if (!process || process.accountId !== row.account_id || process.state !== "active" || process.reconcileClaimId !== row.claim_id) {
|
|
18541
|
+
continue;
|
|
18542
|
+
}
|
|
18543
|
+
claims.push({
|
|
18544
|
+
process,
|
|
18545
|
+
claimId: row.claim_id,
|
|
18546
|
+
ownerState: row.owner_state,
|
|
18547
|
+
ownerAttemptOutcome: row.owner_attempt_outcome
|
|
18548
|
+
});
|
|
18549
|
+
}
|
|
18550
|
+
return claims;
|
|
18551
|
+
}
|
|
18552
|
+
async function recordRetainedProcessReconciliationProof(db, input) {
|
|
18553
|
+
if (input.proof.outcome === "exited" && (!Number.isSafeInteger(input.proof.exitCode) || input.proof.exitCode === null)) {
|
|
18554
|
+
throw new SandboxWorkspaceMutationFencedError(
|
|
18555
|
+
"process_fenced",
|
|
18556
|
+
"Retained process reconciliation exit proof requires a safe integer exit code"
|
|
18557
|
+
);
|
|
18558
|
+
}
|
|
18559
|
+
return await withRlsContext(
|
|
18560
|
+
db,
|
|
18561
|
+
{ accountId: input.accountId, workspaceId: input.workspaceId },
|
|
18562
|
+
async (scopedDb) => await scopedDb.transaction(async (txRaw) => {
|
|
18563
|
+
const tx = txRaw;
|
|
18564
|
+
const [process] = await tx.select().from(sandboxRetainedProcesses).where(
|
|
18565
|
+
and6(
|
|
18566
|
+
eq6(sandboxRetainedProcesses.accountId, input.accountId),
|
|
18567
|
+
eq6(sandboxRetainedProcesses.workspaceId, input.workspaceId),
|
|
18568
|
+
eq6(sandboxRetainedProcesses.sessionId, input.sessionId),
|
|
18569
|
+
eq6(sandboxRetainedProcesses.id, input.processId)
|
|
18570
|
+
)
|
|
18571
|
+
).for("update").limit(1);
|
|
18572
|
+
if (!process || process.state !== "active" || !retainedProcessMatchesSettlementIdentity(process, input.expected)) {
|
|
18573
|
+
throw new SandboxWorkspaceMutationFencedError(
|
|
18574
|
+
"process_fenced",
|
|
18575
|
+
"Retained process proof did not match an active copied durable identity"
|
|
18576
|
+
);
|
|
18577
|
+
}
|
|
18578
|
+
if (process.reconcileClaimId !== input.claimId) {
|
|
18579
|
+
throw new SandboxWorkspaceMutationFencedError(
|
|
18580
|
+
"process_fenced",
|
|
18581
|
+
"Retained process proof claim was lost or superseded"
|
|
18582
|
+
);
|
|
18583
|
+
}
|
|
18584
|
+
const existing = mapRetainedProcess(process);
|
|
18585
|
+
const existingProof = retainedProcessReconciliationProof(existing);
|
|
18586
|
+
if (existingProof) {
|
|
18587
|
+
if (existingProof.outcome !== input.proof.outcome || existingProof.exitCode !== input.proof.exitCode || existingProof.reason !== input.proof.reason) {
|
|
18588
|
+
throw new SandboxWorkspaceMutationFencedError(
|
|
18589
|
+
"process_fenced",
|
|
18590
|
+
"Retained process already carries different provider proof"
|
|
18591
|
+
);
|
|
18592
|
+
}
|
|
18593
|
+
return existing;
|
|
18594
|
+
}
|
|
18595
|
+
const [updated] = await tx.update(sandboxRetainedProcesses).set({
|
|
18596
|
+
reconcileProofOutcome: input.proof.outcome,
|
|
18597
|
+
reconcileProofExitCode: input.proof.exitCode,
|
|
18598
|
+
reconcileProofReason: input.proof.reason,
|
|
18599
|
+
reconcileProofObservedAt: /* @__PURE__ */ new Date(),
|
|
18600
|
+
lastReconcileOutcome: `proof_${input.proof.outcome}`
|
|
18601
|
+
}).where(
|
|
18602
|
+
and6(
|
|
18603
|
+
eq6(sandboxRetainedProcesses.id, process.id),
|
|
18604
|
+
eq6(sandboxRetainedProcesses.state, "active"),
|
|
18605
|
+
eq6(sandboxRetainedProcesses.reconcileClaimId, input.claimId)
|
|
18606
|
+
)
|
|
18607
|
+
).returning();
|
|
18608
|
+
if (!updated) {
|
|
18609
|
+
throw new SandboxWorkspaceMutationFencedError(
|
|
18610
|
+
"process_fenced",
|
|
18611
|
+
"Retained process changed while checkpointing provider proof"
|
|
18612
|
+
);
|
|
18613
|
+
}
|
|
18614
|
+
return mapRetainedProcess(updated);
|
|
18615
|
+
})
|
|
18616
|
+
);
|
|
18617
|
+
}
|
|
18618
|
+
async function deferRetainedProcessReconciliation(db, input) {
|
|
18619
|
+
const outcome = normalizeRetainedProcessReconciliationOutcome(input.outcome);
|
|
18620
|
+
if (!Number.isSafeInteger(input.retryAfterMs) || input.retryAfterMs < 0 || input.retryAfterMs > 864e5) {
|
|
18621
|
+
throw new SandboxWorkspaceMutationFencedError(
|
|
18622
|
+
"process_fenced",
|
|
18623
|
+
"Retained process reconciliation retry delay is invalid"
|
|
18624
|
+
);
|
|
18625
|
+
}
|
|
18626
|
+
return await withRlsContext(
|
|
18627
|
+
db,
|
|
18628
|
+
{ accountId: input.accountId, workspaceId: input.workspaceId },
|
|
18629
|
+
async (scopedDb) => await scopedDb.transaction(async (txRaw) => {
|
|
18630
|
+
const tx = txRaw;
|
|
18631
|
+
const [process] = await tx.select().from(sandboxRetainedProcesses).where(
|
|
18632
|
+
and6(
|
|
18633
|
+
eq6(sandboxRetainedProcesses.accountId, input.accountId),
|
|
18634
|
+
eq6(sandboxRetainedProcesses.workspaceId, input.workspaceId),
|
|
18635
|
+
eq6(sandboxRetainedProcesses.sessionId, input.sessionId),
|
|
18636
|
+
eq6(sandboxRetainedProcesses.id, input.processId)
|
|
18637
|
+
)
|
|
18638
|
+
).for("update").limit(1);
|
|
18639
|
+
if (!process || !retainedProcessMatchesSettlementIdentity(process, input.expected)) {
|
|
18640
|
+
throw new SandboxWorkspaceMutationFencedError(
|
|
18641
|
+
"process_fenced",
|
|
18642
|
+
"Retained process reconciliation did not match the copied durable identity"
|
|
18643
|
+
);
|
|
18644
|
+
}
|
|
18645
|
+
if (process.state !== "active") return false;
|
|
18646
|
+
if (process.reconcileClaimId !== input.claimId) {
|
|
18647
|
+
throw new SandboxWorkspaceMutationFencedError(
|
|
18648
|
+
"process_fenced",
|
|
18649
|
+
"Retained process reconciliation claim was lost or superseded"
|
|
18650
|
+
);
|
|
18651
|
+
}
|
|
18652
|
+
const [updated] = await tx.update(sandboxRetainedProcesses).set({
|
|
18653
|
+
reconcileAfter: new Date(Date.now() + input.retryAfterMs),
|
|
18654
|
+
reconcileClaimId: null,
|
|
18655
|
+
reconcileClaimedAt: null,
|
|
18656
|
+
lastReconcileOutcome: outcome
|
|
18657
|
+
}).where(
|
|
18658
|
+
and6(
|
|
18659
|
+
eq6(sandboxRetainedProcesses.id, process.id),
|
|
18660
|
+
eq6(sandboxRetainedProcesses.state, "active"),
|
|
18661
|
+
eq6(sandboxRetainedProcesses.reconcileClaimId, input.claimId)
|
|
18662
|
+
)
|
|
18663
|
+
).returning({ id: sandboxRetainedProcesses.id });
|
|
18664
|
+
return Boolean(updated);
|
|
18665
|
+
})
|
|
18666
|
+
);
|
|
18667
|
+
}
|
|
18668
|
+
async function countActiveRetainedProcessesByOwnerState(db) {
|
|
18669
|
+
const rows = await rawRows(
|
|
18670
|
+
db,
|
|
18671
|
+
sql4`
|
|
18672
|
+
select owner_state, active_count, terminal_owner_count
|
|
18673
|
+
from opengeni_private.count_active_retained_processes_by_owner_state()
|
|
18674
|
+
`
|
|
18675
|
+
);
|
|
18676
|
+
return rows.map((row) => ({
|
|
18677
|
+
ownerState: row.owner_state,
|
|
18678
|
+
activeCount: Number(row.active_count),
|
|
18679
|
+
terminalOwnerCount: Number(row.terminal_owner_count)
|
|
18680
|
+
}));
|
|
18681
|
+
}
|
|
18682
|
+
async function countExpiredDrainingSandboxLeases(db) {
|
|
18683
|
+
const rows = await rawRows(
|
|
18684
|
+
db,
|
|
18685
|
+
sql4`
|
|
18686
|
+
select backend, age_bucket, count
|
|
18687
|
+
from opengeni_private.count_expired_draining_sandbox_leases()
|
|
18688
|
+
`
|
|
18689
|
+
);
|
|
18690
|
+
return rows.map((row) => ({
|
|
18691
|
+
backend: row.backend,
|
|
18692
|
+
ageBucket: row.age_bucket,
|
|
18693
|
+
count: Number(row.count)
|
|
18694
|
+
}));
|
|
18695
|
+
}
|
|
17920
18696
|
async function settleRetainedProcess(db, input) {
|
|
17921
18697
|
const reason = normalizeRetainedProcessSettlementReason(input.reason);
|
|
17922
18698
|
const exitCode = input.outcome === "exited" ? input.exitCode ?? null : null;
|
|
@@ -17946,6 +18722,12 @@ async function settleRetainedProcess(db, input) {
|
|
|
17946
18722
|
"Retained process settlement did not match a durable process"
|
|
17947
18723
|
);
|
|
17948
18724
|
}
|
|
18725
|
+
if (!retainedProcessMatchesSettlementIdentity(process, input.expected)) {
|
|
18726
|
+
throw new SandboxWorkspaceMutationFencedError(
|
|
18727
|
+
"process_fenced",
|
|
18728
|
+
"Retained process settlement did not match the copied durable identity"
|
|
18729
|
+
);
|
|
18730
|
+
}
|
|
17949
18731
|
if (process.state !== "active") {
|
|
17950
18732
|
if (process.state !== input.outcome || process.exitCode !== exitCode || process.settlementReason !== reason) {
|
|
17951
18733
|
throw new SandboxWorkspaceMutationFencedError(
|
|
@@ -17961,6 +18743,19 @@ async function settleRetainedProcess(db, input) {
|
|
|
17961
18743
|
);
|
|
17962
18744
|
return { settled: false, process: mapRetainedProcess(process) };
|
|
17963
18745
|
}
|
|
18746
|
+
if (input.reconciliationClaimId !== void 0 && process.reconcileClaimId !== input.reconciliationClaimId) {
|
|
18747
|
+
throw new SandboxWorkspaceMutationFencedError(
|
|
18748
|
+
"process_fenced",
|
|
18749
|
+
"Retained process settlement reconciliation claim was lost or superseded"
|
|
18750
|
+
);
|
|
18751
|
+
}
|
|
18752
|
+
const durableProof = retainedProcessReconciliationProof(mapRetainedProcess(process));
|
|
18753
|
+
if (durableProof && (durableProof.outcome !== input.outcome || durableProof.exitCode !== exitCode || durableProof.reason !== reason)) {
|
|
18754
|
+
throw new SandboxWorkspaceMutationFencedError(
|
|
18755
|
+
"process_fenced",
|
|
18756
|
+
"Retained process settlement conflicts with checkpointed provider proof"
|
|
18757
|
+
);
|
|
18758
|
+
}
|
|
17964
18759
|
const admissions = await tx.execute(sql4`
|
|
17965
18760
|
select * from sandbox_workspace_mutation_admissions
|
|
17966
18761
|
where id = ${process.parentAdmissionId}
|
|
@@ -17968,6 +18763,13 @@ async function settleRetainedProcess(db, input) {
|
|
|
17968
18763
|
and workspace_id = ${input.workspaceId}
|
|
17969
18764
|
and session_id = ${input.sessionId}
|
|
17970
18765
|
and lease_id = ${process.leaseId}
|
|
18766
|
+
and sandbox_group_id = ${process.sandboxGroupId}
|
|
18767
|
+
and lease_epoch = ${process.leaseEpoch}
|
|
18768
|
+
and provider_backend = ${process.providerBackend}
|
|
18769
|
+
and provider_instance_id = ${process.providerInstanceId}
|
|
18770
|
+
and route_kind = ${process.routeKind}
|
|
18771
|
+
and route_target_id is not distinct from ${process.routeTargetId}
|
|
18772
|
+
and route_epoch = ${process.routeEpoch}
|
|
17971
18773
|
and provider_outcome = 'retained'
|
|
17972
18774
|
and settled_at is null
|
|
17973
18775
|
for update
|
|
@@ -17978,6 +18780,23 @@ async function settleRetainedProcess(db, input) {
|
|
|
17978
18780
|
"Retained process parent admission is not open"
|
|
17979
18781
|
);
|
|
17980
18782
|
}
|
|
18783
|
+
const leases = await tx.execute(sql4`
|
|
18784
|
+
select * from sandbox_leases
|
|
18785
|
+
where id = ${process.leaseId}
|
|
18786
|
+
and account_id = ${input.accountId}
|
|
18787
|
+
and workspace_id = ${input.workspaceId}
|
|
18788
|
+
and sandbox_group_id = ${process.sandboxGroupId}
|
|
18789
|
+
and lease_epoch = ${process.leaseEpoch}
|
|
18790
|
+
and backend = ${process.providerBackend}
|
|
18791
|
+
and instance_id = ${process.providerInstanceId}
|
|
18792
|
+
for update
|
|
18793
|
+
`);
|
|
18794
|
+
if (!leases[0]) {
|
|
18795
|
+
throw new SandboxWorkspaceMutationFencedError(
|
|
18796
|
+
"lease_fenced",
|
|
18797
|
+
"Retained process settlement cannot mutate a successor lease identity"
|
|
18798
|
+
);
|
|
18799
|
+
}
|
|
17981
18800
|
await tx.update(sandboxPtySessions).set({ status: "closed", closedAt: /* @__PURE__ */ new Date() }).where(
|
|
17982
18801
|
and6(
|
|
17983
18802
|
eq6(sandboxPtySessions.retainedProcessId, process.id),
|
|
@@ -17988,7 +18807,10 @@ async function settleRetainedProcess(db, input) {
|
|
|
17988
18807
|
state: input.outcome,
|
|
17989
18808
|
exitCode,
|
|
17990
18809
|
settlementReason: reason,
|
|
17991
|
-
settledAt: /* @__PURE__ */ new Date()
|
|
18810
|
+
settledAt: /* @__PURE__ */ new Date(),
|
|
18811
|
+
reconcileClaimId: null,
|
|
18812
|
+
reconcileClaimedAt: null,
|
|
18813
|
+
lastReconcileOutcome: input.reconciliationClaimId === void 0 ? `owner_settled_${input.outcome}` : `reconciled_${input.outcome}`
|
|
17992
18814
|
}).where(
|
|
17993
18815
|
and6(
|
|
17994
18816
|
eq6(sandboxRetainedProcesses.id, process.id),
|
|
@@ -18011,6 +18833,8 @@ async function settleRetainedProcess(db, input) {
|
|
|
18011
18833
|
await tx.execute(sql4`
|
|
18012
18834
|
delete from sandbox_lease_holders
|
|
18013
18835
|
where lease_id = ${process.leaseId}
|
|
18836
|
+
and account_id = ${input.accountId}
|
|
18837
|
+
and workspace_id = ${input.workspaceId}
|
|
18014
18838
|
and kind = 'process' and holder_id = ${process.holderId}
|
|
18015
18839
|
`);
|
|
18016
18840
|
const [counts] = await tx.execute(sql4`
|
|
@@ -18031,6 +18855,10 @@ async function settleRetainedProcess(db, input) {
|
|
|
18031
18855
|
else expires_at end,` : sql4``}
|
|
18032
18856
|
updated_at = now()
|
|
18033
18857
|
where id = ${process.leaseId}
|
|
18858
|
+
and sandbox_group_id = ${process.sandboxGroupId}
|
|
18859
|
+
and lease_epoch = ${process.leaseEpoch}
|
|
18860
|
+
and backend = ${process.providerBackend}
|
|
18861
|
+
and instance_id = ${process.providerInstanceId}
|
|
18034
18862
|
`);
|
|
18035
18863
|
return { settled: true, process: mapRetainedProcess(updated) };
|
|
18036
18864
|
})
|
|
@@ -20840,13 +21668,14 @@ async function initializeSessionStartAtomically(db, input) {
|
|
|
20840
21668
|
}).returning();
|
|
20841
21669
|
if (!goal) throw new Error("Failed to create initial session goal");
|
|
20842
21670
|
}
|
|
20843
|
-
|
|
21671
|
+
const existingUserEvents = await tx.select().from(sessionEvents).where(
|
|
20844
21672
|
and6(
|
|
20845
21673
|
eq6(sessionEvents.workspaceId, input.workspaceId),
|
|
20846
21674
|
eq6(sessionEvents.sessionId, session.id),
|
|
20847
21675
|
eq6(sessionEvents.type, "user.message")
|
|
20848
21676
|
)
|
|
20849
21677
|
).orderBy(asc3(sessionEvents.sequence)).limit(1);
|
|
21678
|
+
let userEvent = existingUserEvents[0];
|
|
20850
21679
|
let sequence = session.lastSequence;
|
|
20851
21680
|
const insertedEvents = [];
|
|
20852
21681
|
const runnable = effectiveControl.state === "active";
|
|
@@ -21013,7 +21842,7 @@ async function initializeSessionStartAtomically(db, input) {
|
|
|
21013
21842
|
tx,
|
|
21014
21843
|
input.consumeNewSessionDraft.subjectId
|
|
21015
21844
|
);
|
|
21016
|
-
await
|
|
21845
|
+
await seedNewSessionDraftInTransaction(tx, {
|
|
21017
21846
|
workspaceId: input.workspaceId,
|
|
21018
21847
|
subjectId: input.consumeNewSessionDraft.subjectId,
|
|
21019
21848
|
expectedRevision: input.consumeNewSessionDraft.expectedRevision
|
|
@@ -22324,7 +23153,7 @@ async function peekSessionWork(db, workspaceId, sessionId) {
|
|
|
22324
23153
|
workspaceId,
|
|
22325
23154
|
sessionId
|
|
22326
23155
|
);
|
|
22327
|
-
if (latestInterruption && latestInterruption.quiescedAt === null &&
|
|
23156
|
+
if (latestInterruption && latestInterruption.quiescedAt === null && latestInterruption.interruptionState === "settled") {
|
|
22328
23157
|
return {
|
|
22329
23158
|
kind: "cancellation-wait",
|
|
22330
23159
|
attemptId: latestInterruption.attemptId
|
|
@@ -24992,17 +25821,28 @@ async function appendSessionEventsAndUpdateSession(db, workspaceId, sessionId, i
|
|
|
24992
25821
|
})
|
|
24993
25822
|
);
|
|
24994
25823
|
}
|
|
24995
|
-
async function appendSessionEventsWithLockedSessionUpdate(db, workspaceId, sessionId, build) {
|
|
25824
|
+
async function appendSessionEventsWithLockedSessionUpdate(db, workspaceId, sessionId, build, options = {}) {
|
|
24996
25825
|
return await withWorkspaceRls(
|
|
24997
25826
|
db,
|
|
24998
25827
|
workspaceId,
|
|
24999
25828
|
async (scopedDb) => await scopedDb.transaction(async (tx) => {
|
|
25000
|
-
const
|
|
25829
|
+
const firstLocks = await lockSessionEventWriteRows(tx, {
|
|
25001
25830
|
workspaceId,
|
|
25002
25831
|
controlLock: "share",
|
|
25003
25832
|
sessionIds: [sessionId]
|
|
25004
25833
|
});
|
|
25005
|
-
const
|
|
25834
|
+
const firstSessionRow = firstLocks.sessions.find((row) => row.id === sessionId);
|
|
25835
|
+
if (!firstSessionRow) {
|
|
25836
|
+
throw new Error(`Session not found: ${sessionId}`);
|
|
25837
|
+
}
|
|
25838
|
+
const lockSessionIds = options.lockParentSession ? [sessionId, firstSessionRow.parentSessionId].filter((id) => Boolean(id)) : [sessionId];
|
|
25839
|
+
const locks = lockSessionIds.length === 1 ? firstLocks : await lockSessionEventWriteRows(tx, {
|
|
25840
|
+
workspaceId,
|
|
25841
|
+
controlLock: "already_locked",
|
|
25842
|
+
workspaceLock: "already_locked",
|
|
25843
|
+
sessionIds: lockSessionIds
|
|
25844
|
+
});
|
|
25845
|
+
const sessionRow = locks.sessions.find((row) => row.id === sessionId);
|
|
25006
25846
|
if (!sessionRow) {
|
|
25007
25847
|
throw new Error(`Session not found: ${sessionId}`);
|
|
25008
25848
|
}
|
|
@@ -25034,6 +25874,16 @@ async function appendSessionEventsWithLockedSessionUpdate(db, workspaceId, sessi
|
|
|
25034
25874
|
)
|
|
25035
25875
|
).orderBy(asc3(sessionTurns.position), asc3(sessionTurns.createdAt));
|
|
25036
25876
|
return rows.map(mapSessionTurn);
|
|
25877
|
+
},
|
|
25878
|
+
getLockedSession: async (lockedSessionId) => {
|
|
25879
|
+
const row = locks.sessions.find((candidate) => candidate.id === lockedSessionId);
|
|
25880
|
+
return row ? await mapSessionWithControl(
|
|
25881
|
+
tx,
|
|
25882
|
+
row,
|
|
25883
|
+
[],
|
|
25884
|
+
void 0,
|
|
25885
|
+
locks.control ?? void 0
|
|
25886
|
+
) : null;
|
|
25037
25887
|
}
|
|
25038
25888
|
});
|
|
25039
25889
|
if (built.events.length === 0) {
|
|
@@ -25067,18 +25917,32 @@ async function appendSessionEventsWithLockedSessionUpdate(db, workspaceId, sessi
|
|
|
25067
25917
|
const inserted = await tx.insert(sessionEvents).values(values).returning();
|
|
25068
25918
|
const update = built.update ?? {};
|
|
25069
25919
|
const advancesActivity = sessionMutationAdvancesActivity(update) || sessionEventTypesAdvanceActivity(values);
|
|
25070
|
-
await tx.update(sessions).set({
|
|
25920
|
+
const updated = await tx.update(sessions).set({
|
|
25071
25921
|
lastSequence: sequence,
|
|
25072
25922
|
...update.resources !== void 0 ? { resources: update.resources } : {},
|
|
25073
25923
|
...update.tools !== void 0 ? { tools: update.tools } : {},
|
|
25924
|
+
...update.toolPolicy !== void 0 ? { toolPolicy: update.toolPolicy } : {},
|
|
25925
|
+
...update.toolPolicyVersion !== void 0 ? { toolPolicyVersion: update.toolPolicyVersion } : {},
|
|
25074
25926
|
...update.model !== void 0 ? { model: update.model } : {},
|
|
25075
25927
|
...update.metadata !== void 0 ? { metadata: update.metadata } : {},
|
|
25076
25928
|
...update.status !== void 0 ? { status: update.status } : {},
|
|
25077
25929
|
...update.activeTurnId !== void 0 ? { activeTurnId: update.activeTurnId } : {},
|
|
25078
25930
|
...advancesActivity ? { updatedAt: now } : {}
|
|
25079
25931
|
}).where(
|
|
25080
|
-
and6(
|
|
25081
|
-
|
|
25932
|
+
and6(
|
|
25933
|
+
eq6(sessions.workspaceId, workspaceId),
|
|
25934
|
+
eq6(sessions.id, sessionId),
|
|
25935
|
+
...update.expectedToolPolicyVersion !== void 0 ? [eq6(sessions.toolPolicyVersion, update.expectedToolPolicyVersion)] : []
|
|
25936
|
+
)
|
|
25937
|
+
).returning({ id: sessions.id });
|
|
25938
|
+
if (updated.length === 0) {
|
|
25939
|
+
const [current] = await tx.select({ toolPolicyVersion: sessions.toolPolicyVersion }).from(sessions).where(
|
|
25940
|
+
and6(eq6(sessions.workspaceId, workspaceId), eq6(sessions.id, sessionId))
|
|
25941
|
+
).limit(1);
|
|
25942
|
+
throw new SessionToolPolicyVersionConflictError(
|
|
25943
|
+
Number(current?.toolPolicyVersion ?? update.expectedToolPolicyVersion ?? 1)
|
|
25944
|
+
);
|
|
25945
|
+
}
|
|
25082
25946
|
return inserted.map(mapEvent2);
|
|
25083
25947
|
})
|
|
25084
25948
|
);
|
|
@@ -25119,6 +25983,7 @@ function mapSession(row, effectiveControl, mcpServers = [], pin = mapSessionPin(
|
|
|
25119
25983
|
mode: "legacy",
|
|
25120
25984
|
inheritedFromSessionId: null
|
|
25121
25985
|
},
|
|
25986
|
+
toolPolicyVersion: Number(row.toolPolicyVersion ?? 1),
|
|
25122
25987
|
metadata: row.metadata,
|
|
25123
25988
|
createdBy: initiatorFromStorage(
|
|
25124
25989
|
row.createdByKind,
|
|
@@ -25598,10 +26463,17 @@ function mapGitHubInstallation(row) {
|
|
|
25598
26463
|
accountId: row.accountId,
|
|
25599
26464
|
workspaceId: row.workspaceId,
|
|
25600
26465
|
installationId: row.installationId,
|
|
26466
|
+
githubAccountId: row.githubAccountId,
|
|
25601
26467
|
accountLogin: row.accountLogin,
|
|
25602
26468
|
accountType: row.accountType,
|
|
25603
26469
|
repositoryScope: row.repositoryScope,
|
|
25604
26470
|
linkedBySubjectId: row.linkedBySubjectId,
|
|
26471
|
+
githubActorId: row.githubActorId,
|
|
26472
|
+
githubActorLogin: row.githubActorLogin,
|
|
26473
|
+
authorityKind: row.authorityKind,
|
|
26474
|
+
authorityCheckedAt: row.authorityCheckedAt?.toISOString() ?? null,
|
|
26475
|
+
authorityExpiresAt: row.authorityExpiresAt?.toISOString() ?? null,
|
|
26476
|
+
authorityNonce: row.authorityNonce,
|
|
25605
26477
|
createdAt: row.createdAt.toISOString(),
|
|
25606
26478
|
updatedAt: row.updatedAt.toISOString()
|
|
25607
26479
|
};
|
|
@@ -25758,6 +26630,8 @@ export {
|
|
|
25758
26630
|
CODEX_RESET_REDEMPTION_OUTCOMES,
|
|
25759
26631
|
CODEX_ROTATION_STRATEGIES,
|
|
25760
26632
|
ConnectionRefreshHttpError,
|
|
26633
|
+
FORCE_RLS_TABLES,
|
|
26634
|
+
GitHubInstallationAuthorityCommitError,
|
|
25761
26635
|
HostExportPayloadError,
|
|
25762
26636
|
HostMcpCredentialBindingError,
|
|
25763
26637
|
HostMcpCredentialScopeError,
|
|
@@ -25779,12 +26653,20 @@ export {
|
|
|
25779
26653
|
MEMORY_SEARCH_TOOL_DESCRIPTION,
|
|
25780
26654
|
MEMORY_TEXT_MAX_CHARS,
|
|
25781
26655
|
MEMORY_VISIBLE_RECORD_CAP,
|
|
26656
|
+
NON_RLS_RUNTIME_TABLES,
|
|
25782
26657
|
NewSessionDraftAccessError,
|
|
25783
26658
|
NewSessionDraftConflictError,
|
|
26659
|
+
PROTECTED_NO_DIRECT_DML_TABLES,
|
|
25784
26660
|
QueueCommandConflictError,
|
|
26661
|
+
RUNTIME_DML_TABLES,
|
|
26662
|
+
RUNTIME_FULL_DML_TABLES,
|
|
26663
|
+
RUNTIME_READ_INSERT_TABLES,
|
|
26664
|
+
RUNTIME_READ_ONLY_TABLES,
|
|
26665
|
+
RUNTIME_TABLE_PRIVILEGES,
|
|
25785
26666
|
RigActiveVersionChangedError,
|
|
25786
26667
|
RigChangeAlreadyVerifyingError,
|
|
25787
26668
|
RigChangeTransitionError,
|
|
26669
|
+
RuntimeDatabasePostureError,
|
|
25788
26670
|
SESSION_ANCESTRY_LIMIT,
|
|
25789
26671
|
SESSION_DISCOVERY_CONTROL_TARGET_LIMIT,
|
|
25790
26672
|
SESSION_DISCOVERY_CONTROL_TITLE_MAX_CHARS,
|
|
@@ -25796,6 +26678,7 @@ export {
|
|
|
25796
26678
|
SandboxImageConflictError,
|
|
25797
26679
|
SandboxLeaseRecoveryBlockedError,
|
|
25798
26680
|
SandboxLeaseSupersededError,
|
|
26681
|
+
SandboxRetainedProcessPromotionFencedError,
|
|
25799
26682
|
SandboxRigConflictError,
|
|
25800
26683
|
SandboxWorkspaceMutationFencedError,
|
|
25801
26684
|
SanitizedDatabasePersistenceCause,
|
|
@@ -25812,6 +26695,7 @@ export {
|
|
|
25812
26695
|
SessionPinAccessError,
|
|
25813
26696
|
SessionPinVersionConflictError,
|
|
25814
26697
|
SessionSpawnDeniedDbError,
|
|
26698
|
+
SessionToolPolicyVersionConflictError,
|
|
25815
26699
|
WORKSPACE_MEMORY_BLOCK_EMPTY,
|
|
25816
26700
|
WORKSPACE_MEMORY_BLOCK_HEADER_POPULATED,
|
|
25817
26701
|
WORKSPACE_MEMORY_BLOCK_TOKEN_BUDGET,
|
|
@@ -25847,9 +26731,11 @@ export {
|
|
|
25847
26731
|
areGitHubRepositoriesAllowedForWorkspace,
|
|
25848
26732
|
armCodexCapacityWait,
|
|
25849
26733
|
assertAgentCommandAuthorityInTransaction,
|
|
26734
|
+
assertRuntimeDatabasePosture,
|
|
25850
26735
|
autoResumeSessionBranchInTransaction,
|
|
25851
26736
|
beginRigChangeVerificationAttempt,
|
|
25852
26737
|
beginSandboxRematerialization,
|
|
26738
|
+
bindAuthorizedGitHubInstallationRepositories,
|
|
25853
26739
|
bindGitHubInstallationRepositories,
|
|
25854
26740
|
bootstrapWorkspace,
|
|
25855
26741
|
buildChildCompletionDigest,
|
|
@@ -25864,6 +26750,7 @@ export {
|
|
|
25864
26750
|
claimPendingSessionSystemUpdateOutbox,
|
|
25865
26751
|
claimPendingSessionWorkflowWakes,
|
|
25866
26752
|
claimSessionWorkForAttempt,
|
|
26753
|
+
claimTerminalRetainedProcesses,
|
|
25867
26754
|
clearDurablePendingSessionToolCalls,
|
|
25868
26755
|
clearEnrollmentWentOffline,
|
|
25869
26756
|
clearPendingSessionToolspaceCall,
|
|
@@ -25885,11 +26772,13 @@ export {
|
|
|
25885
26772
|
consumeNewSessionDraftInTransaction,
|
|
25886
26773
|
correctWorkspaceMemory,
|
|
25887
26774
|
countActiveApiKeysForWorkspace,
|
|
26775
|
+
countActiveRetainedProcessesByOwnerState,
|
|
25888
26776
|
countActiveSessionHistoryItems,
|
|
25889
26777
|
countActiveSessionsForWorkspace,
|
|
25890
26778
|
countActiveSessionsUsingEnvironment,
|
|
25891
26779
|
countActiveSessionsUsingVariableSet,
|
|
25892
26780
|
countConsecutiveReactiveRotations,
|
|
26781
|
+
countExpiredDrainingSandboxLeases,
|
|
25893
26782
|
countQueuedTurns,
|
|
25894
26783
|
countRigs,
|
|
25895
26784
|
countSandboxLeasesByLiveness,
|
|
@@ -25933,6 +26822,7 @@ export {
|
|
|
25933
26822
|
decryptEnvironmentValue,
|
|
25934
26823
|
decryptEnvironmentValue as decryptVariableSetValue,
|
|
25935
26824
|
decryptedCapabilityHeaders,
|
|
26825
|
+
deferRetainedProcessReconciliation,
|
|
25936
26826
|
deleteGitHubInstallationBinding,
|
|
25937
26827
|
deleteRecording,
|
|
25938
26828
|
deleteRig,
|
|
@@ -25965,6 +26855,7 @@ export {
|
|
|
25965
26855
|
ensureManagedAccessForUser,
|
|
25966
26856
|
estimateMemoryTokens,
|
|
25967
26857
|
evaluateGoalContinuation,
|
|
26858
|
+
evaluateRuntimeDatabasePosture,
|
|
25968
26859
|
evaluateSessionControl,
|
|
25969
26860
|
evaluateSessionControls,
|
|
25970
26861
|
evaluateSessionDiscoveryControls,
|
|
@@ -26057,6 +26948,7 @@ export {
|
|
|
26057
26948
|
getWorkspaceModelPolicy,
|
|
26058
26949
|
getWorkspacePack,
|
|
26059
26950
|
grantWorkspaceAccess,
|
|
26951
|
+
hasAuditableGitHubInstallationAuthority,
|
|
26060
26952
|
hasCreditLedgerEntry,
|
|
26061
26953
|
hashMemoryText,
|
|
26062
26954
|
heartbeatCodexCredentialLease,
|
|
@@ -26069,6 +26961,7 @@ export {
|
|
|
26069
26961
|
insertPtySession,
|
|
26070
26962
|
insertRecording,
|
|
26071
26963
|
insertWorkspaceCapture,
|
|
26964
|
+
inspectRuntimeDatabasePosture,
|
|
26072
26965
|
installOrReadTurnExecutionPolicyForAttempt,
|
|
26073
26966
|
interruptedToolCallResult,
|
|
26074
26967
|
isCodexBilledModel2 as isCodexBilledModel,
|
|
@@ -26162,6 +27055,7 @@ export {
|
|
|
26162
27055
|
mutateSessionControlInTransaction,
|
|
26163
27056
|
mutateWorkspaceControlInTransaction,
|
|
26164
27057
|
nestedPostgresSqlState,
|
|
27058
|
+
newSessionDraftToolsProvided,
|
|
26165
27059
|
nextSessionHistoryPosition,
|
|
26166
27060
|
normalizeBearerScheme,
|
|
26167
27061
|
normalizeMemoryText,
|
|
@@ -26174,6 +27068,7 @@ export {
|
|
|
26174
27068
|
projectSessionForRelatedAccess,
|
|
26175
27069
|
provisionRoles,
|
|
26176
27070
|
pruneHostExportOutbox,
|
|
27071
|
+
publicNewSessionDraftOptions,
|
|
26177
27072
|
quarantineCodexCredentialForLease,
|
|
26178
27073
|
reArmDrainingLease,
|
|
26179
27074
|
readActiveSandbox,
|
|
@@ -26186,6 +27081,7 @@ export {
|
|
|
26186
27081
|
reapStaleLeaseHolders,
|
|
26187
27082
|
reapStaleLeaseHoldersGlobal,
|
|
26188
27083
|
reconcileCodexCapacityWait,
|
|
27084
|
+
reconcileColdLostLeaseInstanceBlockers,
|
|
26189
27085
|
recordAuditEvent,
|
|
26190
27086
|
recordCodexAccountConnectors,
|
|
26191
27087
|
recordCodexAccountUsage,
|
|
@@ -26196,6 +27092,7 @@ export {
|
|
|
26196
27092
|
recordLeaseDataPlaneUrl,
|
|
26197
27093
|
recordLeaseTerminalDataPlaneUrl,
|
|
26198
27094
|
recordPendingSessionToolCallResult,
|
|
27095
|
+
recordRetainedProcessReconciliationProof,
|
|
26199
27096
|
recordSessionActiveCodexCredential,
|
|
26200
27097
|
recordSkippedContextCompaction,
|
|
26201
27098
|
recordStreamAcknowledgment,
|
|
@@ -26230,6 +27127,8 @@ export {
|
|
|
26230
27127
|
resolveWorkspaceMemoryBlock,
|
|
26231
27128
|
resumeHostExportConsumer,
|
|
26232
27129
|
retainWorkspaceMutationProcess,
|
|
27130
|
+
retainedProcessReconciliationProof,
|
|
27131
|
+
retainedProcessSettlementIdentity,
|
|
26233
27132
|
retireHostExportConsumer,
|
|
26234
27133
|
revokeApiKey,
|
|
26235
27134
|
revokeConnection,
|
|
@@ -26241,6 +27140,7 @@ export {
|
|
|
26241
27140
|
rotateWorkspaceArchives,
|
|
26242
27141
|
runIdempotentPersistenceTransaction,
|
|
26243
27142
|
runMigrations,
|
|
27143
|
+
runtimeDatabaseReadyCheck,
|
|
26244
27144
|
safeDatabaseErrorFacts,
|
|
26245
27145
|
sanitizeEventPayload,
|
|
26246
27146
|
sanitizeEventString,
|
|
@@ -26251,6 +27151,7 @@ export {
|
|
|
26251
27151
|
saveRunState,
|
|
26252
27152
|
saveWorkspaceMemory,
|
|
26253
27153
|
searchWorkspaceMemories,
|
|
27154
|
+
seedNewSessionDraftInTransaction,
|
|
26254
27155
|
sendAgentMessageInTransaction,
|
|
26255
27156
|
serializeEffectiveSessionControl,
|
|
26256
27157
|
sessionAuthorizationScopeFilter,
|