@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.
Files changed (147) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +16 -0
  3. package/dist/cjs/.tsbuildinfo +1 -0
  4. package/dist/cjs/accountGraph.js +489 -0
  5. package/dist/cjs/agency.js +439 -0
  6. package/dist/cjs/browserHub.js +215 -0
  7. package/dist/cjs/civic.js +163 -0
  8. package/dist/cjs/commonsSignIn.js +59 -0
  9. package/dist/cjs/deviceBoot.js +50 -0
  10. package/dist/cjs/deviceDirectory.js +189 -0
  11. package/dist/cjs/devicePairing.js +138 -0
  12. package/dist/cjs/deviceSession.js +164 -0
  13. package/dist/cjs/emailAgentContext.js +32 -0
  14. package/dist/cjs/followGraph.js +28 -0
  15. package/dist/cjs/identity.js +258 -0
  16. package/dist/cjs/inboxPush.js +24 -0
  17. package/dist/cjs/index.js +618 -0
  18. package/dist/cjs/inference/accountBilling.js +334 -0
  19. package/dist/cjs/inference/aliaModelRelease.js +262 -0
  20. package/dist/cjs/inference/attribution.js +106 -0
  21. package/dist/cjs/inference/catalogue.js +487 -0
  22. package/dist/cjs/inference/entitlement.js +217 -0
  23. package/dist/cjs/inference/errors.js +309 -0
  24. package/dist/cjs/inference/identifiers.js +224 -0
  25. package/dist/cjs/inference/inbox.js +105 -0
  26. package/dist/cjs/inference/modelDocumentation.js +433 -0
  27. package/dist/cjs/inference/money.js +188 -0
  28. package/dist/cjs/inference/priceVersion.js +110 -0
  29. package/dist/cjs/inference/providerConnection.js +455 -0
  30. package/dist/cjs/inference/request.js +477 -0
  31. package/dist/cjs/inference/routingPolicy.js +318 -0
  32. package/dist/cjs/inference/streamEvents.js +258 -0
  33. package/dist/cjs/inference/usage.js +329 -0
  34. package/dist/cjs/inference/version.js +105 -0
  35. package/dist/cjs/keyRecovery.js +91 -0
  36. package/dist/cjs/keyRotation.js +75 -0
  37. package/dist/cjs/links.js +68 -0
  38. package/dist/cjs/moderationReputation.js +298 -0
  39. package/dist/cjs/oauth.js +66 -0
  40. package/dist/cjs/oxyRecordTypes.js +71 -0
  41. package/dist/cjs/protocol.js +53 -0
  42. package/dist/cjs/recommendations.js +168 -0
  43. package/dist/cjs/reputation.js +297 -0
  44. package/dist/cjs/sessionStatus.js +121 -0
  45. package/dist/cjs/transparency.js +89 -0
  46. package/dist/cjs/updates.js +252 -0
  47. package/dist/cjs/userInvalidation.js +89 -0
  48. package/dist/cjs/userResponse.js +245 -0
  49. package/dist/cjs/username.js +290 -0
  50. package/dist/cjs/webauthn.js +71 -0
  51. package/dist/esm/.tsbuildinfo +1 -0
  52. package/dist/esm/accountGraph.js +480 -0
  53. package/dist/esm/agency.js +436 -0
  54. package/dist/esm/browserHub.js +212 -0
  55. package/dist/esm/civic.js +160 -0
  56. package/dist/esm/commonsSignIn.js +56 -0
  57. package/dist/esm/deviceBoot.js +47 -0
  58. package/dist/esm/deviceDirectory.js +186 -0
  59. package/dist/esm/devicePairing.js +135 -0
  60. package/dist/esm/deviceSession.js +161 -0
  61. package/dist/esm/emailAgentContext.js +29 -0
  62. package/dist/esm/followGraph.js +27 -0
  63. package/dist/esm/identity.js +255 -0
  64. package/dist/esm/inboxPush.js +21 -0
  65. package/dist/esm/index.js +172 -0
  66. package/dist/esm/inference/accountBilling.js +331 -0
  67. package/dist/esm/inference/aliaModelRelease.js +259 -0
  68. package/dist/esm/inference/attribution.js +103 -0
  69. package/dist/esm/inference/catalogue.js +484 -0
  70. package/dist/esm/inference/entitlement.js +214 -0
  71. package/dist/esm/inference/errors.js +306 -0
  72. package/dist/esm/inference/identifiers.js +221 -0
  73. package/dist/esm/inference/inbox.js +102 -0
  74. package/dist/esm/inference/modelDocumentation.js +430 -0
  75. package/dist/esm/inference/money.js +185 -0
  76. package/dist/esm/inference/priceVersion.js +107 -0
  77. package/dist/esm/inference/providerConnection.js +452 -0
  78. package/dist/esm/inference/request.js +474 -0
  79. package/dist/esm/inference/routingPolicy.js +315 -0
  80. package/dist/esm/inference/streamEvents.js +255 -0
  81. package/dist/esm/inference/usage.js +326 -0
  82. package/dist/esm/inference/version.js +102 -0
  83. package/dist/esm/keyRecovery.js +88 -0
  84. package/dist/esm/keyRotation.js +72 -0
  85. package/dist/esm/links.js +65 -0
  86. package/dist/esm/moderationReputation.js +295 -0
  87. package/dist/esm/oauth.js +63 -0
  88. package/dist/esm/oxyRecordTypes.js +68 -0
  89. package/dist/esm/protocol.js +50 -0
  90. package/dist/esm/recommendations.js +165 -0
  91. package/dist/esm/reputation.js +293 -0
  92. package/dist/esm/sessionStatus.js +118 -0
  93. package/dist/esm/transparency.js +86 -0
  94. package/dist/esm/updates.js +249 -0
  95. package/dist/esm/userInvalidation.js +85 -0
  96. package/dist/esm/userResponse.js +240 -0
  97. package/dist/esm/username.js +283 -0
  98. package/dist/esm/webauthn.js +68 -0
  99. package/dist/types/.tsbuildinfo +1 -0
  100. package/dist/types/accountGraph.d.ts +378 -0
  101. package/dist/types/agency.d.ts +2162 -0
  102. package/dist/types/browserHub.d.ts +856 -0
  103. package/dist/types/civic.d.ts +338 -0
  104. package/dist/types/commonsSignIn.d.ts +58 -0
  105. package/dist/types/deviceBoot.d.ts +74 -0
  106. package/dist/types/deviceDirectory.d.ts +1317 -0
  107. package/dist/types/devicePairing.d.ts +130 -0
  108. package/dist/types/deviceSession.d.ts +411 -0
  109. package/dist/types/emailAgentContext.d.ts +248 -0
  110. package/dist/types/followGraph.d.ts +150 -0
  111. package/dist/types/identity.d.ts +402 -0
  112. package/dist/types/inboxPush.d.ts +30 -0
  113. package/dist/types/index.d.ts +100 -0
  114. package/dist/types/inference/accountBilling.d.ts +738 -0
  115. package/dist/types/inference/aliaModelRelease.d.ts +609 -0
  116. package/dist/types/inference/attribution.d.ts +176 -0
  117. package/dist/types/inference/catalogue.d.ts +1618 -0
  118. package/dist/types/inference/entitlement.d.ts +519 -0
  119. package/dist/types/inference/errors.d.ts +242 -0
  120. package/dist/types/inference/identifiers.d.ts +182 -0
  121. package/dist/types/inference/inbox.d.ts +374 -0
  122. package/dist/types/inference/modelDocumentation.d.ts +1603 -0
  123. package/dist/types/inference/money.d.ts +185 -0
  124. package/dist/types/inference/priceVersion.d.ts +182 -0
  125. package/dist/types/inference/providerConnection.d.ts +968 -0
  126. package/dist/types/inference/request.d.ts +2800 -0
  127. package/dist/types/inference/routingPolicy.d.ts +616 -0
  128. package/dist/types/inference/streamEvents.d.ts +950 -0
  129. package/dist/types/inference/usage.d.ts +1164 -0
  130. package/dist/types/inference/version.d.ts +102 -0
  131. package/dist/types/keyRecovery.d.ts +138 -0
  132. package/dist/types/keyRotation.d.ts +103 -0
  133. package/dist/types/links.d.ts +96 -0
  134. package/dist/types/moderationReputation.d.ts +487 -0
  135. package/dist/types/oauth.d.ts +86 -0
  136. package/dist/types/oxyRecordTypes.d.ts +62 -0
  137. package/dist/types/protocol.d.ts +86 -0
  138. package/dist/types/recommendations.d.ts +542 -0
  139. package/dist/types/reputation.d.ts +457 -0
  140. package/dist/types/sessionStatus.d.ts +231 -0
  141. package/dist/types/transparency.d.ts +392 -0
  142. package/dist/types/updates.d.ts +545 -0
  143. package/dist/types/userInvalidation.d.ts +94 -0
  144. package/dist/types/userResponse.d.ts +1706 -0
  145. package/dist/types/username.d.ts +265 -0
  146. package/dist/types/webauthn.d.ts +77 -0
  147. 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
+ });