@opengeni/db 4.0.0-canary.1 → 4.0.0-canary.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/dist/canonical-human-identities.js +3 -3
  2. package/dist/{chunk-2HGYCAXN.js → chunk-B5Y3TCSP.js} +2 -2
  3. package/dist/{chunk-A5RFBEZR.js → chunk-CQF6G4IF.js} +3 -3
  4. package/dist/{chunk-FVZINOBB.js → chunk-DUB3LUVM.js} +21 -9
  5. package/dist/chunk-DUB3LUVM.js.map +1 -0
  6. package/dist/{chunk-YVDQDD33.js → chunk-L2QFSPFP.js} +2 -2
  7. package/dist/{chunk-47LKLIIK.js → chunk-NPKBYGAK.js} +2 -2
  8. package/dist/{chunk-XAKQCOF3.js → chunk-PWJJKCTF.js} +11 -1
  9. package/dist/chunk-PWJJKCTF.js.map +1 -0
  10. package/dist/{chunk-I6YPQMAL.js → chunk-VU2IK37Y.js} +2 -2
  11. package/dist/{chunk-ZQ32COTC.js → chunk-XIYAXH47.js} +2 -2
  12. package/dist/editable-artifact-durable-export.js +2 -2
  13. package/dist/editable-artifacts.js +3 -3
  14. package/dist/index.d.ts +6 -0
  15. package/dist/index.js +191 -16
  16. package/dist/index.js.map +1 -1
  17. package/dist/managed-auth-session-sets.js +3 -3
  18. package/dist/schema.d.ts +51 -0
  19. package/dist/schema.js +1 -1
  20. package/dist/session-background-commands.d.ts +6 -0
  21. package/dist/session-background-commands.js +4 -4
  22. package/dist/session-command-output.js +4 -4
  23. package/dist/session-event-slices.js +2 -2
  24. package/dist/session-tenancy.js +3 -3
  25. package/dist/video-generation.js +3 -3
  26. package/drizzle/0419_background_command_launch_authority.sql +56 -0
  27. package/package.json +6 -6
  28. package/src/index.ts +264 -10
  29. package/src/schema.ts +10 -0
  30. package/src/session-background-commands.ts +33 -0
  31. package/dist/chunk-FVZINOBB.js.map +0 -1
  32. package/dist/chunk-XAKQCOF3.js.map +0 -1
  33. /package/dist/{chunk-2HGYCAXN.js.map → chunk-B5Y3TCSP.js.map} +0 -0
  34. /package/dist/{chunk-A5RFBEZR.js.map → chunk-CQF6G4IF.js.map} +0 -0
  35. /package/dist/{chunk-YVDQDD33.js.map → chunk-L2QFSPFP.js.map} +0 -0
  36. /package/dist/{chunk-47LKLIIK.js.map → chunk-NPKBYGAK.js.map} +0 -0
  37. /package/dist/{chunk-I6YPQMAL.js.map → chunk-VU2IK37Y.js.map} +0 -0
  38. /package/dist/{chunk-ZQ32COTC.js.map → chunk-XIYAXH47.js.map} +0 -0
@@ -20,9 +20,9 @@ import {
20
20
  reapManagedAuthIsolatedSessions,
21
21
  releaseManagedAuthActorMutationLease,
22
22
  validateManagedAuthActorMutationLease
23
- } from "./chunk-I6YPQMAL.js";
24
- import "./chunk-ZQ32COTC.js";
25
- import "./chunk-XAKQCOF3.js";
23
+ } from "./chunk-VU2IK37Y.js";
24
+ import "./chunk-XIYAXH47.js";
25
+ import "./chunk-PWJJKCTF.js";
26
26
  import "./chunk-PZ5AY32C.js";
27
27
  export {
28
28
  ManagedAuthActorMutationInFlightError,
package/dist/schema.d.ts CHANGED
@@ -41096,6 +41096,57 @@ export declare const sessionBackgroundCommands: import("drizzle-orm/pg-core").Pg
41096
41096
  identity: undefined;
41097
41097
  generated: undefined;
41098
41098
  }, {}, {}>;
