@opengeni/db 0.27.9 → 0.28.1

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/src/index.ts CHANGED
@@ -11473,11 +11473,10 @@ export async function upsertCodexSubscriptionCredential(
11473
11473
  // it when still null) so a re-connect never clobbers a rename.
11474
11474
  accountEmail: input.accountEmail ?? null,
11475
11475
  label: sql`coalesce(${schema.codexSubscriptionCredentials.label}, ${input.label ?? null})`,
11476
- // Ownership follows the most recent connection exactly. A
11477
- // configured/delegated/API-key reconnect is intentionally
11478
- // nonhuman and clears the prior human owner, making the row
11479
- // view-only until a direct managed-cookie human reconnects it.
11480
- connectedBySubjectId: input.connectedBySubjectId ?? null,
11476
+ // Reconnect refreshes credential material, never ownership. A row
11477
+ // without an owner may be claimed by its first direct managed human;
11478
+ // after that, disconnect is the explicit ownership-reset boundary.
11479
+ connectedBySubjectId: sql`coalesce(${schema.codexSubscriptionCredentials.connectedBySubjectId}, ${input.connectedBySubjectId ?? null})`,
11481
11480
  status: "active",
11482
11481
  lastError: null,
11483
11482
  version: sql`${schema.codexSubscriptionCredentials.version} + 1`,
@@ -11503,6 +11502,333 @@ export async function upsertCodexSubscriptionCredential(
11503
11502
  );
11504
11503
  }
11505
11504
 
