@oxyhq/contracts 0.27.0 → 0.29.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 (34) hide show
  1. package/dist/cjs/.tsbuildinfo +1 -1
  2. package/dist/cjs/index.js +44 -1
  3. package/dist/cjs/inference/accountBilling.js +334 -0
  4. package/dist/cjs/inference/attribution.js +9 -4
  5. package/dist/cjs/inference/entitlement.js +217 -0
  6. package/dist/cjs/inference/errors.js +123 -9
  7. package/dist/cjs/inference/identifiers.js +11 -3
  8. package/dist/cjs/inference/money.js +43 -0
  9. package/dist/cjs/inference/streamEvents.js +11 -1
  10. package/dist/cjs/inference/usage.js +6 -0
  11. package/dist/cjs/inference/version.js +38 -3
  12. package/dist/esm/.tsbuildinfo +1 -1
  13. package/dist/esm/index.js +9 -0
  14. package/dist/esm/inference/accountBilling.js +331 -0
  15. package/dist/esm/inference/attribution.js +9 -4
  16. package/dist/esm/inference/entitlement.js +214 -0
  17. package/dist/esm/inference/errors.js +123 -9
  18. package/dist/esm/inference/identifiers.js +11 -3
  19. package/dist/esm/inference/money.js +43 -0
  20. package/dist/esm/inference/streamEvents.js +11 -1
  21. package/dist/esm/inference/usage.js +6 -0
  22. package/dist/esm/inference/version.js +38 -3
  23. package/dist/types/.tsbuildinfo +1 -1
  24. package/dist/types/index.d.ts +4 -0
  25. package/dist/types/inference/accountBilling.d.ts +738 -0
  26. package/dist/types/inference/attribution.d.ts +9 -4
  27. package/dist/types/inference/entitlement.d.ts +519 -0
  28. package/dist/types/inference/errors.d.ts +59 -10
  29. package/dist/types/inference/identifiers.d.ts +11 -3
  30. package/dist/types/inference/money.d.ts +43 -0
  31. package/dist/types/inference/streamEvents.d.ts +31 -22
  32. package/dist/types/inference/usage.d.ts +6 -0
  33. package/dist/types/inference/version.d.ts +38 -3
  34. package/package.json +1 -1
package/dist/cjs/index.js CHANGED
@@ -18,7 +18,7 @@ exports.hubActivateRequestSchema = exports.hubClaimRequestSchema = exports.hubSe
18
18
  exports.transparencyCheckpointSchema = exports.transparencyAnchorSchema = exports.transparencyCheckpointSignatureSchema = exports.deviceTransferDenyResponseSchema = exports.deviceTransferApproveResponseSchema = exports.deviceTransferApproveRequestSchema = exports.deviceTransferInfoResponseSchema = exports.deviceTransferInitResponseSchema = exports.deviceTransferInitRequestSchema = exports.devicePairingStatusSchema = exports.webauthnLoginVerifyRequestSchema = exports.webauthnRegisterVerifyRequestSchema = exports.webauthnLoginOptionsRequestSchema = exports.webauthnRegisterOptionsRequestSchema = exports.updateRolloutPatchSchema = exports.promoteRequestSchema = exports.rollbackToEmbeddedRequestSchema = exports.rollbackRequestSchema = exports.updateListResponseSchema = exports.channelListResponseSchema = exports.channelSchema = exports.rollbackToEmbeddedEntrySchema = exports.createUpdateResponseSchema = exports.updateSchema = exports.createUpdateRequestSchema = exports.updateAssetRefSchema = exports.assetCompleteResponseSchema = exports.assetCompleteResultItemSchema = exports.assetCompleteRequestSchema = exports.assetInitResponseSchema = exports.assetUploadTicketSchema = exports.assetInitRequestSchema = exports.assetInitItemSchema = exports.rolloutPercentSchema = exports.runtimeVersionSchema = exports.channelNameSchema = exports.sha256HexSchema = exports.updateAssetStatusSchema = exports.updateStatusSchema = exports.updatePlatformSchema = exports.backupStatusResponseSchema = exports.backupUploadRequestSchema = exports.encryptedBackupEnvelopeSchema = exports.backupLookupIdSchema = exports.rotateKeyCompleteResponseSchema = exports.rotateKeyCompleteRequestSchema = exports.rotateKeyChallengeResponseSchema = exports.loginResultSchema = exports.hubAuthorizeResultSchema = exports.hubAuthorizeRequestSchema = void 0;
19
19
  exports.inferenceModalitySchema = exports.priceSnapshotSchema = exports.priceVersionSchema = exports.priceVersionStatusSchema = exports.inferenceErrorSchema = exports.providerErrorPassthroughSchema = exports.safeErrorTextSchema = exports.upstreamErrorCategorySchema = exports.inferenceErrorCodeSchema = exports.NON_RETRYABLE_INFERENCE_ERROR_CODES = exports.INFERENCE_ERROR_CODES = exports.inferenceAttributionSchema = exports.authenticatedPrincipalSchema = exports.billingPrincipalSchema = exports.inferenceScopeSchema = exports.INFERENCE_SCOPES = exports.unitPriceSchema = exports.usageQuantitySchema = exports.usageSourceSchema = exports.USAGE_SOURCES = exports.usageUnitSchema = exports.USAGE_UNITS = exports.moneySchema = exports.exactDecimalSchema = exports.INFERENCE_MONEY_SCALE = exports.currencyCodeSchema = exports.RESERVED_ALIA_PUBLISHER = exports.inferenceRegionSchema = exports.deploymentIdSchema = exports.inferenceProviderSlugSchema = exports.routingProfileSlugSchema = exports.modelReferenceSchema = exports.modelRevisionLabelSchema = exports.modelIdSchema = exports.modelSlugSchema = exports.publisherSlugSchema = exports.inferenceHttpsUrlSchema = exports.inferenceDateSchema = exports.inferenceTimestampSchema = exports.inferenceEnvironmentSchema = exports.idempotencyKeySchema = exports.generationIdSchema = exports.requestIdSchema = exports.oxyCredentialIdSchema = exports.oxyApplicationIdSchema = exports.delegatedUserIdSchema = exports.oxyAccountIdSchema = exports.INFERENCE_CONTRACT_VERSION = exports.transparencyCheckpointListSchema = exports.transparencyInclusionProofSchema = void 0;