41099
+ launchTurnId: import("drizzle-orm/pg-core").PgColumn<{
41100
+ name: "launch_turn_id";
41101
+ tableName: "session_background_commands";
41102
+ dataType: "string";
41103
+ columnType: "PgUUID";
41104
+ data: string;
41105
+ driverParam: string;
41106
+ notNull: false;
41107
+ hasDefault: false;
41108
+ isPrimaryKey: false;
41109
+ isAutoincrement: false;
41110
+ hasRuntimeDefault: false;
41111
+ enumValues: undefined;
41112
+ baseColumn: never;
41113
+ identity: undefined;
41114
+ generated: undefined;
41115
+ }, {}, {}>;
41116
+ launchAttemptId: import("drizzle-orm/pg-core").PgColumn<{
41117
+ name: "launch_attempt_id";
41118
+ tableName: "session_background_commands";
41119
+ dataType: "string";
41120
+ columnType: "PgUUID";
41121
+ data: string;
41122
+ driverParam: string;
41123
+ notNull: false;
41124
+ hasDefault: false;
41125
+ isPrimaryKey: false;
41126
+ isAutoincrement: false;
41127
+ hasRuntimeDefault: false;
41128
+ enumValues: undefined;
41129
+ baseColumn: never;
41130
+ identity: undefined;
41131
+ generated: undefined;
41132
+ }, {}, {}>;
41133
+ launchExecutionGeneration: import("drizzle-orm/pg-core").PgColumn<{
41134
+ name: "launch_execution_generation";
41135
+ tableName: "session_background_commands";
41136
+ dataType: "number";
41137
+ columnType: "PgInteger";
41138
+ data: number;
41139
+ driverParam: string | number;
41140
+ notNull: false;
41141
+ hasDefault: false;
41142
+ isPrimaryKey: false;
41143
+ isAutoincrement: false;
41144
+ hasRuntimeDefault: false;
41145
+ enumValues: undefined;
41146
+ baseColumn: never;
41147
+ identity: undefined;
41148
+ generated: undefined;
41149
+ }, {}, {}>;
41099
41150
  controlWorkspaceId: import("drizzle-orm/pg-core").PgColumn<{
41100
41151
  name: "control_workspace_id";
41101
41152
  tableName: "session_background_commands";
package/dist/schema.js CHANGED
@@ -350,7 +350,7 @@ import {
350
350
  xaiRotationSettings,
351
351
  xaiSessionAccountPins,
352
352
  xaiSubscriptionCredentials
353
- } from "./chunk-XAKQCOF3.js";
353
+ } from "./chunk-PWJJKCTF.js";
354
354
  import "./chunk-PZ5AY32C.js";
355
355
  export {
356
356
  agentRunStates,
@@ -47,6 +47,9 @@ export declare function insertManagedSessionBackgroundCommandInTransaction(db: D
47
47
  commandId: string;
48
48
  retainedProcessId: string;
49
49
  command: string;
50
+ turnId?: string;
51
+ attemptId?: string;
52
+ executionGeneration?: number;
50
53
  }): Promise<SessionBackgroundCommand>;
51
54
  /** @internal Caller must already hold the canonical exact-attempt fence. */
52
55
  export declare function insertConnectedMachineSessionBackgroundCommandInTransaction(db: Database, input: {
@@ -59,6 +62,9 @@ export declare function insertConnectedMachineSessionBackgroundCommandInTransact
59
62
  connectionInstanceId: string;
60
63
  opId: string;
61
64
  command: string;
65
+ turnId?: string;
66
+ attemptId?: string;
67
+ executionGeneration?: number;
62
68
  }): Promise<SessionBackgroundCommand>;