11505
+ export type CodexAppsSettings = {
11506
+ credentialId: string | null;
11507
+ version: number;
11508
+ designatedAt: Date | null;
11509
+ };
11510
+
11511
+ export async function getCodexAppsSettings(
11512
+ db: Database,
11513
+ workspaceId: string,
11514
+ ): Promise<CodexAppsSettings> {
11515
+ return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
11516
+ const [row] = await scopedDb
11517
+ .select({
11518
+ credentialId: schema.codexAppsSettings.credentialId,
11519
+ version: schema.codexAppsSettings.version,
11520
+ designatedAt: schema.codexAppsSettings.designatedAt,
11521
+ })
11522
+ .from(schema.codexAppsSettings)
11523
+ .where(eq(schema.codexAppsSettings.workspaceId, workspaceId))
11524
+ .limit(1);
11525
+ return row ?? { credentialId: null, version: 0, designatedAt: null };
11526
+ });
11527
+ }
11528
+
11529
+ export type CodexAppsCredentialAuthorization = {
11530
+ credentialId: string;
11531
+ ownerSubjectId: string;
11532
+ };
11533
+
11534
+ function canManageCodexApps(permissions: unknown): boolean {
11535
+ return (
11536
+ Array.isArray(permissions) &&
11537
+ (permissions.includes("connections:write") || permissions.includes("workspace:admin"))
11538
+ );
11539
+ }
11540
+
11541
+ export class CodexAppsAuthorizationRevokedError extends Error {
11542
+ constructor() {
11543
+ super("Codex Apps authorization is no longer active");
11544
+ this.name = "CodexAppsAuthorizationRevokedError";
11545
+ }
11546
+ }
11547
+
11548
+ /** Exact Apps credential and owner, independent of every inference-capacity field. */
11549
+ export async function getCodexAppsCredentialAuthorizationForRun(
11550
+ db: Database,
11551
+ workspaceId: string,
11552
+ ): Promise<CodexAppsCredentialAuthorization | null> {
11553
+ return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
11554
+ const [row] = await scopedDb
11555
+ .select({
11556
+ credentialId: schema.codexAppsSettings.credentialId,
11557
+ ownerSubjectId: schema.codexSubscriptionCredentials.connectedBySubjectId,
11558
+ })
11559
+ .from(schema.codexAppsSettings)
11560
+ .innerJoin(
11561
+ schema.codexSubscriptionCredentials,
11562
+ and(
11563
+ eq(schema.codexSubscriptionCredentials.id, schema.codexAppsSettings.credentialId),
11564
+ eq(schema.codexSubscriptionCredentials.accountId, schema.codexAppsSettings.accountId),
11565
+ eq(schema.codexSubscriptionCredentials.workspaceId, workspaceId),
11566
+ eq(schema.codexSubscriptionCredentials.status, "active"),
11567
+ ),
11568
+ )
11569
+ .where(eq(schema.codexAppsSettings.workspaceId, workspaceId))
11570
+ .limit(1);
11571
+ return row?.credentialId && row.ownerSubjectId
11572
+ ? { credentialId: row.credentialId, ownerSubjectId: row.ownerSubjectId }
11573
+ : null;
11574
+ });
11575
+ }
11576
+
11577
+ /**
11578
+ * Hold the exact designation, credential, and owner-membership authorization
11579
+ * stable through one outbound Apps request. Clear/disconnect operations share
11580
+ * the workspace advisory lock; credential or membership revocation must wait on
11581
+ * the row locks. The callback deliberately runs inside this short transaction so
11582
+ * no revocation can commit in the gap between a final check and network dispatch.
11583
+ */
11584
+ export async function withCodexAppsRequestAuthorization<T>(
11585
+ db: Database,
11586
+ input: { workspaceId: string; credentialId: string },
11587
+ use: () => Promise<T>,
11588
+ ): Promise<T> {
11589
+ return await withWorkspaceRls(db, input.workspaceId, async (scopedDb) => {
11590
+ await scopedDb.execute(
11591
+ sql`select pg_advisory_xact_lock(hashtextextended(${`codex-apps-settings:${input.workspaceId}`}, 0))`,
11592
+ );
11593
+ const [designation] = await scopedDb
11594
+ .select({ credentialId: schema.codexAppsSettings.credentialId })
11595
+ .from(schema.codexAppsSettings)
11596
+ .where(eq(schema.codexAppsSettings.workspaceId, input.workspaceId))
11597
+ .for("share")
11598
+ .limit(1);
11599
+ if (designation?.credentialId !== input.credentialId) {
11600
+ throw new CodexAppsAuthorizationRevokedError();
11601
+ }
11602
+ const [credential] = await scopedDb
11603
+ .select({
11604
+ ownerSubjectId: schema.codexSubscriptionCredentials.connectedBySubjectId,
11605
+ status: schema.codexSubscriptionCredentials.status,
11606
+ })
11607
+ .from(schema.codexSubscriptionCredentials)
11608
+ .where(
11609
+ and(
11610
+ eq(schema.codexSubscriptionCredentials.workspaceId, input.workspaceId),
11611
+ eq(schema.codexSubscriptionCredentials.id, input.credentialId),
11612
+ ),
11613
+ )
11614
+ .for("share")
11615
+ .limit(1);
11616
+ if (!credential?.ownerSubjectId || credential.status !== "active") {
11617
+ throw new CodexAppsAuthorizationRevokedError();
11618
+ }
11619
+ const [membership] = await scopedDb
11620
+ .select({ permissions: schema.workspaceMemberships.permissions })
11621
+ .from(schema.workspaceMemberships)
11622
+ .where(
11623
+ and(
11624
+ eq(schema.workspaceMemberships.workspaceId, input.workspaceId),
11625
+ eq(schema.workspaceMemberships.subjectId, credential.ownerSubjectId),
11626
+ ),
11627
+ )
11628
+ .for("share")
11629
+ .limit(1);
11630
+ if (!canManageCodexApps(membership?.permissions)) {
11631
+ throw new CodexAppsAuthorizationRevokedError();
11632
+ }
11633
+ return await use();
11634
+ });
11635
+ }
11636
+
11637
+ export type DesignateCodexAppsCredentialResult =
11638
+ | ({ kind: "updated" } & CodexAppsSettings)
11639
+ | ({ kind: "conflict" | "already_designated" } & CodexAppsSettings)
11640
+ | { kind: "not_found" }
11641
+ | { kind: "not_owner" }
11642
+ | { kind: "forbidden" }
11643
+ | { kind: "unavailable" };
11644
+
11645
+ export async function designateCodexAppsCredential(
11646
+ db: Database,
11647
+ input: {
11648
+ accountId: string;
11649
+ workspaceId: string;
11650
+ credentialId: string;
11651
+ subjectId: string;
11652
+ expectedVersion: number;
11653
+ },
11654
+ ): Promise<DesignateCodexAppsCredentialResult> {
11655
+ return await withRlsContext(
11656
+ db,
11657
+ { accountId: input.accountId, workspaceId: input.workspaceId },
11658
+ async (scopedDb) => {
11659
+ await scopedDb.execute(
11660
+ sql`select pg_advisory_xact_lock(hashtextextended(${`codex-apps-settings:${input.workspaceId}`}, 0))`,
11661
+ );
11662
+ const [currentRow] = await scopedDb
11663
+ .select()
11664
+ .from(schema.codexAppsSettings)
11665
+ .where(eq(schema.codexAppsSettings.workspaceId, input.workspaceId))
11666
+ .for("update")
11667
+ .limit(1);
11668
+ const current: CodexAppsSettings = currentRow
11669
+ ? {
11670
+ credentialId: currentRow.credentialId,
11671
+ version: currentRow.version,
11672
+ designatedAt: currentRow.designatedAt,
11673
+ }
11674
+ : { credentialId: null, version: 0, designatedAt: null };
11675
+ if (current.version !== input.expectedVersion) return { kind: "conflict", ...current };
11676
+ if (current.credentialId !== null) return { kind: "already_designated", ...current };
11677
+
11678
+ const [credential] = await scopedDb
11679
+ .select({
11680
+ connectedBySubjectId: schema.codexSubscriptionCredentials.connectedBySubjectId,
11681
+ status: schema.codexSubscriptionCredentials.status,
11682
+ })
11683
+ .from(schema.codexSubscriptionCredentials)
11684
+ .where(
11685
+ and(
11686
+ eq(schema.codexSubscriptionCredentials.accountId, input.accountId),
11687
+ eq(schema.codexSubscriptionCredentials.workspaceId, input.workspaceId),
11688
+ eq(schema.codexSubscriptionCredentials.id, input.credentialId),
11689
+ ),
11690
+ )
11691
+ .for("update")
11692
+ .limit(1);
11693
+ if (!credential) return { kind: "not_found" };
11694
+ if (credential.connectedBySubjectId !== input.subjectId) return { kind: "not_owner" };
11695
+ if (credential.status !== "active") return { kind: "unavailable" };
11696
+
11697
+ const [membership] = await scopedDb
11698
+ .select({ permissions: schema.workspaceMemberships.permissions })
11699
+ .from(schema.workspaceMemberships)
11700
+ .where(
11701
+ and(
11702
+ eq(schema.workspaceMemberships.accountId, input.accountId),
11703
+ eq(schema.workspaceMemberships.workspaceId, input.workspaceId),
11704
+ eq(schema.workspaceMemberships.subjectId, input.subjectId),
11705
+ ),
11706
+ )
11707
+ .for("update")
11708
+ .limit(1);
11709
+ if (!canManageCodexApps(membership?.permissions)) return { kind: "forbidden" };
11710
+
11711
+ const now = new Date();
11712
+ const version = current.version + 1;
11713
+ const [updated] = await scopedDb
11714
+ .insert(schema.codexAppsSettings)
11715
+ .values({
11716
+ accountId: input.accountId,
11717
+ workspaceId: input.workspaceId,
11718
+ credentialId: input.credentialId,
11719
+ version,
11720
+ designatedAt: now,
11721
+ updatedAt: now,
11722
+ })
11723
+ .onConflictDoUpdate({
11724
+ target: schema.codexAppsSettings.workspaceId,
11725
+ set: {
11726
+ credentialId: input.credentialId,
11727
+ version,
11728
+ designatedAt: now,
11729
+ updatedAt: now,
11730
+ },
11731
+ })
11732
+ .returning({
11733
+ credentialId: schema.codexAppsSettings.credentialId,
11734
+ version: schema.codexAppsSettings.version,
11735
+ designatedAt: schema.codexAppsSettings.designatedAt,
11736
+ });
11737
+ if (!updated?.credentialId || !updated.designatedAt) {
11738
+ throw new Error("Codex Apps designation was not persisted");
11739
+ }
11740
+ await scopedDb.insert(schema.auditEvents).values({
11741
+ accountId: input.accountId,
11742
+ workspaceId: input.workspaceId,
11743
+ subjectId: input.subjectId,
11744
+ action: "codex_apps.designated",
11745
+ targetType: "codex_subscription_credential",
11746
+ targetId: input.credentialId,
11747
+ metadata: { version },
11748
+ });
11749
+ return { kind: "updated", ...updated };
11750
+ },
11751
+ );
11752
+ }
11753
+
11754
+ export type ClearCodexAppsCredentialResult = {
11755
+ kind: "updated" | "unchanged" | "conflict" | "forbidden";
11756
+ } & CodexAppsSettings;
11757
+
11758
+ export async function clearCodexAppsCredential(
11759
+ db: Database,
11760
+ input: {
11761
+ accountId: string;
11762
+ workspaceId: string;
11763
+ subjectId: string;
11764
+ expectedVersion: number;
11765
+ },
11766
+ ): Promise<ClearCodexAppsCredentialResult> {
11767
+ return await withRlsContext(
11768
+ db,
11769
+ { accountId: input.accountId, workspaceId: input.workspaceId },
11770
+ async (scopedDb) => {
11771
+ await scopedDb.execute(
11772
+ sql`select pg_advisory_xact_lock(hashtextextended(${`codex-apps-settings:${input.workspaceId}`}, 0))`,
11773
+ );
11774
+ const [row] = await scopedDb
11775
+ .select()
11776
+ .from(schema.codexAppsSettings)
11777
+ .where(eq(schema.codexAppsSettings.workspaceId, input.workspaceId))
11778
+ .for("update")
11779
+ .limit(1);
11780
+ const current: CodexAppsSettings = row
11781
+ ? { credentialId: row.credentialId, version: row.version, designatedAt: row.designatedAt }
11782
+ : { credentialId: null, version: 0, designatedAt: null };
11783
+ const [membership] = await scopedDb
11784
+ .select({ permissions: schema.workspaceMemberships.permissions })
11785
+ .from(schema.workspaceMemberships)
11786
+ .where(
11787
+ and(
11788
+ eq(schema.workspaceMemberships.accountId, input.accountId),
11789
+ eq(schema.workspaceMemberships.workspaceId, input.workspaceId),
11790
+ eq(schema.workspaceMemberships.subjectId, input.subjectId),
11791
+ ),
11792
+ )
11793
+ .for("update")
11794
+ .limit(1);
11795
+ if (!canManageCodexApps(membership?.permissions)) {
11796
+ return { kind: "forbidden", ...current };
11797
+ }
11798
+ if (current.version !== input.expectedVersion) return { kind: "conflict", ...current };
11799
+ if (current.credentialId === null) return { kind: "unchanged", ...current };
11800
+
11801
+ const now = new Date();
11802
+ const version = current.version + 1;
11803
+ const [updated] = await scopedDb
11804
+ .update(schema.codexAppsSettings)
11805
+ .set({
11806
+ credentialId: null,
11807
+ version,
11808
+ designatedAt: null,
11809
+ updatedAt: now,
11810
+ })
11811
+ .where(eq(schema.codexAppsSettings.workspaceId, input.workspaceId))
11812
+ .returning({
11813
+ credentialId: schema.codexAppsSettings.credentialId,
11814
+ version: schema.codexAppsSettings.version,
11815
+ designatedAt: schema.codexAppsSettings.designatedAt,
11816
+ });
11817
+ if (!updated) throw new Error("Codex Apps designation clear was not persisted");
11818
+ await scopedDb.insert(schema.auditEvents).values({
11819
+ accountId: input.accountId,
11820
+ workspaceId: input.workspaceId,
11821
+ subjectId: input.subjectId,
11822
+ action: "codex_apps.cleared",
11823
+ targetType: "codex_subscription_credential",
11824
+ targetId: current.credentialId,
11825
+ metadata: { version },
11826
+ });
11827
+ return { kind: "updated", ...updated };
11828
+ },
11829
+ );
11830
+ }
11831
+
11506
11832
  /**
11507
11833
  * The ONLY decrypt-read accessor. Fails closed. Never call from an API route that
11508
11834
  * returns the result.
@@ -11989,11 +12315,6 @@ export type CodexAccountStatus = {
11989
12315
  // P3 rotation cooldown: when set and in the future, this account is cooling-down
11990
12316
  // (rotated-off after a usage cap) and the engine skips it. null ⇒ not cooling.
11991
12317
  exhaustedUntil: Date | null;
11992
- // P4 connector-aware rotation: the ORIGINAL-dotted connector namespaces this
11993
- // account exposes via codex_apps (github/gmail/linear/…). null ⇒ never probed
11994
- // (the ranker treats it as unknown: never credited as covering, never excluded).
11995
- connectorNamespaces: string[] | null;
11996
- connectorsCheckedAt: Date | null;
11997
12318
  };
11998
12319
 
11999
12320
  /**
@@ -12112,8 +12433,6 @@ type CodexLeaseCandidateRow = {
12112
12433
  secondary_reset_at: Date | string | null;
12113
12434
  usage_checked_at: Date | string | null;
12114
12435
  exhausted_until: Date | string | null;
12115
- connector_namespaces: string[] | null;
12116
- connectors_checked_at: Date | string | null;
12117
12436
  selection_count: number;
12118
12437
  last_selected_at: Date | string | null;
12119
12438
  active_lease_count: number;
@@ -12146,8 +12465,6 @@ function mapCodexLeaseCandidate(
12146
12465
  secondaryResetAt: codexMetadataDate(row.secondary_reset_at),
12147
12466
  usageCheckedAt: codexMetadataDate(row.usage_checked_at),
12148
12467
  exhaustedUntil: codexMetadataDate(row.exhausted_until),
12149
- connectorNamespaces: row.connector_namespaces,
12150
- connectorsCheckedAt: codexMetadataDate(row.connectors_checked_at),
12151
12468
  selectionCount: Number(row.selection_count),
12152
12469
  lastSelectedAt: codexMetadataDate(row.last_selected_at),
12153
12470
  activeLeaseCount: Number(row.active_lease_count),
@@ -12207,8 +12524,6 @@ async function listCodexLeaseCandidatesInTransaction(
12207
12524
  c.secondary_reset_at,
12208
12525
  c.usage_checked_at,
12209
12526
  c.exhausted_until,
12210
- c.connector_namespaces,
12211
- c.connectors_checked_at,
12212
12527
  c.selection_count,
12213
12528
  c.last_selected_at,
12214
12529
  count(l.id) filter (
@@ -12255,8 +12570,6 @@ export async function acquireCodexCredentialLease<
12255
12570
  holderId: string;
12256
12571
  /** Pins must not move the workspace-global cursor. */
