@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.
Files changed (69) hide show
  1. package/dist/artifact-catalog.d.ts +12 -0
  2. package/dist/canonical-human-identities.js +3 -3
  3. package/dist/capability-reconciliation.js +2 -2
  4. package/dist/{chunk-KEDWSSNP.js → chunk-7Z7ALFRE.js} +2 -2
  5. package/dist/{chunk-OQZOGCLG.js → chunk-BLUQG54M.js} +2 -2
  6. package/dist/{chunk-Y77PVZZD.js → chunk-BZV5ZDRY.js} +2 -2
  7. package/dist/{chunk-MACELCXG.js → chunk-CIGHXFPD.js} +3 -3
  8. package/dist/{chunk-7S53WLPI.js → chunk-EKXCF3FH.js} +4 -4
  9. package/dist/{chunk-F3YMVRF4.js → chunk-GLA5YCQS.js} +4 -4
  10. package/dist/{chunk-KH3TYDGY.js → chunk-J4WFOVBV.js} +2 -2
  11. package/dist/{chunk-NLSG7GGA.js → chunk-KJVOBLFC.js} +3 -3
  12. package/dist/{chunk-7POTLB24.js → chunk-M2YGXCCQ.js} +2 -2
  13. package/dist/{chunk-PQGTWFRF.js → chunk-M3SWH4MU.js} +2 -2
  14. package/dist/{chunk-OMHBVHX7.js → chunk-ODKYBA3C.js} +5 -5
  15. package/dist/{chunk-56MYWKQV.js → chunk-SKYRM7R7.js} +2 -2
  16. package/dist/{chunk-N5QCKHYL.js → chunk-T5NHDEZW.js} +62 -19
  17. package/dist/chunk-T5NHDEZW.js.map +1 -0
  18. package/dist/{chunk-LDTQGYD2.js → chunk-VOB6TXSJ.js} +16 -1
  19. package/dist/chunk-VOB6TXSJ.js.map +1 -0
  20. package/dist/{chunk-UEQQ2WWU.js → chunk-Z5HJXH3X.js} +3 -26
  21. package/dist/chunk-Z5HJXH3X.js.map +1 -0
  22. package/dist/connect-attempts.js +3 -3
  23. package/dist/editable-artifact-durable-export.js +2 -2
  24. package/dist/editable-artifacts.js +3 -3
  25. package/dist/index.d.ts +14 -5
  26. package/dist/index.js +85 -30
  27. package/dist/index.js.map +1 -1
  28. package/dist/managed-auth-session-sets.js +3 -3
  29. package/dist/mcp-operations.js +2 -2
  30. package/dist/organization-integration-policy.js +3 -3
  31. package/dist/provision-roles.js +1 -1
  32. package/dist/retained-provider-commands.js +6 -6
  33. package/dist/runtime-posture.d.ts +3 -2
  34. package/dist/schema.d.ts +93 -0
  35. package/dist/schema.js +3 -1
  36. package/dist/session-background-commands.js +4 -4
  37. package/dist/session-command-output.js +5 -5
  38. package/dist/session-event-slices.js +3 -3
  39. package/dist/session-mcp-credential-rotation.js +5 -5
  40. package/dist/session-tenancy.d.ts +6 -1
  41. package/dist/session-tenancy.js +3 -5
  42. package/dist/video-generation.js +3 -3
  43. package/dist/workspace-tool-defaults.js +4 -4
  44. package/drizzle/0610_artifact_catalog_pins.sql +165 -0
  45. package/drizzle/0611_universal_session_tenancy_activation.sql +166 -0
  46. package/drizzle/0612_scheduled_model_unavailable_refusal.sql +21 -0
  47. package/package.json +7 -7
  48. package/src/artifact-catalog.ts +31 -5
  49. package/src/index.ts +94 -19
  50. package/src/provision-roles.ts +16 -0
  51. package/src/runtime-posture-cli.ts +0 -2
  52. package/src/runtime-posture.ts +64 -27
  53. package/src/schema.ts +17 -0
  54. package/src/session-tenancy.ts +9 -31
  55. package/dist/chunk-LDTQGYD2.js.map +0 -1
  56. package/dist/chunk-N5QCKHYL.js.map +0 -1
  57. package/dist/chunk-UEQQ2WWU.js.map +0 -1
  58. /package/dist/{chunk-KEDWSSNP.js.map → chunk-7Z7ALFRE.js.map} +0 -0
  59. /package/dist/{chunk-OQZOGCLG.js.map → chunk-BLUQG54M.js.map} +0 -0
  60. /package/dist/{chunk-Y77PVZZD.js.map → chunk-BZV5ZDRY.js.map} +0 -0
  61. /package/dist/{chunk-MACELCXG.js.map → chunk-CIGHXFPD.js.map} +0 -0
  62. /package/dist/{chunk-7S53WLPI.js.map → chunk-EKXCF3FH.js.map} +0 -0
  63. /package/dist/{chunk-F3YMVRF4.js.map → chunk-GLA5YCQS.js.map} +0 -0
  64. /package/dist/{chunk-KH3TYDGY.js.map → chunk-J4WFOVBV.js.map} +0 -0
  65. /package/dist/{chunk-NLSG7GGA.js.map → chunk-KJVOBLFC.js.map} +0 -0
  66. /package/dist/{chunk-7POTLB24.js.map → chunk-M2YGXCCQ.js.map} +0 -0
  67. /package/dist/{chunk-PQGTWFRF.js.map → chunk-M3SWH4MU.js.map} +0 -0
  68. /package/dist/{chunk-OMHBVHX7.js.map → chunk-ODKYBA3C.js.map} +0 -0
  69. /package/dist/{chunk-56MYWKQV.js.map → chunk-SKYRM7R7.js.map} +0 -0
@@ -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 the inventory stamp are also unsafe; in
2128
- -- particular INSERT can mint authority without a table grant.
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",
@@ -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("Session tenancy product surface is not activated for this organization", options);
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(