@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.
- 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-KH3TYDGY.js → chunk-3GAR5UN3.js} +2 -2
- package/dist/{chunk-UEQQ2WWU.js → chunk-3TGYFFZR.js} +3 -26
- package/dist/chunk-3TGYFFZR.js.map +1 -0
- package/dist/{chunk-F3YMVRF4.js → chunk-6EIEGHPK.js} +4 -4
- package/dist/{chunk-OQZOGCLG.js → chunk-CONWNUSL.js} +2 -2
- package/dist/{chunk-7S53WLPI.js → chunk-CYP72JTQ.js} +11 -4
- package/dist/{chunk-7S53WLPI.js.map → chunk-CYP72JTQ.js.map} +1 -1
- package/dist/{chunk-LDTQGYD2.js → chunk-FWQNHNOU.js} +54 -2
- package/dist/chunk-FWQNHNOU.js.map +1 -0
- package/dist/{chunk-KEDWSSNP.js → chunk-GDN5TSZP.js} +2 -2
- package/dist/{chunk-PQGTWFRF.js → chunk-JICUP7ZR.js} +2 -2
- package/dist/{chunk-NLSG7GGA.js → chunk-JLVWBIEM.js} +3 -3
- package/dist/{chunk-Y77PVZZD.js → chunk-LXPFNG4L.js} +2 -2
- package/dist/{chunk-56MYWKQV.js → chunk-O2Y5E5M3.js} +68 -32
- package/dist/chunk-O2Y5E5M3.js.map +1 -0
- package/dist/{chunk-7POTLB24.js → chunk-QFJNEXFL.js} +2 -2
- package/dist/{chunk-MACELCXG.js → chunk-UCIMHNII.js} +101 -21
- package/dist/chunk-UCIMHNII.js.map +1 -0
- package/dist/{chunk-OMHBVHX7.js → chunk-VDPCIFKL.js} +5 -5
- package/dist/{chunk-N5QCKHYL.js → chunk-YOYILDLY.js} +87 -19
- package/dist/chunk-YOYILDLY.js.map +1 -0
- package/dist/connect-attempts.js +3 -3
- package/dist/credit-balances.d.ts +18 -0
- package/dist/credit-promotion-policy.d.ts +10 -0
- package/dist/editable-artifact-durable-export.js +2 -2
- package/dist/editable-artifacts.js +3 -3
- package/dist/index.d.ts +35 -10
- package/dist/index.js +2432 -2255
- package/dist/index.js.map +1 -1
- package/dist/managed-auth-session-sets.js +3 -3
- package/dist/managed-user-setup.d.ts +1 -0
- package/dist/mcp-operations.js +2 -2
- package/dist/organization-integration-policy.js +3 -3
- package/dist/organization-membership-lifecycle.d.ts +1 -0
- package/dist/persistence-errors.d.ts +9 -6
- package/dist/provision-roles.js +1 -1
- package/dist/retained-provider-commands.js +6 -6
- package/dist/runtime-posture.d.ts +7 -6
- package/dist/schema.d.ts +214 -2
- package/dist/schema.js +5 -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/drizzle/0613_model_scoped_promotional_credits.sql +238 -0
- package/drizzle/0614_quiescence_command_containment.sql +7 -0
- package/drizzle/0615_credit_promotion_policy_validation.sql +63 -0
- package/package.json +7 -7
- package/src/artifact-catalog.ts +31 -5
- package/src/credit-balances.ts +109 -0
- package/src/credit-promotion-policy.ts +12 -0
- package/src/database.ts +81 -42
- package/src/index.ts +295 -79
- package/src/managed-user-setup.ts +8 -0
- package/src/organization-membership-lifecycle.ts +8 -0
- package/src/persistence-errors.ts +17 -6
- package/src/provision-roles.ts +25 -0
- package/src/runtime-posture-cli.ts +0 -2
- package/src/runtime-posture.ts +86 -27
- package/src/schema.ts +57 -1
- package/src/session-tenancy.ts +9 -31
- package/src/video-generation.ts +2 -7
- package/dist/chunk-56MYWKQV.js.map +0 -1
- package/dist/chunk-LDTQGYD2.js.map +0 -1
- package/dist/chunk-MACELCXG.js.map +0 -1
- package/dist/chunk-N5QCKHYL.js.map +0 -1
- package/dist/chunk-UEQQ2WWU.js.map +0 -1
- /package/dist/{chunk-KH3TYDGY.js.map → chunk-3GAR5UN3.js.map} +0 -0
- /package/dist/{chunk-F3YMVRF4.js.map → chunk-6EIEGHPK.js.map} +0 -0
- /package/dist/{chunk-OQZOGCLG.js.map → chunk-CONWNUSL.js.map} +0 -0
- /package/dist/{chunk-KEDWSSNP.js.map → chunk-GDN5TSZP.js.map} +0 -0
- /package/dist/{chunk-PQGTWFRF.js.map → chunk-JICUP7ZR.js.map} +0 -0
- /package/dist/{chunk-NLSG7GGA.js.map → chunk-JLVWBIEM.js.map} +0 -0
- /package/dist/{chunk-Y77PVZZD.js.map → chunk-LXPFNG4L.js.map} +0 -0
- /package/dist/{chunk-7POTLB24.js.map → chunk-QFJNEXFL.js.map} +0 -0
- /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
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
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
|
|
package/src/provision-roles.ts
CHANGED
|
@@ -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.
|
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)",
|
|
@@ -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
|
|
2128
|
-
--
|
|
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",
|
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(
|
package/src/video-generation.ts
CHANGED
|
@@ -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
|
|
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
|
}
|