12257
12572
  advanceActivePointer: boolean;
12258
- /** Exact frozen credential for this same durable turn, if it is resuming. */
12259
- continuationCredentialId?: string | null;
12260
12573
  /**
12261
12574
  * Optional downstream parser for private accepted-turn policy metadata.
12262
12575
  * It is pure, runs under the turn/rotation transaction, and must not query
@@ -12265,8 +12578,8 @@ export async function acquireCodexCredentialLease<
12265
12578
  resolvePolicyScope?: CodexCredentialLeasePolicyScopeResolver<TPolicyScope>;
12266
12579
  /**
12267
12580
  * Optional downstream membership policy for NEW allocations only. A live
12268
- * lease or validated frozen credential is offered to the selector against
12269
- * the complete workspace rows first and can never be filtered out here.
12581
+ * exact-turn lease is offered to the selector against the complete workspace
12582
+ * rows first and can never be filtered out here.
12270
12583
  */
12271
12584
  filterNewAllocationCandidates?: CodexCredentialLeaseCandidateFilter<
12272
12585
  TPolicyScope,
@@ -12332,23 +12645,6 @@ export async function acquireCodexCredentialLease<
12332
12645
  throw new Error(`Session turn not found for Codex lease: ${input.turnId}`);
12333
12646
  }
12334
12647
  const policyScope = input.resolvePolicyScope?.(turns[0].metadata ?? {}) ?? null;
12335
- const continuationRows = input.continuationCredentialId
12336
- ? await tx.execute(
12337
- sql<{ frozen_codex_credential_id: string | null }>`
12338
- select frozen_codex_credential_id
12339
- from agent_run_states
12340
- where account_id = ${input.accountId}
12341
- and workspace_id = ${input.workspaceId}
12342
- and turn_id = ${input.turnId}
12343
- order by state_version desc
12344
- limit 1
12345
- `,
12346
- )
12347
- : [];
12348
- const validatedContinuationCredentialId =
12349
- continuationRows[0]?.frozen_codex_credential_id === input.continuationCredentialId
12350
- ? input.continuationCredentialId
12351
- : null;
12352
12648
  const activeCredentialId = settingsRow.active_credential_id;
12353
12649
  const rotationEnabled = settingsRow.rotation_enabled;
12354
12650
  // Fail closed on a torn/manual legacy write. The user-intent bit and the
@@ -12390,7 +12686,7 @@ export async function acquireCodexCredentialLease<
12390
12686
  activeCredentialId,
12391
12687
  excludeTurnId: input.turnId,
12392
12688
  });
12393
- const sameTurnCredentialId = existingCredentialId ?? validatedContinuationCredentialId;
12689
+ const sameTurnCredentialId = existingCredentialId;
12394
12690
  const selectionContext = (
12395
12691
  accounts: CodexLeaseAccountStatus[],
12396
12692
  unavailableDiagnostics: readonly TUnavailableDiagnostic[],
@@ -12414,10 +12710,9 @@ export async function acquireCodexCredentialLease<
12414
12710
  }
12415
12711
  }
12416
12712
 
12417
- // Exact-turn continuity is resolved before any future pool membership
12713
+ // A live exact-turn lease is resolved before any future pool membership
12418
12714
  // filter. The normal selector still owns health validation: a quarantined
