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