63
69
  /** Claim exact Connected Machine locators globally for provider reconciliation.
64
70
  * The SECURITY DEFINER SQL function owns the bounded SKIP LOCKED inventory;
@@ -22,10 +22,10 @@ import {
22
22
  settleClaimedConnectedMachineBackgroundCommandWithMutation,
23
23
  settleConnectedMachineSessionBackgroundCommandWithMutation,
24
24
  settleSessionBackgroundCommandForRetainedProcessInTransaction
25
- } from "./chunk-FVZINOBB.js";
26
- import "./chunk-A5RFBEZR.js";
27
- import "./chunk-ZQ32COTC.js";
28
- import "./chunk-XAKQCOF3.js";
25
+ } from "./chunk-DUB3LUVM.js";
26
+ import "./chunk-CQF6G4IF.js";
27
+ import "./chunk-XIYAXH47.js";
28
+ import "./chunk-PWJJKCTF.js";
29
29
  import "./chunk-PZ5AY32C.js";
30
30
  export {
31
31
  COMMAND_OUTPUT_DEFAULT_BYTES,
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  lockSessionEventWriteRows
3
- } from "./chunk-FVZINOBB.js";
4
- import "./chunk-A5RFBEZR.js";
3
+ } from "./chunk-DUB3LUVM.js";
4
+ import "./chunk-CQF6G4IF.js";
5
5
  import {
6
6
  withSessionActivityRlsContext
7
- } from "./chunk-ZQ32COTC.js";
7
+ } from "./chunk-XIYAXH47.js";
8
8
  import {
9
9
  fromPostgresLosslessJson,
10
10
  sandboxRetainedProcesses,
@@ -12,7 +12,7 @@ import {
12
12
  sessionEvents,
13
13
  sessions,
14
14
  withLosslessContentWriteVersion
15
- } from "./chunk-XAKQCOF3.js";
15
+ } from "./chunk-PWJJKCTF.js";
16
16
  import "./chunk-PZ5AY32C.js";
17
17
 
18
18
  // src/session-command-output.ts
@@ -1,13 +1,13 @@
1
1
  import {
2
2
  rawRows,
3
3
  withWorkspaceRls
4
- } from "./chunk-ZQ32COTC.js";
4
+ } from "./chunk-XIYAXH47.js";
5
5
  import {
6
6
  LOSSLESS_JSON_STRING_PREFIX,
7
7
  fromPostgresLosslessJson,
8
8
  sessionEvents,
9
9
  sessionTurns
10
- } from "./chunk-XAKQCOF3.js";
10
+ } from "./chunk-PWJJKCTF.js";
11
11
  import "./chunk-PZ5AY32C.js";
12
12
 
13
13
  // src/session-event-slices.ts
@@ -14,9 +14,9 @@ import {
14
14
  replayAppliedSessionFork,
15
15
  sessionTenancyProductActivated,
16
16
  transitionSessionVisibility
17
- } from "./chunk-2HGYCAXN.js";
18
- import "./chunk-ZQ32COTC.js";
19
- import "./chunk-XAKQCOF3.js";
17
+ } from "./chunk-B5Y3TCSP.js";
18
+ import "./chunk-XIYAXH47.js";
19
+ import "./chunk-PWJJKCTF.js";
20
20
  import "./chunk-PZ5AY32C.js";
21
21
  export {
22
22
  SessionTenancyAccessError,
@@ -29,9 +29,9 @@ import {
29
29
  settleVideoGenerationFailure,
30
30
  settleVideoGenerationReady,
31
31
  updateWorkspaceVideoGenerationPolicy
32
- } from "./chunk-A5RFBEZR.js";
33
- import "./chunk-ZQ32COTC.js";
34
- import "./chunk-XAKQCOF3.js";
32
+ } from "./chunk-CQF6G4IF.js";
33
+ import "./chunk-XIYAXH47.js";
34
+ import "./chunk-PWJJKCTF.js";
35
35
  import "./chunk-PZ5AY32C.js";
36
36
  export {
37
37
  ACTIVE_VIDEO_GENERATION_STATUSES,
@@ -0,0 +1,56 @@
1
+ -- deployment-mode: rolling
2
+ -- New adoption writers freeze their already-fenced launch attempt. Older
3
+ -- writers leave this tuple absent; never invent authority for legacy commands.
4
+ ALTER TABLE session_background_commands
5
+ ADD COLUMN launch_turn_id uuid,
6
+ ADD COLUMN launch_attempt_id uuid,
7
+ ADD COLUMN launch_execution_generation integer,
8
+ ADD CONSTRAINT session_background_commands_launch_identity_check CHECK (
9
+ (launch_turn_id IS NULL AND launch_attempt_id IS NULL AND launch_execution_generation IS NULL)
10
+ OR (launch_turn_id IS NOT NULL AND launch_attempt_id IS NOT NULL AND launch_execution_generation IS NOT NULL AND launch_execution_generation > 0)
11
+ );
12
+
13
+ CREATE FUNCTION fence_background_command_launch_identity()
14
+ RETURNS trigger LANGUAGE plpgsql SET search_path = pg_catalog, public AS $$
15
+ DECLARE matches_launch boolean;
16
+ BEGIN
17
+ IF TG_OP = 'UPDATE' THEN
18
+ IF ROW(NEW.launch_turn_id, NEW.launch_attempt_id, NEW.launch_execution_generation,
19
+ NEW.account_id, NEW.workspace_id, NEW.session_id, NEW.provider,
20
+ NEW.retained_process_id, NEW.control_workspace_id, NEW.enrollment_id,
21
+ NEW.connection_instance_id, NEW.op_id)
22
+ IS DISTINCT FROM
23
+ ROW(OLD.launch_turn_id, OLD.launch_attempt_id, OLD.launch_execution_generation,
24
+ OLD.account_id, OLD.workspace_id, OLD.session_id, OLD.provider,
25
+ OLD.retained_process_id, OLD.control_workspace_id, OLD.enrollment_id,
26
+ OLD.connection_instance_id, OLD.op_id) THEN
27
+ RAISE EXCEPTION 'background command launch identity is immutable' USING ERRCODE = '23514';
28
+ END IF;
29
+ RETURN NEW;
30
+ END IF;
31
+ IF NEW.launch_turn_id IS NOT NULL AND NEW.launch_attempt_id IS NOT NULL
32
+ AND NEW.launch_execution_generation IS NOT NULL THEN
33
+ -- The migrator also supports dedicated data schemas. Never resolve an
34
+ -- unrelated public table when this trigger belongs to another schema.
35
+ EXECUTE format(
36
+ 'SELECT EXISTS (
37
+ SELECT 1 FROM %I.session_turn_attempts a
38
+ JOIN %I.session_turns t ON t.id = a.turn_id AND t.workspace_id = a.workspace_id
39
+ AND t.session_id = a.session_id AND t.account_id = a.account_id
40
+ WHERE a.id = $1 AND a.turn_id = $2
41
+ AND a.workspace_id = $3 AND a.session_id = $4
42
+ AND a.account_id = $5 AND a.execution_generation = $6
43
+ )', TG_TABLE_SCHEMA, TG_TABLE_SCHEMA
44
+ ) INTO matches_launch USING NEW.launch_attempt_id, NEW.launch_turn_id,
45
+ NEW.workspace_id, NEW.session_id, NEW.account_id, NEW.launch_execution_generation;
46
+ IF NOT matches_launch THEN
47
+ RAISE EXCEPTION 'background command launch attempt does not match session' USING ERRCODE = '23514';
48
+ END IF;
49
+ END IF;
50
+ RETURN NEW;
51
+ END;
52
+ $$;
53
+ REVOKE ALL ON FUNCTION fence_background_command_launch_identity() FROM PUBLIC;
54
+ CREATE TRIGGER session_background_commands_launch_identity_fence
55
+ BEFORE INSERT OR UPDATE ON session_background_commands
56
+ FOR EACH ROW EXECUTE FUNCTION fence_background_command_launch_identity();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengeni/db",
3
- "version": "4.0.0-canary.1",
3
+ "version": "4.0.0-canary.2",
4
4
  "description": "OpenGeni persistence: Drizzle schema, RLS-scoped query layer, the SQL migration runner, and role provisioning.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -86,11 +86,11 @@
86
86
  "prepublishOnly": "bash ../../scripts/prepublish-guard"
87
87
  },
88
88
  "dependencies": {
89
- "@opengeni/codemode": "^0.4.27-canary.1",
90
- "@opengeni/codex": "^0.2.21-canary.1",
91
- "@opengeni/config": "^1.0.0-canary.1",
92
- "@opengeni/contracts": "^2.13.0-canary.1",
93
- "@opengeni/network": "^0.3.0-canary.1",
89
+ "@opengeni/codemode": "^0.4.27-canary.2",
90
+ "@opengeni/codex": "^0.2.21-canary.2",
91
+ "@opengeni/config": "^1.0.0-canary.2",
92
+ "@opengeni/contracts": "^2.13.0-canary.2",
93
+ "@opengeni/network": "^0.3.0-canary.2",
94
94
  "drizzle-orm": "^0.45.2",
95
95
  "postgres": "^3.4.7"
96
96
  },
package/src/index.ts CHANGED
@@ -38395,6 +38395,9 @@ export async function adoptManagedSessionBackgroundCommand(
38395
38395
  commandId: input.processId,
38396
38396
  retainedProcessId: input.processId,
38397
38397
  command: input.command,
38398
+ turnId: input.turnId,
38399
+ attemptId: input.attemptId,
38400
+ executionGeneration: input.executionGeneration,
38398
38401
  });
38399
38402
  }),
38400
38403
  );
@@ -61784,7 +61787,9 @@ function systemUpdateCausalHumanTurnId(
61784
61787
  : null;
61785
61788
  const value = isChildLifecycleSystemUpdateKind(update.kind)
61786
61789
  ? lineage?.parentTurnId
61787
- : update.kind === "goal_continuation"
61790
+ : update.kind === "goal_continuation" ||
61791
+ update.kind === "background_command_result" ||
61792
+ update.kind === "session_wait_timeout"
61788
61793
  ? lineage?.causalTurnId
61789
61794
  : null;
61790
61795
  if (typeof value !== "string" || !/^[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}$/iu.test(value)) {
@@ -61829,7 +61834,12 @@ function systemUpdateCausalExecutionKey(
61829
61834
  ): string | null {
61830
61835
  const targetTurnId = systemUpdateCausalHumanTurnId(update);
61831
61836
  if (targetTurnId) return `target-turn:${targetTurnId}`;
61832
- if (isChildLifecycleSystemUpdateKind(update.kind) || update.kind === "goal_continuation") {
61837
+ if (
61838
+ isChildLifecycleSystemUpdateKind(update.kind) ||
61839
+ update.kind === "goal_continuation" ||
61840
+ update.kind === "background_command_result" ||
61841
+ update.kind === "session_wait_timeout"
61842
+ ) {
61833
61843
  // Malformed historical authority-bearing rows still own distinct claims.
61834
61844
  // They may carry ordinary authority-neutral context, but they must never
61835
61845
  // borrow another child, goal, or Steer's causal principal.
@@ -66376,6 +66386,10 @@ async function settleSessionInputWaitInActivity(
66376
66386
  }
66377
66387
  const deadlineAt = expected.inputWaitUntil.toISOString();
66378
66388
  const reason = expected.inputWaitReason;
66389
+ const causalAuthority = await sameSessionCausalAuthorityTx(db as unknown as Database, {
66390
+ ...input,
66391
+ turnId: input.waitTurnId,
66392
+ });
66379
66393
  try {
66380
66394
  const result = await addSessionSystemUpdateWithSourceMutation(
66381
66395
  db,
@@ -66384,6 +66398,11 @@ async function settleSessionInputWaitInActivity(
66384
66398
  workspaceId: input.workspaceId,
66385
66399
  sessionId: input.sessionId,
66386
66400
  kind: "session_wait_timeout",
66401
+ personalConnectionDelegations: causalAuthority?.personalConnectionDelegations ?? [],
66402
+ xaiProviderAccountAuthoritySnapshot:
66403
+ causalAuthority?.xaiProviderAccountAuthoritySnapshot ??
66404
+ WORKSPACE_XAI_PROVIDER_ACCOUNT_AUTHORITY_SNAPSHOT_V1,
66405
+ lineage: causalAuthority?.lineage ?? {},
66387
66406
  classification: "info",
66388
66407
  sourceId: input.waitTurnId,
66389
66408
  dedupeKey: `session-input-wait-timeout:${input.waitTurnId}`,
@@ -66572,6 +66591,11 @@ export type FailSessionWorkBeforeAttemptClaimInput = {
66572
66591
  workflowId: string;
66573
66592
  trigger: SessionWorkTrigger;
66574
66593
  error: string;
66594
+ /** Already-classified admission facts from the failed worker activity. */
66595
+ admissionFailure?: {
66596
+ disposition: "retryable" | "permanent";
66597
+ code: "db_deadlock" | "db_serialization_failure" | "db_failure" | "claim_invariant";
66598
+ };
66575
66599
  };