12419
- // live/frozen row falls through to scoped new acquisition rather than
12420
- // being reused blindly.
12715
+ // row falls through to scoped new acquisition rather than being reused.
12421
12716
  if (!selected) {
12422
12717
  const filtered = filterCodexLeaseCandidatesForPolicy(
12423
12718
  allAccounts,
@@ -12454,11 +12749,7 @@ export async function acquireCodexCredentialLease<
12454
12749
  if (!selectedAccount) {
12455
12750
  throw new Error("Codex lease selector returned a credential outside the workspace pool");
12456
12751
  }
12457
- if (
12458
- !selectedAccount.allocatorEnabled &&
12459
- selectedAccount.id !== existingCredentialId &&
12460
- selectedAccount.id !== validatedContinuationCredentialId
12461
- ) {
12752
+ if (!selectedAccount.allocatorEnabled && selectedAccount.id !== existingCredentialId) {
12462
12753
  throw new Error("Codex lease selector returned a credential disabled for new allocations");
12463
12754
  }
12464
12755
 
@@ -13873,9 +14164,6 @@ export async function listCodexAccountStatuses(
13873
14164
  secondaryResetAt: schema.codexSubscriptionCredentials.secondaryResetAt,
13874
14165
  usageCheckedAt: schema.codexSubscriptionCredentials.usageCheckedAt,
13875
14166
  exhaustedUntil: schema.codexSubscriptionCredentials.exhaustedUntil,
13876
- // P4 connector-set cache — metadata-only, rides along on this read.
13877
- connectorNamespaces: schema.codexSubscriptionCredentials.connectorNamespaces,
13878
- connectorsCheckedAt: schema.codexSubscriptionCredentials.connectorsCheckedAt,
13879
14167
  })
13880
14168
  .from(schema.codexSubscriptionCredentials)
13881
14169
  .where(eq(schema.codexSubscriptionCredentials.workspaceId, workspaceId))
@@ -13893,7 +14181,6 @@ export async function listCodexAccountStatuses(
13893
14181
  secondaryResetAt: codexMetadataDate(row.secondaryResetAt),
13894
14182
  usageCheckedAt: codexMetadataDate(row.usageCheckedAt),
13895
14183
  exhaustedUntil: codexMetadataDate(row.exhaustedUntil),
13896
- connectorsCheckedAt: codexMetadataDate(row.connectorsCheckedAt),
13897
14184
  isActive: row.id === activeId,
13898
14185
  }));
13899
14186
  });
@@ -15156,46 +15443,6 @@ export async function countConsecutiveReactiveRotations(
15156
15443
  });
15157
15444
  }
15158
15445
 
15159
- /**
15160
- * P4 connector-set cache writer: persist the set of ORIGINAL-dotted connector
15161
- * namespaces a SPECIFIC credential exposes via codex_apps (+ the freshness clock).
15162
- * Modeled byte-for-byte on recordCodexAccountUsage / setCodexCredentialExhausted:
15163
- * RLS-scoped, guarded by (id, workspace_id), and — critically — NO `version` bump and
15164
- * NO `updatedAt` touch, so it can never race the (id, version) token-refresh CAS.
15165
- *
15166
- * The CALLER must only invoke this with a NON-EMPTY set: codex_apps connects
15167
- * best-effort (a transient failure yields an empty tools/list), and overwriting a
15168
- * known non-empty set with [] would falsely "drop" coverage on a flaky turn. A
15169
- * genuinely connector-less account stays null (the ranker treats null as unknown).
15170
- * Returns true iff a row was updated (false ⇒ the credential was disconnected under us).
15171
- */
15172
- export async function recordCodexAccountConnectors(
15173
- db: Database,
15174
- workspaceId: string,
15175
- credentialId: string,
15176
- namespaces: string[],
15177
- ): Promise<boolean> {
15178
- return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
15179
- const updated = await scopedDb
15180
- .update(schema.codexSubscriptionCredentials)
15181
- .set({
15182
- connectorNamespaces: namespaces,
15183
- connectorsCheckedAt: new Date(),
15184
- // NB: no `version` bump and no `updatedAt` touch — connector set is non-credential
15185
- // metadata and must NOT race the (id, version) refresh CAS (same discipline as
15186
- // recordCodexAccountUsage / setCodexCredentialExhausted).
15187
- })
15188
- .where(
15189
- and(
15190
- eq(schema.codexSubscriptionCredentials.id, credentialId),
15191
- eq(schema.codexSubscriptionCredentials.workspaceId, workspaceId),
15192
- ),
15193
- )
15194
- .returning({ id: schema.codexSubscriptionCredentials.id });
15195
- return updated.length > 0;
15196
- });
15197
- }
15198
-
15199
15446
  /** The supported rotation strategies (P3). */