20
20
  exports.usageReservationSchema = exports.usageReservationStatusSchema = exports.usageReservationRequestSchema = exports.inferenceStreamEventSchema = exports.inferenceStreamDoneEventSchema = exports.inferenceFinishReasonSchema = exports.inferenceStreamErrorEventSchema = exports.inferenceStreamRouteSwitchEventSchema = exports.inferenceRouteSwitchReasonSchema = exports.inferenceRouteSwitchDetailSchema = exports.inferenceStreamUsageEventSchema = exports.inferenceStreamToolCallEventSchema = exports.inferenceStreamDeltaEventSchema = exports.inferenceStreamStartEventSchema = exports.inferenceRequestSchema = exports.clientRequestMetadataSchema = exports.responseFormatSchema = exports.toolChoiceSchema = exports.toolDefinitionSchema = exports.samplingParametersSchema = exports.inferenceInputSchema = exports.inferenceMessageSchema = exports.inferenceMessageRoleSchema = exports.inferenceToolCallSchema = exports.inferenceContentPartSchema = exports.inferenceContentSourceSchema = exports.routingPolicyReferenceSchema = exports.routingPolicySchema = exports.routingFallbackPolicySchema = exports.routingPolicyScopeSchema = exports.routingTargetSchema = exports.modelCatalogueEntrySchema = exports.catalogueServingProviderSummarySchema = exports.cataloguePublisherSummarySchema = exports.routingProfileSchema = exports.routingProfileCandidateSchema = exports.modelDeploymentSchema = exports.inferenceProviderSchema = exports.modelRevisionSchema = exports.catalogueModelSchema = exports.modelPublisherSchema = exports.modelSafetyMetadataSchema = exports.modelEvaluationResultSchema = exports.modelDeprecationSchema = exports.commercialPermissionSchema = exports.availabilityScopeSchema = exports.inferenceDataPolicySchema = exports.modelProvenanceSchema = exports.modelLicenseSchema = exports.modelCapabilitiesSchema = void 0;
21
- exports.providerConnectionSchema = exports.providerConnectionStatusSchema = exports.providerConnectionValidationSchema = exports.providerSecretReferenceSchema = exports.providerConnectionScopeSchema = exports.usageRefundSchema = exports.usageRefundReasonSchema = exports.usageRefundSubjectSchema = exports.usageReceiptSchema = exports.normalizedUsageReportSchema = exports.inferenceRequestOutcomeSchema = void 0;
21
+ exports.productEntitlementSchema = exports.costCenterSpendSchema = exports.costCenterSchema = exports.costCenterStatusSchema = exports.COST_CENTER_STATUSES = exports.payAsYouGoEntitlementSchema = exports.productPlanSchema = exports.planAllowanceSchema = exports.LIVE_PRODUCT_PLAN_STATUSES = exports.productPlanStatusSchema = exports.PRODUCT_PLAN_STATUSES = exports.reconciliationReportSchema = exports.reconciliationRunSchema = exports.reconciliationDiscrepancySchema = exports.reconciliationRunStatusSchema = exports.RECONCILIATION_RUN_STATUSES = exports.reconciliationDiscrepancyKindSchema = exports.RECONCILIATION_DISCREPANCY_KINDS = exports.autoRechargeAttemptSchema = exports.autoRechargeStatusSchema = exports.AUTO_RECHARGE_STATUSES = exports.externalPaymentSchema = exports.externalPaymentKindSchema = exports.EXTERNAL_PAYMENT_KINDS = exports.externalPaymentProviderSchema = exports.EXTERNAL_PAYMENT_PROVIDERS = exports.billingInvoiceSchema = exports.billingInvoiceStatusSchema = exports.BILLING_INVOICE_STATUSES = exports.accountBillingStateSchema = exports.billingProfileSchema = exports.autoRechargeSchema = exports.billingProfileStatusSchema = exports.BILLING_PROFILE_STATUSES = exports.billingModeSchema = exports.BILLING_MODES = exports.providerConnectionSchema = exports.providerConnectionStatusSchema = exports.providerConnectionValidationSchema = exports.providerSecretReferenceSchema = exports.providerConnectionScopeSchema = exports.usageRefundSchema = exports.usageRefundReasonSchema = exports.usageRefundSubjectSchema = exports.usageReceiptSchema = exports.normalizedUsageReportSchema = exports.inferenceRequestOutcomeSchema = void 0;
22
22
  var accountGraph_1 = require("./accountGraph");
23
23
  Object.defineProperty(exports, "ACCOUNT_KINDS", { enumerable: true, get: function () { return accountGraph_1.ACCOUNT_KINDS; } });
24
24
  Object.defineProperty(exports, "accountKindSchema", { enumerable: true, get: function () { return accountGraph_1.accountKindSchema; } });