66576
66600
 
66577
66601
  export type FailSessionWorkBeforeAttemptClaimResult =
@@ -66955,6 +66979,8 @@ export async function failSessionWorkBeforeAttemptClaim(
66955
66979
  payload: {
66956
66980
  status: "failed",
66957
66981
  code: "pre_claim_failure",
66982
+ error: input.error,
66983
+ ...(input.admissionFailure ? { admissionFailure: input.admissionFailure } : {}),
66958
66984
  ...(!turn ? { failedSystemUpdateIds } : {}),
66959
66985
  },
66960
66986
  turnId: turn?.id ?? null,
@@ -67284,6 +67310,8 @@ export async function recoverSessionWorkFailedBeforeAttemptClaim(
67284
67310
  .select({
67285
67311
  id: schema.sessionSystemUpdates.id,
67286
67312
  kind: schema.sessionSystemUpdates.kind,
67313
+ sourceId: schema.sessionSystemUpdates.sourceId,
67314
+ dedupeKey: schema.sessionSystemUpdates.dedupeKey,
67287
67315
  lineage: schema.sessionSystemUpdates.lineage,
67288
67316
  deliveredTurnId: schema.sessionSystemUpdates.deliveredTurnId,
67289
67317
  deliveredHistoryItemId: schema.sessionSystemUpdates.deliveredHistoryItemId,
@@ -67388,22 +67416,71 @@ export async function recoverSessionWorkFailedBeforeAttemptClaim(
67388
67416
  }
67389
67417
 
67390
67418
  const causalTurnIds = new Set<string>();
67419
+ const repairedAuthorities = new Map<
67420
+ string,
67421
+ NonNullable<Awaited<ReturnType<typeof sameSessionCausalAuthorityTx>>>
67422
+ >();
67391
67423
  for (const update of failedUpdates) {
67392
- const causalTurnId = systemUpdateCausalHumanTurnId(update);
67424
+ let causalTurnId = systemUpdateCausalHumanTurnId(update);
67393
67425
  const lineage =
67394
67426
  update.lineage && typeof update.lineage === "object" && !Array.isArray(update.lineage)
67395
67427
  ? (update.lineage as Record<string, unknown>)
67396
67428
  : null;
67397
67429
  if (
67398
- !isChildLifecycleSystemUpdateKind(update.kind) ||
67399
- !causalTurnId ||
67400
- lineage?.parentSessionId !== input.sessionId ||
67401
67430
  update.deliveredTurnId !== null ||
67402
67431
  update.deliveredHistoryItemId !== null ||
67403
67432
  update.deliveredAt !== null
67404
- ) {
67433
+ )
67405
67434
  return { action: "stale", event: null } as const;
67406
- }
67435
+ if (isChildLifecycleSystemUpdateKind(update.kind)) {
67436
+ if (!causalTurnId || lineage?.parentSessionId !== input.sessionId) {
67437
+ return { action: "stale", event: null } as const;
67438
+ }
67439
+ } else if (update.kind === "background_command_result") {
67440
+ if (
67441
+ !update.sourceId ||
67442
+ !UUID_PATTERN.test(update.sourceId) ||
67443
+ update.dedupeKey !== `background-command-result:${update.sourceId}`
67444
+ ) {
67445
+ return { action: "stale", event: null } as const;
67446
+ }
67447
+ const authority = await backgroundCommandCausalAuthorityTx(tx as unknown as Database, {
67448
+ accountId: session.accountId,
67449
+ workspaceId,
67450
+ sessionId: input.sessionId,
67451
+ commandId: update.sourceId,
67452
+ });
67453
+ if (!authority) return { action: "stale", event: null } as const;
67454
+ repairedAuthorities.set(update.id, authority);
67455
+ causalTurnId = authority.lineage.causalTurnId;
67456
+ } else if (update.kind === "session_wait_timeout") {
67457
+ if (
67458
+ !update.sourceId ||
67459
+ !UUID_PATTERN.test(update.sourceId) ||
67460
+ update.dedupeKey !== `session-input-wait-timeout:${update.sourceId}`
67461
+ ) {
67462
+ return { action: "stale", event: null } as const;
67463
+ }
67464
+ const [timeoutProof] = await tx.execute(sql`
67465
+ select id from session_events
67466
+ where workspace_id = ${workspaceId} and session_id = ${input.sessionId}
67467
+ and type = 'session.wait.finished'
67468
+ and payload ->> 'waitTurnId' = ${update.sourceId}
67469
+ and payload ->> 'outcome' = 'timeout'
67470
+ and sequence < ${input.expectedFailureEventSequence}
67471
+ limit 1`);
67472
+ if (!timeoutProof) return { action: "stale", event: null } as const;
67473
+ const authority = await sameSessionCausalAuthorityTx(tx as unknown as Database, {
67474
+ accountId: session.accountId,
67475
+ workspaceId,
67476
+ sessionId: input.sessionId,
67477
+ turnId: update.sourceId,
67478
+ });
67479
+ if (!authority) return { action: "stale", event: null } as const;
67480
+ repairedAuthorities.set(update.id, authority);
67481
+ causalTurnId = authority.lineage.causalTurnId;
67482
+ } else return { action: "stale", event: null } as const;
67483
+ if (!causalTurnId) return { action: "stale", event: null } as const;
67407
67484
  causalTurnIds.add(causalTurnId);
67408
67485
  }
67409
67486
  const causalTurns = await tx
@@ -67432,6 +67509,27 @@ export async function recoverSessionWorkFailedBeforeAttemptClaim(
67432
67509
  return { action: "stale", event: null } as const;
67433
67510
  }
67434
67511
 
67512
+ // Only after every exact failed member and causal receipt passes do
67513
+ // we repair typed authority; message/event content is never rewritten.
67514
+ for (const update of failedUpdates) {
67515
+ const authority = repairedAuthorities.get(update.id);
67516
+ if (!authority) continue;
67517
+ await tx
67518
+ .update(schema.sessionSystemUpdates)
67519
+ .set({
67520
+ personalConnectionDelegations: authority.personalConnectionDelegations,
67521
+ xaiProviderAccountAuthoritySnapshot: authority.xaiProviderAccountAuthoritySnapshot,
67522
+ lineage: { ...update.lineage, ...authority.lineage },
67523
+ })
67524
+ .where(
67525
+ and(
67526
+ eq(schema.sessionSystemUpdates.id, update.id),
67527
+ eq(schema.sessionSystemUpdates.workspaceId, workspaceId),
67528
+ eq(schema.sessionSystemUpdates.sessionId, input.sessionId),
67529
+ eq(schema.sessionSystemUpdates.state, "failed"),
67530
+ ),
67531
+ );
67532
+ }
67435
67533
  const restored = await tx
67436
67534
  .update(schema.sessionSystemUpdates)
67437
67535
  .set({ state: "pending" })
@@ -67622,6 +67720,7 @@ export async function settleSessionIdleWithParentOutbox(
67622
67720
  action: "settled";
67623
67721
  changed: boolean;
67624
67722
  episodeKey: string;
67723
+ notifyParent: boolean;
67625
67724
  events: SessionEvent[];
67626
67725
  }
67627
67726
  | { action: "stale"; episodeKey: null; events: [] }
@@ -67717,11 +67816,30 @@ export async function settleSessionIdleWithParentOutbox(
67717
67816
  // notification to the newest non-status event, not to the idle status
67718
67817
  // event this settlement just appended (or a prior identical idle event).
67719
67818
  const episodeKey = String(Number(episodeSequence));
67720
- if (!session.parentSessionId) {
67819
+ // Workflow closure is not work completion. Input waits (including a
67820
+ // due timeout not yet materialized) and active goals retain obligations
67821
+ // even while their workflow run truthfully parks the session as idle.
67822
+ const waitState = await sessionInputWaitStateTx(tx, workspaceId, sessionId, session);
67823
+ const [goal] = await tx
67824
+ .select({ status: schema.sessionGoals.status })
67825
+ .from(schema.sessionGoals)
67826
+ .where(
67827
+ and(
67828
+ eq(schema.sessionGoals.workspaceId, workspaceId),
67829
+ eq(schema.sessionGoals.sessionId, sessionId),
67830
+ ),
67831
+ )
67832
+ .limit(1);
67833
+ const ongoingWork =
67834
+ waitState.disposition === "held" ||
67835
+ waitState.disposition === "timeout" ||
67836
+ goal?.status === "active";
67837
+ if (!session.parentSessionId || ongoingWork) {
67721
67838
  return {
67722
67839
  action: "settled",
67723
67840
  changed: events.length > 0,
67724
67841
  episodeKey,
67842
+ notifyParent: false,
67725
67843
  events,
67726
67844
  } as const;
67727
67845
  }
@@ -67741,6 +67859,7 @@ export async function settleSessionIdleWithParentOutbox(
67741
67859
  action: "settled",
67742
67860
  changed: events.length > 0,
67743
67861
  episodeKey,
67862
+ notifyParent: true,
67744
67863
  events,
67745
67864
  } as const;
67746
67865
  });
@@ -72799,6 +72918,129 @@ export async function addSessionSystemUpdateWithSourceMutation<
72799
72918
  )) as AddSessionSystemUpdateResult<RequireIdleSession>;
72800
72919
  }