15200
15447
  export const CODEX_ROTATION_STRATEGIES = [
15201
15448
  "most_remaining",
@@ -15406,6 +15653,7 @@ export async function disconnectCodexAccount(
15406
15653
  db: Database,
15407
15654
  workspaceId: string,
15408
15655
  credentialId: string,
15656
+ actorSubjectId: string | null = null,
15409
15657
  ): Promise<{
15410
15658
  removed: boolean;
15411
15659
  newActiveCredentialId: string | null;
@@ -15417,8 +15665,20 @@ export async function disconnectCodexAccount(
15417
15665
  where workspace_id = ${workspaceId}
15418
15666
  for update
15419
15667
  `);
15668
+ await scopedDb.execute(
15669
+ sql`select pg_advisory_xact_lock(hashtextextended(${`codex-apps-settings:${workspaceId}`}, 0))`,
15670
+ );
15671
+ const [appsSettings] = await scopedDb
15672
+ .select()
15673
+ .from(schema.codexAppsSettings)
15674
+ .where(eq(schema.codexAppsSettings.workspaceId, workspaceId))
15675
+ .for("update")
15676
+ .limit(1);
15420
15677
  const [credential] = await scopedDb
15421
- .select({ id: schema.codexSubscriptionCredentials.id })
15678
+ .select({
15679
+ id: schema.codexSubscriptionCredentials.id,
15680
+ accountId: schema.codexSubscriptionCredentials.accountId,
15681
+ })
15422
15682
  .from(schema.codexSubscriptionCredentials)
15423
15683
  .where(
15424
15684
  and(
@@ -15460,6 +15720,27 @@ export async function disconnectCodexAccount(
15460
15720
  blockedByUnresolvedRedemption: true,
15461
15721
  };
15462
15722
  }
15723
+ if (appsSettings?.credentialId === credentialId) {
15724
+ const version = appsSettings.version + 1;
15725
+ await scopedDb
15726
+ .update(schema.codexAppsSettings)
15727
+ .set({
15728
+ credentialId: null,
15729
+ version,
15730
+ designatedAt: null,
15731
+ updatedAt: new Date(),
15732
+ })
15733
+ .where(eq(schema.codexAppsSettings.id, appsSettings.id));
15734
+ await scopedDb.insert(schema.auditEvents).values({
15735
+ accountId: credential.accountId,
15736
+ workspaceId,
15737
+ subjectId: actorSubjectId,
15738
+ action: "codex_apps.cleared_on_disconnect",
15739
+ targetType: "codex_subscription_credential",
15740
+ targetId: credentialId,
15741
+ metadata: { version },
15742
+ });
15743
+ }
15463
15744
  const removedRows = await scopedDb
15464
15745
  .delete(schema.codexSubscriptionCredentials)
15465
15746
  .where(
@@ -15512,10 +15793,23 @@ export async function disconnectCodexAccount(
15512
15793
  export async function disconnectAllCodexAccounts(
15513
15794
  db: Database,
15514
15795
  workspaceId: string,
15796
+ actorSubjectId: string | null = null,
15515
15797
  ): Promise<{ removed: number; blockedCredentialIds: string[] }> {
15516
15798
  return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
15799
+ await scopedDb.execute(
15800
+ sql`select pg_advisory_xact_lock(hashtextextended(${`codex-apps-settings:${workspaceId}`}, 0))`,
15801
+ );
15802
+ const [appsSettings] = await scopedDb
15803
+ .select()
15804
+ .from(schema.codexAppsSettings)
15805
+ .where(eq(schema.codexAppsSettings.workspaceId, workspaceId))
15806
+ .for("update")
15807
+ .limit(1);
15517
15808
  const credentials = await scopedDb
15518
- .select({ id: schema.codexSubscriptionCredentials.id })
15809
+ .select({
15810
+ id: schema.codexSubscriptionCredentials.id,
15811
+ accountId: schema.codexSubscriptionCredentials.accountId,
15812
+ })
15519
15813
  .from(schema.codexSubscriptionCredentials)
15520
15814
  .where(eq(schema.codexSubscriptionCredentials.workspaceId, workspaceId))
15521
15815
  .orderBy(asc(schema.codexSubscriptionCredentials.id))
@@ -15538,6 +15832,27 @@ export async function disconnectAllCodexAccounts(
15538
15832
  blockedCredentialIds: blocked.map((row) => row.credentialId).sort(),
15539
15833
  };
15540
15834
  }
15835
+ if (appsSettings?.credentialId) {
15836
+ const version = appsSettings.version + 1;
15837
+ await scopedDb
15838
+ .update(schema.codexAppsSettings)
15839
+ .set({
15840
+ credentialId: null,
15841
+ version,
15842
+ designatedAt: null,
15843
+ updatedAt: new Date(),
15844
+ })
15845
+ .where(eq(schema.codexAppsSettings.id, appsSettings.id));
15846
+ await scopedDb.insert(schema.auditEvents).values({
15847
+ accountId: credentials[0]!.accountId,
15848
+ workspaceId,
15849
+ subjectId: actorSubjectId,
15850
+ action: "codex_apps.cleared_on_disconnect",
15851
+ targetType: "codex_subscription_credential",
15852
+ targetId: appsSettings.credentialId,
15853
+ metadata: { version },
15854
+ });
15855
+ }
15541
15856
  const rows = await scopedDb
15542
15857
  .delete(schema.codexSubscriptionCredentials)
15543
15858
  .where(eq(schema.codexSubscriptionCredentials.workspaceId, workspaceId))
@@ -17117,6 +17432,18 @@ async function lockSessionCreateIdempotencyKey(
17117
17432
  );
17118
17433
  }
17119
17434
 
17435
+ async function lockAgentSessionCreate(tx: Database, input: SessionCreateInput): Promise<void> {
17436
+ const actorSessionId = input.createdByActor?.sessionId;
17437
+ if (!actorSessionId) return;
17438
+ // Agent-originated child creation first reads the parent under FOR SHARE,
17439
+ // then verifies the live attempt under FOR UPDATE. Serialize creates from
17440
+ // the same parent session so concurrent tool calls cannot deadlock while
17441
+ // upgrading that row lock. Unrelated sessions retain full concurrency.
17442
+ await tx.execute(
17443
+ sql`select pg_advisory_xact_lock(hashtextextended(${`agent-session-create:${input.workspaceId}:${actorSessionId}`}, 0))`,
17444
+ );
17445
+ }
17446
+
17120
17447
  async function existingSpawnDenialForKey(
17121
17448
  tx: Database,
17122
17449
  workspaceId: string,
@@ -17222,6 +17549,8 @@ async function createSessionInTransaction(
17222
17549
  }
17223
17550
  }
17224
17551
 
17552
+ await lockAgentSessionCreate(tx, input);
17553
+
17225
17554
  const decision = await resolveSessionDepthDecision(tx, input, id, workspace, deploymentPolicy);
17226
17555
  if (decision.denied) {
17227
17556
  return {
@@ -20250,11 +20579,6 @@ export async function getLatestRunState(
20250
20579
  turnId: string | null;
20251
20580
  serializedRunState: string;
20252
20581
  pendingApprovals: unknown[];
20253
- // The codex account that froze this state (pin > workspace-active), or null
20254
- // when frozen on the non-codex path / before the column existed. The replay
20255
- // path compares it to the resuming turn's codex account to decide whether the
20256
- // blob's account-bound reasoning must be neutralized before being replayed.
20257
- frozenCodexCredentialId: string | null;
20258
20582
  providerArtifactInvalidatedAt: Date | null;
20259
20583
  } | null> {
20260
20584
  return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
@@ -20275,7 +20599,6 @@ export async function getLatestRunState(
20275
20599
  turnId: row.turnId ?? null,
20276
20600
  serializedRunState: row.serializedRunState,
20277
20601
  pendingApprovals: row.pendingApprovals,
20278
- frozenCodexCredentialId: row.frozenCodexCredentialId ?? null,
20279
20602
  providerArtifactInvalidatedAt: row.providerArtifactInvalidatedAt ?? null,
20280
20603
  }
20281
20604
  : null;
@@ -21032,10 +21355,6 @@ export async function appendSessionHistoryItems(
21032
21355
  turnId: string;
21033
21356
  expectedExecutionGeneration: number;
21034
21357
  expectedAttemptId: string;
21035
- // The codex account that produced these items (the turn's resolved credential
21036
- // id), or null/undefined on the non-codex path. Stored verbatim so the read
21037
- // path can strip cross-account reasoning.encrypted_content blobs per turn.
21038
- producerCodexCredentialId?: string | null;
21039
21358
  modelToolOutputTruncationTokens?: number;
21040
21359
  items: Array<{ position: number; item: Record<string, unknown> }>;
21041
21360
  },
@@ -21064,7 +21383,6 @@ export async function appendSessionHistoryItems(
21064
21383
  workspaceId: input.workspaceId,
21065
21384
  sessionId: input.sessionId,
21066
21385
  turnId: input.turnId,
21067
- producerCodexCredentialId: input.producerCodexCredentialId ?? null,
21068
21386
  position: entry.position,
21069
21387
  // This is the canonical model-memory boundary. The pending-call
21070
21388
  // ledger and audit event may retain their separate raw/preview
@@ -21443,18 +21761,18 @@ export async function getActiveSessionHistoryItems(
21443
21761
  sessionId: string,
21444
21762
  ): Promise<
21445
21763
  Array<{
21764
+ id: string;
21446
21765
  position: number;
21447
21766
  item: Record<string, unknown>;
21448
- producerCodexCredentialId: string | null;
21449
21767
  providerArtifactInvalidatedAt: Date | null;
21450
21768
  }>
21451
21769
  > {
21452
21770
  return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
21453
21771
  const rows = await scopedDb
21454
21772
  .select({
21773
+ id: schema.sessionHistoryItems.id,
21455
21774
  position: schema.sessionHistoryItems.position,
21456
21775
  item: schema.sessionHistoryItems.item,
21457
- producerCodexCredentialId: schema.sessionHistoryItems.producerCodexCredentialId,
21458
21776
  providerArtifactInvalidatedAt: schema.sessionHistoryItems.providerArtifactInvalidatedAt,
21459
21777
  })
21460
21778
  .from(schema.sessionHistoryItems)
@@ -21649,11 +21967,8 @@ export async function applyContextCompaction(
21649
21967
  expectedAttemptId: string;
21650
21968
  replacementItems: Array<Record<string, unknown>>;
21651
21969
  summaryItem: Record<string, unknown>;
21652
- replacementInputTokens: number;
21653
21970
  clearRequestedCompaction?: boolean;
21654
21971
  eventPayload?: Record<string, unknown>;
21655
- /** Tag inserted history rows with the Codex credential that produced them. */
21656
- producerCodexCredentialId?: string | null;
21657
21972
  },
21658
21973
  ): Promise<ApplyContextCompactionResult> {
21659
21974
  return await withRlsContext(
@@ -21703,9 +22018,6 @@ export async function applyContextCompaction(
21703
22018
  position: supersededFrom + index,
21704
22019
  item: sanitizeModelPayload(item),
21705
22020
  active: true,
21706
- ...(input.producerCodexCredentialId
21707
- ? { producerCodexCredentialId: input.producerCodexCredentialId }
21708
- : {}),
21709
22021
  })),
21710
22022
  );
21711
22023
  }
@@ -21718,9 +22030,6 @@ export async function applyContextCompaction(
21718
22030
  position: summaryPosition,
21719
22031
  item: sanitizeModelPayload(input.summaryItem),
21720
22032
  active: true,
21721
- ...(input.producerCodexCredentialId
21722
- ? { producerCodexCredentialId: input.producerCodexCredentialId }
21723
- : {}),
21724
22033
  });
21725
22034
  const insertedEvents = input.eventPayload
21726
22035
  ? await tx
@@ -21746,7 +22055,10 @@ export async function applyContextCompaction(
21746
22055
  await tx
21747
22056
  .update(schema.sessions)
21748
22057
  .set({
21749
- lastInputTokens: Math.max(0, Math.floor(input.replacementInputTokens)),
22058
+ // The active model input changed without an ordinary provider call.
22059
+ // Keep this field provider-only and let the first successful
22060
+ // post-compaction response install the next authoritative count.
22061
+ lastInputTokens: null,
21750
22062
  ...(input.clearRequestedCompaction ? { compactRequested: false } : {}),
21751
22063
  ...(insertedEvents.length > 0
21752
22064
  ? {
@@ -21969,8 +22281,9 @@ export async function nextSessionHistoryPosition(
21969
22281
  }
21970
22282
 
21971
22283
  /**
21972
- * Record the actual input-token count of the most recent turn's final model
21973
- * call, for the next turn's pre-read compaction trigger.
22284
+ * Replace the input-token count for the most recent authoritative terminal
22285
+ * response. Null means that response supplied no usable final-call count, so
22286
+ * the next turn must not reuse an older response's value for compaction.
21974
22287
  */
21975
22288
  export async function setSessionLastInputTokensForTurnAttempt(
21976
22289
  db: Database,
@@ -21980,7 +22293,7 @@ export async function setSessionLastInputTokensForTurnAttempt(
21980
22293
  turnId: string;
21981
22294
  expectedExecutionGeneration: number;
21982
22295
  expectedAttemptId: string;
21983
- lastInputTokens: number;
22296
+ lastInputTokens: number | null;
21984
22297
  },
21985
22298
  ): Promise<boolean> {
21986
22299
  return await withWorkspaceRls(db, input.workspaceId, async (scopedDb) => {
@@ -22038,8 +22351,8 @@ export class SessionContextBusyError extends Error {
22038
22351
  * reserved for an approval that paused mid-turn, and the API forbids a
22039
22352
  * clear while such an approval or active turn exists.
22040
22353
  *
22041
- * Also resets last_input_tokens to 0 so the next turn's compaction trigger
22042
- * starts fresh against the now-short context.
22354
+ * Also clears last_input_tokens because no provider has observed the new
22355
+ * context yet.
22043
22356
  *
22044
22357
  * Idempotent: a re-run supersedes the (now sole, already-marker) active row,
22045
22358
  * inserts another marker at the next position. The post-condition (one active
@@ -22131,7 +22444,7 @@ export async function clearSessionContext(
22131
22444
 
22132
22445
  await tx
22133
22446
  .update(schema.sessions)
22134
- .set({ lastInputTokens: 0, updatedAt: new Date() })
22447
+ .set({ lastInputTokens: null, updatedAt: new Date() })
22135
22448
  .where(
22136
22449
  and(
22137
22450
  eq(schema.sessions.workspaceId, input.workspaceId),
@@ -28857,67 +29170,87 @@ async function verifyWorkspaceMutationSettlementForAuthority(
28857
29170
  },
28858
29171
  ): Promise<void> {
28859
29172
  const operation = normalizeWorkspaceMutationOperation(input.operation);
28860
- const settlement: SandboxWorkspaceMutationSettlementResult = await withRlsContext(
28861
- db,
29173
+ const settleOnce = async (): Promise<SandboxWorkspaceMutationSettlementResult> =>
29174
+ await withRlsContext(
29175
+ db,
29176
+ {
29177
+ accountId: authorityInput.accountId,
29178
+ workspaceId: authorityInput.workspaceId,
29179
+ },
29180
+ async (scopedDb) =>
29181
+ await scopedDb.transaction(async (txRaw) => {
29182
+ const tx = txRaw as unknown as Database;
29183
+ // Admission and settlement must take the same canonical ownership
29184
+ // prefix before either touches the lease. The former order
29185
+ // (admission row -> authority -> lease) deadlocked a completed
29186
+ // parallel exec settlement against retained-process promotion
29187
+ // (admission row -> lease -> authority). PostgreSQL then rolled
29188
+ // back the losing admission settlement and permanently blocked
29189
+ // checkpoint capture.
29190
+ //
29191
+ // Preserve physical-settlement semantics when mutable authority
29192
+ // is stale: retain the typed fence, settle the exact immutable
29193
+ // admission below, commit, and only then reject its output.
29194
+ let authority: LockedWorkspaceMutationAuthority | null = null;
29195
+ let authorityFailure: SandboxWorkspaceMutationSettlementResult | null = null;
29196
+ try {
29197
+ authority = await lockWorkspaceMutationAuthorityTx(tx, authorityInput);
29198
+ } catch (error) {
29199
+ const failure = workspaceMutationAuthorityFailure(error);
29200
+ if (!failure) throw error;
29201
+ authorityFailure = failure;
29202
+ }
29203
+
29204
+ const actorKind = authorityInput.kind;
29205
+ const actorId =
29206
+ authorityInput.kind === "turn"
29207
+ ? authorityInput.attemptId
29208
+ : authorityInput.kind === "direct"
29209
+ ? authorityInput.requestId
29210
+ : authorityInput.processId;
29211
+ const admission = await selectExactAdmissionForUpdate(tx, {
29212
+ accountId: authorityInput.accountId,
29213
+ workspaceId: authorityInput.workspaceId,
29214
+ admissionId: input.admission.id,
29215
+ actorKind,
29216
+ actorId,
29217
+ sessionId: authorityInput.sessionId,
29218
+ admittedWorkspaceGeneration: input.admission.workspaceGeneration,
29219
+ operation,
29220
+ });
29221
+ if (
29222
+ !admission ||
29223
+ !admissionMatchesSnapshot(admission, input.admission) ||
29224
+ !admissionSnapshotMatchesAuthorityInput(input.admission, authorityInput) ||
29225
+ admission.provider_outcome === "retained" ||
29226
+ (admission.provider_outcome && admission.provider_outcome !== input.outcome)
29227
+ ) {
29228
+ return {
29229
+ failure: "admission_fenced" as const,
29230
+ detail: "Workspace mutation settlement did not match its exact durable admission",
29231
+ };
29232
+ }
29233
+ if (!admission.settled_at) {
29234
+ await tx.execute(sql`
29235
+ update sandbox_workspace_mutation_admissions set
29236
+ provider_outcome = ${input.outcome}, settled_at = now()
29237
+ where id = ${input.admission.id} and settled_at is null
29238
+ `);
29239
+ }
29240
+ if (input.outcome === "rejected") return { failure: null };
29241
+ if (authorityFailure) return authorityFailure;
29242
+ if (!authority) {
29243
+ throw new Error("Workspace mutation settlement lost its locked authority");
29244
+ }
29245
+ return await verifyResolvedAdmissionAuthority(tx, authority, admission);
29246
+ }),
29247
+ );
29248
+ const settlement = await runIdempotentPersistenceTransaction(
28862
29249
  {
28863
- accountId: authorityInput.accountId,
28864
- workspaceId: authorityInput.workspaceId,
29250
+ stage: "sandbox_workspace_mutation_settlement",
29251
+ maxAttempts: 5,
28865
29252
  },
28866
- async (scopedDb) =>
28867
- await scopedDb.transaction(async (txRaw) => {
28868
- const tx = txRaw as unknown as Database;
28869
- const actorKind = authorityInput.kind;
28870
- const actorId =
28871
- authorityInput.kind === "turn"
28872
- ? authorityInput.attemptId
28873
- : authorityInput.kind === "direct"
28874
- ? authorityInput.requestId
28875
- : authorityInput.processId;
28876
- const admission = await selectExactAdmissionForUpdate(tx, {
28877
- accountId: authorityInput.accountId,
28878
- workspaceId: authorityInput.workspaceId,
28879
- admissionId: input.admission.id,
28880
- actorKind,
28881
- actorId,
28882
- sessionId: authorityInput.sessionId,
28883
- admittedWorkspaceGeneration: input.admission.workspaceGeneration,
28884
- operation,
28885
- });
28886
- if (
28887
- !admission ||
28888
- !admissionMatchesSnapshot(admission, input.admission) ||
28889
- !admissionSnapshotMatchesAuthorityInput(input.admission, authorityInput) ||
28890
- admission.provider_outcome === "retained" ||
28891
- (admission.provider_outcome && admission.provider_outcome !== input.outcome)
28892
- ) {
28893
- return {
28894
- failure: "admission_fenced" as const,
28895
- detail: "Workspace mutation settlement did not match its exact durable admission",
28896
- };
28897
- }
28898
- if (!admission.settled_at) {
28899
- await tx.execute(sql`
28900
- update sandbox_workspace_mutation_admissions set
28901
- provider_outcome = ${input.outcome}, settled_at = now()
28902
- where id = ${input.admission.id} and settled_at is null
28903
- `);
28904
- }
28905
- if (input.outcome === "rejected") return { failure: null };
28906
- // The provider has already returned. Lock and settle its immutable
28907
- // admission before consulting mutable turn/route/process authority, so
28908
- // a stale-authority rejection cannot roll the physical settlement back
28909
- // and strand archive capture. Only authority-fence errors are converted
28910
- // to a post-commit rejection; database failures still abort normally.
28911
- let authority: LockedWorkspaceMutationAuthority;
28912
- try {
28913
- authority = await lockWorkspaceMutationAuthorityTx(tx, authorityInput);
28914
- } catch (error) {
28915
- const failure = workspaceMutationAuthorityFailure(error);
28916
- if (failure) return failure;
28917
- throw error;
28918
- }
28919
- return await verifyResolvedAdmissionAuthority(tx, authority, admission);
28920
- }),
29253
+ settleOnce,
28921
29254
  );
28922
29255
  if (settlement.failure !== null) {
28923
29256
  throw new SandboxWorkspaceMutationFencedError(settlement.failure, settlement.detail);
@@ -29116,6 +29449,19 @@ export async function retainWorkspaceMutationProcess(
29116
29449
  async (scopedDb) =>
29117
29450
  await scopedDb.transaction(async (txRaw) => {
29118
29451
  const tx = txRaw as unknown as Database;
29452
+ // Use the same ownership prefix as mutation admission and settlement.
29453
+ // A stale authority still cannot strand a provider process: remember
29454
+ // the fence, durably promote the exact process, then reject its output
29455
+ // after this transaction commits.
29456
+ let authority: LockedWorkspaceMutationAuthority | null = null;
29457
+ let authorityFailure: SandboxWorkspaceMutationSettlementResult | null = null;
29458
+ try {
29459
+ authority = await lockWorkspaceMutationAuthorityTx(tx, authorityInput);
29460
+ } catch (error) {
29461
+ const failure = workspaceMutationAuthorityFailure(error);
29462
+ if (!failure) throw error;
29463
+ authorityFailure = failure;
29464
+ }
29119
29465
  const actorKind = authorityInput.kind;
29120
29466
  const actorId =
29121
29467
  authorityInput.kind === "turn" ? authorityInput.attemptId : authorityInput.requestId;
@@ -29276,16 +29622,13 @@ export async function retainWorkspaceMutationProcess(
29276
29622
  }
29277
29623
 
29278
29624
  // A yielded provider process is already a physical outcome. Persist its
29279
- // exact route and non-TTL holder before consulting mutable authority, so
29280
- // a route/turn race cannot leave an untracked process or open parent
29281
- // admission. Report staleness only after this transaction commits.
29282
- let authority: LockedWorkspaceMutationAuthority;
29283
- try {
29284
- authority = await lockWorkspaceMutationAuthorityTx(tx, authorityInput);
29285
- } catch (error) {
29286
- const failure = workspaceMutationAuthorityFailure(error);
29287
- if (failure) return { process: mapRetainedProcess(process!), failure };
29288
- throw error;
29625
+ // exact route and non-TTL holder even when the earlier authority check
29626
+ // found a stale route/turn, then report staleness after commit.
29627
+ if (authorityFailure) {
29628
+ return { process: mapRetainedProcess(process!), failure: authorityFailure };
29629
+ }
29630
+ if (!authority) {
29631
+ throw new Error("Retained process promotion lost its locked authority");
29289
29632
  }
29290
29633
  const identity = await verifyResolvedAdmissionAuthority(tx, authority, admission);
29291
29634
  return { process: mapRetainedProcess(process!), failure: identity };
@@ -34416,11 +34759,6 @@ export async function saveRunState(
34416
34759
  expectedAttemptId: string;
34417
34760
  serializedRunState: string;
34418
34761
  pendingApprovals: unknown[];
34419
- // The codex account freezing this state (the turn's resolved credential id),
34420
- // or null on a non-codex turn. Stamped so a resume on a DIFFERENT codex
34421
- // account can strip the blob's account-bound reasoning. Defaults null so
34422
- // every legacy caller (and the non-codex path) is byte-identical.
34423
- frozenCodexCredentialId?: string | null;
34424
34762
  },
34425
34763
  ): Promise<boolean> {
34426
34764
  return await withRlsContext(
@@ -34455,7 +34793,6 @@ export async function saveRunState(
34455
34793
  stateVersion: Number(maxVersion) + 1,
34456
34794
  serializedRunState: input.serializedRunState,
34457
34795
  pendingApprovals: input.pendingApprovals,
34458
- frozenCodexCredentialId: input.frozenCodexCredentialId ?? null,
34459
34796
  });
34460
34797
  return true;
34461
34798
  });
@@ -37165,7 +37502,6 @@ export async function claimSessionWorkForAttempt(
37165
37502
  turnId,
37166
37503
  position: Number(position),
37167
37504
  item: sanitizeModelPayload(delivered.historyItem),
37168
- producerCodexCredentialId: null,
37169
37505
  });
37170
37506
  };
37171
37507
 
@@ -38213,7 +38549,6 @@ export async function claimSessionWorkForAttempt(
38213
38549
  row.prompt,
38214
38550
  Array.isArray(row.resources) ? (row.resources as ResourceRef[]) : [],
38215
38551
  ),
38216
- producerCodexCredentialId: null,
38217
38552
  });
38218
38553
  const providerDelegatedTurn = isSessionRealtimeDelegationTurnMetadata(row.metadata);
38219
38554
  // Cross-session updates are already projected through
@@ -39192,8 +39527,10 @@ export async function peekSessionWork(
39192
39527
  attemptId: interruption.attemptId,
39193
39528
  };
39194
39529
  }
39195
- if (effectiveControl.state !== "active") return { kind: "idle" };
39196
-
39530
+ // Physical quiescence finishes an already-accepted interruption; it is not
39531
+ // new session work. Reconcile the missing receipt even while control stays
39532
+ // paused, otherwise the pause itself can strand this session and every
39533
+ // ancestor behind a permanent `settlement: stopping` projection.
39197
39534
  const awaitingQuiescence = await nextSessionAttemptAwaitingQuiescence(
39198
39535
  scopedDb,
39199
39536
  workspaceId,
@@ -39206,6 +39543,8 @@ export async function peekSessionWork(
39206
39543
  };
39207
39544
  }
39208
39545
 
39546
+ if (effectiveControl.state !== "active") return { kind: "idle" };
39547
+
39209
39548
  const [capacityWait] = await scopedDb
39210
39549
  .select()
39211
39550
  .from(schema.codexCapacityWaiters)
@@ -39849,7 +40188,6 @@ export type ApplySessionTurnSettlementInput = {
39849
40188
  runState?: {
39850
40189
  serializedRunState: string;
39851
40190
  pendingApprovals: unknown[];
39852
- frozenCodexCredentialId?: string | null;
39853
40191
  humanInputRequests?: Array<{
39854
40192
  id: string;
39855
40193
  toolCallId: string;
@@ -40078,7 +40416,6 @@ export async function applySessionTurnSettlement(
40078
40416
  stateVersion: Number(maxVersion) + 1,
40079
40417
  serializedRunState: input.runState.serializedRunState,
40080
40418
  pendingApprovals: input.runState.pendingApprovals,
40081
- frozenCodexCredentialId: input.runState.frozenCodexCredentialId ?? null,
40082
40419
  });
40083
40420
  if (humanInputRequests.length > 0) {
40084
40421
  for (const request of humanInputRequests) {
@@ -41063,7 +41400,8 @@ export type RequestSessionTurnRecoveryInput = {
41063
41400
  providerRecoveryCount?: number;
41064
41401
  fromStatuses?: SessionTurnStatus[];
41065
41402
  providerArtifactInvalidation?: {
41066
- codexCredentialId: string;
41403
+ historyItemIds: string[];
41404
+ runStateId?: string;
41067
41405
  reason: "encrypted_content_rejected";
41068
41406
  };
41069
41407
  };
@@ -41166,47 +41504,36 @@ export async function requestSessionTurnRecovery(
41166
41504
  }
41167
41505
  let providerArtifactsInvalidated = 0;
41168
41506
  if (input.providerArtifactInvalidation) {
41169
- const invalidatedHistory = await tx
41170
- .update(schema.sessionHistoryItems)
41171
- .set({
41172
- providerArtifactInvalidatedAt: now,
41173
- providerArtifactInvalidationReason: input.providerArtifactInvalidation.reason,
41174
- providerArtifactInvalidatedByAttemptId: input.attemptId,
41175
- })
41176
- .where(
41177
- and(
41178
- eq(schema.sessionHistoryItems.accountId, session.accountId),
41179
- eq(schema.sessionHistoryItems.workspaceId, workspaceId),
41180
- eq(schema.sessionHistoryItems.sessionId, input.sessionId),
41181
- eq(schema.sessionHistoryItems.active, true),
41182
- eq(
41183
- schema.sessionHistoryItems.producerCodexCredentialId,
41184
- input.providerArtifactInvalidation.codexCredentialId,
41185
- ),
41186
- isNull(schema.sessionHistoryItems.providerArtifactInvalidatedAt),
41187
- sql`${schema.sessionHistoryItems.item} ->> 'type' in ('reasoning', 'compaction')`,
41188
- ),
41189
- )
41190
- .returning({ id: schema.sessionHistoryItems.id });
41191
- const [latestRunState] = await tx
41192
- .select({ id: schema.agentRunStates.id })
41193
- .from(schema.agentRunStates)
41194
- .where(
41195
- and(
41196
- eq(schema.agentRunStates.accountId, session.accountId),
41197
- eq(schema.agentRunStates.workspaceId, workspaceId),
41198
- eq(schema.agentRunStates.sessionId, input.sessionId),
41199
- eq(schema.agentRunStates.turnId, input.turnId),
41200
- eq(
41201
- schema.agentRunStates.frozenCodexCredentialId,
41202
- input.providerArtifactInvalidation.codexCredentialId,
41203
- ),
41204
- isNull(schema.agentRunStates.providerArtifactInvalidatedAt),
41205
- ),
41206
- )
41207
- .orderBy(desc(schema.agentRunStates.stateVersion))
41208
- .limit(1);
41209
- const invalidatedRunState = latestRunState
41507
+ const historyItemIds = [...new Set(input.providerArtifactInvalidation.historyItemIds)];
41508
+ const invalidatedHistory =
41509
+ historyItemIds.length > 0
41510
+ ? await tx
41511
+ .update(schema.sessionHistoryItems)
41512
+ .set({
41513
+ providerArtifactInvalidatedAt: now,
41514
+ providerArtifactInvalidationReason: input.providerArtifactInvalidation.reason,
41515
+ providerArtifactInvalidatedByAttemptId: input.attemptId,
41516
+ })
41517
+ .where(
41518
+ and(
41519
+ eq(schema.sessionHistoryItems.accountId, session.accountId),
41520
+ eq(schema.sessionHistoryItems.workspaceId, workspaceId),
41521
+ eq(schema.sessionHistoryItems.sessionId, input.sessionId),
41522
+ eq(schema.sessionHistoryItems.active, true),
41523
+ inArray(schema.sessionHistoryItems.id, historyItemIds),
41524
+ isNull(schema.sessionHistoryItems.providerArtifactInvalidatedAt),
41525
+ sql`${schema.sessionHistoryItems.item} ->> 'type' in ('reasoning', 'compaction')`,
41526
+ sql`(
41527
+ nullif(${schema.sessionHistoryItems.item} ->> 'encrypted_content', '') is not null
41528
+ or nullif(${schema.sessionHistoryItems.item} ->> 'encryptedContent', '') is not null
41529
+ or nullif(${schema.sessionHistoryItems.item} -> 'providerData' ->> 'encrypted_content', '') is not null
41530
+ or nullif(${schema.sessionHistoryItems.item} -> 'providerData' ->> 'encryptedContent', '') is not null
41531
+ )`,
41532
+ ),
41533
+ )
41534
+ .returning({ id: schema.sessionHistoryItems.id })
41535
+ : [];
41536
+ const invalidatedRunState = input.providerArtifactInvalidation.runStateId
41210
41537
  ? await tx
41211
41538
  .update(schema.agentRunStates)
41212
41539
  .set({
@@ -41216,8 +41543,20 @@ export async function requestSessionTurnRecovery(
41216
41543
  })
41217
41544
  .where(
41218
41545
  and(
41219
- eq(schema.agentRunStates.id, latestRunState.id),
41546
+ eq(schema.agentRunStates.accountId, session.accountId),
41547
+ eq(schema.agentRunStates.workspaceId, workspaceId),
41548
+ eq(schema.agentRunStates.sessionId, input.sessionId),
41549
+ eq(schema.agentRunStates.turnId, input.turnId),
41550
+ eq(schema.agentRunStates.id, input.providerArtifactInvalidation.runStateId),
41220
41551
  isNull(schema.agentRunStates.providerArtifactInvalidatedAt),
41552
+ sql`${schema.agentRunStates.stateVersion} = (
41553
+ select max(latest.state_version)
41554
+ from agent_run_states latest
41555
+ where latest.account_id = ${session.accountId}
41556
+ and latest.workspace_id = ${workspaceId}
41557
+ and latest.session_id = ${input.sessionId}
41558
+ and latest.turn_id = ${input.turnId}
41559
+ )`,
41221
41560
  ),
41222
41561
  )
41223
41562
  .returning({ id: schema.agentRunStates.id })
@@ -44835,3 +45174,4 @@ export {
44835
45174
  } from "./connection-token-resolver";
44836
45175
 
44837
45176
  export * from "./workspace-artifacts";
45177
+ export * from "./transcription-recordings";