@oxy.so/contracts 1.0.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/LICENSE +202 -0
- package/NOTICE +16 -0
- package/dist/cjs/.tsbuildinfo +1 -0
- package/dist/cjs/accountGraph.js +489 -0
- package/dist/cjs/agency.js +439 -0
- package/dist/cjs/browserHub.js +215 -0
- package/dist/cjs/civic.js +163 -0
- package/dist/cjs/commonsSignIn.js +59 -0
- package/dist/cjs/deviceBoot.js +50 -0
- package/dist/cjs/deviceDirectory.js +189 -0
- package/dist/cjs/devicePairing.js +138 -0
- package/dist/cjs/deviceSession.js +164 -0
- package/dist/cjs/emailAgentContext.js +32 -0
- package/dist/cjs/followGraph.js +28 -0
- package/dist/cjs/identity.js +258 -0
- package/dist/cjs/inboxPush.js +24 -0
- package/dist/cjs/index.js +618 -0
- package/dist/cjs/inference/accountBilling.js +334 -0
- package/dist/cjs/inference/aliaModelRelease.js +262 -0
- package/dist/cjs/inference/attribution.js +106 -0
- package/dist/cjs/inference/catalogue.js +487 -0
- package/dist/cjs/inference/entitlement.js +217 -0
- package/dist/cjs/inference/errors.js +309 -0
- package/dist/cjs/inference/identifiers.js +224 -0
- package/dist/cjs/inference/inbox.js +105 -0
- package/dist/cjs/inference/modelDocumentation.js +433 -0
- package/dist/cjs/inference/money.js +188 -0
- package/dist/cjs/inference/priceVersion.js +110 -0
- package/dist/cjs/inference/providerConnection.js +455 -0
- package/dist/cjs/inference/request.js +477 -0
- package/dist/cjs/inference/routingPolicy.js +318 -0
- package/dist/cjs/inference/streamEvents.js +258 -0
- package/dist/cjs/inference/usage.js +329 -0
- package/dist/cjs/inference/version.js +105 -0
- package/dist/cjs/keyRecovery.js +91 -0
- package/dist/cjs/keyRotation.js +75 -0
- package/dist/cjs/links.js +68 -0
- package/dist/cjs/moderationReputation.js +298 -0
- package/dist/cjs/oauth.js +66 -0
- package/dist/cjs/oxyRecordTypes.js +71 -0
- package/dist/cjs/protocol.js +53 -0
- package/dist/cjs/recommendations.js +168 -0
- package/dist/cjs/reputation.js +297 -0
- package/dist/cjs/sessionStatus.js +121 -0
- package/dist/cjs/transparency.js +89 -0
- package/dist/cjs/updates.js +252 -0
- package/dist/cjs/userInvalidation.js +89 -0
- package/dist/cjs/userResponse.js +245 -0
- package/dist/cjs/username.js +290 -0
- package/dist/cjs/webauthn.js +71 -0
- package/dist/esm/.tsbuildinfo +1 -0
- package/dist/esm/accountGraph.js +480 -0
- package/dist/esm/agency.js +436 -0
- package/dist/esm/browserHub.js +212 -0
- package/dist/esm/civic.js +160 -0
- package/dist/esm/commonsSignIn.js +56 -0
- package/dist/esm/deviceBoot.js +47 -0
- package/dist/esm/deviceDirectory.js +186 -0
- package/dist/esm/devicePairing.js +135 -0
- package/dist/esm/deviceSession.js +161 -0
- package/dist/esm/emailAgentContext.js +29 -0
- package/dist/esm/followGraph.js +27 -0
- package/dist/esm/identity.js +255 -0
- package/dist/esm/inboxPush.js +21 -0
- package/dist/esm/index.js +172 -0
- package/dist/esm/inference/accountBilling.js +331 -0
- package/dist/esm/inference/aliaModelRelease.js +259 -0
- package/dist/esm/inference/attribution.js +103 -0
- package/dist/esm/inference/catalogue.js +484 -0
- package/dist/esm/inference/entitlement.js +214 -0
- package/dist/esm/inference/errors.js +306 -0
- package/dist/esm/inference/identifiers.js +221 -0
- package/dist/esm/inference/inbox.js +102 -0
- package/dist/esm/inference/modelDocumentation.js +430 -0
- package/dist/esm/inference/money.js +185 -0
- package/dist/esm/inference/priceVersion.js +107 -0
- package/dist/esm/inference/providerConnection.js +452 -0
- package/dist/esm/inference/request.js +474 -0
- package/dist/esm/inference/routingPolicy.js +315 -0
- package/dist/esm/inference/streamEvents.js +255 -0
- package/dist/esm/inference/usage.js +326 -0
- package/dist/esm/inference/version.js +102 -0
- package/dist/esm/keyRecovery.js +88 -0
- package/dist/esm/keyRotation.js +72 -0
- package/dist/esm/links.js +65 -0
- package/dist/esm/moderationReputation.js +295 -0
- package/dist/esm/oauth.js +63 -0
- package/dist/esm/oxyRecordTypes.js +68 -0
- package/dist/esm/protocol.js +50 -0
- package/dist/esm/recommendations.js +165 -0
- package/dist/esm/reputation.js +293 -0
- package/dist/esm/sessionStatus.js +118 -0
- package/dist/esm/transparency.js +86 -0
- package/dist/esm/updates.js +249 -0
- package/dist/esm/userInvalidation.js +85 -0
- package/dist/esm/userResponse.js +240 -0
- package/dist/esm/username.js +283 -0
- package/dist/esm/webauthn.js +68 -0
- package/dist/types/.tsbuildinfo +1 -0
- package/dist/types/accountGraph.d.ts +378 -0
- package/dist/types/agency.d.ts +2162 -0
- package/dist/types/browserHub.d.ts +856 -0
- package/dist/types/civic.d.ts +338 -0
- package/dist/types/commonsSignIn.d.ts +58 -0
- package/dist/types/deviceBoot.d.ts +74 -0
- package/dist/types/deviceDirectory.d.ts +1317 -0
- package/dist/types/devicePairing.d.ts +130 -0
- package/dist/types/deviceSession.d.ts +411 -0
- package/dist/types/emailAgentContext.d.ts +248 -0
- package/dist/types/followGraph.d.ts +150 -0
- package/dist/types/identity.d.ts +402 -0
- package/dist/types/inboxPush.d.ts +30 -0
- package/dist/types/index.d.ts +100 -0
- package/dist/types/inference/accountBilling.d.ts +738 -0
- package/dist/types/inference/aliaModelRelease.d.ts +609 -0
- package/dist/types/inference/attribution.d.ts +176 -0
- package/dist/types/inference/catalogue.d.ts +1618 -0
- package/dist/types/inference/entitlement.d.ts +519 -0
- package/dist/types/inference/errors.d.ts +242 -0
- package/dist/types/inference/identifiers.d.ts +182 -0
- package/dist/types/inference/inbox.d.ts +374 -0
- package/dist/types/inference/modelDocumentation.d.ts +1603 -0
- package/dist/types/inference/money.d.ts +185 -0
- package/dist/types/inference/priceVersion.d.ts +182 -0
- package/dist/types/inference/providerConnection.d.ts +968 -0
- package/dist/types/inference/request.d.ts +2800 -0
- package/dist/types/inference/routingPolicy.d.ts +616 -0
- package/dist/types/inference/streamEvents.d.ts +950 -0
- package/dist/types/inference/usage.d.ts +1164 -0
- package/dist/types/inference/version.d.ts +102 -0
- package/dist/types/keyRecovery.d.ts +138 -0
- package/dist/types/keyRotation.d.ts +103 -0
- package/dist/types/links.d.ts +96 -0
- package/dist/types/moderationReputation.d.ts +487 -0
- package/dist/types/oauth.d.ts +86 -0
- package/dist/types/oxyRecordTypes.d.ts +62 -0
- package/dist/types/protocol.d.ts +86 -0
- package/dist/types/recommendations.d.ts +542 -0
- package/dist/types/reputation.d.ts +457 -0
- package/dist/types/sessionStatus.d.ts +231 -0
- package/dist/types/transparency.d.ts +392 -0
- package/dist/types/updates.d.ts +545 -0
- package/dist/types/userInvalidation.d.ts +94 -0
- package/dist/types/userResponse.d.ts +1706 -0
- package/dist/types/username.d.ts +265 -0
- package/dist/types/webauthn.d.ts +77 -0
- package/package.json +87 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Price versions — the immutable snapshots customer pricing is quoted and
|
|
3
|
+
* settled against.
|
|
4
|
+
*
|
|
5
|
+
* A price is never edited in place. A change publishes a NEW version that
|
|
6
|
+
* supersedes the old one, and every settled receipt keeps the id of the version
|
|
7
|
+
* it was priced with. That is what makes an invoice reproducible a year later:
|
|
8
|
+
* the receipt does not say "3.00 per million tokens", it says "priced under
|
|
9
|
+
* `pv_2026_08`", and that version still exists, unchanged, with its own
|
|
10
|
+
* effective window.
|
|
11
|
+
*
|
|
12
|
+
* Prices are exact decimal strings (see `money.ts`), never floats, and they are
|
|
13
|
+
* quoted per unit. The amount a customer owes is computed from them and is
|
|
14
|
+
* carried in the same exact form, so no step of the calculation passes through
|
|
15
|
+
* a representation that cannot hold the value.
|
|
16
|
+
*
|
|
17
|
+
* Decided in: docs/adr/0009-usage-reservation-and-settlement.md.
|
|
18
|
+
*/
|
|
19
|
+
import { z } from 'zod';
|
|
20
|
+
import { inferenceTimestampSchema, modelReferenceSchema, inferenceProviderSlugSchema, } from './identifiers.js';
|
|
21
|
+
import { currencyCodeSchema, unitPriceSchema } from './money.js';
|
|
22
|
+
/**
|
|
23
|
+
* Lifecycle of a price version.
|
|
24
|
+
*
|
|
25
|
+
* `draft` is quotable in Console previews but may never price a receipt;
|
|
26
|
+
* `active` is what live requests are priced with; `superseded` priced receipts
|
|
27
|
+
* in the past and still resolves for them forever.
|
|
28
|
+
*/
|
|
29
|
+
export const priceVersionStatusSchema = z.enum(['draft', 'active', 'superseded']);
|
|
30
|
+
/**
|
|
31
|
+
* A published set of customer prices for one model reference on one provider.
|
|
32
|
+
*
|
|
33
|
+
* Scoped to a `(modelReference, provider)` pair rather than to a model alone
|
|
34
|
+
* because the same model costs different amounts on different providers, and a
|
|
35
|
+
* receipt has to be reproducible against the route that actually served it.
|
|
36
|
+
*/
|
|
37
|
+
export const priceVersionSchema = z
|
|
38
|
+
.object({
|
|
39
|
+
/** See `version.ts`: served on its own by the catalogue, so it is versioned. */
|
|
40
|
+
schemaVersion: z.literal(1),
|
|
41
|
+
priceVersionId: z.string().min(1).max(128),
|
|
42
|
+
status: priceVersionStatusSchema,
|
|
43
|
+
modelReference: modelReferenceSchema,
|
|
44
|
+
provider: inferenceProviderSlugSchema,
|
|
45
|
+
currency: currencyCodeSchema,
|
|
46
|
+
unitPrices: z.array(unitPriceSchema).min(1),
|
|
47
|
+
effectiveFrom: inferenceTimestampSchema,
|
|
48
|
+
/** Absent while this version is the current one. */
|
|
49
|
+
effectiveUntil: inferenceTimestampSchema.optional(),
|
|
50
|
+
/** The version this one replaced, absent for the first version of a route. */
|
|
51
|
+
supersedesPriceVersionId: z.string().min(1).max(128).optional(),
|
|
52
|
+
createdAt: inferenceTimestampSchema,
|
|
53
|
+
})
|
|
54
|
+
.superRefine((priceVersion, ctx) => {
|
|
55
|
+
const units = priceVersion.unitPrices.map((price) => price.unit);
|
|
56
|
+
if (new Set(units).size !== units.length) {
|
|
57
|
+
ctx.addIssue({
|
|
58
|
+
code: z.ZodIssueCode.custom,
|
|
59
|
+
path: ['unitPrices'],
|
|
60
|
+
message: 'a unit may be priced only once per price version',
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
for (const [index, price] of priceVersion.unitPrices.entries()) {
|
|
64
|
+
if (price.currency !== priceVersion.currency) {
|
|
65
|
+
ctx.addIssue({
|
|
66
|
+
code: z.ZodIssueCode.custom,
|
|
67
|
+
path: ['unitPrices', index, 'currency'],
|
|
68
|
+
message: 'every unit price must be quoted in the price version currency',
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
// Compared as instants, not as strings: `…T00:00:00Z` and `…T00:00:00.000Z`
|
|
73
|
+
// are the same moment and sort differently as text.
|
|
74
|
+
if (priceVersion.effectiveUntil !== undefined &&
|
|
75
|
+
Date.parse(priceVersion.effectiveUntil) <= Date.parse(priceVersion.effectiveFrom)) {
|
|
76
|
+
ctx.addIssue({
|
|
77
|
+
code: z.ZodIssueCode.custom,
|
|
78
|
+
path: ['effectiveUntil'],
|
|
79
|
+
message: 'a price version must stop applying after it started applying',
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
// A superseded version priced requests during a window that has closed. Left
|
|
83
|
+
// open, it is indistinguishable from the current one when a receipt is
|
|
84
|
+
// re-priced years later — which is the one job this record exists to do.
|
|
85
|
+
if (priceVersion.status === 'superseded' && priceVersion.effectiveUntil === undefined) {
|
|
86
|
+
ctx.addIssue({
|
|
87
|
+
code: z.ZodIssueCode.custom,
|
|
88
|
+
path: ['effectiveUntil'],
|
|
89
|
+
message: 'a superseded price version must record when it stopped applying',
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
/**
|
|
94
|
+
* The price snapshot a settled receipt keeps.
|
|
95
|
+
*
|
|
96
|
+
* The unit prices are COPIED onto the receipt, not just referenced, so a receipt
|
|
97
|
+
* remains readable even if the price version record is later archived, and so
|
|
98
|
+
* that a mistake in the copy is visible as a disagreement with the version it
|
|
99
|
+
* names rather than silently invisible.
|
|
100
|
+
*/
|
|
101
|
+
export const priceSnapshotSchema = z
|
|
102
|
+
.object({
|
|
103
|
+
priceVersionId: z.string().min(1).max(128),
|
|
104
|
+
currency: currencyCodeSchema,
|
|
105
|
+
unitPrices: z.array(unitPriceSchema).min(1),
|
|
106
|
+
})
|
|
107
|
+
.strict();
|
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BYOK provider connections — the metadata Oxy holds about a customer's own
|
|
3
|
+
* upstream provider credential.
|
|
4
|
+
*
|
|
5
|
+
* The credential itself is NOT here and cannot be put here. This shape carries
|
|
6
|
+
* an opaque Kaana credential handle, its exact revision and validation state.
|
|
7
|
+
* Three mechanisms
|
|
8
|
+
* make that structural rather than a convention somebody must remember:
|
|
9
|
+
*
|
|
10
|
+
* - The object is `.strict()`. A producer that attaches `apiKey`, `secret`,
|
|
11
|
+
* `token`, `privateKey` or `headers` fails the parse. Nothing is silently
|
|
12
|
+
* stripped, because a stripped field is one that still exists upstream of
|
|
13
|
+
* the parse, in a log line or an error report.
|
|
14
|
+
* - No prefix or digest derived from the credential exists in this contract;
|
|
15
|
+
* even a partial value can make a short credential recoverable by guessing.
|
|
16
|
+
* - `credentialHandle` is an opaque, closed-format identifier minted by Kaana.
|
|
17
|
+
* Oxy cannot resolve it and never stores either plaintext or ciphertext.
|
|
18
|
+
*
|
|
19
|
+
* BYOK does not move the billing relationship: the upstream provider bills the
|
|
20
|
+
* customer's own account directly, and Oxy charges only its platform fee. The
|
|
21
|
+
* record says so explicitly so a receipt against a BYOK route can be read
|
|
22
|
+
* correctly without consulting anything else.
|
|
23
|
+
*
|
|
24
|
+
* Decided in: issue #972 workstream 10.
|
|
25
|
+
*/
|
|
26
|
+
import { z } from 'zod';
|
|
27
|
+
import { deploymentIdSchema, inferenceEnvironmentSchema, inferenceProviderSlugSchema, inferenceTimestampSchema, oxyAccountIdSchema, oxyApplicationIdSchema, } from './identifiers.js';
|
|
28
|
+
/**
|
|
29
|
+
* How widely a connection applies.
|
|
30
|
+
*
|
|
31
|
+
* In the unified account graph a project IS an account, so `account` and
|
|
32
|
+
* `project` differ by INHERITANCE, not by id space: an `account` connection is
|
|
33
|
+
* inherited by every descendant project and application, a `project` one
|
|
34
|
+
* applies to that project account alone, and an `application` one to a single
|
|
35
|
+
* application. Recording which the customer chose is what makes a later
|
|
36
|
+
* "why did this app use that key" answerable.
|
|
37
|
+
*/
|
|
38
|
+
export const providerConnectionScopeSchema = z.discriminatedUnion('kind', [
|
|
39
|
+
z.object({ kind: z.literal('account'), accountId: oxyAccountIdSchema }).strict(),
|
|
40
|
+
z.object({ kind: z.literal('project'), accountId: oxyAccountIdSchema }).strict(),
|
|
41
|
+
z
|
|
42
|
+
.object({
|
|
43
|
+
kind: z.literal('application'),
|
|
44
|
+
accountId: oxyAccountIdSchema,
|
|
45
|
+
applicationId: oxyApplicationIdSchema,
|
|
46
|
+
})
|
|
47
|
+
.strict(),
|
|
48
|
+
]);
|
|
49
|
+
/** Opaque reference minted by Kaana. It is not a KMS/Vault/SSM locator. */
|
|
50
|
+
export const kaanaCredentialHandleSchema = z
|
|
51
|
+
.string()
|
|
52
|
+
.regex(/^kcred_[a-z2-7]{26}$/, 'a Kaana credential handle is kcred_ plus 26 base32 characters');
|
|
53
|
+
/** Oxy-minted, case-sensitive replay identity for one exact Kaana mutation. */
|
|
54
|
+
export const kaanaCredentialOperationIdSchema = z
|
|
55
|
+
.string()
|
|
56
|
+
.regex(/^[A-Za-z0-9_-]{1,128}$/, 'a Kaana credential operation id is 1-128 opaque characters');
|
|
57
|
+
export const kaanaCredentialOperationActionSchema = z.enum(['create', 'rotate', 'revoke']);
|
|
58
|
+
/** Exact immutable Oxy identity repeated by both mutation and reconciliation. */
|
|
59
|
+
export const kaanaCredentialIdentitySchema = z
|
|
60
|
+
.object({
|
|
61
|
+
provider: inferenceProviderSlugSchema,
|
|
62
|
+
ownerAccountId: z.string().regex(/^[A-Za-z0-9_-]{1,64}$/),
|
|
63
|
+
connectionId: z.string().regex(/^[A-Za-z0-9_-]{1,128}$/),
|
|
64
|
+
environment: inferenceEnvironmentSchema,
|
|
65
|
+
})
|
|
66
|
+
.strict();
|
|
67
|
+
const kaanaCredentialOperationActorSchema = z
|
|
68
|
+
.string()
|
|
69
|
+
.min(1)
|
|
70
|
+
.refine((value) => new TextEncoder().encode(value).byteLength <= 256 &&
|
|
71
|
+
value === value.trim() &&
|
|
72
|
+
!/[\r\n]/.test(value), {
|
|
73
|
+
message: 'a credential operation actor is one trimmed line of at most 256 bytes',
|
|
74
|
+
});
|
|
75
|
+
const base64Alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
|
76
|
+
/**
|
|
77
|
+
* Decode only enough of a strict base64 value to prove every output byte is
|
|
78
|
+
* visible ASCII. Keeping this implementation local avoids a Node Buffer or
|
|
79
|
+
* browser atob dependency in the universal contracts package.
|
|
80
|
+
*/
|
|
81
|
+
function isVisibleASCIIProviderCredential(value) {
|
|
82
|
+
const padding = value.endsWith('==') ? 2 : value.endsWith('=') ? 1 : 0;
|
|
83
|
+
const decodedLength = (value.length / 4) * 3 - padding;
|
|
84
|
+
if (decodedLength < 1 || decodedLength > 4096)
|
|
85
|
+
return false;
|
|
86
|
+
for (let offset = 0, output = 0; offset < value.length; offset += 4, output += 3) {
|
|
87
|
+
const first = base64Alphabet.indexOf(value[offset] ?? '');
|
|
88
|
+
const second = base64Alphabet.indexOf(value[offset + 1] ?? '');
|
|
89
|
+
const third = value[offset + 2] === '=' ? 0 : base64Alphabet.indexOf(value[offset + 2] ?? '');
|
|
90
|
+
const fourth = value[offset + 3] === '=' ? 0 : base64Alphabet.indexOf(value[offset + 3] ?? '');
|
|
91
|
+
if (first < 0 || second < 0 || third < 0 || fourth < 0)
|
|
92
|
+
return false;
|
|
93
|
+
const finalQuartet = offset + 4 === value.length;
|
|
94
|
+
if (finalQuartet &&
|
|
95
|
+
((padding === 2 && (second & 0x0f) !== 0) ||
|
|
96
|
+
(padding === 1 && (third & 0x03) !== 0))) {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
const packed = (first << 18) | (second << 12) | (third << 6) | fourth;
|
|
100
|
+
const bytes = [(packed >> 16) & 0xff, (packed >> 8) & 0xff, packed & 0xff];
|
|
101
|
+
const count = Math.min(3, decodedLength - output);
|
|
102
|
+
for (let index = 0; index < count; index += 1) {
|
|
103
|
+
const byte = bytes[index];
|
|
104
|
+
if (byte === undefined || byte < 0x21 || byte > 0x7e)
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
const kaanaCredentialSecretBase64Schema = z
|
|
111
|
+
.string()
|
|
112
|
+
.min(1)
|
|
113
|
+
.max(8192)
|
|
114
|
+
.regex(/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/)
|
|
115
|
+
.refine(isVisibleASCIIProviderCredential, {
|
|
116
|
+
message: 'a decoded provider credential is 1-4096 visible ASCII bytes',
|
|
117
|
+
});
|
|
118
|
+
export const kaanaCredentialCreateMutationSchema = kaanaCredentialIdentitySchema
|
|
119
|
+
.extend({
|
|
120
|
+
schemaVersion: z.literal(1),
|
|
121
|
+
action: z.literal('create'),
|
|
122
|
+
operationId: kaanaCredentialOperationIdSchema,
|
|
123
|
+
operationActor: kaanaCredentialOperationActorSchema,
|
|
124
|
+
secretBase64: kaanaCredentialSecretBase64Schema,
|
|
125
|
+
})
|
|
126
|
+
.strict();
|
|
127
|
+
export const kaanaCredentialRotateMutationSchema = kaanaCredentialIdentitySchema
|
|
128
|
+
.extend({
|
|
129
|
+
schemaVersion: z.literal(1),
|
|
130
|
+
action: z.literal('rotate'),
|
|
131
|
+
operationId: kaanaCredentialOperationIdSchema,
|
|
132
|
+
operationActor: kaanaCredentialOperationActorSchema,
|
|
133
|
+
credentialHandle: kaanaCredentialHandleSchema,
|
|
134
|
+
expectedRevision: z.number().int().positive().max(Number.MAX_SAFE_INTEGER - 1),
|
|
135
|
+
secretBase64: kaanaCredentialSecretBase64Schema,
|
|
136
|
+
})
|
|
137
|
+
.strict();
|
|
138
|
+
export const kaanaCredentialRevokeMutationSchema = kaanaCredentialIdentitySchema
|
|
139
|
+
.extend({
|
|
140
|
+
schemaVersion: z.literal(1),
|
|
141
|
+
action: z.literal('revoke'),
|
|
142
|
+
operationId: kaanaCredentialOperationIdSchema,
|
|
143
|
+
operationActor: kaanaCredentialOperationActorSchema,
|
|
144
|
+
credentialHandle: kaanaCredentialHandleSchema,
|
|
145
|
+
expectedRevision: z.number().int().positive().max(Number.MAX_SAFE_INTEGER - 1),
|
|
146
|
+
})
|
|
147
|
+
.strict();
|
|
148
|
+
export const kaanaCredentialMutationSchema = z.discriminatedUnion('action', [
|
|
149
|
+
kaanaCredentialCreateMutationSchema,
|
|
150
|
+
kaanaCredentialRotateMutationSchema,
|
|
151
|
+
kaanaCredentialRevokeMutationSchema,
|
|
152
|
+
]);
|
|
153
|
+
export const kaanaCredentialCreateOutcomeRequestSchema = kaanaCredentialIdentitySchema
|
|
154
|
+
.extend({
|
|
155
|
+
schemaVersion: z.literal(1),
|
|
156
|
+
action: z.literal('create'),
|
|
157
|
+
operationId: kaanaCredentialOperationIdSchema,
|
|
158
|
+
})
|
|
159
|
+
.strict();
|
|
160
|
+
export const kaanaCredentialRotateOutcomeRequestSchema = kaanaCredentialIdentitySchema
|
|
161
|
+
.extend({
|
|
162
|
+
schemaVersion: z.literal(1),
|
|
163
|
+
action: z.literal('rotate'),
|
|
164
|
+
operationId: kaanaCredentialOperationIdSchema,
|
|
165
|
+
credentialHandle: kaanaCredentialHandleSchema,
|
|
166
|
+
expectedRevision: z.number().int().positive().max(Number.MAX_SAFE_INTEGER - 1),
|
|
167
|
+
})
|
|
168
|
+
.strict();
|
|
169
|
+
export const kaanaCredentialRevokeOutcomeRequestSchema = kaanaCredentialIdentitySchema
|
|
170
|
+
.extend({
|
|
171
|
+
schemaVersion: z.literal(1),
|
|
172
|
+
action: z.literal('revoke'),
|
|
173
|
+
operationId: kaanaCredentialOperationIdSchema,
|
|
174
|
+
credentialHandle: kaanaCredentialHandleSchema,
|
|
175
|
+
expectedRevision: z.number().int().positive().max(Number.MAX_SAFE_INTEGER - 1),
|
|
176
|
+
})
|
|
177
|
+
.strict();
|
|
178
|
+
export const kaanaCredentialOutcomeRequestSchema = z.discriminatedUnion('action', [
|
|
179
|
+
kaanaCredentialCreateOutcomeRequestSchema,
|
|
180
|
+
kaanaCredentialRotateOutcomeRequestSchema,
|
|
181
|
+
kaanaCredentialRevokeOutcomeRequestSchema,
|
|
182
|
+
]);
|
|
183
|
+
export const kaanaCredentialAppliedOutcomeSchema = z
|
|
184
|
+
.object({
|
|
185
|
+
schemaVersion: z.literal(1),
|
|
186
|
+
operationId: kaanaCredentialOperationIdSchema,
|
|
187
|
+
action: kaanaCredentialOperationActionSchema,
|
|
188
|
+
status: z.literal('applied'),
|
|
189
|
+
credentialHandle: kaanaCredentialHandleSchema,
|
|
190
|
+
revision: z.number().int().positive().safe(),
|
|
191
|
+
})
|
|
192
|
+
.strict();
|
|
193
|
+
export const kaanaCredentialConflictOutcomeSchema = z
|
|
194
|
+
.object({
|
|
195
|
+
schemaVersion: z.literal(1),
|
|
196
|
+
operationId: kaanaCredentialOperationIdSchema,
|
|
197
|
+
action: kaanaCredentialOperationActionSchema,
|
|
198
|
+
status: z.literal('conflict'),
|
|
199
|
+
})
|
|
200
|
+
.strict();
|
|
201
|
+
export const kaanaCredentialOutcomeSchema = z.discriminatedUnion('status', [
|
|
202
|
+
kaanaCredentialAppliedOutcomeSchema,
|
|
203
|
+
kaanaCredentialConflictOutcomeSchema,
|
|
204
|
+
]);
|
|
205
|
+
/**
|
|
206
|
+
* One separately authenticated check of a quarantined BYOK generation.
|
|
207
|
+
*
|
|
208
|
+
* This is deliberately not an inference request. It carries no prompt, user
|
|
209
|
+
* response, routing policy or billing principal, and it can select only one
|
|
210
|
+
* exact Kaana deployment plus one exact credential generation.
|
|
211
|
+
*/
|
|
212
|
+
export const kaanaCredentialValidationTaskSchema = kaanaCredentialIdentitySchema
|
|
213
|
+
.extend({
|
|
214
|
+
schemaVersion: z.literal(1),
|
|
215
|
+
operationId: kaanaCredentialOperationIdSchema,
|
|
216
|
+
applicationId: oxyApplicationIdSchema,
|
|
217
|
+
credentialHandle: kaanaCredentialHandleSchema,
|
|
218
|
+
credentialRevision: z.number().int().positive().safe(),
|
|
219
|
+
deploymentId: deploymentIdSchema,
|
|
220
|
+
})
|
|
221
|
+
.strict();
|
|
222
|
+
export const kaanaCredentialValidationOutcomeStateSchema = z.enum([
|
|
223
|
+
'pending',
|
|
224
|
+
'valid',
|
|
225
|
+
'invalid',
|
|
226
|
+
'inconclusive',
|
|
227
|
+
]);
|
|
228
|
+
export const kaanaCredentialValidationFailureCodeSchema = z.enum([
|
|
229
|
+
'unauthorized',
|
|
230
|
+
'forbidden',
|
|
231
|
+
'not_found',
|
|
232
|
+
'rate_limited',
|
|
233
|
+
'network',
|
|
234
|
+
'unknown',
|
|
235
|
+
]);
|
|
236
|
+
/**
|
|
237
|
+
* Durable result for one exact validation operation. `inconclusive` is a
|
|
238
|
+
* terminal answer about the attempt, never evidence that the credential is
|
|
239
|
+
* invalid; Oxy leaves the generation quarantined and may start a new exact
|
|
240
|
+
* operation. Kaana reports terminal outcomes through its service principal.
|
|
241
|
+
*/
|
|
242
|
+
export const kaanaCredentialValidationOutcomeSchema = kaanaCredentialValidationTaskSchema
|
|
243
|
+
.extend({
|
|
244
|
+
state: kaanaCredentialValidationOutcomeStateSchema,
|
|
245
|
+
failureCode: kaanaCredentialValidationFailureCodeSchema.optional(),
|
|
246
|
+
})
|
|
247
|
+
.strict()
|
|
248
|
+
.superRefine((outcome, ctx) => {
|
|
249
|
+
if ((outcome.state === 'pending' || outcome.state === 'valid') &&
|
|
250
|
+
outcome.failureCode !== undefined) {
|
|
251
|
+
ctx.addIssue({
|
|
252
|
+
code: z.ZodIssueCode.custom,
|
|
253
|
+
path: ['failureCode'],
|
|
254
|
+
message: 'a valid credential validation carries no failure code',
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
if ((outcome.state === 'invalid' || outcome.state === 'inconclusive') &&
|
|
258
|
+
outcome.failureCode === undefined) {
|
|
259
|
+
ctx.addIssue({
|
|
260
|
+
code: z.ZodIssueCode.custom,
|
|
261
|
+
path: ['failureCode'],
|
|
262
|
+
message: 'a failed credential validation must state its closed failure code',
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
if (outcome.state === 'invalid' && outcome.failureCode !== 'unauthorized') {
|
|
266
|
+
ctx.addIssue({
|
|
267
|
+
code: z.ZodIssueCode.custom,
|
|
268
|
+
path: ['failureCode'],
|
|
269
|
+
message: 'only a provider authentication refusal proves invalidity',
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
if (outcome.state === 'inconclusive' && outcome.failureCode === 'unauthorized') {
|
|
273
|
+
ctx.addIssue({
|
|
274
|
+
code: z.ZodIssueCode.custom,
|
|
275
|
+
path: ['failureCode'],
|
|
276
|
+
message: 'an explicit authentication refusal is invalid, not inconclusive',
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
/**
|
|
281
|
+
* Customer-safe view of one explicit bootstrap attempt.
|
|
282
|
+
*
|
|
283
|
+
* `deploymentId` is the exact Oxy catalogue row selected by the customer. The
|
|
284
|
+
* internal Kaana route id remains protected; Oxy binds the two in its durable
|
|
285
|
+
* ledger and signs the latter to Kaana.
|
|
286
|
+
*/
|
|
287
|
+
export const providerCredentialValidationOperationSchema = z
|
|
288
|
+
.object({
|
|
289
|
+
schemaVersion: z.literal(1),
|
|
290
|
+
operationId: kaanaCredentialOperationIdSchema,
|
|
291
|
+
connectionId: z.string().min(1).max(128),
|
|
292
|
+
applicationId: oxyApplicationIdSchema,
|
|
293
|
+
deploymentId: deploymentIdSchema,
|
|
294
|
+
state: kaanaCredentialValidationOutcomeStateSchema,
|
|
295
|
+
failureCode: kaanaCredentialValidationFailureCodeSchema.optional(),
|
|
296
|
+
createdAt: inferenceTimestampSchema,
|
|
297
|
+
completedAt: inferenceTimestampSchema.optional(),
|
|
298
|
+
})
|
|
299
|
+
.strict()
|
|
300
|
+
.superRefine((operation, ctx) => {
|
|
301
|
+
const terminal = operation.state !== 'pending';
|
|
302
|
+
if (terminal !== (operation.completedAt !== undefined)) {
|
|
303
|
+
ctx.addIssue({
|
|
304
|
+
code: z.ZodIssueCode.custom,
|
|
305
|
+
path: ['completedAt'],
|
|
306
|
+
message: 'only a terminal validation operation has a completion time',
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
if ((operation.state === 'pending' || operation.state === 'valid') &&
|
|
310
|
+
operation.failureCode !== undefined) {
|
|
311
|
+
ctx.addIssue({
|
|
312
|
+
code: z.ZodIssueCode.custom,
|
|
313
|
+
path: ['failureCode'],
|
|
314
|
+
message: 'pending and valid validation operations carry no failure code',
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
if ((operation.state === 'invalid' || operation.state === 'inconclusive') &&
|
|
318
|
+
operation.failureCode === undefined) {
|
|
319
|
+
ctx.addIssue({
|
|
320
|
+
code: z.ZodIssueCode.custom,
|
|
321
|
+
path: ['failureCode'],
|
|
322
|
+
message: 'a failed validation operation must state its closed failure code',
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
if (operation.state === 'invalid' && operation.failureCode !== 'unauthorized') {
|
|
326
|
+
ctx.addIssue({
|
|
327
|
+
code: z.ZodIssueCode.custom,
|
|
328
|
+
path: ['failureCode'],
|
|
329
|
+
message: 'only a provider authentication refusal proves invalidity',
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
if (operation.state === 'inconclusive' && operation.failureCode === 'unauthorized') {
|
|
333
|
+
ctx.addIssue({
|
|
334
|
+
code: z.ZodIssueCode.custom,
|
|
335
|
+
path: ['failureCode'],
|
|
336
|
+
message: 'an explicit authentication refusal is invalid, not inconclusive',
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
});
|
|
340
|
+
/** Exact customer-selectable catalogue ids; no internal Kaana route is exposed. */
|
|
341
|
+
export const providerCredentialValidationDeploymentSchema = z
|
|
342
|
+
.object({ deploymentId: deploymentIdSchema })
|
|
343
|
+
.strict();
|
|
344
|
+
/**
|
|
345
|
+
* Oxy's view of the cross-service mutation. Only `ready` may be routed.
|
|
346
|
+
* `reconcile` is the fail-closed state after an outcome could not be proven.
|
|
347
|
+
*/
|
|
348
|
+
export const providerCredentialCustodyStateSchema = z.enum([
|
|
349
|
+
'pending',
|
|
350
|
+
'ready',
|
|
351
|
+
'reconcile',
|
|
352
|
+
'revoked',
|
|
353
|
+
]);
|
|
354
|
+
/** Why a credential check failed, as a closed set the Console can render. */
|
|
355
|
+
export const providerConnectionValidationSchema = z
|
|
356
|
+
.object({
|
|
357
|
+
state: z.enum(['unvalidated', 'valid', 'invalid', 'expired']),
|
|
358
|
+
lastValidatedAt: inferenceTimestampSchema.optional(),
|
|
359
|
+
/** Required when `invalid`: a failure nobody can act on is not a result. */
|
|
360
|
+
failureCode: z
|
|
361
|
+
.enum(['unauthorized', 'forbidden', 'not_found', 'rate_limited', 'network', 'unknown'])
|
|
362
|
+
.optional(),
|
|
363
|
+
})
|
|
364
|
+
.strict();
|
|
365
|
+
/**
|
|
366
|
+
* Lifecycle of a connection. `pending_validation` is quarantined from normal
|
|
367
|
+
* serving, `revoked` is terminal, and `disabled` is reversible.
|
|
368
|
+
*/
|
|
369
|
+
export const providerConnectionStatusSchema = z.enum([
|
|
370
|
+
'pending_validation',
|
|
371
|
+
'active',
|
|
372
|
+
'disabled',
|
|
373
|
+
'revoked',
|
|
374
|
+
]);
|
|
375
|
+
/**
|
|
376
|
+
* A customer's provider connection, without secrets.
|
|
377
|
+
*
|
|
378
|
+
* This is the whole of what Oxy stores, and the whole of what the data plane
|
|
379
|
+
* is given.
|
|
380
|
+
* Resolving the opaque handle to plaintext happens only inside Kaana inference.
|
|
381
|
+
*/
|
|
382
|
+
export const providerConnectionSchema = z
|
|
383
|
+
.object({
|
|
384
|
+
/** See `version.ts`: exchanged with the data plane and rendered by Console. */
|
|
385
|
+
schemaVersion: z.literal(2),
|
|
386
|
+
connectionId: z.string().min(1).max(128),
|
|
387
|
+
provider: inferenceProviderSlugSchema,
|
|
388
|
+
/** The Oxy account that owns the connection and answers for its use. */
|
|
389
|
+
ownerAccountId: oxyAccountIdSchema,
|
|
390
|
+
scope: providerConnectionScopeSchema,
|
|
391
|
+
environment: inferenceEnvironmentSchema,
|
|
392
|
+
status: providerConnectionStatusSchema,
|
|
393
|
+
custodyState: providerCredentialCustodyStateSchema,
|
|
394
|
+
credentialHandle: kaanaCredentialHandleSchema.optional(),
|
|
395
|
+
credentialRevision: z.number().int().positive().safe().optional(),
|
|
396
|
+
validation: providerConnectionValidationSchema,
|
|
397
|
+
/**
|
|
398
|
+
* Always `true` for a BYOK connection: the provider bills the customer's own
|
|
399
|
+
* upstream account, and Oxy charges only its platform fee. Stated as data so
|
|
400
|
+
* a receipt against this route is readable without a second lookup.
|
|
401
|
+
*/
|
|
402
|
+
upstreamBillsCustomerDirectly: z.literal(true),
|
|
403
|
+
/** Set when the provider's terms require a per-customer acknowledgement. */
|
|
404
|
+
termsAcknowledgedAt: inferenceTimestampSchema.optional(),
|
|
405
|
+
createdAt: inferenceTimestampSchema,
|
|
406
|
+
rotatedAt: inferenceTimestampSchema.optional(),
|
|
407
|
+
})
|
|
408
|
+
.strict()
|
|
409
|
+
.superRefine((connection, ctx) => {
|
|
410
|
+
if (connection.validation.state === 'invalid' &&
|
|
411
|
+
connection.validation.failureCode === undefined) {
|
|
412
|
+
ctx.addIssue({
|
|
413
|
+
code: z.ZodIssueCode.custom,
|
|
414
|
+
path: ['validation', 'failureCode'],
|
|
415
|
+
message: 'an invalid credential must record why the check failed',
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
// `active` is evidence that this exact credential generation passed the
|
|
419
|
+
// provider check. Pending, expired or rejected credentials cannot be
|
|
420
|
+
// represented as active.
|
|
421
|
+
if (connection.status === 'active' && connection.validation.state !== 'valid') {
|
|
422
|
+
ctx.addIssue({
|
|
423
|
+
code: z.ZodIssueCode.custom,
|
|
424
|
+
path: ['status'],
|
|
425
|
+
message: 'only a successfully validated credential can be active',
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
const hasReference = connection.credentialHandle !== undefined && connection.credentialRevision !== undefined;
|
|
429
|
+
if ((connection.custodyState === 'ready' || connection.custodyState === 'revoked') &&
|
|
430
|
+
!hasReference) {
|
|
431
|
+
ctx.addIssue({
|
|
432
|
+
code: z.ZodIssueCode.custom,
|
|
433
|
+
path: ['credentialHandle'],
|
|
434
|
+
message: 'ready and revoked custody states require an exact Kaana handle and revision',
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
if (connection.custodyState === 'pending' && hasReference) {
|
|
438
|
+
ctx.addIssue({
|
|
439
|
+
code: z.ZodIssueCode.custom,
|
|
440
|
+
path: ['custodyState'],
|
|
441
|
+
message: 'a pending create cannot claim a Kaana reference before Kaana acknowledges it',
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
if ((connection.credentialHandle === undefined) !==
|
|
445
|
+
(connection.credentialRevision === undefined)) {
|
|
446
|
+
ctx.addIssue({
|
|
447
|
+
code: z.ZodIssueCode.custom,
|
|
448
|
+
path: ['credentialRevision'],
|
|
449
|
+
message: 'a Kaana credential handle and revision are present or absent together',
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
});
|