@oxyhq/contracts 0.30.0 → 0.32.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.
@@ -4,7 +4,7 @@
4
4
  *
5
5
  * The credential itself is NOT here and cannot be put here. This shape carries
6
6
  * a locator (`secretRef`) into Vault/KMS/managed secret storage, a prefix short
7
- * enough to be useless, a fingerprint, and validation state. Two mechanisms
7
+ * enough to be useless, a fingerprint, and validation state. Three mechanisms
8
8
  * make that structural rather than a convention somebody must remember:
9
9
  *
10
10
  * - The object is `.strict()`. A producer that attaches `apiKey`, `secret`,
@@ -14,6 +14,10 @@
14
14
  * - `keyPrefix` is capped at 12 characters — shorter than any provider's
15
15
  * usable credential — so the one field designed to show part of a key cannot
16
16
  * be widened into showing all of it without changing the contract.
17
+ * - `secretRef` is DERIVED, not free text: the grammar is closed and the
18
+ * refinement below requires it to be this connection's own environment,
19
+ * owner account and id under one namespace. A field with no free span is a
20
+ * field a credential cannot be smuggled through.
17
21
  *
18
22
  * BYOK does not move the billing relationship: the upstream provider bills the
19
23
  * customer's own account directly, and Oxy charges only its platform fee. The
@@ -45,17 +49,61 @@ export const providerConnectionScopeSchema = z.discriminatedUnion('kind', [
45
49
  })
46
50
  .strict(),
47
51
  ]);
52
+ /**
53
+ * The managed secret stores a reference may point into.
54
+ *
55
+ * A closed set, because the scheme is the half of a locator that says who can
56
+ * resolve it: a scheme nothing in this system can dereference is not a
57
+ * reference. Restated as a SQL alternation in
58
+ * `packages/api/src/db/schema/inferenceProviderConnections.ts`, and that file's
59
+ * schema test holds the two equal.
60
+ */
61
+ const SECRET_STORE_NAMES = ['vault', 'kms', 'ssm', 'secretsmanager'];
62
+ /**
63
+ * The namespace every Oxy BYOK locator lives under, whichever store holds it.
64
+ *
65
+ * Part of the grammar rather than an implementation detail of the writer: it is
66
+ * the prefix a store-side IAM or Vault policy is scoped to, so a locator outside
67
+ * it is one Oxy's own credentials could not resolve anyway.
68
+ */
69
+ export const PROVIDER_SECRET_REFERENCE_NAMESPACE = 'oxy/inference/byok';
70
+ /**
71
+ * The two id segments, bounded exactly as the fields they must equal are:
72
+ * `oxyAccountIdSchema` caps an account id at 64 characters and
73
+ * `providerConnectionSchema.connectionId` caps a connection id at 128. A tighter
74
+ * bound here would refuse a reference to a connection the same contract accepts.
75
+ */
76
+ const ACCOUNT_SEGMENT = '[A-Za-z0-9_-]{1,64}';
77
+ const CONNECTION_SEGMENT = '[A-Za-z0-9_-]{1,128}';
48
78
  /**
49
79
  * A locator for the credential in managed secret storage — never the credential.
50
80
  *
51
- * The scheme prefix is constrained to the stores Oxy actually uses, so a
52
- * producer cannot pass a raw key through this field and have it look like a
53
- * reference; whitespace is excluded for the same reason.
81
+ * The grammar is CLOSED, and that is the whole of its value: a store from a
82
+ * four-name set, one fixed namespace, an environment from a three-name set, and
83
+ * two bounded id segments. Nothing may precede, follow or be interpolated
84
+ * between them, so there is no free-form span for credential material to occupy:
85
+ * the only places anything a producer chooses can sit are the two ids, and
86
+ * `providerConnectionSchema` below pins those to THIS connection's own owner
87
+ * account and id.
88
+ *
89
+ * ## It was not always closed, and the difference was measured
90
+ *
91
+ * The previous grammar was `<store>:<anything from a wide charset>`, under a
92
+ * comment claiming that meant "a producer cannot pass a raw key through this
93
+ * field and have it look like a reference". It did not. Splicing a credential in
94
+ * after the store name —
95
+ * `vault:sk-ant-api03-…/oxy/inference/byok/production/<account>/<id>` — satisfied
96
+ * that regex, satisfied the storage partition CHECK (which pins the END of the
97
+ * string and said nothing about its start), and parsed cleanly. Both mechanisms
98
+ * constrained the SHAPE of the locator; neither constrained what could be put in
99
+ * front of it. `packages/api`'s `providerSecretLeak.test.ts` plants exactly that
100
+ * value, and it is now refused here, by the CHECK, and by the refinement below.
54
101
  */
