@opengeni/db 3.2.0-canary.0 → 3.4.0-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-SIBYBA2D.js → chunk-6DPJA36C.js} +30 -11
- package/dist/chunk-6DPJA36C.js.map +1 -0
- package/dist/{chunk-KRLPEMLH.js → chunk-C2AFP7SB.js} +184 -6
- package/dist/chunk-C2AFP7SB.js.map +1 -0
- package/dist/{chunk-AZHW7R5V.js → chunk-H7MXYG7R.js} +12 -8
- package/dist/chunk-H7MXYG7R.js.map +1 -0
- package/dist/{chunk-2PRS4J5Q.js → chunk-HY3F5Q4E.js} +3 -3
- package/dist/{chunk-NAB6FKFN.js → chunk-O6FTELKH.js} +3 -3
- package/dist/{chunk-UZNXG44K.js → chunk-PYMY3AJW.js} +324 -8
- package/dist/chunk-PYMY3AJW.js.map +1 -0
- package/dist/{chunk-6HMM2R5T.js → chunk-TRBOWUG3.js} +98 -4
- package/dist/chunk-TRBOWUG3.js.map +1 -0
- package/dist/{chunk-E6CLMKEE.js → chunk-V3TRUXFJ.js} +2 -2
- package/dist/{chunk-2EH4WRHL.js → chunk-ZWRIPCTR.js} +2 -2
- package/dist/editable-artifact-durable-export.js +2 -2
- package/dist/editable-artifacts.js +3 -3
- package/dist/governed-learning-activation.d.ts +16 -0
- package/dist/index.d.ts +108 -7
- package/dist/index.js +3605 -1417
- package/dist/index.js.map +1 -1
- package/dist/insights-model-bundle.d.ts +30 -0
- package/dist/insights-usage-bundle.d.ts +32 -0
- package/dist/insights.d.ts +10 -2
- package/dist/lossless-json.d.ts +1 -1
- package/dist/managed-user-setup.d.ts +82 -0
- package/dist/memory-domain.d.ts +2 -2
- package/dist/migrate.d.ts +3 -1
- package/dist/migrate.js +1 -1
- package/dist/new-session-drafts.d.ts +4 -4
- package/dist/organization-membership-lifecycle.d.ts +60 -1
- package/dist/pr-review.d.ts +44 -3
- package/dist/provision-roles.js +1 -1
- package/dist/runtime-posture.d.ts +12 -7
- package/dist/schema.d.ts +839 -101
- package/dist/schema.js +9 -1
- package/dist/session-background-commands.js +3 -3
- package/dist/session-tenancy.d.ts +12 -1
- package/dist/session-tenancy.js +3 -3
- package/dist/video-generation.js +3 -3
- package/drizzle/0348_named_signup_and_user_setup.sql +838 -0
- package/drizzle/0349_greenfield_session_tenancy_activation.sql +792 -0
- package/drizzle/0350_organization_shared_workspace_administration.sql +889 -0
- package/drizzle/0351_organization_user_setup_delivery.sql +978 -0
- package/drizzle/0352_session_variable_set_attachments.sql +1661 -0
- package/drizzle/0353_automatic_session_title_policy_fence.sql +476 -0
- package/drizzle/0354_automatic_session_title_quarantine_index.sql +9 -0
- package/drizzle/0355_automatic_session_title_quarantine.sql +92 -0
- package/drizzle/0356_set_based_insights_session_visibility.sql +237 -0
- package/drizzle/0357_rig_platform_base_only.sql +44 -0
- package/drizzle/0358_pr_review_managed_github_app.sql +108 -0
- package/drizzle/0359_insights_force_rls_read_capability.sql +936 -0
- package/drizzle/0360_organization_identity_confirmation_prompt.sql +103 -0
- package/drizzle/0361_remember_knowledge_memory_materialization.sql +249 -0
- package/package.json +6 -6
- package/src/database.ts +12 -8
- package/src/governed-learning-activation.ts +74 -2
- package/src/index.ts +879 -92
- package/src/insights-model-bundle.ts +681 -0
- package/src/insights-usage-bundle.ts +240 -0
- package/src/insights.ts +265 -152
- package/src/lossless-json.ts +2 -1
- package/src/managed-user-setup.ts +316 -0
- package/src/memory-domain.ts +3 -3
- package/src/migrate.ts +38 -14
- package/src/new-session-drafts.ts +22 -15
- package/src/organization-membership-lifecycle.ts +253 -1
- package/src/pr-review.ts +449 -6
- package/src/provision-roles.ts +92 -0
- package/src/runtime-posture.ts +247 -4
- package/src/schema.ts +210 -4
- package/src/session-tenancy.ts +120 -2
- package/dist/chunk-6HMM2R5T.js.map +0 -1
- package/dist/chunk-AZHW7R5V.js.map +0 -1
- package/dist/chunk-KRLPEMLH.js.map +0 -1
- package/dist/chunk-SIBYBA2D.js.map +0 -1
- package/dist/chunk-UZNXG44K.js.map +0 -1
- /package/dist/{chunk-2PRS4J5Q.js.map → chunk-HY3F5Q4E.js.map} +0 -0
- /package/dist/{chunk-NAB6FKFN.js.map → chunk-O6FTELKH.js.map} +0 -0
- /package/dist/{chunk-E6CLMKEE.js.map → chunk-V3TRUXFJ.js.map} +0 -0
- /package/dist/{chunk-2EH4WRHL.js.map → chunk-ZWRIPCTR.js.map} +0 -0
package/src/schema.ts
CHANGED
|
@@ -34,6 +34,7 @@ import {
|
|
|
34
34
|
integer,
|
|
35
35
|
jsonb,
|
|
36
36
|
numeric,
|
|
37
|
+
pgSchema,
|
|
37
38
|
pgTable,
|
|
38
39
|
primaryKey,
|
|
39
40
|
text,
|
|
@@ -61,6 +62,8 @@ const bytea = customType<{ data: Uint8Array; driverData: Uint8Array }>({
|
|
|
61
62
|
},
|
|
62
63
|
});
|
|
63
64
|
|
|
65
|
+
const opengeniPrivateSchema = pgSchema("opengeni_private");
|
|
66
|
+
|
|
64
67
|
export type ConnectorActionPolicyDecision = "allow" | "ask" | "block";
|
|
65
68
|
|
|
66
69
|
export type ConnectorActionPolicySnapshotEntry = {
|
|
@@ -3786,6 +3789,10 @@ export const sessions = pgTable(
|
|
|
3786
3789
|
// write the result through the epoch-fenced setActiveSandbox CAS. Legacy
|
|
3787
3790
|
// rows may still be null/relative and are resolved at establishment.
|
|
3788
3791
|
workingDir: text("working_dir"),
|
|
3792
|
+
// Ordered low-to-high precedence source for public/session/runtime reads.
|
|
3793
|
+
// session_variable_set_attachments is the FK-backed lifecycle projection;
|
|
3794
|
+
// the session trigger maintains it and the legacy singular alias atomically.
|
|
3795
|
+
variableSetIds: jsonb("variable_set_ids").$type<string[]>().notNull().default([]),
|
|
3789
3796
|
variableSetId: uuid("variable_set_id").references(() => workspaceVariableSets.id, {
|
|
3790
3797
|
onDelete: "set null",
|
|
3791
3798
|
}),
|
|
@@ -4023,6 +4030,63 @@ export const sessions = pgTable(
|
|
|
4023
4030
|
}),
|
|
4024
4031
|
);
|
|
4025
4032
|
|
|
4033
|
+
export const sessionVariableSetAttachments = pgTable(
|
|
4034
|
+
"session_variable_set_attachments",
|
|
4035
|
+
{
|
|
4036
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
4037
|
+
accountId: uuid("account_id")
|
|
4038
|
+
.notNull()
|
|
4039
|
+
.references(() => managedAccounts.id, { onDelete: "cascade" }),
|
|
4040
|
+
workspaceId: uuid("workspace_id").notNull(),
|
|
4041
|
+
sessionId: uuid("session_id").notNull(),
|
|
4042
|
+
variableSetId: uuid("variable_set_id")
|
|
4043
|
+
.notNull()
|
|
4044
|
+
.references(() => workspaceVariableSets.id, { onDelete: "cascade" }),
|
|
4045
|
+
position: integer("position").notNull(),
|
|
4046
|
+
sessionStatus: text("session_status").notNull(),
|
|
4047
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
4048
|
+
},
|
|
4049
|
+
(table) => ({
|
|
4050
|
+
workspaceAccount: foreignKey({
|
|
4051
|
+
name: "session_variable_set_attachments_workspace_account_fk",
|
|
4052
|
+
columns: [table.workspaceId, table.accountId],
|
|
4053
|
+
foreignColumns: [workspaces.id, workspaces.accountId],
|
|
4054
|
+
}).onDelete("cascade"),
|
|
4055
|
+
session: foreignKey({
|
|
4056
|
+
name: "session_variable_set_attachments_session_fk",
|
|
4057
|
+
columns: [table.workspaceId, table.sessionId],
|
|
4058
|
+
foreignColumns: [sessions.workspaceId, sessions.id],
|
|
4059
|
+
}).onDelete("cascade"),
|
|
4060
|
+
sessionPosition: uniqueIndex("session_variable_set_attachments_session_position_uq").on(
|
|
4061
|
+
table.workspaceId,
|
|
4062
|
+
table.sessionId,
|
|
4063
|
+
table.position,
|
|
4064
|
+
),
|
|
4065
|
+
sessionVariableSet: uniqueIndex("session_variable_set_attachments_session_set_uq").on(
|
|
4066
|
+
table.workspaceId,
|
|
4067
|
+
table.sessionId,
|
|
4068
|
+
table.variableSetId,
|
|
4069
|
+
),
|
|
4070
|
+
variableSetSessions: index("session_variable_set_attachments_set_sessions_idx").on(
|
|
4071
|
+
table.variableSetId,
|
|
4072
|
+
table.sessionStatus,
|
|
4073
|
+
table.workspaceId,
|
|
4074
|
+
table.sessionId,
|
|
4075
|
+
),
|
|
4076
|
+
positionValid: check(
|
|
4077
|
+
"session_variable_set_attachments_position_check",
|
|
4078
|
+
sql`${table.position} >= 0 and ${table.position} < 25`,
|
|
4079
|
+
),
|
|
4080
|
+
statusValid: check(
|
|
4081
|
+
"session_variable_set_attachments_status_check",
|
|
4082
|
+
sql`${table.sessionStatus} in (
|
|
4083
|
+
'queued', 'running', 'idle', 'requires_action', 'recovering',
|
|
4084
|
+
'waiting_capacity', 'failed', 'cancelled'
|
|
4085
|
+
)`,
|
|
4086
|
+
),
|
|
4087
|
+
}),
|
|
4088
|
+
);
|
|
4089
|
+
|
|
4026
4090
|
export const organizationUserResourceGrants = pgTable(
|
|
4027
4091
|
"organization_user_resource_grants",
|
|
4028
4092
|
{
|
|
@@ -5997,7 +6061,7 @@ export const turnPersonalResourceAttachmentReceipts = pgTable(
|
|
|
5997
6061
|
sql`${table.sessionAuthorityEpoch} > 0
|
|
5998
6062
|
and octet_length(${table.initiatingHumanSubjectId}) between 1 and 512
|
|
5999
6063
|
and ${table.membershipAuthorizationRevision} > 0
|
|
6000
|
-
and ${table.resourceCount} between 1 and
|
|
6064
|
+
and ${table.resourceCount} between 1 and 52
|
|
6001
6065
|
and ${table.grantMode} in ('once', 'session', 'always')
|
|
6002
6066
|
and ${table.sessionVisibility} in ('user_private', 'workspace_shared')
|
|
6003
6067
|
and ${table.sharedOutputWarningVersion} = 1
|
|
@@ -6063,7 +6127,7 @@ export const turnPersonalResourceSnapshots = pgTable(
|
|
|
6063
6127
|
sql`${table.membershipAuthorizationRevision} > 0
|
|
6064
6128
|
and ${table.authorityGeneration} > 0
|
|
6065
6129
|
and ${table.grantGeneration} > 0
|
|
6066
|
-
and cardinality(${table.selectionSources}) between 1 and
|
|
6130
|
+
and cardinality(${table.selectionSources}) between 1 and 50`,
|
|
6067
6131
|
),
|
|
6068
6132
|
grantShape: check(
|
|
6069
6133
|
"turn_personal_resource_snapshots_grant_chk",
|
|
@@ -7823,6 +7887,50 @@ export const sessionEvents = pgTable(
|
|
|
7823
7887
|
}),
|
|
7824
7888
|
);
|
|
7825
7889
|
|
|
7890
|
+
/**
|
|
7891
|
+
* Durable fanout obligation for the migration-owned automatic-title
|
|
7892
|
+
* quarantine event. The deployment-wide workflow-wake dispatcher publishes
|
|
7893
|
+
* these exact committed events and marks them delivered; duplicate publication
|
|
7894
|
+
* after a process crash is safe because consumers sequence-fence every event.
|
|
7895
|
+
*/
|
|
7896
|
+
export const automaticSessionTitleFanoutOutboxV1 = opengeniPrivateSchema.table(
|
|
7897
|
+
"automatic_session_title_fanout_outbox_v1",
|
|
7898
|
+
{
|
|
7899
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
7900
|
+
accountId: uuid("account_id").notNull(),
|
|
7901
|
+
workspaceId: uuid("workspace_id").notNull(),
|
|
7902
|
+
sessionId: uuid("session_id").notNull(),
|
|
7903
|
+
eventId: uuid("event_id").notNull(),
|
|
7904
|
+
attempts: integer("attempts").notNull().default(0),
|
|
7905
|
+
lastError: text("last_error"),
|
|
7906
|
+
deliveredAt: timestamp("delivered_at", { withTimezone: true }),
|
|
7907
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
7908
|
+
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
|
7909
|
+
},
|
|
7910
|
+
(table) => ({
|
|
7911
|
+
attemptsValid: check("automatic_title_fanout_attempts_chk", sql`${table.attempts} >= 0`),
|
|
7912
|
+
workspaceAccount: foreignKey({
|
|
7913
|
+
name: "automatic_title_fanout_workspace_account_fk",
|
|
7914
|
+
columns: [table.workspaceId, table.accountId],
|
|
7915
|
+
foreignColumns: [workspaces.id, workspaces.accountId],
|
|
7916
|
+
}).onDelete("cascade"),
|
|
7917
|
+
workspaceSession: foreignKey({
|
|
7918
|
+
name: "automatic_title_fanout_workspace_session_fk",
|
|
7919
|
+
columns: [table.workspaceId, table.sessionId],
|
|
7920
|
+
foreignColumns: [sessions.workspaceId, sessions.id],
|
|
7921
|
+
}).onDelete("cascade"),
|
|
7922
|
+
workspaceEvent: foreignKey({
|
|
7923
|
+
name: "automatic_title_fanout_workspace_event_fk",
|
|
7924
|
+
columns: [table.workspaceId, table.eventId],
|
|
7925
|
+
foreignColumns: [sessionEvents.workspaceId, sessionEvents.id],
|
|
7926
|
+
}).onDelete("cascade"),
|
|
7927
|
+
event: uniqueIndex("automatic_title_fanout_event_uq").on(table.eventId),
|
|
7928
|
+
pending: index("automatic_title_fanout_pending_idx")
|
|
7929
|
+
.on(table.createdAt, table.id)
|
|
7930
|
+
.where(sql`${table.deliveredAt} is null`),
|
|
7931
|
+
}),
|
|
7932
|
+
);
|
|
7933
|
+
|
|
7826
7934
|
export const agentRunStates = pgTable("agent_run_states", {
|
|
7827
7935
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
7828
7936
|
accountId: uuid("account_id")
|
|
@@ -10329,6 +10437,14 @@ export const prReviewAppRegistrations = pgTable(
|
|
|
10329
10437
|
provider: text("provider").notNull(),
|
|
10330
10438
|
providerBaseUrl: text("provider_base_url").notNull(),
|
|
10331
10439
|
appId: text("app_id"),
|
|
10440
|
+
installationId: text("installation_id"),
|
|
10441
|
+
providerAccountLogin: text("provider_account_login"),
|
|
10442
|
+
providerAccountType: text("provider_account_type"),
|
|
10443
|
+
githubActorId: text("github_actor_id"),
|
|
10444
|
+
authorityKind: text("authority_kind"),
|
|
10445
|
+
authorityCheckedAt: timestamp("authority_checked_at", { withTimezone: true }),
|
|
10446
|
+
authorityExpiresAt: timestamp("authority_expires_at", { withTimezone: true }),
|
|
10447
|
+
authorityNonce: text("authority_nonce"),
|
|
10332
10448
|
credentialKind: text("credential_kind").notNull(),
|
|
10333
10449
|
credentialEncrypted: text("credential_encrypted"),
|
|
10334
10450
|
accessTokenExpiresAt: timestamp("access_token_expires_at", { withTimezone: true }),
|
|
@@ -10356,6 +10472,12 @@ export const prReviewAppRegistrations = pgTable(
|
|
|
10356
10472
|
table.workspaceId,
|
|
10357
10473
|
table.status,
|
|
10358
10474
|
),
|
|
10475
|
+
managedGithubInstallation: uniqueIndex("pr_review_managed_github_workspace_installation_uq")
|
|
10476
|
+
.on(table.workspaceId, table.installationId)
|
|
10477
|
+
.where(sql`${table.credentialKind} = 'managed_github_app'`),
|
|
10478
|
+
managedGithubAuthorityNonce: uniqueIndex("pr_review_managed_github_authority_nonce_uq")
|
|
10479
|
+
.on(table.authorityNonce)
|
|
10480
|
+
.where(sql`${table.authorityNonce} is not null`),
|
|
10359
10481
|
providerCheck: check(
|
|
10360
10482
|
"pr_review_app_registrations_provider_chk",
|
|
10361
10483
|
sql`${table.provider} in ('github', 'gitlab', 'azure_devops')`,
|
|
@@ -10363,9 +10485,11 @@ export const prReviewAppRegistrations = pgTable(
|
|
|
10363
10485
|
credentialCheck: check(
|
|
10364
10486
|
"pr_review_app_registrations_credential_chk",
|
|
10365
10487
|
sql`(
|
|
10366
|
-
(${table.provider} = 'github' and ${table.credentialKind} = 'github_app' and ${table.credentialEncrypted} is not null and ${table.appId} is not null)
|
|
10488
|
+
(${table.provider} = 'github' and ${table.credentialKind} = 'github_app' and ${table.credentialEncrypted} is not null and ${table.appId} is not null and ${table.installationId} is null)
|
|
10489
|
+
or
|
|
10490
|
+
(${table.provider} = 'github' and ${table.credentialKind} = 'managed_github_app' and ${table.credentialEncrypted} is null and ${table.appId} is not null and ${table.installationId} is not null and ${table.providerAccountType} in ('User', 'Organization') and ${table.githubActorId} ~ '^[1-9][0-9]*$' and ${table.authorityKind} in ('personal_owner', 'organization_owner') and ${table.authorityCheckedAt} is not null and ${table.authorityExpiresAt} is not null and ${table.authorityExpiresAt} > ${table.authorityCheckedAt} and octet_length(${table.authorityNonce}) between 16 and 256)
|
|
10367
10491
|
or
|
|
10368
|
-
(${table.provider} in ('gitlab', 'azure_devops') and ${table.credentialKind} = 'provider_token' and ${table.credentialEncrypted} is not null)
|
|
10492
|
+
(${table.provider} in ('gitlab', 'azure_devops') and ${table.credentialKind} = 'provider_token' and ${table.credentialEncrypted} is not null and ${table.installationId} is null)
|
|
10369
10493
|
)`,
|
|
10370
10494
|
),
|
|
10371
10495
|
webhookAuthCheck: check(
|
|
@@ -10455,6 +10579,86 @@ export const prReviewRepositoryBindings = pgTable(
|
|
|
10455
10579
|
}),
|
|
10456
10580
|
);
|
|
10457
10581
|
|
|
10582
|
+
/** Append-only successful OAuth-state consumption receipts. The latest receipt
|
|
10583
|
+
* is also projected on the registration, but this ledger owns durable replay
|
|
10584
|
+
* rejection across later reconnects. */
|
|
10585
|
+
export const prReviewManagedGithubAuthorityNonces = pgTable(
|
|
10586
|
+
"pr_review_managed_github_authority_nonces",
|
|
10587
|
+
{
|
|
10588
|
+
authorityNonce: text("authority_nonce").primaryKey(),
|
|
10589
|
+
accountId: uuid("account_id")
|
|
10590
|
+
.notNull()
|
|
10591
|
+
.references(() => managedAccounts.id, { onDelete: "cascade" }),
|
|
10592
|
+
workspaceId: uuid("workspace_id")
|
|
10593
|
+
.notNull()
|
|
10594
|
+
.references(() => workspaces.id, { onDelete: "cascade" }),
|
|
10595
|
+
installationId: text("installation_id").notNull(),
|
|
10596
|
+
authorityExpiresAt: timestamp("authority_expires_at", { withTimezone: true }).notNull(),
|
|
10597
|
+
consumedAt: timestamp("consumed_at", { withTimezone: true }).notNull().defaultNow(),
|
|
10598
|
+
},
|
|
10599
|
+
(table) => ({
|
|
10600
|
+
identityCheck: check(
|
|
10601
|
+
"pr_review_managed_github_authority_nonces_identity_chk",
|
|
10602
|
+
sql`octet_length(${table.authorityNonce}) between 16 and 256
|
|
10603
|
+
and ${table.installationId} ~ '^[1-9][0-9]*$'
|
|
10604
|
+
and ${table.authorityExpiresAt} > ${table.consumedAt}`,
|
|
10605
|
+
),
|
|
10606
|
+
workspaceAccount: foreignKey({
|
|
10607
|
+
columns: [table.workspaceId, table.accountId],
|
|
10608
|
+
foreignColumns: [workspaces.id, workspaces.accountId],
|
|
10609
|
+
name: "pr_review_managed_github_authority_nonces_workspace_account_fk",
|
|
10610
|
+
}).onDelete("cascade"),
|
|
10611
|
+
}),
|
|
10612
|
+
);
|
|
10613
|
+
|
|
10614
|
+
/** Credential-free routing for the deployment-owned PR-review GitHub App.
|
|
10615
|
+
* Signature verification happens before provider ids reach this table. */
|
|
10616
|
+
export const prReviewManagedGithubRoutes = pgTable(
|
|
10617
|
+
"pr_review_managed_github_routes",
|
|
10618
|
+
{
|
|
10619
|
+
bindingId: uuid("binding_id")
|
|
10620
|
+
.primaryKey()
|
|
10621
|
+
.references(() => prReviewRepositoryBindings.id, { onDelete: "cascade" }),
|
|
10622
|
+
accountId: uuid("account_id")
|
|
10623
|
+
.notNull()
|
|
10624
|
+
.references(() => managedAccounts.id, { onDelete: "cascade" }),
|
|
10625
|
+
workspaceId: uuid("workspace_id")
|
|
10626
|
+
.notNull()
|
|
10627
|
+
.references(() => workspaces.id, { onDelete: "cascade" }),
|
|
10628
|
+
registrationId: uuid("registration_id").notNull(),
|
|
10629
|
+
sourceId: uuid("source_id").notNull(),
|
|
10630
|
+
installationId: text("installation_id").notNull(),
|
|
10631
|
+
providerRepositoryId: text("provider_repository_id").notNull(),
|
|
10632
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
10633
|
+
},
|
|
10634
|
+
(table) => ({
|
|
10635
|
+
routeIdentity: uniqueIndex("pr_review_managed_github_route_identity_uq").on(
|
|
10636
|
+
table.installationId,
|
|
10637
|
+
table.providerRepositoryId,
|
|
10638
|
+
),
|
|
10639
|
+
identityCheck: check(
|
|
10640
|
+
"pr_review_managed_github_routes_identity_chk",
|
|
10641
|
+
sql`${table.installationId} ~ '^[1-9][0-9]*$'
|
|
10642
|
+
and ${table.providerRepositoryId} ~ '^[1-9][0-9]*$'`,
|
|
10643
|
+
),
|
|
10644
|
+
workspaceAccount: foreignKey({
|
|
10645
|
+
columns: [table.workspaceId, table.accountId],
|
|
10646
|
+
foreignColumns: [workspaces.id, workspaces.accountId],
|
|
10647
|
+
name: "pr_review_managed_github_routes_workspace_account_fk",
|
|
10648
|
+
}).onDelete("cascade"),
|
|
10649
|
+
registration: foreignKey({
|
|
10650
|
+
columns: [table.workspaceId, table.registrationId],
|
|
10651
|
+
foreignColumns: [prReviewAppRegistrations.workspaceId, prReviewAppRegistrations.id],
|
|
10652
|
+
name: "pr_review_managed_github_routes_registration_fk",
|
|
10653
|
+
}).onDelete("cascade"),
|
|
10654
|
+
source: foreignKey({
|
|
10655
|
+
columns: [table.workspaceId, table.sourceId],
|
|
10656
|
+
foreignColumns: [automationSources.workspaceId, automationSources.id],
|
|
10657
|
+
name: "pr_review_managed_github_routes_source_fk",
|
|
10658
|
+
}).onDelete("cascade"),
|
|
10659
|
+
}),
|
|
10660
|
+
);
|
|
10661
|
+
|
|
10458
10662
|
export const usageEvents = pgTable(
|
|
10459
10663
|
"usage_events",
|
|
10460
10664
|
{
|
|
@@ -12189,6 +12393,8 @@ export const rigVersions = pgTable(
|
|
|
12189
12393
|
.notNull()
|
|
12190
12394
|
.references(() => rigs.id, { onDelete: "cascade" }),
|
|
12191
12395
|
version: integer("version").notNull(),
|
|
12396
|
+
// Legacy audit field only. Migration 0356 rejects new non-null values;
|
|
12397
|
+
// runtime always uses the deployment platform sandbox image.
|
|
12192
12398
|
image: text("image"),
|
|
12193
12399
|
setupScript: text("setup_script"),
|
|
12194
12400
|
// Self-declared health checks: [{ name, command }].
|
package/src/session-tenancy.ts
CHANGED
|
@@ -121,6 +121,17 @@ export type ForkSessionContentInput = {
|
|
|
121
121
|
destinationVisibility: SessionTenancyVisibility;
|
|
122
122
|
workspaceSharedAcknowledged: boolean;
|
|
123
123
|
operationKey: string;
|
|
124
|
+
runtimeRequest?: ForkSessionRuntimeRequest;
|
|
125
|
+
runtimeConfiguration?: ForkSessionRuntimeConfiguration;
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
export type ForkSessionRuntimeRequest = {
|
|
129
|
+
variableSetIds: string[];
|
|
130
|
+
rigId: string | null;
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
export type ForkSessionRuntimeConfiguration = ForkSessionRuntimeRequest & {
|
|
134
|
+
rigVersionId: string | null;
|
|
124
135
|
};
|
|
125
136
|
|
|
126
137
|
export type ForkSessionContentResult = {
|
|
@@ -133,6 +144,8 @@ export type ForkSessionContentResult = {
|
|
|
133
144
|
authorityEpoch: number;
|
|
134
145
|
copiedHistoryItemCount: number;
|
|
135
146
|
replay: boolean;
|
|
147
|
+
runtimeEventId: string | null;
|
|
148
|
+
runtimeEventSequence: number | null;
|
|
136
149
|
};
|
|
137
150
|
|
|
138
151
|
export async function sessionTenancyProductActivated(
|
|
@@ -332,8 +345,23 @@ export function canonicalSessionForkHash(
|
|
|
332
345
|
| "destinationWorkspaceId"
|
|
333
346
|
| "destinationVisibility"
|
|
334
347
|
| "workspaceSharedAcknowledged"
|
|
348
|
+
| "runtimeRequest"
|
|
335
349
|
>,
|
|
336
350
|
): string {
|
|
351
|
+
if (input.runtimeRequest) {
|
|
352
|
+
return createHash("sha256")
|
|
353
|
+
.update(
|
|
354
|
+
JSON.stringify({
|
|
355
|
+
version: 3,
|
|
356
|
+
sourceSessionId: input.sourceSessionId,
|
|
357
|
+
destinationWorkspaceId: input.destinationWorkspaceId,
|
|
358
|
+
destinationVisibility: input.destinationVisibility,
|
|
359
|
+
workspaceSharedAcknowledged: input.workspaceSharedAcknowledged,
|
|
360
|
+
runtime: input.runtimeRequest,
|
|
361
|
+
}),
|
|
362
|
+
)
|
|
363
|
+
.digest("hex");
|
|
364
|
+
}
|
|
337
365
|
const legacyCompatiblePrivateRequest =
|
|
338
366
|
input.destinationVisibility === "user_private" && !input.workspaceSharedAcknowledged;
|
|
339
367
|
return createHash("sha256")
|
|
@@ -358,6 +386,12 @@ export function canonicalSessionForkHash(
|
|
|
358
386
|
.digest("hex");
|
|
359
387
|
}
|
|
360
388
|
|
|
389
|
+
function canonicalForkRuntimeConfigurationDigest(input: ForkSessionRuntimeRequest): string {
|
|
390
|
+
return createHash("sha256")
|
|
391
|
+
.update(JSON.stringify({ version: 1, ...input }))
|
|
392
|
+
.digest("hex");
|
|
393
|
+
}
|
|
394
|
+
|
|
361
395
|
export async function transitionSessionVisibility(
|
|
362
396
|
db: Database,
|
|
363
397
|
input: TransitionSessionVisibilityInput,
|
|
@@ -437,6 +471,17 @@ export async function forkSessionContent(
|
|
|
437
471
|
if (input.destinationWorkspaceId !== input.sourceWorkspaceId) {
|
|
438
472
|
throw new Error("The first session fork contract is same-workspace only");
|
|
439
473
|
}
|
|
474
|
+
if (Boolean(input.runtimeRequest) !== Boolean(input.runtimeConfiguration)) {
|
|
475
|
+
throw new Error("A fresh session fork runtime request requires its resolved configuration");
|
|
476
|
+
}
|
|
477
|
+
if (
|
|
478
|
+
input.runtimeRequest &&
|
|
479
|
+
(input.runtimeRequest.rigId !== input.runtimeConfiguration?.rigId ||
|
|
480
|
+
JSON.stringify(input.runtimeRequest.variableSetIds) !==
|
|
481
|
+
JSON.stringify(input.runtimeConfiguration?.variableSetIds))
|
|
482
|
+
) {
|
|
483
|
+
throw new Error("Resolved session fork runtime configuration does not match its request");
|
|
484
|
+
}
|
|
440
485
|
const { accountId } = await rlsContextForWorkspace(db, input.sourceWorkspaceId);
|
|
441
486
|
await assertSessionTenancyProductActivated(db, input.sourceWorkspaceId);
|
|
442
487
|
const requestHash = canonicalSessionForkHash(input);
|
|
@@ -446,6 +491,43 @@ export async function forkSessionContent(
|
|
|
446
491
|
input.sourceWorkspaceId,
|
|
447
492
|
input.actorSubjectId,
|
|
448
493
|
async (scopedDb) => {
|
|
494
|
+
if (input.runtimeRequest && input.runtimeConfiguration) {
|
|
495
|
+
const runtimeDigest = canonicalForkRuntimeConfigurationDigest(input.runtimeRequest);
|
|
496
|
+
const rows = await rawRows<ForkSessionContentResult>(
|
|
497
|
+
scopedDb,
|
|
498
|
+
sql`select
|
|
499
|
+
operation_id as "operationId",
|
|
500
|
+
event_id as "eventId",
|
|
501
|
+
event_sequence as "eventSequence",
|
|
502
|
+
session_id as "sessionId",
|
|
503
|
+
workspace_id as "workspaceId",
|
|
504
|
+
visibility,
|
|
505
|
+
authority_epoch as "authorityEpoch",
|
|
506
|
+
copied_history_item_count as "copiedHistoryItemCount",
|
|
507
|
+
replay,
|
|
508
|
+
runtime_event_id as "runtimeEventId",
|
|
509
|
+
runtime_event_sequence as "runtimeEventSequence"
|
|
510
|
+
from fork_session_content_with_runtime(
|
|
511
|
+
${accountId}::uuid,
|
|
512
|
+
${input.sourceWorkspaceId}::uuid,
|
|
513
|
+
${input.sourceSessionId}::uuid,
|
|
514
|
+
${input.actorSubjectId},
|
|
515
|
+
${input.destinationWorkspaceId}::uuid,
|
|
516
|
+
${input.destinationVisibility},
|
|
517
|
+
${input.workspaceSharedAcknowledged},
|
|
518
|
+
${input.operationKey},
|
|
519
|
+
${requestHash},
|
|
520
|
+
${SESSION_TENANCY_ACTIVATION_VERSION},
|
|
521
|
+
${JSON.stringify(input.runtimeConfiguration.variableSetIds)}::jsonb,
|
|
522
|
+
${input.runtimeConfiguration.rigId}::uuid,
|
|
523
|
+
${input.runtimeConfiguration.rigVersionId}::uuid,
|
|
524
|
+
${runtimeDigest}
|
|
525
|
+
)`,
|
|
526
|
+
);
|
|
527
|
+
const result = rows[0];
|
|
528
|
+
if (!result) throw new Error("Session fork returned no result");
|
|
529
|
+
return result;
|
|
530
|
+
}
|
|
449
531
|
const rows = await rawRows<ForkSessionContentResult>(
|
|
450
532
|
scopedDb,
|
|
451
533
|
sql`select
|
|
@@ -457,7 +539,9 @@ export async function forkSessionContent(
|
|
|
457
539
|
visibility,
|
|
458
540
|
authority_epoch as "authorityEpoch",
|
|
459
541
|
copied_history_item_count as "copiedHistoryItemCount",
|
|
460
|
-
replay
|
|
542
|
+
replay,
|
|
543
|
+
null::uuid as "runtimeEventId",
|
|
544
|
+
null::integer as "runtimeEventSequence"
|
|
461
545
|
from fork_session_content(
|
|
462
546
|
${accountId}::uuid,
|
|
463
547
|
${input.sourceWorkspaceId}::uuid,
|
|
@@ -515,6 +599,38 @@ export async function replayAppliedSessionFork(
|
|
|
515
599
|
input.sourceWorkspaceId,
|
|
516
600
|
input.actorSubjectId,
|
|
517
601
|
async (scopedDb) => {
|
|
602
|
+
if (input.runtimeRequest) {
|
|
603
|
+
const runtimeDigest = canonicalForkRuntimeConfigurationDigest(input.runtimeRequest);
|
|
604
|
+
const rows = await rawRows<ForkSessionContentResult>(
|
|
605
|
+
scopedDb,
|
|
606
|
+
sql`select
|
|
607
|
+
operation_id as "operationId",
|
|
608
|
+
event_id as "eventId",
|
|
609
|
+
event_sequence as "eventSequence",
|
|
610
|
+
session_id as "sessionId",
|
|
611
|
+
workspace_id as "workspaceId",
|
|
612
|
+
visibility,
|
|
613
|
+
authority_epoch as "authorityEpoch",
|
|
614
|
+
copied_history_item_count as "copiedHistoryItemCount",
|
|
615
|
+
replay,
|
|
616
|
+
runtime_event_id as "runtimeEventId",
|
|
617
|
+
runtime_event_sequence as "runtimeEventSequence"
|
|
618
|
+
from replay_applied_session_fork_with_runtime(
|
|
619
|
+
${accountId}::uuid,
|
|
620
|
+
${input.sourceWorkspaceId}::uuid,
|
|
621
|
+
${input.sourceSessionId}::uuid,
|
|
622
|
+
${input.actorSubjectId},
|
|
623
|
+
${input.destinationWorkspaceId}::uuid,
|
|
624
|
+
${input.destinationVisibility},
|
|
625
|
+
${input.workspaceSharedAcknowledged},
|
|
626
|
+
${input.operationKey},
|
|
627
|
+
${requestHash},
|
|
628
|
+
${SESSION_TENANCY_ACTIVATION_VERSION},
|
|
629
|
+
${runtimeDigest}
|
|
630
|
+
)`,
|
|
631
|
+
);
|
|
632
|
+
return rows[0] ?? null;
|
|
633
|
+
}
|
|
518
634
|
const rows = await rawRows<ForkSessionContentResult>(
|
|
519
635
|
scopedDb,
|
|
520
636
|
sql`select
|
|
@@ -526,7 +642,9 @@ export async function replayAppliedSessionFork(
|
|
|
526
642
|
visibility,
|
|
527
643
|
authority_epoch as "authorityEpoch",
|
|
528
644
|
copied_history_item_count as "copiedHistoryItemCount",
|
|
529
|
-
replay
|
|
645
|
+
replay,
|
|
646
|
+
null::uuid as "runtimeEventId",
|
|
647
|
+
null::integer as "runtimeEventSequence"
|
|
530
648
|
from replay_applied_session_fork(
|
|
531
649
|
${accountId}::uuid,
|
|
532
650
|
${input.sourceWorkspaceId}::uuid,
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/session-tenancy.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport { sql } from \"drizzle-orm\";\nimport {\n SessionTenancyBlocker as SessionTenancyBlockerSchema,\n type SessionTenancyBlocker,\n} from \"@opengeni/contracts\";\nimport {\n rawRows,\n type Database,\n rlsContextForWorkspace,\n withWorkspaceRls,\n withWorkspaceSubjectRls,\n withWorkspaceSubjectSessionActivityRls,\n} from \"./database\";\nimport { nestedPostgresSqlState } from \"./persistence-errors\";\n\nconst SESSION_TENANCY_ACTIVATION_VERSION = 1;\n\nexport class SessionTenancyConflictError extends Error {\n readonly name = \"SessionTenancyConflictError\";\n constructor(\n readonly reason: \"not_quiescent\" | \"authority_epoch\" | \"operation_reuse\",\n readonly blocker: SessionTenancyBlocker | null = null,\n options?: ErrorOptions,\n ) {\n super(\n reason === \"not_quiescent\"\n ? \"Session must be fully quiescent before its tenancy can change\"\n : reason === \"authority_epoch\"\n ? \"Session authority changed before the operation committed\"\n : \"Session tenancy operation key was reused with different input\",\n options,\n );\n }\n}\n\nexport class SessionTenancyNotActivatedError extends Error {\n readonly name = \"SessionTenancyNotActivatedError\";\n constructor(options?: ErrorOptions) {\n super(\"Session tenancy product surface is not activated for this organization\", options);\n }\n}\n\nexport class SessionTenancyAccessError extends Error {\n readonly name = \"SessionTenancyAccessError\";\n constructor(options?: ErrorOptions) {\n super(\"Session tenancy target is unavailable\", options);\n }\n}\n\nexport class SessionTenancyInvalidRequestError extends Error {\n readonly name = \"SessionTenancyInvalidRequestError\";\n constructor(options?: ErrorOptions) {\n super(\"Session tenancy request is invalid\", options);\n }\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return Boolean(value && typeof value === \"object\");\n}\n\n/** Extract only the fixed quiescence DETAIL vocabulary, never arbitrary driver detail. */\nexport function nestedSessionTenancyBlocker(error: unknown): SessionTenancyBlocker | null {\n const queue: unknown[] = [error];\n const seen = new Set<unknown>();\n while (queue.length > 0 && seen.size < 64) {\n const current = queue.shift();\n if (!isRecord(current) || seen.has(current)) continue;\n seen.add(current);\n for (const key of [\"detail\", \"detailMessage\"] as const) {\n const parsed = SessionTenancyBlockerSchema.safeParse(current[key]);\n if (parsed.success) return parsed.data;\n }\n for (const key of [\"cause\", \"original\", \"driverError\", \"error\", \"errors\"] as const) {\n const nested = current[key];\n if (Array.isArray(nested)) queue.push(...nested);\n else if (nested !== undefined) queue.push(nested);\n }\n }\n return null;\n}\n\nexport type SessionTenancyVisibility = \"user_private\" | \"workspace_shared\";\n\nexport type PrivateSessionCreatePolicy = {\n personalWorkspace: boolean;\n platformAvailable: boolean;\n organizationEnabled: boolean;\n};\n\nexport type TransitionSessionVisibilityInput = {\n workspaceId: string;\n sessionId: string;\n actorSubjectId: string;\n targetVisibility: SessionTenancyVisibility;\n expectedAuthorityEpoch: number;\n operationKey: string;\n};\n\nexport type TransitionSessionVisibilityResult = {\n operationId: string;\n eventId: string | null;\n eventSequence: number | null;\n visibility: SessionTenancyVisibility;\n authorityEpoch: number;\n ownerOrganizationMembershipId: string | null;\n changed: boolean;\n replay: boolean;\n interruptedAttemptCount: number;\n cancelledTurnCount: number;\n cancelledUpdateCount: number;\n pausedGoalCount: number;\n revokedGrantCount: number;\n};\n\nexport type ForkSessionContentInput = {\n sourceWorkspaceId: string;\n sourceSessionId: string;\n actorSubjectId: string;\n destinationWorkspaceId: string;\n destinationVisibility: SessionTenancyVisibility;\n workspaceSharedAcknowledged: boolean;\n operationKey: string;\n};\n\nexport type ForkSessionContentResult = {\n operationId: string;\n eventId: string;\n eventSequence: number;\n sessionId: string;\n workspaceId: string;\n visibility: SessionTenancyVisibility;\n authorityEpoch: number;\n copiedHistoryItemCount: number;\n replay: boolean;\n};\n\nexport async function sessionTenancyProductActivated(\n db: Database,\n workspaceId: string,\n): Promise<boolean> {\n const { accountId } = await rlsContextForWorkspace(db, workspaceId);\n return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {\n const rows = await rawRows<{ activated: boolean }>(\n scopedDb,\n sql`select session_tenancy_product_activated(\n ${accountId}::uuid,\n ${SESSION_TENANCY_ACTIVATION_VERSION}\n ) as activated`,\n );\n return rows[0]?.activated === true;\n });\n}\n\nexport async function getPrivateSessionCreatePolicy(\n db: Database,\n input: { workspaceId: string; actorSubjectId: string },\n): Promise<PrivateSessionCreatePolicy> {\n const { accountId } = await rlsContextForWorkspace(db, input.workspaceId);\n return await withWorkspaceSubjectRls(\n db,\n input.workspaceId,\n input.actorSubjectId,\n async (scopedDb) => {\n const rows = await rawRows<{\n personalWorkspace: boolean;\n platformAvailable: boolean;\n organizationEnabled: boolean;\n }>(\n scopedDb,\n sql`select\n personal_workspace as \"personalWorkspace\",\n platform_available as \"platformAvailable\",\n organization_enabled as \"organizationEnabled\"\n from get_private_session_create_policy(\n ${accountId}::uuid,\n ${input.workspaceId}::uuid,\n ${input.actorSubjectId}\n )`,\n );\n const policy = rows[0];\n if (!policy) throw new SessionTenancyAccessError();\n return policy;\n },\n );\n}\n\n/** Open the exact transaction-local trigger capability used by atomic private create. */\nexport async function openPrivateSessionCreateCapability(\n db: Database,\n input: { accountId: string; workspaceId: string; sessionId: string; actorSubjectId: string },\n): Promise<{ capabilityId: string; ownerMembershipId: string }> {\n let rows: Array<{ capabilityId: string; ownerMembershipId: string }>;\n try {\n rows = await rawRows<{ capabilityId: string; ownerMembershipId: string }>(\n db,\n sql`select\n capability_id as \"capabilityId\",\n owner_membership_id as \"ownerMembershipId\"\n from open_private_session_create_capability(\n ${input.accountId}::uuid,\n ${input.workspaceId}::uuid,\n ${input.sessionId}::uuid,\n ${input.actorSubjectId}\n )`,\n );\n } catch (error) {\n // The definer raises 55000 only when the organization owner/admin setting\n // does not permit a new private session in this shared workspace; every\n // authority failure remains 42501 and propagates unchanged.\n if (nestedPostgresSqlState(error) === \"55000\") {\n throw new SessionTenancyNotActivatedError({ cause: error });\n }\n throw error;\n }\n const capabilityId = rows[0]?.capabilityId;\n const ownerMembershipId = rows[0]?.ownerMembershipId;\n if (!capabilityId || !ownerMembershipId) {\n throw new Error(\"Private session create capability was not returned\");\n }\n return { capabilityId, ownerMembershipId };\n}\n\n/** Open the exact transaction-local capability for one private child insert. */\nexport async function openPrivateChildSessionCreateCapability(\n db: Database,\n input: {\n accountId: string;\n workspaceId: string;\n sessionId: string;\n parentSessionId: string;\n actorTurnId: string;\n actorAttemptId: string;\n actorExecutionGeneration: number;\n },\n): Promise<{ capabilityId: string; ownerMembershipId: string; ownerSubjectId: string }> {\n const rows = await rawRows<{\n capabilityId: string;\n ownerMembershipId: string;\n ownerSubjectId: string;\n }>(\n db,\n sql`select\n capability_id as \"capabilityId\",\n owner_membership_id as \"ownerMembershipId\",\n owner_subject_id as \"ownerSubjectId\"\n from open_private_child_session_create_capability(\n ${input.accountId}::uuid,\n ${input.workspaceId}::uuid,\n ${input.sessionId}::uuid,\n ${input.parentSessionId}::uuid,\n ${input.actorTurnId}::uuid,\n ${input.actorAttemptId}::uuid,\n ${input.actorExecutionGeneration}::integer\n )`,\n );\n const capabilityId = rows[0]?.capabilityId;\n const ownerMembershipId = rows[0]?.ownerMembershipId;\n const ownerSubjectId = rows[0]?.ownerSubjectId;\n if (!capabilityId || !ownerMembershipId || !ownerSubjectId) {\n throw new Error(\"Private child session create capability was not returned\");\n }\n return { capabilityId, ownerMembershipId, ownerSubjectId };\n}\n\nexport async function closePrivateSessionCreateCapability(\n db: Database,\n capabilityId: string,\n): Promise<void> {\n await db.execute(sql`select close_private_session_create_capability(${capabilityId}::uuid)`);\n}\n\nasync function assertSessionTenancyProductActivated(\n db: Database,\n workspaceId: string,\n): Promise<void> {\n if (!(await sessionTenancyProductActivated(db, workspaceId))) {\n throw new SessionTenancyNotActivatedError();\n }\n}\n\nfunction mapSessionTenancyPersistenceError(\n error: unknown,\n options: { authorityEpochConflict: boolean },\n): never {\n const state = nestedPostgresSqlState(error);\n if (state === \"55P03\") {\n throw new SessionTenancyConflictError(\"not_quiescent\", nestedSessionTenancyBlocker(error), {\n cause: error,\n });\n }\n if (state === \"40001\" && options.authorityEpochConflict) {\n throw new SessionTenancyConflictError(\"authority_epoch\", null, { cause: error });\n }\n if (state === \"23505\") {\n throw new SessionTenancyConflictError(\"operation_reuse\", null, { cause: error });\n }\n if (state === \"55000\") {\n throw new SessionTenancyNotActivatedError({ cause: error });\n }\n if (state === \"42501\" || state === \"P0002\") {\n throw new SessionTenancyAccessError({ cause: error });\n }\n if (state === \"22023\") {\n throw new SessionTenancyInvalidRequestError({ cause: error });\n }\n throw error;\n}\n\nexport function canonicalSessionVisibilityTransitionHash(\n input: Pick<\n TransitionSessionVisibilityInput,\n \"sessionId\" | \"targetVisibility\" | \"expectedAuthorityEpoch\"\n >,\n): string {\n return createHash(\"sha256\")\n .update(\n JSON.stringify({\n version: 1,\n sessionId: input.sessionId,\n targetVisibility: input.targetVisibility,\n expectedAuthorityEpoch: input.expectedAuthorityEpoch,\n }),\n )\n .digest(\"hex\");\n}\n\nexport function canonicalSessionForkHash(\n input: Pick<\n ForkSessionContentInput,\n | \"sourceSessionId\"\n | \"destinationWorkspaceId\"\n | \"destinationVisibility\"\n | \"workspaceSharedAcknowledged\"\n >,\n): string {\n const legacyCompatiblePrivateRequest =\n input.destinationVisibility === \"user_private\" && !input.workspaceSharedAcknowledged;\n return createHash(\"sha256\")\n .update(\n JSON.stringify(\n legacyCompatiblePrivateRequest\n ? {\n version: 1,\n sourceSessionId: input.sourceSessionId,\n destinationWorkspaceId: input.destinationWorkspaceId,\n destinationVisibility: input.destinationVisibility,\n }\n : {\n version: 2,\n sourceSessionId: input.sourceSessionId,\n destinationWorkspaceId: input.destinationWorkspaceId,\n destinationVisibility: input.destinationVisibility,\n workspaceSharedAcknowledged: input.workspaceSharedAcknowledged,\n },\n ),\n )\n .digest(\"hex\");\n}\n\nexport async function transitionSessionVisibility(\n db: Database,\n input: TransitionSessionVisibilityInput,\n): Promise<TransitionSessionVisibilityResult> {\n if (!Number.isSafeInteger(input.expectedAuthorityEpoch) || input.expectedAuthorityEpoch < 1) {\n throw new Error(\"expectedAuthorityEpoch must be a positive safe integer\");\n }\n if (!input.operationKey.trim()) throw new Error(\"operationKey must not be empty\");\n const requestHash = canonicalSessionVisibilityTransitionHash(input);\n const { accountId } = await rlsContextForWorkspace(db, input.workspaceId);\n await assertSessionTenancyProductActivated(db, input.workspaceId);\n try {\n return await withWorkspaceSubjectSessionActivityRls(\n db,\n input.workspaceId,\n input.actorSubjectId,\n async (scopedDb) => {\n const rows = await rawRows<{\n operationId: string;\n eventId: string | null;\n eventSequence: number | null;\n visibility: SessionTenancyVisibility;\n authorityEpoch: number;\n ownerOrganizationMembershipId: string | null;\n changed: boolean;\n replay: boolean;\n interruptedAttemptCount: number;\n cancelledTurnCount: number;\n cancelledUpdateCount: number;\n pausedGoalCount: number;\n revokedGrantCount: number;\n }>(\n scopedDb,\n sql`select\n operation_id as \"operationId\",\n event_id as \"eventId\",\n event_sequence as \"eventSequence\",\n visibility,\n authority_epoch as \"authorityEpoch\",\n owner_organization_membership_id as \"ownerOrganizationMembershipId\",\n changed,\n replay,\n interrupted_attempt_count as \"interruptedAttemptCount\",\n cancelled_turn_count as \"cancelledTurnCount\",\n cancelled_update_count as \"cancelledUpdateCount\",\n paused_goal_count as \"pausedGoalCount\",\n revoked_grant_count as \"revokedGrantCount\"\n from transition_session_visibility(\n ${accountId}::uuid,\n ${input.workspaceId}::uuid,\n ${input.sessionId}::uuid,\n ${input.actorSubjectId},\n ${input.targetVisibility},\n ${input.expectedAuthorityEpoch},\n ${input.operationKey},\n ${requestHash},\n ${SESSION_TENANCY_ACTIVATION_VERSION}\n )`,\n );\n const result = rows[0];\n if (!result) throw new Error(\"Session visibility transition returned no result\");\n return result;\n },\n undefined,\n \"none\",\n );\n } catch (error) {\n mapSessionTenancyPersistenceError(error, { authorityEpochConflict: true });\n }\n}\n\nexport async function forkSessionContent(\n db: Database,\n input: ForkSessionContentInput,\n): Promise<ForkSessionContentResult> {\n if (!input.operationKey.trim()) throw new Error(\"operationKey must not be empty\");\n if (input.destinationWorkspaceId !== input.sourceWorkspaceId) {\n throw new Error(\"The first session fork contract is same-workspace only\");\n }\n const { accountId } = await rlsContextForWorkspace(db, input.sourceWorkspaceId);\n await assertSessionTenancyProductActivated(db, input.sourceWorkspaceId);\n const requestHash = canonicalSessionForkHash(input);\n try {\n return await withWorkspaceSubjectRls(\n db,\n input.sourceWorkspaceId,\n input.actorSubjectId,\n async (scopedDb) => {\n const rows = await rawRows<ForkSessionContentResult>(\n scopedDb,\n sql`select\n operation_id as \"operationId\",\n event_id as \"eventId\",\n event_sequence as \"eventSequence\",\n session_id as \"sessionId\",\n workspace_id as \"workspaceId\",\n visibility,\n authority_epoch as \"authorityEpoch\",\n copied_history_item_count as \"copiedHistoryItemCount\",\n replay\n from fork_session_content(\n ${accountId}::uuid,\n ${input.sourceWorkspaceId}::uuid,\n ${input.sourceSessionId}::uuid,\n ${input.actorSubjectId},\n ${input.destinationWorkspaceId}::uuid,\n ${input.destinationVisibility},\n ${input.workspaceSharedAcknowledged},\n ${input.operationKey},\n ${requestHash},\n ${SESSION_TENANCY_ACTIVATION_VERSION}\n )`,\n );\n const result = rows[0];\n if (!result) throw new Error(\"Session fork returned no result\");\n return result;\n },\n undefined,\n \"none\",\n );\n } catch (error) {\n // A private destination inside a shared workspace carries the same\n // organization owner/admin product decision the create path enforces\n // (migration 0323), and the fork routine raises the same 55000. Surface it\n // as the not-activated product state rather than an opaque database error.\n if (nestedPostgresSqlState(error) === \"55000\") {\n throw new SessionTenancyNotActivatedError({ cause: error });\n }\n mapSessionTenancyPersistenceError(error, { authorityEpochConflict: false });\n }\n}\n\n/**\n * Recover one exact committed fork before mutable source-session authorization.\n *\n * The database capability requires the authenticated actor's active workspace\n * authority and matches the complete actor/workspace/source/key/request-hash\n * tuple. A fresh key returns null; changed intent raises the ordinary tenancy\n * idempotency conflict. This is receipt recovery, not session discovery.\n */\nexport async function replayAppliedSessionFork(\n db: Database,\n input: ForkSessionContentInput,\n): Promise<ForkSessionContentResult | null> {\n if (!input.operationKey.trim()) throw new Error(\"operationKey must not be empty\");\n if (input.destinationWorkspaceId !== input.sourceWorkspaceId) {\n throw new Error(\"The first session fork contract is same-workspace only\");\n }\n const { accountId } = await rlsContextForWorkspace(db, input.sourceWorkspaceId);\n await assertSessionTenancyProductActivated(db, input.sourceWorkspaceId);\n const requestHash = canonicalSessionForkHash(input);\n try {\n return await withWorkspaceSubjectRls(\n db,\n input.sourceWorkspaceId,\n input.actorSubjectId,\n async (scopedDb) => {\n const rows = await rawRows<ForkSessionContentResult>(\n scopedDb,\n sql`select\n operation_id as \"operationId\",\n event_id as \"eventId\",\n event_sequence as \"eventSequence\",\n session_id as \"sessionId\",\n workspace_id as \"workspaceId\",\n visibility,\n authority_epoch as \"authorityEpoch\",\n copied_history_item_count as \"copiedHistoryItemCount\",\n replay\n from replay_applied_session_fork(\n ${accountId}::uuid,\n ${input.sourceWorkspaceId}::uuid,\n ${input.sourceSessionId}::uuid,\n ${input.actorSubjectId},\n ${input.destinationWorkspaceId}::uuid,\n ${input.destinationVisibility},\n ${input.workspaceSharedAcknowledged},\n ${input.operationKey},\n ${requestHash},\n ${SESSION_TENANCY_ACTIVATION_VERSION}\n )`,\n );\n return rows[0] ?? null;\n },\n );\n } catch (error) {\n mapSessionTenancyPersistenceError(error, { authorityEpochConflict: false });\n }\n}\n"],"mappings":";;;;;;;;;;AAAA,SAAS,kBAAkB;AAC3B,SAAS,WAAW;AACpB;AAAA,EACE,yBAAyB;AAAA,OAEpB;AAWP,IAAM,qCAAqC;AAEpC,IAAM,8BAAN,cAA0C,MAAM;AAAA,EAErD,YACW,QACA,UAAwC,MACjD,SACA;AACA;AAAA,MACE,WAAW,kBACP,kEACA,WAAW,oBACT,6DACA;AAAA,MACN;AAAA,IACF;AAXS;AACA;AAAA,EAWX;AAAA,EAdS,OAAO;AAelB;AAEO,IAAM,kCAAN,cAA8C,MAAM;AAAA,EAChD,OAAO;AAAA,EAChB,YAAY,SAAwB;AAClC,UAAM,0EAA0E,OAAO;AAAA,EACzF;AACF;AAEO,IAAM,4BAAN,cAAwC,MAAM;AAAA,EAC1C,OAAO;AAAA,EAChB,YAAY,SAAwB;AAClC,UAAM,yCAAyC,OAAO;AAAA,EACxD;AACF;AAEO,IAAM,oCAAN,cAAgD,MAAM;AAAA,EAClD,OAAO;AAAA,EAChB,YAAY,SAAwB;AAClC,UAAM,sCAAsC,OAAO;AAAA,EACrD;AACF;AAEA,SAAS,SAAS,OAAkD;AAClE,SAAO,QAAQ,SAAS,OAAO,UAAU,QAAQ;AACnD;AAGO,SAAS,4BAA4B,OAA8C;AACxF,QAAM,QAAmB,CAAC,KAAK;AAC/B,QAAM,OAAO,oBAAI,IAAa;AAC9B,SAAO,MAAM,SAAS,KAAK,KAAK,OAAO,IAAI;AACzC,UAAM,UAAU,MAAM,MAAM;AAC5B,QAAI,CAAC,SAAS,OAAO,KAAK,KAAK,IAAI,OAAO,EAAG;AAC7C,SAAK,IAAI,OAAO;AAChB,eAAW,OAAO,CAAC,UAAU,eAAe,GAAY;AACtD,YAAM,SAAS,4BAA4B,UAAU,QAAQ,GAAG,CAAC;AACjE,UAAI,OAAO,QAAS,QAAO,OAAO;AAAA,IACpC;AACA,eAAW,OAAO,CAAC,SAAS,YAAY,eAAe,SAAS,QAAQ,GAAY;AAClF,YAAM,SAAS,QAAQ,GAAG;AAC1B,UAAI,MAAM,QAAQ,MAAM,EAAG,OAAM,KAAK,GAAG,MAAM;AAAA,eACtC,WAAW,OAAW,OAAM,KAAK,MAAM;AAAA,IAClD;AAAA,EACF;AACA,SAAO;AACT;AAyDA,eAAsB,+BACpB,IACA,aACkB;AAClB,QAAM,EAAE,UAAU,IAAI,MAAM,uBAAuB,IAAI,WAAW;AAClE,SAAO,MAAM,iBAAiB,IAAI,aAAa,OAAO,aAAa;AACjE,UAAM,OAAO,MAAM;AAAA,MACjB;AAAA,MACA;AAAA,UACI,SAAS;AAAA,UACT,kCAAkC;AAAA;AAAA,IAExC;AACA,WAAO,KAAK,CAAC,GAAG,cAAc;AAAA,EAChC,CAAC;AACH;AAEA,eAAsB,8BACpB,IACA,OACqC;AACrC,QAAM,EAAE,UAAU,IAAI,MAAM,uBAAuB,IAAI,MAAM,WAAW;AACxE,SAAO,MAAM;AAAA,IACX;AAAA,IACA,MAAM;AAAA,IACN,MAAM;AAAA,IACN,OAAO,aAAa;AAClB,YAAM,OAAO,MAAM;AAAA,QAKjB;AAAA,QACA;AAAA;AAAA;AAAA;AAAA;AAAA,YAKI,SAAS;AAAA,YACT,MAAM,WAAW;AAAA,YACjB,MAAM,cAAc;AAAA;AAAA,MAE1B;AACA,YAAM,SAAS,KAAK,CAAC;AACrB,UAAI,CAAC,OAAQ,OAAM,IAAI,0BAA0B;AACjD,aAAO;AAAA,IACT;AAAA,EACF;AACF;AAGA,eAAsB,mCACpB,IACA,OAC8D;AAC9D,MAAI;AACJ,MAAI;AACF,WAAO,MAAM;AAAA,MACX;AAAA,MACA;AAAA;AAAA;AAAA;AAAA,UAII,MAAM,SAAS;AAAA,UACf,MAAM,WAAW;AAAA,UACjB,MAAM,SAAS;AAAA,UACf,MAAM,cAAc;AAAA;AAAA,IAE1B;AAAA,EACF,SAAS,OAAO;AAId,QAAI,uBAAuB,KAAK,MAAM,SAAS;AAC7C,YAAM,IAAI,gCAAgC,EAAE,OAAO,MAAM,CAAC;AAAA,IAC5D;AACA,UAAM;AAAA,EACR;AACA,QAAM,eAAe,KAAK,CAAC,GAAG;AAC9B,QAAM,oBAAoB,KAAK,CAAC,GAAG;AACnC,MAAI,CAAC,gBAAgB,CAAC,mBAAmB;AACvC,UAAM,IAAI,MAAM,oDAAoD;AAAA,EACtE;AACA,SAAO,EAAE,cAAc,kBAAkB;AAC3C;AAGA,eAAsB,wCACpB,IACA,OASsF;AACtF,QAAM,OAAO,MAAM;AAAA,IAKjB;AAAA,IACA;AAAA;AAAA;AAAA;AAAA;AAAA,QAKI,MAAM,SAAS;AAAA,QACf,MAAM,WAAW;AAAA,QACjB,MAAM,SAAS;AAAA,QACf,MAAM,eAAe;AAAA,QACrB,MAAM,WAAW;AAAA,QACjB,MAAM,cAAc;AAAA,QACpB,MAAM,wBAAwB;AAAA;AAAA,EAEpC;AACA,QAAM,eAAe,KAAK,CAAC,GAAG;AAC9B,QAAM,oBAAoB,KAAK,CAAC,GAAG;AACnC,QAAM,iBAAiB,KAAK,CAAC,GAAG;AAChC,MAAI,CAAC,gBAAgB,CAAC,qBAAqB,CAAC,gBAAgB;AAC1D,UAAM,IAAI,MAAM,0DAA0D;AAAA,EAC5E;AACA,SAAO,EAAE,cAAc,mBAAmB,eAAe;AAC3D;AAEA,eAAsB,oCACpB,IACA,cACe;AACf,QAAM,GAAG,QAAQ,qDAAqD,YAAY,SAAS;AAC7F;AAEA,eAAe,qCACb,IACA,aACe;AACf,MAAI,CAAE,MAAM,+BAA+B,IAAI,WAAW,GAAI;AAC5D,UAAM,IAAI,gCAAgC;AAAA,EAC5C;AACF;AAEA,SAAS,kCACP,OACA,SACO;AACP,QAAM,QAAQ,uBAAuB,KAAK;AAC1C,MAAI,UAAU,SAAS;AACrB,UAAM,IAAI,4BAA4B,iBAAiB,4BAA4B,KAAK,GAAG;AAAA,MACzF,OAAO;AAAA,IACT,CAAC;AAAA,EACH;AACA,MAAI,UAAU,WAAW,QAAQ,wBAAwB;AACvD,UAAM,IAAI,4BAA4B,mBAAmB,MAAM,EAAE,OAAO,MAAM,CAAC;AAAA,EACjF;AACA,MAAI,UAAU,SAAS;AACrB,UAAM,IAAI,4BAA4B,mBAAmB,MAAM,EAAE,OAAO,MAAM,CAAC;AAAA,EACjF;AACA,MAAI,UAAU,SAAS;AACrB,UAAM,IAAI,gCAAgC,EAAE,OAAO,MAAM,CAAC;AAAA,EAC5D;AACA,MAAI,UAAU,WAAW,UAAU,SAAS;AAC1C,UAAM,IAAI,0BAA0B,EAAE,OAAO,MAAM,CAAC;AAAA,EACtD;AACA,MAAI,UAAU,SAAS;AACrB,UAAM,IAAI,kCAAkC,EAAE,OAAO,MAAM,CAAC;AAAA,EAC9D;AACA,QAAM;AACR;AAEO,SAAS,yCACd,OAIQ;AACR,SAAO,WAAW,QAAQ,EACvB;AAAA,IACC,KAAK,UAAU;AAAA,MACb,SAAS;AAAA,MACT,WAAW,MAAM;AAAA,MACjB,kBAAkB,MAAM;AAAA,MACxB,wBAAwB,MAAM;AAAA,IAChC,CAAC;AAAA,EACH,EACC,OAAO,KAAK;AACjB;AAEO,SAAS,yBACd,OAOQ;AACR,QAAM,iCACJ,MAAM,0BAA0B,kBAAkB,CAAC,MAAM;AAC3D,SAAO,WAAW,QAAQ,EACvB;AAAA,IACC,KAAK;AAAA,MACH,iCACI;AAAA,QACE,SAAS;AAAA,QACT,iBAAiB,MAAM;AAAA,QACvB,wBAAwB,MAAM;AAAA,QAC9B,uBAAuB,MAAM;AAAA,MAC/B,IACA;AAAA,QACE,SAAS;AAAA,QACT,iBAAiB,MAAM;AAAA,QACvB,wBAAwB,MAAM;AAAA,QAC9B,uBAAuB,MAAM;AAAA,QAC7B,6BAA6B,MAAM;AAAA,MACrC;AAAA,IACN;AAAA,EACF,EACC,OAAO,KAAK;AACjB;AAEA,eAAsB,4BACpB,IACA,OAC4C;AAC5C,MAAI,CAAC,OAAO,cAAc,MAAM,sBAAsB,KAAK,MAAM,yBAAyB,GAAG;AAC3F,UAAM,IAAI,MAAM,wDAAwD;AAAA,EAC1E;AACA,MAAI,CAAC,MAAM,aAAa,KAAK,EAAG,OAAM,IAAI,MAAM,gCAAgC;AAChF,QAAM,cAAc,yCAAyC,KAAK;AAClE,QAAM,EAAE,UAAU,IAAI,MAAM,uBAAuB,IAAI,MAAM,WAAW;AACxE,QAAM,qCAAqC,IAAI,MAAM,WAAW;AAChE,MAAI;AACF,WAAO,MAAM;AAAA,MACX;AAAA,MACA,MAAM;AAAA,MACN,MAAM;AAAA,MACN,OAAO,aAAa;AAClB,cAAM,OAAO,MAAM;AAAA,UAejB;AAAA,UACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,YAeE,SAAS;AAAA,YACT,MAAM,WAAW;AAAA,YACjB,MAAM,SAAS;AAAA,YACf,MAAM,cAAc;AAAA,YACpB,MAAM,gBAAgB;AAAA,YACtB,MAAM,sBAAsB;AAAA,YAC5B,MAAM,YAAY;AAAA,YAClB,WAAW;AAAA,YACX,kCAAkC;AAAA;AAAA,QAEtC;AACA,cAAM,SAAS,KAAK,CAAC;AACrB,YAAI,CAAC,OAAQ,OAAM,IAAI,MAAM,kDAAkD;AAC/E,eAAO;AAAA,MACT;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF,SAAS,OAAO;AACd,sCAAkC,OAAO,EAAE,wBAAwB,KAAK,CAAC;AAAA,EAC3E;AACF;AAEA,eAAsB,mBACpB,IACA,OACmC;AACnC,MAAI,CAAC,MAAM,aAAa,KAAK,EAAG,OAAM,IAAI,MAAM,gCAAgC;AAChF,MAAI,MAAM,2BAA2B,MAAM,mBAAmB;AAC5D,UAAM,IAAI,MAAM,wDAAwD;AAAA,EAC1E;AACA,QAAM,EAAE,UAAU,IAAI,MAAM,uBAAuB,IAAI,MAAM,iBAAiB;AAC9E,QAAM,qCAAqC,IAAI,MAAM,iBAAiB;AACtE,QAAM,cAAc,yBAAyB,KAAK;AAClD,MAAI;AACF,WAAO,MAAM;AAAA,MACX;AAAA,MACA,MAAM;AAAA,MACN,MAAM;AAAA,MACN,OAAO,aAAa;AAClB,cAAM,OAAO,MAAM;AAAA,UACjB;AAAA,UACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,YAWE,SAAS;AAAA,YACT,MAAM,iBAAiB;AAAA,YACvB,MAAM,eAAe;AAAA,YACrB,MAAM,cAAc;AAAA,YACpB,MAAM,sBAAsB;AAAA,YAC5B,MAAM,qBAAqB;AAAA,YAC3B,MAAM,2BAA2B;AAAA,YACjC,MAAM,YAAY;AAAA,YAClB,WAAW;AAAA,YACX,kCAAkC;AAAA;AAAA,QAEtC;AACA,cAAM,SAAS,KAAK,CAAC;AACrB,YAAI,CAAC,OAAQ,OAAM,IAAI,MAAM,iCAAiC;AAC9D,eAAO;AAAA,MACT;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF,SAAS,OAAO;AAKd,QAAI,uBAAuB,KAAK,MAAM,SAAS;AAC7C,YAAM,IAAI,gCAAgC,EAAE,OAAO,MAAM,CAAC;AAAA,IAC5D;AACA,sCAAkC,OAAO,EAAE,wBAAwB,MAAM,CAAC;AAAA,EAC5E;AACF;AAUA,eAAsB,yBACpB,IACA,OAC0C;AAC1C,MAAI,CAAC,MAAM,aAAa,KAAK,EAAG,OAAM,IAAI,MAAM,gCAAgC;AAChF,MAAI,MAAM,2BAA2B,MAAM,mBAAmB;AAC5D,UAAM,IAAI,MAAM,wDAAwD;AAAA,EAC1E;AACA,QAAM,EAAE,UAAU,IAAI,MAAM,uBAAuB,IAAI,MAAM,iBAAiB;AAC9E,QAAM,qCAAqC,IAAI,MAAM,iBAAiB;AACtE,QAAM,cAAc,yBAAyB,KAAK;AAClD,MAAI;AACF,WAAO,MAAM;AAAA,MACX;AAAA,MACA,MAAM;AAAA,MACN,MAAM;AAAA,MACN,OAAO,aAAa;AAClB,cAAM,OAAO,MAAM;AAAA,UACjB;AAAA,UACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,YAWE,SAAS;AAAA,YACT,MAAM,iBAAiB;AAAA,YACvB,MAAM,eAAe;AAAA,YACrB,MAAM,cAAc;AAAA,YACpB,MAAM,sBAAsB;AAAA,YAC5B,MAAM,qBAAqB;AAAA,YAC3B,MAAM,2BAA2B;AAAA,YACjC,MAAM,YAAY;AAAA,YAClB,WAAW;AAAA,YACX,kCAAkC;AAAA;AAAA,QAEtC;AACA,eAAO,KAAK,CAAC,KAAK;AAAA,MACpB;AAAA,IACF;AAAA,EACF,SAAS,OAAO;AACd,sCAAkC,OAAO,EAAE,wBAAwB,MAAM,CAAC;AAAA,EAC5E;AACF;","names":[]}
|