72801
72920
 
72921
+ async function sameSessionCausalAuthorityTx(
72922
+ tx: Database,
72923
+ input: { accountId: string; workspaceId: string; sessionId: string; turnId: string },
72924
+ ) {
72925
+ const [turn] = await tx
72926
+ .select()
72927
+ .from(schema.sessionTurns)
72928
+ .where(
72929
+ and(
72930
+ eq(schema.sessionTurns.accountId, input.accountId),
72931
+ eq(schema.sessionTurns.workspaceId, input.workspaceId),
72932
+ eq(schema.sessionTurns.sessionId, input.sessionId),
72933
+ eq(schema.sessionTurns.id, input.turnId),
72934
+ ),
72935
+ )
72936
+ .limit(1);
72937
+ if (!turn) return null;
72938
+ const personalConnectionDelegations = personalConnectionDelegationsForSameSessionSuccessor(
72939
+ parsedPersonalConnectionDelegations(
72940
+ turn.personalConnectionDelegations,
72941
+ `session_turns:${turn.id}`,
72942
+ ),
72943
+ input.sessionId,
72944
+ );
72945
+ const xaiProviderAccountAuthoritySnapshot = XaiProviderAccountAuthoritySnapshotV1.parse(
72946
+ turn.xaiProviderAccountAuthoritySnapshot,
72947
+ );
72948
+ const human =
72949
+ turn.initiatingHumanSubjectId ??
72950
+ (turn.initiatorKind === "subject" ? turn.initiatorSubjectId : null);
72951
+ if (
72952
+ !human &&
72953
+ (personalConnectionDelegations.some((d) => d.userDelegation) ||
72954
+ xaiProviderAccountAuthoritySnapshot.scope === "user")
72955
+ ) {
72956
+ throw new SessionControlInvariantError("Causal turn lost its personal execution authority");
72957
+ }
72958
+ return {
72959
+ personalConnectionDelegations,
72960
+ xaiProviderAccountAuthoritySnapshot,
72961
+ lineage: {
72962
+ causalTurnId: turn.id,
72963
+ ...(human && personalConnectionDelegations.some((d) => d.userDelegation)
72964
+ ? { connectionAuthoritySubjectId: human }
72965
+ : {}),
72966
+ ...(human && xaiProviderAccountAuthoritySnapshot.scope === "user"
72967
+ ? { xaiAuthoritySubjectId: human }
72968
+ : {}),
72969
+ },
72970
+ };
72971
+ }
72972
+
72973
+ /** Resolve only the immutable launch receipt, including pre-0419 managed rows.
72974
+ * Legacy Connected Machine rows have no durable launch identity and stay
72975
+ * unattributed. Neither a current session owner nor its latest turn is a substitute. */
72976
+ async function backgroundCommandCausalAuthorityTx(
72977
+ tx: Database,
72978
+ input: { accountId: string; workspaceId: string; sessionId: string; commandId: string },
72979
+ ) {
72980
+ const [command] = await tx
72981
+ .select()
72982
+ .from(schema.sessionBackgroundCommands)
72983
+ .where(
72984
+ and(
72985
+ eq(schema.sessionBackgroundCommands.accountId, input.accountId),
72986
+ eq(schema.sessionBackgroundCommands.workspaceId, input.workspaceId),
72987
+ eq(schema.sessionBackgroundCommands.sessionId, input.sessionId),
72988
+ eq(schema.sessionBackgroundCommands.id, input.commandId),
72989
+ ),
72990
+ )
72991
+ .limit(1);
72992
+ if (!command || !["exited", "lost"].includes(command.state) || !command.settledAt) return null;
72993
+ let turnId = command.launchTurnId;
72994
+ let attemptId = command.launchAttemptId;
72995
+ let generation = command.launchExecutionGeneration;
72996
+ if (!turnId && command.provider === "managed" && command.retainedProcessId) {
72997
+ const [process] = await tx
72998
+ .select()
72999
+ .from(schema.sandboxRetainedProcesses)
73000
+ .where(
73001
+ and(
73002
+ eq(schema.sandboxRetainedProcesses.id, command.retainedProcessId),
73003
+ eq(schema.sandboxRetainedProcesses.accountId, input.accountId),
73004
+ eq(schema.sandboxRetainedProcesses.workspaceId, input.workspaceId),
73005
+ eq(schema.sandboxRetainedProcesses.sessionId, input.sessionId),
73006
+ ),
73007
+ )
73008
+ .limit(1);
73009
+ if (process?.ownerActorKind === "turn" && process.ownerActorId === process.ownerAttemptId) {
73010
+ turnId = process.ownerTurnId;
73011
+ attemptId = process.ownerAttemptId;
73012
+ generation = process.ownerExecutionGeneration;
73013
+ }
73014
+ }
73015
+ if (!turnId || !attemptId || !generation) return null;
73016
+ const [attempt] = await tx
73017
+ .select({ id: schema.sessionTurnAttempts.id })
73018
+ .from(schema.sessionTurnAttempts)
73019
+ .where(
73020
+ and(
73021
+ eq(schema.sessionTurnAttempts.id, attemptId),
73022
+ eq(schema.sessionTurnAttempts.accountId, input.accountId),
73023
+ eq(schema.sessionTurnAttempts.workspaceId, input.workspaceId),
73024
+ eq(schema.sessionTurnAttempts.sessionId, input.sessionId),
73025
+ eq(schema.sessionTurnAttempts.turnId, turnId),
73026
+ eq(schema.sessionTurnAttempts.executionGeneration, generation),
73027
+ ),
73028
+ )
73029
+ .limit(1);
73030
+ if (!attempt) return null;
73031
+ const authority = await sameSessionCausalAuthorityTx(tx, { ...input, turnId });
73032
+ return authority
73033
+ ? {
73034
+ ...authority,
73035
+ lineage: {
73036
+ ...authority.lineage,
73037
+ causalAttemptId: attemptId,
73038
+ causalExecutionGeneration: generation,
73039
+ },
73040
+ }
73041
+ : null;
73042
+ }
73043
+
72802
73044
  function backgroundCommandTerminalMutation(input: {
72803
73045
  accountId: string;
72804
73046
  workspaceId: string;
@@ -72934,6 +73176,10 @@ function backgroundCommandTerminalMutation(input: {
72934
73176
  ...(command.failure ? { failure: command.failure } : {}),
72935
73177
  outputLocator,
72936
73178
  };
73179
+ const causalAuthority = await backgroundCommandCausalAuthorityTx(tx as unknown as Database, {
73180
+ ...input,
73181
+ commandId: command.id,
73182
+ });
72937
73183
  const [insertedUpdate] = await tx
72938
73184
  .insert(schema.sessionSystemUpdates)
72939
73185
  .values(
@@ -72949,7 +73195,15 @@ function backgroundCommandTerminalMutation(input: {
72949
73195
  dedupeKey: `background-command-result:${command.id}`,
72950
73196
  summary,
72951
73197
  payload,
72952
- lineage: { commandId: command.id, provider: command.provider },
73198
+ personalConnectionDelegations: causalAuthority?.personalConnectionDelegations ?? [],
73199
+ xaiProviderAccountAuthoritySnapshot:
73200
+ causalAuthority?.xaiProviderAccountAuthoritySnapshot ??
73201
+ WORKSPACE_XAI_PROVIDER_ACCOUNT_AUTHORITY_SNAPSHOT_V1,
73202
+ lineage: {
73203
+ commandId: command.id,
73204
+ provider: command.provider,
73205
+ ...causalAuthority?.lineage,
73206
+ },
72953
73207
  state: "pending",
72954
73208
  },
72955
73209
  "summary",