55
102
  export const providerSecretReferenceSchema = z
56
103
  .string()
57
- .max(512)
58
- .regex(/^(?:vault|kms|ssm|secretsmanager):[A-Za-z0-9/_.:@-]{1,480}$/, 'a secret reference is a <store>:<locator> pointer, never credential material');
104
+ .regex(new RegExp(`^(?:${SECRET_STORE_NAMES.join('|')}):${PROVIDER_SECRET_REFERENCE_NAMESPACE}/` +
105
+ `(?:${inferenceEnvironmentSchema.options.join('|')})/${ACCOUNT_SEGMENT}/${CONNECTION_SEGMENT}$`), 'a secret reference is <store>:oxy/inference/byok/<environment>/<accountId>/<connectionId>, ' +
106
+ 'never credential material');
59
107
  /** Why a credential check failed, as a closed set the Console can render. */
60
108
  export const providerConnectionValidationSchema = z
61
109
  .object({
@@ -136,4 +184,26 @@ export const providerConnectionSchema = z
136
184
  message: 'a connection whose credential failed validation cannot be active',
137
185
  });
138
186
  }
187
+ // The reference is a FUNCTION of this record, not a value a producer chooses:
188
+ // the store, then the namespace, then this connection's own environment,
189
+ // owner account and id. `providerSecretReferenceSchema` already refuses
190
+ // anything outside the grammar; this is what closes the two id segments, the
191
+ // only spans left that a producer picks the contents of.
192
+ //
193
+ // The same rule the `inference_provider_connections_secret_ref_partition`
194
+ // CHECK enforces on the row. Both exist because they cover different
195
+ // producers: the CHECK protects the TABLE from a backfill or a service that
196
+ // skipped the parse, and this protects the WIRE from a producer that never
197
+ // touches the table — the data plane echoing a connection back, or a future
198
+ // service building a DTO by hand.
199
+ const expected = SECRET_STORE_NAMES.map((store) => `${store}:${PROVIDER_SECRET_REFERENCE_NAMESPACE}/${connection.environment}/` +
200
+ `${connection.ownerAccountId}/${connection.connectionId}`);
201
+ if (!expected.includes(connection.secretRef)) {
202
+ ctx.addIssue({
203
+ code: z.ZodIssueCode.custom,
204
+ path: ['secretRef'],
205
+ message: 'a secret reference must name this connection: ' +
206
+ '<store>:oxy/inference/byok/<environment>/<ownerAccountId>/<connectionId>',
207
+ });
208
+ }
139
209
  });
@@ -99,4 +99,4 @@
99
99
  * change to, say, the catalogue reject every in-flight inference request; the
100
100
  * per-shape `schemaVersion` is what a message is validated against.
101
101
  */
102
- export const INFERENCE_CONTRACT_VERSION = '1.2.0';
102
+ export const INFERENCE_CONTRACT_VERSION = '1.3.0';
@@ -88,6 +88,8 @@ export const assetUploadTicketSchema = z.object({
88
88
  * carries the long immutable cache header (assets are content-addressed).
89
89
  */
90
90
  cacheControl: z.string(),
91
+ /** Base64 SHA-256 value required in the presigned PUT's checksum header. */
92
+ checksumSHA256: z.string(),
91
93
  });
92
94
  /**
93
95
  * `POST /updates/v1/assets/init` response. `missing` holds a presigned upload