@opengeni/db 4.2.2 → 4.3.2-canary.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/canonical-human-identities.js +3 -3
- package/dist/{chunk-S4Y42VSX.js → chunk-AI4NC6XW.js} +212 -7
- package/dist/chunk-AI4NC6XW.js.map +1 -0
- package/dist/{chunk-SNLIKELL.js → chunk-DVSLOUEI.js} +2 -2
- package/dist/{chunk-6GAPAYZR.js → chunk-F4BIHJ7J.js} +562 -86
- package/dist/chunk-F4BIHJ7J.js.map +1 -0
- package/dist/{chunk-B2KR3TEM.js → chunk-I4ITNMX2.js} +2 -2
- package/dist/{chunk-MGVOYVPS.js → chunk-PFBUHOI5.js} +3 -3
- package/dist/{chunk-6Y7ZDVJ5.js → chunk-UUPY6IGP.js} +2 -2
- package/dist/{chunk-FPTI4NP4.js → chunk-UVBPOGA7.js} +2 -2
- package/dist/{chunk-7WX3R3YL.js → chunk-XBXANJQS.js} +7 -2
- package/dist/chunk-XBXANJQS.js.map +1 -0
- package/dist/{chunk-6IQ4X35Y.js → chunk-YVYBAWBU.js} +46 -1
- package/dist/chunk-YVYBAWBU.js.map +1 -0
- package/dist/connect-attempts.d.ts +60 -0
- package/dist/connection-setup-authority.d.ts +10 -0
- package/dist/connection-token-resolver.d.ts +11 -2
- package/dist/editable-artifact-durable-export.js +2 -2
- package/dist/editable-artifacts.js +3 -3
- package/dist/external-identities.d.ts +16 -0
- package/dist/external-identity-links.d.ts +74 -0
- package/dist/external-link-work.d.ts +51 -0
- package/dist/host-mcp-binding-match.d.ts +5 -0
- package/dist/host-mcp-bindings.d.ts +73 -0
- package/dist/host-mcp-task-authority.d.ts +47 -0
- package/dist/host-selection-identity.d.ts +6 -0
- package/dist/index.d.ts +60 -35
- package/dist/index.js +15247 -13793
- package/dist/index.js.map +1 -1
- package/dist/live-session-attempt.d.ts +275 -0
- package/dist/managed-auth-session-sets.js +3 -3
- package/dist/memory-domain.d.ts +3 -3
- package/dist/provision-roles.js +1 -1
- package/dist/retained-provider-commands.js +2 -2
- package/dist/runtime-posture.d.ts +6 -6
- package/dist/scheduled-task-revision-authority.d.ts +11 -0
- package/dist/schema.d.ts +1279 -9
- package/dist/schema.js +11 -1
- package/dist/session-background-commands.js +4 -4
- package/dist/session-command-output.js +4 -4
- package/dist/session-event-slices.js +2 -2
- package/dist/session-queue-commands.d.ts +7 -0
- package/dist/session-tenancy.d.ts +2 -0
- package/dist/session-tenancy.js +3 -3
- package/dist/video-generation.js +3 -3
- package/dist/workspace-authority.d.ts +4 -4
- package/dist/workspace-tool-defaults.js +4 -4
- package/drizzle/0437_organization_scoped_external_workspaces.sql +71 -0
- package/drizzle/0438_durable_connect_attempts.sql +95 -0
- package/drizzle/0439_external_identity_provisioning.sql +128 -0
- package/drizzle/0440_external_workspace_member_removal.sql +59 -0
- package/drizzle/0441_external_identity_membership_lifecycle.sql +163 -0
- package/drizzle/0442_external_owning_user_authority.sql +81 -0
- package/drizzle/0443_host_mcp_binding_registry.sql +93 -0
- package/drizzle/0444_host_mcp_delegations.sql +129 -0
- package/drizzle/0445_host_mcp_turn_authorities.sql +145 -0
- package/drizzle/0446_host_mcp_causal_continuation.sql +126 -0
- package/drizzle/0447_host_mcp_task_authorities.sql +264 -0
- package/drizzle/0448_host_mcp_child_authority.sql +115 -0
- package/drizzle/0449_external_identity_link_lifecycle.sql +107 -0
- package/drizzle/0450_external_identity_link_work.sql +179 -0
- package/drizzle/0451_external_link_preview_and_permission_ceiling.sql +82 -0
- package/drizzle/0452_external_link_scheduled_origin.sql +34 -0
- package/drizzle/0453_host_mcp_native_owner.sql +35 -0
- package/drizzle/0454_connect_origin_authority.sql +42 -0
- package/drizzle/0455_external_link_inventory_labels.sql +44 -0
- package/drizzle/0456_social_connection_versions.sql +14 -0
- package/drizzle/0457_canonical_session_scope_subject.sql +46 -0
- package/drizzle/0458_skill_review_wire_compatibility.sql +36 -0
- package/package.json +6 -6
- package/src/connect-attempts.ts +317 -0
- package/src/connection-authority.ts +9 -0
- package/src/connection-setup-authority.ts +35 -0
- package/src/connection-token-resolver.ts +177 -48
- package/src/external-identities.ts +70 -0
- package/src/external-identity-links.ts +364 -0
- package/src/external-link-work.ts +228 -0
- package/src/host-mcp-binding-match.ts +33 -0
- package/src/host-mcp-bindings.ts +741 -0
- package/src/host-mcp-task-authority.ts +383 -0
- package/src/host-selection-identity.ts +29 -0
- package/src/index.ts +481 -151
- package/src/live-session-attempt.ts +85 -0
- package/src/managed-human-provisioning.ts +6 -1
- package/src/memory-domain.ts +6 -11
- package/src/provision-roles.ts +3 -0
- package/src/runtime-posture.ts +47 -0
- package/src/scheduled-task-revision-authority.ts +30 -0
- package/src/schema.ts +233 -7
- package/src/session-control.ts +14 -0
- package/src/session-queue-commands.ts +11 -0
- package/src/session-tenancy.ts +7 -0
- package/src/workspace-artifacts.ts +10 -4
- package/src/workspace-authority.ts +5 -5
- package/dist/chunk-6GAPAYZR.js.map +0 -1
- package/dist/chunk-6IQ4X35Y.js.map +0 -1
- package/dist/chunk-7WX3R3YL.js.map +0 -1
- package/dist/chunk-S4Y42VSX.js.map +0 -1
- /package/dist/{chunk-SNLIKELL.js.map → chunk-DVSLOUEI.js.map} +0 -0
- /package/dist/{chunk-B2KR3TEM.js.map → chunk-I4ITNMX2.js.map} +0 -0
- /package/dist/{chunk-MGVOYVPS.js.map → chunk-PFBUHOI5.js.map} +0 -0
- /package/dist/{chunk-6Y7ZDVJ5.js.map → chunk-UUPY6IGP.js.map} +0 -0
- /package/dist/{chunk-FPTI4NP4.js.map → chunk-UVBPOGA7.js.map} +0 -0
package/src/index.ts
CHANGED
|
@@ -39,6 +39,33 @@ import {
|
|
|
39
39
|
import { releaseOrphanedSkillHeads, type SkillSourceReleaseReceipt } from "./skill-source-release";
|
|
40
40
|
import type { SkillActor, SkillWriteReceipt } from "@opengeni/contracts";
|
|
41
41
|
import { isDeepStrictEqual } from "node:util";
|
|
42
|
+
import {
|
|
43
|
+
normalizeHostCreateSelection,
|
|
44
|
+
metadataWithHostCreateSelection,
|
|
45
|
+
hostCreateSelectionFromMetadata,
|
|
46
|
+
type HostMcpCreateSelection,
|
|
47
|
+
} from "./host-selection-identity";
|
|
48
|
+
export * from "./connect-attempts";
|
|
49
|
+
export * from "./external-identities";
|
|
50
|
+
export * from "./external-identity-links";
|
|
51
|
+
export * from "./connection-setup-authority";
|
|
52
|
+
export * from "./external-link-work";
|
|
53
|
+
import {
|
|
54
|
+
inheritExternalLinkTurnAuthority,
|
|
55
|
+
captureScheduledExternalLinkTurnAuthority,
|
|
56
|
+
cloneExternalLinkTaskAuthority,
|
|
57
|
+
getExternalLinkTurnAuthorization,
|
|
58
|
+
} from "./external-link-work";
|
|
59
|
+
export * from "./host-mcp-bindings";
|
|
60
|
+
import {
|
|
61
|
+
inheritCausalHostMcpTurnAuthorities,
|
|
62
|
+
inheritChildHostMcpTurnAuthorities,
|
|
63
|
+
} from "./host-mcp-bindings";
|
|
64
|
+
export * from "./host-mcp-task-authority";
|
|
65
|
+
import {
|
|
66
|
+
cloneHostMcpTaskAuthorities,
|
|
67
|
+
captureScheduledHostMcpTurnAuthorities,
|
|
68
|
+
} from "./host-mcp-task-authority";
|
|
42
69
|
import {
|
|
43
70
|
SESSION_GOAL_PROGRESS_MAX_BYTES,
|
|
44
71
|
SESSION_GOAL_RATIONALE_MAX_BYTES,
|
|
@@ -150,7 +177,7 @@ import type {
|
|
|
150
177
|
SessionAgentAccess,
|
|
151
178
|
SessionAgentAccessViewer,
|
|
152
179
|
SessionAuthorizationListScope,
|
|
153
|
-
|
|
180
|
+
SessionScopeSubjectId,
|
|
154
181
|
SessionMemoryScope,
|
|
155
182
|
SessionListResponse,
|
|
156
183
|
SessionTenancyPublicProjection,
|
|
@@ -245,6 +272,7 @@ import {
|
|
|
245
272
|
SANDBOX_PROVIDER_INSTANCE_ID_FIELDS_BY_BACKEND,
|
|
246
273
|
parseWorkspaceArchiveDescriptor,
|
|
247
274
|
parseWorkspaceArchiveObjectRef,
|
|
275
|
+
validateWorkspaceArchiveObjectRef,
|
|
248
276
|
workspaceArchivePayloadPresent,
|
|
249
277
|
omitInlineWorkspaceArchiveWhenObjectRefPresent,
|
|
250
278
|
type WorkspaceArchiveObjectRef,
|
|
@@ -296,6 +324,7 @@ import {
|
|
|
296
324
|
HostUsageExportBatch as HostUsageExportBatchContract,
|
|
297
325
|
OPENGENI_HOST_EXPORT_SCHEMA_REVISION,
|
|
298
326
|
HumanInputQuestion as HumanInputQuestionContract,
|
|
327
|
+
canonicalSkillReviewQuestion,
|
|
299
328
|
SubmitHumanInputResponseRequest,
|
|
300
329
|
TurnExecutionPolicyV1,
|
|
301
330
|
CodexCredentialPolicySnapshotV1,
|
|
@@ -349,6 +378,7 @@ import {
|
|
|
349
378
|
type SQLWrapper,
|
|
350
379
|
} from "drizzle-orm";
|
|
351
380
|
import type { PgTransactionConfig } from "drizzle-orm/pg-core";
|
|
381
|
+
import { getLiveSessionAttemptTurn } from "./live-session-attempt";
|
|
352
382
|
import {
|
|
353
383
|
creatorColumns,
|
|
354
384
|
frozenInitiatorForCommandActor,
|
|
@@ -488,7 +518,6 @@ import {
|
|
|
488
518
|
renderWorkspaceMemoryBlock,
|
|
489
519
|
memoryTextForStorage,
|
|
490
520
|
WORKSPACE_MEMORY_BLOCK_EMPTY,
|
|
491
|
-
endUserMemorySubjectId,
|
|
492
521
|
memoryReadScopesForAgentScope,
|
|
493
522
|
memoryWriteScopeForAgentScope,
|
|
494
523
|
normalizeMemoryScope,
|
|
@@ -644,6 +673,7 @@ export {
|
|
|
644
673
|
type UserProfileLookup,
|
|
645
674
|
} from "./database";
|
|
646
675
|
export { withSessionRlsActorContext } from "./database";
|
|
676
|
+
export { normalizedHostCredentialHeaders } from "./connection-token-resolver";
|
|
647
677
|
import {
|
|
648
678
|
buildCodexTokenResolver as buildCodexTokenResolverCore,
|
|
649
679
|
fetchCodexRateLimitResetCreditsForAccount as fetchCodexRateLimitResetCreditsForAccountCore,
|
|
@@ -1495,6 +1525,7 @@ export async function bootstrapWorkspace(
|
|
|
1495
1525
|
and(
|
|
1496
1526
|
eq(schema.workspaces.externalSource, input.workspaceExternalSource),
|
|
1497
1527
|
eq(schema.workspaces.externalId, input.workspaceExternalId),
|
|
1528
|
+
eq(schema.workspaces.accountId, account.id),
|
|
1498
1529
|
),
|
|
1499
1530
|
)
|
|
1500
1531
|
.limit(1);
|
|
@@ -1508,7 +1539,11 @@ export async function bootstrapWorkspace(
|
|
|
1508
1539
|
externalId: input.workspaceExternalId,
|
|
1509
1540
|
})
|
|
1510
1541
|
.onConflictDoUpdate({
|
|
1511
|
-
target: [
|
|
1542
|
+
target: [
|
|
1543
|
+
schema.workspaces.accountId,
|
|
1544
|
+
schema.workspaces.externalSource,
|
|
1545
|
+
schema.workspaces.externalId,
|
|
1546
|
+
],
|
|
1512
1547
|
set: {
|
|
1513
1548
|
name: input.workspaceName,
|
|
1514
1549
|
updatedAt: new Date(),
|
|
@@ -1976,6 +2011,7 @@ export async function ensureManagedAccessForUserWithOrganizationMemberships(
|
|
|
1976
2011
|
and(
|
|
1977
2012
|
eq(schema.workspaces.externalSource, "better-auth:user"),
|
|
1978
2013
|
eq(schema.workspaces.externalId, `${input.userId}:default`),
|
|
2014
|
+
eq(schema.workspaces.accountId, account.id),
|
|
1979
2015
|
),
|
|
1980
2016
|
)
|
|
1981
2017
|
.limit(1);
|
|
@@ -1990,7 +2026,11 @@ export async function ensureManagedAccessForUserWithOrganizationMemberships(
|
|
|
1990
2026
|
externalId: `${input.userId}:default`,
|
|
1991
2027
|
})
|
|
1992
2028
|
.onConflictDoUpdate({
|
|
1993
|
-
target: [
|
|
2029
|
+
target: [
|
|
2030
|
+
schema.workspaces.accountId,
|
|
2031
|
+
schema.workspaces.externalSource,
|
|
2032
|
+
schema.workspaces.externalId,
|
|
2033
|
+
],
|
|
1994
2034
|
// Preserve a later administrator rename on legacy fallback workspaces.
|
|
1995
2035
|
set: { updatedAt: new Date() },
|
|
1996
2036
|
})
|
|
@@ -2403,6 +2443,7 @@ export class WorkspaceExternalIdentityConflictError extends Error {
|
|
|
2403
2443
|
export async function findWorkspaceByExternalIdentity(
|
|
2404
2444
|
db: Database,
|
|
2405
2445
|
input: {
|
|
2446
|
+
accountId: string;
|
|
2406
2447
|
externalSource: string;
|
|
2407
2448
|
externalId: string;
|
|
2408
2449
|
},
|
|
@@ -2414,6 +2455,7 @@ export async function findWorkspaceByExternalIdentity(
|
|
|
2414
2455
|
and(
|
|
2415
2456
|
eq(schema.workspaces.externalSource, input.externalSource),
|
|
2416
2457
|
eq(schema.workspaces.externalId, input.externalId),
|
|
2458
|
+
eq(schema.workspaces.accountId, input.accountId),
|
|
2417
2459
|
),
|
|
2418
2460
|
)
|
|
2419
2461
|
.limit(1);
|
|
@@ -2461,7 +2503,7 @@ async function assertWorkspaceCapacity(
|
|
|
2461
2503
|
|
|
2462
2504
|
/**
|
|
2463
2505
|
* Create an organization workspace exactly once for a stable external tenant
|
|
2464
|
-
* identity. The
|
|
2506
|
+
* identity. The organization-scoped unique index is the concurrency arbiter. A replay never
|
|
2465
2507
|
* mutates presentation fields supplied by the original create.
|
|
2466
2508
|
*/
|
|
2467
2509
|
export async function ensureWorkspaceByExternalIdentity(
|
|
@@ -2488,6 +2530,7 @@ export async function ensureWorkspaceByExternalIdentity(
|
|
|
2488
2530
|
and(
|
|
2489
2531
|
eq(schema.workspaces.externalSource, input.externalSource),
|
|
2490
2532
|
eq(schema.workspaces.externalId, input.externalId),
|
|
2533
|
+
eq(schema.workspaces.accountId, input.accountId),
|
|
2491
2534
|
),
|
|
2492
2535
|
)
|
|
2493
2536
|
.limit(1);
|
|
@@ -2522,7 +2565,11 @@ export async function ensureWorkspaceByExternalIdentity(
|
|
|
2522
2565
|
agentInstructions: input.agentInstructions ?? null,
|
|
2523
2566
|
})
|
|
2524
2567
|
.onConflictDoNothing({
|
|
2525
|
-
target: [
|
|
2568
|
+
target: [
|
|
2569
|
+
schema.workspaces.accountId,
|
|
2570
|
+
schema.workspaces.externalSource,
|
|
2571
|
+
schema.workspaces.externalId,
|
|
2572
|
+
],
|
|
2526
2573
|
})
|
|
2527
2574
|
.returning();
|
|
2528
2575
|
|
|
@@ -2558,6 +2605,7 @@ export async function ensureWorkspaceByExternalIdentity(
|
|
|
2558
2605
|
and(
|
|
2559
2606
|
eq(schema.workspaces.externalSource, input.externalSource),
|
|
2560
2607
|
eq(schema.workspaces.externalId, input.externalId),
|
|
2608
|
+
eq(schema.workspaces.accountId, input.accountId),
|
|
2561
2609
|
),
|
|
2562
2610
|
)
|
|
2563
2611
|
.limit(1);
|
|
@@ -5415,7 +5463,7 @@ export type AppendEventInput = {
|
|
|
5415
5463
|
*/
|
|
5416
5464
|
export type ScheduledTaskCreatorSessionPolicy = {
|
|
5417
5465
|
agentAccess: string | null;
|
|
5418
|
-
|
|
5466
|
+
scopeSubjectId: string | null;
|
|
5419
5467
|
memoryScope: string | null;
|
|
5420
5468
|
};
|
|
5421
5469
|
|
|
@@ -5458,6 +5506,8 @@ export type CreateScheduledTaskInput = {
|
|
|
5458
5506
|
metadata: Record<string, unknown>;
|
|
5459
5507
|
/** Trusted database-only admission seam. Throwing rolls the task creation back. */
|
|
5460
5508
|
beforeCreateCommit?: (tx: Database) => Promise<void>;
|
|
5509
|
+
captureHostAuthority?: (tx: Database, task: ScheduledTask) => Promise<void>;
|
|
5510
|
+
captureLinkAuthority?: (tx: Database, task: ScheduledTask) => Promise<void>;
|
|
5461
5511
|
};
|
|
5462
5512
|
|
|
5463
5513
|
export type UpdateScheduledTaskInput = Partial<{
|
|
@@ -5482,6 +5532,8 @@ export type UpdateScheduledTaskInput = Partial<{
|
|
|
5482
5532
|
authorityUpdatedByActor: AgentSessionCreationActor | null;
|
|
5483
5533
|
/** Trusted database-only admission seam. Throwing rolls the task update back. */
|
|
5484
5534
|
beforeUpdateCommit: (tx: Database) => Promise<void>;
|
|
5535
|
+
captureHostAuthority: (tx: Database, task: ScheduledTask) => Promise<void>;
|
|
5536
|
+
captureLinkAuthority: (tx: Database, task: ScheduledTask) => Promise<void>;
|
|
5485
5537
|
}>;
|
|
5486
5538
|
|
|
5487
5539
|
export type CreatePackInstallationInput = {
|
|
@@ -5566,6 +5618,7 @@ export type CreateSocialConnectionInput = {
|
|
|
5566
5618
|
};
|
|
5567
5619
|
|
|
5568
5620
|
export type UpsertSocialOAuthConnectionInput = {
|
|
5621
|
+
expectedConnection?: { id: string; version: number };
|
|
5569
5622
|
accountId: string;
|
|
5570
5623
|
workspaceId: string;
|
|
5571
5624
|
subjectId?: string | null;
|
|
@@ -5682,6 +5735,8 @@ export type UpdateConnectionInput = {
|
|
|
5682
5735
|
};
|
|
5683
5736
|
|
|
5684
5737
|
export type PersistProviderOAuthConnectionInput = CreateConnectionInput & {
|
|
5738
|
+
/** Server continuation reauthorization; runs inside the credential transaction. */
|
|
5739
|
+
authorize?: (tx: Database) => Promise<void>;
|
|
5685
5740
|
visibleToSubjectId: string;
|
|
5686
5741
|
credentialRole: string;
|
|
5687
5742
|
providerFamily: string;
|
|
@@ -10291,6 +10346,11 @@ export async function persistProviderOAuthConnection(
|
|
|
10291
10346
|
return await scopedDb.transaction(async (txRaw) => {
|
|
10292
10347
|
const tx = txRaw as unknown as Database;
|
|
10293
10348
|
const ownerKey = input.subjectId ?? "workspace";
|
|
10349
|
+
if (input.authorize) {
|
|
10350
|
+
await input.authorize(tx);
|
|
10351
|
+
await setRlsContext(tx, { accountId: input.accountId, workspaceId: input.workspaceId });
|
|
10352
|
+
await setSubjectRlsContext(tx, input.subjectId ?? input.visibleToSubjectId ?? "");
|
|
10353
|
+
}
|
|
10294
10354
|
if (input.requireLiveUserAuthority) {
|
|
10295
10355
|
if (!input.subjectId) {
|
|
10296
10356
|
throw new Error("Live user authority requires a subject-owned connection");
|
|
@@ -16274,6 +16334,40 @@ export async function upsertSocialOAuthConnection(
|
|
|
16274
16334
|
{ accountId: input.accountId, workspaceId: input.workspaceId },
|
|
16275
16335
|
async (scopedDb) => {
|
|
16276
16336
|
if (input.subjectId) await setSubjectRlsContext(scopedDb, input.subjectId);
|
|
16337
|
+
if (input.expectedConnection) {
|
|
16338
|
+
const [updated] = await scopedDb
|
|
16339
|
+
.update(schema.socialConnections)
|
|
16340
|
+
.set({
|
|
16341
|
+
accountHandle: input.accountHandle,
|
|
16342
|
+
accountName: input.accountName ?? null,
|
|
16343
|
+
status: "connected",
|
|
16344
|
+
scopes: input.scopes,
|
|
16345
|
+
credentialEncrypted: input.credentialEncrypted,
|
|
16346
|
+
tokenMetadata: input.tokenMetadata ?? {},
|
|
16347
|
+
updatedAt: new Date(),
|
|
16348
|
+
})
|
|
16349
|
+
.where(
|
|
16350
|
+
and(
|
|
16351
|
+
eq(schema.socialConnections.accountId, input.accountId),
|
|
16352
|
+
eq(schema.socialConnections.workspaceId, input.workspaceId),
|
|
16353
|
+
eq(schema.socialConnections.id, input.expectedConnection.id),
|
|
16354
|
+
eq(schema.socialConnections.version, input.expectedConnection.version),
|
|
16355
|
+
eq(schema.socialConnections.provider, input.provider),
|
|
16356
|
+
input.subjectId
|
|
16357
|
+
? eq(schema.socialConnections.subjectId, input.subjectId)
|
|
16358
|
+
: isNull(schema.socialConnections.subjectId),
|
|
16359
|
+
input.externalAccountId
|
|
16360
|
+
? eq(schema.socialConnections.externalAccountId, input.externalAccountId)
|
|
16361
|
+
: sql`false`,
|
|
16362
|
+
),
|
|
16363
|
+
)
|
|
16364
|
+
.returning();
|
|
16365
|
+
if (!updated)
|
|
16366
|
+
throw new Error(
|
|
16367
|
+
"Social connection changed; authorize the same account again from current state",
|
|
16368
|
+
);
|
|
16369
|
+
return mapSocialConnection(updated);
|
|
16370
|
+
}
|
|
16277
16371
|
const [row] = await scopedDb
|
|
16278
16372
|
.insert(schema.socialConnections)
|
|
16279
16373
|
.values({
|
|
@@ -16651,6 +16745,8 @@ export async function createScheduledTask(
|
|
|
16651
16745
|
${row.id}::uuid,
|
|
16652
16746
|
${row.authorityRevision}::bigint
|
|
16653
16747
|
)`);
|
|
16748
|
+
await input.captureHostAuthority?.(scopedDb, mapScheduledTask(row));
|
|
16749
|
+
await input.captureLinkAuthority?.(scopedDb, mapScheduledTask(row));
|
|
16654
16750
|
return mapScheduledTask(row);
|
|
16655
16751
|
},
|
|
16656
16752
|
);
|
|
@@ -16663,6 +16759,17 @@ export async function updateScheduledTask(
|
|
|
16663
16759
|
input: UpdateScheduledTaskInput,
|
|
16664
16760
|
): Promise<ScheduledTask> {
|
|
16665
16761
|
return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
|
|
16762
|
+
const [previousHostRevision] = await scopedDb
|
|
16763
|
+
.select({ authorityRevision: schema.scheduledTasks.authorityRevision })
|
|
16764
|
+
.from(schema.scheduledTasks)
|
|
16765
|
+
.where(
|
|
16766
|
+
and(
|
|
16767
|
+
eq(schema.scheduledTasks.workspaceId, workspaceId),
|
|
16768
|
+
eq(schema.scheduledTasks.id, taskId),
|
|
16769
|
+
),
|
|
16770
|
+
)
|
|
16771
|
+
.for("update")
|
|
16772
|
+
.limit(1);
|
|
16666
16773
|
if (
|
|
16667
16774
|
input.refreshPersonalResourceAuthority &&
|
|
16668
16775
|
input.clonePersonalResourceAuthorityFromRevision !== undefined
|
|
@@ -16772,7 +16879,26 @@ export async function updateScheduledTask(
|
|
|
16772
16879
|
${row.authorityRevision}::bigint
|
|
16773
16880
|
)`);
|
|
16774
16881
|
}
|
|
16775
|
-
|
|
16882
|
+
const mapped = mapScheduledTask(row);
|
|
16883
|
+
if (input.captureLinkAuthority) await input.captureLinkAuthority(scopedDb, mapped);
|
|
16884
|
+
else if (previousHostRevision)
|
|
16885
|
+
await cloneExternalLinkTaskAuthority(
|
|
16886
|
+
scopedDb,
|
|
16887
|
+
mapped,
|
|
16888
|
+
input.clonePersonalResourceAuthorityFromRevision ??
|
|
16889
|
+
input.cloneConnectionAuthorityFromRevision ??
|
|
16890
|
+
previousHostRevision.authorityRevision,
|
|
16891
|
+
);
|
|
16892
|
+
if (input.captureHostAuthority) await input.captureHostAuthority(scopedDb, mapped);
|
|
16893
|
+
else if (previousHostRevision)
|
|
16894
|
+
await cloneHostMcpTaskAuthorities(
|
|
16895
|
+
scopedDb,
|
|
16896
|
+
mapped,
|
|
16897
|
+
input.clonePersonalResourceAuthorityFromRevision ??
|
|
16898
|
+
input.cloneConnectionAuthorityFromRevision ??
|
|
16899
|
+
previousHostRevision.authorityRevision,
|
|
16900
|
+
);
|
|
16901
|
+
return mapped;
|
|
16776
16902
|
});
|
|
16777
16903
|
}
|
|
16778
16904
|
|
|
@@ -16973,8 +17099,11 @@ export async function getScheduledTaskCreatorPolicy(
|
|
|
16973
17099
|
sessionPolicy: row.sessionPolicy
|
|
16974
17100
|
? {
|
|
16975
17101
|
agentAccess: row.sessionPolicy.agentAccess ?? null,
|
|
16976
|
-
|
|
16977
|
-
memoryScope:
|
|
17102
|
+
scopeSubjectId: row.sessionPolicy.scopeSubjectId ?? null,
|
|
17103
|
+
memoryScope:
|
|
17104
|
+
row.sessionPolicy.memoryScope === "session"
|
|
17105
|
+
? "off"
|
|
17106
|
+
: (row.sessionPolicy.memoryScope ?? null),
|
|
16978
17107
|
}
|
|
16979
17108
|
: null,
|
|
16980
17109
|
};
|
|
@@ -17722,6 +17851,19 @@ export async function materializeScheduledTaskReusableSessionFromRun(
|
|
|
17722
17851
|
if (!row) {
|
|
17723
17852
|
throw new Error("scheduled reusable-session materialization returned no revision");
|
|
17724
17853
|
}
|
|
17854
|
+
const materializedTask = await getScheduledTask(scopedDb, input.workspaceId, input.taskId);
|
|
17855
|
+
if (!materializedTask || materializedTask.authorityRevision !== Number(row.authorityRevision))
|
|
17856
|
+
throw new Error("scheduled host materialization revision changed");
|
|
17857
|
+
await cloneHostMcpTaskAuthorities(
|
|
17858
|
+
scopedDb,
|
|
17859
|
+
materializedTask,
|
|
17860
|
+
input.sourceTaskAuthorityRevision,
|
|
17861
|
+
);
|
|
17862
|
+
await cloneExternalLinkTaskAuthority(
|
|
17863
|
+
scopedDb,
|
|
17864
|
+
materializedTask,
|
|
17865
|
+
input.sourceTaskAuthorityRevision,
|
|
17866
|
+
);
|
|
17725
17867
|
return Number(row.authorityRevision);
|
|
17726
17868
|
},
|
|
17727
17869
|
);
|
|
@@ -17861,38 +18003,7 @@ export async function getScheduledTaskRevisionAuthoritySubject(
|
|
|
17861
18003
|
);
|
|
17862
18004
|
}
|
|
17863
18005
|
|
|
17864
|
-
export
|
|
17865
|
-
db: Database,
|
|
17866
|
-
input: {
|
|
17867
|
-
accountId: string;
|
|
17868
|
-
workspaceId: string;
|
|
17869
|
-
taskId: string;
|
|
17870
|
-
taskAuthorityRevision: number;
|
|
17871
|
-
},
|
|
17872
|
-
): Promise<{
|
|
17873
|
-
subjectId: string;
|
|
17874
|
-
organizationMembershipId: string;
|
|
17875
|
-
membershipAuthorizationRevision: number;
|
|
17876
|
-
} | null> {
|
|
17877
|
-
return await withRlsContext(
|
|
17878
|
-
db,
|
|
17879
|
-
{ accountId: input.accountId, workspaceId: input.workspaceId },
|
|
17880
|
-
async (scopedDb) => {
|
|
17881
|
-
const [row] = await rawRows<{ authority: unknown }>(
|
|
17882
|
-
scopedDb,
|
|
17883
|
-
sql`select scheduled_task_revision_authority_snapshot(
|
|
17884
|
-
${input.accountId}::uuid,
|
|
17885
|
-
${input.workspaceId}::uuid,
|
|
17886
|
-
${input.taskId}::uuid,
|
|
17887
|
-
${input.taskAuthorityRevision}::bigint
|
|
17888
|
-
) as authority`,
|
|
17889
|
-
);
|
|
17890
|
-
const authority = row?.authority;
|
|
17891
|
-
if (!authority) return null;
|
|
17892
|
-
return ScheduledTaskRunAcceptedExecution.shape.causalHumanAuthority.parse(authority);
|
|
17893
|
-
},
|
|
17894
|
-
);
|
|
17895
|
-
}
|
|
18006
|
+
export { getScheduledTaskRevisionAuthority } from "./scheduled-task-revision-authority";
|
|
17896
18007
|
|
|
17897
18008
|
/** Failure settlement must not rewrite a source already committed as dispatched. */
|
|
17898
18009
|
export async function markScheduledTaskRunFailedIfQueued(
|
|
@@ -22606,6 +22717,17 @@ async function lockOrganizationCodexSubscriptionSources(
|
|
|
22606
22717
|
order by workspace_id
|
|
22607
22718
|
`);
|
|
22608
22719
|
const workspaceIds = rows.map((row: { workspace_id: string }) => row.workspace_id);
|
|
22720
|
+
for (const workspaceId of workspaceIds) {
|
|
22721
|
+
// Credential deletion clears session pins/last-used references through FK
|
|
22722
|
+
// SET NULL, including references in Personal or no-longer-inheriting
|
|
22723
|
+
// workspaces. Fence the complete authorized inventory before any source,
|
|
22724
|
+
// pool, or credential lock; the FK's session writes cannot acquire this
|
|
22725
|
+
// prefix after taking their row locks. Keep the UUID order across both
|
|
22726
|
+
// passes, matching ordinary workspace writers' tenancy -> source order.
|
|
22727
|
+
await scopedDb.execute(
|
|
22728
|
+
sql`select pg_advisory_xact_lock_shared(hashtextextended(${`session-tenancy:${workspaceId}`}, 0))`,
|
|
22729
|
+
);
|
|
22730
|
+
}
|
|
22609
22731
|
for (const workspaceId of workspaceIds) {
|
|
22610
22732
|
// Organization credential mutations can change automatic routing in every
|
|
22611
22733
|
// inheriting workspace. Acquire all workspace source locks before the
|
|
@@ -31444,11 +31566,16 @@ async function setScheduledTaskAuthorityRlsContext(
|
|
|
31444
31566
|
): Promise<void> {
|
|
31445
31567
|
const subjectId = frozen.initiator.subjectId.trim();
|
|
31446
31568
|
if (!subjectId) throw new Error("scheduled task authority writer has no subject");
|
|
31569
|
+
// frozenSessionCreatorForInsert verified the agent attempt before reading
|
|
31570
|
+
// this separate causal-human field. Keep service audit attribution intact;
|
|
31571
|
+
// never substitute the session creator or parse a caller's provenance JSON.
|
|
31572
|
+
const causalHumanSubjectId =
|
|
31573
|
+
frozen.initiatingHumanSubjectId ?? (frozen.initiator.kind === "subject" ? subjectId : "");
|
|
31447
31574
|
await tx.execute(sql`select
|
|
31448
31575
|
set_config('opengeni.subject_id', ${subjectId}, true),
|
|
31449
31576
|
set_config(
|
|
31450
31577
|
'opengeni.initiating_human_subject_id',
|
|
31451
|
-
${
|
|
31578
|
+
${causalHumanSubjectId},
|
|
31452
31579
|
true
|
|
31453
31580
|
)`);
|
|
31454
31581
|
}
|
|
@@ -31485,13 +31612,15 @@ export type SessionCreateInput = {
|
|
|
31485
31612
|
/** Agent-to-agent reach (migration 0427); omitted means the workspace default. */
|
|
31486
31613
|
agentAccess?: SessionAgentAccess;
|
|
31487
31614
|
/** Opaque end-user label; omitted or null means none. */
|
|
31488
|
-
|
|
31615
|
+
scopeSubjectId?: SessionScopeSubjectId | null;
|
|
31489
31616
|
/** Typed Memory selector (migration 0427); omitted means the workspace layer. */
|
|
31490
31617
|
memoryScope?: SessionMemoryScope;
|
|
31491
31618
|
parentSessionId?: string | null;
|
|
31492
31619
|
createIdempotencyKey?: string | null;
|
|
31493
31620
|
/** Exact explicit installed-Skill selection used for keyed-create replay. */
|
|
31494
31621
|
selectedInstalledSkillIds?: string[];
|
|
31622
|
+
/** Backend-only replay identity; does not admit host credential authority. */
|
|
31623
|
+
selectedHostMcpDelegations?: HostMcpCreateSelection[];
|
|
31495
31624
|
sandboxGroupId?: string | null;
|
|
31496
31625
|
sandboxOs?: SandboxOs;
|
|
31497
31626
|
/** Exact accepted generated-session compaction policy; internal lifecycle callers only. */
|
|
@@ -31785,10 +31914,11 @@ type SessionCreateReplayIdentity = {
|
|
|
31785
31914
|
visibility?: "user_private" | "workspace_shared";
|
|
31786
31915
|
variableSetIds: string[];
|
|
31787
31916
|
selectedInstalledSkillIds: string[];
|
|
31917
|
+
selectedHostMcpDelegations?: HostMcpCreateSelection[];
|
|
31788
31918
|
initialPersonalResourceAttachmentIntent?: PersonalResourceAttachmentIntent | null;
|
|
31789
31919
|
/** Access/memory scope of the retrying request; omitted means the defaults. */
|
|
31790
31920
|
agentAccess?: SessionAgentAccess;
|
|
31791
|
-
|
|
31921
|
+
scopeSubjectId?: SessionScopeSubjectId | null;
|
|
31792
31922
|
memoryScope?: SessionMemoryScope;
|
|
31793
31923
|
};
|
|
31794
31924
|
|
|
@@ -31823,6 +31953,12 @@ function assertSessionCreateReplayIdentity(
|
|
|
31823
31953
|
existing: typeof schema.sessions.$inferSelect,
|
|
31824
31954
|
input: SessionCreateReplayIdentity,
|
|
31825
31955
|
): void {
|
|
31956
|
+
if (
|
|
31957
|
+
stableJson(hostCreateSelectionFromMetadata(existing.metadata)) !==
|
|
31958
|
+
stableJson(normalizeHostCreateSelection(input.selectedHostMcpDelegations))
|
|
31959
|
+
) {
|
|
31960
|
+
throw new SessionCreateIdempotencyConflictError();
|
|
31961
|
+
}
|
|
31826
31962
|
if (
|
|
31827
31963
|
stableJson(bundledSkillSelectionFromMetadata(existing.metadata) ?? null) !==
|
|
31828
31964
|
stableJson(input.bundledSkillIds ? [...input.bundledSkillIds].sort() : null)
|
|
@@ -31856,8 +31992,7 @@ function assertSessionCreateReplayIdentity(
|
|
|
31856
31992
|
if (
|
|
31857
31993
|
existing.agentAccess !== (input.agentAccess ?? "workspace") ||
|
|
31858
31994
|
existing.memoryScope !== (input.memoryScope ?? "workspace") ||
|
|
31859
|
-
(
|
|
31860
|
-
(existing.endUserId ?? null) !== (input.endUser?.id ?? null)
|
|
31995
|
+
(input.scopeSubjectId !== undefined && sessionEndUserFromRow(existing) !== input.scopeSubjectId)
|
|
31861
31996
|
) {
|
|
31862
31997
|
throw new SessionCreateIdempotencyConflictError();
|
|
31863
31998
|
}
|
|
@@ -31954,7 +32089,10 @@ async function createSessionInTransaction(
|
|
|
31954
32089
|
const variableSetId = variableSetIds.at(-1) ?? null;
|
|
31955
32090
|
const selectedInstalledSkillIds = input.selectedInstalledSkillIds ?? [];
|
|
31956
32091
|
const sessionMetadata = metadataWithSelectedInstalledSkillCreateIdentity(
|
|
31957
|
-
|
|
32092
|
+
metadataWithHostCreateSelection(
|
|
32093
|
+
withBundledSkillSelectionMetadata(input.metadata, input.bundledSkillIds),
|
|
32094
|
+
input.selectedHostMcpDelegations,
|
|
32095
|
+
),
|
|
31958
32096
|
selectedInstalledSkillIds,
|
|
31959
32097
|
);
|
|
31960
32098
|
const createIdempotencyKey = input.createIdempotencyKey ?? null;
|
|
@@ -32009,10 +32147,11 @@ async function createSessionInTransaction(
|
|
|
32009
32147
|
visibility: createRequestedVisibility,
|
|
32010
32148
|
variableSetIds,
|
|
32011
32149
|
selectedInstalledSkillIds: input.selectedInstalledSkillIds ?? [],
|
|
32150
|
+
selectedHostMcpDelegations: input.selectedHostMcpDelegations ?? [],
|
|
32012
32151
|
initialPersonalResourceAttachmentIntent:
|
|
32013
32152
|
input.initialPersonalResourceAttachmentIntent ?? null,
|
|
32014
32153
|
...(input.agentAccess ? { agentAccess: input.agentAccess } : {}),
|
|
32015
|
-
...(input.
|
|
32154
|
+
...(input.scopeSubjectId !== undefined ? { scopeSubjectId: input.scopeSubjectId } : {}),
|
|
32016
32155
|
...(input.memoryScope ? { memoryScope: input.memoryScope } : {}),
|
|
32017
32156
|
});
|
|
32018
32157
|
const grouped = await sessionMcpServerMetadataForSessions(tx, input.workspaceId, [
|
|
@@ -32169,8 +32308,17 @@ async function createSessionInTransaction(
|
|
|
32169
32308
|
instructions: input.instructions ?? null,
|
|
32170
32309
|
policyRole: input.policyRole ?? null,
|
|
32171
32310
|
agentAccess: input.agentAccess ?? "workspace",
|
|
32172
|
-
|
|
32173
|
-
|
|
32311
|
+
scopeSubjectId:
|
|
32312
|
+
input.scopeSubjectId !== undefined
|
|
32313
|
+
? input.scopeSubjectId
|
|
32314
|
+
: frozenCreator.initiator.kind === "subject" &&
|
|
32315
|
+
/^(?:user:|external_user:)/u.test(frozenCreator.initiator.subjectId)
|
|
32316
|
+
? frozenCreator.initiator.subjectId
|
|
32317
|
+
: null,
|
|
32318
|
+
// Retained columns are historical only. Canonical scope is derived
|
|
32319
|
+
// from the creator resolved under the admission locks above.
|
|
32320
|
+
endUserSource: null,
|
|
32321
|
+
endUserId: null,
|
|
32174
32322
|
memoryScope: input.memoryScope ?? "workspace",
|
|
32175
32323
|
parentSessionId: input.parentSessionId ?? null,
|
|
32176
32324
|
parentTurnId,
|
|
@@ -32223,10 +32371,11 @@ async function createSessionInTransaction(
|
|
|
32223
32371
|
visibility: createRequestedVisibility,
|
|
32224
32372
|
variableSetIds,
|
|
32225
32373
|
selectedInstalledSkillIds: input.selectedInstalledSkillIds ?? [],
|
|
32374
|
+
selectedHostMcpDelegations: input.selectedHostMcpDelegations ?? [],
|
|
32226
32375
|
initialPersonalResourceAttachmentIntent:
|
|
32227
32376
|
input.initialPersonalResourceAttachmentIntent ?? null,
|
|
32228
32377
|
...(input.agentAccess ? { agentAccess: input.agentAccess } : {}),
|
|
32229
|
-
...(input.
|
|
32378
|
+
...(input.scopeSubjectId !== undefined ? { scopeSubjectId: input.scopeSubjectId } : {}),
|
|
32230
32379
|
...(input.memoryScope ? { memoryScope: input.memoryScope } : {}),
|
|
32231
32380
|
});
|
|
32232
32381
|
const grouped = await sessionMcpServerMetadataForSessions(tx, input.workspaceId, [
|
|
@@ -32418,6 +32567,7 @@ export async function getInitializedSessionCreateReplay(
|
|
|
32418
32567
|
visibility?: "user_private" | "workspace_shared";
|
|
32419
32568
|
variableSetIds: string[];
|
|
32420
32569
|
selectedInstalledSkillIds: string[];
|
|
32570
|
+
selectedHostMcpDelegations?: HostMcpCreateSelection[];
|
|
32421
32571
|
initialPersonalResourceAttachmentIntent?: PersonalResourceAttachmentIntent | null;
|
|
32422
32572
|
deferInitialTurn?: boolean;
|
|
32423
32573
|
},
|
|
@@ -32876,7 +33026,7 @@ export type SessionAccessProjection = {
|
|
|
32876
33026
|
sessionId: string;
|
|
32877
33027
|
rootSessionId: string;
|
|
32878
33028
|
agentAccess: SessionAgentAccess;
|
|
32879
|
-
|
|
33029
|
+
scopeSubjectId: SessionScopeSubjectId | null;
|
|
32880
33030
|
memoryScope: SessionMemoryScope;
|
|
32881
33031
|
};
|
|
32882
33032
|
|
|
@@ -32887,7 +33037,7 @@ export type SessionAuthorityProjection = SessionAccessProjection & {
|
|
|
32887
33037
|
|
|
32888
33038
|
type SessionAccessRow = Pick<
|
|
32889
33039
|
typeof schema.sessions.$inferSelect,
|
|
32890
|
-
"agentAccess" | "
|
|
33040
|
+
"agentAccess" | "scopeSubjectId" | "memoryScope"
|
|
32891
33041
|
>;
|
|
32892
33042
|
|
|
32893
33043
|
function sessionAgentAccessFromRow(row: Pick<SessionAccessRow, "agentAccess">): SessionAgentAccess {
|
|
@@ -32897,20 +33047,18 @@ function sessionAgentAccessFromRow(row: Pick<SessionAccessRow, "agentAccess">):
|
|
|
32897
33047
|
}
|
|
32898
33048
|
|
|
32899
33049
|
function sessionEndUserFromRow(
|
|
32900
|
-
row: Pick<SessionAccessRow, "
|
|
32901
|
-
):
|
|
32902
|
-
return row.
|
|
32903
|
-
row.
|
|
32904
|
-
row.
|
|
32905
|
-
row.endUserId !== undefined
|
|
32906
|
-
? { source: row.endUserSource, id: row.endUserId }
|
|
33050
|
+
row: Pick<SessionAccessRow, "scopeSubjectId">,
|
|
33051
|
+
): SessionScopeSubjectId | null {
|
|
33052
|
+
return typeof row.scopeSubjectId === "string" &&
|
|
33053
|
+
/^(?:user:|external_user:)/u.test(row.scopeSubjectId)
|
|
33054
|
+
? row.scopeSubjectId
|
|
32907
33055
|
: null;
|
|
32908
33056
|
}
|
|
32909
33057
|
|
|
32910
33058
|
function sessionMemoryScopeFromRow(row: Pick<SessionAccessRow, "memoryScope">): SessionMemoryScope {
|
|
32911
|
-
|
|
32912
|
-
|
|
32913
|
-
|
|
33059
|
+
// Legacy task-local Memory is no longer active. Never widen it to workspace.
|
|
33060
|
+
if (row.memoryScope === "session") return "off";
|
|
33061
|
+
return row.memoryScope === "user" || row.memoryScope === "off" ? row.memoryScope : "workspace";
|
|
32914
33062
|
}
|
|
32915
33063
|
|
|
32916
33064
|
export async function getSessionAuthorityProjection(
|
|
@@ -32926,8 +33074,7 @@ export async function getSessionAuthorityProjection(
|
|
|
32926
33074
|
visibility: schema.sessions.visibility,
|
|
32927
33075
|
ownerSubjectId: schema.sessions.ownerSubjectId,
|
|
32928
33076
|
agentAccess: schema.sessions.agentAccess,
|
|
32929
|
-
|
|
32930
|
-
endUserId: schema.sessions.endUserId,
|
|
33077
|
+
scopeSubjectId: schema.sessions.scopeSubjectId,
|
|
32931
33078
|
memoryScope: schema.sessions.memoryScope,
|
|
32932
33079
|
})
|
|
32933
33080
|
.from(schema.sessions)
|
|
@@ -32940,7 +33087,7 @@ export async function getSessionAuthorityProjection(
|
|
|
32940
33087
|
visibility: row.visibility as SessionAuthorityProjection["visibility"],
|
|
32941
33088
|
ownerSubjectId: row.ownerSubjectId ?? null,
|
|
32942
33089
|
agentAccess: sessionAgentAccessFromRow(row),
|
|
32943
|
-
|
|
33090
|
+
scopeSubjectId: sessionEndUserFromRow(row),
|
|
32944
33091
|
memoryScope: sessionMemoryScopeFromRow(row),
|
|
32945
33092
|
};
|
|
32946
33093
|
});
|
|
@@ -32958,33 +33105,52 @@ export async function getSessionAccessProjection(
|
|
|
32958
33105
|
sessionId: authority.sessionId,
|
|
32959
33106
|
rootSessionId: authority.rootSessionId,
|
|
32960
33107
|
agentAccess: authority.agentAccess,
|
|
32961
|
-
|
|
33108
|
+
scopeSubjectId: authority.scopeSubjectId,
|
|
32962
33109
|
memoryScope: authority.memoryScope,
|
|
32963
33110
|
};
|
|
32964
33111
|
}
|
|
32965
33112
|
|
|
32966
33113
|
/**
|
|
32967
33114
|
* The typed Memory selector an agent on this session reads and writes. The
|
|
32968
|
-
*
|
|
32969
|
-
*
|
|
33115
|
+
* user subject comes from the verified active execution, not a conversation
|
|
33116
|
+
* label or its original creator. Task notes own temporary tree-local data.
|
|
32970
33117
|
* Null when the session does not exist in the workspace.
|
|
32971
33118
|
*/
|
|
32972
33119
|
export async function resolveSessionMemoryAgentScope(
|
|
32973
33120
|
db: Database,
|
|
32974
33121
|
workspaceId: string,
|
|
32975
33122
|
sessionId: string,
|
|
33123
|
+
caller?: { turnId?: unknown; attemptId?: unknown; executionGeneration?: unknown },
|
|
32976
33124
|
): Promise<MemoryAgentScope | null> {
|
|
32977
33125
|
const access = await getSessionAccessProjection(db, workspaceId, sessionId);
|
|
32978
33126
|
if (!access) return null;
|
|
33127
|
+
if (access.memoryScope === "user") {
|
|
33128
|
+
const turn = await getActiveSessionTurnForExecution(db, workspaceId, sessionId);
|
|
33129
|
+
const subjectId =
|
|
33130
|
+
turn?.initiatingHumanSubjectId ??
|
|
33131
|
+
(turn?.initiator.kind === "subject" ? turn.initiator.subjectId : null);
|
|
33132
|
+
if (
|
|
33133
|
+
!turn ||
|
|
33134
|
+
!caller ||
|
|
33135
|
+
turn.id !== caller.turnId ||
|
|
33136
|
+
turn.activeAttemptId !== caller.attemptId ||
|
|
33137
|
+
turn.executionGeneration !== caller.executionGeneration ||
|
|
33138
|
+
!subjectId ||
|
|
33139
|
+
!/^(?:user:|external_user:)/u.test(subjectId)
|
|
33140
|
+
) {
|
|
33141
|
+
return { mode: "off", userSubjectId: null, rootSessionId: access.rootSessionId };
|
|
33142
|
+
}
|
|
33143
|
+
return memoryAgentScopeForSessionAccess({ ...access, scopeSubjectId: subjectId });
|
|
33144
|
+
}
|
|
32979
33145
|
return memoryAgentScopeForSessionAccess(access);
|
|
32980
33146
|
}
|
|
32981
33147
|
|
|
32982
33148
|
export function memoryAgentScopeForSessionAccess(
|
|
32983
|
-
access: Pick<SessionAccessProjection, "rootSessionId" | "
|
|
33149
|
+
access: Pick<SessionAccessProjection, "rootSessionId" | "scopeSubjectId" | "memoryScope">,
|
|
32984
33150
|
): MemoryAgentScope {
|
|
32985
33151
|
return {
|
|
32986
33152
|
mode: access.memoryScope,
|
|
32987
|
-
|
|
33153
|
+
userSubjectId: access.scopeSubjectId,
|
|
32988
33154
|
rootSessionId: access.rootSessionId,
|
|
32989
33155
|
};
|
|
32990
33156
|
}
|
|
@@ -33392,7 +33558,7 @@ export type SessionListFilterOptions = {
|
|
|
33392
33558
|
/** Exclusive creation upper bound. */
|
|
33393
33559
|
createdBefore?: Date;
|
|
33394
33560
|
/** Exact opaque end-user label pair (both parts). */
|
|
33395
|
-
|
|
33561
|
+
scopeSubjectId?: SessionScopeSubjectId;
|
|
33396
33562
|
};
|
|
33397
33563
|
|
|
33398
33564
|
export type ListSessionsForSubjectOptions = ListSessionsOptions &
|
|
@@ -33423,13 +33589,13 @@ export type ListSessionsForSubjectOptions = ListSessionsOptions &
|
|
|
33423
33589
|
*
|
|
33424
33590
|
* **This flag is the authorization.** The resolver it gates is not: it answers
|
|
33425
33591
|
* "does subject X hold authority here" and establishes nothing about who the
|
|
33426
|
-
* caller is. The exception
|
|
33427
|
-
*
|
|
33428
|
-
* organization administrators receive no
|
|
33429
|
-
*
|
|
33592
|
+
* caller is. The exception requires verified native-cookie or dedicated
|
|
33593
|
+
* external owning-user provenance. Unscoped service keys and account or
|
|
33594
|
+
* organization administrators receive no Personal access through it.
|
|
33595
|
+
* A host-signed delegated bearer chooses its own `subjectId`,
|
|
33430
33596
|
* `principalKind`, `metadata.delegated`, and `serviceInitiator` claims, so no
|
|
33431
33597
|
* inspection of the grant can carry that distinction. Set this ONLY from
|
|
33432
|
-
* `
|
|
33598
|
+
* `hasVerifiedOwningUserAuthorization` (`@opengeni/core`),
|
|
33433
33599
|
* which reflects HOW the request authenticated. Omitted/false keeps the
|
|
33434
33600
|
* historical bare-membership fence exactly.
|
|
33435
33601
|
*/
|
|
@@ -34189,7 +34355,7 @@ function sessionFilters(
|
|
|
34189
34355
|
| "updatedBefore"
|
|
34190
34356
|
| "createdFrom"
|
|
34191
34357
|
| "createdBefore"
|
|
34192
|
-
| "
|
|
34358
|
+
| "scopeSubjectId"
|
|
34193
34359
|
>,
|
|
34194
34360
|
): SQL[] {
|
|
34195
34361
|
const filters: SQL[] = [
|
|
@@ -34258,43 +34424,30 @@ function sessionFilters(
|
|
|
34258
34424
|
if (options.updatedBefore) filters.push(lt(schema.sessions.updatedAt, options.updatedBefore));
|
|
34259
34425
|
if (options.createdFrom) filters.push(gte(schema.sessions.createdAt, options.createdFrom));
|
|
34260
34426
|
if (options.createdBefore) filters.push(lt(schema.sessions.createdAt, options.createdBefore));
|
|
34261
|
-
if (options.
|
|
34262
|
-
filters.push(
|
|
34263
|
-
eq(schema.sessions.endUserSource, options.endUser.source),
|
|
34264
|
-
eq(schema.sessions.endUserId, options.endUser.id),
|
|
34265
|
-
);
|
|
34427
|
+
if (options.scopeSubjectId) {
|
|
34428
|
+
filters.push(eq(schema.sessions.scopeSubjectId, options.scopeSubjectId));
|
|
34266
34429
|
}
|
|
34267
34430
|
return filters;
|
|
34268
34431
|
}
|
|
34269
34432
|
|
|
34270
34433
|
/**
|
|
34271
34434
|
* The agent-access predicate for one calling attempt (migration 0427). It
|
|
34272
|
-
* mirrors
|
|
34273
|
-
*
|
|
34274
|
-
*
|
|
34275
|
-
* `workspace` caller additionally sees every `workspace` session and the
|
|
34276
|
-
* `user` sessions carrying its own label. A caller without a label can never
|
|
34277
|
-
* match a labelled `user` session.
|
|
34435
|
+
* mirrors outgoing reach in the core seam: own tree, same canonical user, or
|
|
34436
|
+
* workspace. Target task scope never hides the conversation. This predicate
|
|
34437
|
+
* remains intersected with host scope and ordinary private-session visibility.
|
|
34278
34438
|
*/
|
|
34279
34439
|
export function sessionAgentAccessViewerFilter(viewer: SessionAgentAccessViewer): SQL {
|
|
34280
34440
|
const ownTree = eq(schema.sessions.rootSessionId, viewer.callerRootSessionId);
|
|
34281
|
-
const
|
|
34282
|
-
? and(
|
|
34283
|
-
eq(schema.sessions.endUserSource, viewer.endUser.source),
|
|
34284
|
-
eq(schema.sessions.endUserId, viewer.endUser.id),
|
|
34285
|
-
)!
|
|
34441
|
+
const sameUser = viewer.scopeSubjectId
|
|
34442
|
+
? and(eq(schema.sessions.scopeSubjectId, viewer.scopeSubjectId))!
|
|
34286
34443
|
: sql`false`;
|
|
34287
34444
|
switch (viewer.agentAccess) {
|
|
34288
34445
|
case "session":
|
|
34289
34446
|
return ownTree;
|
|
34290
34447
|
case "user":
|
|
34291
|
-
return or(ownTree,
|
|
34448
|
+
return or(ownTree, sameUser)!;
|
|
34292
34449
|
case "workspace":
|
|
34293
|
-
return
|
|
34294
|
-
ownTree,
|
|
34295
|
-
eq(schema.sessions.agentAccess, "workspace"),
|
|
34296
|
-
and(eq(schema.sessions.agentAccess, "user"), sameEndUser)!,
|
|
34297
|
-
)!;
|
|
34450
|
+
return sql`true`;
|
|
34298
34451
|
}
|
|
34299
34452
|
}
|
|
34300
34453
|
|
|
@@ -34431,7 +34584,7 @@ function sessionListFilterIdentity(options: SessionListFilterOptions): string {
|
|
|
34431
34584
|
!options.updatedBefore &&
|
|
34432
34585
|
!options.createdFrom &&
|
|
34433
34586
|
!options.createdBefore &&
|
|
34434
|
-
!options.
|
|
34587
|
+
!options.scopeSubjectId
|
|
34435
34588
|
) {
|
|
34436
34589
|
return "all";
|
|
34437
34590
|
}
|
|
@@ -34443,7 +34596,7 @@ function sessionListFilterIdentity(options: SessionListFilterOptions): string {
|
|
|
34443
34596
|
options.updatedBefore ? ["updatedBefore", options.updatedBefore.toISOString()] : null,
|
|
34444
34597
|
options.createdFrom ? ["createdFrom", options.createdFrom.toISOString()] : null,
|
|
34445
34598
|
options.createdBefore ? ["createdBefore", options.createdBefore.toISOString()] : null,
|
|
34446
|
-
options.
|
|
34599
|
+
options.scopeSubjectId ? ["scopeSubjectId", options.scopeSubjectId] : null,
|
|
34447
34600
|
]);
|
|
34448
34601
|
}
|
|
34449
34602
|
|
|
@@ -49787,6 +49940,15 @@ async function advanceWorkspaceGenerationForAuthorityOnce(
|
|
|
49787
49940
|
await scopedDb.transaction(async (txRaw) => {
|
|
49788
49941
|
const tx = txRaw as unknown as Database;
|
|
49789
49942
|
const locked = await lockWorkspaceMutationAuthorityTx(tx, authority);
|
|
49943
|
+
// Keep the admission UPDATE and its rejection diagnostic on one lease
|
|
49944
|
+
// state. A concurrent capture release between these READ COMMITTED
|
|
49945
|
+
// statements otherwise turns a recoverable capture wait into a false
|
|
49946
|
+
// lease_fenced error. Take this after the canonical actor/attempt prefix.
|
|
49947
|
+
await tx.execute(sql`select id from sandbox_leases
|
|
49948
|
+
where account_id = ${locked.accountId}
|
|
49949
|
+
and workspace_id = ${locked.workspaceId}
|
|
49950
|
+
and sandbox_group_id = ${locked.sandboxGroupId}
|
|
49951
|
+
for update`);
|
|
49790
49952
|
const rows = await tx.execute<{
|
|
49791
49953
|
id: string;
|
|
49792
49954
|
lease_id: string;
|
|
@@ -53287,13 +53449,30 @@ export async function adoptLegacyModalCheckpointArtifact(
|
|
|
53287
53449
|
}
|
|
53288
53450
|
|
|
53289
53451
|
function publishedWorkspaceArchiveFields(input: {
|
|
53452
|
+
accountId: string;
|
|
53453
|
+
workspaceId: string;
|
|
53454
|
+
sandboxGroupId: string;
|
|
53455
|
+
workspaceArchiveMeta: SandboxArchiveRevision;
|
|
53290
53456
|
workspaceArchive?: string | null;
|
|
53291
53457
|
workspaceArchiveRef?: WorkspaceArchiveObjectRef | null;
|
|
53292
53458
|
}): {
|
|
53293
53459
|
workspaceArchive?: string;
|
|
53294
53460
|
workspaceArchiveRef?: WorkspaceArchiveObjectRef;
|
|
53295
53461
|
} {
|
|
53296
|
-
const ref =
|
|
53462
|
+
const ref =
|
|
53463
|
+
input.workspaceArchiveRef == null
|
|
53464
|
+
? undefined
|
|
53465
|
+
: (validateWorkspaceArchiveObjectRef(input.workspaceArchiveRef, {
|
|
53466
|
+
accountId: input.accountId,
|
|
53467
|
+
workspaceId: input.workspaceId,
|
|
53468
|
+
sandboxGroupId: input.sandboxGroupId,
|
|
53469
|
+
descriptor: input.workspaceArchiveMeta,
|
|
53470
|
+
}) ?? undefined);
|
|
53471
|
+
if (input.workspaceArchiveRef != null && !ref) {
|
|
53472
|
+
throw new Error(
|
|
53473
|
+
"Invalid workspace archive object ref: publication scope or descriptor mismatch",
|
|
53474
|
+
);
|
|
53475
|
+
}
|
|
53297
53476
|
const inline =
|
|
53298
53477
|
typeof input.workspaceArchive === "string" && input.workspaceArchive.length > 0
|
|
53299
53478
|
? input.workspaceArchive
|
|
@@ -53314,6 +53493,30 @@ function publishedWorkspaceArchiveFields(input: {
|
|
|
53314
53493
|
return { workspaceArchive: inline };
|
|
53315
53494
|
}
|
|
53316
53495
|
|
|
53496
|
+
/** Object-candidate outcome from the publication transaction, independent of
|
|
53497
|
+
* lifecycle `wrote`. Absent for inline/provider receipts and pure CAS checks.
|
|
53498
|
+
* `unused` is NOT a retirement receipt: only the owner of a fresh candidate with
|
|
53499
|
+
* no other publication in flight may use it for immediate cleanup. Exceptions
|
|
53500
|
+
* give no disposition (commit may have happened). Legacy/shared keys and evicted
|
|
53501
|
+
* refs need durable no-reattachment evidence before deletion. */
|
|
53502
|
+
export type WorkspaceArchiveCandidateDisposition = "adopted" | "already_referenced" | "unused";
|
|
53503
|
+
|
|
53504
|
+
function workspaceArchiveCandidateFields(
|
|
53505
|
+
candidate: WorkspaceArchiveObjectRef | undefined,
|
|
53506
|
+
lockedResumeState: Record<string, unknown> | null | undefined,
|
|
53507
|
+
): { candidateDisposition?: WorkspaceArchiveCandidateDisposition } {
|
|
53508
|
+
if (!candidate) return {};
|
|
53509
|
+
const sessionState = lockedResumeState?.sessionState as Record<string, unknown> | undefined;
|
|
53510
|
+
const referenced = [
|
|
53511
|
+
sessionState?.workspaceArchiveRef,
|
|
53512
|
+
sessionState?.workspaceArchivePrevRef,
|
|
53513
|
+
].some((value) => {
|
|
53514
|
+
const ref = parseWorkspaceArchiveObjectRef(value);
|
|
53515
|
+
return ref?.key === candidate.key && ref.backend === candidate.backend;
|
|
53516
|
+
});
|
|
53517
|
+
return { candidateDisposition: referenced ? "already_referenced" : "unused" };
|
|
53518
|
+
}
|
|
53519
|
+
|
|
53317
53520
|
export async function persistDrainSnapshot(
|
|
53318
53521
|
db: Database,
|
|
53319
53522
|
input: {
|
|
@@ -53350,12 +53553,14 @@ export async function persistDrainSnapshot(
|
|
|
53350
53553
|
): Promise<{
|
|
53351
53554
|
wrote: boolean;
|
|
53352
53555
|
archiveRevision: string | null;
|
|
53556
|
+
candidateDisposition?: WorkspaceArchiveCandidateDisposition;
|
|
53353
53557
|
}> {
|
|
53354
53558
|
const workspaceArchiveMeta =
|
|
53355
53559
|
input.workspaceArchive === null ? null : parseArchiveRevision(input.workspaceArchiveMeta);
|
|
53356
53560
|
if (input.workspaceArchive !== null && !workspaceArchiveMeta) {
|
|
53357
53561
|
throw new Error("Invalid verified workspace archive descriptor");
|
|
53358
53562
|
}
|
|
53563
|
+
const published = input.workspaceArchive === null ? null : publishedWorkspaceArchiveFields(input);
|
|
53359
53564
|
if (
|
|
53360
53565
|
workspaceArchiveMeta?.version === 2 &&
|
|
53361
53566
|
(workspaceArchiveMeta.provider === "modal_snapshot_filesystem" ||
|
|
@@ -53414,10 +53619,15 @@ export async function persistDrainSnapshot(
|
|
|
53414
53619
|
for update
|
|
53415
53620
|
`);
|
|
53416
53621
|
const row = guard[0];
|
|
53622
|
+
const candidateFields = workspaceArchiveCandidateFields(
|
|
53623
|
+
published?.workspaceArchiveRef,
|
|
53624
|
+
row?.resume_state,
|
|
53625
|
+
);
|
|
53417
53626
|
if (!row) {
|
|
53418
53627
|
return {
|
|
53419
53628
|
wrote: false,
|
|
53420
53629
|
archiveRevision: null,
|
|
53630
|
+
...candidateFields,
|
|
53421
53631
|
};
|
|
53422
53632
|
}
|
|
53423
53633
|
const sourceLeaseMatches =
|
|
@@ -53463,7 +53673,7 @@ export async function persistDrainSnapshot(
|
|
|
53463
53673
|
lateReceipt.providerRequestId === input.providerRequestId &&
|
|
53464
53674
|
!row.unsettled_mutation;
|
|
53465
53675
|
if (!activePublication && !coldLatePublication) {
|
|
53466
|
-
return { wrote: false, archiveRevision: null };
|
|
53676
|
+
return { wrote: false, archiveRevision: null, ...candidateFields };
|
|
53467
53677
|
}
|
|
53468
53678
|
const priorArchive = row.prior_archive ?? null;
|
|
53469
53679
|
const priorArchivePrev = row.prior_archive_prev ?? null;
|
|
@@ -53481,7 +53691,7 @@ export async function persistDrainSnapshot(
|
|
|
53481
53691
|
archiveRevision: null,
|
|
53482
53692
|
};
|
|
53483
53693
|
}
|
|
53484
|
-
|
|
53694
|
+
if (!published) throw new Error("Missing workspace archive publication fields");
|
|
53485
53695
|
const priorMeta = parseArchiveRevision(priorSessionState?.workspaceArchiveMeta);
|
|
53486
53696
|
if (activePublication && row.archive_capture_published_at !== null) {
|
|
53487
53697
|
// A predecessor and its successor may receive the same provider result.
|
|
@@ -53507,7 +53717,7 @@ export async function persistDrainSnapshot(
|
|
|
53507
53717
|
)
|
|
53508
53718
|
`);
|
|
53509
53719
|
}
|
|
53510
|
-
return { wrote: true, archiveRevision: priorMeta.revision };
|
|
53720
|
+
return { wrote: true, archiveRevision: priorMeta.revision, ...candidateFields };
|
|
53511
53721
|
}
|
|
53512
53722
|
const rotation = rotateWorkspaceArchives({
|
|
53513
53723
|
resumeState: row.resume_state,
|
|
@@ -53568,11 +53778,13 @@ export async function persistDrainSnapshot(
|
|
|
53568
53778
|
return {
|
|
53569
53779
|
wrote: false,
|
|
53570
53780
|
archiveRevision: null,
|
|
53781
|
+
...candidateFields,
|
|
53571
53782
|
};
|
|
53572
53783
|
}
|
|
53573
53784
|
return {
|
|
53574
53785
|
wrote: true,
|
|
53575
53786
|
archiveRevision: workspaceArchiveMeta?.revision ?? null,
|
|
53787
|
+
...(published.workspaceArchiveRef ? { candidateDisposition: "adopted" as const } : {}),
|
|
53576
53788
|
};
|
|
53577
53789
|
},
|
|
53578
53790
|
);
|
|
@@ -53971,6 +54183,7 @@ export async function persistWarmSnapshot(
|
|
|
53971
54183
|
throttled: boolean;
|
|
53972
54184
|
superseded: boolean;
|
|
53973
54185
|
archiveRevision: string | null;
|
|
54186
|
+
candidateDisposition?: WorkspaceArchiveCandidateDisposition;
|
|
53974
54187
|
}> {
|
|
53975
54188
|
const capturedAtMs = input.capturedAtMs ?? Date.now();
|
|
53976
54189
|
const workspaceArchiveMeta = parseArchiveRevision(input.workspaceArchiveMeta);
|
|
@@ -54040,6 +54253,26 @@ export async function persistWarmSnapshot(
|
|
|
54040
54253
|
(attempt.outcome === "completed" ||
|
|
54041
54254
|
attempt.outcome === "failed" ||
|
|
54042
54255
|
attempt.outcome === "requires_action"));
|
|
54256
|
+
// Even a replay rejected by the attempt/epoch/capture guards may name
|
|
54257
|
+
// a committed current or previous object. Inspect those slots under the
|
|
54258
|
+
// same lease lock as publication, after the canonical workspace lock.
|
|
54259
|
+
// This conveys no retirement or no-future-reattachment authority.
|
|
54260
|
+
const candidateLease = published.workspaceArchiveRef
|
|
54261
|
+
? await scopedDb.execute<{ resume_state: Record<string, unknown> | null }>(sql`
|
|
54262
|
+
select jsonb_build_object('sessionState', jsonb_build_object(
|
|
54263
|
+
'workspaceArchiveRef', resume_state #> '{sessionState,workspaceArchiveRef}',
|
|
54264
|
+
'workspaceArchivePrevRef', resume_state #> '{sessionState,workspaceArchivePrevRef}'
|
|
54265
|
+
)) as resume_state
|
|
54266
|
+
from sandbox_leases
|
|
54267
|
+
where workspace_id = ${input.workspaceId}
|
|
54268
|
+
and sandbox_group_id = ${input.sandboxGroupId}
|
|
54269
|
+
for update
|
|
54270
|
+
`)
|
|
54271
|
+
: [];
|
|
54272
|
+
const candidateFields = workspaceArchiveCandidateFields(
|
|
54273
|
+
published.workspaceArchiveRef,
|
|
54274
|
+
candidateLease[0]?.resume_state,
|
|
54275
|
+
);
|
|
54043
54276
|
if (
|
|
54044
54277
|
!attempt ||
|
|
54045
54278
|
attempt.accountId !== input.accountId ||
|
|
@@ -54052,6 +54285,7 @@ export async function persistWarmSnapshot(
|
|
|
54052
54285
|
throttled: false,
|
|
54053
54286
|
superseded: true,
|
|
54054
54287
|
archiveRevision: null,
|
|
54288
|
+
...candidateFields,
|
|
54055
54289
|
};
|
|
54056
54290
|
}
|
|
54057
54291
|
const guard = await scopedDb.execute<{
|
|
@@ -54109,6 +54343,7 @@ export async function persistWarmSnapshot(
|
|
|
54109
54343
|
throttled: false,
|
|
54110
54344
|
superseded: false,
|
|
54111
54345
|
archiveRevision: null,
|
|
54346
|
+
...candidateFields,
|
|
54112
54347
|
};
|
|
54113
54348
|
}
|
|
54114
54349
|
const priorArchive = guard[0]!.prior_archive ?? null;
|
|
@@ -54129,6 +54364,7 @@ export async function persistWarmSnapshot(
|
|
|
54129
54364
|
throttled: false,
|
|
54130
54365
|
superseded: true,
|
|
54131
54366
|
archiveRevision: null,
|
|
54367
|
+
...candidateFields,
|
|
54132
54368
|
};
|
|
54133
54369
|
}
|
|
54134
54370
|
if (
|
|
@@ -54141,6 +54377,7 @@ export async function persistWarmSnapshot(
|
|
|
54141
54377
|
throttled: true,
|
|
54142
54378
|
superseded: false,
|
|
54143
54379
|
archiveRevision: null,
|
|
54380
|
+
...candidateFields,
|
|
54144
54381
|
};
|
|
54145
54382
|
}
|
|
54146
54383
|
const rotation = rotateWorkspaceArchives({
|
|
@@ -54162,6 +54399,7 @@ export async function persistWarmSnapshot(
|
|
|
54162
54399
|
throttled: false,
|
|
54163
54400
|
superseded: false,
|
|
54164
54401
|
archiveRevision: null,
|
|
54402
|
+
...candidateFields,
|
|
54165
54403
|
};
|
|
54166
54404
|
}
|
|
54167
54405
|
const livenessGuard: "warm" | "draining" = rowLiveness;
|
|
@@ -54194,6 +54432,7 @@ export async function persistWarmSnapshot(
|
|
|
54194
54432
|
throttled: false,
|
|
54195
54433
|
superseded: false,
|
|
54196
54434
|
archiveRevision: null,
|
|
54435
|
+
...candidateFields,
|
|
54197
54436
|
};
|
|
54198
54437
|
}
|
|
54199
54438
|
return {
|
|
@@ -54201,6 +54440,7 @@ export async function persistWarmSnapshot(
|
|
|
54201
54440
|
throttled: false,
|
|
54202
54441
|
superseded: false,
|
|
54203
54442
|
archiveRevision: workspaceArchiveMeta.revision,
|
|
54443
|
+
...(published.workspaceArchiveRef ? { candidateDisposition: "adopted" as const } : {}),
|
|
54204
54444
|
};
|
|
54205
54445
|
},
|
|
54206
54446
|
);
|
|
@@ -54451,6 +54691,21 @@ async function commitWorkspaceCaptureRevision(
|
|
|
54451
54691
|
return null;
|
|
54452
54692
|
}
|
|
54453
54693
|
|
|
54694
|
+
// Capture yields when new work is accepted. Its last database call can
|
|
54695
|
+
// still be in flight when that cancellation arrives, so serialize this
|
|
54696
|
+
// fence with enqueue/claim on the session row and refuse older reviews.
|
|
54697
|
+
// Position is priority order, not chronology.
|
|
54698
|
+
const successor = await tx.execute<{ id: string }>(sql`
|
|
54699
|
+
select id from session_turns
|
|
54700
|
+
where workspace_id = ${input.workspaceId}
|
|
54701
|
+
and session_id = ${input.sessionId}
|
|
54702
|
+
and id <> ${input.turnId}
|
|
54703
|
+
and (created_at >= (select created_at from session_turns where id = ${input.turnId})
|
|
54704
|
+
or status in ('queued', 'running'))
|
|
54705
|
+
limit 1
|
|
54706
|
+
`);
|
|
54707
|
+
if (successor.length > 0) return null;
|
|
54708
|
+
|
|
54454
54709
|
const capturedAt = input.capturedAt ?? new Date();
|
|
54455
54710
|
const rows = await tx.execute<{ revision: number | string }>(sql`
|
|
54456
54711
|
insert into workspace_captures
|
|
@@ -62258,6 +62513,10 @@ export type InitializeSessionStartInput = {
|
|
|
62258
62513
|
/** Trusted create-session policy. Omitted only by legacy low-level callers. */
|
|
62259
62514
|
turnExecutionPolicy?: TurnExecutionPolicyV1;
|
|
62260
62515
|
createdEventPayload: Record<string, unknown>;
|
|
62516
|
+
/** Trusted backend-only capture for a newly inserted initial turn. Runs under
|
|
62517
|
+
* the canonical activity transaction; failure rolls back events and turn.
|
|
62518
|
+
* Never invoked on replay or deferred starts; not caller JSON authority. */
|
|
62519
|
+
captureInitialTurnAuthority?: (tx: Database, turnId: string) => Promise<void>;
|
|
62261
62520
|
/** Sensitive-safe semantic title supplied by the trusted agent-child create
|
|
62262
62521
|
* path. It is committed with the initial timeline, never as a bare row edit. */
|
|
62263
62522
|
initialAutomaticTitle?: string | null;
|
|
@@ -62314,6 +62573,8 @@ export async function initializeSessionStartAtomically(
|
|
|
62314
62573
|
db: Database,
|
|
62315
62574
|
input: InitializeSessionStartInput,
|
|
62316
62575
|
): Promise<InitializeSessionStartResult> {
|
|
62576
|
+
if (input.deferInitialTurn && input.captureInitialTurnAuthority)
|
|
62577
|
+
throw new Error("Deferred session starts cannot capture initial turn authority");
|
|
62317
62578
|
return await withSessionActivityRlsContext(
|
|
62318
62579
|
db,
|
|
62319
62580
|
{ accountId: input.accountId, workspaceId: input.workspaceId },
|
|
@@ -62844,6 +63105,30 @@ export async function initializeSessionStartAtomically(
|
|
|
62844
63105
|
});
|
|
62845
63106
|
|
|
62846
63107
|
const initialPersonalResourceIntent = session.initialPersonalResourceAttachmentIntent;
|
|
63108
|
+
if (insertedTurn && input.captureInitialTurnAuthority)
|
|
63109
|
+
await input.captureInitialTurnAuthority(tx as unknown as Database, turn.id);
|
|
63110
|
+
if (insertedTurn && session.parentSessionId && session.parentTurnId)
|
|
63111
|
+
await inheritExternalLinkTurnAuthority(tx as unknown as Database, {
|
|
63112
|
+
accountId: session.accountId,
|
|
63113
|
+
workspaceId: input.workspaceId,
|
|
63114
|
+
sessionId: session.id,
|
|
63115
|
+
turnId: turn.id,
|
|
63116
|
+
sourceTurnId: session.parentTurnId,
|
|
63117
|
+
kind: "child",
|
|
63118
|
+
});
|
|
63119
|
+
if (
|
|
63120
|
+
insertedTurn &&
|
|
63121
|
+
session.parentSessionId &&
|
|
63122
|
+
session.parentTurnId &&
|
|
63123
|
+
turn.initiatingHumanSubjectId
|
|
63124
|
+
)
|
|
63125
|
+
await inheritChildHostMcpTurnAuthorities(tx as unknown as Database, {
|
|
63126
|
+
accountId: session.accountId,
|
|
63127
|
+
workspaceId: input.workspaceId,
|
|
63128
|
+
sessionId: session.id,
|
|
63129
|
+
turnId: turn.id,
|
|
63130
|
+
subjectId: turn.initiatingHumanSubjectId,
|
|
63131
|
+
});
|
|
62847
63132
|
if (initialPersonalResourceIntent) {
|
|
62848
63133
|
const attachmentInitiatingHumanSubjectId =
|
|
62849
63134
|
turn.initiatingHumanSubjectId ??
|
|
@@ -65811,6 +66096,39 @@ export async function claimSessionWorkForAttempt(
|
|
|
65811
66096
|
frozenGoalSnapshot,
|
|
65812
66097
|
goalContinuationHistoryItem ?? scheduledOccurrenceHistoryItem,
|
|
65813
66098
|
);
|
|
66099
|
+
if (scheduledTaskRunId) {
|
|
66100
|
+
await captureScheduledExternalLinkTurnAuthority(tx as unknown as Database, {
|
|
66101
|
+
accountId: session.accountId,
|
|
66102
|
+
workspaceId,
|
|
66103
|
+
sessionId,
|
|
66104
|
+
turnId: internalTurn.id,
|
|
66105
|
+
runId: scheduledTaskRunId,
|
|
66106
|
+
});
|
|
66107
|
+
await captureScheduledHostMcpTurnAuthorities(tx as unknown as Database, {
|
|
66108
|
+
accountId: session.accountId,
|
|
66109
|
+
workspaceId,
|
|
66110
|
+
sessionId,
|
|
66111
|
+
turnId: internalTurn.id,
|
|
66112
|
+
runId: scheduledTaskRunId,
|
|
66113
|
+
});
|
|
66114
|
+
} else if (causalHumanTurnId && initiatingHumanSubjectId) {
|
|
66115
|
+
await inheritExternalLinkTurnAuthority(tx as unknown as Database, {
|
|
66116
|
+
accountId: session.accountId,
|
|
66117
|
+
workspaceId,
|
|
66118
|
+
sessionId,
|
|
66119
|
+
turnId: internalTurn.id,
|
|
66120
|
+
sourceTurnId: causalHumanTurnId,
|
|
66121
|
+
kind: "causal",
|
|
66122
|
+
});
|
|
66123
|
+
await inheritCausalHostMcpTurnAuthorities(tx as unknown as Database, {
|
|
66124
|
+
accountId: session.accountId,
|
|
66125
|
+
workspaceId,
|
|
66126
|
+
sessionId,
|
|
66127
|
+
subjectId: initiatingHumanSubjectId,
|
|
66128
|
+
sourceTurnId: causalHumanTurnId,
|
|
66129
|
+
targetTurnId: internalTurn.id,
|
|
66130
|
+
});
|
|
66131
|
+
}
|
|
65814
66132
|
// The batch is now durable model memory. Every child it reports on has
|
|
65815
66133
|
// been carried to this turn's initiating human, so their read fence on
|
|
65816
66134
|
// those children advances with the same commit.
|
|
@@ -70157,6 +70475,13 @@ export async function applySessionTurnSettlement(
|
|
|
70157
70475
|
...request,
|
|
70158
70476
|
questions: request.questions.map((question) => {
|
|
70159
70477
|
const parsed = HumanInputQuestionContract.parse(question);
|
|
70478
|
+
if (parsed.skillReview) {
|
|
70479
|
+
const canonical = canonicalSkillReviewQuestion(question);
|
|
70480
|
+
if (!canonical || request.questions.length !== 1 || request.allowSkip) {
|
|
70481
|
+
throw new Error("Skill review requires the exact dedicated confirmation contract");
|
|
70482
|
+
}
|
|
70483
|
+
return canonical;
|
|
70484
|
+
}
|
|
70160
70485
|
return parsed.kind === "text" || parsed.allowOther
|
|
70161
70486
|
? parsed
|
|
70162
70487
|
: { ...parsed, allowOther: true };
|
|
@@ -70226,6 +70551,40 @@ export async function applySessionTurnSettlement(
|
|
|
70226
70551
|
);
|
|
70227
70552
|
if (humanInputRequests.length > 0) {
|
|
70228
70553
|
for (const request of humanInputRequests) {
|
|
70554
|
+
if (request.questions.length === 1 && request.questions[0]!.skillReview) {
|
|
70555
|
+
const [existing] = await tx
|
|
70556
|
+
.select()
|
|
70557
|
+
.from(schema.sessionHumanInputRequests)
|
|
70558
|
+
.where(
|
|
70559
|
+
and(
|
|
70560
|
+
eq(schema.sessionHumanInputRequests.accountId, session.accountId),
|
|
70561
|
+
eq(schema.sessionHumanInputRequests.workspaceId, workspaceId),
|
|
70562
|
+
eq(schema.sessionHumanInputRequests.sessionId, input.sessionId),
|
|
70563
|
+
eq(schema.sessionHumanInputRequests.turnId, input.turnId),
|
|
70564
|
+
eq(schema.sessionHumanInputRequests.toolCallId, request.toolCallId),
|
|
70565
|
+
),
|
|
70566
|
+
)
|
|
70567
|
+
.for("update");
|
|
70568
|
+
if (existing) {
|
|
70569
|
+
const canonical =
|
|
70570
|
+
existing.questions.length === 1
|
|
70571
|
+
? canonicalSkillReviewQuestion(existing.questions[0]!)
|
|
70572
|
+
: null;
|
|
70573
|
+
if (
|
|
70574
|
+
existing.id !== request.id ||
|
|
70575
|
+
!canonical ||
|
|
70576
|
+
stableJson(canonical) !== stableJson(request.questions[0])
|
|
70577
|
+
) {
|
|
70578
|
+
throw new Error(
|
|
70579
|
+
`Human-input request ${request.id} changed contract before re-freeze`,
|
|
70580
|
+
);
|
|
70581
|
+
}
|
|
70582
|
+
// Preserve immutable displayed/audited bytes. The unchanged
|
|
70583
|
+
// UPSERT predicate below still fences pending status, skip and
|
|
70584
|
+
// exact deadline; only generation ownership can advance.
|
|
70585
|
+
request.questions = existing.questions;
|
|
70586
|
+
}
|
|
70587
|
+
}
|
|
70229
70588
|
const [persistedRequest] = await tx
|
|
70230
70589
|
.insert(schema.sessionHumanInputRequests)
|
|
70231
70590
|
.values({
|
|
@@ -72293,6 +72652,15 @@ export async function getActiveSessionTurnForExecution(
|
|
|
72293
72652
|
)
|
|
72294
72653
|
.limit(1);
|
|
72295
72654
|
if (!row) return null;
|
|
72655
|
+
const linked = await getExternalLinkTurnAuthorization(
|
|
72656
|
+
scopedDb,
|
|
72657
|
+
{
|
|
72658
|
+
accountId: row.turn.accountId,
|
|
72659
|
+
workspaceId,
|
|
72660
|
+
},
|
|
72661
|
+
row.turn.id,
|
|
72662
|
+
);
|
|
72663
|
+
if (linked && !linked.authorized) return null;
|
|
72296
72664
|
await goalSnapshotForAcceptedTurnInTransaction(scopedDb, row.turn);
|
|
72297
72665
|
return mapSessionTurnForExecution(row.turn);
|
|
72298
72666
|
});
|
|
@@ -72305,50 +72673,10 @@ export async function getSessionTurnForAttempt(
|
|
|
72305
72673
|
attemptId: string,
|
|
72306
72674
|
): Promise<SessionTurnForExecution | null> {
|
|
72307
72675
|
return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
|
|
72308
|
-
const
|
|
72309
|
-
.select({ turn: schema.sessionTurns })
|
|
72310
|
-
.from(schema.sessionTurnAttempts)
|
|
72311
|
-
.innerJoin(
|
|
72312
|
-
schema.sessionTurns,
|
|
72313
|
-
and(
|
|
72314
|
-
eq(schema.sessionTurns.workspaceId, schema.sessionTurnAttempts.workspaceId),
|
|
72315
|
-
eq(schema.sessionTurns.id, schema.sessionTurnAttempts.turnId),
|
|
72316
|
-
),
|
|
72317
|
-
)
|
|
72318
|
-
.innerJoin(
|
|
72319
|
-
schema.sessions,
|
|
72320
|
-
and(
|
|
72321
|
-
eq(schema.sessions.workspaceId, schema.sessionTurnAttempts.workspaceId),
|
|
72322
|
-
eq(schema.sessions.id, schema.sessionTurnAttempts.sessionId),
|
|
72323
|
-
),
|
|
72324
|
-
)
|
|
72325
|
-
.where(
|
|
72326
|
-
and(
|
|
72327
|
-
eq(schema.sessionTurnAttempts.workspaceId, workspaceId),
|
|
72328
|
-
eq(schema.sessionTurnAttempts.sessionId, sessionId),
|
|
72329
|
-
eq(schema.sessionTurnAttempts.id, attemptId),
|
|
72330
|
-
inArray(schema.sessionTurnAttempts.state, ["claimed", "running"]),
|
|
72331
|
-
eq(schema.sessionTurns.activeAttemptId, attemptId),
|
|
72332
|
-
eq(schema.sessions.activeTurnId, schema.sessionTurns.id),
|
|
72333
|
-
inArray(schema.sessionTurns.status, [
|
|
72334
|
-
"running",
|
|
72335
|
-
"requires_action",
|
|
72336
|
-
"recovering",
|
|
72337
|
-
"waiting_capacity",
|
|
72338
|
-
]),
|
|
72339
|
-
sql`not exists (
|
|
72340
|
-
select 1
|
|
72341
|
-
from ${schema.sessionAttemptInterruptions} interruption
|
|
72342
|
-
where interruption.workspace_id = ${workspaceId}
|
|
72343
|
-
and interruption.attempt_id = ${attemptId}
|
|
72344
|
-
and interruption.state in ('pending', 'delivered', 'acknowledged')
|
|
72345
|
-
)`,
|
|
72346
|
-
),
|
|
72347
|
-
)
|
|
72348
|
-
.limit(1);
|
|
72676
|
+
const row = await getLiveSessionAttemptTurn(scopedDb, workspaceId, sessionId, attemptId);
|
|
72349
72677
|
if (!row) return null;
|
|
72350
|
-
await goalSnapshotForAcceptedTurnInTransaction(scopedDb, row
|
|
72351
|
-
return mapSessionTurnForExecution(row
|
|
72678
|
+
await goalSnapshotForAcceptedTurnInTransaction(scopedDb, row);
|
|
72679
|
+
return mapSessionTurnForExecution(row);
|
|
72352
72680
|
});
|
|
72353
72681
|
}
|
|
72354
72682
|
|
|
@@ -76755,7 +77083,7 @@ function mapSession(
|
|
|
76755
77083
|
instructions: row.instructions ?? null,
|
|
76756
77084
|
policyRole: row.policyRole ?? null,
|
|
76757
77085
|
agentAccess: sessionAgentAccessFromRow(row),
|
|
76758
|
-
|
|
77086
|
+
scopeSubjectId: sessionEndUserFromRow(row),
|
|
76759
77087
|
memoryScope: sessionMemoryScopeFromRow(row),
|
|
76760
77088
|
resources: row.resources as ResourceRef[],
|
|
76761
77089
|
skills: StoredSessionSkills.parse(row.skills ?? []),
|
|
@@ -77543,6 +77871,7 @@ function mapSocialConnection(
|
|
|
77543
77871
|
): SocialConnection {
|
|
77544
77872
|
return {
|
|
77545
77873
|
id: row.id,
|
|
77874
|
+
version: row.version,
|
|
77546
77875
|
accountId: row.accountId,
|
|
77547
77876
|
workspaceId: row.workspaceId,
|
|
77548
77877
|
provider: row.provider as SocialProvider,
|
|
@@ -77919,6 +78248,7 @@ export {
|
|
|
77919
78248
|
|
|
77920
78249
|
export {
|
|
77921
78250
|
buildHostConnectionTokenResolver,
|
|
78251
|
+
buildHostGatewayConnectionTokenResolver,
|
|
77922
78252
|
ConnectionRefreshHttpError,
|
|
77923
78253
|
HostMcpCredentialBindingError,
|
|
77924
78254
|
HostMcpCredentialScopeError,
|