@azx-pbc/helix-cli 0.3.0 → 0.4.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/README.md +14 -8
- package/dist/helix.js +1249 -579
- package/package.json +3 -3
package/dist/helix.js
CHANGED
|
@@ -10,6 +10,7 @@ var options = {
|
|
|
10
10
|
token: { type: "string" },
|
|
11
11
|
promote: { type: "boolean" },
|
|
12
12
|
"display-name": { type: "string" },
|
|
13
|
+
description: { type: "string" },
|
|
13
14
|
visibility: { type: "string" },
|
|
14
15
|
path: { type: "string" },
|
|
15
16
|
help: { type: "boolean" }
|
|
@@ -20,7 +21,7 @@ function parseCliArgs(argv) {
|
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
// src/client.ts
|
|
23
|
-
import { z as
|
|
24
|
+
import { z as z27 } from "zod";
|
|
24
25
|
|
|
25
26
|
// ../shared/src/visibility.ts
|
|
26
27
|
import { z } from "zod";
|
|
@@ -88,10 +89,19 @@ var DirectoryGroupsResponseSchema = z2.discriminatedUnion("available", [
|
|
|
88
89
|
// ../shared/src/app.ts
|
|
89
90
|
import { z as z3 } from "zod";
|
|
90
91
|
var SLUG_PATTERN = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
|
|
92
|
+
var APP_DESCRIPTION_MAX = 500;
|
|
91
93
|
var AppSchema = z3.object({
|
|
92
94
|
id: z3.uuid(),
|
|
93
95
|
slug: z3.string().min(1).max(63).regex(SLUG_PATTERN, "must be a lowercase DNS label (a-z, 0-9, hyphen)"),
|
|
94
96
|
displayName: z3.string().min(1).max(200),
|
|
97
|
+
/**
|
|
98
|
+
* What the app is for, in one sentence. Plain text (≤ {@link APP_DESCRIPTION_MAX}
|
|
99
|
+
* chars) — it renders as a subtitle in list rows and the detail header, so
|
|
100
|
+
* markdown would invite a wall of text in a table cell. Optional on the wire
|
|
101
|
+
* like `url`/`ownerId`: the CLI parses this schema, and every row predating
|
|
102
|
+
* the field carries none. Never `null` — an unset description omits the key.
|
|
103
|
+
*/
|
|
104
|
+
description: z3.string().max(APP_DESCRIPTION_MAX).optional(),
|
|
95
105
|
visibility: VisibilitySchema,
|
|
96
106
|
/** The version currently served; null before the first deploy. */
|
|
97
107
|
currentVersionId: z3.uuid().nullable(),
|
|
@@ -109,9 +119,9 @@ var AppSchema = z3.object({
|
|
|
109
119
|
*/
|
|
110
120
|
url: z3.url().optional(),
|
|
111
121
|
/**
|
|
112
|
-
* Who owns this app — the
|
|
113
|
-
*
|
|
114
|
-
*
|
|
122
|
+
* Who owns this app — the creator's Entra `oid` (ADR-0048). The **identity**
|
|
123
|
+
* half: this is what `scope=mine` and `ownsApp` compare against, and it is
|
|
124
|
+
* never what a screen renders.
|
|
115
125
|
*
|
|
116
126
|
* Optional for the same reason as `url`: the CLI parses this schema, and rows
|
|
117
127
|
* predating the approvals work carry no owner at all.
|
|
@@ -119,11 +129,12 @@ var AppSchema = z3.object({
|
|
|
119
129
|
ownerId: z3.string().optional(),
|
|
120
130
|
/**
|
|
121
131
|
* The owner's claims as captured at create time. The **display** half: render
|
|
122
|
-
* these, never compare them. `ownerId`
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
132
|
+
* these, never compare them. `ownerId` is the Entra `oid` (ADR-0048) — an
|
|
133
|
+
* opaque directory object id since the re-base, opaque-by-accident (an
|
|
134
|
+
* email) on rows created before it — so display travels separately. Absent
|
|
135
|
+
* for rows created before the columns existed; fall back to `ownerId` (the
|
|
136
|
+
* pre-re-base rows where it is still an email are the one case where the
|
|
137
|
+
* fallback renders something readable).
|
|
127
138
|
*/
|
|
128
139
|
ownerName: z3.string().optional(),
|
|
129
140
|
ownerEmail: z3.string().optional()
|
|
@@ -179,16 +190,379 @@ var VersionSchema = z4.object({
|
|
|
179
190
|
});
|
|
180
191
|
|
|
181
192
|
// ../shared/src/manifest.ts
|
|
193
|
+
import { z as z8 } from "zod";
|
|
194
|
+
|
|
195
|
+
// ../shared/src/providers.ts
|
|
196
|
+
import { z as z7 } from "zod";
|
|
197
|
+
|
|
198
|
+
// ../shared/src/env.ts
|
|
182
199
|
import { z as z5 } from "zod";
|
|
183
|
-
var
|
|
184
|
-
|
|
200
|
+
var ENVS = ["prod", "dev"];
|
|
201
|
+
var EnvSchema = z5.enum(ENVS);
|
|
202
|
+
|
|
203
|
+
// ../shared/src/secrets.ts
|
|
204
|
+
import { z as z6 } from "zod";
|
|
205
|
+
var FORBIDDEN_HEADER_NAMES = /* @__PURE__ */ new Set([
|
|
206
|
+
"host",
|
|
207
|
+
"content-length",
|
|
208
|
+
"transfer-encoding",
|
|
209
|
+
"connection",
|
|
210
|
+
"te",
|
|
211
|
+
"upgrade",
|
|
212
|
+
"expect",
|
|
213
|
+
"trailer"
|
|
214
|
+
]);
|
|
215
|
+
var HEADER_TOKEN = /^[A-Za-z0-9!#$%&'*+.^_`|~-]+$/;
|
|
216
|
+
var HEADER_VALUE_SAFE = /^[\t\x20-\x7e]*$/;
|
|
217
|
+
var HEADER_VALUE_WIRE = /^[\t\x20-\x7e\x80-\xff]*$/;
|
|
218
|
+
var headerName = (from) => {
|
|
219
|
+
const base = z6.string().min(1);
|
|
220
|
+
return (from === "request" ? base.max(64).regex(HEADER_TOKEN, "must be an RFC 7230 token") : base).transform((n) => n.trim().toLowerCase()).refine((n) => !FORBIDDEN_HEADER_NAMES.has(n), "reserved header name").refine((n) => !n.startsWith("x-helix-"), "reserved header prefix");
|
|
221
|
+
};
|
|
222
|
+
var headerTemplate = (from) => from === "request" ? z6.string().max(512).regex(HEADER_VALUE_SAFE) : z6.string().regex(HEADER_VALUE_WIRE);
|
|
223
|
+
var injectionRecipe = (from) => z6.discriminatedUnion("kind", [
|
|
224
|
+
/** `Authorization: Bearer <secret>` — the common case. */
|
|
225
|
+
z6.object({ kind: z6.literal("header-bearer") }),
|
|
226
|
+
/** Arbitrary header; `{}` in `template` is replaced with the secret. */
|
|
227
|
+
z6.object({
|
|
228
|
+
kind: z6.literal("header"),
|
|
229
|
+
name: headerName(from),
|
|
230
|
+
template: headerTemplate(from).default("{}")
|
|
231
|
+
}),
|
|
232
|
+
/** Query parameter `?<param>=<secret>`. */
|
|
233
|
+
z6.object({ kind: z6.literal("query"), param: z6.string().min(1) }),
|
|
234
|
+
/**
|
|
235
|
+
* HMAC over a timestamp. The signed input is the timestamp string **alone** —
|
|
236
|
+
* not the method, path, query, or body — so injection is a pure function of
|
|
237
|
+
* (private key, now) and needs no request context.
|
|
238
|
+
*
|
|
239
|
+
* The canonical form lives in the KIND NAME, deliberately. A scheme that signs
|
|
240
|
+
* method+path+body is a *sibling kind* (`hmac-request`) — a code change with
|
|
241
|
+
* tests, reviewed — never an admin-editable canonical-string template.
|
|
242
|
+
* Canonicalization is where implementations of this family go wrong, and it
|
|
243
|
+
* does not belong in a text box.
|
|
244
|
+
*
|
|
245
|
+
* SHA-256, lowercase-hex, and ISO-8601-with-milliseconds are fixed rather than
|
|
246
|
+
* configurable. Each would carry a default, and `app_secrets.injection` is a
|
|
247
|
+
* schemaless JSON column, so adding a knob later is purely additive with no
|
|
248
|
+
* migration — while fixing them now removes the weak-algorithm and
|
|
249
|
+
* unencodable-digest failure classes outright.
|
|
250
|
+
*
|
|
251
|
+
* The stored value is a JSON blob carrying both halves of the key pair
|
|
252
|
+
* ({@link HmacCredentialSchema}): regenerating the pair changes both, and a
|
|
253
|
+
* blob rotates atomically through the existing rotate route.
|
|
254
|
+
*/
|
|
255
|
+
z6.object({
|
|
256
|
+
kind: z6.literal("hmac-timestamp"),
|
|
257
|
+
/** Header carrying the timestamp that is also the entire signed input. */
|
|
258
|
+
timestampHeader: headerName(from),
|
|
259
|
+
/** Header carrying the rendered credential + signature. */
|
|
260
|
+
authHeader: headerName(from).default("authorization"),
|
|
261
|
+
/**
|
|
262
|
+
* Value written to `authHeader`. `{credential}` and `{signature}` are
|
|
263
|
+
* substituted. Named rather than the `header` kind's bare `{}` because there
|
|
264
|
+
* are two substitutions: the convention is one value ⇒ `{}`, more than one ⇒
|
|
265
|
+
* named placeholders. Positional `{}` here would let a swapped template
|
|
266
|
+
* produce a well-formed header that silently fails to authenticate.
|
|
267
|
+
*
|
|
268
|
+
* Both placeholders are required. This recipe writes exactly two headers,
|
|
269
|
+
* one of them the timestamp, so there is no configuration where the public
|
|
270
|
+
* credential id travels elsewhere — omitting `{credential}` means the
|
|
271
|
+
* upstream can never identify the key and every call 401s.
|
|
272
|
+
*/
|
|
273
|
+
template: headerTemplate(from).refine((t) => t.includes("{signature}"), "template must contain {signature}").refine((t) => t.includes("{credential}"), "template must contain {credential}")
|
|
274
|
+
}).refine(
|
|
275
|
+
(r) => r.timestampHeader !== r.authHeader,
|
|
276
|
+
"timestampHeader and authHeader must differ \u2014 the second write overwrites the first"
|
|
277
|
+
)
|
|
278
|
+
]);
|
|
279
|
+
var InjectionRecipeSchema = injectionRecipe("request");
|
|
280
|
+
var StoredInjectionRecipeSchema = injectionRecipe("stored");
|
|
281
|
+
var HeaderNameSchema = headerName("request");
|
|
282
|
+
var HmacCredentialSchema = z6.object({
|
|
283
|
+
credential: z6.string().min(1),
|
|
284
|
+
key: z6.string().min(1)
|
|
285
|
+
});
|
|
286
|
+
var SECRET_SCOPES = ["app", "global", "platform"];
|
|
287
|
+
var SecretScopeSchema = z6.enum(SECRET_SCOPES);
|
|
288
|
+
var SecretMetadataSchema = z6.object({
|
|
289
|
+
id: z6.string(),
|
|
290
|
+
name: z6.string().min(1),
|
|
291
|
+
scope: SecretScopeSchema,
|
|
292
|
+
/** Partition tier (dev-mode §6): a dev fetch injects only `dev` connection secrets. */
|
|
293
|
+
env: EnvSchema,
|
|
294
|
+
/**
|
|
295
|
+
* The **stored** parser, not the strict one — this field describes a row, and
|
|
296
|
+
* the SPA re-parses this schema on every response. Using the strict parser here
|
|
297
|
+
* would let the portal return a name the browser then refuses, moving the read
|
|
298
|
+
* failure from the server to the client, where it presents as a dead page.
|
|
299
|
+
*
|
|
300
|
+
* `null` = the stored recipe is unreadable (it names a reserved header, or is
|
|
301
|
+
* not a recipe at all). The credential still exists and is still deletable; it
|
|
302
|
+
* cannot be rotated, and egress fails its hop closed. Recipes are immutable by
|
|
303
|
+
* design, so recovery is delete-and-recreate.
|
|
304
|
+
*/
|
|
305
|
+
injection: StoredInjectionRecipeSchema.nullable(),
|
|
306
|
+
createdBy: z6.string(),
|
|
307
|
+
createdAt: z6.string(),
|
|
308
|
+
rotatedAt: z6.string().nullable().optional(),
|
|
309
|
+
lastUsedAt: z6.string().nullable().optional(),
|
|
310
|
+
boundApps: z6.array(z6.string()).default([])
|
|
311
|
+
});
|
|
312
|
+
var SecretCreateRequestSchema = z6.object({
|
|
313
|
+
name: z6.string().min(1).max(64).regex(/^[a-z0-9][a-z0-9-]*$/, "lowercase letters, digits, and hyphens"),
|
|
314
|
+
value: z6.string().min(1),
|
|
315
|
+
/** Target tier (dev-mode §6). Defaults `prod`; `dev` configures a dev-tier credential. */
|
|
316
|
+
env: EnvSchema.default("prod"),
|
|
317
|
+
injection: InjectionRecipeSchema.default({ kind: "header-bearer" })
|
|
318
|
+
});
|
|
319
|
+
var SecretRotateRequestSchema = z6.object({ value: z6.string().min(1) });
|
|
320
|
+
var SecretGrantRequestSchema = z6.object({ appSlug: z6.string().min(1) });
|
|
321
|
+
|
|
322
|
+
// ../shared/src/providers.ts
|
|
323
|
+
var PROVIDER_KINDS = ["rest-delegated"];
|
|
324
|
+
var ProviderKindSchema = z7.enum(PROVIDER_KINDS);
|
|
325
|
+
var PROVIDER_REF_PATTERN = /^[a-z0-9][a-z0-9-]*$/;
|
|
326
|
+
var PROVIDER_REF_MAX = 64;
|
|
327
|
+
var ProviderRefSchema = z7.string().min(1).max(PROVIDER_REF_MAX).regex(PROVIDER_REF_PATTERN, "lowercase letters, digits, and hyphens");
|
|
328
|
+
var TokenPlacementSchema = z7.discriminatedUnion("kind", [
|
|
329
|
+
/** `Authorization: Bearer <access token>` — the default placement. */
|
|
330
|
+
z7.strictObject({ kind: z7.literal("header-bearer") }),
|
|
331
|
+
/** The access token verbatim in the named header (no template machinery). */
|
|
332
|
+
z7.strictObject({ kind: z7.literal("header"), name: HeaderNameSchema })
|
|
333
|
+
]);
|
|
334
|
+
var ScopeTokenSchema = z7.string().min(1).max(256).regex(/^[\x21\x23-\x5b\x5d-\x7e]+$/, "must be an RFC 6749 scope token");
|
|
335
|
+
var API_ORIGIN_RULE = "an API destination is an origin \u2014 scheme://host[:port], no path, query, credentials, or fragment";
|
|
336
|
+
var BARE_ORIGIN = /^https?:\/\/[^/?#@\\]*(?:\/)?$/i;
|
|
337
|
+
var ApiOriginSchema = z7.url({ protocol: /^https?$/ }).superRefine((value, ctx) => {
|
|
338
|
+
if (!BARE_ORIGIN.test(value)) {
|
|
339
|
+
ctx.addIssue({ code: "custom", message: API_ORIGIN_RULE });
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
let url;
|
|
343
|
+
try {
|
|
344
|
+
url = new URL(value);
|
|
345
|
+
} catch {
|
|
346
|
+
ctx.addIssue({ code: "custom", message: API_ORIGIN_RULE });
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
if (url.username || url.password || url.pathname !== "/" || url.search || url.hash) {
|
|
350
|
+
ctx.addIssue({ code: "custom", message: API_ORIGIN_RULE });
|
|
351
|
+
}
|
|
352
|
+
}).transform((value) => new URL(value).origin);
|
|
353
|
+
var URL_PARSER_CONTROLS = /[\t\n\r]/g;
|
|
354
|
+
var ENDPOINT_USERINFO = /^https?:\/\/[/\\]*[^/?#\\]*@/i;
|
|
355
|
+
var OAuthEndpointSchema = z7.url({ protocol: /^https?$/ }).refine(
|
|
356
|
+
(value) => !ENDPOINT_USERINFO.test(value.replace(URL_PARSER_CONTROLS, "")),
|
|
357
|
+
"a vendor endpoint URL must not carry userinfo \u2014 client auth is never configured in the URL"
|
|
358
|
+
);
|
|
359
|
+
var MAX_API_ORIGINS = 16;
|
|
360
|
+
var MAX_REQUESTED_SCOPES = 32;
|
|
361
|
+
var noDuplicates = (values) => new Set(values).size === values.length;
|
|
362
|
+
var ApiOriginsSchema = z7.array(ApiOriginSchema).min(1, "a provider needs at least one API destination \u2014 nothing can bind to it otherwise").max(MAX_API_ORIGINS).refine(noDuplicates, "duplicate API destination");
|
|
363
|
+
var RequestedScopesSchema = z7.array(ScopeTokenSchema).max(MAX_REQUESTED_SCOPES).refine(noDuplicates, "duplicate requested scope");
|
|
364
|
+
var ProviderDisplayNameSchema = z7.string().min(1).max(200);
|
|
365
|
+
var ProviderEditableFieldsSchema = z7.strictObject({
|
|
366
|
+
displayName: ProviderDisplayNameSchema,
|
|
367
|
+
authorizeEndpoint: OAuthEndpointSchema,
|
|
368
|
+
tokenEndpoint: OAuthEndpointSchema,
|
|
369
|
+
requestedScopes: RequestedScopesSchema.default([]),
|
|
370
|
+
apiOrigins: ApiOriginsSchema,
|
|
371
|
+
tokenPlacement: TokenPlacementSchema.default({ kind: "header-bearer" })
|
|
372
|
+
});
|
|
373
|
+
var ProviderConfigSchema = ProviderEditableFieldsSchema.extend({
|
|
374
|
+
ref: ProviderRefSchema,
|
|
375
|
+
kind: ProviderKindSchema.default("rest-delegated")
|
|
376
|
+
});
|
|
377
|
+
var ProviderCreateRequestSchema = ProviderConfigSchema.extend({
|
|
378
|
+
env: EnvSchema,
|
|
379
|
+
clientId: z7.string().min(1),
|
|
380
|
+
clientSecret: z7.string().min(1)
|
|
381
|
+
});
|
|
382
|
+
var undefinedKeysMeanAbsence = (body) => {
|
|
383
|
+
if (typeof body !== "object" || body === null) return body;
|
|
384
|
+
const withoutUndefined = { ...body };
|
|
385
|
+
for (const key of Object.keys(withoutUndefined)) {
|
|
386
|
+
if (withoutUndefined[key] === void 0) delete withoutUndefined[key];
|
|
387
|
+
}
|
|
388
|
+
return withoutUndefined;
|
|
389
|
+
};
|
|
390
|
+
var OWN_PROTO_KEY_RULE = "an own __proto__ key is not a provider field \u2014 the body is refused whole";
|
|
391
|
+
var ProviderUpdateRequestSchema = z7.preprocess(
|
|
392
|
+
(body, ctx) => {
|
|
393
|
+
const normalized = undefinedKeysMeanAbsence(body);
|
|
394
|
+
if (typeof normalized === "object" && normalized !== null && Object.hasOwn(normalized, "__proto__")) {
|
|
395
|
+
ctx.addIssue({ code: "custom", message: OWN_PROTO_KEY_RULE });
|
|
396
|
+
}
|
|
397
|
+
return normalized;
|
|
398
|
+
},
|
|
399
|
+
ProviderEditableFieldsSchema.extend({
|
|
400
|
+
requestedScopes: RequestedScopesSchema,
|
|
401
|
+
tokenPlacement: TokenPlacementSchema,
|
|
402
|
+
revision: z7.int().positive(),
|
|
403
|
+
clientId: z7.string().min(1).optional(),
|
|
404
|
+
clientSecret: z7.string().min(1).optional(),
|
|
405
|
+
/**
|
|
406
|
+
* The invalidation acknowledgement ({@link CONFIRM_INVALIDATION_FIELD}):
|
|
407
|
+
* the client sets it to `true` when submitting a sensitive delta through
|
|
408
|
+
* the review panel. Absent or
|
|
409
|
+
* `false` against a sensitive delta is the 409
|
|
410
|
+
* `confirmation_required` rejection — nothing is applied; against a
|
|
411
|
+
* non-sensitive edit it is irrelevant and ignored. Not stored: it is
|
|
412
|
+
* consent for the mutation, never provider state.
|
|
413
|
+
*/
|
|
414
|
+
confirmInvalidation: z7.boolean().optional()
|
|
415
|
+
})
|
|
416
|
+
);
|
|
417
|
+
var ConnectionProviderSchema = ProviderEditableFieldsSchema.extend({
|
|
418
|
+
requestedScopes: RequestedScopesSchema,
|
|
419
|
+
tokenPlacement: TokenPlacementSchema,
|
|
420
|
+
id: z7.uuid(),
|
|
421
|
+
revision: z7.int().positive(),
|
|
422
|
+
ref: ProviderRefSchema,
|
|
423
|
+
kind: ProviderKindSchema,
|
|
424
|
+
env: EnvSchema,
|
|
425
|
+
clientIdMaterial: z7.string().min(1),
|
|
426
|
+
clientSecretMaterial: z7.string().min(1),
|
|
427
|
+
createdAt: z7.iso.datetime(),
|
|
428
|
+
updatedAt: z7.iso.datetime()
|
|
429
|
+
});
|
|
430
|
+
var ProviderMetadataSchema = ConnectionProviderSchema.omit({
|
|
431
|
+
clientIdMaterial: true,
|
|
432
|
+
clientSecretMaterial: true
|
|
433
|
+
});
|
|
434
|
+
var ProviderListResponseSchema = z7.strictObject({
|
|
435
|
+
callbackUrl: z7.url(),
|
|
436
|
+
providers: z7.array(ProviderMetadataSchema)
|
|
437
|
+
});
|
|
438
|
+
var ProviderExportDocumentSchema = z7.strictObject({
|
|
439
|
+
version: z7.literal(1),
|
|
440
|
+
provider: ProviderConfigSchema
|
|
441
|
+
});
|
|
442
|
+
var CatalogueProviderSchema = z7.strictObject({
|
|
443
|
+
ref: ProviderRefSchema,
|
|
444
|
+
kind: ProviderKindSchema,
|
|
445
|
+
displayName: z7.string().min(1).max(200),
|
|
446
|
+
apiOrigins: ApiOriginsSchema,
|
|
447
|
+
env: EnvSchema
|
|
448
|
+
});
|
|
449
|
+
var SENSITIVE_PROVIDER_FIELDS = [
|
|
450
|
+
"clientId",
|
|
451
|
+
"authorizeEndpoint",
|
|
452
|
+
"tokenEndpoint",
|
|
453
|
+
"requestedScopes",
|
|
454
|
+
"apiOrigins",
|
|
455
|
+
"tokenPlacement"
|
|
456
|
+
];
|
|
457
|
+
var CONFIRM_INVALIDATION_FIELD = "confirmInvalidation";
|
|
458
|
+
var SensitiveProviderFieldSchema = z7.enum(SENSITIVE_PROVIDER_FIELDS);
|
|
459
|
+
var PROVIDER_DIFF_FIELDS = [
|
|
460
|
+
"displayName",
|
|
461
|
+
"authorizeEndpoint",
|
|
462
|
+
"tokenEndpoint",
|
|
463
|
+
"requestedScopes",
|
|
464
|
+
"apiOrigins",
|
|
465
|
+
"tokenPlacement"
|
|
466
|
+
];
|
|
467
|
+
var ProviderDiffFieldSchema = z7.enum(PROVIDER_DIFF_FIELDS);
|
|
468
|
+
var ProviderImportPreviewRequestSchema = z7.strictObject({
|
|
469
|
+
document: ProviderExportDocumentSchema,
|
|
470
|
+
mode: z7.enum(["create", "update"]).optional(),
|
|
471
|
+
env: EnvSchema.optional(),
|
|
472
|
+
targetId: z7.uuid().optional()
|
|
473
|
+
});
|
|
474
|
+
var ProviderImportCollisionSchema = z7.strictObject({
|
|
475
|
+
providerId: z7.uuid(),
|
|
476
|
+
ref: ProviderRefSchema,
|
|
477
|
+
env: EnvSchema
|
|
478
|
+
});
|
|
479
|
+
var ProviderImportPreviewTargetSchema = z7.strictObject({
|
|
480
|
+
providerId: z7.uuid(),
|
|
481
|
+
ref: ProviderRefSchema,
|
|
482
|
+
env: EnvSchema,
|
|
483
|
+
displayName: z7.string().min(1).max(200),
|
|
484
|
+
revision: z7.int().positive()
|
|
485
|
+
});
|
|
486
|
+
var ProviderImportPreviewDiffEntrySchema = z7.strictObject({
|
|
487
|
+
field: ProviderDiffFieldSchema,
|
|
488
|
+
current: z7.union([z7.string(), z7.array(z7.string()), TokenPlacementSchema]),
|
|
489
|
+
imported: z7.union([z7.string(), z7.array(z7.string()), TokenPlacementSchema])
|
|
490
|
+
});
|
|
491
|
+
var ProviderImportPreviewResponseSchema = z7.discriminatedUnion("mode", [
|
|
492
|
+
z7.strictObject({ mode: z7.literal(null), provider: ProviderConfigSchema }),
|
|
493
|
+
z7.strictObject({
|
|
494
|
+
mode: z7.literal("create"),
|
|
495
|
+
env: EnvSchema,
|
|
496
|
+
provider: ProviderConfigSchema,
|
|
497
|
+
collision: ProviderImportCollisionSchema.nullable()
|
|
498
|
+
}),
|
|
499
|
+
z7.strictObject({
|
|
500
|
+
mode: z7.literal("update"),
|
|
501
|
+
target: ProviderImportPreviewTargetSchema,
|
|
502
|
+
diff: z7.array(ProviderImportPreviewDiffEntrySchema),
|
|
503
|
+
sensitiveFields: z7.array(SensitiveProviderFieldSchema)
|
|
504
|
+
})
|
|
505
|
+
]);
|
|
506
|
+
var ProviderImportCreateRequestSchema = z7.strictObject({
|
|
507
|
+
mode: z7.literal("create"),
|
|
508
|
+
document: ProviderExportDocumentSchema,
|
|
509
|
+
env: EnvSchema,
|
|
510
|
+
clientId: z7.string().min(1),
|
|
511
|
+
clientSecret: z7.string().min(1)
|
|
512
|
+
});
|
|
513
|
+
var ProviderImportUpdateRequestSchema = z7.strictObject({
|
|
514
|
+
mode: z7.literal("update"),
|
|
515
|
+
document: ProviderExportDocumentSchema,
|
|
516
|
+
targetId: z7.uuid(),
|
|
517
|
+
revision: z7.int().positive(),
|
|
518
|
+
clientId: z7.string().min(1).optional(),
|
|
519
|
+
clientSecret: z7.string().min(1).optional(),
|
|
520
|
+
[CONFIRM_INVALIDATION_FIELD]: z7.boolean().optional()
|
|
521
|
+
});
|
|
522
|
+
var ProviderImportRequestSchema = z7.discriminatedUnion("mode", [
|
|
523
|
+
ProviderImportCreateRequestSchema,
|
|
524
|
+
ProviderImportUpdateRequestSchema
|
|
525
|
+
]);
|
|
526
|
+
var ProviderImportResponseSchema = z7.strictObject({
|
|
527
|
+
outcome: z7.enum(["created", "updated"]),
|
|
528
|
+
provider: ProviderMetadataSchema
|
|
529
|
+
});
|
|
530
|
+
var BoundAppSchema = z7.strictObject({
|
|
531
|
+
id: z7.uuid(),
|
|
532
|
+
slug: z7.string().max(63).regex(SLUG_PATTERN, "must be a lowercase DNS label (a-z, 0-9, hyphen)"),
|
|
533
|
+
displayName: z7.string().min(1).max(200)
|
|
534
|
+
});
|
|
535
|
+
var ProviderImpactSchema = z7.strictObject({
|
|
536
|
+
providerId: z7.uuid(),
|
|
537
|
+
ref: ProviderRefSchema,
|
|
538
|
+
env: EnvSchema,
|
|
539
|
+
displayName: z7.string().min(1).max(200),
|
|
540
|
+
revision: z7.int().positive(),
|
|
541
|
+
boundApps: z7.array(BoundAppSchema),
|
|
542
|
+
connections: z7.number().int().nonnegative(),
|
|
543
|
+
pendingAttempts: z7.number().int().nonnegative()
|
|
544
|
+
});
|
|
545
|
+
var ConfirmationRequiredDetailsSchema = z7.strictObject({
|
|
546
|
+
impact: ProviderImpactSchema,
|
|
547
|
+
sensitiveFields: z7.array(SensitiveProviderFieldSchema).optional()
|
|
548
|
+
});
|
|
549
|
+
var ProviderDeleteRequestSchema = z7.strictObject({
|
|
550
|
+
[CONFIRM_INVALIDATION_FIELD]: z7.boolean().optional()
|
|
551
|
+
});
|
|
552
|
+
var ProviderDeleteResponseSchema = z7.strictObject({
|
|
553
|
+
outcome: z7.enum(["deleted", "already_removed"])
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
// ../shared/src/manifest.ts
|
|
557
|
+
var LlmCapabilitySchema = z8.object({
|
|
558
|
+
models: z8.array(z8.string()).default([]),
|
|
185
559
|
/**
|
|
186
560
|
* Per-app daily LLM spend cap in USD; unset ⇒ unbounded. Denominated in
|
|
187
561
|
* dollars (not tokens) so the cap means the same thing across models — the
|
|
188
562
|
* edge prices each call via `@azx-pbc/shared` pricing and enforces a daily +
|
|
189
563
|
* rolling-hour burst window off the frozen `costMicroUsd` ledger column.
|
|
190
564
|
*/
|
|
191
|
-
dollarsPerDay:
|
|
565
|
+
dollarsPerDay: z8.number().positive().optional()
|
|
192
566
|
});
|
|
193
567
|
var KEY_MAX_CHARS = 256;
|
|
194
568
|
var PRINTABLE_ASCII = /^[\x20-\x7e]*$/;
|
|
@@ -199,18 +573,18 @@ function isValidDataKey(key) {
|
|
|
199
573
|
key === key.trim();
|
|
200
574
|
}
|
|
201
575
|
var DATA_KEY_RULE = "must be 1\u2013256 printable ASCII characters (0x20\u20130x7E) with no leading or trailing space";
|
|
202
|
-
var DataKeySchema =
|
|
203
|
-
var DataCapabilitySchema =
|
|
204
|
-
user:
|
|
205
|
-
collections:
|
|
206
|
-
sharedRead:
|
|
207
|
-
sharedWrite:
|
|
576
|
+
var DataKeySchema = z8.string().refine(isValidDataKey, { message: DATA_KEY_RULE });
|
|
577
|
+
var DataCapabilitySchema = z8.object({
|
|
578
|
+
user: z8.boolean().default(false),
|
|
579
|
+
collections: z8.array(DataKeySchema).default([]),
|
|
580
|
+
sharedRead: z8.array(DataKeySchema).default([]),
|
|
581
|
+
sharedWrite: z8.array(DataKeySchema).default([]),
|
|
208
582
|
/** ADR-0042: authorize every key starting with one of these prefixes (read). */
|
|
209
|
-
sharedReadPrefixes:
|
|
583
|
+
sharedReadPrefixes: z8.array(DataKeySchema).default([]),
|
|
210
584
|
/** ADR-0042: authorize every key starting with one of these prefixes (write). */
|
|
211
|
-
sharedWritePrefixes:
|
|
212
|
-
writesPerDay:
|
|
213
|
-
bytesPerDay:
|
|
585
|
+
sharedWritePrefixes: z8.array(DataKeySchema).default([]),
|
|
586
|
+
writesPerDay: z8.int().positive().optional(),
|
|
587
|
+
bytesPerDay: z8.int().positive().optional()
|
|
214
588
|
}).superRefine((d, ctx) => {
|
|
215
589
|
if (d.sharedWritePrefixes.length > 0 && d.writesPerDay === void 0) {
|
|
216
590
|
ctx.addIssue({
|
|
@@ -220,17 +594,37 @@ var DataCapabilitySchema = z5.object({
|
|
|
220
594
|
});
|
|
221
595
|
}
|
|
222
596
|
});
|
|
223
|
-
var FetchConnectionSchema =
|
|
224
|
-
origin:
|
|
225
|
-
connection:
|
|
597
|
+
var FetchConnectionSchema = z8.strictObject({
|
|
598
|
+
origin: z8.url(),
|
|
599
|
+
connection: z8.string().min(1).optional(),
|
|
600
|
+
provider: ProviderRefSchema.optional(),
|
|
601
|
+
required: z8.boolean().optional()
|
|
602
|
+
}).superRefine((c, ctx) => {
|
|
603
|
+
if (c.connection !== void 0 && c.provider !== void 0) {
|
|
604
|
+
ctx.addIssue({
|
|
605
|
+
code: "custom",
|
|
606
|
+
path: ["provider"],
|
|
607
|
+
message: "an origin binds at most one credential source \u2014 declare a stored secret (connection) or an OAuth provider (provider), not both"
|
|
608
|
+
});
|
|
609
|
+
}
|
|
226
610
|
});
|
|
227
|
-
var FetchCapabilitySchema =
|
|
228
|
-
/**
|
|
229
|
-
|
|
611
|
+
var FetchCapabilitySchema = z8.object({
|
|
612
|
+
/**
|
|
613
|
+
* Legacy view of the fetch-shim grant. The grant is declared first-class as
|
|
614
|
+
* {@link ShimCapabilitySchema} (`capabilities.shim.fetch`); this boolean is
|
|
615
|
+
* the pre-`capabilities.shim` spelling, still accepted on input and kept synchronized
|
|
616
|
+
* with the canonical block on every parse (see {@link CapabilitiesSchema}) —
|
|
617
|
+
* new manifests should declare `capabilities.shim`, not this field.
|
|
618
|
+
*/
|
|
619
|
+
shim: z8.boolean().default(false),
|
|
230
620
|
/** Origins reached through the proxy (mode `proxy`); direct stays in `externalOrigins`. */
|
|
231
|
-
origins:
|
|
621
|
+
origins: z8.array(FetchConnectionSchema).default([]),
|
|
232
622
|
/** Per-app daily proxied-request budget; unset ⇒ unbounded (fetch-proxy §7). */
|
|
233
|
-
requestsPerDay:
|
|
623
|
+
requestsPerDay: z8.int().positive().optional()
|
|
624
|
+
});
|
|
625
|
+
var ShimCapabilitySchema = z8.object({
|
|
626
|
+
fetch: z8.boolean().default(false),
|
|
627
|
+
connect: z8.boolean().default(false)
|
|
234
628
|
});
|
|
235
629
|
var SCOPE_SEGMENT = /^[A-Za-z0-9\-._~]+$/;
|
|
236
630
|
function isValidServiceWorkerScope(scope) {
|
|
@@ -243,36 +637,67 @@ function isValidServiceWorkerScope(scope) {
|
|
|
243
637
|
}
|
|
244
638
|
return !first.startsWith("_");
|
|
245
639
|
}
|
|
246
|
-
var OfflineCapabilitySchema =
|
|
640
|
+
var OfflineCapabilitySchema = z8.object({
|
|
247
641
|
/**
|
|
248
642
|
* URL path prefix the worker controls, e.g. `/app/`. Leading and trailing
|
|
249
643
|
* slash required; never root, never a `_`-prefixed platform namespace.
|
|
250
644
|
*/
|
|
251
|
-
scope:
|
|
645
|
+
scope: z8.string().refine(isValidServiceWorkerScope, {
|
|
252
646
|
message: "scope must be a non-root path prefix with a leading and trailing slash, and must not start with a reserved `_` segment (e.g. `/app/`)"
|
|
253
647
|
})
|
|
254
648
|
});
|
|
255
|
-
var CapabilitiesSchema =
|
|
649
|
+
var CapabilitiesSchema = z8.object({
|
|
256
650
|
llm: LlmCapabilitySchema.optional(),
|
|
257
651
|
data: DataCapabilitySchema.optional(),
|
|
258
652
|
/** Platform-registered MCP servers this app may reach (§6.1, exposed as REST). */
|
|
259
|
-
mcp:
|
|
653
|
+
mcp: z8.array(z8.string()).default([]),
|
|
260
654
|
/** Extra CSP `connect-src` origins for **direct** browser calls (§4.4). */
|
|
261
|
-
externalOrigins:
|
|
655
|
+
externalOrigins: z8.array(z8.url()).default([]),
|
|
262
656
|
/** Governed outbound HTTP via the fetch-proxy / egress plane (in build, M4.5). */
|
|
263
657
|
fetch: FetchCapabilitySchema.optional(),
|
|
264
658
|
/** Platform-owned, scope-confined service worker for offline cold boot (ADR-0035). */
|
|
265
|
-
offline: OfflineCapabilitySchema.optional()
|
|
659
|
+
offline: OfflineCapabilitySchema.optional(),
|
|
660
|
+
/** Injected platform helpers — the first-class shim grant. */
|
|
661
|
+
shim: ShimCapabilitySchema.optional()
|
|
662
|
+
}).transform((caps) => {
|
|
663
|
+
const legacyShim = caps.fetch?.shim ?? false;
|
|
664
|
+
const fetchOn = legacyShim || (caps.shim?.fetch ?? false);
|
|
665
|
+
const connectOn = caps.shim?.connect ?? false;
|
|
666
|
+
const fetch2 = caps.fetch ?? (fetchOn ? { shim: fetchOn, origins: [] } : void 0);
|
|
667
|
+
return {
|
|
668
|
+
...caps,
|
|
669
|
+
fetch: fetch2 === void 0 ? void 0 : { ...fetch2, shim: fetchOn },
|
|
670
|
+
...fetchOn || connectOn ? { shim: { fetch: fetchOn, connect: connectOn } } : {}
|
|
671
|
+
};
|
|
266
672
|
});
|
|
267
|
-
var
|
|
673
|
+
var ProviderBindingStatusSchema = z8.strictObject({
|
|
674
|
+
/** The manifest origin as declared (matches `FetchConnection.origin`). */
|
|
675
|
+
origin: z8.string(),
|
|
676
|
+
/** The bound provider reference. */
|
|
677
|
+
ref: ProviderRefSchema,
|
|
678
|
+
/**
|
|
679
|
+
* Whether an approval that granted this binding still matches the provider
|
|
680
|
+
* row's current identity — the {@link isProviderBindingEffective} rule
|
|
681
|
+
* (the one definition; the portal never re-derives the comparison). `false`
|
|
682
|
+
* means the provider was deleted or a sensitive edit stale-dated the stamp.
|
|
683
|
+
*/
|
|
684
|
+
effective: z8.boolean()
|
|
685
|
+
});
|
|
686
|
+
var AppManifestSchema = z8.object({
|
|
268
687
|
/** App slug; matches `App.slug`. */
|
|
269
|
-
app:
|
|
688
|
+
app: z8.string().min(1),
|
|
270
689
|
visibility: VisibilitySchema,
|
|
271
|
-
capabilities: CapabilitiesSchema.default({ mcp: [], externalOrigins: [] })
|
|
690
|
+
capabilities: CapabilitiesSchema.default({ mcp: [], externalOrigins: [] }),
|
|
691
|
+
/**
|
|
692
|
+
* Effectiveness of every provider-bound origin in `capabilities`, computed
|
|
693
|
+
* server-side on the read (never stored). Absent when the manifest declares
|
|
694
|
+
* no provider binding — every payload without one keeps its exact shape.
|
|
695
|
+
*/
|
|
696
|
+
providerBindings: z8.array(ProviderBindingStatusSchema).optional()
|
|
272
697
|
});
|
|
273
698
|
|
|
274
699
|
// ../shared/src/approval.ts
|
|
275
|
-
import { z as
|
|
700
|
+
import { z as z9 } from "zod";
|
|
276
701
|
|
|
277
702
|
// ../shared/src/pricing.ts
|
|
278
703
|
var MODEL_PRICING = {
|
|
@@ -320,6 +745,9 @@ var MODEL_PRICING = {
|
|
|
320
745
|
"claude-opus-4-7": { inputPerMTok: 5, outputPerMTok: 25, provider: "anthropic" },
|
|
321
746
|
"claude-opus-4-6": { inputPerMTok: 5, outputPerMTok: 25, provider: "anthropic" },
|
|
322
747
|
"claude-sonnet-4-6": { inputPerMTok: 3, outputPerMTok: 15, provider: "anthropic" },
|
|
748
|
+
// Near-term churn: haiku-4-5's only two Foundry versions both stop serving
|
|
749
|
+
// 2026-10-19 (measured eastus2 2026-09-16). Not a cut — first-party serving
|
|
750
|
+
// is unaffected — but expect the Foundry deployments to need a successor.
|
|
323
751
|
"claude-haiku-4-5": {
|
|
324
752
|
inputPerMTok: 1,
|
|
325
753
|
outputPerMTok: 5,
|
|
@@ -393,13 +821,14 @@ var MODEL_PRICING = {
|
|
|
393
821
|
},
|
|
394
822
|
// Previous generations, kept curated so a deployed app's manifest keeps working.
|
|
395
823
|
// Rates re-verified 2026-09-14 and unchanged.
|
|
824
|
+
//
|
|
825
|
+
// `gpt-4o-mini`, `o3` and `o4-mini` were deliberately cut (2026-09-16) rather
|
|
826
|
+
// than retained on that principle: Azure AI Foundry's RP refuses any model
|
|
827
|
+
// whose *default* version is `Deprecating` (`ServiceModelDeprecating`), so
|
|
828
|
+
// they could never ride the one-flag Foundry path, and the current generation
|
|
829
|
+
// covers their price points first-party. A manifest still naming one gets the
|
|
830
|
+
// edge's `model_not_allowed` (no price configured) — the cut is the break.
|
|
396
831
|
"gpt-4o": { inputPerMTok: 2.5, outputPerMTok: 10, provider: "openai", structuredOutputs: true },
|
|
397
|
-
"gpt-4o-mini": {
|
|
398
|
-
inputPerMTok: 0.15,
|
|
399
|
-
outputPerMTok: 0.6,
|
|
400
|
-
provider: "openai",
|
|
401
|
-
structuredOutputs: true
|
|
402
|
-
},
|
|
403
832
|
"gpt-4.1": { inputPerMTok: 2, outputPerMTok: 8, provider: "openai", structuredOutputs: true },
|
|
404
833
|
"gpt-4.1-mini": {
|
|
405
834
|
inputPerMTok: 0.4,
|
|
@@ -412,28 +841,12 @@ var MODEL_PRICING = {
|
|
|
412
841
|
outputPerMTok: 0.4,
|
|
413
842
|
provider: "openai",
|
|
414
843
|
structuredOutputs: true
|
|
415
|
-
},
|
|
416
|
-
o3: {
|
|
417
|
-
inputPerMTok: 2,
|
|
418
|
-
outputPerMTok: 8,
|
|
419
|
-
provider: "openai",
|
|
420
|
-
reasoning: true,
|
|
421
|
-
minCompletionTokens: 25e3,
|
|
422
|
-
structuredOutputs: true
|
|
423
|
-
},
|
|
424
|
-
"o4-mini": {
|
|
425
|
-
inputPerMTok: 1.1,
|
|
426
|
-
outputPerMTok: 4.4,
|
|
427
|
-
provider: "openai",
|
|
428
|
-
reasoning: true,
|
|
429
|
-
minCompletionTokens: 25e3,
|
|
430
|
-
structuredOutputs: true
|
|
431
844
|
}
|
|
432
845
|
};
|
|
433
846
|
|
|
434
847
|
// ../shared/src/approval.ts
|
|
435
848
|
var RISK_LEVELS = ["low", "med", "high"];
|
|
436
|
-
var RiskSchema =
|
|
849
|
+
var RiskSchema = z9.enum(RISK_LEVELS);
|
|
437
850
|
var APPROVAL_STATUSES = [
|
|
438
851
|
"pending",
|
|
439
852
|
"approved",
|
|
@@ -441,151 +854,195 @@ var APPROVAL_STATUSES = [
|
|
|
441
854
|
"withdrawn",
|
|
442
855
|
"needs_changes"
|
|
443
856
|
];
|
|
444
|
-
var ApprovalStatusSchema =
|
|
445
|
-
var
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
857
|
+
var ApprovalStatusSchema = z9.enum(APPROVAL_STATUSES);
|
|
858
|
+
var ProviderStampSchema = z9.strictObject({
|
|
859
|
+
/** The env-unique reference the manifest binds (the catalogue/manifest key). */
|
|
860
|
+
ref: ProviderRefSchema,
|
|
861
|
+
/** Which partition the stamped row lives in — a ref may exist in both. */
|
|
862
|
+
env: EnvSchema,
|
|
863
|
+
/** The surrogate row id — delete+recreate mints a new one, dangling old stamps. */
|
|
864
|
+
providerId: z9.uuid(),
|
|
865
|
+
/** The row's revision at filing; a sensitive edit advances it. */
|
|
866
|
+
revision: z9.int().positive()
|
|
867
|
+
});
|
|
868
|
+
var DeltaSchema = z9.object({
|
|
869
|
+
path: z9.string(),
|
|
870
|
+
from: z9.union([z9.string(), z9.number(), z9.boolean()]).optional(),
|
|
871
|
+
to: z9.union([z9.string(), z9.number(), z9.boolean()]).optional(),
|
|
872
|
+
/**
|
|
873
|
+
* The provider stamps recorded **at filing** for a provider-bound origin
|
|
874
|
+
* delta — one per env-partitioned `connection_providers` row that
|
|
875
|
+
* existed under the bound ref when the request was filed (an
|
|
876
|
+
* approval records the provider's `ref` + `providerId` + `revision`, and
|
|
877
|
+
* env partitioning makes a ref's full identity the set of its rows).
|
|
878
|
+
*
|
|
879
|
+
* This is the payload half of the apply-time conflict: approving a request
|
|
880
|
+
* whose stamps no longer match the provider's current identity/revision
|
|
881
|
+
* approves nothing — an old pending approval cannot approve access to a
|
|
882
|
+
* newer configuration. Consumers read the rule itself from
|
|
883
|
+
* {@link isProviderBindingEffective}; none re-derives the comparison.
|
|
884
|
+
*/
|
|
885
|
+
providerStamps: z9.array(ProviderStampSchema).optional(),
|
|
886
|
+
/**
|
|
887
|
+
* The requesting app's visibility **at filing**: `true` when the app was
|
|
888
|
+
* `public`, `false` (stamped as not-public) otherwise. The
|
|
889
|
+
* warning — a public app's anonymous visitors can never connect a vendor
|
|
890
|
+
* account — is data on the request, so the queue card needs no extra fetch.
|
|
891
|
+
* The flag never auto-rejects.
|
|
892
|
+
*/
|
|
893
|
+
publicApp: z9.boolean().optional(),
|
|
894
|
+
/**
|
|
895
|
+
* This delta's own severity as classified at filing — what the request-level
|
|
896
|
+
* `risk` is the max of. Absent on requests filed before the field existed
|
|
897
|
+
* (deltas are stored as JSON, so nothing backfills them); renderers fall
|
|
898
|
+
* back to the aggregate rather than guessing.
|
|
899
|
+
*/
|
|
900
|
+
risk: RiskSchema.optional()
|
|
449
901
|
});
|
|
450
|
-
var PriorDecisionsSchema =
|
|
902
|
+
var PriorDecisionsSchema = z9.object({
|
|
451
903
|
/** Prior *decided* (non-pending) requests on this app — the history count. */
|
|
452
|
-
total:
|
|
904
|
+
total: z9.number().int().nonnegative(),
|
|
453
905
|
/** Prior denied requests touching an overlapping policy area (quiet signal). */
|
|
454
|
-
deniedSameArea:
|
|
906
|
+
deniedSameArea: z9.number().int().nonnegative(),
|
|
455
907
|
/** Prior denied requests sharing an exact delta path (loud signal; ⊆ area). */
|
|
456
|
-
deniedSameGrant:
|
|
908
|
+
deniedSameGrant: z9.number().int().nonnegative(),
|
|
457
909
|
/** The most recent decided request (any status), for the "last decision" line. */
|
|
458
|
-
last:
|
|
910
|
+
last: z9.object({
|
|
459
911
|
status: ApprovalStatusSchema,
|
|
460
|
-
note:
|
|
461
|
-
decidedBy:
|
|
462
|
-
decidedAt:
|
|
912
|
+
note: z9.string().nullable(),
|
|
913
|
+
decidedBy: z9.string().nullable(),
|
|
914
|
+
decidedAt: z9.string()
|
|
463
915
|
}).optional()
|
|
464
916
|
});
|
|
465
|
-
var ApprovalRequestSchema =
|
|
466
|
-
id:
|
|
467
|
-
appId:
|
|
917
|
+
var ApprovalRequestSchema = z9.object({
|
|
918
|
+
id: z9.string(),
|
|
919
|
+
appId: z9.string(),
|
|
468
920
|
/** Joined for the admin queue / banner; not a column. */
|
|
469
|
-
appSlug:
|
|
470
|
-
appDisplayName:
|
|
921
|
+
appSlug: z9.string().optional(),
|
|
922
|
+
appDisplayName: z9.string().optional(),
|
|
471
923
|
status: ApprovalStatusSchema,
|
|
472
924
|
risk: RiskSchema,
|
|
473
|
-
deltas:
|
|
474
|
-
baseSnapshot:
|
|
475
|
-
requestedBy:
|
|
476
|
-
reason:
|
|
477
|
-
decidedBy:
|
|
478
|
-
decisionNote:
|
|
479
|
-
createdAt:
|
|
480
|
-
decidedAt:
|
|
925
|
+
deltas: z9.array(DeltaSchema),
|
|
926
|
+
baseSnapshot: z9.unknown(),
|
|
927
|
+
requestedBy: z9.string(),
|
|
928
|
+
reason: z9.string().nullable().optional(),
|
|
929
|
+
decidedBy: z9.string().nullable().optional(),
|
|
930
|
+
decisionNote: z9.string().nullable().optional(),
|
|
931
|
+
createdAt: z9.string(),
|
|
932
|
+
decidedAt: z9.string().nullable().optional(),
|
|
481
933
|
/** Sibling-decision context for the admin queue (issue #26); read-side only. */
|
|
482
934
|
priorDecisions: PriorDecisionsSchema.optional()
|
|
483
935
|
});
|
|
484
|
-
var ManifestUpdateResultSchema =
|
|
936
|
+
var ManifestUpdateResultSchema = z9.object({
|
|
485
937
|
manifest: AppManifestSchema,
|
|
486
|
-
applied:
|
|
487
|
-
pending:
|
|
938
|
+
applied: z9.array(DeltaSchema),
|
|
939
|
+
pending: z9.string().nullable()
|
|
488
940
|
});
|
|
489
|
-
var VisibilityUpdateResultSchema =
|
|
941
|
+
var VisibilityUpdateResultSchema = z9.object({
|
|
490
942
|
app: AppSchema,
|
|
491
|
-
applied:
|
|
492
|
-
pending:
|
|
943
|
+
applied: z9.array(DeltaSchema),
|
|
944
|
+
pending: z9.string().nullable()
|
|
493
945
|
});
|
|
494
|
-
var ApprovalDecisionRequestSchema =
|
|
495
|
-
note:
|
|
946
|
+
var ApprovalDecisionRequestSchema = z9.object({
|
|
947
|
+
note: z9.string().max(2e3).optional()
|
|
496
948
|
});
|
|
497
949
|
var CURATED_LLM_MODELS = Object.keys(MODEL_PRICING);
|
|
498
950
|
|
|
499
951
|
// ../shared/src/health.ts
|
|
500
|
-
import { z as
|
|
501
|
-
var HealthStateSchema =
|
|
502
|
-
var HealthCheckSchema =
|
|
952
|
+
import { z as z10 } from "zod";
|
|
953
|
+
var HealthStateSchema = z10.enum(["ok", "degraded", "error"]);
|
|
954
|
+
var HealthCheckSchema = z10.object({
|
|
503
955
|
/** Stable identifier an alert rule can key on, e.g. `registry-projection`. */
|
|
504
|
-
name:
|
|
956
|
+
name: z10.string(),
|
|
505
957
|
status: HealthStateSchema,
|
|
506
958
|
/** Operator-facing one-liner: what is wrong, and for how long. */
|
|
507
|
-
detail:
|
|
959
|
+
detail: z10.string().optional(),
|
|
508
960
|
/** Wall-clock instant this check last succeeded. Report-only — never the
|
|
509
961
|
* basis of a staleness decision (see the monotonic note in the edge's
|
|
510
962
|
* registry projection). */
|
|
511
|
-
lastSuccessAt:
|
|
963
|
+
lastSuccessAt: z10.iso.datetime().optional(),
|
|
512
964
|
/** Numeric facts a dashboard or a log-based metric can key on. */
|
|
513
|
-
metrics:
|
|
965
|
+
metrics: z10.record(z10.string(), z10.number()).optional()
|
|
514
966
|
});
|
|
515
|
-
var HealthStatusSchema =
|
|
967
|
+
var HealthStatusSchema = z10.object({
|
|
516
968
|
/** The roll-up: the worst state across `checks` (see `worstHealthState`). */
|
|
517
969
|
status: HealthStateSchema,
|
|
518
|
-
service:
|
|
970
|
+
service: z10.string(),
|
|
519
971
|
/** Process uptime in seconds. */
|
|
520
|
-
uptime:
|
|
972
|
+
uptime: z10.number().nonnegative(),
|
|
521
973
|
/** Absent means the service reports liveness only (portal and egress today). */
|
|
522
|
-
checks:
|
|
974
|
+
checks: z10.array(HealthCheckSchema).optional()
|
|
523
975
|
});
|
|
524
976
|
|
|
525
977
|
// ../shared/src/api.ts
|
|
526
|
-
import { z as
|
|
527
|
-
var CreateAppRequestSchema =
|
|
978
|
+
import { z as z11 } from "zod";
|
|
979
|
+
var CreateAppRequestSchema = z11.object({
|
|
528
980
|
slug: AppSchema.shape.slug,
|
|
529
981
|
displayName: AppSchema.shape.displayName,
|
|
982
|
+
/** Optional one-line plain-text summary; rendered as a subtitle in the portal. */
|
|
983
|
+
description: AppSchema.shape.description,
|
|
530
984
|
visibility: WritableVisibilitySchema.default({ mode: "internal" }),
|
|
531
985
|
/** Optional per-app capability grant set at create time (architecture §6.3). */
|
|
532
986
|
capabilities: CapabilitiesSchema.optional()
|
|
533
987
|
});
|
|
534
|
-
var
|
|
988
|
+
var UpdateAppRequestSchema = z11.object({
|
|
989
|
+
description: z11.string().max(APP_DESCRIPTION_MAX).nullable().optional()
|
|
990
|
+
});
|
|
991
|
+
var SetManifestRequestSchema = z11.object({
|
|
535
992
|
capabilities: CapabilitiesSchema,
|
|
536
993
|
/** Justification carried onto an approval request for any elevated deltas. */
|
|
537
|
-
reason:
|
|
994
|
+
reason: z11.string().max(2e3).optional()
|
|
538
995
|
});
|
|
539
|
-
var SetVisibilityRequestSchema =
|
|
996
|
+
var SetVisibilityRequestSchema = z11.object({
|
|
540
997
|
visibility: WritableVisibilitySchema,
|
|
541
998
|
/** Justification carried onto the approval request when going public. */
|
|
542
|
-
reason:
|
|
999
|
+
reason: z11.string().max(2e3).optional()
|
|
543
1000
|
});
|
|
544
1001
|
var MIN_PASSWORD_LENGTH = 12;
|
|
545
|
-
var SetPasswordRequestSchema =
|
|
546
|
-
password:
|
|
1002
|
+
var SetPasswordRequestSchema = z11.object({
|
|
1003
|
+
password: z11.string().min(MIN_PASSWORD_LENGTH).optional()
|
|
547
1004
|
});
|
|
548
|
-
var PasswordCredentialResponseSchema =
|
|
1005
|
+
var PasswordCredentialResponseSchema = z11.object({
|
|
549
1006
|
/** The shared passphrase, in cleartext, for the owner to copy/share. */
|
|
550
|
-
password:
|
|
1007
|
+
password: z11.string().min(1),
|
|
551
1008
|
/** The app's public URL, prebuilt so the UI can offer a one-click copy. */
|
|
552
|
-
url:
|
|
1009
|
+
url: z11.url(),
|
|
553
1010
|
/** When the current password was set (ISO 8601). */
|
|
554
|
-
setAt:
|
|
555
|
-
});
|
|
556
|
-
var CspWarningSchema =
|
|
557
|
-
file:
|
|
558
|
-
origin:
|
|
559
|
-
hint:
|
|
560
|
-
});
|
|
561
|
-
var OriginGrantRequestSchema =
|
|
562
|
-
origin:
|
|
563
|
-
reason:
|
|
564
|
-
});
|
|
565
|
-
var CspViolationSchema =
|
|
566
|
-
appId:
|
|
567
|
-
appSlug:
|
|
568
|
-
directive:
|
|
569
|
-
blockedUri:
|
|
570
|
-
count:
|
|
571
|
-
lastSeen:
|
|
1011
|
+
setAt: z11.string()
|
|
1012
|
+
});
|
|
1013
|
+
var CspWarningSchema = z11.object({
|
|
1014
|
+
file: z11.string(),
|
|
1015
|
+
origin: z11.string(),
|
|
1016
|
+
hint: z11.string()
|
|
1017
|
+
});
|
|
1018
|
+
var OriginGrantRequestSchema = z11.object({
|
|
1019
|
+
origin: z11.url(),
|
|
1020
|
+
reason: z11.string().max(2e3).optional()
|
|
1021
|
+
});
|
|
1022
|
+
var CspViolationSchema = z11.object({
|
|
1023
|
+
appId: z11.string(),
|
|
1024
|
+
appSlug: z11.string().nullable(),
|
|
1025
|
+
directive: z11.string(),
|
|
1026
|
+
blockedUri: z11.string(),
|
|
1027
|
+
count: z11.number().int(),
|
|
1028
|
+
lastSeen: z11.string(),
|
|
572
1029
|
/**
|
|
573
1030
|
* The blocked origin is already permitted by the app's *current* manifest, so
|
|
574
1031
|
* this historical report is no longer actionable. Derived at read time
|
|
575
1032
|
* (directive-aware — only `connect-src`/`img-src` are widened by an
|
|
576
1033
|
* `externalOrigins` grant); the row itself is never deleted.
|
|
577
1034
|
*/
|
|
578
|
-
resolved:
|
|
1035
|
+
resolved: z11.boolean()
|
|
579
1036
|
});
|
|
580
|
-
var CspViolationsPageSchema =
|
|
581
|
-
violations:
|
|
1037
|
+
var CspViolationsPageSchema = z11.object({
|
|
1038
|
+
violations: z11.array(CspViolationSchema)
|
|
582
1039
|
});
|
|
583
|
-
var UploadVersionResponseSchema =
|
|
1040
|
+
var UploadVersionResponseSchema = z11.object({
|
|
584
1041
|
version: VersionSchema,
|
|
585
|
-
warnings:
|
|
1042
|
+
warnings: z11.array(CspWarningSchema)
|
|
586
1043
|
});
|
|
587
|
-
var RollbackRequestSchema =
|
|
588
|
-
toNumber:
|
|
1044
|
+
var RollbackRequestSchema = z11.object({
|
|
1045
|
+
toNumber: z11.int().positive().optional()
|
|
589
1046
|
});
|
|
590
1047
|
var API_ERROR_CODES = [
|
|
591
1048
|
"validation_failed",
|
|
@@ -596,6 +1053,14 @@ var API_ERROR_CODES = [
|
|
|
596
1053
|
/** Authenticated but not allowed — reserved for v1 RBAC. */
|
|
597
1054
|
"forbidden",
|
|
598
1055
|
"conflict",
|
|
1056
|
+
/**
|
|
1057
|
+
* Portal: a sensitive provider edit (or a deletion) arrived without its
|
|
1058
|
+
* invalidation acknowledgement. HTTP 409 like `conflict`, but
|
|
1059
|
+
* distinguishable: the SPA's
|
|
1060
|
+
* review panel opens on this code, and `details` carries the impact payload
|
|
1061
|
+
* (`ConfirmationRequiredDetailsSchema` in providers.ts) it renders.
|
|
1062
|
+
*/
|
|
1063
|
+
"confirmation_required",
|
|
599
1064
|
/** Gateway: requested model is not in the app's manifest allowlist (§6.3). */
|
|
600
1065
|
"model_not_allowed",
|
|
601
1066
|
/** Gateway: the app's daily token budget is exhausted (§6.1). */
|
|
@@ -619,22 +1084,28 @@ var API_ERROR_CODES = [
|
|
|
619
1084
|
"capability_unavailable",
|
|
620
1085
|
"internal"
|
|
621
1086
|
];
|
|
622
|
-
var ApiErrorCodeSchema =
|
|
623
|
-
var ApiErrorSchema =
|
|
624
|
-
error:
|
|
1087
|
+
var ApiErrorCodeSchema = z11.enum(API_ERROR_CODES);
|
|
1088
|
+
var ApiErrorSchema = z11.object({
|
|
1089
|
+
error: z11.object({
|
|
625
1090
|
code: ApiErrorCodeSchema,
|
|
626
|
-
message:
|
|
627
|
-
details:
|
|
1091
|
+
message: z11.string(),
|
|
1092
|
+
details: z11.unknown().optional()
|
|
628
1093
|
})
|
|
629
1094
|
});
|
|
630
1095
|
|
|
631
1096
|
// ../shared/src/auth.ts
|
|
632
|
-
import { z as
|
|
633
|
-
var MeResponseSchema =
|
|
634
|
-
user:
|
|
635
|
-
/**
|
|
636
|
-
|
|
637
|
-
|
|
1097
|
+
import { z as z12 } from "zod";
|
|
1098
|
+
var MeResponseSchema = z12.object({
|
|
1099
|
+
user: z12.object({
|
|
1100
|
+
/**
|
|
1101
|
+
* The principal id — Entra's `oid` claim (the directory object id), stable
|
|
1102
|
+
* for the life of the user object and identical across every app
|
|
1103
|
+
* registration in the tenant (ADR-0048): safe to key app data on, and the
|
|
1104
|
+
* user scope partitions on exactly this value. Opaque by design — never
|
|
1105
|
+
* render it; `displayName` and `email` are the half fit to display.
|
|
1106
|
+
*/
|
|
1107
|
+
id: z12.string(),
|
|
1108
|
+
displayName: z12.string(),
|
|
638
1109
|
/**
|
|
639
1110
|
* The captured address claim, or `null` when there isn't one — **nullable,
|
|
640
1111
|
* not optional, and the difference is the contract**. A missing key would
|
|
@@ -649,15 +1120,29 @@ var MeResponseSchema = z9.object({
|
|
|
649
1120
|
* to join on. Entra addresses are reassignable and B2B guests carry their
|
|
650
1121
|
* home-tenant address.
|
|
651
1122
|
*/
|
|
652
|
-
email:
|
|
1123
|
+
email: z12.string().nullable()
|
|
653
1124
|
})
|
|
654
1125
|
});
|
|
655
|
-
var PortalMeResponseSchema =
|
|
656
|
-
|
|
1126
|
+
var PortalMeResponseSchema = z12.object({
|
|
1127
|
+
/**
|
|
1128
|
+
* The canonical principal id — Entra's `oid` claim from the access token
|
|
1129
|
+
* (ADR-0048). This is what `App.ownerId` stores and what ownership compares;
|
|
1130
|
+
* `sub` below is the display/audit half, never a join key.
|
|
1131
|
+
*
|
|
1132
|
+
* **Optional, not required**, for the same cross-version reason as the
|
|
1133
|
+
* defaulted fields below: this schema parses `PortalClient.me()` in the
|
|
1134
|
+
* *published* CLI, which routinely talks to portals older than itself, and
|
|
1135
|
+
* portals are customer-deployed and version independently (ADR-0028). An
|
|
1136
|
+
* older portal has no `oid` to echo — pre-ADR-0048 it has no such concept —
|
|
1137
|
+
* and nothing in the CLI keys on it. A current portal always sends it (the
|
|
1138
|
+
* dev-token actor gets a fixed synthetic value).
|
|
1139
|
+
*/
|
|
1140
|
+
oid: z12.string().optional(),
|
|
1141
|
+
sub: z12.string(),
|
|
657
1142
|
/** How the actor was established: `oidc` or `dev-token`. */
|
|
658
|
-
via:
|
|
659
|
-
name:
|
|
660
|
-
email:
|
|
1143
|
+
via: z12.string(),
|
|
1144
|
+
name: z12.string().optional(),
|
|
1145
|
+
email: z12.string().optional(),
|
|
661
1146
|
/**
|
|
662
1147
|
* Whether the actor holds the `platform-admin` role. Computed server-side from
|
|
663
1148
|
* the actor's group/role claim — the raw ids never cross to the browser. Drives
|
|
@@ -669,7 +1154,7 @@ var PortalMeResponseSchema = z9.object({
|
|
|
669
1154
|
* polarity is the cautious one — a portal that never sent the field is not
|
|
670
1155
|
* telling us the caller is an admin.
|
|
671
1156
|
*/
|
|
672
|
-
isAdmin:
|
|
1157
|
+
isAdmin: z12.boolean().default(false),
|
|
673
1158
|
/**
|
|
674
1159
|
* Whether this caller may run a tenant-wide group search (ADR-0040 decision
|
|
675
1160
|
* 11). Computed server-side from the deployment's `PORTAL_DIRECTORY_SEARCH`
|
|
@@ -695,7 +1180,7 @@ var PortalMeResponseSchema = z9.object({
|
|
|
695
1180
|
* describes the portal actually being talked to. Defaulting to `false` would
|
|
696
1181
|
* instead tell a client that search is unavailable where it works.
|
|
697
1182
|
*/
|
|
698
|
-
canSearchDirectory:
|
|
1183
|
+
canSearchDirectory: z12.boolean().default(true),
|
|
699
1184
|
/**
|
|
700
1185
|
* Why search was refused, when it was — **present only when
|
|
701
1186
|
* {@link canSearchDirectory} is false**, and never `everyone`, which by
|
|
@@ -713,15 +1198,15 @@ var PortalMeResponseSchema = z9.object({
|
|
|
713
1198
|
* infer from the 403 they would get by asking. The admin group id still never
|
|
714
1199
|
* crosses.
|
|
715
1200
|
*/
|
|
716
|
-
searchRestriction:
|
|
1201
|
+
searchRestriction: z12.enum(["admins", "none"]).optional()
|
|
717
1202
|
});
|
|
718
|
-
var AuthConfigResponseSchema =
|
|
719
|
-
issuer:
|
|
720
|
-
cliClientId:
|
|
1203
|
+
var AuthConfigResponseSchema = z12.object({
|
|
1204
|
+
issuer: z12.url(),
|
|
1205
|
+
cliClientId: z12.string().min(1),
|
|
721
1206
|
/** Public client the portal SPA uses for code+PKCE in the browser. */
|
|
722
|
-
webClientId:
|
|
1207
|
+
webClientId: z12.string().min(1).optional(),
|
|
723
1208
|
/** Expected token audience — part of what the CLI binds cached tokens to. */
|
|
724
|
-
audience:
|
|
1209
|
+
audience: z12.string().min(1).optional(),
|
|
725
1210
|
/**
|
|
726
1211
|
* Whether this deployment permits `public` (anonymous) apps. Drives the SPA's
|
|
727
1212
|
* visibility UI — it hides the public option when false. Absent = forbidden
|
|
@@ -729,9 +1214,9 @@ var AuthConfigResponseSchema = z9.object({
|
|
|
729
1214
|
* opt-in). Server-side enforcement is independent of this hint (portal routes
|
|
730
1215
|
* + edge serving).
|
|
731
1216
|
*/
|
|
732
|
-
allowPublicApps:
|
|
1217
|
+
allowPublicApps: z12.boolean().optional(),
|
|
733
1218
|
/** Whether this deployment permits `password` (shared-passphrase) apps. Same shape as {@link allowPublicApps}. */
|
|
734
|
-
allowPasswordApps:
|
|
1219
|
+
allowPasswordApps: z12.boolean().optional()
|
|
735
1220
|
});
|
|
736
1221
|
function portalApiScope(audience) {
|
|
737
1222
|
if (!audience || audience.startsWith("urn:")) return null;
|
|
@@ -740,27 +1225,27 @@ function portalApiScope(audience) {
|
|
|
740
1225
|
}
|
|
741
1226
|
|
|
742
1227
|
// ../shared/src/deployment.ts
|
|
743
|
-
import { z as
|
|
744
|
-
var DeploymentConfigResponseSchema =
|
|
1228
|
+
import { z as z13 } from "zod";
|
|
1229
|
+
var DeploymentConfigResponseSchema = z13.object({
|
|
745
1230
|
/**
|
|
746
1231
|
* Scheme + host + (non-default) port where apps are served, as reachable by
|
|
747
1232
|
* this browser (architecture §4.1). The app slug is prepended as a subdomain:
|
|
748
1233
|
* `https://<slug>.<host>`.
|
|
749
1234
|
*/
|
|
750
|
-
appPublicBase:
|
|
1235
|
+
appPublicBase: z13.url(),
|
|
751
1236
|
/**
|
|
752
1237
|
* Base of the opt-in dev gateway (dev-mode design §3), absent when it is not
|
|
753
1238
|
* deployed. Unlike {@link appPublicBase} the slug goes in the *path*, not the
|
|
754
1239
|
* host. Absent ⇒ dev mode is unavailable here, so the UI says so instead of
|
|
755
1240
|
* printing an unreachable host.
|
|
756
1241
|
*/
|
|
757
|
-
devApiBase:
|
|
1242
|
+
devApiBase: z13.url().optional(),
|
|
758
1243
|
/**
|
|
759
1244
|
* Month-to-date platform spend ceiling (USD) for the admin budget watch line.
|
|
760
1245
|
* Display-only — the gateway is the choke point, so the rollup is exact, but
|
|
761
1246
|
* nothing enforces this. Absent ⇒ no ceiling shown.
|
|
762
1247
|
*/
|
|
763
|
-
platformMonthlyUsdCap:
|
|
1248
|
+
platformMonthlyUsdCap: z13.number().positive().optional(),
|
|
764
1249
|
/**
|
|
765
1250
|
* Deploy bundle size caps in megabytes (`DEPLOY_MAX_FILE_MB` /
|
|
766
1251
|
* `DEPLOY_MAX_BUNDLE_MB` on the portal) — `deployMaxFileMb` is per file,
|
|
@@ -770,13 +1255,13 @@ var DeploymentConfigResponseSchema = z10.object({
|
|
|
770
1255
|
* substitute a default, because printing the wrong cap to an agent sends it
|
|
771
1256
|
* chasing a rejection it can't see the cause of.
|
|
772
1257
|
*/
|
|
773
|
-
deployMaxFileMb:
|
|
774
|
-
deployMaxBundleMb:
|
|
1258
|
+
deployMaxFileMb: z13.number().positive().optional(),
|
|
1259
|
+
deployMaxBundleMb: z13.number().positive().optional()
|
|
775
1260
|
});
|
|
776
1261
|
|
|
777
1262
|
// ../shared/src/catalogue.ts
|
|
778
|
-
import { z as
|
|
779
|
-
var CapabilityCatalogueSchema =
|
|
1263
|
+
import { z as z14 } from "zod";
|
|
1264
|
+
var CapabilityCatalogueSchema = z14.object({
|
|
780
1265
|
/**
|
|
781
1266
|
* Visibility modes an app may declare here. `internal` is always present
|
|
782
1267
|
* (SSO, any directory principal — the baseline). `group` is present only when
|
|
@@ -785,24 +1270,28 @@ var CapabilityCatalogueSchema = z11.object({
|
|
|
785
1270
|
* present only when the operator policy permits them
|
|
786
1271
|
* (`PORTAL_ALLOW_*_APPS`). A mode absent from this list is not requestable.
|
|
787
1272
|
*/
|
|
788
|
-
visibility:
|
|
789
|
-
modes:
|
|
1273
|
+
visibility: z14.object({
|
|
1274
|
+
modes: z14.array(VisibilityModeSchema).min(1)
|
|
790
1275
|
}),
|
|
791
1276
|
/**
|
|
792
|
-
* LLM: the **servable** model ids, not merely the curated catalog.
|
|
793
|
-
*
|
|
794
|
-
* `
|
|
795
|
-
*
|
|
796
|
-
*
|
|
797
|
-
*
|
|
1277
|
+
* LLM: the **servable** model ids, not merely the curated catalog. The set is
|
|
1278
|
+
* the operator's declaration when one exists (`PORTAL_LLM_MODEL_ALLOWLIST`,
|
|
1279
|
+
* minus `PORTAL_LLM_MODEL_BLOCKLIST` — ADR-0047; on a `deployFoundry` install
|
|
1280
|
+
* the Bicep derives it from `foundryModels`, so it reads exactly what was
|
|
1281
|
+
* deployed), and otherwise the ADR-0036 v1 heuristic: a model is servable
|
|
1282
|
+
* when its upstream family has a seeded `platform` secret, so a
|
|
1283
|
+
* curated-but-unseeded model (e.g. a `gpt-*` model whose `openai` platform
|
|
1284
|
+
* key was never added) is omitted and an agent that reads this list never
|
|
1285
|
+
* designs an app that 502s at call time. Every id in this list is also
|
|
1286
|
+
* curated, so requesting one is baseline (auto-approve).
|
|
798
1287
|
*
|
|
799
1288
|
* `baselineDollarsPerDay` is the spend cap at/under which an LLM grant
|
|
800
1289
|
* auto-approves; above it queues. From `@azx-pbc/shared/approval.ts`, so the
|
|
801
1290
|
* catalogue and the classifier cannot drift.
|
|
802
1291
|
*/
|
|
803
|
-
llm:
|
|
804
|
-
models:
|
|
805
|
-
baselineDollarsPerDay:
|
|
1292
|
+
llm: z14.object({
|
|
1293
|
+
models: z14.array(z14.string().min(1)),
|
|
1294
|
+
baselineDollarsPerDay: z14.number().positive()
|
|
806
1295
|
}),
|
|
807
1296
|
/**
|
|
808
1297
|
* App-data: whether the three scopes are provisioned here, and the daily
|
|
@@ -811,10 +1300,10 @@ var CapabilityCatalogueSchema = z11.object({
|
|
|
811
1300
|
* a future deployment-level toggle, present before it is needed. The
|
|
812
1301
|
* baselines come from `@azx-pbc/shared/approval.ts`.
|
|
813
1302
|
*/
|
|
814
|
-
data:
|
|
815
|
-
provisioned:
|
|
816
|
-
baselineWritesPerDay:
|
|
817
|
-
baselineBytesPerDay:
|
|
1303
|
+
data: z14.object({
|
|
1304
|
+
provisioned: z14.boolean(),
|
|
1305
|
+
baselineWritesPerDay: z14.number().positive(),
|
|
1306
|
+
baselineBytesPerDay: z14.number().positive()
|
|
818
1307
|
}),
|
|
819
1308
|
/**
|
|
820
1309
|
* The fetch-proxy (`/_api/fetch`) and direct-CSP surfaces.
|
|
@@ -826,42 +1315,51 @@ var CapabilityCatalogueSchema = z11.object({
|
|
|
826
1315
|
* values, and without the origin each one fronts: a global secret has no
|
|
827
1316
|
* stored origin (the origin is declared per-app in each manifest), so listing
|
|
828
1317
|
* one here would be a guess. An agent learns "this connection exists and is
|
|
829
|
-
* referenceable" and supplies the origin itself. `
|
|
830
|
-
*
|
|
1318
|
+
* referenceable" and supplies the origin itself. `providers` is the configured
|
|
1319
|
+
* OAuth connection providers an app may bind from
|
|
1320
|
+
* `capabilities.fetch.origins[].provider` — metadata-only
|
|
1321
|
+
* {@link CatalogueProviderSchema} entries (ref, kind, display name, API
|
|
1322
|
+
* destinations, env), never a credential field, served to every
|
|
1323
|
+
* authenticated principal exactly like `connections`. An origin bound to a
|
|
1324
|
+
* provider must be one of that provider's `apiOrigins` in the applicable
|
|
1325
|
+
* environment, so an app author picks both from this list.
|
|
1326
|
+
* `baselineRequestsPerDay` is the proxied-request budget at/under which a
|
|
1327
|
+
* fetch grant auto-approves.
|
|
831
1328
|
*/
|
|
832
|
-
fetch:
|
|
833
|
-
externalOriginsPermitted:
|
|
834
|
-
connections:
|
|
835
|
-
|
|
1329
|
+
fetch: z14.object({
|
|
1330
|
+
externalOriginsPermitted: z14.boolean(),
|
|
1331
|
+
connections: z14.array(z14.object({ name: z14.string().min(1) })),
|
|
1332
|
+
providers: z14.array(CatalogueProviderSchema),
|
|
1333
|
+
baselineRequestsPerDay: z14.number().positive()
|
|
836
1334
|
}),
|
|
837
1335
|
/**
|
|
838
1336
|
* MCP is carried in the manifest but **not enforced** — no transport exists
|
|
839
1337
|
* today. Surfaced as `false` so an agent does not assume a working `mcp`
|
|
840
1338
|
* grant; declaring any server queues for approval regardless.
|
|
841
1339
|
*/
|
|
842
|
-
mcp:
|
|
843
|
-
enforced:
|
|
1340
|
+
mcp: z14.object({
|
|
1341
|
+
enforced: z14.boolean()
|
|
844
1342
|
}),
|
|
845
1343
|
/**
|
|
846
1344
|
* Offline (ADR-0035): whether the platform service-worker grant is available,
|
|
847
1345
|
* and the rule a `scope` must satisfy (not the domain root, not a `/_…`
|
|
848
1346
|
* reserved path). Any grant or rescope queues for approval.
|
|
849
1347
|
*/
|
|
850
|
-
offline:
|
|
851
|
-
available:
|
|
1348
|
+
offline: z14.object({
|
|
1349
|
+
available: z14.boolean(),
|
|
852
1350
|
/** Human-facing summary of the scope rule; not machine-parsed. */
|
|
853
|
-
scopeRule:
|
|
1351
|
+
scopeRule: z14.string().min(1)
|
|
854
1352
|
}),
|
|
855
1353
|
/** Deploy bundle size caps in megabytes, as enforced on upload. */
|
|
856
|
-
deploy:
|
|
857
|
-
maxFileMb:
|
|
858
|
-
maxBundleMb:
|
|
1354
|
+
deploy: z14.object({
|
|
1355
|
+
maxFileMb: z14.number().positive(),
|
|
1356
|
+
maxBundleMb: z14.number().positive()
|
|
859
1357
|
}),
|
|
860
1358
|
/**
|
|
861
1359
|
* The opt-in dev-gateway base, absent when it is not deployed here (same
|
|
862
1360
|
* convention as `DeploymentConfigResponseSchema.devApiBase`).
|
|
863
1361
|
*/
|
|
864
|
-
devApiBase:
|
|
1362
|
+
devApiBase: z14.url().optional(),
|
|
865
1363
|
/**
|
|
866
1364
|
* The approval half of the catalogue (decision 3). The four baselines mirror
|
|
867
1365
|
* `@azx-pbc/shared/approval.ts` exactly, and `elevationTriggers` enumerates
|
|
@@ -869,15 +1367,15 @@ var CapabilityCatalogueSchema = z11.object({
|
|
|
869
1367
|
* thing an agent reads to design an app that ships immediately. A trigger
|
|
870
1368
|
* absent from this list never queues.
|
|
871
1369
|
*/
|
|
872
|
-
approval:
|
|
873
|
-
baselines:
|
|
874
|
-
dollarsPerDay:
|
|
875
|
-
writesPerDay:
|
|
876
|
-
bytesPerDay:
|
|
877
|
-
fetchRequestsPerDay:
|
|
1370
|
+
approval: z14.object({
|
|
1371
|
+
baselines: z14.object({
|
|
1372
|
+
dollarsPerDay: z14.number().positive(),
|
|
1373
|
+
writesPerDay: z14.number().positive(),
|
|
1374
|
+
bytesPerDay: z14.number().positive(),
|
|
1375
|
+
fetchRequestsPerDay: z14.number().positive()
|
|
878
1376
|
}),
|
|
879
|
-
elevationTriggers:
|
|
880
|
-
|
|
1377
|
+
elevationTriggers: z14.array(
|
|
1378
|
+
z14.enum([
|
|
881
1379
|
/** Any `externalOrigins` entry — widens CSP for a direct browser call. */
|
|
882
1380
|
"externalOrigins",
|
|
883
1381
|
/** Any `fetch.origins` entry — a proxied outbound origin. */
|
|
@@ -908,16 +1406,16 @@ var SCRYPT_PARAMS = {
|
|
|
908
1406
|
};
|
|
909
1407
|
|
|
910
1408
|
// ../shared/src/llm.ts
|
|
911
|
-
import { z as
|
|
912
|
-
var LlmMessageSchema =
|
|
913
|
-
role:
|
|
914
|
-
content:
|
|
1409
|
+
import { z as z15 } from "zod";
|
|
1410
|
+
var LlmMessageSchema = z15.object({
|
|
1411
|
+
role: z15.enum(["user", "assistant"]),
|
|
1412
|
+
content: z15.string().min(1)
|
|
915
1413
|
});
|
|
916
|
-
var LlmUsageSchema =
|
|
917
|
-
inputTokens:
|
|
918
|
-
outputTokens:
|
|
919
|
-
cacheReadInputTokens:
|
|
920
|
-
cacheCreationInputTokens:
|
|
1414
|
+
var LlmUsageSchema = z15.object({
|
|
1415
|
+
inputTokens: z15.int().nonnegative(),
|
|
1416
|
+
outputTokens: z15.int().nonnegative(),
|
|
1417
|
+
cacheReadInputTokens: z15.int().nonnegative().default(0),
|
|
1418
|
+
cacheCreationInputTokens: z15.int().nonnegative().default(0)
|
|
921
1419
|
});
|
|
922
1420
|
var MAX_SCHEMA_CHARS = 32768;
|
|
923
1421
|
var MAX_SCHEMA_DEPTH = 12;
|
|
@@ -940,11 +1438,11 @@ function withinSchemaBudget(schema) {
|
|
|
940
1438
|
}
|
|
941
1439
|
return serialized.length <= MAX_SCHEMA_CHARS && schemaDepth(schema) <= MAX_SCHEMA_DEPTH;
|
|
942
1440
|
}
|
|
943
|
-
var LlmResponseFormatSchema =
|
|
944
|
-
type:
|
|
1441
|
+
var LlmResponseFormatSchema = z15.object({
|
|
1442
|
+
type: z15.literal("json_schema"),
|
|
945
1443
|
/** Schema name. OpenAI requires one; defaulted at translation when omitted. */
|
|
946
|
-
name:
|
|
947
|
-
schema:
|
|
1444
|
+
name: z15.string().regex(/^[A-Za-z0-9_-]{1,64}$/, "must be 1-64 chars of [A-Za-z0-9_-]").optional(),
|
|
1445
|
+
schema: z15.record(z15.string(), z15.unknown()).refine((s) => s.type === "object", 'schema root must be `{"type":"object"}`').refine(
|
|
948
1446
|
withinSchemaBudget,
|
|
949
1447
|
`schema must serialize to <= ${MAX_SCHEMA_CHARS} characters and nest <= ${MAX_SCHEMA_DEPTH} levels`
|
|
950
1448
|
)
|
|
@@ -954,18 +1452,18 @@ var LlmResponseFormatSchema = z12.object({
|
|
|
954
1452
|
// on `gpt-*` but not `claude-*`, which is exactly the provider leak this seam
|
|
955
1453
|
// exists to prevent. The platform always enforces; see ADR-0034.
|
|
956
1454
|
});
|
|
957
|
-
var LlmChatRequestSchema =
|
|
958
|
-
model:
|
|
959
|
-
messages:
|
|
1455
|
+
var LlmChatRequestSchema = z15.object({
|
|
1456
|
+
model: z15.string().min(1),
|
|
1457
|
+
messages: z15.array(LlmMessageSchema).min(1),
|
|
960
1458
|
/** Optional system prompt; maps to the vendor's system channel. */
|
|
961
|
-
system:
|
|
962
|
-
maxTokens:
|
|
1459
|
+
system: z15.string().optional(),
|
|
1460
|
+
maxTokens: z15.int().positive().max(128e3).optional(),
|
|
963
1461
|
/** Sampling temperature; forwarded as-is (the vendor validates its own range). */
|
|
964
|
-
temperature:
|
|
1462
|
+
temperature: z15.number().optional(),
|
|
965
1463
|
/** Nucleus sampling; forwarded as-is. */
|
|
966
|
-
topP:
|
|
1464
|
+
topP: z15.number().optional(),
|
|
967
1465
|
/** Stop sequences; normalized to a list at the boundary. */
|
|
968
|
-
stop:
|
|
1466
|
+
stop: z15.array(z15.string()).optional(),
|
|
969
1467
|
/**
|
|
970
1468
|
* Constrain the completion to a JSON schema (ADR-0034). Refused up front when
|
|
971
1469
|
* the requested model can't enforce it (`ModelPrice.structuredOutputs`). The
|
|
@@ -974,70 +1472,70 @@ var LlmChatRequestSchema = z12.object({
|
|
|
974
1472
|
*/
|
|
975
1473
|
responseFormat: LlmResponseFormatSchema.optional(),
|
|
976
1474
|
/** SSE streaming (default) vs a single JSON body. */
|
|
977
|
-
stream:
|
|
1475
|
+
stream: z15.boolean().default(true)
|
|
978
1476
|
});
|
|
979
|
-
var LlmChatResponseSchema =
|
|
980
|
-
model:
|
|
981
|
-
content:
|
|
982
|
-
stopReason:
|
|
1477
|
+
var LlmChatResponseSchema = z15.object({
|
|
1478
|
+
model: z15.string(),
|
|
1479
|
+
content: z15.string(),
|
|
1480
|
+
stopReason: z15.string(),
|
|
983
1481
|
usage: LlmUsageSchema
|
|
984
1482
|
});
|
|
985
|
-
var LlmStreamDeltaSchema =
|
|
986
|
-
var LlmStreamDoneSchema =
|
|
987
|
-
stopReason:
|
|
1483
|
+
var LlmStreamDeltaSchema = z15.object({ text: z15.string() });
|
|
1484
|
+
var LlmStreamDoneSchema = z15.object({
|
|
1485
|
+
stopReason: z15.string(),
|
|
988
1486
|
usage: LlmUsageSchema
|
|
989
1487
|
});
|
|
990
|
-
var LlmStreamErrorSchema =
|
|
991
|
-
code:
|
|
992
|
-
message:
|
|
1488
|
+
var LlmStreamErrorSchema = z15.object({
|
|
1489
|
+
code: z15.string(),
|
|
1490
|
+
message: z15.string()
|
|
993
1491
|
});
|
|
994
1492
|
|
|
995
1493
|
// ../shared/src/llmOpenai.ts
|
|
996
|
-
import { z as
|
|
997
|
-
var OpenAiRoleSchema =
|
|
998
|
-
var OpenAiMessageSchema =
|
|
1494
|
+
import { z as z16 } from "zod";
|
|
1495
|
+
var OpenAiRoleSchema = z16.enum(["system", "developer", "user", "assistant", "tool"]);
|
|
1496
|
+
var OpenAiMessageSchema = z16.object({
|
|
999
1497
|
role: OpenAiRoleSchema,
|
|
1000
|
-
content:
|
|
1498
|
+
content: z16.union([z16.string(), z16.array(z16.unknown())]).nullish(),
|
|
1001
1499
|
/** Present only on assistant tool-call turns — parsed to reject, never honored. */
|
|
1002
|
-
tool_calls:
|
|
1003
|
-
tool_call_id:
|
|
1004
|
-
name:
|
|
1005
|
-
});
|
|
1006
|
-
var OpenAiStreamOptionsSchema =
|
|
1007
|
-
include_usage:
|
|
1008
|
-
});
|
|
1009
|
-
var OpenAiResponseFormatSchema =
|
|
1010
|
-
|
|
1011
|
-
type:
|
|
1012
|
-
json_schema:
|
|
1013
|
-
name:
|
|
1014
|
-
schema:
|
|
1015
|
-
strict:
|
|
1500
|
+
tool_calls: z16.array(z16.unknown()).optional(),
|
|
1501
|
+
tool_call_id: z16.string().optional(),
|
|
1502
|
+
name: z16.string().optional()
|
|
1503
|
+
});
|
|
1504
|
+
var OpenAiStreamOptionsSchema = z16.object({
|
|
1505
|
+
include_usage: z16.boolean().optional()
|
|
1506
|
+
});
|
|
1507
|
+
var OpenAiResponseFormatSchema = z16.discriminatedUnion("type", [
|
|
1508
|
+
z16.object({
|
|
1509
|
+
type: z16.literal("json_schema"),
|
|
1510
|
+
json_schema: z16.object({
|
|
1511
|
+
name: z16.string().optional(),
|
|
1512
|
+
schema: z16.record(z16.string(), z16.unknown()),
|
|
1513
|
+
strict: z16.boolean().nullish(),
|
|
1016
1514
|
/**
|
|
1017
1515
|
* Declared so the codec can reject it rather than zod silently stripping it.
|
|
1018
1516
|
* It is honorable on the OpenAI path but has no Anthropic equivalent, so
|
|
1019
1517
|
* forwarding it would make behaviour depend on the backing vendor — the same
|
|
1020
1518
|
* provider leak that got `json_object` rejected (ADR-0034).
|
|
1021
1519
|
*/
|
|
1022
|
-
description:
|
|
1520
|
+
description: z16.string().optional()
|
|
1023
1521
|
})
|
|
1024
1522
|
}),
|
|
1025
|
-
|
|
1026
|
-
|
|
1523
|
+
z16.object({ type: z16.literal("json_object") }),
|
|
1524
|
+
z16.object({ type: z16.literal("text") })
|
|
1027
1525
|
]);
|
|
1028
|
-
var OpenAiChatCompletionRequestSchema =
|
|
1029
|
-
model:
|
|
1030
|
-
messages:
|
|
1031
|
-
max_tokens:
|
|
1032
|
-
max_completion_tokens:
|
|
1033
|
-
temperature:
|
|
1034
|
-
top_p:
|
|
1035
|
-
stop:
|
|
1036
|
-
stream:
|
|
1526
|
+
var OpenAiChatCompletionRequestSchema = z16.object({
|
|
1527
|
+
model: z16.string().min(1),
|
|
1528
|
+
messages: z16.array(OpenAiMessageSchema).min(1),
|
|
1529
|
+
max_tokens: z16.int().positive().max(128e3).optional(),
|
|
1530
|
+
max_completion_tokens: z16.int().positive().max(128e3).optional(),
|
|
1531
|
+
temperature: z16.number().optional(),
|
|
1532
|
+
top_p: z16.number().optional(),
|
|
1533
|
+
stop: z16.union([z16.string(), z16.array(z16.string())]).optional(),
|
|
1534
|
+
stream: z16.boolean().optional(),
|
|
1037
1535
|
stream_options: OpenAiStreamOptionsSchema.optional(),
|
|
1038
1536
|
/** Declared so the codec can reject tool use in v1 (not supported). */
|
|
1039
|
-
tools:
|
|
1040
|
-
tool_choice:
|
|
1537
|
+
tools: z16.array(z16.unknown()).optional(),
|
|
1538
|
+
tool_choice: z16.unknown().optional(),
|
|
1041
1539
|
/**
|
|
1042
1540
|
* Structured output (ADR-0034). Parsed, not rejected — see the union above.
|
|
1043
1541
|
* `.nullish()` because clients and proxies that serialize every field send
|
|
@@ -1048,71 +1546,71 @@ var OpenAiChatCompletionRequestSchema = z13.object({
|
|
|
1048
1546
|
// Behaviour-changing params the platform does not honor in v1. Declared (not
|
|
1049
1547
|
// stripped) so the codec can reject them with a 400 rather than silently drop
|
|
1050
1548
|
// them — the same "reject, never silently drop" contract as `tools`.
|
|
1051
|
-
n:
|
|
1052
|
-
seed:
|
|
1053
|
-
logit_bias:
|
|
1054
|
-
presence_penalty:
|
|
1055
|
-
frequency_penalty:
|
|
1056
|
-
logprobs:
|
|
1057
|
-
top_logprobs:
|
|
1058
|
-
});
|
|
1059
|
-
var OpenAiUsageSchema =
|
|
1060
|
-
prompt_tokens:
|
|
1061
|
-
completion_tokens:
|
|
1062
|
-
total_tokens:
|
|
1063
|
-
});
|
|
1064
|
-
var OpenAiChatCompletionResponseSchema =
|
|
1065
|
-
id:
|
|
1066
|
-
object:
|
|
1067
|
-
created:
|
|
1068
|
-
model:
|
|
1069
|
-
choices:
|
|
1070
|
-
|
|
1071
|
-
index:
|
|
1072
|
-
message:
|
|
1073
|
-
role:
|
|
1074
|
-
content:
|
|
1549
|
+
n: z16.unknown().optional(),
|
|
1550
|
+
seed: z16.unknown().optional(),
|
|
1551
|
+
logit_bias: z16.unknown().optional(),
|
|
1552
|
+
presence_penalty: z16.unknown().optional(),
|
|
1553
|
+
frequency_penalty: z16.unknown().optional(),
|
|
1554
|
+
logprobs: z16.unknown().optional(),
|
|
1555
|
+
top_logprobs: z16.unknown().optional()
|
|
1556
|
+
});
|
|
1557
|
+
var OpenAiUsageSchema = z16.object({
|
|
1558
|
+
prompt_tokens: z16.int().nonnegative(),
|
|
1559
|
+
completion_tokens: z16.int().nonnegative(),
|
|
1560
|
+
total_tokens: z16.int().nonnegative()
|
|
1561
|
+
});
|
|
1562
|
+
var OpenAiChatCompletionResponseSchema = z16.object({
|
|
1563
|
+
id: z16.string(),
|
|
1564
|
+
object: z16.literal("chat.completion"),
|
|
1565
|
+
created: z16.int(),
|
|
1566
|
+
model: z16.string(),
|
|
1567
|
+
choices: z16.array(
|
|
1568
|
+
z16.object({
|
|
1569
|
+
index: z16.int(),
|
|
1570
|
+
message: z16.object({
|
|
1571
|
+
role: z16.literal("assistant"),
|
|
1572
|
+
content: z16.string()
|
|
1075
1573
|
}),
|
|
1076
|
-
finish_reason:
|
|
1574
|
+
finish_reason: z16.string()
|
|
1077
1575
|
})
|
|
1078
1576
|
),
|
|
1079
1577
|
usage: OpenAiUsageSchema
|
|
1080
1578
|
});
|
|
1081
|
-
var OpenAiChatCompletionChunkSchema =
|
|
1082
|
-
id:
|
|
1083
|
-
object:
|
|
1084
|
-
created:
|
|
1085
|
-
model:
|
|
1086
|
-
choices:
|
|
1087
|
-
|
|
1088
|
-
index:
|
|
1089
|
-
delta:
|
|
1090
|
-
role:
|
|
1091
|
-
content:
|
|
1579
|
+
var OpenAiChatCompletionChunkSchema = z16.object({
|
|
1580
|
+
id: z16.string(),
|
|
1581
|
+
object: z16.literal("chat.completion.chunk"),
|
|
1582
|
+
created: z16.int(),
|
|
1583
|
+
model: z16.string(),
|
|
1584
|
+
choices: z16.array(
|
|
1585
|
+
z16.object({
|
|
1586
|
+
index: z16.int(),
|
|
1587
|
+
delta: z16.object({
|
|
1588
|
+
role: z16.literal("assistant").optional(),
|
|
1589
|
+
content: z16.string().optional()
|
|
1092
1590
|
}),
|
|
1093
|
-
finish_reason:
|
|
1591
|
+
finish_reason: z16.string().nullable()
|
|
1094
1592
|
})
|
|
1095
1593
|
),
|
|
1096
1594
|
usage: OpenAiUsageSchema.nullish()
|
|
1097
1595
|
});
|
|
1098
|
-
var OpenAiModelSchema =
|
|
1099
|
-
id:
|
|
1100
|
-
object:
|
|
1101
|
-
created:
|
|
1102
|
-
owned_by:
|
|
1596
|
+
var OpenAiModelSchema = z16.object({
|
|
1597
|
+
id: z16.string(),
|
|
1598
|
+
object: z16.literal("model"),
|
|
1599
|
+
created: z16.int(),
|
|
1600
|
+
owned_by: z16.string()
|
|
1103
1601
|
});
|
|
1104
|
-
var OpenAiModelListSchema =
|
|
1105
|
-
object:
|
|
1106
|
-
data:
|
|
1602
|
+
var OpenAiModelListSchema = z16.object({
|
|
1603
|
+
object: z16.literal("list"),
|
|
1604
|
+
data: z16.array(OpenAiModelSchema)
|
|
1107
1605
|
});
|
|
1108
1606
|
|
|
1109
1607
|
// ../shared/src/usage.ts
|
|
1110
|
-
import { z as
|
|
1608
|
+
import { z as z18 } from "zod";
|
|
1111
1609
|
|
|
1112
1610
|
// ../shared/src/principal.ts
|
|
1113
|
-
import { z as
|
|
1611
|
+
import { z as z17 } from "zod";
|
|
1114
1612
|
var PRINCIPAL_KINDS = ["user", "password", "anon", "dev"];
|
|
1115
|
-
var PrincipalKindSchema =
|
|
1613
|
+
var PrincipalKindSchema = z17.enum(PRINCIPAL_KINDS);
|
|
1116
1614
|
|
|
1117
1615
|
// ../shared/src/usage.ts
|
|
1118
1616
|
var GATEWAY_OUTCOMES = [
|
|
@@ -1121,159 +1619,155 @@ var GATEWAY_OUTCOMES = [
|
|
|
1121
1619
|
"refusal",
|
|
1122
1620
|
"quota_blocked",
|
|
1123
1621
|
"conflict",
|
|
1124
|
-
"forbidden"
|
|
1622
|
+
"forbidden",
|
|
1623
|
+
"connection_required"
|
|
1125
1624
|
];
|
|
1126
|
-
var GatewayOutcomeSchema =
|
|
1625
|
+
var GatewayOutcomeSchema = z18.enum(GATEWAY_OUTCOMES);
|
|
1127
1626
|
var USAGE_RANGES = ["24h", "7d", "30d"];
|
|
1128
|
-
var UsageRangeSchema =
|
|
1627
|
+
var UsageRangeSchema = z18.enum(USAGE_RANGES);
|
|
1129
1628
|
var PLATFORM_RANGES = ["7d", "30d", "90d"];
|
|
1130
|
-
var PlatformRangeSchema =
|
|
1131
|
-
var UsageSeriesPointSchema =
|
|
1132
|
-
bucket:
|
|
1133
|
-
costUsd:
|
|
1134
|
-
tokens:
|
|
1135
|
-
requests:
|
|
1136
|
-
});
|
|
1137
|
-
var UsageSummarySchema =
|
|
1138
|
-
appId:
|
|
1629
|
+
var PlatformRangeSchema = z18.enum(PLATFORM_RANGES);
|
|
1630
|
+
var UsageSeriesPointSchema = z18.object({
|
|
1631
|
+
bucket: z18.iso.datetime(),
|
|
1632
|
+
costUsd: z18.number().nonnegative(),
|
|
1633
|
+
tokens: z18.int().nonnegative(),
|
|
1634
|
+
requests: z18.int().nonnegative()
|
|
1635
|
+
});
|
|
1636
|
+
var UsageSummarySchema = z18.object({
|
|
1637
|
+
appId: z18.uuid(),
|
|
1139
1638
|
/** The rolling range these figures cover. */
|
|
1140
1639
|
range: UsageRangeSchema,
|
|
1141
|
-
requests:
|
|
1142
|
-
inputTokens:
|
|
1143
|
-
outputTokens:
|
|
1640
|
+
requests: z18.int().nonnegative(),
|
|
1641
|
+
inputTokens: z18.int().nonnegative(),
|
|
1642
|
+
outputTokens: z18.int().nonnegative(),
|
|
1144
1643
|
/** Cache-aware input token totals (0 until prompt caching is enabled). */
|
|
1145
|
-
cacheReadInputTokens:
|
|
1146
|
-
cacheCreationInputTokens:
|
|
1644
|
+
cacheReadInputTokens: z18.int().nonnegative(),
|
|
1645
|
+
cacheCreationInputTokens: z18.int().nonnegative(),
|
|
1147
1646
|
/** Estimated spend in USD over the window at current rates (./pricing.ts). */
|
|
1148
|
-
costUsd:
|
|
1647
|
+
costUsd: z18.number().nonnegative(),
|
|
1149
1648
|
/** 95th-percentile upstream latency (ms) over the window; null when no timed calls. */
|
|
1150
|
-
latencyP95Ms:
|
|
1649
|
+
latencyP95Ms: z18.number().nonnegative().nullable(),
|
|
1151
1650
|
/** Fraction of calls in the window whose outcome was not `ok` (0..1). */
|
|
1152
|
-
errorRate:
|
|
1651
|
+
errorRate: z18.number().min(0).max(1),
|
|
1153
1652
|
/** Count of calls keyed by outcome — see {@link GATEWAY_OUTCOMES}. */
|
|
1154
|
-
byOutcome:
|
|
1155
|
-
byModel:
|
|
1156
|
-
|
|
1157
|
-
model:
|
|
1158
|
-
tokens:
|
|
1159
|
-
requests:
|
|
1653
|
+
byOutcome: z18.record(z18.string(), z18.int().nonnegative()),
|
|
1654
|
+
byModel: z18.array(
|
|
1655
|
+
z18.object({
|
|
1656
|
+
model: z18.string(),
|
|
1657
|
+
tokens: z18.int().nonnegative(),
|
|
1658
|
+
requests: z18.int().nonnegative(),
|
|
1160
1659
|
/** Estimated spend in USD for this model over the window. */
|
|
1161
|
-
costUsd:
|
|
1660
|
+
costUsd: z18.number().nonnegative()
|
|
1162
1661
|
})
|
|
1163
1662
|
),
|
|
1164
1663
|
/** Dense, zero-filled buckets across the range, oldest-first, for the trend chart. */
|
|
1165
|
-
series:
|
|
1664
|
+
series: z18.array(UsageSeriesPointSchema),
|
|
1166
1665
|
/**
|
|
1167
1666
|
* Today-since-midnight totals, independent of `range` — backs the daily-cap
|
|
1168
1667
|
* gauge (the budget the edge enforces is per calendar day).
|
|
1169
1668
|
*/
|
|
1170
|
-
today:
|
|
1171
|
-
tokens:
|
|
1172
|
-
costUsd:
|
|
1669
|
+
today: z18.object({
|
|
1670
|
+
tokens: z18.int().nonnegative(),
|
|
1671
|
+
costUsd: z18.number().nonnegative()
|
|
1173
1672
|
})
|
|
1174
1673
|
});
|
|
1175
|
-
var GatewayCallSchema =
|
|
1176
|
-
id:
|
|
1177
|
-
appId:
|
|
1674
|
+
var GatewayCallSchema = z18.object({
|
|
1675
|
+
id: z18.uuid(),
|
|
1676
|
+
appId: z18.uuid(),
|
|
1178
1677
|
/** App slug at read time; null when the app row no longer exists. */
|
|
1179
|
-
slug:
|
|
1180
|
-
userOid:
|
|
1678
|
+
slug: z18.string().nullable(),
|
|
1679
|
+
userOid: z18.string(),
|
|
1181
1680
|
/**
|
|
1182
1681
|
* The caller's claims as captured when the call was made — the **display half**
|
|
1183
1682
|
* (`App.ownerName`/`ownerEmail` applied to the ledger). Render these; never
|
|
1184
1683
|
* compare them, and never join on them. Null for anonymous, shared-password and
|
|
1185
1684
|
* dev-token callers, and for rows predating the columns, in which case the UI
|
|
1186
|
-
* falls back to `userOid` —
|
|
1187
|
-
*
|
|
1685
|
+
* falls back to `userOid` — an opaque principal id the platform declines
|
|
1686
|
+
* to resolve (no Graph `/users` grant by design, ADR-0040 decision 3; and
|
|
1687
|
+
* pre-re-base rows hold a pairwise `sub`), so it is a last resort rather
|
|
1688
|
+
* than a label.
|
|
1188
1689
|
*/
|
|
1189
|
-
userName:
|
|
1190
|
-
userEmail:
|
|
1690
|
+
userName: z18.string().nullable(),
|
|
1691
|
+
userEmail: z18.string().nullable(),
|
|
1191
1692
|
/**
|
|
1192
1693
|
* Which kind of principal made the call. Read this instead of parsing
|
|
1193
1694
|
* `userOid` — see {@link PrincipalKindSchema} for why the obvious shape test is
|
|
1194
1695
|
* unsound. Null on rows written before the column, which render from `userOid`.
|
|
1195
1696
|
*/
|
|
1196
1697
|
userKind: PrincipalKindSchema.nullable(),
|
|
1197
|
-
capability:
|
|
1198
|
-
model:
|
|
1199
|
-
inputTokens:
|
|
1200
|
-
outputTokens:
|
|
1201
|
-
cacheReadInputTokens:
|
|
1202
|
-
cacheCreationInputTokens:
|
|
1698
|
+
capability: z18.string(),
|
|
1699
|
+
model: z18.string(),
|
|
1700
|
+
inputTokens: z18.int().nonnegative(),
|
|
1701
|
+
outputTokens: z18.int().nonnegative(),
|
|
1702
|
+
cacheReadInputTokens: z18.int().nonnegative(),
|
|
1703
|
+
cacheCreationInputTokens: z18.int().nonnegative(),
|
|
1203
1704
|
/** Estimated spend in USD for this single call at current rates. */
|
|
1204
|
-
costUsd:
|
|
1705
|
+
costUsd: z18.number().nonnegative(),
|
|
1205
1706
|
/** Upstream round-trip latency in ms (0 when not measured). */
|
|
1206
|
-
durationMs:
|
|
1707
|
+
durationMs: z18.int().nonnegative(),
|
|
1207
1708
|
/** Upstream/egress HTTP status — set for `fetch`; null for streamed `llm`. */
|
|
1208
|
-
statusCode:
|
|
1709
|
+
statusCode: z18.int().nullable(),
|
|
1209
1710
|
/**
|
|
1210
1711
|
* Request path of the proxied call — set for `fetch`; null otherwise. The
|
|
1211
1712
|
* target's query string is excluded (see `redactFetchTarget` in `./logging.ts`)
|
|
1212
1713
|
* because that is where credentials are conventionally placed — but a path can
|
|
1213
1714
|
* carry one too, and nothing here detects that. See ADR-0021.
|
|
1214
1715
|
*/
|
|
1215
|
-
path:
|
|
1716
|
+
path: z18.string().nullable(),
|
|
1216
1717
|
/** HTTP method of the proxied call — set for `fetch`; null otherwise. */
|
|
1217
|
-
method:
|
|
1718
|
+
method: z18.string().nullable(),
|
|
1218
1719
|
/** LLM stop reason; null for non-LLM calls. */
|
|
1219
|
-
stopReason:
|
|
1720
|
+
stopReason: z18.string().nullable(),
|
|
1220
1721
|
/** Short upstream error string; null on success. */
|
|
1221
|
-
errorDetail:
|
|
1722
|
+
errorDetail: z18.string().nullable(),
|
|
1222
1723
|
outcome: GatewayOutcomeSchema,
|
|
1223
|
-
createdAt:
|
|
1724
|
+
createdAt: z18.iso.datetime()
|
|
1224
1725
|
});
|
|
1225
|
-
var GatewayAuditPageSchema =
|
|
1226
|
-
rows:
|
|
1726
|
+
var GatewayAuditPageSchema = z18.object({
|
|
1727
|
+
rows: z18.array(GatewayCallSchema),
|
|
1227
1728
|
/** Pass as `?before=` to fetch the next (older) page; absent when exhausted. */
|
|
1228
|
-
nextBefore:
|
|
1729
|
+
nextBefore: z18.iso.datetime().optional()
|
|
1229
1730
|
});
|
|
1230
|
-
var PlatformUsageSchema =
|
|
1731
|
+
var PlatformUsageSchema = z18.object({
|
|
1231
1732
|
/** The rolling range the series + breakdowns cover. */
|
|
1232
1733
|
range: PlatformRangeSchema,
|
|
1233
1734
|
/** Dense, zero-filled daily buckets across the range, oldest-first. */
|
|
1234
|
-
series:
|
|
1735
|
+
series: z18.array(UsageSeriesPointSchema),
|
|
1235
1736
|
/** Per-app rollup over the range, busiest-first. */
|
|
1236
|
-
byApp:
|
|
1237
|
-
|
|
1238
|
-
slug:
|
|
1239
|
-
tokens:
|
|
1240
|
-
requests:
|
|
1737
|
+
byApp: z18.array(
|
|
1738
|
+
z18.object({
|
|
1739
|
+
slug: z18.string().nullable(),
|
|
1740
|
+
tokens: z18.int().nonnegative(),
|
|
1741
|
+
requests: z18.int().nonnegative(),
|
|
1241
1742
|
/** Estimated spend in USD for this app over the range. */
|
|
1242
|
-
costUsd:
|
|
1743
|
+
costUsd: z18.number().nonnegative()
|
|
1243
1744
|
})
|
|
1244
1745
|
),
|
|
1245
1746
|
/** Month-to-date headline KPIs (independent of `range`). */
|
|
1246
|
-
totals:
|
|
1247
|
-
tokensMTD:
|
|
1248
|
-
requestsMTD:
|
|
1747
|
+
totals: z18.object({
|
|
1748
|
+
tokensMTD: z18.int().nonnegative(),
|
|
1749
|
+
requestsMTD: z18.int().nonnegative(),
|
|
1249
1750
|
/** Estimated month-to-date spend in USD across all apps. */
|
|
1250
|
-
costMTD:
|
|
1751
|
+
costMTD: z18.number().nonnegative(),
|
|
1251
1752
|
/** Distinct `userOid`s seen month-to-date. */
|
|
1252
|
-
activeUsers:
|
|
1753
|
+
activeUsers: z18.int().nonnegative()
|
|
1253
1754
|
}),
|
|
1254
1755
|
/** Token + cost share by capability over the range (essentially all `llm` in M4). */
|
|
1255
|
-
capabilityMix:
|
|
1256
|
-
|
|
1257
|
-
capability:
|
|
1258
|
-
tokens:
|
|
1756
|
+
capabilityMix: z18.array(
|
|
1757
|
+
z18.object({
|
|
1758
|
+
capability: z18.string(),
|
|
1759
|
+
tokens: z18.int().nonnegative(),
|
|
1259
1760
|
/** Estimated spend in USD for this capability over the range. */
|
|
1260
|
-
costUsd:
|
|
1761
|
+
costUsd: z18.number().nonnegative()
|
|
1261
1762
|
})
|
|
1262
1763
|
)
|
|
1263
1764
|
});
|
|
1264
1765
|
|
|
1265
1766
|
// ../shared/src/data.ts
|
|
1266
|
-
import { z as
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
var ENVS = ["prod", "dev"];
|
|
1271
|
-
var EnvSchema = z16.enum(ENVS);
|
|
1272
|
-
|
|
1273
|
-
// ../shared/src/data.ts
|
|
1274
|
-
var CollectionItemSchema = z17.object({
|
|
1275
|
-
id: z17.uuid(),
|
|
1276
|
-
collection: z17.string(),
|
|
1767
|
+
import { z as z19 } from "zod";
|
|
1768
|
+
var CollectionItemSchema = z19.object({
|
|
1769
|
+
id: z19.uuid(),
|
|
1770
|
+
collection: z19.string(),
|
|
1277
1771
|
/**
|
|
1278
1772
|
* Which tier collected the row (dev-mode §5). The runtime roles are RLS-pinned
|
|
1279
1773
|
* to one tier each, but the portal reads across both, so the discriminator has
|
|
@@ -1282,157 +1776,215 @@ var CollectionItemSchema = z17.object({
|
|
|
1282
1776
|
*/
|
|
1283
1777
|
env: EnvSchema,
|
|
1284
1778
|
/** The submitting user, if authenticated; null for anonymous/public visitors. */
|
|
1285
|
-
userOid:
|
|
1779
|
+
userOid: z19.string().nullable(),
|
|
1286
1780
|
/**
|
|
1287
1781
|
* The submitter's captured claims — display half, rendered but never compared
|
|
1288
1782
|
* (see `GatewayCallSchema.userName`). Null whenever `userOid` is, and for
|
|
1289
1783
|
* password/dev-token principals.
|
|
1290
1784
|
*/
|
|
1291
|
-
userName:
|
|
1292
|
-
userEmail:
|
|
1785
|
+
userName: z19.string().nullable(),
|
|
1786
|
+
userEmail: z19.string().nullable(),
|
|
1293
1787
|
/** The submitter's kind; null whenever `userOid` is, and on pre-column rows. */
|
|
1294
1788
|
userKind: PrincipalKindSchema.nullable(),
|
|
1295
|
-
item:
|
|
1789
|
+
item: z19.unknown(),
|
|
1296
1790
|
/** Hashed IP / truncated UA for abuse triage; null if not captured. */
|
|
1297
|
-
meta:
|
|
1298
|
-
createdAt:
|
|
1791
|
+
meta: z19.unknown().nullable(),
|
|
1792
|
+
createdAt: z19.iso.datetime()
|
|
1299
1793
|
});
|
|
1300
|
-
var CollectionItemsPageSchema =
|
|
1301
|
-
rows:
|
|
1794
|
+
var CollectionItemsPageSchema = z19.object({
|
|
1795
|
+
rows: z19.array(CollectionItemSchema),
|
|
1302
1796
|
/** Pass as `?before=` to fetch the next (older) page; absent when exhausted. */
|
|
1303
|
-
nextBefore:
|
|
1797
|
+
nextBefore: z19.iso.datetime().optional()
|
|
1304
1798
|
});
|
|
1305
|
-
var CollectionSummarySchema =
|
|
1306
|
-
name:
|
|
1799
|
+
var CollectionSummarySchema = z19.object({
|
|
1800
|
+
name: z19.string(),
|
|
1307
1801
|
env: EnvSchema,
|
|
1308
|
-
count:
|
|
1802
|
+
count: z19.int().nonnegative(),
|
|
1309
1803
|
/** Newest row's timestamp; null only if the group is somehow empty. */
|
|
1310
|
-
lastAt:
|
|
1804
|
+
lastAt: z19.iso.datetime().nullable()
|
|
1311
1805
|
});
|
|
1312
1806
|
|
|
1313
1807
|
// ../shared/src/collectionTable.ts
|
|
1314
1808
|
var BOM = String.fromCharCode(65279);
|
|
1315
1809
|
|
|
1316
|
-
// ../shared/src/
|
|
1317
|
-
import { z as
|
|
1318
|
-
var
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1810
|
+
// ../shared/src/connections.ts
|
|
1811
|
+
import { z as z20 } from "zod";
|
|
1812
|
+
var CONNECTION_STATUSES = ["live", "reconnect-needed", "invalidated"];
|
|
1813
|
+
var ConnectionStatusSchema = z20.enum(CONNECTION_STATUSES);
|
|
1814
|
+
var ConnectionMaterialSchema = z20.strictObject({
|
|
1815
|
+
access: z20.string().min(1),
|
|
1816
|
+
refresh: z20.string().min(1)
|
|
1817
|
+
});
|
|
1818
|
+
var MAX_GRANTED_SCOPES = 64;
|
|
1819
|
+
var GrantedScopesSchema = z20.array(ScopeTokenSchema).max(MAX_GRANTED_SCOPES);
|
|
1820
|
+
var UserConnectionSchema = z20.strictObject({
|
|
1821
|
+
id: z20.uuid(),
|
|
1822
|
+
userOid: z20.string().min(1),
|
|
1823
|
+
providerId: z20.uuid(),
|
|
1824
|
+
providerRevision: z20.int().positive(),
|
|
1825
|
+
env: EnvSchema,
|
|
1826
|
+
status: ConnectionStatusSchema,
|
|
1827
|
+
material: z20.string().min(1),
|
|
1828
|
+
grantedScopes: GrantedScopesSchema,
|
|
1829
|
+
grantedAt: z20.iso.datetime(),
|
|
1830
|
+
expiresAt: z20.iso.datetime(),
|
|
1831
|
+
renewBeforeNext: z20.boolean(),
|
|
1832
|
+
pendingRetire: z20.string().min(1).nullable(),
|
|
1833
|
+
lastRenewedAt: z20.iso.datetime().nullable(),
|
|
1834
|
+
createdAt: z20.iso.datetime(),
|
|
1835
|
+
updatedAt: z20.iso.datetime()
|
|
1836
|
+
});
|
|
1837
|
+
var MyConnectionSchema = z20.strictObject({
|
|
1838
|
+
id: z20.uuid(),
|
|
1839
|
+
providerRef: ProviderRefSchema,
|
|
1840
|
+
providerDisplayName: z20.string().min(1).max(200),
|
|
1841
|
+
env: EnvSchema,
|
|
1842
|
+
status: ConnectionStatusSchema.exclude(["invalidated"]),
|
|
1843
|
+
grantedScopes: GrantedScopesSchema,
|
|
1844
|
+
grantedAt: z20.iso.datetime(),
|
|
1845
|
+
sharedApps: z20.array(BoundAppSchema)
|
|
1846
|
+
});
|
|
1847
|
+
var MyConnectionsResponseSchema = z20.strictObject({
|
|
1848
|
+
connections: z20.array(MyConnectionSchema)
|
|
1849
|
+
});
|
|
1850
|
+
var DisconnectResponseSchema = z20.strictObject({
|
|
1851
|
+
outcome: z20.enum(["disconnected", "already_removed"])
|
|
1852
|
+
});
|
|
1853
|
+
|
|
1854
|
+
// ../shared/src/consent.ts
|
|
1855
|
+
import { z as z21 } from "zod";
|
|
1856
|
+
var ConsentStateSchema = z21.string().min(43).max(128);
|
|
1857
|
+
var ConsentNonceSchema = z21.string().min(16).max(128);
|
|
1858
|
+
var ConsentAttemptTagSchema = z21.string().min(1).max(128).regex(/^[A-Za-z0-9._~-]+$/, "must be URL-safe punctuation, letters, or digits");
|
|
1859
|
+
var ConsentIdentitySchema = z21.discriminatedUnion("kind", [
|
|
1860
|
+
z21.strictObject({ kind: z21.literal("user"), userOid: z21.string().min(1).max(128) }),
|
|
1861
|
+
z21.strictObject({
|
|
1862
|
+
kind: z21.literal("dev"),
|
|
1863
|
+
developerOid: z21.string().min(1).max(128),
|
|
1864
|
+
nonce: ConsentNonceSchema
|
|
1865
|
+
})
|
|
1327
1866
|
]);
|
|
1328
|
-
var
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
};
|
|
1335
|
-
var
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
}),
|
|
1345
|
-
|
|
1346
|
-
z18.object({ kind: z18.literal("query"), param: z18.string().min(1) }),
|
|
1347
|
-
/**
|
|
1348
|
-
* HMAC over a timestamp. The signed input is the timestamp string **alone** —
|
|
1349
|
-
* not the method, path, query, or body — so injection is a pure function of
|
|
1350
|
-
* (private key, now) and needs no request context.
|
|
1351
|
-
*
|
|
1352
|
-
* The canonical form lives in the KIND NAME, deliberately. A scheme that signs
|
|
1353
|
-
* method+path+body is a *sibling kind* (`hmac-request`) — a code change with
|
|
1354
|
-
* tests, reviewed — never an admin-editable canonical-string template.
|
|
1355
|
-
* Canonicalization is where implementations of this family go wrong, and it
|
|
1356
|
-
* does not belong in a text box.
|
|
1357
|
-
*
|
|
1358
|
-
* SHA-256, lowercase-hex, and ISO-8601-with-milliseconds are fixed rather than
|
|
1359
|
-
* configurable. Each would carry a default, and `app_secrets.injection` is a
|
|
1360
|
-
* schemaless JSON column, so adding a knob later is purely additive with no
|
|
1361
|
-
* migration — while fixing them now removes the weak-algorithm and
|
|
1362
|
-
* unencodable-digest failure classes outright.
|
|
1363
|
-
*
|
|
1364
|
-
* The stored value is a JSON blob carrying both halves of the key pair
|
|
1365
|
-
* ({@link HmacCredentialSchema}): regenerating the pair changes both, and a
|
|
1366
|
-
* blob rotates atomically through the existing rotate route.
|
|
1367
|
-
*/
|
|
1368
|
-
z18.object({
|
|
1369
|
-
kind: z18.literal("hmac-timestamp"),
|
|
1370
|
-
/** Header carrying the timestamp that is also the entire signed input. */
|
|
1371
|
-
timestampHeader: headerName(from),
|
|
1372
|
-
/** Header carrying the rendered credential + signature. */
|
|
1373
|
-
authHeader: headerName(from).default("authorization"),
|
|
1374
|
-
/**
|
|
1375
|
-
* Value written to `authHeader`. `{credential}` and `{signature}` are
|
|
1376
|
-
* substituted. Named rather than the `header` kind's bare `{}` because there
|
|
1377
|
-
* are two substitutions: the convention is one value ⇒ `{}`, more than one ⇒
|
|
1378
|
-
* named placeholders. Positional `{}` here would let a swapped template
|
|
1379
|
-
* produce a well-formed header that silently fails to authenticate.
|
|
1380
|
-
*
|
|
1381
|
-
* Both placeholders are required. This recipe writes exactly two headers,
|
|
1382
|
-
* one of them the timestamp, so there is no configuration where the public
|
|
1383
|
-
* credential id travels elsewhere — omitting `{credential}` means the
|
|
1384
|
-
* upstream can never identify the key and every call 401s.
|
|
1385
|
-
*/
|
|
1386
|
-
template: headerTemplate(from).refine((t) => t.includes("{signature}"), "template must contain {signature}").refine((t) => t.includes("{credential}"), "template must contain {credential}")
|
|
1387
|
-
}).refine(
|
|
1388
|
-
(r) => r.timestampHeader !== r.authHeader,
|
|
1389
|
-
"timestampHeader and authHeader must differ \u2014 the second write overwrites the first"
|
|
1390
|
-
)
|
|
1867
|
+
var HttpUrlNoUserinfoSchema = (() => {
|
|
1868
|
+
const userinfo = /^https?:\/\/[/\\]*[^/?#\\]*@/i;
|
|
1869
|
+
return z21.url({ protocol: /^https?$/ }).refine(
|
|
1870
|
+
(value) => !userinfo.test(value.replace(/[\t\n\r]/g, "")),
|
|
1871
|
+
"URL must not carry userinfo"
|
|
1872
|
+
);
|
|
1873
|
+
})();
|
|
1874
|
+
var ConsultRequestSchema = z21.strictObject({
|
|
1875
|
+
identity: ConsentIdentitySchema,
|
|
1876
|
+
appSlug: z21.string().regex(SLUG_PATTERN, "must be a lowercase DNS label (a-z, 0-9, hyphen)"),
|
|
1877
|
+
providerRef: ProviderRefSchema,
|
|
1878
|
+
openerOrigin: HttpUrlNoUserinfoSchema.transform((value) => new URL(value).origin),
|
|
1879
|
+
callbackUrl: HttpUrlNoUserinfoSchema
|
|
1880
|
+
});
|
|
1881
|
+
var ConsultResponseSchema = z21.discriminatedUnion("outcome", [
|
|
1882
|
+
z21.strictObject({ outcome: z21.literal("already_connected") }),
|
|
1883
|
+
z21.strictObject({ outcome: z21.literal("not_available") }),
|
|
1884
|
+
z21.strictObject({ outcome: z21.literal("started"), authorizeUrl: z21.url() })
|
|
1391
1885
|
]);
|
|
1392
|
-
var
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
credential: z18.string().min(1),
|
|
1396
|
-
key: z18.string().min(1)
|
|
1886
|
+
var CancelRequestSchema = z21.strictObject({
|
|
1887
|
+
identity: ConsentIdentitySchema,
|
|
1888
|
+
state: ConsentStateSchema
|
|
1397
1889
|
});
|
|
1398
|
-
var
|
|
1399
|
-
var
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
var
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1890
|
+
var CONSENT_CANCEL_OUTCOMES = ["cancelled", "not_cancellable"];
|
|
1891
|
+
var CancelResponseSchema = z21.strictObject({
|
|
1892
|
+
outcome: z21.enum(CONSENT_CANCEL_OUTCOMES)
|
|
1893
|
+
});
|
|
1894
|
+
var ConnectionStatusIdentitySchema = z21.discriminatedUnion("kind", [
|
|
1895
|
+
z21.strictObject({ kind: z21.literal("user"), userOid: z21.string().min(1).max(128) }),
|
|
1896
|
+
z21.strictObject({ kind: z21.literal("dev"), developerOid: z21.string().min(1).max(128) })
|
|
1897
|
+
]);
|
|
1898
|
+
var ConnectionStatusRequestSchema = z21.strictObject({
|
|
1899
|
+
identity: ConnectionStatusIdentitySchema,
|
|
1900
|
+
appSlug: z21.string().regex(SLUG_PATTERN, "must be a lowercase DNS label (a-z, 0-9, hyphen)"),
|
|
1901
|
+
providerRef: ProviderRefSchema
|
|
1902
|
+
});
|
|
1903
|
+
var ConnectionStatusResponseSchema = z21.strictObject({
|
|
1904
|
+
status: z21.enum(["connected", "not_connected", "not_available"])
|
|
1905
|
+
});
|
|
1906
|
+
var AttemptCancelRequestSchema = z21.strictObject({
|
|
1907
|
+
provider: ProviderRefSchema,
|
|
1908
|
+
attempt: ConsentAttemptTagSchema
|
|
1909
|
+
});
|
|
1910
|
+
var DevConsentStartResponseSchema = z21.discriminatedUnion("outcome", [
|
|
1911
|
+
z21.strictObject({ outcome: z21.literal("started"), popupUrl: z21.url() }),
|
|
1912
|
+
z21.strictObject({ outcome: z21.literal("already_connected") }),
|
|
1913
|
+
z21.strictObject({ outcome: z21.literal("not_available") })
|
|
1914
|
+
]);
|
|
1915
|
+
var HELIX_CONNECT_MESSAGE_SOURCE = "helix-connect";
|
|
1916
|
+
var CONNECT_MESSAGE_VERSION = 1;
|
|
1917
|
+
var CONSENT_MESSAGE_OUTCOMES = [
|
|
1918
|
+
"connected",
|
|
1919
|
+
"already_connected",
|
|
1920
|
+
"denied",
|
|
1921
|
+
"cancelled",
|
|
1922
|
+
"timeout",
|
|
1923
|
+
"blocked",
|
|
1924
|
+
"signin_required",
|
|
1925
|
+
"error"
|
|
1926
|
+
];
|
|
1927
|
+
var CONSENT_MESSAGE_REASONS = [
|
|
1928
|
+
"conflict",
|
|
1929
|
+
"provider_unavailable",
|
|
1930
|
+
"provider_misconfigured",
|
|
1931
|
+
"provider_incompatible",
|
|
1932
|
+
"service_unavailable"
|
|
1933
|
+
];
|
|
1934
|
+
var ConnectOutcomeMessageSchema = z21.strictObject({
|
|
1935
|
+
source: z21.literal(HELIX_CONNECT_MESSAGE_SOURCE),
|
|
1936
|
+
version: z21.literal(CONNECT_MESSAGE_VERSION),
|
|
1937
|
+
attempt: ConsentAttemptTagSchema.optional(),
|
|
1938
|
+
provider: ProviderRefSchema,
|
|
1939
|
+
outcome: z21.enum(CONSENT_MESSAGE_OUTCOMES),
|
|
1940
|
+
reason: z21.enum(CONSENT_MESSAGE_REASONS).nullable()
|
|
1941
|
+
}).refine((message) => message.outcome === "error" || message.reason === null, {
|
|
1942
|
+
error: "reason is only carried with the error outcome"
|
|
1943
|
+
});
|
|
1944
|
+
var ConnectResultSchema = z21.object({
|
|
1945
|
+
outcome: z21.enum(CONSENT_MESSAGE_OUTCOMES),
|
|
1946
|
+
provider: z21.string(),
|
|
1947
|
+
attempt: ConsentAttemptTagSchema.optional(),
|
|
1948
|
+
reason: z21.enum(CONSENT_MESSAGE_REASONS).optional()
|
|
1949
|
+
}).refine((result) => result.outcome === "error" || result.reason === void 0, {
|
|
1950
|
+
error: "reason is only carried with the error outcome"
|
|
1430
1951
|
});
|
|
1431
|
-
|
|
1432
|
-
|
|
1952
|
+
|
|
1953
|
+
// ../shared/src/exchange.ts
|
|
1954
|
+
import { z as z22 } from "zod";
|
|
1955
|
+
var ExchangeCodeSchema = z22.string().min(1).max(1024);
|
|
1956
|
+
var CodeVerifierSchema = z22.string().min(43).max(128).regex(/^[A-Za-z0-9\-._~]+$/, "must be an RFC 7636 code_verifier (43-128 unreserved characters)");
|
|
1957
|
+
var ExchangeRequestSchema = z22.strictObject({
|
|
1958
|
+
providerId: z22.uuid(),
|
|
1959
|
+
providerRevision: z22.int().positive(),
|
|
1960
|
+
env: EnvSchema,
|
|
1961
|
+
code: ExchangeCodeSchema,
|
|
1962
|
+
codeVerifier: CodeVerifierSchema,
|
|
1963
|
+
redirectUri: HttpUrlNoUserinfoSchema
|
|
1964
|
+
});
|
|
1965
|
+
var EXCHANGE_REJECTION_REASONS = [
|
|
1966
|
+
"unusable_lifetime",
|
|
1967
|
+
"missing_refresh_token",
|
|
1968
|
+
"missing_permissions"
|
|
1969
|
+
];
|
|
1970
|
+
var ExchangeRejectionReasonSchema = z22.enum(EXCHANGE_REJECTION_REASONS);
|
|
1971
|
+
var MAX_EXCHANGED_SCOPES = 64;
|
|
1972
|
+
var ExchangedScopesSchema = z22.array(ScopeTokenSchema).max(MAX_EXCHANGED_SCOPES);
|
|
1973
|
+
var ExchangeResponseSchema = z22.discriminatedUnion("outcome", [
|
|
1974
|
+
z22.strictObject({
|
|
1975
|
+
outcome: z22.literal("exchanged"),
|
|
1976
|
+
access: z22.string().min(1),
|
|
1977
|
+
refresh: z22.string().min(1),
|
|
1978
|
+
grantedScopes: ExchangedScopesSchema,
|
|
1979
|
+
accessExpiresAt: z22.iso.datetime()
|
|
1980
|
+
}),
|
|
1981
|
+
z22.strictObject({ outcome: z22.literal("rejected"), reason: ExchangeRejectionReasonSchema }),
|
|
1982
|
+
z22.strictObject({ outcome: z22.literal("provider_unavailable") }),
|
|
1983
|
+
z22.strictObject({ outcome: z22.literal("exchange_failed") })
|
|
1984
|
+
]);
|
|
1433
1985
|
|
|
1434
1986
|
// ../shared/src/devTokens.ts
|
|
1435
|
-
import { z as
|
|
1987
|
+
import { z as z23 } from "zod";
|
|
1436
1988
|
function isValidDevOrigin(input) {
|
|
1437
1989
|
if (input.includes("*")) return false;
|
|
1438
1990
|
let url;
|
|
@@ -1446,47 +1998,69 @@ function isValidDevOrigin(input) {
|
|
|
1446
1998
|
if (url.username !== "" || url.password !== "") return false;
|
|
1447
1999
|
return input === url.origin || input === `${url.origin}/`;
|
|
1448
2000
|
}
|
|
1449
|
-
var DevOriginSchema =
|
|
2001
|
+
var DevOriginSchema = z23.string().refine(
|
|
1450
2002
|
isValidDevOrigin,
|
|
1451
2003
|
"must be an exact origin (scheme://host[:port]) \u2014 no path, query, or wildcard"
|
|
1452
2004
|
).transform((o) => new URL(o).origin);
|
|
1453
|
-
var TtlDaysSchema =
|
|
1454
|
-
var DevTokenMintRequestSchema =
|
|
1455
|
-
origins:
|
|
2005
|
+
var TtlDaysSchema = z23.number().int().min(1).max(365).optional();
|
|
2006
|
+
var DevTokenMintRequestSchema = z23.object({
|
|
2007
|
+
origins: z23.array(DevOriginSchema).min(1).max(20),
|
|
1456
2008
|
ttlDays: TtlDaysSchema
|
|
1457
2009
|
});
|
|
1458
|
-
var DevTokenRotateRequestSchema =
|
|
2010
|
+
var DevTokenRotateRequestSchema = z23.object({
|
|
1459
2011
|
ttlDays: TtlDaysSchema
|
|
1460
2012
|
});
|
|
1461
|
-
var DevTokenMetadataSchema =
|
|
1462
|
-
id:
|
|
1463
|
-
developerOid:
|
|
1464
|
-
origins:
|
|
1465
|
-
expiresAt:
|
|
1466
|
-
revokedAt:
|
|
1467
|
-
createdAt:
|
|
2013
|
+
var DevTokenMetadataSchema = z23.object({
|
|
2014
|
+
id: z23.string(),
|
|
2015
|
+
developerOid: z23.string(),
|
|
2016
|
+
origins: z23.array(z23.string()),
|
|
2017
|
+
expiresAt: z23.string(),
|
|
2018
|
+
revokedAt: z23.string().nullable().optional(),
|
|
2019
|
+
createdAt: z23.string()
|
|
1468
2020
|
});
|
|
1469
|
-
var DevTokenMintResponseSchema =
|
|
1470
|
-
token:
|
|
2021
|
+
var DevTokenMintResponseSchema = z23.object({
|
|
2022
|
+
token: z23.string(),
|
|
1471
2023
|
metadata: DevTokenMetadataSchema
|
|
1472
2024
|
});
|
|
1473
2025
|
|
|
1474
2026
|
// ../shared/src/instruction.ts
|
|
1475
|
-
import { z as
|
|
2027
|
+
import { z as z24 } from "zod";
|
|
1476
2028
|
var INSTRUCTION_CAPABILITIES = ["fetch", "llm"];
|
|
1477
|
-
var InstructionCapabilitySchema =
|
|
1478
|
-
var AttestedInstructionSchema =
|
|
2029
|
+
var InstructionCapabilitySchema = z24.enum(INSTRUCTION_CAPABILITIES);
|
|
2030
|
+
var AttestedInstructionSchema = z24.strictObject({
|
|
1479
2031
|
/** App the call is attributed to (registry app id). */
|
|
1480
|
-
appId:
|
|
2032
|
+
appId: z24.string().min(1),
|
|
1481
2033
|
/** Authenticated user, or the anonymous sentinel on `public` apps. */
|
|
1482
|
-
userOid:
|
|
2034
|
+
userOid: z24.string().min(1),
|
|
2035
|
+
/**
|
|
2036
|
+
* Which kind of principal `userOid` is (the `PrincipalKind` vocabulary the
|
|
2037
|
+
* edge records at capture time — recorded, never inferred). The delegated
|
|
2038
|
+
* resolution refuses `anon` and `password` callers — they can
|
|
2039
|
+
* never hold a connection — and serving that refusal
|
|
2040
|
+
* requires knowing the KIND: `userOid`'s shape proves nothing (a shared-
|
|
2041
|
+
* password pseudonym and an Entra `sub` share the base64url alphabet, and
|
|
2042
|
+
* the sentinel is only exact for `anon`). Optional on the payload as a
|
|
2043
|
+
* whole so secret-backed and keyless instructions keep minting unchanged;
|
|
2044
|
+
* mandatory on a delegated one — the refine below makes a provider-bearing
|
|
2045
|
+
* instruction without it unrepresentable, so an old edge cannot mint a
|
|
2046
|
+
* delegated call egress would have to guess about.
|
|
2047
|
+
*/
|
|
2048
|
+
userKind: PrincipalKindSchema.optional(),
|
|
1483
2049
|
capability: InstructionCapabilitySchema,
|
|
1484
2050
|
/** The allowlisted origin the edge authorized (scheme + host + port). */
|
|
1485
|
-
origin:
|
|
2051
|
+
origin: z24.url(),
|
|
1486
2052
|
/** Connection (secret) name to inject, if this is a secret-backed call. */
|
|
1487
|
-
connection:
|
|
2053
|
+
connection: z24.string().min(1).optional(),
|
|
2054
|
+
/**
|
|
2055
|
+
* Provider reference for a delegated call — the user's connection to this
|
|
2056
|
+
* provider is what egress injects. Exactly
|
|
2057
|
+
* one credential source travels: this and `connection` are XOR-enforced
|
|
2058
|
+
* below — a delegated instruction carries a provider ref or a
|
|
2059
|
+
* secret name, never both.
|
|
2060
|
+
*/
|
|
2061
|
+
provider: ProviderRefSchema.optional(),
|
|
1488
2062
|
/** Correlates the edge audit row with the egress call. */
|
|
1489
|
-
requestId:
|
|
2063
|
+
requestId: z24.string().min(1),
|
|
1490
2064
|
/**
|
|
1491
2065
|
* The HTTP method + URL pathname the edge authorized (ADR-0013 step 2, issue #6).
|
|
1492
2066
|
* Egress refuses a mismatched verb/resource, so a captured instruction can't be
|
|
@@ -1501,8 +2075,8 @@ var AttestedInstructionSchema = z20.object({
|
|
|
1501
2075
|
* means "old edge", not tampering — egress asserts ONLY when the claim is
|
|
1502
2076
|
* present. Make required once a fleet is reliably past deploy.
|
|
1503
2077
|
*/
|
|
1504
|
-
method:
|
|
1505
|
-
path:
|
|
2078
|
+
method: z24.string().min(1).optional(),
|
|
2079
|
+
path: z24.string().optional(),
|
|
1506
2080
|
/**
|
|
1507
2081
|
* Environment tier this call is scoped to (dev-mode design §6). Egress resolves
|
|
1508
2082
|
* the connection secret within this tier — a `dev` instruction can never reach a
|
|
@@ -1511,12 +2085,27 @@ var AttestedInstructionSchema = z20.object({
|
|
|
1511
2085
|
* minted before this field existed verifies as production.
|
|
1512
2086
|
*/
|
|
1513
2087
|
env: EnvSchema.default("prod")
|
|
2088
|
+
}).superRefine((i, ctx) => {
|
|
2089
|
+
if (i.connection !== void 0 && i.provider !== void 0) {
|
|
2090
|
+
ctx.addIssue({
|
|
2091
|
+
code: "custom",
|
|
2092
|
+
path: ["provider"],
|
|
2093
|
+
message: "an instruction carries exactly one credential source \u2014 a connection secret or a provider reference, never both."
|
|
2094
|
+
});
|
|
2095
|
+
}
|
|
2096
|
+
if (i.provider !== void 0 && i.userKind === void 0) {
|
|
2097
|
+
ctx.addIssue({
|
|
2098
|
+
code: "custom",
|
|
2099
|
+
path: ["userKind"],
|
|
2100
|
+
message: "a delegated instruction must carry the caller's principal kind \u2014 egress refuses anonymous and shared-password callers by kind, never by inferring it from userOid."
|
|
2101
|
+
});
|
|
2102
|
+
}
|
|
1514
2103
|
});
|
|
1515
2104
|
var INSTRUCTION_TTL_SECONDS = 30;
|
|
1516
2105
|
var INSTRUCTION_BURN_RETENTION_SECONDS = INSTRUCTION_TTL_SECONDS + 15;
|
|
1517
2106
|
|
|
1518
2107
|
// ../shared/src/fetch.ts
|
|
1519
|
-
import { z as
|
|
2108
|
+
import { z as z25 } from "zod";
|
|
1520
2109
|
var FETCH_ERROR_CODES = [
|
|
1521
2110
|
"forbidden",
|
|
1522
2111
|
"rate_limited",
|
|
@@ -1524,12 +2113,75 @@ var FETCH_ERROR_CODES = [
|
|
|
1524
2113
|
"blocked",
|
|
1525
2114
|
"too_large",
|
|
1526
2115
|
"replay",
|
|
1527
|
-
"upstream_error"
|
|
2116
|
+
"upstream_error",
|
|
2117
|
+
"connection_required",
|
|
2118
|
+
"provider_unavailable",
|
|
2119
|
+
"provider_misconfigured"
|
|
1528
2120
|
];
|
|
1529
|
-
var FetchErrorCodeSchema =
|
|
1530
|
-
var
|
|
2121
|
+
var FetchErrorCodeSchema = z25.enum(FETCH_ERROR_CODES);
|
|
2122
|
+
var FetchErrorProviderSchema = z25.strictObject({
|
|
2123
|
+
ref: ProviderRefSchema,
|
|
2124
|
+
displayName: z25.string().min(1).max(200).optional()
|
|
2125
|
+
});
|
|
2126
|
+
var FetchProxyErrorSchema = z25.object({
|
|
1531
2127
|
code: FetchErrorCodeSchema,
|
|
1532
|
-
message:
|
|
2128
|
+
message: z25.string(),
|
|
2129
|
+
/** Present only on the delegated-call codes that carry it (`connection_required`, `provider_unavailable`). */
|
|
2130
|
+
provider: FetchErrorProviderSchema.optional()
|
|
2131
|
+
});
|
|
2132
|
+
|
|
2133
|
+
// ../shared/src/sessions.ts
|
|
2134
|
+
import { z as z26 } from "zod";
|
|
2135
|
+
var SessionPrincipalKindSchema = z26.enum(["user", "password"]);
|
|
2136
|
+
var SessionSummarySchema = z26.object({
|
|
2137
|
+
id: z26.uuid(),
|
|
2138
|
+
appId: z26.uuid(),
|
|
2139
|
+
/** App slug at read time; the FK guarantees the app row exists. */
|
|
2140
|
+
slug: z26.string(),
|
|
2141
|
+
/**
|
|
2142
|
+
* The identity half — Entra's `oid` claim since ADR-0048 (a pairwise `sub`
|
|
2143
|
+
* on rows that predate the re-base; a `pw_*` pseudonym for shared-password
|
|
2144
|
+
* visitors). Compared and joined on, never rendered except as a last
|
|
2145
|
+
* resort: the platform deliberately holds no Graph `/users` grant, so no
|
|
2146
|
+
* principal id it stores resolves to a name.
|
|
2147
|
+
*/
|
|
2148
|
+
userOid: z26.string(),
|
|
2149
|
+
/**
|
|
2150
|
+
* The display half, captured at login. Render, never compare. Null for
|
|
2151
|
+
* shared-password visitors (each login is a fresh pseudonym) and for rows
|
|
2152
|
+
* predating the columns.
|
|
2153
|
+
*/
|
|
2154
|
+
userName: z26.string().nullable(),
|
|
2155
|
+
userEmail: z26.string().nullable(),
|
|
2156
|
+
userKind: SessionPrincipalKindSchema.nullable(),
|
|
2157
|
+
/** Group-id snapshot as of this session's login/refresh. */
|
|
2158
|
+
groups: z26.array(z26.string()),
|
|
2159
|
+
createdAt: z26.iso.datetime(),
|
|
2160
|
+
/** Null while the handoff is un-redeemed — impossible here (live rows only). */
|
|
2161
|
+
activatedAt: z26.iso.datetime().nullable(),
|
|
2162
|
+
/** When silent re-auth (`prompt=none`) becomes due; past = overdue. */
|
|
2163
|
+
refreshDueAt: z26.iso.datetime(),
|
|
2164
|
+
/** Hard cap — the session is dead at this instant regardless of activity. */
|
|
2165
|
+
expiresAt: z26.iso.datetime()
|
|
2166
|
+
});
|
|
2167
|
+
var SessionListResponseSchema = z26.object({
|
|
2168
|
+
rows: z26.array(SessionSummarySchema),
|
|
2169
|
+
groupNames: z26.record(z26.string(), z26.string().nullable()),
|
|
2170
|
+
groupsResolved: z26.boolean()
|
|
2171
|
+
});
|
|
2172
|
+
var SessionRevokeRequestSchema = z26.object({
|
|
2173
|
+
/**
|
|
2174
|
+
* A session's principal id as seen in the list — the `oid` claim, a `pw_*`
|
|
2175
|
+
* pseudonym, or a pre-re-base pairwise `sub`. Opaque to the admin; copied
|
|
2176
|
+
* from the row they clicked, so its shape is whatever the session rows
|
|
2177
|
+
* hold — bounded only against abuse, not parsed.
|
|
2178
|
+
*/
|
|
2179
|
+
userOid: z26.string().min(1).max(200)
|
|
2180
|
+
});
|
|
2181
|
+
var SessionRevokeResultSchema = z26.object({
|
|
2182
|
+
removed: z26.int().nonnegative(),
|
|
2183
|
+
/** Slugs of the apps the removed sessions lived on; empty when none. */
|
|
2184
|
+
apps: z26.array(z26.string())
|
|
1533
2185
|
});
|
|
1534
2186
|
|
|
1535
2187
|
// src/client.ts
|
|
@@ -1541,7 +2193,7 @@ var CliError = class extends Error {
|
|
|
1541
2193
|
this.code = code;
|
|
1542
2194
|
}
|
|
1543
2195
|
};
|
|
1544
|
-
var VersionListSchema =
|
|
2196
|
+
var VersionListSchema = z27.array(VersionSchema);
|
|
1545
2197
|
var PortalClient = class {
|
|
1546
2198
|
#baseUrl;
|
|
1547
2199
|
#tokenProvider;
|
|
@@ -1551,7 +2203,17 @@ var PortalClient = class {
|
|
|
1551
2203
|
this.#tokenProvider = typeof token === "string" ? async () => token : token;
|
|
1552
2204
|
}
|
|
1553
2205
|
createApp(input) {
|
|
1554
|
-
return this.#json(AppSchema, "POST", "/api/v1/apps", {
|
|
2206
|
+
return this.#json(AppSchema, "POST", "/api/v1/apps", {
|
|
2207
|
+
auth: true,
|
|
2208
|
+
body: {
|
|
2209
|
+
slug: input.slug,
|
|
2210
|
+
displayName: input.displayName,
|
|
2211
|
+
// Sent only when set, so a description-free create posts exactly the
|
|
2212
|
+
// body it always did — an older portal never sees a new key at all.
|
|
2213
|
+
...input.description !== void 0 ? { description: input.description } : {},
|
|
2214
|
+
...input.visibility !== void 0 ? { visibility: input.visibility } : {}
|
|
2215
|
+
}
|
|
2216
|
+
});
|
|
1555
2217
|
}
|
|
1556
2218
|
/** Public IdP discovery info — how `helix login` finds the issuer. */
|
|
1557
2219
|
getAuthConfig() {
|
|
@@ -1821,12 +2483,16 @@ function printVersion(v) {
|
|
|
1821
2483
|
}
|
|
1822
2484
|
function printApp(app) {
|
|
1823
2485
|
console.log(` app ${app.slug} \u2014 live version: ${app.currentVersionId ?? "(none)"}`);
|
|
2486
|
+
if (app.description) console.log(` ${app.description}`);
|
|
1824
2487
|
}
|
|
1825
2488
|
async function createCommand(client, config, opts) {
|
|
1826
2489
|
const slug = requireSlug(config);
|
|
1827
2490
|
const app = await client.createApp({
|
|
1828
2491
|
slug,
|
|
1829
2492
|
displayName: opts.displayName ?? slug,
|
|
2493
|
+
// Flag wins over helix.json, and an unset description is simply not sent —
|
|
2494
|
+
// an older portal never sees a key it doesn't know.
|
|
2495
|
+
description: opts.description ?? config.description,
|
|
1830
2496
|
visibility: parseVisibility(opts.visibility)
|
|
1831
2497
|
});
|
|
1832
2498
|
console.log(`Created app "${app.slug}".`);
|
|
@@ -1889,7 +2555,8 @@ async function loginCommand(client, config) {
|
|
|
1889
2555
|
await writeTokens({ portalUrl: config.portalUrl, issuer }, tokens, void 0, { audience });
|
|
1890
2556
|
const authed = new PortalClient(config.portalUrl, tokens.accessToken);
|
|
1891
2557
|
const me = await authed.me();
|
|
1892
|
-
|
|
2558
|
+
const oid = me.oid ? ` \u2014 oid ${me.oid}` : "";
|
|
2559
|
+
console.log(`Logged in as ${me.name ?? me.sub} (${me.sub})${oid}.`);
|
|
1893
2560
|
}
|
|
1894
2561
|
async function logoutCommand(config) {
|
|
1895
2562
|
const forgot = await deleteTokens(config.portalUrl);
|
|
@@ -1897,7 +2564,8 @@ async function logoutCommand(config) {
|
|
|
1897
2564
|
}
|
|
1898
2565
|
async function whoamiCommand(client) {
|
|
1899
2566
|
const me = await client.me();
|
|
1900
|
-
|
|
2567
|
+
const oid = me.oid ? `${me.oid} \u2014 ` : "";
|
|
2568
|
+
console.log(`${oid}${me.sub} (via ${me.via}${me.name ? `, ${me.name}` : ""})`);
|
|
1901
2569
|
}
|
|
1902
2570
|
async function skillCommand(client, _config, opts) {
|
|
1903
2571
|
const skill = await client.getSkill();
|
|
@@ -1971,7 +2639,8 @@ async function resolveConfig(flags, env = process.env, cwd = process.cwd()) {
|
|
|
1971
2639
|
token: flags.token ?? env.HELIX_TOKEN ?? env.AZX_TOKEN,
|
|
1972
2640
|
slug: flags.slug ?? file.slug,
|
|
1973
2641
|
dir: flags.dir ?? file.dir ?? DEFAULT_DIR,
|
|
1974
|
-
bundle: flags.bundle
|
|
2642
|
+
bundle: flags.bundle,
|
|
2643
|
+
description: flags.description ?? file.description
|
|
1975
2644
|
};
|
|
1976
2645
|
}
|
|
1977
2646
|
|
|
@@ -1983,7 +2652,7 @@ Usage:
|
|
|
1983
2652
|
helix logout
|
|
1984
2653
|
helix whoami
|
|
1985
2654
|
helix deploy [--dir <dir>] [--bundle <zip>] [--promote]
|
|
1986
|
-
helix create [--display-name <name>] [--visibility <v>]
|
|
2655
|
+
helix create [--display-name <name>] [--description <text>] [--visibility <v>]
|
|
1987
2656
|
helix versions
|
|
1988
2657
|
helix promote <number>
|
|
1989
2658
|
helix rollback [number]
|
|
@@ -1991,7 +2660,7 @@ Usage:
|
|
|
1991
2660
|
|
|
1992
2661
|
Common flags: --slug <slug> --portal-url <url> --token <token>
|
|
1993
2662
|
Env: HELIX_PORTAL_URL, HELIX_TOKEN (static token \u2014 skips login; CI/scripts).
|
|
1994
|
-
Config file: helix.json { slug, portalUrl, dir }
|
|
2663
|
+
Config file: helix.json { slug, portalUrl, dir, description }
|
|
1995
2664
|
Visibility: internal | group:<id>[,<id>...] | password | public
|
|
1996
2665
|
`;
|
|
1997
2666
|
async function main() {
|
|
@@ -2031,6 +2700,7 @@ async function main() {
|
|
|
2031
2700
|
case "create":
|
|
2032
2701
|
await createCommand(client, config, {
|
|
2033
2702
|
displayName: values["display-name"],
|
|
2703
|
+
description: values.description ?? config.description,
|
|
2034
2704
|
visibility: values.visibility
|
|
2035
2705
|
});
|
|
2036
2706
|
break;
|