@@ -472,3 +472,46 @@ Object.defineProperty(exports, "providerSecretReferenceSchema", { enumerable: tr
472
472
  Object.defineProperty(exports, "providerConnectionValidationSchema", { enumerable: true, get: function () { return providerConnection_1.providerConnectionValidationSchema; } });
473
473
  Object.defineProperty(exports, "providerConnectionStatusSchema", { enumerable: true, get: function () { return providerConnection_1.providerConnectionStatusSchema; } });
474
474
  Object.defineProperty(exports, "providerConnectionSchema", { enumerable: true, get: function () { return providerConnection_1.providerConnectionSchema; } });
475
+ var accountBilling_1 = require("./inference/accountBilling");
476
+ // Account-scoped billing: who pays, what they hold, what bounds them, and
477
+ // how it reconciles against the payment processor. A grant and a purchase
478
+ // are never one number — there is deliberately no total.
479
+ Object.defineProperty(exports, "BILLING_MODES", { enumerable: true, get: function () { return accountBilling_1.BILLING_MODES; } });
480
+ Object.defineProperty(exports, "billingModeSchema", { enumerable: true, get: function () { return accountBilling_1.billingModeSchema; } });
481
+ Object.defineProperty(exports, "BILLING_PROFILE_STATUSES", { enumerable: true, get: function () { return accountBilling_1.BILLING_PROFILE_STATUSES; } });
482
+ Object.defineProperty(exports, "billingProfileStatusSchema", { enumerable: true, get: function () { return accountBilling_1.billingProfileStatusSchema; } });
483
+ Object.defineProperty(exports, "autoRechargeSchema", { enumerable: true, get: function () { return accountBilling_1.autoRechargeSchema; } });
484
+ Object.defineProperty(exports, "billingProfileSchema", { enumerable: true, get: function () { return accountBilling_1.billingProfileSchema; } });
485
+ Object.defineProperty(exports, "accountBillingStateSchema", { enumerable: true, get: function () { return accountBilling_1.accountBillingStateSchema; } });
486
+ Object.defineProperty(exports, "BILLING_INVOICE_STATUSES", { enumerable: true, get: function () { return accountBilling_1.BILLING_INVOICE_STATUSES; } });
487
+ Object.defineProperty(exports, "billingInvoiceStatusSchema", { enumerable: true, get: function () { return accountBilling_1.billingInvoiceStatusSchema; } });
488
+ Object.defineProperty(exports, "billingInvoiceSchema", { enumerable: true, get: function () { return accountBilling_1.billingInvoiceSchema; } });
489
+ Object.defineProperty(exports, "EXTERNAL_PAYMENT_PROVIDERS", { enumerable: true, get: function () { return accountBilling_1.EXTERNAL_PAYMENT_PROVIDERS; } });
490
+ Object.defineProperty(exports, "externalPaymentProviderSchema", { enumerable: true, get: function () { return accountBilling_1.externalPaymentProviderSchema; } });
491
+ Object.defineProperty(exports, "EXTERNAL_PAYMENT_KINDS", { enumerable: true, get: function () { return accountBilling_1.EXTERNAL_PAYMENT_KINDS; } });
492
+ Object.defineProperty(exports, "externalPaymentKindSchema", { enumerable: true, get: function () { return accountBilling_1.externalPaymentKindSchema; } });
493
+ Object.defineProperty(exports, "externalPaymentSchema", { enumerable: true, get: function () { return accountBilling_1.externalPaymentSchema; } });
494
+ Object.defineProperty(exports, "AUTO_RECHARGE_STATUSES", { enumerable: true, get: function () { return accountBilling_1.AUTO_RECHARGE_STATUSES; } });
495
+ Object.defineProperty(exports, "autoRechargeStatusSchema", { enumerable: true, get: function () { return accountBilling_1.autoRechargeStatusSchema; } });
496
+ Object.defineProperty(exports, "autoRechargeAttemptSchema", { enumerable: true, get: function () { return accountBilling_1.autoRechargeAttemptSchema; } });
497
+ Object.defineProperty(exports, "RECONCILIATION_DISCREPANCY_KINDS", { enumerable: true, get: function () { return accountBilling_1.RECONCILIATION_DISCREPANCY_KINDS; } });
498
+ Object.defineProperty(exports, "reconciliationDiscrepancyKindSchema", { enumerable: true, get: function () { return accountBilling_1.reconciliationDiscrepancyKindSchema; } });
499
+ Object.defineProperty(exports, "RECONCILIATION_RUN_STATUSES", { enumerable: true, get: function () { return accountBilling_1.RECONCILIATION_RUN_STATUSES; } });
500
+ Object.defineProperty(exports, "reconciliationRunStatusSchema", { enumerable: true, get: function () { return accountBilling_1.reconciliationRunStatusSchema; } });
501
+ Object.defineProperty(exports, "reconciliationDiscrepancySchema", { enumerable: true, get: function () { return accountBilling_1.reconciliationDiscrepancySchema; } });
502
+ Object.defineProperty(exports, "reconciliationRunSchema", { enumerable: true, get: function () { return accountBilling_1.reconciliationRunSchema; } });
503
+ Object.defineProperty(exports, "reconciliationReportSchema", { enumerable: true, get: function () { return accountBilling_1.reconciliationReportSchema; } });
504
+ var entitlement_1 = require("./inference/entitlement");
505
+ // Product entitlements. Allowances are integer counts and money is an exact
506
+ // decimal, so a plan allowance cannot be added to a balance.
507
+ Object.defineProperty(exports, "PRODUCT_PLAN_STATUSES", { enumerable: true, get: function () { return entitlement_1.PRODUCT_PLAN_STATUSES; } });
508
+ Object.defineProperty(exports, "productPlanStatusSchema", { enumerable: true, get: function () { return entitlement_1.productPlanStatusSchema; } });
509
+ Object.defineProperty(exports, "LIVE_PRODUCT_PLAN_STATUSES", { enumerable: true, get: function () { return entitlement_1.LIVE_PRODUCT_PLAN_STATUSES; } });
510
+ Object.defineProperty(exports, "planAllowanceSchema", { enumerable: true, get: function () { return entitlement_1.planAllowanceSchema; } });
511
+ Object.defineProperty(exports, "productPlanSchema", { enumerable: true, get: function () { return entitlement_1.productPlanSchema; } });
512
+ Object.defineProperty(exports, "payAsYouGoEntitlementSchema", { enumerable: true, get: function () { return entitlement_1.payAsYouGoEntitlementSchema; } });
513
+ Object.defineProperty(exports, "COST_CENTER_STATUSES", { enumerable: true, get: function () { return entitlement_1.COST_CENTER_STATUSES; } });
514
+ Object.defineProperty(exports, "costCenterStatusSchema", { enumerable: true, get: function () { return entitlement_1.costCenterStatusSchema; } });
515
+ Object.defineProperty(exports, "costCenterSchema", { enumerable: true, get: function () { return entitlement_1.costCenterSchema; } });
516
+ Object.defineProperty(exports, "costCenterSpendSchema", { enumerable: true, get: function () { return entitlement_1.costCenterSpendSchema; } });
517
+ Object.defineProperty(exports, "productEntitlementSchema", { enumerable: true, get: function () { return entitlement_1.productEntitlementSchema; } });
@@ -0,0 +1,334 @@
1
+ "use strict";
2
+ /**
3
+ * The account-scoped billing surface: who pays, what they hold, what bounds
4
+ * them, and how it reconciles against the payment processor.
5
+ *
6
+ * Every shape here is denominated in {@link exactDecimalSchema} — an exact
7
+ * decimal string — for the reason `money.ts` gives at length: a JS `number`
8
+ * cannot represent `0.1 + 0.2`, and a balance is the last place a rounding error
9
+ * should be allowed to accumulate silently.
10
+ *
11
+ * ## What is NOT here, and where it is instead
12
+ *
13
+ * The BALANCE and the BUDGETS both live at `/inference/reporting` (#972
14
+ * workstream 8), which owns the customer's view of what they hold and what
15
+ * bounds them, stamped `{source, consistency}` so a reader can always tell which
16
+ * kind of number they are looking at. This file declares who PAYS and on what
17
+ * TERMS, plus the records that sit behind those numbers — invoices, processor
18
+ * payments, auto-recharge attempts and reconciliation.
19
+ *
20
+ * The split is not cosmetic. A second balance shape here would be a second
21
+ * answer to one question, and the pair would disagree the day one of them stops
22
+ * accounting for an invoiced account's unused credit line — which is exactly the
23
+ * kind of drift a customer discovers before we do.
24
+ *
25
+ * ## Product entitlements are NOT here
26
+ *
27
+ * Alia plans, their monthly allowances and the API-credit product live in
28
+ * `entitlement.ts`, in a shape that cannot be added to a balance. #972 is
29
+ * explicit that confusing a product subscription with pay-as-you-go inference
30
+ * spend is the failure mode, so the two are separate types that share no field
31
+ * and no unit.
32
+ *
33
+ * ## Stripe appears only as a REFERENCE
34
+ *
35
+ * `externalRef` names a record in the processor's database; nothing in this file
36
+ * is derived from Stripe, and no shape here can carry a processor-computed
37
+ * balance. The epic's invariant is that Stripe is a payment and invoicing
38
+ * processor and not the authoritative usage ledger, so the reconciliation shapes
39
+ * below describe a COMPARISON between two independent records rather than an
40
+ * import of one into the other.
41
+ *
42
+ * Decided in: docs/adr/0014-account-billing-and-entitlements.md,
43
+ * docs/adr/0009-usage-reservation-and-settlement.md.
44
+ */
45
+ Object.defineProperty(exports, "__esModule", { value: true });
46
+ exports.reconciliationReportSchema = exports.reconciliationRunSchema = exports.reconciliationDiscrepancySchema = exports.reconciliationRunStatusSchema = exports.RECONCILIATION_RUN_STATUSES = exports.reconciliationDiscrepancyKindSchema = exports.RECONCILIATION_DISCREPANCY_KINDS = exports.autoRechargeAttemptSchema = exports.autoRechargeStatusSchema = exports.AUTO_RECHARGE_STATUSES = exports.externalPaymentSchema = exports.externalPaymentKindSchema = exports.EXTERNAL_PAYMENT_KINDS = exports.externalPaymentProviderSchema = exports.EXTERNAL_PAYMENT_PROVIDERS = exports.billingInvoiceSchema = exports.billingInvoiceStatusSchema = exports.BILLING_INVOICE_STATUSES = exports.accountBillingStateSchema = exports.billingProfileSchema = exports.autoRechargeSchema = exports.billingProfileStatusSchema = exports.BILLING_PROFILE_STATUSES = exports.billingModeSchema = exports.BILLING_MODES = void 0;
47
+ const zod_1 = require("zod");
48
+ const identifiers_1 = require("./identifiers");
49
+ const money_1 = require("./money");
50
+ /* -------------------------------------------------------------------------- */
51
+ /* Billing profile */
52
+ /* -------------------------------------------------------------------------- */
53
+ /**
54
+ * How an account pays.
55
+ *
56
+ * `prepaid` spends money it topped up in advance. `invoiced` draws against a
57
+ * credit limit and is billed in arrears — the enterprise shape. Both settle
58
+ * through the same ledger; the difference is only which bucket a reservation
59
+ * draws from once the prepaid and granted balances are exhausted.
60
+ */
61
+ exports.BILLING_MODES = ['prepaid', 'invoiced'];
62
+ exports.billingModeSchema = zod_1.z.enum(exports.BILLING_MODES);
63
+ /** Whether the profile may currently spend at all. */
64
+ exports.BILLING_PROFILE_STATUSES = ['active', 'suspended', 'closed'];
65
+ exports.billingProfileStatusSchema = zod_1.z.enum(exports.BILLING_PROFILE_STATUSES);
66
+ /**
67
+ * Automatic top-up settings.
68
+ *
69
+ * An IMPLICATION rather than a biconditional, matching the column CHECK: the
70
+ * two amounts may be configured before the feature is switched on, but an
71
+ * `enabled` recharge with either missing is a setting that reads as "on" and can
72
+ * never fire — the shape a customer discovers only when their traffic stops.
73
+ */
74
+ exports.autoRechargeSchema = zod_1.z
75
+ .object({
76
+ enabled: zod_1.z.boolean(),
77
+ /** Recharge when the spendable balance falls below this. */
78
+ threshold: money_1.exactDecimalSchema.optional(),
79
+ /** How much to add. */
80
+ amount: money_1.exactDecimalSchema.optional(),
81
+ })
82
+ .strict()
83
+ .refine((value) => !value.enabled || (value.threshold !== undefined && value.amount !== undefined), { message: 'an enabled auto-recharge must carry both a threshold and an amount' });
84
+ /**
85
+ * Which account pays for a workload, and on what terms.
86
+ *
87
+ * `accountId` is an Oxy account of ANY kind — personal, organization, project,
88
+ * bot or channel — because `Application.ownerAccountId` may be any of them. It
89
+ * is branded (`oxyAccountIdSchema`), so a delegated end-user id cannot be
90
+ * substituted for it anywhere in this contract.
91
+ */
92
+ exports.billingProfileSchema = zod_1.z
93
+ .object({
94
+ /** See `version.ts`: this shape is served to Console and to Alia. */
95
+ schemaVersion: zod_1.z.literal(1),
96
+ accountId: identifiers_1.oxyAccountIdSchema,
97
+ currency: money_1.currencyCodeSchema,
98
+ billingMode: exports.billingModeSchema,
99
+ status: exports.billingProfileStatusSchema,
100
+ /**
101
+ * How far an `invoiced` account may draw before a reservation is refused.
102
+ * Always `'0'` for a `prepaid` account, where it is not consulted at all.
103
+ */
104
+ creditLimit: money_1.exactDecimalSchema,
105
+ autoRecharge: exports.autoRechargeSchema,
106
+ createdAt: zod_1.z.string().datetime(),
107
+ updatedAt: zod_1.z.string().datetime(),
108
+ })
109
+ .strict();
110
+ /* -------------------------------------------------------------------------- */
111
+ /* Billing state */
112
+ /* -------------------------------------------------------------------------- */
113
+ /**
114
+ * WHO pays for an account and on what TERMS. Deliberately not how much they
115
+ * have.
116
+ *
117
+ * The balance lives at `GET /inference/reporting/accounts/:accountId/balance`
118
+ * (#972 workstream 8), which reads `account_balances` and stamps every response
119
+ * with `{source: 'financial_ledger', consistency: 'authoritative'}`. Restating
120
+ * those amounts here would be a second answer to one question, and the failure
121
+ * mode of two balance endpoints is the pair disagreeing on the day one of them
122
+ * stops accounting for the credit line.
123
+ *
124
+ * What this shape adds, and what nothing else carries: `billingAccountId` and
125
+ * `inherited`. A project draws on the nearest ancestor that has a profile
126
+ * (ADR 0014), so a Console page has to be able to say "this project spends the
127
+ * organization's balance" — showing somebody else's money under a project's name
128
+ * with no indication whose it is would be worse than showing nothing.
129
+ */
130
+ exports.accountBillingStateSchema = zod_1.z
131
+ .object({
132
+ /** See `version.ts`: this shape is served to Console and to Alia. */
133
+ schemaVersion: zod_1.z.literal(1),
134
+ accountId: identifiers_1.oxyAccountIdSchema,
135
+ billingAccountId: identifiers_1.oxyAccountIdSchema,
136
+ inherited: zod_1.z.boolean(),
137
+ profile: exports.billingProfileSchema,
138
+ })
139
+ .strict();
140
+ /*
141
+ * SPENDING LIMITS AND THEIR ALERTS ARE NOT DECLARED HERE.
142
+ *
143
+ * `/inference/reporting` owns the budget surface (#972 workstream 8) and
144
+ * declares its own shapes beside the router that serves them, stamped
145
+ * `{source, consistency}` so a reader can always tell which kind of number they
146
+ * are looking at. A second set here would be a second answer to what a budget
147
+ * is, on a table both would write — and the closed alert-threshold set already
148
+ * lives once more where it is ENFORCED, as a CHECK on
149
+ * `spending_limits.alert_threshold_bps`.
150
+ */
151
+ /* -------------------------------------------------------------------------- */
152
+ /* Invoices */
153
+ /* -------------------------------------------------------------------------- */
154
+ exports.BILLING_INVOICE_STATUSES = ['draft', 'open', 'paid', 'void'];
155
+ exports.billingInvoiceStatusSchema = zod_1.z.enum(exports.BILLING_INVOICE_STATUSES);
156
+ /**
157
+ * A period's charges, aggregated — the invoiced-enterprise settlement document.
158
+ *
159
+ * `subtotalAmount` is the EXACT sum of the receipts on the invoice, at full
160
+ * scale; `totalAmount` is the rounded figure actually charged. The difference is
161
+ * booked as an `invoice_rounding` ledger entry rather than discarded, because a
162
+ * discarded remainder is money that exists in one system and not the other.
163
+ *
164
+ * `minorUnitExponent` is carried rather than derived from the currency code:
165
+ * USD is 2, JPY is 0, BHD is 3, and that is not a property this platform's
166
+ * database knows. Storing what was used keeps the invoice reproducible.
167
+ */
168
+ exports.billingInvoiceSchema = zod_1.z
169
+ .object({
170
+ /** See `version.ts`: this shape is served to Console and to Alia. */
171
+ schemaVersion: zod_1.z.literal(1),
172
+ id: zod_1.z.string().min(1).max(64),
173
+ accountId: identifiers_1.oxyAccountIdSchema,
174
+ currency: money_1.currencyCodeSchema,
175
+ periodStart: zod_1.z.string().datetime(),
176
+ periodEnd: zod_1.z.string().datetime(),
177
+ status: exports.billingInvoiceStatusSchema,
178
+ subtotalAmount: money_1.exactDecimalSchema,
179
+ totalAmount: money_1.exactDecimalSchema,
180
+ minorUnitExponent: zod_1.z.number().int().min(0).max(4),
181
+ /** The processor's own invoice id, for reconciliation. Never an authority. */
182
+ externalInvoiceRef: zod_1.z.string().min(1).max(255).optional(),
183
+ issuedAt: zod_1.z.string().datetime().optional(),
184
+ paidAt: zod_1.z.string().datetime().optional(),
185
+ receiptCount: zod_1.z.number().int().nonnegative().safe(),
186
+ })
187
+ .strict();
188
+ /* -------------------------------------------------------------------------- */
189
+ /* Payment processor boundary */
190
+ /* -------------------------------------------------------------------------- */
191
+ /** The processors this platform records payments from. */
192
+ exports.EXTERNAL_PAYMENT_PROVIDERS = ['stripe'];
193
+ exports.externalPaymentProviderSchema = zod_1.z.enum(exports.EXTERNAL_PAYMENT_PROVIDERS);
194
+ /**
195
+ * What kind of processor record `externalRef` names.
196
+ *
197
+ * Kept explicit because reconciliation compares like with like: a payment intent
198
+ * and the invoice it paid are two records of one movement of money, and counting
199
+ * both would double the external total.
200
+ */
201
+ exports.EXTERNAL_PAYMENT_KINDS = ['payment_intent', 'invoice'];
202
+ exports.externalPaymentKindSchema = zod_1.z.enum(exports.EXTERNAL_PAYMENT_KINDS);
203
+ /**
204
+ * A processor payment that funded an Oxy balance.
205
+ *
206
+ * This is the reconciliation ANCHOR: one row per charge that landed in the
207
+ * ledger, carrying both the processor's reference and the ledger entry it
208
+ * produced. Without it, matching a Stripe charge back to a ledger entry means
209
+ * parsing an idempotency key, and a parser is not a foreign key.
210
+ */
211
+ exports.externalPaymentSchema = zod_1.z
212
+ .object({
213
+ id: zod_1.z.string().min(1).max(64),
214
+ accountId: identifiers_1.oxyAccountIdSchema,
215
+ provider: exports.externalPaymentProviderSchema,
216
+ externalKind: exports.externalPaymentKindSchema,
217
+ externalRef: zod_1.z.string().min(1).max(255),
218
+ amount: money_1.exactDecimalSchema,
219
+ currency: money_1.currencyCodeSchema,
220
+ ledgerEntryId: zod_1.z.string().min(1).max(64),
221
+ occurredAt: zod_1.z.string().datetime(),
222
+ createdAt: zod_1.z.string().datetime(),
223
+ })
224
+ .strict();
225
+ /** Lifecycle of one automatic top-up. */
226
+ exports.AUTO_RECHARGE_STATUSES = ['pending', 'succeeded', 'failed'];
227
+ exports.autoRechargeStatusSchema = zod_1.z.enum(exports.AUTO_RECHARGE_STATUSES);
228
+ /**
229
+ * One attempt to top an account up automatically.
230
+ *
231
+ * Recorded BEFORE the processor is called and keyed on the account, currency and
232
+ * the window it fired in, so a sweep that runs twice — or two instances of it —
233
+ * charges a customer's card once. An off-session charge is the one operation on
234
+ * this surface where a duplicate is not merely a wrong number in a report.
235
+ */
236
+ exports.autoRechargeAttemptSchema = zod_1.z
237
+ .object({
238
+ /** See `version.ts`: this shape is served to Console and to Alia. */
239
+ schemaVersion: zod_1.z.literal(1),
240
+ id: zod_1.z.string().min(1).max(64),
241
+ accountId: identifiers_1.oxyAccountIdSchema,
242
+ currency: money_1.currencyCodeSchema,
243
+ requestedAmount: money_1.exactDecimalSchema,
244
+ /** The spendable balance that triggered it — why this attempt exists. */
245
+ balanceAtTrigger: money_1.exactDecimalSchema,
246
+ status: exports.autoRechargeStatusSchema,
247
+ externalRef: zod_1.z.string().min(1).max(255).optional(),
248
+ /** The processor's own decline code. Never a free-form message. */
249
+ failureCode: zod_1.z.string().min(1).max(64).optional(),
250
+ createdAt: zod_1.z.string().datetime(),
251
+ updatedAt: zod_1.z.string().datetime(),
252
+ })
253
+ .strict();
254
+ /* -------------------------------------------------------------------------- */
255
+ /* Reconciliation */
256
+ /* -------------------------------------------------------------------------- */
257
+ /**
258
+ * How an Oxy record and a processor record fail to agree.
259
+ *
260
+ * A closed set, and every member is a DIFFERENT operational problem:
261
+ *
262
+ * - `missing_in_ledger` — the processor took money Oxy never credited. The
263
+ * customer paid and has no balance. This is the one that costs a customer.
264
+ * - `missing_in_external` — Oxy credited a balance with no processor charge
265
+ * behind it. This is the one that costs Oxy.
266
+ * - `amount_mismatch` — both exist and disagree.
267
+ * - `account_unresolved` — a processor charge whose customer maps to no Oxy
268
+ * account. Money arrived and nobody owns it.
269
+ *
270
+ * Collapsing them into a single "discrepancy" count is what makes a
271
+ * reconciliation report a number nobody acts on.
272
+ */
273
+ exports.RECONCILIATION_DISCREPANCY_KINDS = [
274
+ 'missing_in_ledger',
275
+ 'missing_in_external',
276
+ 'amount_mismatch',
277
+ 'account_unresolved',
278
+ ];
279
+ exports.reconciliationDiscrepancyKindSchema = zod_1.z.enum(exports.RECONCILIATION_DISCREPANCY_KINDS);
280
+ exports.RECONCILIATION_RUN_STATUSES = ['running', 'completed', 'failed'];
281
+ exports.reconciliationRunStatusSchema = zod_1.z.enum(exports.RECONCILIATION_RUN_STATUSES);
282
+ exports.reconciliationDiscrepancySchema = zod_1.z
283
+ .object({
284
+ /** See `version.ts`: this shape is served to Console and to Alia. */
285
+ schemaVersion: zod_1.z.literal(1),
286
+ id: zod_1.z.string().min(1).max(64),
287
+ runId: zod_1.z.string().min(1).max(64),
288
+ kind: exports.reconciliationDiscrepancyKindSchema,
289
+ accountId: identifiers_1.oxyAccountIdSchema.optional(),
290
+ externalRef: zod_1.z.string().min(1).max(255).optional(),
291
+ ledgerEntryId: zod_1.z.string().min(1).max(64).optional(),
292
+ /** What Oxy recorded. Absent for `missing_in_ledger`. */
293
+ ledgerAmount: money_1.exactDecimalSchema.optional(),
294
+ /** What the processor recorded. Absent for `missing_in_external`. */
295
+ externalAmount: money_1.exactDecimalSchema.optional(),
296
+ currency: money_1.currencyCodeSchema,
297
+ createdAt: zod_1.z.string().datetime(),
298
+ })
299
+ .strict();
300
+ /**
301
+ * One reconciliation pass over a window.
302
+ *
303
+ * The totals are carried beside the discrepancy list on purpose: two totals that
304
+ * agree while the lists differ is a real and common state (a charge recorded
305
+ * against the wrong account), and a report that only published a difference
306
+ * would call it clean.
307
+ */
308
+ exports.reconciliationRunSchema = zod_1.z
309
+ .object({
310
+ /** See `version.ts`: this shape is served to Console and to Alia. */
311
+ schemaVersion: zod_1.z.literal(1),
312
+ id: zod_1.z.string().min(1).max(64),
313
+ provider: exports.externalPaymentProviderSchema,
314
+ /** Absent for a platform-wide pass. */
315
+ accountId: identifiers_1.oxyAccountIdSchema.optional(),
316
+ currency: money_1.currencyCodeSchema,
317
+ periodStart: zod_1.z.string().datetime(),
318
+ periodEnd: zod_1.z.string().datetime(),
319
+ status: exports.reconciliationRunStatusSchema,
320
+ ledgerTotal: money_1.exactDecimalSchema,
321
+ externalTotal: money_1.exactDecimalSchema,
322
+ discrepancyCount: zod_1.z.number().int().nonnegative().safe(),
323
+ startedAt: zod_1.z.string().datetime(),
324
+ completedAt: zod_1.z.string().datetime().optional(),
325
+ })
326
+ .strict();
327
+ exports.reconciliationReportSchema = zod_1.z
328
+ .object({
329
+ /** See `version.ts`: this shape is served to Console and to Alia. */
330
+ schemaVersion: zod_1.z.literal(1),
331
+ run: exports.reconciliationRunSchema,
332
+ discrepancies: zod_1.z.array(exports.reconciliationDiscrepancySchema),
333
+ })
334
+ .strict();
@@ -88,10 +88,15 @@ exports.authenticatedPrincipalSchema = zod_1.z.object({
88
88
  * access, and lives outside {@link billingPrincipalSchema} so that no code path
89
89
  * can read it as the payer.
90
90
  *
91
- * `requestId` is generated by the data plane and always present; `generationId`
92
- * is present
93
- * once a generation exists, which is why it is optional on a request and
94
- * expected on a receipt.
91
+ * `requestId` is generated by the OXY EDGE, on admission and before
92
+ * authentication completes, and is always present (ADR 0007). Not by the data
93
+ * plane: a data plane that minted the correlation key could not correlate a
94
+ * request it never received, and the requests rejected at the edge are exactly
95
+ * the ones an operator most needs to find.
96
+ *
97
+ * `generationId` is present once a generation exists, which is why it is
98
+ * optional on a request and expected on a receipt. That one IS the data plane's,
99
+ * because only the data plane knows how many outputs a request produced.
95
100
  */
96
101
  exports.inferenceAttributionSchema = zod_1.z.object({
97
102
  principal: exports.authenticatedPrincipalSchema,
@@ -0,0 +1,217 @@
1
+ "use strict";
2
+ /**
3
+ * Product entitlements — the interface a first-party product (Alia) queries to
4
+ * learn what an account is entitled to, WITHOUT learning anything it could
5
+ * mistake for a balance.
6
+ *
7
+ * ## The separation this file is
8
+ *
9
+ * #972 states the failure mode outright: confusing a product subscription with
10
+ * pay-as-you-go inference usage. So the two live in disjoint shapes here, and
11
+ * the separation is structural rather than documented:
12
+ *
13
+ * - {@link productEntitlementSchema} carries PLAN and ALLOWANCES. Allowances
14
+ * are whole integer counts of a product entitlement (API credits, included
15
+ * requests) — `z.number().int()`, never `exactDecimalSchema`, so an allowance
16
+ * is not even the same TYPE as money and cannot be added to one.
17
+ * - `accountBalanceSchema` (in `accountBilling.ts`) carries MONEY, as exact
18
+ * decimal strings.
19
+ *
20
+ * There is no field anywhere that is both, and no schema that sums them. A
21
+ * consumer wanting "what can this account do right now" reads both sections and
22
+ * presents both; a consumer that only wanted one is not handed the other in a
23
+ * form it can accidentally arithmetic.
24
+ *
25
+ * ## Allowances do not change what a request COSTS
26
+ *
27
+ * An Alia plan may include an allowance of inference. Oxy still records the
28
+ * exact underlying cost of every request against the account's ledger — the
29
+ * allowance is a PRODUCT-side entitlement that decides what Alia charges its
30
+ * user, not a discount applied to the receipt. That is why
31
+ * {@link productEntitlementSchema} names no price and no currency: a plan that
32
+ * could restate the cost of a request would be a second pricing authority, and
33
+ * a receipt has exactly one.
34
+ *
35
+ * ## Cost centres
36
+ *
37
+ * A first-party cost centre IS an Oxy project account — `users.kind` already has
38
+ * `project`, and an application's `ownerAccountId` already points at one. So a
39
+ * cost centre adds a LABEL and a slug to an account rather than a parallel
40
+ * hierarchy, which is the epic's "do not add a second organization model" rule
41
+ * applied to internal accounting.
42
+ *
43
+ * Decided in: docs/adr/0014-account-billing-and-entitlements.md.
44
+ */
45
+ Object.defineProperty(exports, "__esModule", { value: true });
46
+ exports.productEntitlementSchema = exports.costCenterSpendSchema = exports.costCenterSchema = exports.costCenterStatusSchema = exports.COST_CENTER_STATUSES = exports.payAsYouGoEntitlementSchema = exports.productPlanSchema = exports.planAllowanceSchema = exports.LIVE_PRODUCT_PLAN_STATUSES = exports.productPlanStatusSchema = exports.PRODUCT_PLAN_STATUSES = void 0;
47
+ const zod_1 = require("zod");
48
+ const identifiers_1 = require("./identifiers");
49
+ const accountBilling_1 = require("./accountBilling");
50
+ const money_1 = require("./money");
51
+ /* -------------------------------------------------------------------------- */
52
+ /* Plans and allowances */
53
+ /* -------------------------------------------------------------------------- */
54
+ /**
55
+ * The statuses a subscription may be mirrored in.
56
+ *
57
+ * All of the processor's, not just the ones this platform sells: a mirror that
58
+ * cannot represent what it mirrors freezes at its previous value, and a
59
+ * subscription the processor moved to `paused` would keep granting a plan
60
+ * nobody is paying for.
61
+ */
62
+ exports.PRODUCT_PLAN_STATUSES = [
63
+ 'active',
64
+ 'canceled',
65
+ 'incomplete',
66
+ 'incomplete_expired',
67
+ 'past_due',
68
+ 'paused',
69
+ 'trialing',
70
+ 'unpaid',
71
+ ];
72
+ exports.productPlanStatusSchema = zod_1.z.enum(exports.PRODUCT_PLAN_STATUSES);
73
+ /**
74
+ * The statuses that mean the plan is LIVE.
75
+ *
76
+ * Exported because "is this entitlement in force" must have one answer across
77
+ * Oxy and every product consuming it — a consumer deriving its own list is how
78
+ * `past_due` comes to be honoured in one place and refused in another.
79
+ */
80
+ exports.LIVE_PRODUCT_PLAN_STATUSES = ['active', 'trialing'];
81
+ /**
82
+ * One allowance included in a plan.
83
+ *
84
+ * `remaining` is optional and absent means "not metered against this allowance
85
+ * here" — NOT zero. A consumer that read an absent allowance as exhausted would
86
+ * refuse a user who has spent nothing.
87
+ */
88
+ exports.planAllowanceSchema = zod_1.z
89
+ .object({
90
+ /** A stable machine name, e.g. `api_credits_per_month`. */
91
+ key: zod_1.z.string().regex(/^[a-z][a-z0-9_]{0,62}$/),
92
+ /** Whole units included per period. Never money. */
93
+ included: zod_1.z.number().int().nonnegative().safe(),
94
+ remaining: zod_1.z.number().int().nonnegative().safe().optional(),
95
+ })
96
+ .strict();
97
+ /**
98
+ * The plan an account is on, if any.
99
+ *
100
+ * `price` is deliberately absent. What a customer pays for their plan is the
101
+ * processor's record and this platform's `billing_transactions`; restating it
102
+ * here would put a second price authority in the one interface whose whole job
103
+ * is to keep product pricing and inference cost apart.
104
+ */
105
+ exports.productPlanSchema = zod_1.z
106
+ .object({
107
+ id: zod_1.z.string().min(1).max(64),
108
+ name: zod_1.z.string().min(1).max(120),
109
+ status: exports.productPlanStatusSchema,
110
+ live: zod_1.z.boolean(),
111
+ currentPeriodStart: zod_1.z.string().datetime(),
112
+ currentPeriodEnd: zod_1.z.string().datetime(),
113
+ cancelAtPeriodEnd: zod_1.z.boolean(),
114
+ allowances: zod_1.z.array(exports.planAllowanceSchema),
115
+ })
116
+ .strict();
117
+ /* -------------------------------------------------------------------------- */
118
+ /* Pay-as-you-go position */
119
+ /* -------------------------------------------------------------------------- */
120
+ /**
121
+ * The account's inference-spend position, summarised for a product consumer.
122
+ *
123
+ * A REDUCTION of `accountBillingStateSchema`, not a copy: a product asking "may
124
+ * this account run another request" needs to know whether spending is possible
125
+ * and roughly how much room is left, and does not need the bucket breakdown.
126
+ * `promotionalBalance` and `purchasedBalance` are still separate — the rule that
127
+ * a grant and a purchase are never one number does not relax because the
128
+ * consumer is first-party.
129
+ */
130
+ exports.payAsYouGoEntitlementSchema = zod_1.z
131
+ .object({
132
+ /** The account that actually pays — the nearest ancestor with a profile. */
133
+ billingAccountId: identifiers_1.oxyAccountIdSchema,
134
+ currency: money_1.currencyCodeSchema,
135
+ billingMode: accountBilling_1.billingModeSchema,
136
+ purchasedBalance: money_1.exactDecimalSchema,
137
+ promotionalBalance: money_1.exactDecimalSchema,
138
+ availableToSpend: money_1.exactDecimalSchema,
139
+ /** False when the profile is suspended, closed, or out of room. */
140
+ canSpend: zod_1.z.boolean(),
141
+ })
142
+ .strict();
143
+ /* -------------------------------------------------------------------------- */
144
+ /* Cost centres */
145
+ /* -------------------------------------------------------------------------- */
146
+ exports.COST_CENTER_STATUSES = ['active', 'retired'];
147
+ exports.costCenterStatusSchema = zod_1.z.enum(exports.COST_CENTER_STATUSES);
148
+ /**
149
+ * An internal cost centre — an Oxy account that first-party spend is attributed
150
+ * to, with a stable slug so a report can name it without an id.
151
+ *
152
+ * The account IS the cost centre; this shape only labels it. There is no
153
+ * `parentId` here for the same reason: the account graph already has one, and a
154
+ * second parent link would be a second hierarchy that can disagree with it.
155
+ */
156
+ exports.costCenterSchema = zod_1.z
157
+ .object({
158
+ /** See `version.ts`: this shape is served to Console and to Alia. */
159
+ schemaVersion: zod_1.z.literal(1),
160
+ accountId: identifiers_1.oxyAccountIdSchema,
161
+ slug: zod_1.z.string().regex(/^[a-z0-9][a-z0-9-]{0,62}$/),
162
+ label: zod_1.z.string().min(1).max(120),
163
+ status: exports.costCenterStatusSchema,
164
+ createdAt: zod_1.z.string().datetime(),
165
+ updatedAt: zod_1.z.string().datetime(),
166
+ })
167
+ .strict();
168
+ /**
169
+ * What one cost centre spent over a window.
170
+ *
171
+ * `billedAmount` comes from settled receipts — the FINANCIAL ledger — never from
172
+ * telemetry sums, per #972 workstream 8. `requestCount` is a count of receipts,
173
+ * so the two are always about the same set of rows.
174
+ */
175
+ exports.costCenterSpendSchema = zod_1.z
176
+ .object({
177
+ /** See `version.ts`: this shape is served to Console and to Alia. */
178
+ schemaVersion: zod_1.z.literal(1),
179
+ costCenter: exports.costCenterSchema,
180
+ currency: money_1.currencyCodeSchema,
181
+ periodStart: zod_1.z.string().datetime(),
182
+ periodEnd: zod_1.z.string().datetime(),
183
+ billedAmount: money_1.exactDecimalSchema,
184
+ requestCount: zod_1.z.number().int().nonnegative().safe(),
185
+ })
186
+ .strict();
187
+ /* -------------------------------------------------------------------------- */
188
+ /* The interface Alia queries */
189
+ /* -------------------------------------------------------------------------- */
190
+ /**
191
+ * Everything a product needs to decide what an account may do, in one read.
192
+ *
193
+ * The three sections never merge:
194
+ *
195
+ * - `plan` + `allowances` — the product subscription.
196
+ * - `payAsYouGo` — inference money. `null` when the account has no billing
197
+ * profile anywhere up its ancestry, which is a REAL and distinct state from a
198
+ * zero balance: nobody has decided who pays for this account yet.
199
+ * - `costCenter` — where first-party spend is booked, `null` for a customer.
200
+ */
201
+ exports.productEntitlementSchema = zod_1.z
202
+ .object({
203
+ /** See `version.ts`: this shape is served to Console and to Alia. */
204
+ schemaVersion: zod_1.z.literal(1),
205
+ accountId: identifiers_1.oxyAccountIdSchema,
206
+ plan: exports.productPlanSchema.nullable(),
207
+ /**
208
+ * Allowances in force right now, whether they came from a plan or from the
209
+ * platform's own free tier. Whole counts; never money.
210
+ */
211
+ allowances: zod_1.z.array(exports.planAllowanceSchema),
212
+ payAsYouGo: exports.payAsYouGoEntitlementSchema.nullable(),
213
+ costCenter: exports.costCenterSchema.nullable(),
214
+ /** When this view was computed. Entitlements are eventually consistent. */
215
+ resolvedAt: zod_1.z.string().datetime(),
216
+ })
217
+ .strict();