@opengeni/db 1.1.0 → 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/canonical-human-identities.js +3 -3
- package/dist/capability-reconciliation.js +2 -2
- package/dist/{chunk-J4WFOVBV.js → chunk-3GAR5UN3.js} +2 -2
- package/dist/{chunk-Z5HJXH3X.js → chunk-3TGYFFZR.js} +2 -2
- package/dist/{chunk-GLA5YCQS.js → chunk-6EIEGHPK.js} +4 -4
- package/dist/{chunk-BLUQG54M.js → chunk-CONWNUSL.js} +2 -2
- package/dist/{chunk-EKXCF3FH.js → chunk-CYP72JTQ.js} +11 -4
- package/dist/{chunk-EKXCF3FH.js.map → chunk-CYP72JTQ.js.map} +1 -1
- package/dist/{chunk-VOB6TXSJ.js → chunk-FWQNHNOU.js} +39 -2
- package/dist/chunk-FWQNHNOU.js.map +1 -0
- package/dist/{chunk-7Z7ALFRE.js → chunk-GDN5TSZP.js} +2 -2
- package/dist/{chunk-M3SWH4MU.js → chunk-JICUP7ZR.js} +2 -2
- package/dist/{chunk-KJVOBLFC.js → chunk-JLVWBIEM.js} +3 -3
- package/dist/{chunk-BZV5ZDRY.js → chunk-LXPFNG4L.js} +2 -2
- package/dist/{chunk-SKYRM7R7.js → chunk-O2Y5E5M3.js} +68 -32
- package/dist/chunk-O2Y5E5M3.js.map +1 -0
- package/dist/{chunk-M2YGXCCQ.js → chunk-QFJNEXFL.js} +2 -2
- package/dist/{chunk-CIGHXFPD.js → chunk-UCIMHNII.js} +101 -21
- package/dist/chunk-UCIMHNII.js.map +1 -0
- package/dist/{chunk-ODKYBA3C.js → chunk-VDPCIFKL.js} +5 -5
- package/dist/{chunk-T5NHDEZW.js → chunk-YOYILDLY.js} +26 -1
- 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 +21 -5
- package/dist/index.js +2369 -2247
- 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 +4 -4
- package/dist/schema.d.ts +121 -2
- package/dist/schema.js +3 -1
- package/dist/session-background-commands.js +4 -4
- package/dist/session-command-output.js +5 -5
- package/dist/session-event-slices.js +3 -3
- package/dist/session-mcp-credential-rotation.js +5 -5
- package/dist/session-tenancy.js +3 -3
- package/dist/video-generation.js +3 -3
- package/dist/workspace-tool-defaults.js +4 -4
- 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/credit-balances.ts +109 -0
- package/src/credit-promotion-policy.ts +12 -0
- package/src/database.ts +81 -42
- package/src/index.ts +201 -60
- 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 +9 -0
- package/src/runtime-posture.ts +22 -0
- package/src/schema.ts +40 -1
- package/src/video-generation.ts +2 -7
- package/dist/chunk-CIGHXFPD.js.map +0 -1
- package/dist/chunk-SKYRM7R7.js.map +0 -1
- package/dist/chunk-T5NHDEZW.js.map +0 -1
- package/dist/chunk-VOB6TXSJ.js.map +0 -1
- /package/dist/{chunk-J4WFOVBV.js.map → chunk-3GAR5UN3.js.map} +0 -0
- /package/dist/{chunk-Z5HJXH3X.js.map → chunk-3TGYFFZR.js.map} +0 -0
- /package/dist/{chunk-GLA5YCQS.js.map → chunk-6EIEGHPK.js.map} +0 -0
- /package/dist/{chunk-BLUQG54M.js.map → chunk-CONWNUSL.js.map} +0 -0
- /package/dist/{chunk-7Z7ALFRE.js.map → chunk-GDN5TSZP.js.map} +0 -0
- /package/dist/{chunk-M3SWH4MU.js.map → chunk-JICUP7ZR.js.map} +0 -0
- /package/dist/{chunk-KJVOBLFC.js.map → chunk-JLVWBIEM.js.map} +0 -0
- /package/dist/{chunk-BZV5ZDRY.js.map → chunk-LXPFNG4L.js.map} +0 -0
- /package/dist/{chunk-M2YGXCCQ.js.map → chunk-QFJNEXFL.js.map} +0 -0
- /package/dist/{chunk-ODKYBA3C.js.map → chunk-VDPCIFKL.js.map} +0 -0
package/src/index.ts
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
export { readCreditPromotionPolicy } from "./credit-promotion-policy";
|
|
2
|
+
import { getBillingBalance, getSpendableCreditBalance, planCreditDebit } from "./credit-balances";
|
|
3
|
+
export {
|
|
4
|
+
getBillingBalance,
|
|
5
|
+
getSpendableCreditBalance,
|
|
6
|
+
spendableCreditMicros,
|
|
7
|
+
} from "./credit-balances";
|
|
1
8
|
import {
|
|
2
9
|
agentSelectionNotes,
|
|
3
10
|
loadInboxExecutionContext,
|
|
@@ -616,6 +623,7 @@ import {
|
|
|
616
623
|
SessionEventPersistenceError,
|
|
617
624
|
type IdempotentPersistenceTransactionOptions,
|
|
618
625
|
} from "./persistence-errors";
|
|
626
|
+
export { DatabaseTransactionError } from "./persistence-errors";
|
|
619
627
|
import {
|
|
620
628
|
assertCapabilityComponentVersionCanChange,
|
|
621
629
|
effectiveCapabilityOwnerSql,
|
|
@@ -5701,6 +5709,7 @@ export async function applyCreditLedgerEntry(
|
|
|
5701
5709
|
workspaceId?: string | null;
|
|
5702
5710
|
type: string;
|
|
5703
5711
|
amountMicros: number;
|
|
5712
|
+
eligibleModelIds?: string[] | undefined;
|
|
5704
5713
|
sourceType?: string | null;
|
|
5705
5714
|
sourceId?: string | null;
|
|
5706
5715
|
idempotencyKey: string;
|
|
@@ -5719,6 +5728,7 @@ export async function applyCreditLedgerEntry(
|
|
|
5719
5728
|
workspaceId: input.workspaceId ?? null,
|
|
5720
5729
|
type: input.type,
|
|
5721
5730
|
amountMicros: input.amountMicros,
|
|
5731
|
+
eligibleModelIds: input.eligibleModelIds ?? null,
|
|
5722
5732
|
sourceType: input.sourceType ?? null,
|
|
5723
5733
|
sourceId: input.sourceId ?? null,
|
|
5724
5734
|
idempotencyKey: input.idempotencyKey,
|
|
@@ -5740,6 +5750,9 @@ export async function applyCreditDebitUpToBalance(
|
|
|
5740
5750
|
workspaceId?: string | null;
|
|
5741
5751
|
type: string;
|
|
5742
5752
|
requestedAmountMicros: number;
|
|
5753
|
+
/** Canonical, accepted model ID. Omitted for non-model usage. */
|
|
5754
|
+
modelId?: string;
|
|
5755
|
+
creditPolicyRevision?: number | undefined;
|
|
5743
5756
|
sourceType?: string | null;
|
|
5744
5757
|
sourceId?: string | null;
|
|
5745
5758
|
idempotencyKey: string;
|
|
@@ -5758,35 +5771,42 @@ export async function applyCreditDebitUpToBalance(
|
|
|
5758
5771
|
{ accountId: input.accountId, workspaceId: input.workspaceId ?? null },
|
|
5759
5772
|
async (scopedDb) => {
|
|
5760
5773
|
await scopedDb.execute(sql`select pg_advisory_xact_lock(hashtext(${input.accountId}))`);
|
|
5761
|
-
const before = await getBillingBalance(scopedDb, input.accountId);
|
|
5762
|
-
const
|
|
5763
|
-
|
|
5764
|
-
|
|
5765
|
-
|
|
5766
|
-
|
|
5767
|
-
|
|
5768
|
-
|
|
5769
|
-
.
|
|
5770
|
-
.
|
|
5774
|
+
const before = await getBillingBalance(scopedDb, input.accountId, input.creditPolicyRevision);
|
|
5775
|
+
const plan = planCreditDebit(before, input.requestedAmountMicros, input.modelId);
|
|
5776
|
+
const candidateDebitMicros = plan.debitedMicros;
|
|
5777
|
+
// Keep a zero-cost receipt too: retrying after a top-up must not charge
|
|
5778
|
+
// a response that was already settled while the eligible balance was empty.
|
|
5779
|
+
const [inserted] = await scopedDb
|
|
5780
|
+
.insert(schema.creditLedgerEntries)
|
|
5781
|
+
.values({
|
|
5782
|
+
accountId: input.accountId,
|
|
5783
|
+
workspaceId: input.workspaceId ?? null,
|
|
5784
|
+
type: input.type,
|
|
5785
|
+
amountMicros: -candidateDebitMicros,
|
|
5786
|
+
sourceType: input.sourceType ?? null,
|
|
5787
|
+
sourceId: input.sourceId ?? null,
|
|
5788
|
+
idempotencyKey: input.idempotencyKey,
|
|
5789
|
+
metadata: {
|
|
5790
|
+
...input.metadata,
|
|
5791
|
+
requestedAmountMicros: input.requestedAmountMicros,
|
|
5792
|
+
debitedMicros: candidateDebitMicros,
|
|
5793
|
+
creditPolicyRevision: before.creditPolicyRevision,
|
|
5794
|
+
},
|
|
5795
|
+
occurredAt: input.occurredAt ?? new Date(),
|
|
5796
|
+
})
|
|
5797
|
+
.onConflictDoNothing({
|
|
5798
|
+
target: schema.creditLedgerEntries.idempotencyKey,
|
|
5799
|
+
})
|
|
5800
|
+
.returning({ id: schema.creditLedgerEntries.id });
|
|
5801
|
+
const debitedMicros = inserted ? candidateDebitMicros : 0;
|
|
5802
|
+
if (inserted && plan.allocations.length) {
|
|
5803
|
+
await scopedDb.insert(schema.creditDebitAllocations).values(
|
|
5804
|
+
plan.allocations.map((allocation) => ({
|
|
5805
|
+
...allocation,
|
|
5771
5806
|
accountId: input.accountId,
|
|
5772
|
-
|
|
5773
|
-
|
|
5774
|
-
|
|
5775
|
-
sourceType: input.sourceType ?? null,
|
|
5776
|
-
sourceId: input.sourceId ?? null,
|
|
5777
|
-
idempotencyKey: input.idempotencyKey,
|
|
5778
|
-
metadata: {
|
|
5779
|
-
...input.metadata,
|
|
5780
|
-
requestedAmountMicros: input.requestedAmountMicros,
|
|
5781
|
-
debitedMicros: candidateDebitMicros,
|
|
5782
|
-
},
|
|
5783
|
-
occurredAt: input.occurredAt ?? new Date(),
|
|
5784
|
-
})
|
|
5785
|
-
.onConflictDoNothing({
|
|
5786
|
-
target: schema.creditLedgerEntries.idempotencyKey,
|
|
5787
|
-
})
|
|
5788
|
-
.returning({ id: schema.creditLedgerEntries.id });
|
|
5789
|
-
debitedMicros = inserted.length === 1 ? candidateDebitMicros : 0;
|
|
5807
|
+
debitEntryId: inserted.id,
|
|
5808
|
+
})),
|
|
5809
|
+
);
|
|
5790
5810
|
}
|
|
5791
5811
|
return {
|
|
5792
5812
|
balance: await getBillingBalance(scopedDb, input.accountId),
|
|
@@ -6019,23 +6039,6 @@ export async function markStripeWebhookProcessed(db: Database, id: string): Prom
|
|
|
6019
6039
|
.where(eq(schema.stripeWebhookEvents.id, id));
|
|
6020
6040
|
}
|
|
6021
6041
|
|
|
6022
|
-
export async function getBillingBalance(db: Database, accountId: string): Promise<BillingBalance> {
|
|
6023
|
-
return await withAccountRls(db, accountId, async (scopedDb) => {
|
|
6024
|
-
const [{ balance } = { balance: 0 }] = await scopedDb
|
|
6025
|
-
.select({
|
|
6026
|
-
balance: sql<number>`coalesce(sum(${schema.creditLedgerEntries.amountMicros}), 0)`,
|
|
6027
|
-
})
|
|
6028
|
-
.from(schema.creditLedgerEntries)
|
|
6029
|
-
.where(eq(schema.creditLedgerEntries.accountId, accountId));
|
|
6030
|
-
return {
|
|
6031
|
-
accountId,
|
|
6032
|
-
balanceMicros: Number(balance),
|
|
6033
|
-
currency: "usd",
|
|
6034
|
-
updatedAt: new Date().toISOString(),
|
|
6035
|
-
};
|
|
6036
|
-
});
|
|
6037
|
-
}
|
|
6038
|
-
|
|
6039
6042
|
/**
|
|
6040
6043
|
* Whether the organization holds a positive OpenGeni credit balance, whatever
|
|
6041
6044
|
* its source: a purchase, an operator grant, a test credit, or the one-time
|
|
@@ -47853,7 +47856,7 @@ async function acquireLeaseOnce(
|
|
|
47853
47856
|
refusal,
|
|
47854
47857
|
);
|
|
47855
47858
|
}
|
|
47856
|
-
const balance = await
|
|
47859
|
+
const balance = await getSpendableCreditBalance(tx, accountId);
|
|
47857
47860
|
if (balance.balanceMicros <= 0) {
|
|
47858
47861
|
throw new SandboxPaidComputeAdmissionError(workspaceId, sandboxGroupId);
|
|
47859
47862
|
}
|
|
@@ -52814,8 +52817,8 @@ type LostProviderBlockerScope = {
|
|
|
52814
52817
|
};
|
|
52815
52818
|
|
|
52816
52819
|
/** Take the session-event write prefix (workspace control share, workspace,
|
|
52817
|
-
* sessions, cursors) for
|
|
52818
|
-
*
|
|
52820
|
+
* sessions, cursors) for linked background commands and attempt owners in the
|
|
52821
|
+
* exact lost-provider scope. Call it before the
|
|
52819
52822
|
* process/admission/PTY/lease locks so terminal command delivery keeps the
|
|
52820
52823
|
* canonical control -> session -> process -> admission -> lease order. */
|
|
52821
52824
|
async function linkedLostProviderCommandSessionIdsTx(
|
|
@@ -52825,7 +52828,8 @@ async function linkedLostProviderCommandSessionIdsTx(
|
|
|
52825
52828
|
const sessions = await rawRows<{ session_id: string }>(
|
|
52826
52829
|
tx,
|
|
52827
52830
|
sql`
|
|
52828
|
-
select distinct
|
|
52831
|
+
select distinct session_id from (
|
|
52832
|
+
select command.session_id
|
|
52829
52833
|
from session_background_commands command
|
|
52830
52834
|
join sandbox_retained_processes process on process.id = command.retained_process_id
|
|
52831
52835
|
and process.workspace_id = command.workspace_id
|
|
@@ -52839,7 +52843,21 @@ async function linkedLostProviderCommandSessionIdsTx(
|
|
|
52839
52843
|
and process.provider_instance_id = ${input.lostInstanceId}
|
|
52840
52844
|
and process.state = 'active'
|
|
52841
52845
|
and command.state in ('running', 'stopping')
|
|
52842
|
-
|
|
52846
|
+
union
|
|
52847
|
+
select process.session_id
|
|
52848
|
+
from sandbox_retained_processes process
|
|
52849
|
+
join session_turn_attempts attempt on attempt.id = process.owner_attempt_id
|
|
52850
|
+
and attempt.workspace_id = process.workspace_id
|
|
52851
|
+
and attempt.session_id = process.session_id
|
|
52852
|
+
where process.account_id = ${input.accountId}
|
|
52853
|
+
and process.workspace_id = ${input.workspaceId}
|
|
52854
|
+
and process.lease_id = ${input.leaseId}
|
|
52855
|
+
and process.sandbox_group_id = ${input.sandboxGroupId}
|
|
52856
|
+
and process.lease_epoch = ${input.lostEpoch}
|
|
52857
|
+
and process.provider_backend = ${input.lostBackend}
|
|
52858
|
+
and process.provider_instance_id = ${input.lostInstanceId}
|
|
52859
|
+
and process.state = 'active'
|
|
52860
|
+
) owners order by session_id
|
|
52843
52861
|
`,
|
|
52844
52862
|
);
|
|
52845
52863
|
return sessions.map((row) => row.session_id);
|
|
@@ -53061,6 +53079,37 @@ async function settleExactLostProviderWorkspaceBlockersTx(
|
|
|
53061
53079
|
}
|
|
53062
53080
|
}
|
|
53063
53081
|
|
|
53082
|
+
if (lostProcesses.length) {
|
|
53083
|
+
// The session prefix was locked before blockers and revalidated afterward.
|
|
53084
|
+
// Unadopted writers have no background-command delivery to wake their owner;
|
|
53085
|
+
// persist that obligation atomically with physical provider settlement.
|
|
53086
|
+
const owners = await rawRows<{ session_id: string; temporal_workflow_id: string }>(
|
|
53087
|
+
tx,
|
|
53088
|
+
sql`
|
|
53089
|
+
select distinct attempt.session_id, attempt.temporal_workflow_id
|
|
53090
|
+
from session_turn_attempts attempt
|
|
53091
|
+
join sandbox_retained_processes process on process.owner_attempt_id = attempt.id
|
|
53092
|
+
and process.workspace_id = attempt.workspace_id
|
|
53093
|
+
and process.session_id = attempt.session_id
|
|
53094
|
+
where attempt.workspace_id = ${input.workspaceId}
|
|
53095
|
+
and attempt.account_id = ${input.accountId}
|
|
53096
|
+
and attempt.state = 'closed' and attempt.quiesced_at is null
|
|
53097
|
+
and attempt.temporal_workflow_id is not null
|
|
53098
|
+
and process.id = any(${`{${lostProcesses.map((p) => p.id).join(",")}}`}::uuid[])
|
|
53099
|
+
order by attempt.session_id, attempt.temporal_workflow_id
|
|
53100
|
+
`,
|
|
53101
|
+
);
|
|
53102
|
+
for (const owner of owners) {
|
|
53103
|
+
await enqueueSessionWorkflowWakeInTransaction(tx, {
|
|
53104
|
+
accountId: input.accountId,
|
|
53105
|
+
workspaceId: input.workspaceId,
|
|
53106
|
+
sessionId: owner.session_id,
|
|
53107
|
+
temporalWorkflowId: owner.temporal_workflow_id,
|
|
53108
|
+
reason: "attempt_writer_provider_settled",
|
|
53109
|
+
});
|
|
53110
|
+
}
|
|
53111
|
+
}
|
|
53112
|
+
|
|
53064
53113
|
return {
|
|
53065
53114
|
processesLost: lostProcesses.length,
|
|
53066
53115
|
admissionsRejected: rejectedAdmissions.length,
|
|
@@ -53947,7 +53996,7 @@ export async function heartbeatLeaseHolderStatus(
|
|
|
53947
53996
|
(input.billingMode ?? snapshot?.mode) === "credits" &&
|
|
53948
53997
|
snapshot?.mode === "credits" &&
|
|
53949
53998
|
snapshot.rateMicrosPerSecond > 0 &&
|
|
53950
|
-
(await
|
|
53999
|
+
(await getSpendableCreditBalance(tx, input.accountId)).balanceMicros <= 0
|
|
53951
54000
|
) {
|
|
53952
54001
|
// Touching the holder above is essential: the running writer remains
|
|
53953
54002
|
// protected while it winds down. The provider/lease TTL is not renewed.
|
|
@@ -54603,23 +54652,69 @@ export async function reapStaleLeaseHolders(
|
|
|
54603
54652
|
}
|
|
54604
54653
|
|
|
54605
54654
|
/** Settlement reasons for legacy retained commands that containment stopped
|
|
54606
|
-
* after a verified checkpoint.
|
|
54655
|
+
* after a verified checkpoint. None is exit proof: the command settles
|
|
54607
54656
|
* `lost` with no exit code. */
|
|
54608
54657
|
export const IDLE_COMMAND_CONTAINMENT_REASON = "idle_containment";
|
|
54609
54658
|
export const DEADLINE_COMMAND_CONTAINMENT_REASON = "provider_deadline_containment";
|
|
54659
|
+
export const QUIESCENCE_COMMAND_CONTAINMENT_REASON = "quiescence_containment";
|
|
54660
|
+
|
|
54661
|
+
type SettledCommandOwner = {
|
|
54662
|
+
sessionId: string;
|
|
54663
|
+
attemptId: string;
|
|
54664
|
+
temporalWorkflowId: string;
|
|
54665
|
+
temporalWorkflowRunId: string;
|
|
54666
|
+
temporalActivityId: string;
|
|
54667
|
+
};
|
|
54668
|
+
|
|
54669
|
+
/** Exact dispatch settlement is enrollment authority, never physical proof. */
|
|
54670
|
+
async function settledCommandOwnerMatchesTx(
|
|
54671
|
+
tx: Database,
|
|
54672
|
+
input: { accountId: string; workspaceId: string; sandboxGroupId: string },
|
|
54673
|
+
owner: SettledCommandOwner,
|
|
54674
|
+
): Promise<boolean> {
|
|
54675
|
+
const [row] = await rawRows<{ eligible: boolean }>(
|
|
54676
|
+
tx,
|
|
54677
|
+
sql`
|
|
54678
|
+
select exists (
|
|
54679
|
+
select 1 from session_turn_attempts attempt
|
|
54680
|
+
join sessions session on session.id = attempt.session_id
|
|
54681
|
+
and session.workspace_id = attempt.workspace_id
|
|
54682
|
+
where attempt.account_id = ${input.accountId}
|
|
54683
|
+
and attempt.workspace_id = ${input.workspaceId}
|
|
54684
|
+
and attempt.session_id = ${owner.sessionId} and attempt.id = ${owner.attemptId}
|
|
54685
|
+
and attempt.temporal_workflow_id = ${owner.temporalWorkflowId}
|
|
54686
|
+
and attempt.temporal_workflow_run_id = ${owner.temporalWorkflowRunId}
|
|
54687
|
+
and attempt.temporal_activity_id = ${owner.temporalActivityId}
|
|
54688
|
+
and attempt.state = 'closed' and attempt.quiesced_at is null
|
|
54689
|
+
and session.sandbox_group_id = ${input.sandboxGroupId}
|
|
54690
|
+
and not exists (select 1 from session_attempt_interruptions interruption
|
|
54691
|
+
where interruption.workspace_id = attempt.workspace_id
|
|
54692
|
+
and interruption.session_id = attempt.session_id and interruption.attempt_id = attempt.id
|
|
54693
|
+
and interruption.state in ('pending', 'delivered', 'acknowledged'))
|
|
54694
|
+
) and not exists (
|
|
54695
|
+
select 1 from session_turn_attempts live
|
|
54696
|
+
join sessions member on member.id = live.session_id and member.workspace_id = live.workspace_id
|
|
54697
|
+
where live.workspace_id = ${input.workspaceId}
|
|
54698
|
+
and member.sandbox_group_id = ${input.sandboxGroupId} and live.state <> 'closed'
|
|
54699
|
+
) as eligible
|
|
54700
|
+
`,
|
|
54701
|
+
);
|
|
54702
|
+
return row?.eligible === true;
|
|
54703
|
+
}
|
|
54610
54704
|
|
|
54611
54705
|
/** Bounded per-candidate containment inspection outcomes (metric labels). */
|
|
54612
54706
|
export type CommandContainmentInspection =
|
|
54613
54707
|
| "idle_enrolled"
|
|
54614
54708
|
| "deadline_enrolled"
|
|
54709
|
+
| "quiescence_enrolled"
|
|
54615
54710
|
| "resumed_enrolled"
|
|
54616
54711
|
| "not_eligible"
|
|
54617
54712
|
| "inspection_failed";
|
|
54618
54713
|
|
|
54619
54714
|
export type CommandContainmentEnrollment = ReapDrainable & {
|
|
54620
|
-
/** `idle`/`deadline` newly enrolled this call; `resumed` continues an
|
|
54715
|
+
/** `idle`/`deadline`/`quiescence` newly enrolled this call; `resumed` continues an
|
|
54621
54716
|
* existing enrollment whose drain has not committed yet. */
|
|
54622
|
-
mode: "idle" | "deadline" | "resumed";
|
|
54717
|
+
mode: "idle" | "deadline" | "quiescence" | "resumed";
|
|
54623
54718
|
};
|
|
54624
54719
|
|
|
54625
54720
|
/** Whole-group "unused" from durable facts only. Every session of the group,
|
|
@@ -54822,6 +54917,8 @@ export async function enrollRetainedCommandContainment(
|
|
|
54822
54917
|
sandboxGroupId: string;
|
|
54823
54918
|
/** Omit to evaluate only the provider-deadline rule. */
|
|
54824
54919
|
idleCommandContainmentMs?: number | undefined;
|
|
54920
|
+
/** Internal recovery authority: caller verified exact Temporal settlement. */
|
|
54921
|
+
settledOwner?: SettledCommandOwner;
|
|
54825
54922
|
},
|
|
54826
54923
|
): Promise<CommandContainmentEnrollment | null> {
|
|
54827
54924
|
const screened = await withRlsContext(db, input, async (tx) => {
|
|
@@ -54834,6 +54931,8 @@ export async function enrollRetainedCommandContainment(
|
|
|
54834
54931
|
// Enrolled drains resume; any requested rotation also refreshes command
|
|
54835
54932
|
// observation backoff below and may meet the deadline rule.
|
|
54836
54933
|
(Boolean(lease.unobservableCommandDrainIds?.length) ||
|
|
54934
|
+
(input.settledOwner !== undefined &&
|
|
54935
|
+
(await settledCommandOwnerMatchesTx(tx, input, input.settledOwner))) ||
|
|
54837
54936
|
lease.rotationRequestedAt !== null ||
|
|
54838
54937
|
(input.idleCommandContainmentMs !== undefined &&
|
|
54839
54938
|
(await sandboxGroupIdleForCommandContainmentTx(tx, {
|
|
@@ -54883,11 +54982,17 @@ export async function enrollRetainedCommandContainment(
|
|
|
54883
54982
|
supervised: boolean;
|
|
54884
54983
|
owned: boolean;
|
|
54885
54984
|
deadline_ready: boolean;
|
|
54985
|
+
adopted: boolean;
|
|
54886
54986
|
}>(
|
|
54887
54987
|
tx,
|
|
54888
54988
|
sql`
|
|
54889
54989
|
select process.id, process.session_id, process.owner_attempt_id,
|
|
54890
54990
|
process.parent_admission_id, process.holder_id,
|
|
54991
|
+
exists (select 1 from session_background_commands background
|
|
54992
|
+
where background.retained_process_id = process.id
|
|
54993
|
+
and background.workspace_id = process.workspace_id
|
|
54994
|
+
and background.session_id = process.session_id
|
|
54995
|
+
and background.state in ('running', 'stopping')) as adopted,
|
|
54891
54996
|
(coalesce(process.provider_command, '{}'::jsonb) ? 'supervision') as supervised,
|
|
54892
54997
|
(process.lease_epoch = ${initial.leaseEpoch}
|
|
54893
54998
|
and process.provider_instance_id = ${initial.instanceId}
|
|
@@ -54961,8 +55066,19 @@ export async function enrollRetainedCommandContainment(
|
|
|
54961
55066
|
};
|
|
54962
55067
|
if (enrolled) return { ...target, mode: "resumed" };
|
|
54963
55068
|
if (lease.archive_capture_id !== null) return null;
|
|
54964
|
-
let mode: "idle" | "deadline" | null = null;
|
|
55069
|
+
let mode: "idle" | "deadline" | "quiescence" | null = null;
|
|
54965
55070
|
if (
|
|
55071
|
+
input.settledOwner !== undefined &&
|
|
55072
|
+
processes.every(
|
|
55073
|
+
(p) =>
|
|
55074
|
+
p.owner_attempt_id === input.settledOwner!.attemptId &&
|
|
55075
|
+
p.session_id === input.settledOwner!.sessionId &&
|
|
55076
|
+
!p.adopted,
|
|
55077
|
+
) &&
|
|
55078
|
+
(await settledCommandOwnerMatchesTx(tx, input, input.settledOwner))
|
|
55079
|
+
) {
|
|
55080
|
+
mode = "quiescence";
|
|
55081
|
+
} else if (
|
|
54966
55082
|
deadlineRotation &&
|
|
54967
55083
|
processes.every((p) => p.deadline_ready) &&
|
|
54968
55084
|
!(await deadlineOwnerQuiescencePending(tx, input.workspaceId, processes)) &&
|
|
@@ -54986,9 +55102,11 @@ export async function enrollRetainedCommandContainment(
|
|
|
54986
55102
|
await tx.execute(sql`
|
|
54987
55103
|
update sandbox_leases set unobservable_command_drain_ids = ${`{${ids.join(",")}}`}::uuid[],
|
|
54988
55104
|
command_containment_reason = ${
|
|
54989
|
-
mode === "
|
|
54990
|
-
?
|
|
54991
|
-
:
|
|
55105
|
+
mode === "quiescence"
|
|
55106
|
+
? QUIESCENCE_COMMAND_CONTAINMENT_REASON
|
|
55107
|
+
: mode === "deadline"
|
|
55108
|
+
? DEADLINE_COMMAND_CONTAINMENT_REASON
|
|
55109
|
+
: IDLE_COMMAND_CONTAINMENT_REASON
|
|
54992
55110
|
},
|
|
54993
55111
|
liveness = 'draining', rotation_requested_at = coalesce(rotation_requested_at, now()),
|
|
54994
55112
|
rotation_reason = coalesce(rotation_reason, 'operator'), expires_at = now(), updated_at = now()
|
|
@@ -55344,7 +55462,7 @@ export async function reArmDrainingLease(
|
|
|
55344
55462
|
? snapshot.mode === "credits" && snapshot.rateMicrosPerSecond > 0
|
|
55345
55463
|
: input.warmBilling?.rateMicrosPerSecond !== undefined &&
|
|
55346
55464
|
input.warmBilling.rateMicrosPerSecond > 0) &&
|
|
55347
|
-
(await
|
|
55465
|
+
(await getSpendableCreditBalance(tx, input.accountId)).balanceMicros <= 0
|
|
55348
55466
|
) {
|
|
55349
55467
|
throw new SandboxPaidComputeAdmissionError(input.workspaceId, input.sandboxGroupId);
|
|
55350
55468
|
}
|
|
@@ -55528,7 +55646,8 @@ export async function confirmDrainCold(
|
|
|
55528
55646
|
!input.providerMissingBeforeCapture &&
|
|
55529
55647
|
row.archive_capture_published_at !== null &&
|
|
55530
55648
|
(reason === IDLE_COMMAND_CONTAINMENT_REASON ||
|
|
55531
|
-
reason === DEADLINE_COMMAND_CONTAINMENT_REASON
|
|
55649
|
+
reason === DEADLINE_COMMAND_CONTAINMENT_REASON ||
|
|
55650
|
+
reason === QUIESCENCE_COMMAND_CONTAINMENT_REASON) &&
|
|
55532
55651
|
row.unobservable_command_drain_ids?.length
|
|
55533
55652
|
? row.unobservable_command_drain_ids
|
|
55534
55653
|
: [];
|
|
@@ -75366,6 +75485,28 @@ export async function reconcileSessionAttemptQuiescence(
|
|
|
75366
75485
|
};
|
|
75367
75486
|
}
|
|
75368
75487
|
if (!input.activitySettled || eligibility.writer_pending) {
|
|
75488
|
+
if (input.activitySettled && eligibility.writer_pending) {
|
|
75489
|
+
const group = await withRlsContext(db, input, async (tx) => {
|
|
75490
|
+
const [row] = await tx
|
|
75491
|
+
.select({ id: schema.sessions.sandboxGroupId })
|
|
75492
|
+
.from(schema.sessions)
|
|
75493
|
+
.where(
|
|
75494
|
+
and(
|
|
75495
|
+
eq(schema.sessions.workspaceId, input.workspaceId),
|
|
75496
|
+
eq(schema.sessions.id, input.sessionId),
|
|
75497
|
+
),
|
|
75498
|
+
)
|
|
75499
|
+
.limit(1);
|
|
75500
|
+
return row?.id;
|
|
75501
|
+
});
|
|
75502
|
+
if (group)
|
|
75503
|
+
await enrollRetainedCommandContainment(db, {
|
|
75504
|
+
accountId: input.accountId,
|
|
75505
|
+
workspaceId: input.workspaceId,
|
|
75506
|
+
sandboxGroupId: group,
|
|
75507
|
+
settledOwner: input,
|
|
75508
|
+
});
|
|
75509
|
+
}
|
|
75369
75510
|
return { action: "pending", events: [] };
|
|
75370
75511
|
}
|
|
75371
75512
|
const events = await markSessionAttemptQuiesced(db, {
|
|
@@ -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.
|
package/src/runtime-posture.ts
CHANGED
|
@@ -794,11 +794,13 @@ export const RUNTIME_TARGET_SCHEMA_FORBIDDEN_ROUTINES = [
|
|
|
794
794
|
SESSION_TENANCY_QUIESCENCE_ROUTINE,
|
|
795
795
|
TENANCY_BACKFILL_ACTIVATION_EVIDENCE_ROUTINE,
|
|
796
796
|
VERIFIED_SIGNUP_TRIAL_SWITCH_SETTER_ROUTINE,
|
|
797
|
+
"set_credit_promotion_policy(jsonb, text, text)",
|
|
797
798
|
MANAGED_AUTH_NEW_SIGNUPS_SWITCH_SETTER_ROUTINE,
|
|
798
799
|
...DOCUMENT_MIGRATION_AUDIT_INTERNAL_ROUTINES,
|
|
799
800
|
] as const;
|
|
800
801
|
|
|
801
802
|
export const RUNTIME_TARGET_SCHEMA_INVOKER_ROUTINES = [
|
|
803
|
+
"set_credit_promotion_policy(jsonb, text, text)",
|
|
802
804
|
CLAUDE_SNAPSHOT_VALIDATOR_ROUTINE,
|
|
803
805
|
"usage_allowance_period(jsonb, timestamp with time zone)",
|
|
804
806
|
"validate_usage_allowance_config(jsonb)",
|
|
@@ -894,6 +896,7 @@ export const FORCE_RLS_TABLES = [
|
|
|
894
896
|
"connections",
|
|
895
897
|
"connector_action_policies",
|
|
896
898
|
"connector_action_requests",
|
|
899
|
+
"credit_debit_allocations",
|
|
897
900
|
"credit_ledger_entries",
|
|
898
901
|
"device_enrollment_requests",
|
|
899
902
|
"document_authority_reclassifications",
|
|
@@ -1476,6 +1479,7 @@ export const RUNTIME_READ_INSERT_TABLES = [
|
|
|
1476
1479
|
"browser_revision_components",
|
|
1477
1480
|
"browser_revisions",
|
|
1478
1481
|
"company_profile_revisions",
|
|
1482
|
+
"credit_debit_allocations",
|
|
1479
1483
|
"editable_artifact_blob_refs",
|
|
1480
1484
|
"editable_artifact_idempotency_receipts",
|
|
1481
1485
|
"editable_artifact_live_outbox",
|
|
@@ -2181,6 +2185,7 @@ export async function inspectRuntimeDatabasePosture(
|
|
|
2181
2185
|
'modal_inventory_read_capabilities',
|
|
2182
2186
|
${AUTOMATIC_SESSION_TITLE_FANOUT_OUTBOX_TABLE},
|
|
2183
2187
|
${VERIFIED_SIGNUP_TRIAL_SWITCH_TABLE},
|
|
2188
|
+
'credit_promotion_policy_revisions',
|
|
2184
2189
|
${MANAGED_AUTH_NEW_SIGNUPS_SWITCH_TABLE}
|
|
2185
2190
|
)
|
|
2186
2191
|
`),
|
|
@@ -3752,6 +3757,23 @@ export function evaluateRuntimeDatabasePosture(
|
|
|
3752
3757
|
|
|
3753
3758
|
// The trial-credit kill switch is operator state. Runtime roles may read it
|
|
3754
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
|
+
}
|
|
3755
3777
|
const trialSwitchTable = posture.privateTables.find(
|
|
3756
3778
|
(table) => table.name === VERIFIED_SIGNUP_TRIAL_SWITCH_TABLE,
|
|
3757
3779
|
);
|
package/src/schema.ts
CHANGED
|
@@ -9680,7 +9680,7 @@ export const sandboxLeases = pgTable(
|
|
|
9680
9680
|
// Why an enrolled drain contains its commands (0547); cleared with the
|
|
9681
9681
|
// enrollment. Null for a pre-0547 enrollment: settle with neutral wording.
|
|
9682
9682
|
commandContainmentReason: text("command_containment_reason", {
|
|
9683
|
-
enum: ["idle_containment", "provider_deadline_containment"],
|
|
9683
|
+
enum: ["idle_containment", "provider_deadline_containment", "quiescence_containment"],
|
|
9684
9684
|
}),
|
|
9685
9685
|
liveness: text("liveness", { enum: sandboxLeaseLivenessValues }).notNull().default("cold"),
|
|
9686
9686
|
refcount: integer("refcount").notNull().default(0),
|
|
@@ -12628,6 +12628,8 @@ export const creditLedgerEntries = pgTable(
|
|
|
12628
12628
|
}),
|
|
12629
12629
|
type: text("type").notNull(),
|
|
12630
12630
|
amountMicros: bigint("amount_micros", { mode: "number" }).notNull(),
|
|
12631
|
+
/** Null preserves the unrestricted terms of legacy credits. */
|
|
12632
|
+
eligibleModelIds: text("eligible_model_ids").array(),
|
|
12631
12633
|
currency: text("currency").notNull().default("usd"),
|
|
12632
12634
|
sourceType: text("source_type"),
|
|
12633
12635
|
sourceId: text("source_id"),
|
|
@@ -12638,6 +12640,19 @@ export const creditLedgerEntries = pgTable(
|
|
|
12638
12640
|
},
|
|
12639
12641
|
(table) => ({
|
|
12640
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
|
+
),
|
|
12641
12656
|
modelDebitPeriod: index("credit_ledger_entries_model_debit_period_idx")
|
|
12642
12657
|
.on(table.accountId, table.occurredAt, table.workspaceId)
|
|
12643
12658
|
.where(
|
|
@@ -12650,6 +12665,30 @@ export const creditLedgerEntries = pgTable(
|
|
|
12650
12665
|
}),
|
|
12651
12666
|
);
|
|
12652
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
|
+
|
|
12653
12692
|
/** Content-free immutable accepted billing facts; no runtime table privileges. */
|
|
12654
12693
|
export const usageAllowanceAttributionReceipts = opengeniPrivateSchema.table(
|
|
12655
12694
|
"usage_allowance_attribution_receipts",
|