@opengeni/db 1.0.2 → 1.2.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 (86) 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-KH3TYDGY.js → chunk-3GAR5UN3.js} +2 -2
  5. package/dist/{chunk-UEQQ2WWU.js → chunk-3TGYFFZR.js} +3 -26
  6. package/dist/chunk-3TGYFFZR.js.map +1 -0
  7. package/dist/{chunk-F3YMVRF4.js → chunk-6EIEGHPK.js} +4 -4
  8. package/dist/{chunk-OQZOGCLG.js → chunk-CONWNUSL.js} +2 -2
  9. package/dist/{chunk-7S53WLPI.js → chunk-CYP72JTQ.js} +11 -4
  10. package/dist/{chunk-7S53WLPI.js.map → chunk-CYP72JTQ.js.map} +1 -1
  11. package/dist/{chunk-LDTQGYD2.js → chunk-FWQNHNOU.js} +54 -2
  12. package/dist/chunk-FWQNHNOU.js.map +1 -0
  13. package/dist/{chunk-KEDWSSNP.js → chunk-GDN5TSZP.js} +2 -2
  14. package/dist/{chunk-PQGTWFRF.js → chunk-JICUP7ZR.js} +2 -2
  15. package/dist/{chunk-NLSG7GGA.js → chunk-JLVWBIEM.js} +3 -3
  16. package/dist/{chunk-Y77PVZZD.js → chunk-LXPFNG4L.js} +2 -2
  17. package/dist/{chunk-56MYWKQV.js → chunk-O2Y5E5M3.js} +68 -32
  18. package/dist/chunk-O2Y5E5M3.js.map +1 -0
  19. package/dist/{chunk-7POTLB24.js → chunk-QFJNEXFL.js} +2 -2
  20. package/dist/{chunk-MACELCXG.js → chunk-UCIMHNII.js} +101 -21
  21. package/dist/chunk-UCIMHNII.js.map +1 -0
  22. package/dist/{chunk-OMHBVHX7.js → chunk-VDPCIFKL.js} +5 -5
  23. package/dist/{chunk-N5QCKHYL.js → chunk-YOYILDLY.js} +87 -19
  24. package/dist/chunk-YOYILDLY.js.map +1 -0
  25. package/dist/connect-attempts.js +3 -3
  26. package/dist/credit-balances.d.ts +18 -0
  27. package/dist/credit-promotion-policy.d.ts +10 -0
  28. package/dist/editable-artifact-durable-export.js +2 -2
  29. package/dist/editable-artifacts.js +3 -3
  30. package/dist/index.d.ts +35 -10
  31. package/dist/index.js +2432 -2255
  32. package/dist/index.js.map +1 -1
  33. package/dist/managed-auth-session-sets.js +3 -3
  34. package/dist/managed-user-setup.d.ts +1 -0
  35. package/dist/mcp-operations.js +2 -2
  36. package/dist/organization-integration-policy.js +3 -3
  37. package/dist/organization-membership-lifecycle.d.ts +1 -0
  38. package/dist/persistence-errors.d.ts +9 -6
  39. package/dist/provision-roles.js +1 -1
  40. package/dist/retained-provider-commands.js +6 -6
  41. package/dist/runtime-posture.d.ts +7 -6
  42. package/dist/schema.d.ts +214 -2
  43. package/dist/schema.js +5 -1
  44. package/dist/session-background-commands.js +4 -4
  45. package/dist/session-command-output.js +5 -5
  46. package/dist/session-event-slices.js +3 -3
  47. package/dist/session-mcp-credential-rotation.js +5 -5
  48. package/dist/session-tenancy.d.ts +6 -1
  49. package/dist/session-tenancy.js +3 -5
  50. package/dist/video-generation.js +3 -3
  51. package/dist/workspace-tool-defaults.js +4 -4
  52. package/drizzle/0610_artifact_catalog_pins.sql +165 -0
  53. package/drizzle/0611_universal_session_tenancy_activation.sql +166 -0
  54. package/drizzle/0612_scheduled_model_unavailable_refusal.sql +21 -0
  55. package/drizzle/0613_model_scoped_promotional_credits.sql +238 -0
  56. package/drizzle/0614_quiescence_command_containment.sql +7 -0
  57. package/drizzle/0615_credit_promotion_policy_validation.sql +63 -0
  58. package/package.json +7 -7
  59. package/src/artifact-catalog.ts +31 -5
  60. package/src/credit-balances.ts +109 -0
  61. package/src/credit-promotion-policy.ts +12 -0
  62. package/src/database.ts +81 -42
  63. package/src/index.ts +295 -79
  64. package/src/managed-user-setup.ts +8 -0
  65. package/src/organization-membership-lifecycle.ts +8 -0
  66. package/src/persistence-errors.ts +17 -6
  67. package/src/provision-roles.ts +25 -0
  68. package/src/runtime-posture-cli.ts +0 -2
  69. package/src/runtime-posture.ts +86 -27
  70. package/src/schema.ts +57 -1
  71. package/src/session-tenancy.ts +9 -31
  72. package/src/video-generation.ts +2 -7
  73. package/dist/chunk-56MYWKQV.js.map +0 -1
  74. package/dist/chunk-LDTQGYD2.js.map +0 -1
  75. package/dist/chunk-MACELCXG.js.map +0 -1
  76. package/dist/chunk-N5QCKHYL.js.map +0 -1
  77. package/dist/chunk-UEQQ2WWU.js.map +0 -1
  78. /package/dist/{chunk-KH3TYDGY.js.map → chunk-3GAR5UN3.js.map} +0 -0
  79. /package/dist/{chunk-F3YMVRF4.js.map → chunk-6EIEGHPK.js.map} +0 -0
  80. /package/dist/{chunk-OQZOGCLG.js.map → chunk-CONWNUSL.js.map} +0 -0
  81. /package/dist/{chunk-KEDWSSNP.js.map → chunk-GDN5TSZP.js.map} +0 -0
  82. /package/dist/{chunk-PQGTWFRF.js.map → chunk-JICUP7ZR.js.map} +0 -0
  83. /package/dist/{chunk-NLSG7GGA.js.map → chunk-JLVWBIEM.js.map} +0 -0
  84. /package/dist/{chunk-Y77PVZZD.js.map → chunk-LXPFNG4L.js.map} +0 -0
  85. /package/dist/{chunk-7POTLB24.js.map → chunk-QFJNEXFL.js.map} +0 -0
  86. /package/dist/{chunk-OMHBVHX7.js.map → chunk-VDPCIFKL.js.map} +0 -0
