@opengeni/db 1.0.2 → 1.1.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/artifact-catalog.d.ts +12 -0
- package/dist/canonical-human-identities.js +3 -3
- package/dist/capability-reconciliation.js +2 -2
- package/dist/{chunk-KEDWSSNP.js → chunk-7Z7ALFRE.js} +2 -2
- package/dist/{chunk-OQZOGCLG.js → chunk-BLUQG54M.js} +2 -2
- package/dist/{chunk-Y77PVZZD.js → chunk-BZV5ZDRY.js} +2 -2
- package/dist/{chunk-MACELCXG.js → chunk-CIGHXFPD.js} +3 -3
- package/dist/{chunk-7S53WLPI.js → chunk-EKXCF3FH.js} +4 -4
- package/dist/{chunk-F3YMVRF4.js → chunk-GLA5YCQS.js} +4 -4
- package/dist/{chunk-KH3TYDGY.js → chunk-J4WFOVBV.js} +2 -2
- package/dist/{chunk-NLSG7GGA.js → chunk-KJVOBLFC.js} +3 -3
- package/dist/{chunk-7POTLB24.js → chunk-M2YGXCCQ.js} +2 -2
- package/dist/{chunk-PQGTWFRF.js → chunk-M3SWH4MU.js} +2 -2
- package/dist/{chunk-OMHBVHX7.js → chunk-ODKYBA3C.js} +5 -5
- package/dist/{chunk-56MYWKQV.js → chunk-SKYRM7R7.js} +2 -2
- package/dist/{chunk-N5QCKHYL.js → chunk-T5NHDEZW.js} +62 -19
- package/dist/chunk-T5NHDEZW.js.map +1 -0
- package/dist/{chunk-LDTQGYD2.js → chunk-VOB6TXSJ.js} +16 -1
- package/dist/chunk-VOB6TXSJ.js.map +1 -0
- package/dist/{chunk-UEQQ2WWU.js → chunk-Z5HJXH3X.js} +3 -26
- package/dist/chunk-Z5HJXH3X.js.map +1 -0
- package/dist/connect-attempts.js +3 -3
- package/dist/editable-artifact-durable-export.js +2 -2
- package/dist/editable-artifacts.js +3 -3
- package/dist/index.d.ts +14 -5
- package/dist/index.js +85 -30
- package/dist/index.js.map +1 -1
- package/dist/managed-auth-session-sets.js +3 -3
- package/dist/mcp-operations.js +2 -2
- package/dist/organization-integration-policy.js +3 -3
- package/dist/provision-roles.js +1 -1
- package/dist/retained-provider-commands.js +6 -6
- package/dist/runtime-posture.d.ts +3 -2
- package/dist/schema.d.ts +93 -0
- package/dist/schema.js +3 -1
- package/dist/session-background-commands.js +4 -4
- package/dist/session-command-output.js +5 -5
- package/dist/session-event-slices.js +3 -3
- package/dist/session-mcp-credential-rotation.js +5 -5
- package/dist/session-tenancy.d.ts +6 -1
- package/dist/session-tenancy.js +3 -5
- package/dist/video-generation.js +3 -3
- package/dist/workspace-tool-defaults.js +4 -4
- package/drizzle/0610_artifact_catalog_pins.sql +165 -0
- package/drizzle/0611_universal_session_tenancy_activation.sql +166 -0
- package/drizzle/0612_scheduled_model_unavailable_refusal.sql +21 -0
- package/package.json +7 -7
- package/src/artifact-catalog.ts +31 -5
- package/src/index.ts +94 -19
- package/src/provision-roles.ts +16 -0
- package/src/runtime-posture-cli.ts +0 -2
- package/src/runtime-posture.ts +64 -27
- package/src/schema.ts +17 -0
- package/src/session-tenancy.ts +9 -31
- package/dist/chunk-LDTQGYD2.js.map +0 -1
- package/dist/chunk-N5QCKHYL.js.map +0 -1
- package/dist/chunk-UEQQ2WWU.js.map +0 -1
- /package/dist/{chunk-KEDWSSNP.js.map → chunk-7Z7ALFRE.js.map} +0 -0
- /package/dist/{chunk-OQZOGCLG.js.map → chunk-BLUQG54M.js.map} +0 -0
- /package/dist/{chunk-Y77PVZZD.js.map → chunk-BZV5ZDRY.js.map} +0 -0
- /package/dist/{chunk-MACELCXG.js.map → chunk-CIGHXFPD.js.map} +0 -0
- /package/dist/{chunk-7S53WLPI.js.map → chunk-EKXCF3FH.js.map} +0 -0
- /package/dist/{chunk-F3YMVRF4.js.map → chunk-GLA5YCQS.js.map} +0 -0
- /package/dist/{chunk-KH3TYDGY.js.map → chunk-J4WFOVBV.js.map} +0 -0
- /package/dist/{chunk-NLSG7GGA.js.map → chunk-KJVOBLFC.js.map} +0 -0
- /package/dist/{chunk-7POTLB24.js.map → chunk-M2YGXCCQ.js.map} +0 -0
- /package/dist/{chunk-PQGTWFRF.js.map → chunk-M3SWH4MU.js.map} +0 -0
- /package/dist/{chunk-OMHBVHX7.js.map → chunk-ODKYBA3C.js.map} +0 -0
- /package/dist/{chunk-56MYWKQV.js.map → chunk-SKYRM7R7.js.map} +0 -0
package/src/runtime-posture.ts
CHANGED
|
@@ -80,6 +80,12 @@ export const SANDBOX_FILE_PUBLICATION_RUNTIME_ROUTINES = [
|
|
|
80
80
|
"list_sandbox_file_publications(uuid, uuid, jsonb)",
|
|
81
81
|
] as const;
|
|
82
82
|
const SANDBOX_FILE_PUBLICATIONS_TABLE = "sandbox_file_publications";
|
|
83
|
+
export const ARTIFACT_PIN_RUNTIME_ROUTINES = [
|
|
84
|
+
"update_artifact_pin(uuid, uuid, text, text, boolean)",
|
|
85
|
+
"list_artifact_pins(uuid, uuid)",
|
|
86
|
+
"list_sandbox_file_publications_pinned(uuid, uuid, jsonb)",
|
|
87
|
+
] as const;
|
|
88
|
+
const ARTIFACT_PINS_TABLE = "artifact_catalog_pins";
|
|
83
89
|
export const SCHEDULED_SLACK_BOT_MESSAGE_RUNTIME_ROUTINES = [
|
|
84
90
|
"prepare_scheduled_slack_bot_message(uuid, uuid, uuid, uuid, uuid, integer, text, text, text)",
|
|
85
91
|
"read_scheduled_slack_bot_message(uuid, uuid, uuid, uuid)",
|
|
@@ -1732,7 +1738,6 @@ export type RuntimeDatabasePostureOptions = {
|
|
|
1732
1738
|
targetSchemaForbiddenRoutines?: readonly string[];
|
|
1733
1739
|
/** Frozen binary contract; current callers require both additive Insights capabilities. */
|
|
1734
1740
|
modelFactCapabilityRoutines?: readonly string[];
|
|
1735
|
-
organizationTenancyCanonicalActivationEnabled?: boolean;
|
|
1736
1741
|
};
|
|
1737
1742
|
|
|
1738
1743
|
export type RuntimeDatabaseIdentity = {
|
|
@@ -1803,6 +1808,8 @@ export type RuntimePrivateTablePosture = {
|
|
|
1803
1808
|
truncate?: boolean;
|
|
1804
1809
|
references?: boolean;
|
|
1805
1810
|
trigger?: boolean;
|
|
1811
|
+
/** Effective relation ACL privileges not represented by the named flags. */
|
|
1812
|
+
extraPrivileges?: string[];
|
|
1806
1813
|
};
|
|
1807
1814
|
|
|
1808
1815
|
export type RuntimeDatabasePosture = {
|
|
@@ -1816,7 +1823,6 @@ export type RuntimeDatabasePosture = {
|
|
|
1816
1823
|
privateTables: RuntimePrivateTablePosture[];
|
|
1817
1824
|
targetRoutines: RuntimeTargetRoutinePosture[];
|
|
1818
1825
|
privateRoutines: RuntimeRoutinePosture[];
|
|
1819
|
-
sessionTenancyProductActivationPresent: boolean;
|
|
1820
1826
|
sessionVariableSetAttachmentsCutoverPresent: boolean;
|
|
1821
1827
|
claudeSubscriptionPoolActivationPresent: boolean;
|
|
1822
1828
|
};
|
|
@@ -1942,14 +1948,6 @@ export async function inspectRuntimeDatabasePosture(
|
|
|
1942
1948
|
bypassRls: identity.rolbypassrls,
|
|
1943
1949
|
};
|
|
1944
1950
|
|
|
1945
|
-
// The forward-only activation receipt outlives topology. Embedded/scoped
|
|
1946
|
-
// deployments must enforce the same environment interlock as standalone
|
|
1947
|
-
// FORCE-RLS deployments, so inspect the value-free predicate before the
|
|
1948
|
-
// scoped catalog fast-path.
|
|
1949
|
-
const activationRows = resultRows<{ activated: boolean }>(
|
|
1950
|
-
await tx.execute(sql`select session_tenancy_any_product_activation() as activated`),
|
|
1951
|
-
);
|
|
1952
|
-
const sessionTenancyProductActivationPresent = activationRows[0]?.activated === true;
|
|
1953
1951
|
const variableSetCutoverRows = resultRows<{ present: boolean }>(
|
|
1954
1952
|
await tx.execute(sql`
|
|
1955
1953
|
select to_regprocedure(
|
|
@@ -1980,7 +1978,6 @@ export async function inspectRuntimeDatabasePosture(
|
|
|
1980
1978
|
privateTables: [],
|
|
1981
1979
|
targetRoutines: [],
|
|
1982
1980
|
privateRoutines: [],
|
|
1983
|
-
sessionTenancyProductActivationPresent,
|
|
1984
1981
|
sessionVariableSetAttachmentsCutoverPresent,
|
|
1985
1982
|
claudeSubscriptionPoolActivationPresent,
|
|
1986
1983
|
};
|
|
@@ -2115,6 +2112,7 @@ export async function inspectRuntimeDatabasePosture(
|
|
|
2115
2112
|
can_truncate: boolean;
|
|
2116
2113
|
can_references: boolean;
|
|
2117
2114
|
can_trigger: boolean;
|
|
2115
|
+
extra_privileges: string[];
|
|
2118
2116
|
}>(
|
|
2119
2117
|
await tx.execute(sql`
|
|
2120
2118
|
select
|
|
@@ -2124,25 +2122,31 @@ export async function inspectRuntimeDatabasePosture(
|
|
|
2124
2122
|
c.relforcerowsecurity as rls_forced,
|
|
2125
2123
|
row_security_active(c.oid) as rls_active,
|
|
2126
2124
|
(select count(*)::int from pg_policy policy where policy.polrelid = c.oid) as policy_count,
|
|
2127
|
-
-- Column-only grants on
|
|
2128
|
-
--
|
|
2125
|
+
-- Column-only grants on capability tables are also unsafe; a pin
|
|
2126
|
+
-- table must remain EXECUTE-only even after column ACL drift.
|
|
2129
2127
|
(has_table_privilege(current_user, c.oid, 'SELECT') or
|
|
2130
|
-
((c.relname in ('modal_inventory_read_capabilities','usage_allowance_capabilities','session_import_batches') or
|
|
2128
|
+
((c.relname in ('modal_inventory_read_capabilities','usage_allowance_capabilities','session_import_batches', ${ARTIFACT_PINS_TABLE}) or
|
|
2131
2129
|
c.relname in (select jsonb_array_elements_text(${JSON.stringify(RUNTIME_ALLOWANCE_PRIVATE_TABLES)}::jsonb))) and
|
|
2132
2130
|
has_any_column_privilege(current_user, c.oid, 'SELECT'))) as can_select,
|
|
2133
2131
|
(has_table_privilege(current_user, c.oid, 'INSERT') or
|
|
2134
|
-
((c.relname in ('modal_inventory_read_capabilities','usage_allowance_capabilities','session_import_batches') or
|
|
2132
|
+
((c.relname in ('modal_inventory_read_capabilities','usage_allowance_capabilities','session_import_batches', ${ARTIFACT_PINS_TABLE}) or
|
|
2135
2133
|
c.relname in (select jsonb_array_elements_text(${JSON.stringify(RUNTIME_ALLOWANCE_PRIVATE_TABLES)}::jsonb))) and
|
|
2136
2134
|
has_any_column_privilege(current_user, c.oid, 'INSERT'))) as can_insert,
|
|
2137
2135
|
(has_table_privilege(current_user, c.oid, 'UPDATE') or
|
|
2138
|
-
((c.relname in ('modal_inventory_read_capabilities','usage_allowance_capabilities','session_import_batches') or
|
|
2136
|
+
((c.relname in ('modal_inventory_read_capabilities','usage_allowance_capabilities','session_import_batches', ${ARTIFACT_PINS_TABLE}) or
|
|
2139
2137
|
c.relname in (select jsonb_array_elements_text(${JSON.stringify(RUNTIME_ALLOWANCE_PRIVATE_TABLES)}::jsonb))) and
|
|
2140
2138
|
has_any_column_privilege(current_user, c.oid, 'UPDATE'))) as can_update,
|
|
2141
2139
|
has_table_privilege(current_user, c.oid, 'DELETE') as can_delete,
|
|
2142
2140
|
has_table_privilege(current_user, c.oid, 'TRUNCATE') as can_truncate,
|
|
2143
2141
|
(has_table_privilege(current_user, c.oid, 'REFERENCES') or
|
|
2144
2142
|
has_any_column_privilege(current_user, c.oid, 'REFERENCES')) as can_references,
|
|
2145
|
-
has_table_privilege(current_user, c.oid, 'TRIGGER') as can_trigger
|
|
2143
|
+
has_table_privilege(current_user, c.oid, 'TRIGGER') as can_trigger,
|
|
2144
|
+
CASE WHEN c.relname = ${ARTIFACT_PINS_TABLE} THEN ARRAY(
|
|
2145
|
+
SELECT DISTINCT acl.privilege_type FROM aclexplode(coalesce(c.relacl, acldefault('r', c.relowner))) acl
|
|
2146
|
+
WHERE acl.privilege_type NOT IN ('SELECT','INSERT','UPDATE','DELETE','TRUNCATE','REFERENCES','TRIGGER')
|
|
2147
|
+
AND CASE WHEN acl.grantee = 0 THEN true ELSE pg_has_role(current_user, acl.grantee, 'USAGE') END
|
|
2148
|
+
ORDER BY acl.privilege_type
|
|
2149
|
+
) ELSE ARRAY[]::text[] END AS extra_privileges
|
|
2146
2150
|
from pg_class c
|
|
2147
2151
|
join pg_namespace n on n.oid = c.relnamespace
|
|
2148
2152
|
where n.nspname = 'opengeni_private'
|
|
@@ -2156,6 +2160,7 @@ export async function inspectRuntimeDatabasePosture(
|
|
|
2156
2160
|
${SCOPED_COMPUTE_CAPABILITY_TABLE},
|
|
2157
2161
|
${CONNECTION_TENANCY_BACKFILL_CAPABILITY_TABLE},
|
|
2158
2162
|
${SANDBOX_FILE_PUBLICATIONS_TABLE},
|
|
2163
|
+
${ARTIFACT_PINS_TABLE},
|
|
2159
2164
|
${SCHEDULED_SLACK_BOT_MESSAGES_TABLE},
|
|
2160
2165
|
${ORGANIZATION_SIGNUP_USE_CASES_TABLE},
|
|
2161
2166
|
${SLACK_FILE_UPLOAD_OPERATIONS_TABLE},
|
|
@@ -2193,6 +2198,7 @@ export async function inspectRuntimeDatabasePosture(
|
|
|
2193
2198
|
truncate: row.can_truncate,
|
|
2194
2199
|
references: row.can_references,
|
|
2195
2200
|
trigger: row.can_trigger,
|
|
2201
|
+
...(row.extra_privileges?.length ? { extraPrivileges: row.extra_privileges } : {}),
|
|
2196
2202
|
}));
|
|
2197
2203
|
|
|
2198
2204
|
const targetRoutines = resultRows<{
|
|
@@ -2284,7 +2290,6 @@ export async function inspectRuntimeDatabasePosture(
|
|
|
2284
2290
|
privateTables,
|
|
2285
2291
|
targetRoutines,
|
|
2286
2292
|
privateRoutines,
|
|
2287
|
-
sessionTenancyProductActivationPresent,
|
|
2288
2293
|
sessionVariableSetAttachmentsCutoverPresent,
|
|
2289
2294
|
claudeSubscriptionPoolActivationPresent,
|
|
2290
2295
|
};
|
|
@@ -2321,15 +2326,6 @@ export function evaluateRuntimeDatabasePosture(
|
|
|
2321
2326
|
violations.push("database is missing the 0352 session Variable Set attachment runtime receipt");
|
|
2322
2327
|
}
|
|
2323
2328
|
|
|
2324
|
-
if (
|
|
2325
|
-
posture.sessionTenancyProductActivationPresent &&
|
|
2326
|
-
options.organizationTenancyCanonicalActivationEnabled !== true
|
|
2327
|
-
) {
|
|
2328
|
-
violations.push(
|
|
2329
|
-
"session-tenancy product activation is durable but OPENGENI_ORGANIZATION_TENANCY_CANONICAL_ACTIVATION_ENABLED is not true",
|
|
2330
|
-
);
|
|
2331
|
-
}
|
|
2332
|
-
|
|
2333
2329
|
if (!identity.currentUser || !identity.sessionUser) {
|
|
2334
2330
|
violations.push("database identity is empty");
|
|
2335
2331
|
}
|
|
@@ -3886,6 +3882,47 @@ export function evaluateRuntimeDatabasePosture(
|
|
|
3886
3882
|
}
|
|
3887
3883
|
}
|
|
3888
3884
|
|
|
3885
|
+
const pinTables = posture.privateTables.filter((table) => table.name === ARTIFACT_PINS_TABLE);
|
|
3886
|
+
if (pinTables.length !== 1) {
|
|
3887
|
+
if (!options.protectedTables)
|
|
3888
|
+
violations.push("artifact pin private relation is missing or ambiguous");
|
|
3889
|
+
} else {
|
|
3890
|
+
const table = pinTables[0]!;
|
|
3891
|
+
if (!table.rlsEnabled || !table.rlsForced || !table.rlsActive || (table.policyCount ?? 0) < 1)
|
|
3892
|
+
violations.push("artifact pin relation lacks active FORCE-RLS workspace isolation");
|
|
3893
|
+
if (
|
|
3894
|
+
table.select ||
|
|
3895
|
+
table.insert ||
|
|
3896
|
+
table.update ||
|
|
3897
|
+
table.delete ||
|
|
3898
|
+
table.truncate ||
|
|
3899
|
+
table.references ||
|
|
3900
|
+
table.trigger ||
|
|
3901
|
+
table.extraPrivileges?.length ||
|
|
3902
|
+
table.owner === expectedRole
|
|
3903
|
+
)
|
|
3904
|
+
violations.push("runtime role has forbidden direct artifact pin authority");
|
|
3905
|
+
if (tableByName.get("files")?.owner && table.owner !== tableByName.get("files")!.owner)
|
|
3906
|
+
violations.push("artifact pin owner does not match workspace authority");
|
|
3907
|
+
const quotedSchema = `"${targetSchema.replaceAll('"', '""')}"`;
|
|
3908
|
+
const searchPaths = new Set([
|
|
3909
|
+
`search_path=pg_catalog, ${quotedSchema}, pg_temp`,
|
|
3910
|
+
`search_path=pg_catalog, ${/^[a-z_][a-z0-9_]*$/.test(targetSchema) ? targetSchema : quotedSchema}, pg_temp`,
|
|
3911
|
+
]);
|
|
3912
|
+
for (const name of ARTIFACT_PIN_RUNTIME_ROUTINES) {
|
|
3913
|
+
const routines = posture.privateRoutines.filter((routine) => routine.name === name);
|
|
3914
|
+
if (
|
|
3915
|
+
routines.length !== 1 ||
|
|
3916
|
+
!routines[0]!.execute ||
|
|
3917
|
+
routines[0]!.publicExecute ||
|
|
3918
|
+
!routines[0]!.securityDefiner ||
|
|
3919
|
+
routines[0]!.owner !== table.owner ||
|
|
3920
|
+
!routines[0]!.configuration?.some((configuration) => searchPaths.has(configuration))
|
|
3921
|
+
)
|
|
3922
|
+
violations.push(`artifact pin capability ${name} is missing or unsafe`);
|
|
3923
|
+
}
|
|
3924
|
+
}
|
|
3925
|
+
|
|
3889
3926
|
const slackFileUploadTables = posture.privateTables.filter(
|
|
3890
3927
|
(table) => table.name === SLACK_FILE_UPLOAD_OPERATIONS_TABLE,
|
|
3891
3928
|
);
|
package/src/schema.ts
CHANGED
|
@@ -5870,6 +5870,23 @@ export const sandboxFilePublications = opengeniPrivateSchema.table(
|
|
|
5870
5870
|
}),
|
|
5871
5871
|
);
|
|
5872
5872
|
|
|
5873
|
+
/** Shared discovery pins; no content or access authority. SQL owns target validation. */
|
|
5874
|
+
export const artifactCatalogPins = opengeniPrivateSchema.table(
|
|
5875
|
+
"artifact_catalog_pins",
|
|
5876
|
+
{
|
|
5877
|
+
accountId: uuid("account_id").notNull(),
|
|
5878
|
+
workspaceId: uuid("workspace_id")
|
|
5879
|
+
.notNull()
|
|
5880
|
+
.references(() => workspaces.id, { onDelete: "cascade" }),
|
|
5881
|
+
kind: text("kind").notNull(),
|
|
5882
|
+
artifactId: text("artifact_id").notNull(),
|
|
5883
|
+
pinnedAt: timestamp("pinned_at", { withTimezone: true }).notNull().defaultNow(),
|
|
5884
|
+
},
|
|
5885
|
+
(table) => ({
|
|
5886
|
+
pk: primaryKey({ columns: [table.accountId, table.workspaceId, table.kind, table.artifactId] }),
|
|
5887
|
+
}),
|
|
5888
|
+
);
|
|
5889
|
+
|
|
5873
5890
|
/** Source-session upload fence; never stores file bytes or expiring upload URLs. */
|
|
5874
5891
|
export const slackFileUploadOperations = opengeniPrivateSchema.table(
|
|
5875
5892
|
"slack_file_upload_operations",
|
package/src/session-tenancy.ts
CHANGED
|
@@ -8,12 +8,13 @@ import {
|
|
|
8
8
|
rawRows,
|
|
9
9
|
type Database,
|
|
10
10
|
rlsContextForWorkspace,
|
|
11
|
-
withWorkspaceRls,
|
|
12
11
|
withWorkspaceSubjectRls,
|
|
13
12
|
withWorkspaceSubjectSessionActivityRls,
|
|
14
13
|
} from "./database";
|
|
15
14
|
import { nestedPostgresSqlState } from "./persistence-errors";
|
|
16
15
|
|
|
16
|
+
// Frozen SQL protocol argument: every organization is activated at version 1
|
|
17
|
+
// since migration 0611, but the lifecycle routines still require it explicitly.
|
|
17
18
|
const SESSION_TENANCY_ACTIVATION_VERSION = 1;
|
|
18
19
|
|
|
19
20
|
export class SessionTenancyConflictError extends Error {
|
|
@@ -34,10 +35,16 @@ export class SessionTenancyConflictError extends Error {
|
|
|
34
35
|
}
|
|
35
36
|
}
|
|
36
37
|
|
|
38
|
+
/**
|
|
39
|
+
* The organization's owner/admin Only-me setting does not permit a new private
|
|
40
|
+
* session (or private transition) in this shared workspace. Activation itself is
|
|
41
|
+
* universal since migration 0611; the name and the SESSION_TENANCY_NOT_ACTIVATED
|
|
42
|
+
* wire code are kept for client compatibility.
|
|
43
|
+
*/
|
|
37
44
|
export class SessionTenancyNotActivatedError extends Error {
|
|
38
45
|
readonly name = "SessionTenancyNotActivatedError";
|
|
39
46
|
constructor(options?: ErrorOptions) {
|
|
40
|
-
super("
|
|
47
|
+
super("Only-me chats are not enabled for this organization", options);
|
|
41
48
|
}
|
|
42
49
|
}
|
|
43
50
|
|
|
@@ -151,23 +158,6 @@ export type ForkSessionContentResult = {
|
|
|
151
158
|
runtimeEventSequence: number | null;
|
|
152
159
|
};
|
|
153
160
|
|
|
154
|
-
export async function sessionTenancyProductActivated(
|
|
155
|
-
db: Database,
|
|
156
|
-
workspaceId: string,
|
|
157
|
-
): Promise<boolean> {
|
|
158
|
-
const { accountId } = await rlsContextForWorkspace(db, workspaceId);
|
|
159
|
-
return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
|
|
160
|
-
const rows = await rawRows<{ activated: boolean }>(
|
|
161
|
-
scopedDb,
|
|
162
|
-
sql`select session_tenancy_product_activated(
|
|
163
|
-
${accountId}::uuid,
|
|
164
|
-
${SESSION_TENANCY_ACTIVATION_VERSION}
|
|
165
|
-
) as activated`,
|
|
166
|
-
);
|
|
167
|
-
return rows[0]?.activated === true;
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
|
|
171
161
|
export async function getPrivateSessionCreatePolicy(
|
|
172
162
|
db: Database,
|
|
173
163
|
input: { workspaceId: string; actorSubjectId: string },
|
|
@@ -286,15 +276,6 @@ export async function closePrivateSessionCreateCapability(
|
|
|
286
276
|
await db.execute(sql`select close_private_session_create_capability(${capabilityId}::uuid)`);
|
|
287
277
|
}
|
|
288
278
|
|
|
289
|
-
async function assertSessionTenancyProductActivated(
|
|
290
|
-
db: Database,
|
|
291
|
-
workspaceId: string,
|
|
292
|
-
): Promise<void> {
|
|
293
|
-
if (!(await sessionTenancyProductActivated(db, workspaceId))) {
|
|
294
|
-
throw new SessionTenancyNotActivatedError();
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
|
|
298
279
|
function mapSessionTenancyPersistenceError(
|
|
299
280
|
error: unknown,
|
|
300
281
|
options: { authorityEpochConflict: boolean },
|
|
@@ -420,7 +401,6 @@ export async function transitionSessionVisibility(
|
|
|
420
401
|
if (!input.operationKey.trim()) throw new Error("operationKey must not be empty");
|
|
421
402
|
const requestHash = canonicalSessionVisibilityTransitionHash(input);
|
|
422
403
|
const { accountId } = await rlsContextForWorkspace(db, input.workspaceId);
|
|
423
|
-
await assertSessionTenancyProductActivated(db, input.workspaceId);
|
|
424
404
|
try {
|
|
425
405
|
return await withWorkspaceSubjectSessionActivityRls(
|
|
426
406
|
db,
|
|
@@ -505,7 +485,6 @@ export async function forkSessionContent(
|
|
|
505
485
|
throw new Error("Resolved session fork runtime configuration does not match its request");
|
|
506
486
|
}
|
|
507
487
|
const { accountId } = await rlsContextForWorkspace(db, input.sourceWorkspaceId);
|
|
508
|
-
await assertSessionTenancyProductActivated(db, input.sourceWorkspaceId);
|
|
509
488
|
const requestHash = canonicalSessionForkHash(input);
|
|
510
489
|
try {
|
|
511
490
|
return await withWorkspaceSubjectRls(
|
|
@@ -616,7 +595,6 @@ export async function replayAppliedSessionFork(
|
|
|
616
595
|
throw new Error("The first session fork contract is same-workspace only");
|
|
617
596
|
}
|
|
618
597
|
const { accountId } = await rlsContextForWorkspace(db, input.sourceWorkspaceId);
|
|
619
|
-
await assertSessionTenancyProductActivated(db, input.sourceWorkspaceId);
|
|
620
598
|
const requestHash = canonicalSessionForkHash(input);
|
|
621
599
|
try {
|
|
622
600
|
return await withWorkspaceSubjectRls(
|