@@ -84,6 +84,7 @@ export async function completeSelfServiceOrganizationSetup(
84
84
  operationId: string;
85
85
  requestFingerprint: string;
86
86
  trialCreditsEnabled?: boolean;
87
+ trialCreditModelIds?: string[] | undefined;
87
88
  },
88
89
  ): Promise<CompleteSelfServiceOrganizationSetupResponseType> {
89
90
  return await db.transaction(async (tx) => {
@@ -99,6 +100,13 @@ export async function completeSelfServiceOrganizationSetup(
99
100
  ${input.trialCreditsEnabled === true ? "on" : "off"}, true
100
101
  )`,
101
102
  );
103
+ await rawRows(
104
+ txDb,
105
+ sql`select pg_catalog.set_config(
106
+ 'opengeni.signup_credit_model_ids',
107
+ ${input.trialCreditModelIds ? JSON.stringify(input.trialCreditModelIds) : ""}, true
108
+ )`,
109
+ );
102
110
  const [row] = await rawRows<{ result: unknown }>(
103
111
  txDb,
104
112
  sql`select complete_self_service_organization_setup(
@@ -60,6 +60,7 @@ export async function createManagedOrganization(
60
60
  name: string;
61
61
  operationId: string;
62
62
  trialCreditsEnabled?: boolean;
63
+ trialCreditModelIds?: string[] | undefined;
63
64
  },
64
65
  ): Promise<CreateOrganizationResponseType> {
65
66
  return await db.transaction(async (tx) => {
@@ -74,6 +75,13 @@ export async function createManagedOrganization(
74
75
  ${input.trialCreditsEnabled === true ? "on" : "off"}, true
75
76
  )`,
76
77
  );
78
+ await rawRows(
79
+ tx,
80
+ sql`select pg_catalog.set_config(
81
+ 'opengeni.signup_credit_model_ids',
82
+ ${input.trialCreditModelIds ? JSON.stringify(input.trialCreditModelIds) : ""}, true
83
+ )`,
84
+ );
77
85
  const [row] = await rawRows<{ result: unknown }>(
78
86
  tx,
79
87
  sql`select create_managed_organization(
@@ -213,12 +213,23 @@ export function safeDatabaseErrorFacts(error: unknown): SafeDatabaseErrorFacts {
213
213
  return facts;
214
214
  }
215
215
 
216
- /**
217
- * Typed persistence classification that retains the exact original failure as
218
- * `cause` for internal diagnostics. The ordinary Error message is deliberately
219
- * stable and source-free so a generic presentation path can never disclose SQL,
220
- * parameters, or driver detail by rendering `.message`.
221
- */
216
+ /** Own-driver transaction boundary; callback errors never establish provenance. */
217
+ export class DatabaseTransactionError extends Error {
218
+ readonly name = "DatabaseTransactionError";
219
+
220
+ constructor(
221
+ readonly stage: "admission" | "settlement",
222
+ cause: unknown,
223
+ // A rollback failure must not erase a callback's no-replay/permanent
224
+ // evidence. The recovery classifier inspects both branches for vetoes.
225
+ readonly original?: unknown,
226
+ ) {
227
+ super(`Database transaction ${stage} failed`, { cause });
228
+ }
229
+ }
230
+
231
+ /** Typed persistence classification retaining the original cause internally.
232
+ * Its ordinary message is stable and contains no SQL or driver parameters. */
222
233
  export class SessionEventPersistenceError extends Error {
223
234
  readonly name = "SessionEventPersistenceError";
224
235
 
@@ -1629,6 +1629,10 @@ BEGIN
1629
1629
  END IF;
1630
1630
  -- The trial-credit kill switch setter is operator-only (migration owner).
1631
1631
  -- Reprovisioning repairs any accidental runtime or PUBLIC grant.
1632
+ IF to_regprocedure(format('%I.set_credit_promotion_policy(jsonb,text,text)', ${literal(schema)})) IS NOT NULL THEN
1633
+ EXECUTE format('REVOKE ALL ON FUNCTION %I.set_credit_promotion_policy(jsonb,text,text) FROM PUBLIC', ${literal(schema)});
1634
+ EXECUTE format('REVOKE ALL ON FUNCTION %I.set_credit_promotion_policy(jsonb,text,text) FROM %I', ${literal(schema)}, ${literal(role)});
1635
+ END IF;
1632
1636
  IF to_regprocedure(
1633
1637
  format('%I.set_verified_signup_trial_credits_enabled(boolean,text,text)', ${literal(schema)})
1634
1638
  ) IS NOT NULL THEN
@@ -2110,6 +2114,11 @@ BEGIN
2110
2114
  REVOKE ALL (singleton, consent_enabled, release_evidence) ON TABLE opengeni_private.sandbox_recovery_rollout FROM PUBLIC;
2111
2115
  EXECUTE format('GRANT SELECT ON TABLE opengeni_private.sandbox_recovery_rollout TO %I', ${literal(role)});
2112
2116
  END IF;
2117
+ IF to_regclass('opengeni_private.credit_promotion_policy_revisions') IS NOT NULL THEN
2118
+ EXECUTE format('REVOKE ALL ON TABLE opengeni_private.credit_promotion_policy_revisions FROM %I', ${literal(role)});
2119
+ REVOKE ALL ON TABLE opengeni_private.credit_promotion_policy_revisions FROM PUBLIC;
2120
+ EXECUTE format('GRANT SELECT ON TABLE opengeni_private.credit_promotion_policy_revisions TO %I', ${literal(role)});
2121
+ END IF;
2113
2122
  IF to_regclass('opengeni_private.verified_signup_trial_switch_revisions') IS NOT NULL THEN
2114
2123
  -- Read-only for the operator gauge. Only the owner-only audited setter
2115
2124
  -- appends revisions; runtime identities and PUBLIC never write them.
@@ -2163,6 +2172,22 @@ BEGIN
2163
2172
  REVOKE ALL ON FUNCTION opengeni_private.record_sandbox_file_publication(uuid,uuid,uuid,uuid) FROM PUBLIC;
2164
2173
  REVOKE ALL ON FUNCTION opengeni_private.list_sandbox_file_publications(uuid,uuid,jsonb) FROM PUBLIC;
2165
2174
  END IF;
2175
+ IF to_regclass('opengeni_private.artifact_catalog_pins') IS NOT NULL THEN
2176
+ EXECUTE format('REVOKE ALL ON TABLE opengeni_private.artifact_catalog_pins FROM %I', ${literal(role)});
2177
+ -- Table revocation does not remove column ACLs. Reconcile every current
2178
+ -- column for both the runtime and PUBLIC, including future additions.
2179
+ EXECUTE format('REVOKE ALL (%s) ON TABLE opengeni_private.artifact_catalog_pins FROM %I',
2180
+ (SELECT string_agg(quote_ident(attname),',' ORDER BY attnum) FROM pg_attribute
2181
+ WHERE attrelid='opengeni_private.artifact_catalog_pins'::regclass AND attnum>0 AND NOT attisdropped),
2182
+ ${literal(role)});
2183
+ REVOKE ALL ON TABLE opengeni_private.artifact_catalog_pins FROM PUBLIC;
2184
+ EXECUTE format('REVOKE ALL (%s) ON TABLE opengeni_private.artifact_catalog_pins FROM PUBLIC',
2185
+ (SELECT string_agg(quote_ident(attname),',' ORDER BY attnum) FROM pg_attribute
2186
+ WHERE attrelid='opengeni_private.artifact_catalog_pins'::regclass AND attnum>0 AND NOT attisdropped));
2187
+ REVOKE ALL ON FUNCTION opengeni_private.update_artifact_pin(uuid,uuid,text,text,boolean),
2188
+ opengeni_private.list_artifact_pins(uuid,uuid),
2189
+ opengeni_private.list_sandbox_file_publications_pinned(uuid,uuid,jsonb) FROM PUBLIC;
2190
+ END IF;
2166
2191
  IF to_regclass('opengeni_private.slack_file_upload_operations') IS NOT NULL THEN
2167
2192
  -- Ordinary RLS repositories own the upload CAS, not owner capabilities.
2168
2193
  -- Never allow deletion/truncation to erase its durable completion fence.
@@ -25,8 +25,6 @@ try {
25
25
  rlsStrategy: settings.rlsStrategy,
26
26
  expectedRole: settings.runtimeDatabaseRole,
27
27
  targetSchema: settings.dbSchema.trim() || "public",
28
- organizationTenancyCanonicalActivationEnabled:
29
- settings.organizationTenancyCanonicalActivationEnabled,
30
28
  });
31
29
  // Structural evidence only: never print a connection string, secret, GUC, or
32
30
  // tenant row. The command is intended for release Jobs and audit artifacts.
@@ -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)",
@@ -788,11 +794,13 @@ export const RUNTIME_TARGET_SCHEMA_FORBIDDEN_ROUTINES = [
788
794
  SESSION_TENANCY_QUIESCENCE_ROUTINE,
789
795
  TENANCY_BACKFILL_ACTIVATION_EVIDENCE_ROUTINE,
790
796
  VERIFIED_SIGNUP_TRIAL_SWITCH_SETTER_ROUTINE,
797
+ "set_credit_promotion_policy(jsonb, text, text)",
791
798
  MANAGED_AUTH_NEW_SIGNUPS_SWITCH_SETTER_ROUTINE,
792
799
  ...DOCUMENT_MIGRATION_AUDIT_INTERNAL_ROUTINES,
793
800
  ] as const;
794
801
 
795
802
  export const RUNTIME_TARGET_SCHEMA_INVOKER_ROUTINES = [
803
+ "set_credit_promotion_policy(jsonb, text, text)",
796
804
  CLAUDE_SNAPSHOT_VALIDATOR_ROUTINE,
797
805
  "usage_allowance_period(jsonb, timestamp with time zone)",
798
806
  "validate_usage_allowance_config(jsonb)",
@@ -888,6 +896,7 @@ export const FORCE_RLS_TABLES = [
888
896
  "connections",
889
897
  "connector_action_policies",
890
898
  "connector_action_requests",
899
+ "credit_debit_allocations",
891
900
  "credit_ledger_entries",
892
901
  "device_enrollment_requests",
893
902
  "document_authority_reclassifications",
@@ -1470,6 +1479,7 @@ export const RUNTIME_READ_INSERT_TABLES = [
1470
1479
  "browser_revision_components",
1471
1480
  "browser_revisions",
1472
1481
  "company_profile_revisions",
1482
+ "credit_debit_allocations",
1473
1483
  "editable_artifact_blob_refs",
1474
1484
  "editable_artifact_idempotency_receipts",
1475
1485
  "editable_artifact_live_outbox",
@@ -1732,7 +1742,6 @@ export type RuntimeDatabasePostureOptions = {
1732
1742
  targetSchemaForbiddenRoutines?: readonly string[];
1733
1743
  /** Frozen binary contract; current callers require both additive Insights capabilities. */
1734
1744
  modelFactCapabilityRoutines?: readonly string[];
1735
- organizationTenancyCanonicalActivationEnabled?: boolean;
1736
1745
  };
1737
1746
 
1738
1747
  export type RuntimeDatabaseIdentity = {
@@ -1803,6 +1812,8 @@ export type RuntimePrivateTablePosture = {
1803
1812
  truncate?: boolean;
1804
1813
  references?: boolean;
1805
1814
  trigger?: boolean;
1815
+ /** Effective relation ACL privileges not represented by the named flags. */
1816
+ extraPrivileges?: string[];
1806
1817
  };
1807
1818
 
1808
1819
  export type RuntimeDatabasePosture = {
@@ -1816,7 +1827,6 @@ export type RuntimeDatabasePosture = {
1816
1827
  privateTables: RuntimePrivateTablePosture[];
1817
1828
  targetRoutines: RuntimeTargetRoutinePosture[];
1818
1829
  privateRoutines: RuntimeRoutinePosture[];
1819
- sessionTenancyProductActivationPresent: boolean;
1820
1830
  sessionVariableSetAttachmentsCutoverPresent: boolean;
1821
1831
  claudeSubscriptionPoolActivationPresent: boolean;
1822
1832
  };
@@ -1942,14 +1952,6 @@ export async function inspectRuntimeDatabasePosture(
1942
1952
  bypassRls: identity.rolbypassrls,
1943
1953
  };
1944
1954
 
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
1955
  const variableSetCutoverRows = resultRows<{ present: boolean }>(
1954
1956
  await tx.execute(sql`
1955
1957
  select to_regprocedure(
@@ -1980,7 +1982,6 @@ export async function inspectRuntimeDatabasePosture(
1980
1982
  privateTables: [],
1981
1983
  targetRoutines: [],
1982
1984
  privateRoutines: [],
1983
- sessionTenancyProductActivationPresent,
1984
1985
  sessionVariableSetAttachmentsCutoverPresent,
1985
1986
  claudeSubscriptionPoolActivationPresent,
1986
1987
  };
@@ -2115,6 +2116,7 @@ export async function inspectRuntimeDatabasePosture(
2115
2116
  can_truncate: boolean;
2116
2117
  can_references: boolean;
2117
2118
  can_trigger: boolean;
2119
+ extra_privileges: string[];
2118
2120
  }>(
2119
2121
  await tx.execute(sql`
2120
2122
  select
@@ -2124,25 +2126,31 @@ export async function inspectRuntimeDatabasePosture(
2124
2126
  c.relforcerowsecurity as rls_forced,
2125
2127
  row_security_active(c.oid) as rls_active,
2126
2128
  (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.
2129
+ -- Column-only grants on capability tables are also unsafe; a pin
2130
+ -- table must remain EXECUTE-only even after column ACL drift.
2129
2131
  (has_table_privilege(current_user, c.oid, 'SELECT') or
2130
- ((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
2131
2133
  c.relname in (select jsonb_array_elements_text(${JSON.stringify(RUNTIME_ALLOWANCE_PRIVATE_TABLES)}::jsonb))) and
2132
2134
  has_any_column_privilege(current_user, c.oid, 'SELECT'))) as can_select,
2133
2135
  (has_table_privilege(current_user, c.oid, 'INSERT') or
2134
- ((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
2135
2137
  c.relname in (select jsonb_array_elements_text(${JSON.stringify(RUNTIME_ALLOWANCE_PRIVATE_TABLES)}::jsonb))) and
2136
2138
  has_any_column_privilege(current_user, c.oid, 'INSERT'))) as can_insert,
2137
2139
  (has_table_privilege(current_user, c.oid, 'UPDATE') or
2138
- ((c.relname in ('modal_inventory_read_capabilities','usage_allowance_capabilities','session_import_batches') or
2140
+ ((c.relname in ('modal_inventory_read_capabilities','usage_allowance_capabilities','session_import_batches', ${ARTIFACT_PINS_TABLE}) or
2139
2141
  c.relname in (select jsonb_array_elements_text(${JSON.stringify(RUNTIME_ALLOWANCE_PRIVATE_TABLES)}::jsonb))) and
2140
2142
  has_any_column_privilege(current_user, c.oid, 'UPDATE'))) as can_update,
2141
2143
  has_table_privilege(current_user, c.oid, 'DELETE') as can_delete,
2142
2144
  has_table_privilege(current_user, c.oid, 'TRUNCATE') as can_truncate,
2143
2145
  (has_table_privilege(current_user, c.oid, 'REFERENCES') or
2144
2146
  has_any_column_privilege(current_user, c.oid, 'REFERENCES')) as can_references,
2145
- has_table_privilege(current_user, c.oid, 'TRIGGER') as can_trigger
2147
+ has_table_privilege(current_user, c.oid, 'TRIGGER') as can_trigger,
2148
+ CASE WHEN c.relname = ${ARTIFACT_PINS_TABLE} THEN ARRAY(
2149
+ SELECT DISTINCT acl.privilege_type FROM aclexplode(coalesce(c.relacl, acldefault('r', c.relowner))) acl
2150
+ WHERE acl.privilege_type NOT IN ('SELECT','INSERT','UPDATE','DELETE','TRUNCATE','REFERENCES','TRIGGER')
2151
+ AND CASE WHEN acl.grantee = 0 THEN true ELSE pg_has_role(current_user, acl.grantee, 'USAGE') END
2152
+ ORDER BY acl.privilege_type
2153
+ ) ELSE ARRAY[]::text[] END AS extra_privileges
2146
2154
  from pg_class c
2147
2155
  join pg_namespace n on n.oid = c.relnamespace
2148
2156
  where n.nspname = 'opengeni_private'
@@ -2156,6 +2164,7 @@ export async function inspectRuntimeDatabasePosture(
2156
2164
  ${SCOPED_COMPUTE_CAPABILITY_TABLE},
2157
2165
  ${CONNECTION_TENANCY_BACKFILL_CAPABILITY_TABLE},
2158
2166
  ${SANDBOX_FILE_PUBLICATIONS_TABLE},
2167
+ ${ARTIFACT_PINS_TABLE},
2159
2168
  ${SCHEDULED_SLACK_BOT_MESSAGES_TABLE},
2160
2169
  ${ORGANIZATION_SIGNUP_USE_CASES_TABLE},
2161
2170
  ${SLACK_FILE_UPLOAD_OPERATIONS_TABLE},
@@ -2176,6 +2185,7 @@ export async function inspectRuntimeDatabasePosture(
2176
2185
  'modal_inventory_read_capabilities',
2177
2186
  ${AUTOMATIC_SESSION_TITLE_FANOUT_OUTBOX_TABLE},
2178
2187
  ${VERIFIED_SIGNUP_TRIAL_SWITCH_TABLE},
2188
+ 'credit_promotion_policy_revisions',
2179
2189
  ${MANAGED_AUTH_NEW_SIGNUPS_SWITCH_TABLE}
2180
2190
  )
2181
2191
  `),
@@ -2193,6 +2203,7 @@ export async function inspectRuntimeDatabasePosture(
2193
2203
  truncate: row.can_truncate,
2194
2204
  references: row.can_references,
2195
2205
  trigger: row.can_trigger,
2206
+ ...(row.extra_privileges?.length ? { extraPrivileges: row.extra_privileges } : {}),
2196
2207
  }));
2197
2208
 
2198
2209
  const targetRoutines = resultRows<{
@@ -2284,7 +2295,6 @@ export async function inspectRuntimeDatabasePosture(
2284
2295
  privateTables,
2285
2296
  targetRoutines,
2286
2297
  privateRoutines,
2287
- sessionTenancyProductActivationPresent,
2288
2298
  sessionVariableSetAttachmentsCutoverPresent,
2289
2299
  claudeSubscriptionPoolActivationPresent,
2290
2300
  };
@@ -2321,15 +2331,6 @@ export function evaluateRuntimeDatabasePosture(
2321
2331
  violations.push("database is missing the 0352 session Variable Set attachment runtime receipt");
2322
2332
  }
2323
2333
 
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
2334
  if (!identity.currentUser || !identity.sessionUser) {
2334
2335
  violations.push("database identity is empty");
2335
2336
  }
@@ -3756,6 +3757,23 @@ export function evaluateRuntimeDatabasePosture(
3756
3757
 
3757
3758
  // The trial-credit kill switch is operator state. Runtime roles may read it
3758
3759
  // for the gauge (SELECT is optional) but must never append or rewrite it.
3760
+ const creditPolicyTable = posture.privateTables.find(
3761
+ (table) => table.name === "credit_promotion_policy_revisions",
3762
+ );
3763
+ if (!creditPolicyTable) {
3764
+ violations.push("credit promotion policy table is missing");
3765
+ } else if (!creditPolicyTable.select) {
3766
+ violations.push("runtime role cannot read the credit promotion policy");
3767
+ }
3768
+ if (
3769
+ creditPolicyTable &&
3770
+ (creditPolicyTable.owner === expectedRole ||
3771
+ creditPolicyTable.insert ||
3772
+ creditPolicyTable.update ||
3773
+ creditPolicyTable.delete)
3774
+ ) {
3775
+ violations.push("runtime role has forbidden write authority on the credit promotion policy");
3776
+ }
3759
3777
  const trialSwitchTable = posture.privateTables.find(
3760
3778
  (table) => table.name === VERIFIED_SIGNUP_TRIAL_SWITCH_TABLE,
3761
3779
  );
@@ -3886,6 +3904,47 @@ export function evaluateRuntimeDatabasePosture(
3886
3904
  }
3887
3905
  }
3888
3906
 
3907
+ const pinTables = posture.privateTables.filter((table) => table.name === ARTIFACT_PINS_TABLE);
3908
+ if (pinTables.length !== 1) {
3909
+ if (!options.protectedTables)
3910
+ violations.push("artifact pin private relation is missing or ambiguous");
3911
+ } else {
3912
+ const table = pinTables[0]!;
3913
+ if (!table.rlsEnabled || !table.rlsForced || !table.rlsActive || (table.policyCount ?? 0) < 1)
3914
+ violations.push("artifact pin relation lacks active FORCE-RLS workspace isolation");
3915
+ if (
3916
+ table.select ||
3917
+ table.insert ||
3918
+ table.update ||
3919
+ table.delete ||
3920
+ table.truncate ||
3921
+ table.references ||
3922
+ table.trigger ||
3923
+ table.extraPrivileges?.length ||
3924
+ table.owner === expectedRole
3925
+ )
3926
+ violations.push("runtime role has forbidden direct artifact pin authority");
3927
+ if (tableByName.get("files")?.owner && table.owner !== tableByName.get("files")!.owner)
3928
+ violations.push("artifact pin owner does not match workspace authority");
3929
+ const quotedSchema = `"${targetSchema.replaceAll('"', '""')}"`;
3930
+ const searchPaths = new Set([
3931
+ `search_path=pg_catalog, ${quotedSchema}, pg_temp`,
3932
+ `search_path=pg_catalog, ${/^[a-z_][a-z0-9_]*$/.test(targetSchema) ? targetSchema : quotedSchema}, pg_temp`,
3933
+ ]);
3934
+ for (const name of ARTIFACT_PIN_RUNTIME_ROUTINES) {
3935
+ const routines = posture.privateRoutines.filter((routine) => routine.name === name);
3936
+ if (
3937
+ routines.length !== 1 ||
3938
+ !routines[0]!.execute ||
3939
+ routines[0]!.publicExecute ||
3940
+ !routines[0]!.securityDefiner ||
3941
+ routines[0]!.owner !== table.owner ||
3942
+ !routines[0]!.configuration?.some((configuration) => searchPaths.has(configuration))
3943
+ )
3944
+ violations.push(`artifact pin capability ${name} is missing or unsafe`);
3945
+ }
3946
+ }
3947
+
3889
3948
  const slackFileUploadTables = posture.privateTables.filter(
3890
3949
  (table) => table.name === SLACK_FILE_UPLOAD_OPERATIONS_TABLE,
3891
3950
  );
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",
@@ -9663,7 +9680,7 @@ export const sandboxLeases = pgTable(
9663
9680
  // Why an enrolled drain contains its commands (0547); cleared with the
9664
9681
  // enrollment. Null for a pre-0547 enrollment: settle with neutral wording.
9665
9682
  commandContainmentReason: text("command_containment_reason", {
9666
- enum: ["idle_containment", "provider_deadline_containment"],
9683
+ enum: ["idle_containment", "provider_deadline_containment", "quiescence_containment"],
9667
9684
  }),
9668
9685
  liveness: text("liveness", { enum: sandboxLeaseLivenessValues }).notNull().default("cold"),
9669
9686
  refcount: integer("refcount").notNull().default(0),
@@ -12611,6 +12628,8 @@ export const creditLedgerEntries = pgTable(
12611
12628
  }),
12612
12629
  type: text("type").notNull(),
12613
12630
  amountMicros: bigint("amount_micros", { mode: "number" }).notNull(),
12631
+ /** Null preserves the unrestricted terms of legacy credits. */
12632
+ eligibleModelIds: text("eligible_model_ids").array(),
12614
12633
  currency: text("currency").notNull().default("usd"),
12615
12634
  sourceType: text("source_type"),
12616
12635
  sourceId: text("source_id"),
@@ -12621,6 +12640,19 @@ export const creditLedgerEntries = pgTable(
12621
12640
  },
12622
12641
  (table) => ({
12623
12642
  idempotency: uniqueIndex("credit_ledger_entries_idempotency_idx").on(table.idempotencyKey),
12643
+ idAccount: uniqueIndex("credit_ledger_entries_id_account_idx").on(table.id, table.accountId),
12644
+ scopedGrants: index("credit_ledger_scoped_grants_idx")
12645
+ .on(table.accountId, table.createdAt, table.id)
12646
+ .where(sql`${table.eligibleModelIds} is not null`),
12647
+ scopeValid: check(
12648
+ "credit_ledger_scope_valid",
12649
+ sql`${table.eligibleModelIds} is null or (
12650
+ ${table.type} = 'grant' and ${table.amountMicros} > 0
12651
+ and cardinality(${table.eligibleModelIds}) between 1 and 40
12652
+ and array_position(${table.eligibleModelIds}, null) is null
12653
+ and array_position(${table.eligibleModelIds}, '') is null
12654
+ )`,
12655
+ ),
12624
12656
  modelDebitPeriod: index("credit_ledger_entries_model_debit_period_idx")
12625
12657
  .on(table.accountId, table.occurredAt, table.workspaceId)
12626
12658
  .where(
@@ -12633,6 +12665,30 @@ export const creditLedgerEntries = pgTable(
12633
12665
  }),
12634
12666
  );
12635
12667
 
12668
+ /** One debit can consume several grants; the remainder is paid by general credits. */
12669
+ export const creditDebitAllocations = pgTable(
12670
+ "credit_debit_allocations",
12671
+ {
12672
+ accountId: uuid("account_id").notNull(),
12673
+ debitEntryId: uuid("debit_entry_id").notNull(),
12674
+ grantEntryId: uuid("grant_entry_id").notNull(),
12675
+ amountMicros: bigint("amount_micros", { mode: "number" }).notNull(),
12676
+ },
12677
+ (table) => ({
12678
+ pk: primaryKey({ columns: [table.debitEntryId, table.grantEntryId] }),
12679
+ debit: foreignKey({
12680
+ columns: [table.debitEntryId, table.accountId],
12681
+ foreignColumns: [creditLedgerEntries.id, creditLedgerEntries.accountId],
12682
+ }).onDelete("cascade"),
12683
+ grant: foreignKey({
12684
+ columns: [table.grantEntryId, table.accountId],
12685
+ foreignColumns: [creditLedgerEntries.id, creditLedgerEntries.accountId],
12686
+ }).onDelete("cascade"),
12687
+ grantIndex: index("credit_debit_allocations_grant_idx").on(table.accountId, table.grantEntryId),
12688
+ positive: check("credit_debit_allocations_positive", sql`${table.amountMicros} > 0`),
12689
+ }),
12690
+ );
12691
+
12636
12692
  /** Content-free immutable accepted billing facts; no runtime table privileges. */
12637
12693
  export const usageAllowanceAttributionReceipts = opengeniPrivateSchema.table(
12638
12694
  "usage_allowance_attribution_receipts",
@@ -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(
@@ -17,6 +17,7 @@ import { rawRows, withRlsContext, withWorkspaceRls } from "./database";
17
17
  import * as schema from "./schema";
18
18
  import { checkWorkspaceAllowance } from "./usage-allowances";
19
19
  import { creditDebitAttributionForTurn } from "./credit-debit-attribution";
20
+ import { getSpendableCreditBalance } from "./credit-balances";
20
21
 
21
22
  export const ACTIVE_VIDEO_GENERATION_STATUSES = [
22
23
  "preparing",
@@ -1381,13 +1382,7 @@ async function debitVideoGenerationCredits(
1381
1382
  subjectId: attribution.kind === "turn" ? attribution.initiatingHumanSubjectId : null,
1382
1383
  });
1383
1384
  if (refusal) throw Object.assign(new VideoGenerationCreditError(refusal.message), refusal);
1384
- const [balanceRow] = await tx
1385
- .select({
1386
- balanceMicros: sql<number>`coalesce(sum(${schema.creditLedgerEntries.amountMicros}), 0)`,
1387
- })
1388
- .from(schema.creditLedgerEntries)
1389
- .where(eq(schema.creditLedgerEntries.accountId, operation.accountId));
1390
- const balanceMicros = Number(balanceRow?.balanceMicros ?? 0);
1385
+ const { balanceMicros } = await getSpendableCreditBalance(tx, operation.accountId);
1391
1386
  if (!Number.isSafeInteger(balanceMicros) || balanceMicros < operation.pricedCostMicros) {
1392
1387
  throw new VideoGenerationCreditError();
1393
1388
  }