@varde-flyt/vfac 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +29 -5
  2. package/dist/vfac.mjs +1257 -373
  3. package/package.json +1 -1
package/dist/vfac.mjs CHANGED
@@ -7424,22 +7424,46 @@ function parseArgs(argv) {
7424
7424
  }
7425
7425
 
7426
7426
  // src/config.ts
7427
- import { chmodSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
7427
+ import { chmodSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
7428
7428
  import { homedir } from "node:os";
7429
7429
  import { dirname, join } from "node:path";
7430
+ var salvaged = null;
7431
+ function salvagedConfigPath() {
7432
+ return salvaged;
7433
+ }
7430
7434
  function configPath() {
7431
7435
  const base = process.env["VFAC_CONFIG_HOME"] ?? join(homedir(), ".config", "vfac");
7432
7436
  return join(base, "config.json");
7433
7437
  }
7434
7438
  function readStored(path = configPath()) {
7439
+ let raw;
7440
+ try {
7441
+ raw = readFileSync(path, "utf8");
7442
+ } catch {
7443
+ return {};
7444
+ }
7435
7445
  try {
7436
- const parsed = JSON.parse(readFileSync(path, "utf8"));
7437
- if (typeof parsed !== "object" || parsed === null) return {};
7446
+ const parsed = JSON.parse(raw);
7447
+ if (typeof parsed !== "object" || parsed === null) throw new Error("not an object");
7438
7448
  return parsed;
7439
7449
  } catch {
7450
+ salvage(path);
7440
7451
  return {};
7441
7452
  }
7442
7453
  }
7454
+ function salvage(path) {
7455
+ const kept = `${path}.corrupt`;
7456
+ try {
7457
+ readFileSync(kept, "utf8");
7458
+ return;
7459
+ } catch {
7460
+ }
7461
+ try {
7462
+ renameSync(path, kept);
7463
+ salvaged = kept;
7464
+ } catch {
7465
+ }
7466
+ }
7443
7467
  function writeStored(next, path = configPath()) {
7444
7468
  const directory = dirname(path);
7445
7469
  mkdirSync(directory, { recursive: true, mode: 448 });
@@ -11964,6 +11988,14 @@ var coerce = {
11964
11988
  };
11965
11989
  var NEVER = INVALID;
11966
11990
 
11991
+ // ../product-configuration/dist/configuration-bounds.js
11992
+ var MAX_CONFIGURATION_DEPTH = 2;
11993
+ var MAX_CONFIGURATION_LEAVES = 40;
11994
+ var MAX_CONFIGURATION_NODES = 60;
11995
+ var MAX_CONFIGURATION_VALUE_LEAVES = 1e3;
11996
+ var MAX_CONFIGURATION_ISSUES = 50;
11997
+ var MAX_CONFIGURATION_VALUE_BYTES = 16384;
11998
+
11967
11999
  // ../product-configuration/dist/json-schema-annex.js
11968
12000
  var ANNEXES = /* @__PURE__ */ new WeakMap();
11969
12001
  function withJsonSchemaAnnex(schema, annex) {
@@ -12263,12 +12295,49 @@ var ArrayPropertySchema = external_exports.object({
12263
12295
  uniqueItems: external_exports.boolean().optional(),
12264
12296
  default: external_exports.array(external_exports.string().max(200)).max(100).optional()
12265
12297
  }).strict();
12298
+ var MAX_CONFIGURATION_PROPERTIES = 40;
12266
12299
  var ConfigurationPropertySchema = external_exports.discriminatedUnion("type", [
12267
12300
  StringPropertySchema,
12268
12301
  NumberPropertySchema,
12269
12302
  BooleanPropertySchema,
12270
12303
  ArrayPropertySchema
12271
12304
  ]);
12305
+ var NestedStringPropertySchema = StringPropertySchema.omit({
12306
+ "x-secret": true,
12307
+ writeOnly: true
12308
+ });
12309
+ var NestedConfigurationPropertySchema = external_exports.discriminatedUnion("type", [
12310
+ NestedStringPropertySchema,
12311
+ NumberPropertySchema,
12312
+ BooleanPropertySchema,
12313
+ ArrayPropertySchema
12314
+ ]);
12315
+ var nestedPropertiesShape = {
12316
+ properties: external_exports.record(PropertyNameSchema, NestedConfigurationPropertySchema),
12317
+ required: external_exports.array(PropertyNameSchema).max(MAX_CONFIGURATION_PROPERTIES).optional(),
12318
+ additionalProperties: external_exports.literal(false)
12319
+ };
12320
+ var ObjectPropertySchema = external_exports.object({
12321
+ type: external_exports.literal("object"),
12322
+ title: TitleSchema,
12323
+ description: HelpTextSchema.optional(),
12324
+ group: ConfigurationGroupSchema,
12325
+ ...nestedPropertiesShape
12326
+ }).strict();
12327
+ var ObjectArrayItemsSchema = external_exports.object({
12328
+ type: external_exports.literal("object"),
12329
+ ...nestedPropertiesShape
12330
+ }).strict();
12331
+ var ArrayPropertyV2Schema = ArrayPropertySchema.extend({
12332
+ items: external_exports.discriminatedUnion("type", [ArrayItemsSchema, ObjectArrayItemsSchema])
12333
+ });
12334
+ var RootConfigurationPropertySchema = external_exports.discriminatedUnion("type", [
12335
+ StringPropertySchema,
12336
+ NumberPropertySchema,
12337
+ BooleanPropertySchema,
12338
+ ArrayPropertyV2Schema,
12339
+ ObjectPropertySchema
12340
+ ]);
12272
12341
  var CustomerConfigurationObjectSchema = external_exports.object({
12273
12342
  type: external_exports.literal("object"),
12274
12343
  properties: external_exports.record(PropertyNameSchema, ConfigurationPropertySchema).describe("Customer-editable settings, keyed by camelCase property name."),
@@ -12302,7 +12371,6 @@ var CustomerConfigurationObjectSchema = external_exports.object({
12302
12371
  */
12303
12372
  additionalProperties: external_exports.literal(false)
12304
12373
  }).strict();
12305
- var MAX_CONFIGURATION_PROPERTIES = 40;
12306
12374
  var CustomerConfigurationSchema = CustomerConfigurationObjectSchema.superRefine((config, ctx) => {
12307
12375
  const names = Object.keys(config.properties);
12308
12376
  if (names.length > MAX_CONFIGURATION_PROPERTIES) {
@@ -12314,182 +12382,192 @@ var CustomerConfigurationSchema = CustomerConfigurationObjectSchema.superRefine(
12314
12382
  }
12315
12383
  for (const name of names) {
12316
12384
  const property = config.properties[name];
12317
- const path = ["properties", name];
12318
- const infrastructure = infrastructureVocabularyViolation(name);
12319
- if (infrastructure) {
12385
+ checkDeclaredProperty(name, property, ["properties", name], ctx, true);
12386
+ }
12387
+ for (const name of config.required ?? []) {
12388
+ if (!Object.hasOwn(config.properties, name)) {
12320
12389
  ctx.addIssue({
12321
12390
  code: external_exports.ZodIssueCode.custom,
12322
- path,
12323
- message: `${infrastructure}. Customer settings describe outcomes, not infrastructure.`
12391
+ path: ["required"],
12392
+ message: `"${name}" is listed as required but is not a declared property.`
12324
12393
  });
12325
12394
  }
12395
+ }
12396
+ const duplicates = (config.required ?? []).filter((name, index, all) => all.indexOf(name) !== index);
12397
+ if (duplicates.length > 0) {
12398
+ ctx.addIssue({
12399
+ code: external_exports.ZodIssueCode.custom,
12400
+ path: ["required"],
12401
+ message: `Duplicate required entries: ${[...new Set(duplicates)].join(", ")}.`
12402
+ });
12403
+ }
12404
+ checkDependentRequired(config, ctx);
12405
+ });
12406
+ function checkDeclaredProperty(name, property, path, ctx, atRoot) {
12407
+ const infrastructure = infrastructureVocabularyViolation(name);
12408
+ if (infrastructure) {
12409
+ ctx.addIssue({
12410
+ code: external_exports.ZodIssueCode.custom,
12411
+ path,
12412
+ message: `${infrastructure}. Customer settings describe outcomes, not infrastructure.`
12413
+ });
12414
+ }
12415
+ if (atRoot) {
12326
12416
  const platformOwned = platformOwnedFieldViolation(name);
12327
12417
  if (platformOwned) {
12328
12418
  ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path, message: `${platformOwned}.` });
12329
12419
  }
12330
- for (const [label, values] of enumerableValues(property)) {
12331
- for (const value of values) {
12332
- const violation = infrastructureValueViolation(value);
12333
- if (violation) {
12334
- ctx.addIssue({
12335
- code: external_exports.ZodIssueCode.custom,
12336
- path: [...path, label],
12337
- message: `${violation}. Customer settings offer outcomes, not infrastructure sizes.`
12338
- });
12339
- }
12340
- }
12341
- }
12342
- if (property.type === "string") {
12343
- const secret = property["x-secret"] === true;
12344
- const credential = credentialNameViolation(name);
12345
- if (credential && !secret) {
12346
- ctx.addIssue({
12347
- code: external_exports.ZodIssueCode.custom,
12348
- path,
12349
- message: `${credential} and must be declared "x-secret": true with "writeOnly": true, or renamed if it is not one.`
12350
- });
12351
- }
12352
- if (secret && property.writeOnly !== true) {
12353
- ctx.addIssue({
12354
- code: external_exports.ZodIssueCode.custom,
12355
- path,
12356
- message: `"${name}" is marked x-secret but not writeOnly \u2014 a read API could echo the value.`
12357
- });
12358
- }
12359
- if (property.writeOnly === true && !secret) {
12360
- ctx.addIssue({
12361
- code: external_exports.ZodIssueCode.custom,
12362
- path,
12363
- message: `"${name}" is marked writeOnly but not x-secret \u2014 the value would not be routed to the secret flow.`
12364
- });
12365
- }
12366
- if (secret && property.default !== void 0) {
12367
- ctx.addIssue({
12368
- code: external_exports.ZodIssueCode.custom,
12369
- path: [...path, "default"],
12370
- message: `"${name}" is a secret and must not declare a default value.`
12371
- });
12372
- }
12373
- if (secret && property.enum !== void 0) {
12374
- ctx.addIssue({
12375
- code: external_exports.ZodIssueCode.custom,
12376
- path: [...path, "enum"],
12377
- message: `"${name}" is a secret; an enum would enumerate candidate secret values.`
12378
- });
12379
- }
12380
- if (property.enum === void 0 && property.maxLength === void 0) {
12381
- ctx.addIssue({
12382
- code: external_exports.ZodIssueCode.custom,
12383
- path,
12384
- message: `"${name}" must declare either an enum or a maxLength.`
12385
- });
12386
- }
12387
- if (property.enum !== void 0 && property.default !== void 0 && !property.enum.includes(property.default)) {
12388
- ctx.addIssue({
12389
- code: external_exports.ZodIssueCode.custom,
12390
- path: [...path, "default"],
12391
- message: `"${name}" declares a default outside its enum.`
12392
- });
12393
- }
12394
- if (property.minLength !== void 0 && property.maxLength !== void 0 && property.minLength > property.maxLength) {
12395
- ctx.addIssue({
12396
- code: external_exports.ZodIssueCode.custom,
12397
- path,
12398
- message: `"${name}" has minLength greater than maxLength.`
12399
- });
12400
- }
12401
- if (property.default !== void 0 && property.maxLength !== void 0 && property.default.length > property.maxLength) {
12402
- ctx.addIssue({
12403
- code: external_exports.ZodIssueCode.custom,
12404
- path: [...path, "default"],
12405
- message: `"${name}" declares a default longer than its own maxLength.`
12406
- });
12407
- }
12408
- if (property.pattern !== void 0 && !isSafeRegex(property.pattern)) {
12420
+ }
12421
+ for (const [label, values] of enumerableValues(property)) {
12422
+ for (const value of values) {
12423
+ const violation = infrastructureValueViolation(value);
12424
+ if (violation) {
12409
12425
  ctx.addIssue({
12410
12426
  code: external_exports.ZodIssueCode.custom,
12411
- path: [...path, "pattern"],
12412
- message: `"${name}" declares a pattern that is not a valid regular expression.`
12427
+ path: [...path, label],
12428
+ message: `${violation}. Customer settings offer outcomes, not infrastructure sizes.`
12413
12429
  });
12414
12430
  }
12415
12431
  }
12416
- if (property.type === "integer" || property.type === "number") {
12417
- if (property.minimum > property.maximum) {
12418
- ctx.addIssue({
12419
- code: external_exports.ZodIssueCode.custom,
12420
- path,
12421
- message: `"${name}" has minimum greater than maximum.`
12422
- });
12423
- }
12424
- if (property.default !== void 0 && (property.default < property.minimum || property.default > property.maximum)) {
12432
+ }
12433
+ if (property.type === "string") {
12434
+ const secret = property["x-secret"] === true;
12435
+ const credential = credentialNameViolation(name);
12436
+ if (credential && !secret) {
12437
+ ctx.addIssue({
12438
+ code: external_exports.ZodIssueCode.custom,
12439
+ path,
12440
+ // THE REMEDIATION HAS TO BE ONE THE CONTRACT ALLOWS. At the root, the
12441
+ // answer is to declare the two markers. Below it, that answer is
12442
+ // IMPOSSIBLE — the nested shape omits both keys and `.strict()` refuses
12443
+ // them — so telling a publisher to add them sends them to write a
12444
+ // second invalid document, and an agent following the validator loops.
12445
+ // A validator's errors are part of the agent-facing contract.
12446
+ message: atRoot ? `${credential} and must be declared "x-secret": true with "writeOnly": true, or renamed if it is not one.` : `${credential}, and a credential must be a TOP-LEVEL setting in this contract format \u2014 move it out of the group and declare it "x-secret": true with "writeOnly": true, or rename it if it is not one.`
12447
+ });
12448
+ }
12449
+ if (secret && property.writeOnly !== true) {
12450
+ ctx.addIssue({
12451
+ code: external_exports.ZodIssueCode.custom,
12452
+ path,
12453
+ message: `"${name}" is marked x-secret but not writeOnly \u2014 a read API could echo the value.`
12454
+ });
12455
+ }
12456
+ if (property.writeOnly === true && !secret) {
12457
+ ctx.addIssue({
12458
+ code: external_exports.ZodIssueCode.custom,
12459
+ path,
12460
+ message: `"${name}" is marked writeOnly but not x-secret \u2014 the value would not be routed to the secret flow.`
12461
+ });
12462
+ }
12463
+ if (secret && property.default !== void 0) {
12464
+ ctx.addIssue({
12465
+ code: external_exports.ZodIssueCode.custom,
12466
+ path: [...path, "default"],
12467
+ message: `"${name}" is a secret and must not declare a default value.`
12468
+ });
12469
+ }
12470
+ if (secret && property.enum !== void 0) {
12471
+ ctx.addIssue({
12472
+ code: external_exports.ZodIssueCode.custom,
12473
+ path: [...path, "enum"],
12474
+ message: `"${name}" is a secret; an enum would enumerate candidate secret values.`
12475
+ });
12476
+ }
12477
+ if (property.enum === void 0 && property.maxLength === void 0) {
12478
+ ctx.addIssue({
12479
+ code: external_exports.ZodIssueCode.custom,
12480
+ path,
12481
+ message: `"${name}" must declare either an enum or a maxLength.`
12482
+ });
12483
+ }
12484
+ if (property.enum !== void 0 && property.default !== void 0 && !property.enum.includes(property.default)) {
12485
+ ctx.addIssue({
12486
+ code: external_exports.ZodIssueCode.custom,
12487
+ path: [...path, "default"],
12488
+ message: `"${name}" declares a default outside its enum.`
12489
+ });
12490
+ }
12491
+ if (property.minLength !== void 0 && property.maxLength !== void 0 && property.minLength > property.maxLength) {
12492
+ ctx.addIssue({
12493
+ code: external_exports.ZodIssueCode.custom,
12494
+ path,
12495
+ message: `"${name}" has minLength greater than maxLength.`
12496
+ });
12497
+ }
12498
+ if (property.default !== void 0 && property.maxLength !== void 0 && property.default.length > property.maxLength) {
12499
+ ctx.addIssue({
12500
+ code: external_exports.ZodIssueCode.custom,
12501
+ path: [...path, "default"],
12502
+ message: `"${name}" declares a default longer than its own maxLength.`
12503
+ });
12504
+ }
12505
+ if (property.pattern !== void 0 && !isSafeRegex(property.pattern)) {
12506
+ ctx.addIssue({
12507
+ code: external_exports.ZodIssueCode.custom,
12508
+ path: [...path, "pattern"],
12509
+ message: `"${name}" declares a pattern that is not a valid regular expression.`
12510
+ });
12511
+ }
12512
+ }
12513
+ if (property.type === "integer" || property.type === "number") {
12514
+ if (property.minimum > property.maximum) {
12515
+ ctx.addIssue({
12516
+ code: external_exports.ZodIssueCode.custom,
12517
+ path,
12518
+ message: `"${name}" has minimum greater than maximum.`
12519
+ });
12520
+ }
12521
+ if (property.default !== void 0 && (property.default < property.minimum || property.default > property.maximum)) {
12522
+ ctx.addIssue({
12523
+ code: external_exports.ZodIssueCode.custom,
12524
+ path: [...path, "default"],
12525
+ message: `"${name}" declares a default outside its minimum/maximum range.`
12526
+ });
12527
+ }
12528
+ if (property.type === "integer" && !Number.isInteger(property.default ?? 0)) {
12529
+ ctx.addIssue({
12530
+ code: external_exports.ZodIssueCode.custom,
12531
+ path: [...path, "default"],
12532
+ message: `"${name}" is an integer but declares a fractional default.`
12533
+ });
12534
+ }
12535
+ }
12536
+ if (property.type === "array") {
12537
+ const allowed = property.items.enum;
12538
+ if (allowed === void 0 && property.items.maxLength === void 0) {
12539
+ ctx.addIssue({
12540
+ code: external_exports.ZodIssueCode.custom,
12541
+ path: [...path, "items"],
12542
+ message: `"${name}" items must declare either an enum or a maxLength.`
12543
+ });
12544
+ }
12545
+ if (property.minItems !== void 0 && property.minItems > property.maxItems) {
12546
+ ctx.addIssue({
12547
+ code: external_exports.ZodIssueCode.custom,
12548
+ path,
12549
+ message: `"${name}" has minItems greater than maxItems.`
12550
+ });
12551
+ }
12552
+ if (property.default !== void 0) {
12553
+ if (property.default.length > property.maxItems) {
12425
12554
  ctx.addIssue({
12426
12555
  code: external_exports.ZodIssueCode.custom,
12427
12556
  path: [...path, "default"],
12428
- message: `"${name}" declares a default outside its minimum/maximum range.`
12557
+ message: `"${name}" declares a default longer than maxItems.`
12429
12558
  });
12430
12559
  }
12431
- if (property.type === "integer" && !Number.isInteger(property.default ?? 0)) {
12560
+ const outside = allowed ? property.default.filter((value) => !allowed.includes(value)) : [];
12561
+ if (outside.length > 0) {
12432
12562
  ctx.addIssue({
12433
12563
  code: external_exports.ZodIssueCode.custom,
12434
12564
  path: [...path, "default"],
12435
- message: `"${name}" is an integer but declares a fractional default.`
12436
- });
12437
- }
12438
- }
12439
- if (property.type === "array") {
12440
- const allowed = property.items.enum;
12441
- if (allowed === void 0 && property.items.maxLength === void 0) {
12442
- ctx.addIssue({
12443
- code: external_exports.ZodIssueCode.custom,
12444
- path: [...path, "items"],
12445
- message: `"${name}" items must declare either an enum or a maxLength.`
12446
- });
12447
- }
12448
- if (property.minItems !== void 0 && property.minItems > property.maxItems) {
12449
- ctx.addIssue({
12450
- code: external_exports.ZodIssueCode.custom,
12451
- path,
12452
- message: `"${name}" has minItems greater than maxItems.`
12565
+ message: `"${name}" declares default values outside its items enum: ${outside.join(", ")}.`
12453
12566
  });
12454
12567
  }
12455
- if (property.default !== void 0) {
12456
- if (property.default.length > property.maxItems) {
12457
- ctx.addIssue({
12458
- code: external_exports.ZodIssueCode.custom,
12459
- path: [...path, "default"],
12460
- message: `"${name}" declares a default longer than maxItems.`
12461
- });
12462
- }
12463
- const outside = allowed ? property.default.filter((value) => !allowed.includes(value)) : [];
12464
- if (outside.length > 0) {
12465
- ctx.addIssue({
12466
- code: external_exports.ZodIssueCode.custom,
12467
- path: [...path, "default"],
12468
- message: `"${name}" declares default values outside its items enum: ${outside.join(", ")}.`
12469
- });
12470
- }
12471
- }
12472
12568
  }
12473
12569
  }
12474
- for (const name of config.required ?? []) {
12475
- if (!Object.hasOwn(config.properties, name)) {
12476
- ctx.addIssue({
12477
- code: external_exports.ZodIssueCode.custom,
12478
- path: ["required"],
12479
- message: `"${name}" is listed as required but is not a declared property.`
12480
- });
12481
- }
12482
- }
12483
- const duplicates = (config.required ?? []).filter((name, index, all) => all.indexOf(name) !== index);
12484
- if (duplicates.length > 0) {
12485
- ctx.addIssue({
12486
- code: external_exports.ZodIssueCode.custom,
12487
- path: ["required"],
12488
- message: `Duplicate required entries: ${[...new Set(duplicates)].join(", ")}.`
12489
- });
12490
- }
12491
- checkDependentRequired(config, ctx);
12492
- });
12570
+ }
12493
12571
  function checkDependentRequired(config, ctx) {
12494
12572
  const clauses = Object.entries(config.dependentRequired ?? {});
12495
12573
  if (clauses.length === 0)
@@ -12611,29 +12689,400 @@ function checkOneSecretPerGroup(config, ctx) {
12611
12689
  });
12612
12690
  }
12613
12691
  }
12614
- function enumerableValues(property) {
12615
- const out = [];
12616
- if (property.type === "string") {
12617
- if (property.enum)
12618
- out.push(["enum", property.enum]);
12619
- if (property.default !== void 0)
12620
- out.push(["default", [property.default]]);
12692
+ var CustomerConfigurationV2ObjectSchema = external_exports.object({
12693
+ type: external_exports.literal("object"),
12694
+ properties: external_exports.record(PropertyNameSchema, RootConfigurationPropertySchema).describe("Customer-editable settings, keyed by camelCase property name."),
12695
+ required: external_exports.array(PropertyNameSchema).max(MAX_CONFIGURATION_PROPERTIES).optional(),
12696
+ /**
12697
+ * ROOT PROPERTIES ONLY, in v2.0.
12698
+ *
12699
+ * The published key promises that "a publisher running an off-the-shelf
12700
+ * validator over that file must get the answer the platform gives", and
12701
+ * JSON Schema 2020-12 `dependentRequired` is scoped to the object that
12702
+ * carries it. A clause naming a nested leaf would be a rule no standard
12703
+ * validator applies, which is the one thing this key may not become. A
12704
+ * nested object may not carry its own clause either — that is expressible
12705
+ * and honest, and it is deliberately left for when something needs it.
12706
+ */
12707
+ dependentRequired: external_exports.record(PropertyNameSchema, external_exports.array(PropertyNameSchema).min(1).max(MAX_CONFIGURATION_PROPERTIES)).optional().describe("Settings that must be supplied together, keyed by the triggering property."),
12708
+ additionalProperties: external_exports.literal(false)
12709
+ }).strict();
12710
+ function countNodes(property) {
12711
+ if (property.type === "object") {
12712
+ const children = Object.keys(property.properties).length;
12713
+ return { leaves: children, nodes: children + 1, instantiated: children };
12621
12714
  }
12622
- if (property.type === "array") {
12623
- if (property.items.enum)
12624
- out.push(["items", property.items.enum]);
12625
- if (property.default)
12626
- out.push(["default", property.default]);
12715
+ if (property.type === "array" && property.items.type === "object") {
12716
+ const children = Object.keys(property.items.properties).length;
12717
+ return {
12718
+ leaves: children,
12719
+ nodes: children + 1,
12720
+ instantiated: children * property.maxItems
12721
+ };
12627
12722
  }
12628
- return out;
12723
+ return { leaves: 1, nodes: 1, instantiated: 1 };
12629
12724
  }
12630
- function isSafeRegex(pattern) {
12631
- try {
12632
- new RegExp(pattern);
12633
- return true;
12634
- } catch {
12635
- return false;
12636
- }
12725
+ function childrenOf(property) {
12726
+ if (property.type === "object")
12727
+ return property;
12728
+ if (property.type === "array" && property.items.type === "object")
12729
+ return property.items;
12730
+ return null;
12731
+ }
12732
+ function minimumConfigurationBytes(config) {
12733
+ const rootRequired = new Set(config.required ?? []);
12734
+ let total = 2;
12735
+ for (const [name, property] of Object.entries(config.properties)) {
12736
+ const children = childrenOf(property);
12737
+ if (children === null) {
12738
+ if (!rootRequired.has(name) && declaredLeafDefault(property) === void 0)
12739
+ continue;
12740
+ total += name.length + 3 + minimumLeafBytes(property);
12741
+ continue;
12742
+ }
12743
+ if (!rootRequired.has(name))
12744
+ continue;
12745
+ const inner = minimumGroupBytes(children.properties, children.required ?? []);
12746
+ total += name.length + 3 + (property.type === "array" ? 2 + (property.minItems ?? 0) * inner : inner);
12747
+ }
12748
+ return total;
12749
+ }
12750
+ function minimumGroupBytes(properties, required) {
12751
+ const names = new Set(required);
12752
+ let total = 2;
12753
+ for (const [name, property] of Object.entries(properties)) {
12754
+ if (!names.has(name) && declaredLeafDefault(property) === void 0)
12755
+ continue;
12756
+ total += name.length + 3 + minimumLeafBytes(property);
12757
+ }
12758
+ return total;
12759
+ }
12760
+ function minimumLeafBytes(property) {
12761
+ switch (property.type) {
12762
+ case "string": {
12763
+ const shortestEnum = property.enum?.reduce((a, b) => a.length <= b.length ? a : b);
12764
+ const shortest = shortestEnum?.length ?? Math.max(property.minLength ?? 0, 1);
12765
+ return shortest + 2;
12766
+ }
12767
+ case "integer":
12768
+ case "number":
12769
+ return 1;
12770
+ case "boolean":
12771
+ return 4;
12772
+ case "array":
12773
+ return 2 + (property.minItems ?? 0) * 3;
12774
+ case "object":
12775
+ return 2;
12776
+ }
12777
+ }
12778
+ function declaredLeafDefault(property) {
12779
+ return property.type === "object" ? void 0 : property.default;
12780
+ }
12781
+ var CustomerConfigurationV2Schema = CustomerConfigurationV2ObjectSchema.superRefine((config, ctx) => {
12782
+ const names = Object.keys(config.properties);
12783
+ if (names.length > MAX_CONFIGURATION_PROPERTIES) {
12784
+ ctx.addIssue({
12785
+ code: external_exports.ZodIssueCode.custom,
12786
+ path: ["properties"],
12787
+ message: `A Product may declare at most ${MAX_CONFIGURATION_PROPERTIES} customer settings; found ${names.length}.`
12788
+ });
12789
+ }
12790
+ let leaves = 0;
12791
+ let nodes = 0;
12792
+ let instantiated = 0;
12793
+ for (const name of names) {
12794
+ const property = config.properties[name];
12795
+ const path = ["properties", name];
12796
+ const counted = countNodes(property);
12797
+ leaves += counted.leaves;
12798
+ nodes += counted.nodes;
12799
+ instantiated += counted.instantiated;
12800
+ const children = childrenOf(property);
12801
+ if (children === null) {
12802
+ checkDeclaredProperty(name, property, path, ctx, true);
12803
+ continue;
12804
+ }
12805
+ const infrastructure = infrastructureVocabularyViolation(name);
12806
+ if (infrastructure) {
12807
+ ctx.addIssue({
12808
+ code: external_exports.ZodIssueCode.custom,
12809
+ path,
12810
+ message: `${infrastructure}. Customer settings describe outcomes, not infrastructure.`
12811
+ });
12812
+ }
12813
+ const platformOwned = platformOwnedFieldViolation(name);
12814
+ if (platformOwned) {
12815
+ ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path, message: `${platformOwned}.` });
12816
+ }
12817
+ const childNames = Object.keys(children.properties);
12818
+ if (childNames.length === 0) {
12819
+ ctx.addIssue({
12820
+ code: external_exports.ZodIssueCode.custom,
12821
+ path: [...path, "properties"],
12822
+ message: `"${name}" declares no settings. A group with nothing in it renders as an empty section.`
12823
+ });
12824
+ }
12825
+ if (childNames.length > MAX_CONFIGURATION_PROPERTIES) {
12826
+ ctx.addIssue({
12827
+ code: external_exports.ZodIssueCode.custom,
12828
+ path: [...path, "properties"],
12829
+ message: `"${name}" may declare at most ${MAX_CONFIGURATION_PROPERTIES} settings; found ${childNames.length}.`
12830
+ });
12831
+ }
12832
+ if (property.type === "array" && property.default !== void 0) {
12833
+ ctx.addIssue({
12834
+ code: external_exports.ZodIssueCode.custom,
12835
+ path: [...path, "default"],
12836
+ message: `"${name}" is a list of groups and cannot declare a default. Declare defaults on the settings inside it.`
12837
+ });
12838
+ }
12839
+ if (property.type === "array" && property.minItems !== void 0 && property.minItems > property.maxItems) {
12840
+ ctx.addIssue({
12841
+ code: external_exports.ZodIssueCode.custom,
12842
+ path,
12843
+ message: `"${name}" has minItems greater than maxItems.`
12844
+ });
12845
+ }
12846
+ for (const childName of childNames) {
12847
+ checkDeclaredProperty(childName, children.properties[childName], [...path, "properties", childName], ctx, false);
12848
+ }
12849
+ for (const required of children.required ?? []) {
12850
+ if (!Object.hasOwn(children.properties, required)) {
12851
+ ctx.addIssue({
12852
+ code: external_exports.ZodIssueCode.custom,
12853
+ path: [...path, "required"],
12854
+ message: `"${required}" is listed as required in "${name}" but is not one of its settings.`
12855
+ });
12856
+ }
12857
+ }
12858
+ }
12859
+ if (leaves > MAX_CONFIGURATION_LEAVES) {
12860
+ ctx.addIssue({
12861
+ code: external_exports.ZodIssueCode.custom,
12862
+ path: ["properties"],
12863
+ message: `A Product may declare at most ${MAX_CONFIGURATION_LEAVES} customer settings in total; found ${leaves}.`
12864
+ });
12865
+ }
12866
+ if (nodes > MAX_CONFIGURATION_NODES) {
12867
+ ctx.addIssue({
12868
+ code: external_exports.ZodIssueCode.custom,
12869
+ path: ["properties"],
12870
+ message: `A Product may declare at most ${MAX_CONFIGURATION_NODES} configuration nodes; found ${nodes}.`
12871
+ });
12872
+ }
12873
+ if (instantiated > MAX_CONFIGURATION_VALUE_LEAVES) {
12874
+ ctx.addIssue({
12875
+ code: external_exports.ZodIssueCode.custom,
12876
+ path: ["properties"],
12877
+ message: `This Product's settings could expand to ${instantiated} values \u2014 a list of groups instantiates its settings once per item. At most ${MAX_CONFIGURATION_VALUE_LEAVES} are allowed; reduce maxItems, or the number of settings inside the list.`
12878
+ });
12879
+ }
12880
+ for (const name of config.required ?? []) {
12881
+ if (!Object.hasOwn(config.properties, name)) {
12882
+ ctx.addIssue({
12883
+ code: external_exports.ZodIssueCode.custom,
12884
+ path: ["required"],
12885
+ message: `"${name}" is listed as required but is not a declared property.`
12886
+ });
12887
+ }
12888
+ }
12889
+ const floor = minimumConfigurationBytes(config);
12890
+ if (floor > MAX_CONFIGURATION_VALUE_BYTES) {
12891
+ ctx.addIssue({
12892
+ code: external_exports.ZodIssueCode.custom,
12893
+ path: ["properties"],
12894
+ message: `The smallest configuration that could satisfy this Product is at least ${floor} bytes, and the limit is ${MAX_CONFIGURATION_VALUE_BYTES}. No customer could deploy it \u2014 reduce the required settings, their minimum lengths, or a list's minItems.`
12895
+ });
12896
+ }
12897
+ checkDependentRequired(config, ctx);
12898
+ checkOneSecretPerGroup(config, ctx);
12899
+ });
12900
+ function enumerableValues(property) {
12901
+ const out = [];
12902
+ if (property.type === "string") {
12903
+ if (property.enum)
12904
+ out.push(["enum", property.enum]);
12905
+ if (property.default !== void 0)
12906
+ out.push(["default", [property.default]]);
12907
+ }
12908
+ if (property.type === "array") {
12909
+ if (property.items.enum)
12910
+ out.push(["items", property.items.enum]);
12911
+ if (property.default)
12912
+ out.push(["default", property.default]);
12913
+ }
12914
+ return out;
12915
+ }
12916
+ function isSafeRegex(pattern) {
12917
+ try {
12918
+ new RegExp(pattern);
12919
+ return true;
12920
+ } catch {
12921
+ return false;
12922
+ }
12923
+ }
12924
+ function isSecretProperty(property) {
12925
+ return property.type === "string" && property["x-secret"] === true;
12926
+ }
12927
+
12928
+ // ../product-configuration/dist/configuration-path.js
12929
+ var MAX_CONFIGURATION_PATH_SEGMENTS = MAX_CONFIGURATION_DEPTH + 1;
12930
+ function isIndexSegment(raw) {
12931
+ return /^\d+$/.test(raw) && String(Number(raw)) === raw;
12932
+ }
12933
+ function isPathSegment(segment) {
12934
+ if (typeof segment === "number")
12935
+ return Number.isInteger(segment) && segment >= 0;
12936
+ return CONFIGURATION_PROPERTY_NAME_REGEX.test(segment) || isIndexSegment(segment);
12937
+ }
12938
+ function configurationPath(...segments) {
12939
+ if (segments.length === 0 || segments.length > MAX_CONFIGURATION_PATH_SEGMENTS)
12940
+ return null;
12941
+ if (!segments.every(isPathSegment))
12942
+ return null;
12943
+ if (typeof segments[0] === "number" || isIndexSegment(String(segments[0])))
12944
+ return null;
12945
+ if (segments.length === 1)
12946
+ return String(segments[0]);
12947
+ return `/${segments.map(String).join("/")}`;
12948
+ }
12949
+ function parseConfigurationPath(raw) {
12950
+ if (typeof raw !== "string" || raw.length === 0)
12951
+ return null;
12952
+ if (!raw.startsWith("/"))
12953
+ return configurationPath(raw);
12954
+ const segments = raw.slice(1).split("/");
12955
+ if (segments.length < 2)
12956
+ return null;
12957
+ return configurationPath(...segments);
12958
+ }
12959
+ function pathSegments(path) {
12960
+ const raw = path.startsWith("/") ? path.slice(1).split("/") : [path];
12961
+ return raw.map((segment) => isIndexSegment(segment) ? Number(segment) : segment);
12962
+ }
12963
+ function declarationPathOf(path) {
12964
+ const segments = pathSegments(path).filter((segment) => typeof segment !== "number");
12965
+ return configurationPath(...segments) ?? path;
12966
+ }
12967
+ function printableConfigurationPath(path) {
12968
+ return pathSegments(path).join(".");
12969
+ }
12970
+
12971
+ // ../product-configuration/dist/canonical-json.js
12972
+ function canonicalise(value, path) {
12973
+ if (value === null)
12974
+ return "null";
12975
+ if (typeof value === "string")
12976
+ return JSON.stringify(value);
12977
+ if (typeof value === "boolean")
12978
+ return value ? "true" : "false";
12979
+ if (typeof value === "number") {
12980
+ if (!Number.isFinite(value)) {
12981
+ throw new Error(`canonicalJson: non-finite number at ${path}`);
12982
+ }
12983
+ return JSON.stringify(value === 0 ? 0 : value);
12984
+ }
12985
+ if (typeof value !== "object") {
12986
+ throw new Error(`canonicalJson: ${typeof value} is not JSON at ${path}`);
12987
+ }
12988
+ if (Array.isArray(value)) {
12989
+ return `[${value.map((item, i) => canonicalise(item, `${path}[${i}]`)).join(",")}]`;
12990
+ }
12991
+ const entries = Object.entries(value);
12992
+ for (const [key, item] of entries) {
12993
+ if (item === void 0) {
12994
+ throw new Error(`canonicalJson: undefined property at ${path}.${key}`);
12995
+ }
12996
+ }
12997
+ const sorted = entries.sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
12998
+ const body = sorted.map(([key, item]) => `${JSON.stringify(key)}:${canonicalise(item, `${path}.${key}`)}`).join(",");
12999
+ return `{${body}}`;
13000
+ }
13001
+ function canonicalJson(value) {
13002
+ return canonicalise(value, "$");
13003
+ }
13004
+
13005
+ // ../product-configuration/dist/configuration-tree.js
13006
+ function containerChildren(property) {
13007
+ if (property.type === "object" && property.properties !== void 0) {
13008
+ return {
13009
+ kind: "object",
13010
+ properties: property.properties,
13011
+ required: property.required ?? []
13012
+ };
13013
+ }
13014
+ if (property.type === "array" && property.items !== void 0) {
13015
+ const items = property.items;
13016
+ if (items.type !== "object" || items.properties === void 0)
13017
+ return null;
13018
+ return {
13019
+ kind: "array",
13020
+ properties: items.properties,
13021
+ required: items.required ?? []
13022
+ };
13023
+ }
13024
+ return null;
13025
+ }
13026
+ function isConfigurationContainer(property) {
13027
+ return containerChildren(property) !== null;
13028
+ }
13029
+ function configurationLeaves(schema) {
13030
+ const out = [];
13031
+ for (const [name, property] of Object.entries(schema.properties)) {
13032
+ const children = containerChildren(property);
13033
+ if (children === null) {
13034
+ const path = configurationPath(name);
13035
+ if (path !== null) {
13036
+ out.push({ path, name, property, container: null });
13037
+ }
13038
+ continue;
13039
+ }
13040
+ const containerPath = configurationPath(name);
13041
+ if (containerPath === null)
13042
+ continue;
13043
+ const container = {
13044
+ path: containerPath,
13045
+ name,
13046
+ kind: children.kind,
13047
+ property,
13048
+ required: children.required
13049
+ };
13050
+ for (const [childName, child] of Object.entries(children.properties)) {
13051
+ const path = configurationPath(name, childName);
13052
+ if (path === null)
13053
+ continue;
13054
+ out.push({ path, name: childName, property: child, container });
13055
+ }
13056
+ }
13057
+ return out;
13058
+ }
13059
+ function configurationContainers(schema) {
13060
+ const out = [];
13061
+ for (const [name, property] of Object.entries(schema.properties)) {
13062
+ const children = containerChildren(property);
13063
+ if (children === null)
13064
+ continue;
13065
+ const path = configurationPath(name);
13066
+ if (path === null)
13067
+ continue;
13068
+ out.push({
13069
+ path,
13070
+ name,
13071
+ kind: children.kind,
13072
+ property,
13073
+ required: children.required
13074
+ });
13075
+ }
13076
+ return out;
13077
+ }
13078
+ function leafAt(schema, path) {
13079
+ const declaration = declarationPathOf(path);
13080
+ return configurationLeaves(schema).find((leaf) => leaf.path === declaration) ?? null;
13081
+ }
13082
+ function declaresPath(schema, path) {
13083
+ if (leafAt(schema, path) !== null)
13084
+ return true;
13085
+ return configurationContainers(schema).some((container) => container.path === declarationPathOf(path));
12637
13086
  }
12638
13087
 
12639
13088
  // ../product-configuration/dist/customer-configuration-input.js
@@ -12642,11 +13091,24 @@ var MAX_REPORTED_UNDECLARED_KEYS = 10;
12642
13091
  function isPlainObject(value) {
12643
13092
  return typeof value === "object" && value !== null && !Array.isArray(value);
12644
13093
  }
12645
- function isSecretProperty(property) {
12646
- return property.type === "string" && property["x-secret"] === true;
13094
+ function report(walk, field, message) {
13095
+ if (walk.issues.length >= MAX_CONFIGURATION_ISSUES) {
13096
+ walk.suppressed += 1;
13097
+ return;
13098
+ }
13099
+ walk.issues.push({ field, message });
13100
+ }
13101
+ function drain(walk, collected) {
13102
+ for (const issue of collected)
13103
+ report(walk, issue.field, issue.message);
12647
13104
  }
12648
13105
  function validateSubmittedConfiguration(schema, submitted, presence = {}) {
12649
- const issues = [];
13106
+ const walk = {
13107
+ issues: [],
13108
+ suppressed: 0,
13109
+ leaves: 0,
13110
+ referenced: new Set(presence.referenced ?? [])
13111
+ };
12650
13112
  const supplied = /* @__PURE__ */ new Set();
12651
13113
  if (submitted !== void 0 && submitted !== null && !isPlainObject(submitted)) {
12652
13114
  return {
@@ -12656,64 +13118,195 @@ function validateSubmittedConfiguration(schema, submitted, presence = {}) {
12656
13118
  };
12657
13119
  }
12658
13120
  const input = isPlainObject(submitted) ? submitted : {};
12659
- const undeclared = Object.keys(input).filter((name) => !Object.hasOwn(schema.properties, name));
12660
- for (const name of undeclared.slice(0, MAX_REPORTED_UNDECLARED_KEYS)) {
12661
- issues.push({
12662
- field: CONFIGURATION_PROPERTY_NAME_REGEX.test(name) ? name : "",
12663
- message: "This Product does not declare a setting with that name."
12664
- });
12665
- }
12666
- if (undeclared.length > MAX_REPORTED_UNDECLARED_KEYS) {
12667
- issues.push({
13121
+ const secrets = {};
13122
+ const configuration = validateObject({ properties: schema.properties, required: schema.required }, input, null, walk, { secrets, supplied });
13123
+ drain(walk, crossFieldIssues(schema, supplied, presence));
13124
+ if (walk.suppressed > 0) {
13125
+ walk.issues.push({
12668
13126
  field: "",
12669
- // A COUNT, never the names. The count is the actionable part and it
12670
- // cannot carry anything the caller chose.
12671
- message: `${undeclared.length - MAX_REPORTED_UNDECLARED_KEYS} further settings are not declared by this Product.`
13127
+ message: `${walk.suppressed} further problems were found and are not listed.`
12672
13128
  });
12673
13129
  }
12674
- const required = new Set(schema.required ?? []);
12675
- const configuration = {};
12676
- const secrets = {};
12677
- for (const [name, property] of Object.entries(schema.properties)) {
12678
- const blank = typeof input[name] === "string" && input[name].trim().length === 0;
12679
- const present = Object.hasOwn(input, name) && input[name] !== void 0 && !blank;
12680
- const secret = isSecretProperty(property);
12681
- if (present)
12682
- supplied.add(name);
13130
+ if (declaresContainer(schema)) {
13131
+ const size = utf8ByteLength(JSON.stringify(configuration));
13132
+ if (size > MAX_CONFIGURATION_VALUE_BYTES) {
13133
+ report(walk, "", `This configuration is ${size} bytes once defaults are applied, and the limit is ${MAX_CONFIGURATION_VALUE_BYTES}. Remove some items.`);
13134
+ }
13135
+ }
13136
+ if (walk.issues.length > 0)
13137
+ return { ok: false, issues: walk.issues, partial: configuration };
13138
+ return { ok: true, configuration, secrets };
13139
+ }
13140
+ function utf8ByteLength(value) {
13141
+ return new TextEncoder().encode(value).length;
13142
+ }
13143
+ function declaresContainer(schema) {
13144
+ return Object.values(schema.properties).some((property) => isConfigurationContainer(property));
13145
+ }
13146
+ function containsReferenced(walk, container) {
13147
+ if (walk.referenced.size === 0)
13148
+ return false;
13149
+ const parsedOuter = parseConfigurationPath(container);
13150
+ if (parsedOuter === null)
13151
+ return false;
13152
+ const outer = pathSegments(parsedOuter);
13153
+ for (const candidate of walk.referenced) {
13154
+ const parsed = parseConfigurationPath(candidate);
13155
+ if (parsed === null)
13156
+ continue;
13157
+ const inner = pathSegments(parsed);
13158
+ if (inner.length <= outer.length)
13159
+ continue;
13160
+ if (outer.every((segment, index) => inner[index] === segment))
13161
+ return true;
13162
+ }
13163
+ return false;
13164
+ }
13165
+ function validateObject(declaration, input, container, walk, root) {
13166
+ const required = new Set(declaration.required ?? []);
13167
+ const out = {};
13168
+ const fieldFor = (name) => {
13169
+ const path = container === null ? configurationPath(name) : configurationPath(...pathSegments(container), name);
13170
+ return path ?? "";
13171
+ };
13172
+ const undeclared = Object.keys(input).filter((name) => !Object.hasOwn(declaration.properties, name));
13173
+ for (const name of undeclared.slice(0, MAX_REPORTED_UNDECLARED_KEYS)) {
13174
+ report(walk, fieldFor(name), "This Product does not declare a setting with that name.");
13175
+ }
13176
+ if (undeclared.length > MAX_REPORTED_UNDECLARED_KEYS) {
13177
+ report(walk, container === null ? "" : container, `${undeclared.length - MAX_REPORTED_UNDECLARED_KEYS} further settings are not declared by this Product.`);
13178
+ }
13179
+ for (const [name, property] of Object.entries(declaration.properties)) {
13180
+ const field = fieldFor(name);
13181
+ const children = containerDeclaration(property);
13182
+ if (children !== null) {
13183
+ const value = input[name];
13184
+ const filled = validateContainer(name, property, children, value, field, walk);
13185
+ if (filled !== void 0)
13186
+ out[name] = filled;
13187
+ else if (required.has(name) && !containsReferenced(walk, field))
13188
+ report(walk, field, "This setting is required.");
13189
+ continue;
13190
+ }
13191
+ const leaf = property;
13192
+ if (walk.referenced.has(field)) {
13193
+ const sent = input[name];
13194
+ if (Object.hasOwn(input, name) && sent !== void 0) {
13195
+ report(walk, field, "This setting is answered by another Resource, so it cannot also be set here.");
13196
+ }
13197
+ continue;
13198
+ }
13199
+ const raw = input[name];
13200
+ const blank = typeof raw === "string" && raw.trim().length === 0;
13201
+ const present = Object.hasOwn(input, name) && raw !== void 0 && !blank;
13202
+ const secret = root !== null && isSecretProperty(leaf);
13203
+ if (present && root !== null && container === null)
13204
+ root.supplied.add(name);
12683
13205
  if (!present) {
12684
- const fallback = secret ? void 0 : defaultOf(property);
13206
+ const fallback = secret ? void 0 : defaultOf(leaf);
12685
13207
  if (fallback !== void 0) {
12686
- configuration[name] = fallback;
13208
+ walk.leaves += 1;
13209
+ out[name] = fallback;
12687
13210
  } else if (required.has(name)) {
12688
- issues.push({ field: name, message: "This setting is required." });
13211
+ report(walk, field, "This setting is required.");
12689
13212
  }
12690
13213
  continue;
12691
13214
  }
12692
- const value = input[name];
12693
- const before = issues.length;
12694
- const checked = checkProperty(name, property, value, issues);
12695
- if (issues.length !== before || checked === void 0)
13215
+ walk.leaves += 1;
13216
+ const collected = [];
13217
+ const checked = checkProperty(field, leaf, raw, collected);
13218
+ if (collected.length > 0 || checked === void 0) {
13219
+ drain(walk, collected);
12696
13220
  continue;
13221
+ }
12697
13222
  if (secret) {
12698
13223
  if (typeof checked === "string" && checked.length > 0)
12699
- secrets[name] = checked;
12700
- else if (required.has(name)) {
12701
- issues.push({ field: name, message: "This setting is required." });
12702
- }
13224
+ root.secrets[name] = checked;
13225
+ else if (required.has(name))
13226
+ report(walk, field, "This setting is required.");
12703
13227
  continue;
12704
13228
  }
12705
- configuration[name] = checked;
13229
+ out[name] = checked;
12706
13230
  }
12707
- issues.push(...crossFieldIssues(schema, supplied, presence));
12708
- if (issues.length > 0)
12709
- return { ok: false, issues, partial: configuration };
12710
- return { ok: true, configuration, secrets };
13231
+ return out;
13232
+ }
13233
+ function containerDeclaration(property) {
13234
+ if (property.type === "object" && property.properties !== void 0) {
13235
+ return {
13236
+ kind: "object",
13237
+ properties: property.properties,
13238
+ required: property.required,
13239
+ maxItems: 1
13240
+ };
13241
+ }
13242
+ if (property.type === "array" && property.items !== void 0) {
13243
+ const items = property.items;
13244
+ if (items.type !== "object" || items.properties === void 0)
13245
+ return null;
13246
+ return {
13247
+ kind: "array",
13248
+ properties: items.properties,
13249
+ required: items.required,
13250
+ maxItems: property.maxItems ?? 0,
13251
+ minItems: property.minItems,
13252
+ uniqueItems: property.uniqueItems === true
13253
+ };
13254
+ }
13255
+ return null;
13256
+ }
13257
+ function validateContainer(name, property, children, value, field, walk) {
13258
+ const path = parseConfigurationPath(field);
13259
+ if (children.kind === "object") {
13260
+ if (value === void 0)
13261
+ return void 0;
13262
+ if (!isPlainObject(value)) {
13263
+ report(walk, field, "Expected a group of settings.");
13264
+ return void 0;
13265
+ }
13266
+ const filled = validateObject(children, value, path, walk, null);
13267
+ return Object.keys(filled).length === 0 ? void 0 : filled;
13268
+ }
13269
+ if (value === void 0)
13270
+ return void 0;
13271
+ if (!Array.isArray(value)) {
13272
+ report(walk, field, "Expected a list.");
13273
+ return void 0;
13274
+ }
13275
+ if (value.length > children.maxItems) {
13276
+ report(walk, field, `At most ${children.maxItems} items are allowed.`);
13277
+ return void 0;
13278
+ }
13279
+ if (children.minItems !== void 0 && value.length < children.minItems) {
13280
+ report(walk, field, `At least ${children.minItems} items are required.`);
13281
+ return void 0;
13282
+ }
13283
+ const out = [];
13284
+ for (const [index, element] of value.entries()) {
13285
+ if (walk.leaves >= MAX_CONFIGURATION_VALUE_LEAVES) {
13286
+ report(walk, field, "This configuration has too many values.");
13287
+ break;
13288
+ }
13289
+ if (!isPlainObject(element)) {
13290
+ report(walk, `${field}`, `Item ${index + 1} is not a group of settings.`);
13291
+ continue;
13292
+ }
13293
+ const elementPath = path === null ? null : configurationPath(...pathSegments(path), index);
13294
+ out.push(validateObject(children, element, elementPath, walk, null));
13295
+ }
13296
+ if (children.uniqueItems === true) {
13297
+ const seen = new Set(out.map((entry) => canonicalJson(entry)));
13298
+ if (seen.size !== out.length) {
13299
+ report(walk, field, "Items must be unique.");
13300
+ return void 0;
13301
+ }
13302
+ }
13303
+ return out;
12711
13304
  }
12712
13305
  function crossFieldIssues(schema, supplied, presence) {
12713
13306
  const clauses = Object.entries(schema.dependentRequired ?? {});
12714
13307
  if (clauses.length === 0)
12715
13308
  return [];
12716
- const alsoPresent = new Set(presence.present ?? []);
13309
+ const alsoPresent = /* @__PURE__ */ new Set([...presence.present ?? [], ...presence.referenced ?? []]);
12717
13310
  const unknown = new Set(presence.unknown ?? []);
12718
13311
  const known = (name) => !unknown.has(name);
12719
13312
  const held = (name) => supplied.has(name) || alsoPresent.has(name);
@@ -12875,12 +13468,17 @@ function matches(pattern, value) {
12875
13468
  var RESOURCE_API_VERSION = "resources.vardeflyt.no/v1";
12876
13469
  var PRODUCT_INSTANCE_KIND = "ProductInstance";
12877
13470
 
13471
+ // ../product-configuration/dist/resource-key.js
13472
+ var RESOURCE_KEY_PATTERN = /^[a-z][a-z0-9-]{1,61}[a-z0-9]$/;
13473
+ var RESOURCE_KEY_RULE = "A resource key is 3\u201363 characters of lowercase letters, digits and hyphens, starting with a letter and ending with a letter or digit.";
13474
+ var ResourceKeySchema = external_exports.string().regex(RESOURCE_KEY_PATTERN, RESOURCE_KEY_RULE).describe(RESOURCE_KEY_RULE);
13475
+
12878
13476
  // ../product-configuration/dist/product-instance-example.js
12879
- function resolveExampleChoice(options, selected, declaredDefault) {
13477
+ function resolveExampleChoice(options, selected, declaredDefault2) {
12880
13478
  if (selected !== void 0)
12881
13479
  return options.includes(selected) ? { value: selected, issue: null } : { value: null, issue: "invalid" };
12882
- if (declaredDefault !== void 0 && options.includes(declaredDefault))
12883
- return { value: declaredDefault, issue: null };
13480
+ if (declaredDefault2 !== void 0 && options.includes(declaredDefault2))
13481
+ return { value: declaredDefault2, issue: null };
12884
13482
  if (options.length === 1)
12885
13483
  return { value: options[0], issue: null };
12886
13484
  return { value: null, issue: options.length === 0 ? "unavailable" : "required" };
@@ -12908,17 +13506,84 @@ function seedConfiguration(configuration) {
12908
13506
  requiredSecrets.push(name);
12909
13507
  continue;
12910
13508
  }
13509
+ const children = containerChildren2(declaration);
13510
+ if (children !== null) {
13511
+ if (!required.has(name))
13512
+ continue;
13513
+ if (children.kind === "object") {
13514
+ values[name] = seedGroup(children, name, open);
13515
+ continue;
13516
+ }
13517
+ const count = children.minItems ?? 0;
13518
+ values[name] = Array.from({ length: count }, () => seedGroup(children, name, open));
13519
+ if (count > 1)
13520
+ dedupeOpen(open);
13521
+ continue;
13522
+ }
12911
13523
  if (declaration.default !== void 0) {
12912
13524
  values[name] = declaration.default;
12913
13525
  continue;
12914
13526
  }
12915
13527
  if (required.has(name)) {
12916
13528
  values[name] = "";
12917
- open.push({ name, type: declaration.type ?? null });
13529
+ open.push({ name, label: name, type: declaration.type ?? null });
12918
13530
  }
12919
13531
  }
12920
13532
  return { values, open, requiredSecrets, groups };
12921
13533
  }
13534
+ function containerChildren2(declaration) {
13535
+ const node = declaration;
13536
+ if (node.type === "object" && node.properties !== void 0) {
13537
+ return {
13538
+ kind: "object",
13539
+ properties: node.properties,
13540
+ required: node.required ?? []
13541
+ };
13542
+ }
13543
+ if (node.type === "array" && node.items !== void 0) {
13544
+ const items = node.items;
13545
+ if (items.type !== "object" || items.properties === void 0)
13546
+ return null;
13547
+ return {
13548
+ kind: "array",
13549
+ properties: items.properties,
13550
+ required: items.required ?? [],
13551
+ ...typeof node.minItems === "number" ? { minItems: node.minItems } : {}
13552
+ };
13553
+ }
13554
+ return null;
13555
+ }
13556
+ function dedupeOpen(open) {
13557
+ const seen = /* @__PURE__ */ new Set();
13558
+ const unique = open.filter((entry) => {
13559
+ if (seen.has(entry.name))
13560
+ return false;
13561
+ seen.add(entry.name);
13562
+ return true;
13563
+ });
13564
+ open.length = 0;
13565
+ open.push(...unique);
13566
+ }
13567
+ function seedGroup(children, container, open) {
13568
+ const required = new Set(children.required);
13569
+ const values = {};
13570
+ for (const [name, declaration] of Object.entries(children.properties)) {
13571
+ if (declaration.default !== void 0) {
13572
+ values[name] = declaration.default;
13573
+ continue;
13574
+ }
13575
+ if (required.has(name)) {
13576
+ values[name] = "";
13577
+ const path = configurationPath(container, name);
13578
+ open.push({
13579
+ name: path ?? name,
13580
+ label: path === null ? name : printableConfigurationPath(path),
13581
+ type: declaration.type ?? null
13582
+ });
13583
+ }
13584
+ }
13585
+ return values;
13586
+ }
12922
13587
  function productInstanceExample(input) {
12923
13588
  return {
12924
13589
  apiVersion: RESOURCE_API_VERSION,
@@ -12937,7 +13602,7 @@ function productInstanceExample(input) {
12937
13602
  }
12938
13603
  function exampleResourceKey(name) {
12939
13604
  const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^[^a-z]+/, "").replace(/-+$/, "").slice(0, 63);
12940
- return /^[a-z][a-z0-9-]{1,61}[a-z0-9]$/.test(slug) ? slug : "";
13605
+ return RESOURCE_KEY_PATTERN.test(slug) ? slug : "";
12941
13606
  }
12942
13607
 
12943
13608
  // ../platform-schemas/dist/index.js
@@ -13030,13 +13695,32 @@ var OperationStatusSchema = external_exports.enum([
13030
13695
 
13031
13696
  // ../product-contracts/dist/api-version.js
13032
13697
  var PRODUCT_API_VERSION = "products.vardeflyt.no/v1";
13698
+ var PRODUCT_API_VERSION_V2 = "products.vardeflyt.no/v2";
13699
+ var PARSEABLE_PRODUCT_API_VERSIONS = /* @__PURE__ */ new Set([
13700
+ PRODUCT_API_VERSION,
13701
+ PRODUCT_API_VERSION_V2
13702
+ ]);
13703
+ function supportedList() {
13704
+ return [...PARSEABLE_PRODUCT_API_VERSIONS].map((value) => `"${value}"`).join(" or ");
13705
+ }
13706
+ function unsupportedProductApiVersionMessage() {
13707
+ return `apiVersion must be ${supportedList()}. An unrecognized contract format is refused rather than ignored.`;
13708
+ }
13033
13709
  var PRODUCT_DEFINITION_KIND = "ProductDefinition";
13034
13710
  var DEPLOYMENT_BLUEPRINT_KIND = "DeploymentBlueprint";
13035
13711
  var ProductApiVersionSchema = external_exports.literal(PRODUCT_API_VERSION, {
13036
13712
  errorMap: () => ({
13037
- message: `apiVersion must be "${PRODUCT_API_VERSION}". An unrecognized contract format is refused rather than ignored.`
13713
+ message: `apiVersion must be ${supportedList()}. An unrecognized contract format is refused rather than ignored.`
13038
13714
  })
13039
13715
  });
13716
+ var ProductApiVersionV2Schema = external_exports.literal(PRODUCT_API_VERSION_V2, {
13717
+ errorMap: () => ({
13718
+ message: `apiVersion must be ${supportedList()}. An unrecognized contract format is refused rather than ignored.`
13719
+ })
13720
+ });
13721
+ var ProductApiVersionAnySchema = external_exports.union([ProductApiVersionSchema, ProductApiVersionV2Schema], {
13722
+ errorMap: () => ({ message: unsupportedProductApiVersionMessage() })
13723
+ }).describe("The Product contract format this document is written against.");
13040
13724
  var ResourceApiVersionSchema = external_exports.literal(RESOURCE_API_VERSION, {
13041
13725
  errorMap: () => ({
13042
13726
  message: `apiVersion must be "${RESOURCE_API_VERSION}". An unrecognized contract format is refused rather than ignored.`
@@ -13413,6 +14097,18 @@ var ProductConditionSchema = external_exports.object({
13413
14097
  */
13414
14098
  description: external_exports.string().min(10).max(300)
13415
14099
  }).strict();
14100
+ var RequiredWhenSchema = external_exports.object({
14101
+ /** A declared, non-secret customer-configuration property. */
14102
+ property: external_exports.string().regex(CONFIGURATION_PROPERTY_NAME_REGEX, "Must name a declared customer-configuration property."),
14103
+ /** The values that make this dependency required. Non-empty. */
14104
+ anyOf: external_exports.array(external_exports.string().min(1).max(80)).min(1).max(40)
14105
+ }).strict();
14106
+ var RequiredWhenV2Schema = external_exports.object({
14107
+ path: external_exports.string().refine((raw) => parseConfigurationPath(raw) !== null, {
14108
+ message: 'A configuration path is a root property name, or an RFC 6901 pointer such as "/assistant/model".'
14109
+ }),
14110
+ anyOf: external_exports.array(external_exports.string().min(1).max(80)).min(1).max(40)
14111
+ }).strict();
13416
14112
  var ProductDependencySchema = external_exports.object({
13417
14113
  /**
13418
14114
  * OPTIONAL ONLY WHEN `interface` IS PRESENT, enforced below.
@@ -13527,13 +14223,11 @@ var ProductDependencySchema = external_exports.object({
13527
14223
  * `.optional()`, never `.default()` — see the Blueprint's banner on
13528
14224
  * `runtime`. Every dependency published to date carries none.
13529
14225
  */
13530
- requiredWhen: external_exports.object({
13531
- /** A declared, non-secret customer-configuration property. */
13532
- property: external_exports.string().regex(CONFIGURATION_PROPERTY_NAME_REGEX, "Must name a declared customer-configuration property."),
13533
- /** The values that make this dependency required. Non-empty. */
13534
- anyOf: external_exports.array(external_exports.string().min(1).max(80)).min(1).max(40)
13535
- }).strict().optional()
14226
+ requiredWhen: RequiredWhenSchema.optional()
13536
14227
  }).strict();
14228
+ var ProductDependencyV2Schema = ProductDependencySchema.extend({
14229
+ requiredWhen: RequiredWhenV2Schema.optional()
14230
+ });
13537
14231
  var ServiceInterfaceProvideSchema = external_exports.object({
13538
14232
  key: ServiceBindingKeySchema,
13539
14233
  interfaceId: ServiceInterfaceIdSchema,
@@ -13670,7 +14364,14 @@ var ProductDefinitionObjectSchema = external_exports.object({
13670
14364
  */
13671
14365
  blueprint: external_exports.object({ version: ContractVersionSchema }).strict()
13672
14366
  }).strict();
13673
- var ProductDefinitionSchema = ProductDefinitionObjectSchema.superRefine((definition, ctx) => {
14367
+ var ProductDefinitionSchema = ProductDefinitionObjectSchema.superRefine(checkProductDefinition);
14368
+ var ProductDefinitionV2ObjectSchema = ProductDefinitionObjectSchema.extend({
14369
+ apiVersion: ProductApiVersionV2Schema,
14370
+ configuration: CustomerConfigurationV2Schema,
14371
+ dependencies: external_exports.array(ProductDependencyV2Schema).max(8)
14372
+ });
14373
+ var ProductDefinitionV2Schema = ProductDefinitionV2ObjectSchema.superRefine(checkProductDefinition);
14374
+ function checkProductDefinition(definition, ctx) {
13674
14375
  const profileIds = definition.profiles.map((profile) => profile.id);
13675
14376
  const duplicateProfiles = profileIds.filter((id, i) => profileIds.indexOf(id) !== i);
13676
14377
  if (duplicateProfiles.length > 0) {
@@ -13792,42 +14493,55 @@ var ProductDefinitionSchema = ProductDefinitionObjectSchema.superRefine((definit
13792
14493
  message: "Repeated values in `anyOf`."
13793
14494
  });
13794
14495
  }
13795
- const property = definition.configuration.properties[condition.property];
13796
- if (property === void 0) {
14496
+ const named = "path" in condition ? condition.path : condition.property;
14497
+ const member = "path" in condition ? "path" : "property";
14498
+ const parsed = parseConfigurationPath(named);
14499
+ const leaf = parsed === null ? null : leafAt(definition.configuration, parsed);
14500
+ if (leaf === null) {
14501
+ const isGroup = parsed !== null && declaresPath(definition.configuration, parsed);
14502
+ ctx.addIssue({
14503
+ code: external_exports.ZodIssueCode.custom,
14504
+ path: [...at, member],
14505
+ message: isGroup ? `"${named}" is a group of settings, which holds no single value to compare. Name one setting inside it.` : `"${named}" is not a declared customer configuration property.`
14506
+ });
14507
+ return;
14508
+ }
14509
+ if (leaf.container?.kind === "array") {
13797
14510
  ctx.addIssue({
13798
14511
  code: external_exports.ZodIssueCode.custom,
13799
- path: [...at, "property"],
13800
- message: `"${condition.property}" is not a declared customer configuration property.`
14512
+ path: [...at, member],
14513
+ message: `"${named}" is inside a list, so it names one value per item rather than one value. A condition must name a setting the instance holds exactly once.`
13801
14514
  });
13802
14515
  return;
13803
14516
  }
14517
+ const property = leaf.property;
13804
14518
  if (property.type !== "string") {
13805
14519
  ctx.addIssue({
13806
14520
  code: external_exports.ZodIssueCode.custom,
13807
- path: [...at, "property"],
13808
- message: `"${condition.property}" is a ${property.type} setting; a condition compares string values.`
14521
+ path: [...at, member],
14522
+ message: `"${named}" is a ${property.type} setting; a condition compares string values.`
13809
14523
  });
13810
14524
  return;
13811
14525
  }
13812
- if (property["x-secret"] === true) {
14526
+ if (isSecretProperty(property)) {
13813
14527
  ctx.addIssue({
13814
14528
  code: external_exports.ZodIssueCode.custom,
13815
- path: [...at, "property"],
13816
- message: `"${condition.property}" is a secret. The platform never reads a secret's value, so a condition on one could never be evaluated.`
14529
+ path: [...at, member],
14530
+ message: `"${named}" is a secret. The platform never reads a secret's value, so a condition on one could never be evaluated.`
13817
14531
  });
13818
14532
  return;
13819
14533
  }
13820
14534
  const single = withoutCrossFieldRules({
13821
14535
  ...definition.configuration,
13822
- properties: { [condition.property]: property },
13823
- required: [condition.property]
14536
+ properties: { [leaf.name]: property },
14537
+ required: [leaf.name]
13824
14538
  });
13825
- const outside = condition.anyOf.filter((value) => !validateSubmittedConfiguration(single, { [condition.property]: value }).ok);
14539
+ const outside = condition.anyOf.filter((value) => !validateSubmittedConfiguration(single, { [leaf.name]: value }).ok);
13826
14540
  if (outside.length > 0) {
13827
14541
  ctx.addIssue({
13828
14542
  code: external_exports.ZodIssueCode.custom,
13829
14543
  path: [...at, "anyOf"],
13830
- message: `"${condition.property}" never accepts ${outside.map((v) => `"${v}"`).join(", ")}, so this condition could never make the dependency required.`
14544
+ message: `"${named}" never accepts ${outside.map((v) => `"${v}"`).join(", ")}, so this condition could never make the dependency required.`
13831
14545
  });
13832
14546
  }
13833
14547
  });
@@ -13941,7 +14655,7 @@ var ProductDefinitionSchema = ProductDefinitionObjectSchema.superRefine((definit
13941
14655
  message: 'dataResidency "global" contradicts externalProcessing false \u2014 data cannot be global without leaving the tenant boundary.'
13942
14656
  });
13943
14657
  }
13944
- });
14658
+ }
13945
14659
 
13946
14660
  // ../product-contracts/dist/announcements.js
13947
14661
  var ANNOUNCED_PRODUCTS = Object.freeze([
@@ -14131,108 +14845,166 @@ var ComponentIngressSchema = external_exports.discriminatedUnion("exposure", [
14131
14845
  authentication: external_exports.enum(["signed-request", "mtls"])
14132
14846
  }).strict()
14133
14847
  ]);
14848
+ var LiteralEnvBindingSchema = external_exports.object({
14849
+ source: external_exports.literal("literal"),
14850
+ name: EnvVarNameSchema,
14851
+ value: external_exports.string().max(512)
14852
+ }).strict();
14853
+ var InstanceEnvBindingSchema = external_exports.object({
14854
+ source: external_exports.literal("instance"),
14855
+ name: EnvVarNameSchema,
14856
+ /** Closed set: the Control Plane knows these without Product knowledge. */
14857
+ field: external_exports.enum([
14858
+ "tenantKey",
14859
+ "tenantId",
14860
+ "projectId",
14861
+ "productInstanceId",
14862
+ "instanceKey",
14863
+ "productId",
14864
+ "productVersion",
14865
+ "profile",
14866
+ "region",
14867
+ "environment"
14868
+ ])
14869
+ }).strict();
14870
+ var PlatformEnvBindingSchema = external_exports.object({
14871
+ source: external_exports.literal("platform"),
14872
+ name: EnvVarNameSchema,
14873
+ key: external_exports.enum(["controlPlaneUrl", "observabilityEndpoint", "logLevel", "serviceVersion"])
14874
+ }).strict();
14875
+ var ComponentEndpointEnvBindingSchema = external_exports.object({
14876
+ source: external_exports.literal("component-endpoint"),
14877
+ name: EnvVarNameSchema,
14878
+ /** Resolved to the sibling component's private URL at reconcile time. */
14879
+ componentId: ComponentIdSchema
14880
+ }).strict();
14881
+ var ComponentPublicUrlEnvBindingSchema = external_exports.object({
14882
+ /**
14883
+ * The CUSTOMER-FACING address of a PUBLIC component — the URL a browser
14884
+ * opens, not the in-network one a sibling calls.
14885
+ *
14886
+ * WHY THE PLATFORM HAS TO SUPPLY IT. The address is assigned during
14887
+ * deployment, so nobody knows it earlier: not the publisher, who writes
14888
+ * the contract months before, and not the customer, who would otherwise
14889
+ * be asked to type an address that does not exist yet. A console that
14890
+ * needs its own address — to build an OAuth redirect URI, to put an
14891
+ * absolute link in an email — had no way to learn it, and asking the
14892
+ * customer produced exactly the failure you would expect: an empty
14893
+ * setting and a console nobody can sign in to.
14894
+ *
14895
+ * It is deliberately NOT derivable from the request. A server behind a
14896
+ * proxy infers its bind address, which looks right in development and is
14897
+ * wrong in production; a forwarded Host header is attacker-controlled.
14898
+ * The platform knows the answer, so the platform states it.
14899
+ *
14900
+ * `componentId` may name this component or another, but it must be a
14901
+ * PUBLIC one: `validateProductContracts` refuses a private target, the
14902
+ * same rule `outputs` already carries, because a private endpoint is not
14903
+ * an address to hand out.
14904
+ */
14905
+ source: external_exports.literal("component-public-url"),
14906
+ name: EnvVarNameSchema,
14907
+ componentId: ComponentIdSchema
14908
+ }).strict();
14909
+ var ServiceBindingAttributeEnvBindingSchema = external_exports.object({
14910
+ /**
14911
+ * A NON-SECRET value from a service binding to ANOTHER Product.
14912
+ *
14913
+ * THE ONLY SOURCE THAT LEAVES THIS PRODUCT INSTANCE. Every other member of
14914
+ * this union resolves inside one deployment: a literal, a fact about this
14915
+ * instance, a sibling component's address, a setting this customer typed.
14916
+ * This one is answered by a different Product Instance, possibly in a
14917
+ * different logical Project, which is why it is the only source whose
14918
+ * value can be absent for a reason that is nobody's mistake.
14919
+ *
14920
+ * ABSENT WHILE THE BINDING IS PENDING, and absent rather than empty. The
14921
+ * variable is left out of the container entirely until the provider
14922
+ * completes the binding, so a Product can tell "not yet" from "set to
14923
+ * nothing" — the same rule `configuration` already follows. Read
14924
+ * `VFAC_SERVICE_BINDING_<KEY>_STATUS`, which the platform injects for
14925
+ * every declared binding, rather than inferring readiness from absence.
14926
+ *
14927
+ * CREDENTIALS DO NOT COME THIS WAY. `field` must name a field the
14928
+ * interface classifies as an attribute; a secret field bound here is
14929
+ * refused by `validateProductContracts()`, and the credential-shaped-name
14930
+ * rule below refuses the variable name independently.
14931
+ */
14932
+ source: external_exports.literal("service-binding-attribute"),
14933
+ name: EnvVarNameSchema,
14934
+ /** The `serviceBindings[].key` this value comes from. */
14935
+ binding: ServiceBindingKeySchema,
14936
+ /** The interface field, e.g. `issuerUrl`. Checked against the registry. */
14937
+ field: external_exports.string().regex(/^[a-z][a-zA-Z0-9]{1,47}$/)
14938
+ }).strict();
14939
+ var ConfigurationEnvBindingSchema = external_exports.object({
14940
+ source: external_exports.literal("configuration"),
14941
+ name: EnvVarNameSchema,
14942
+ /**
14943
+ * A NON-SECRET customer setting from the Product Definition.
14944
+ * `validateProductContracts()` rejects a reference to an `x-secret`
14945
+ * property — those travel through `secrets[]` and Secret Manager, never
14946
+ * through plain environment wiring.
14947
+ */
14948
+ property: external_exports.string().regex(/^[a-z][a-zA-Z0-9]{1,47}$/)
14949
+ }).strict();
14134
14950
  var EnvBindingSchema = external_exports.discriminatedUnion("source", [
14951
+ LiteralEnvBindingSchema,
14952
+ InstanceEnvBindingSchema,
14953
+ PlatformEnvBindingSchema,
14954
+ ComponentEndpointEnvBindingSchema,
14955
+ ComponentPublicUrlEnvBindingSchema,
14956
+ ServiceBindingAttributeEnvBindingSchema,
14957
+ ConfigurationEnvBindingSchema
14958
+ ]);
14959
+ var ConfigurationBindingPathSchema = external_exports.string().refine((raw) => parseConfigurationPath(raw) !== null, {
14960
+ message: 'A configuration path is a root property name, or an RFC 6901 pointer such as "/assistant/model".'
14961
+ }).refine((raw) => !/\/\d+(\/|$)/.test(raw), {
14962
+ message: 'A Blueprint binds a DECLARED setting, so a path may not carry a list index. Bind the list with "configuration-json".'
14963
+ });
14964
+ var EnvBindingV2Schema = external_exports.discriminatedUnion("source", [
14965
+ LiteralEnvBindingSchema,
14966
+ InstanceEnvBindingSchema,
14967
+ PlatformEnvBindingSchema,
14968
+ ComponentEndpointEnvBindingSchema,
14969
+ ComponentPublicUrlEnvBindingSchema,
14970
+ ServiceBindingAttributeEnvBindingSchema,
14135
14971
  external_exports.object({
14136
- source: external_exports.literal("literal"),
14137
- name: EnvVarNameSchema,
14138
- value: external_exports.string().max(512)
14139
- }).strict(),
14140
- external_exports.object({
14141
- source: external_exports.literal("instance"),
14142
- name: EnvVarNameSchema,
14143
- /** Closed set: the Control Plane knows these without Product knowledge. */
14144
- field: external_exports.enum([
14145
- "tenantKey",
14146
- "tenantId",
14147
- "projectId",
14148
- "productInstanceId",
14149
- "instanceKey",
14150
- "productId",
14151
- "productVersion",
14152
- "profile",
14153
- "region",
14154
- "environment"
14155
- ])
14156
- }).strict(),
14157
- external_exports.object({
14158
- source: external_exports.literal("platform"),
14159
- name: EnvVarNameSchema,
14160
- key: external_exports.enum(["controlPlaneUrl", "observabilityEndpoint", "logLevel", "serviceVersion"])
14161
- }).strict(),
14162
- external_exports.object({
14163
- source: external_exports.literal("component-endpoint"),
14972
+ source: external_exports.literal("configuration"),
14164
14973
  name: EnvVarNameSchema,
14165
- /** Resolved to the sibling component's private URL at reconcile time. */
14166
- componentId: ComponentIdSchema
14167
- }).strict(),
14168
- external_exports.object({
14169
14974
  /**
14170
- * The CUSTOMER-FACING address of a PUBLIC component the URL a browser
14171
- * opens, not the in-network one a sibling calls.
14975
+ * A NON-SECRET customer setting, named by its canonical path.
14172
14976
  *
14173
- * WHY THE PLATFORM HAS TO SUPPLY IT. The address is assigned during
14174
- * deployment, so nobody knows it earlier: not the publisher, who writes
14175
- * the contract months before, and not the customer, who would otherwise
14176
- * be asked to type an address that does not exist yet. A console that
14177
- * needs its own address — to build an OAuth redirect URI, to put an
14178
- * absolute link in an email — had no way to learn it, and asking the
14179
- * customer produced exactly the failure you would expect: an empty
14180
- * setting and a console nobody can sign in to.
14181
- *
14182
- * It is deliberately NOT derivable from the request. A server behind a
14183
- * proxy infers its bind address, which looks right in development and is
14184
- * wrong in production; a forwarded Host header is attacker-controlled.
14185
- * The platform knows the answer, so the platform states it.
14186
- *
14187
- * `componentId` may name this component or another, but it must be a
14188
- * PUBLIC one: `validateProductContracts` refuses a private target, the
14189
- * same rule `outputs` already carries, because a private endpoint is not
14190
- * an address to hand out.
14977
+ * `validateProductContracts` refuses a path that names no leaf, a path
14978
+ * that names a GROUP a group has no scalar rendering, and
14979
+ * `configuration-json` is how one is carried and any `x-secret`
14980
+ * property, which travels through `secrets[]` and never through plain
14981
+ * environment wiring.
14191
14982
  */
14192
- source: external_exports.literal("component-public-url"),
14193
- name: EnvVarNameSchema,
14194
- componentId: ComponentIdSchema
14983
+ path: ConfigurationBindingPathSchema
14195
14984
  }).strict(),
14196
14985
  external_exports.object({
14986
+ source: external_exports.literal("configuration-json"),
14987
+ name: EnvVarNameSchema,
14197
14988
  /**
14198
- * A NON-SECRET value from a service binding to ANOTHER Product.
14989
+ * THE WHOLE VALIDATED CONFIGURATION, OR ONE SUBTREE OF IT, AS JSON.
14199
14990
  *
14200
- * THE ONLY SOURCE THAT LEAVES THIS PRODUCT INSTANCE. Every other member of
14201
- * this union resolves inside one deployment: a literal, a fact about this
14202
- * instance, a sibling component's address, a setting this customer typed.
14203
- * This one is answered by a different Product Instance, possibly in a
14204
- * different logical Project, which is why it is the only source whose
14205
- * value can be absent for a reason that is nobody's mistake.
14991
+ * WHY THIS EXISTS. A component may carry at most forty environment
14992
+ * bindings, and a nested Product spends one per leaf so the format that
14993
+ * lets a Product have structure would immediately run out of room to
14994
+ * deliver it. This binds a whole document instead, and the Product's own
14995
+ * runtime translates it into whatever shape it wants. The Control Plane
14996
+ * learns nothing about that shape, which is the point.
14206
14997
  *
14207
- * ABSENT WHILE THE BINDING IS PENDING, and absent rather than empty. The
14208
- * variable is left out of the container entirely until the provider
14209
- * completes the binding, so a Product can tell "not yet" from "set to
14210
- * nothing" the same rule `configuration` already follows. Read
14211
- * `VFAC_SERVICE_BINDING_<KEY>_STATUS`, which the platform injects for
14212
- * every declared binding, rather than inferring readiness from absence.
14998
+ * WHAT IT CARRIES: validated, non-secret configuration with resolved
14999
+ * ORDINARY resource references materialised in place. Never a secret
15000
+ * value secrets are root-only and travel through `secrets[]`, and a
15001
+ * subtree cannot contain one. Serialised with `canonicalJson`, so the same
15002
+ * logical configuration is the same bytes and the environment fingerprint
15003
+ * does not move on key order alone.
14213
15004
  *
14214
- * CREDENTIALS DO NOT COME THIS WAY. `field` must name a field the
14215
- * interface classifies as an attribute; a secret field bound here is
14216
- * refused by `validateProductContracts()`, and the credential-shaped-name
14217
- * rule below refuses the variable name independently.
15005
+ * Absent `path` means the whole document.
14218
15006
  */
14219
- source: external_exports.literal("service-binding-attribute"),
14220
- name: EnvVarNameSchema,
14221
- /** The `serviceBindings[].key` this value comes from. */
14222
- binding: ServiceBindingKeySchema,
14223
- /** The interface field, e.g. `issuerUrl`. Checked against the registry. */
14224
- field: external_exports.string().regex(/^[a-z][a-zA-Z0-9]{1,47}$/)
14225
- }).strict(),
14226
- external_exports.object({
14227
- source: external_exports.literal("configuration"),
14228
- name: EnvVarNameSchema,
14229
- /**
14230
- * A NON-SECRET customer setting from the Product Definition.
14231
- * `validateProductContracts()` rejects a reference to an `x-secret`
14232
- * property — those travel through `secrets[]` and Secret Manager, never
14233
- * through plain environment wiring.
14234
- */
14235
- property: external_exports.string().regex(/^[a-z][a-zA-Z0-9]{1,47}$/)
15007
+ path: ConfigurationBindingPathSchema.optional()
14236
15008
  }).strict()
14237
15009
  ]);
14238
15010
  var MAX_BOOTSTRAP_SCRIPT_LENGTH = 32768;
@@ -14444,11 +15216,12 @@ var ComponentDependencySchema = external_exports.object({
14444
15216
  minVersion: ContractVersionSchema.optional(),
14445
15217
  maxVersion: ContractVersionSchema.optional()
14446
15218
  }).strict();
15219
+ var MAX_COMPONENT_ENV_BINDINGS = 40;
14447
15220
  var componentCommonShape = {
14448
15221
  id: ComponentIdSchema.describe("Scoped by its Product; does not repeat the Product ID (\xA78)."),
14449
15222
  kind: ComponentKindSchema,
14450
15223
  description: external_exports.string().min(10).max(300),
14451
- env: external_exports.array(EnvBindingSchema).max(40),
15224
+ env: external_exports.array(EnvBindingSchema).max(MAX_COMPONENT_ENV_BINDINGS),
14452
15225
  dependsOn: external_exports.array(ComponentDependencySchema).max(8)
14453
15226
  };
14454
15227
  var ServerlessContainerComponentSchema = external_exports.object({
@@ -14500,6 +15273,16 @@ var RuntimeComponentSchema = external_exports.discriminatedUnion("runtime", [
14500
15273
  ServerlessContainerComponentSchema,
14501
15274
  VirtualMachineComponentSchema
14502
15275
  ]);
15276
+ var ServerlessContainerComponentV2Schema = ServerlessContainerComponentSchema.extend({
15277
+ env: external_exports.array(EnvBindingV2Schema).max(MAX_COMPONENT_ENV_BINDINGS)
15278
+ });
15279
+ var VirtualMachineComponentV2Schema = VirtualMachineComponentSchema.extend({
15280
+ env: external_exports.array(EnvBindingV2Schema).max(MAX_COMPONENT_ENV_BINDINGS)
15281
+ });
15282
+ var RuntimeComponentV2Schema = external_exports.discriminatedUnion("runtime", [
15283
+ ServerlessContainerComponentV2Schema,
15284
+ VirtualMachineComponentV2Schema
15285
+ ]);
14503
15286
  function isVirtualMachineComponent(component) {
14504
15287
  return component.runtime === "virtual-machine";
14505
15288
  }
@@ -14748,7 +15531,47 @@ var BlueprintLifecycleSchema = external_exports.object({
14748
15531
  * the service-interface surface is unchanged. Nothing published to date
14749
15532
  * regresses, and a release cannot strand an instance by forgetting it.
14750
15533
  */
14751
- directFromVersions: external_exports.array(ContractVersionSchema).max(20).optional()
15534
+ directFromVersions: external_exports.array(ContractVersionSchema).max(20).optional(),
15535
+ /**
15536
+ * How a stored configuration is carried across a release that MOVED a
15537
+ * setting.
15538
+ *
15539
+ * THE CASE THIS EXISTS FOR is a flat Product taking on structure:
15540
+ * `assistantName` becomes `/assistant/name`. Nothing else on this
15541
+ * platform can express that. Without it the target release simply does
15542
+ * not declare `assistantName`, so the upgrade drops the customer's value
15543
+ * and then refuses the deploy for a missing required setting — and the
15544
+ * customer's answer is gone either way.
15545
+ *
15546
+ * `move` AND NOTHING ELSE. A migration the platform cannot fully
15547
+ * evaluate is one it cannot apply safely, and every other operation a
15548
+ * publisher might want — rename a value, split one setting into two,
15549
+ * compute a default — needs the Product's own knowledge of what those
15550
+ * values MEAN. Moving a value is the one thing the platform can do
15551
+ * correctly without knowing anything about it.
15552
+ *
15553
+ * APPLIED AT CUTOVER, in the transaction that writes the new
15554
+ * configuration, and to the REFERENCE ROWS as well as the stored
15555
+ * document — a Resource Reference is identified by the path it answers,
15556
+ * so a setting that moved takes its edge with it or the edge points at
15557
+ * nothing.
15558
+ *
15559
+ * AN ABSENT SOURCE IS A NO-OP, deliberately. A customer who never
15560
+ * answered an optional setting has nothing to carry, and inventing an
15561
+ * empty value for them would turn "not configured" into "configured
15562
+ * blank" — two states this platform keeps apart everywhere else.
15563
+ *
15564
+ * `.optional()`, never `.default([])`, for the reason
15565
+ * `directFromVersions` states one field above: a materialised key makes
15566
+ * a byte-identical republish `RELEASED_CONTRACT_MUTATED`.
15567
+ */
15568
+ configurationMigrations: external_exports.array(external_exports.object({
15569
+ op: external_exports.literal("move"),
15570
+ /** Where the value is today, in the release being left. */
15571
+ from: ConfigurationBindingPathSchema,
15572
+ /** Where this release declares it. */
15573
+ to: ConfigurationBindingPathSchema
15574
+ }).strict()).max(40).optional()
14752
15575
  }).strict(),
14753
15576
  rollback: external_exports.object({
14754
15577
  supported: external_exports.boolean(),
@@ -14874,7 +15697,14 @@ function isOidcClientServiceBinding(binding) {
14874
15697
  }
14875
15698
  var DeploymentBlueprintObjectSchema = external_exports.object({
14876
15699
  /** The CONTRACT FORMAT version — not the Blueprint's. See api-version.ts. */
14877
- apiVersion: ProductApiVersionSchema,
15700
+ /**
15701
+ * The SHARED base carries either literal; each format pins its own below.
15702
+ *
15703
+ * Nothing parses this object schema directly — `DeploymentBlueprintV1Schema`
15704
+ * and `…V2Schema` extend it — so the permissive literal here is never what a
15705
+ * document is held to, and never what an artifact publishes.
15706
+ */
15707
+ apiVersion: ProductApiVersionAnySchema,
14878
15708
  kind: kindSchema(DEPLOYMENT_BLUEPRINT_KIND),
14879
15709
  productId: ProductIdSchema,
14880
15710
  /** This Blueprint's own version. The Definition pins it exactly. */
@@ -14942,7 +15772,17 @@ var DeploymentBlueprintObjectSchema = external_exports.object({
14942
15772
  outputs: external_exports.array(BlueprintOutputSchema).min(1).max(12),
14943
15773
  lifecycle: BlueprintLifecycleSchema
14944
15774
  }).strict();
14945
- var DeploymentBlueprintSchema = DeploymentBlueprintObjectSchema.superRefine((blueprint, ctx) => {
15775
+ var DeploymentBlueprintV1ObjectSchema = DeploymentBlueprintObjectSchema.extend({
15776
+ apiVersion: ProductApiVersionSchema
15777
+ });
15778
+ var DeploymentBlueprintV2ObjectSchema = DeploymentBlueprintObjectSchema.extend({
15779
+ apiVersion: ProductApiVersionV2Schema,
15780
+ components: external_exports.array(RuntimeComponentV2Schema).min(1).max(MAX_RUNTIME_COMPONENTS)
15781
+ });
15782
+ var DeploymentBlueprintV1Schema = DeploymentBlueprintV1ObjectSchema.superRefine(checkDeploymentBlueprint);
15783
+ var DeploymentBlueprintV2Schema = DeploymentBlueprintV2ObjectSchema.superRefine(checkDeploymentBlueprint);
15784
+ var DeploymentBlueprintSchema = DeploymentBlueprintV1Schema;
15785
+ function checkDeploymentBlueprint(blueprint, ctx) {
14946
15786
  const componentIds = blueprint.components.map((component) => component.id);
14947
15787
  const knownComponents = new Set(componentIds);
14948
15788
  const publicComponents = new Set(blueprint.components.filter((component) => component.ingress.exposure === "public").map((component) => component.id));
@@ -15043,13 +15883,7 @@ var DeploymentBlueprintSchema = DeploymentBlueprintObjectSchema.superRefine((blu
15043
15883
  });
15044
15884
  const handoff = resource.bootstrap.productHandoff;
15045
15885
  if (handoff !== void 0) {
15046
- const handoffPath = [
15047
- "dynamicResources",
15048
- index,
15049
- "bootstrap",
15050
- "productHandoff",
15051
- "componentId"
15052
- ];
15886
+ const handoffPath = ["dynamicResources", index, "bootstrap", "productHandoff", "componentId"];
15053
15887
  const target = blueprint.components.find((component) => component.id === handoff.componentId);
15054
15888
  if (target === void 0) {
15055
15889
  ctx.addIssue({
@@ -15660,7 +16494,7 @@ var DeploymentBlueprintSchema = DeploymentBlueprintObjectSchema.superRefine((blu
15660
16494
  });
15661
16495
  }
15662
16496
  }
15663
- });
16497
+ }
15664
16498
 
15665
16499
  // ../product-contracts/dist/deployability.js
15666
16500
  var VIRTUAL_MACHINE_PROFILE_INTERFACE = {
@@ -15701,17 +16535,15 @@ function isTerminalAction(action) {
15701
16535
  }
15702
16536
  var ManifestActionSchema = external_exports.enum(MANIFEST_ACTIONS);
15703
16537
 
15704
- // ../product-contracts/dist/resource-key.js
15705
- var ResourceKeySchema = external_exports.string().regex(/^[a-z][a-z0-9-]{1,61}[a-z0-9]$/, "A resource key is 3\u201363 characters of lowercase letters, digits and hyphens, starting with a letter and ending with a letter or digit.");
15706
-
15707
16538
  // ../product-contracts/dist/resource-output-reference.js
16539
+ var OUTPUT_KEY_RULE = "Output keys are camelCase.";
15708
16540
  var ResourceOutputReferenceSchema = external_exports.object({
15709
16541
  valueFrom: external_exports.object({
15710
16542
  resourceOutput: external_exports.object({
15711
16543
  /** The producing Resource's `metadata.key`, in the same Project. */
15712
16544
  resourceKey: ResourceKeySchema,
15713
16545
  /** The key of an output that Resource's Product declares. */
15714
- output: external_exports.string().regex(/^[a-z][a-zA-Z0-9]{1,47}$/, "Output keys are camelCase.")
16546
+ output: external_exports.string().regex(/^[a-z][a-zA-Z0-9]{1,47}$/, OUTPUT_KEY_RULE)
15715
16547
  }).strict()
15716
16548
  }).strict()
15717
16549
  }).strict();
@@ -15869,7 +16701,33 @@ var ProductSubmissionObjectSchema = external_exports.object({
15869
16701
  definition: ProductDefinitionSchema,
15870
16702
  blueprint: DeploymentBlueprintSchema
15871
16703
  }).strict();
15872
- var ProductSubmissionSchema = ProductSubmissionObjectSchema.superRefine((submission, ctx) => {
16704
+ var submissionShape = {
16705
+ kind: kindSchema(PRODUCT_SUBMISSION_KIND),
16706
+ submission: SubmissionEnvelopeSchema
16707
+ };
16708
+ var ProductSubmissionV1ObjectSchema = external_exports.object({
16709
+ apiVersion: ProductApiVersionSchema,
16710
+ ...submissionShape,
16711
+ definition: ProductDefinitionSchema,
16712
+ blueprint: DeploymentBlueprintV1Schema
16713
+ }).strict();
16714
+ var ProductSubmissionV2ObjectSchema = external_exports.object({
16715
+ apiVersion: ProductApiVersionV2Schema,
16716
+ ...submissionShape,
16717
+ definition: ProductDefinitionV2Schema,
16718
+ blueprint: DeploymentBlueprintV2Schema
16719
+ }).strict();
16720
+ var ProductSubmissionV1Schema = ProductSubmissionV1ObjectSchema.superRefine(checkProductSubmission);
16721
+ var ProductSubmissionV2Schema = ProductSubmissionV2ObjectSchema.superRefine(checkProductSubmission);
16722
+ var ProductSubmissionSchema = ProductSubmissionV1Schema;
16723
+ function checkProductSubmission(submission, ctx) {
16724
+ if (submission.definition.apiVersion !== submission.apiVersion) {
16725
+ ctx.addIssue({
16726
+ code: external_exports.ZodIssueCode.custom,
16727
+ path: ["definition", "apiVersion"],
16728
+ message: `This submission is written against "${submission.apiVersion}" but its Product Definition declares "${submission.definition.apiVersion}". One submission is one contract format.`
16729
+ });
16730
+ }
15873
16731
  if (submission.definition.id !== submission.submission.productId) {
15874
16732
  ctx.addIssue({
15875
16733
  code: external_exports.ZodIssueCode.custom,
@@ -15891,7 +16749,7 @@ var ProductSubmissionSchema = ProductSubmissionObjectSchema.superRefine((submiss
15891
16749
  message: `The Product Definition is version "${submission.definition.version}" but the submission claims "${submission.submission.version}". They must be the same version.`
15892
16750
  });
15893
16751
  }
15894
- });
16752
+ }
15895
16753
  var PUBLISH_RULE_ANNEX = {
15896
16754
  allOf: [
15897
16755
  {
@@ -16327,6 +17185,16 @@ async function runDoctor(parsed) {
16327
17185
  const json = parsed.flags["output"] === "json";
16328
17186
  const checks = [{ name: "CLI", ok: true, detail: `vfac ${cliVersion()}` }];
16329
17187
  const stored = readStored();
17188
+ const salvaged2 = salvagedConfigPath();
17189
+ if (salvaged2 !== null) {
17190
+ checks.push({
17191
+ name: "Config",
17192
+ ok: true,
17193
+ note: true,
17194
+ detail: `the previous config could not be read and was kept at ${salvaged2}. What this command reports below is a fresh one \u2014 the endpoint and Project it held are gone until you set them again. Delete the kept file once you have; it may hold a stale session.`,
17195
+ facts: { salvagedConfigPath: salvaged2 }
17196
+ });
17197
+ }
16330
17198
  const resolved = resolveEndpoint(parsed.flags["endpoint"], stored);
16331
17199
  if (!resolved) {
16332
17200
  checks.push({
@@ -16334,32 +17202,41 @@ async function runDoctor(parsed) {
16334
17202
  ok: false,
16335
17203
  detail: "not set. Run `vfac context set --endpoint https://your-organization.cloud.vardeflyt.no`, or set VFAC_ENDPOINT."
16336
17204
  });
16337
- return report(checks, json);
17205
+ return report2(checks, json);
16338
17206
  }
16339
17207
  const checked = validateEndpoint(resolved.value, resolved.source);
16340
17208
  if (!checked.ok) {
16341
17209
  checks.push({ name: "Endpoint", ok: false, detail: checked.message });
16342
- return report(checks, json);
17210
+ return report2(checks, json);
16343
17211
  }
16344
17212
  const endpoint = checked.endpoint;
16345
17213
  checks.push({
16346
17214
  name: "Endpoint",
16347
17215
  ok: true,
16348
- detail: `${endpoint} (from ${resolved.source})`
17216
+ detail: `${endpoint} (from ${resolved.source})`,
17217
+ facts: { endpoint, source: resolved.source }
17218
+ });
17219
+ const selected = stored.context?.projectId ?? null;
17220
+ checks.push({
17221
+ name: "Project",
17222
+ ok: true,
17223
+ note: selected === null,
17224
+ detail: selected === null ? "none selected. Commands need `--project prj_\u2026`, or run `vfac context set --project prj_\u2026` once. This is not a fault \u2014 it is what a pipeline that names the Project per command looks like \u2014 but it is NOT the same thing as which Projects your credential reaches." : `${selected} (selected in this context; which Projects the credential reaches is a separate question)`,
17225
+ facts: { selectedProjectId: selected }
16349
17226
  });
16350
17227
  const probe = await probeMachineApi({ endpoint });
16351
17228
  if (!probe.ok) {
16352
17229
  checks.push({ name: "Machine API", ok: false, detail: probe.message });
16353
- return report(checks, json);
17230
+ return report2(checks, json);
16354
17231
  }
16355
17232
  checks.push({ name: "Machine API", ok: true, detail: probe.apiVersion });
16356
17233
  const compatible = compatibility(cliVersion(), probe.minimumCliVersion);
16357
17234
  checks.push(compatible);
16358
- if (!compatible.ok) return report(checks, json);
17235
+ if (!compatible.ok) return report2(checks, json);
16359
17236
  const signedIn = await signIn({ endpoint });
16360
17237
  if (!signedIn.ok) {
16361
17238
  checks.push({ name: "Context Gate", ok: false, detail: signedIn.message });
16362
- return report(checks, json);
17239
+ return report2(checks, json);
16363
17240
  }
16364
17241
  checks.push({
16365
17242
  name: "Context Gate",
@@ -16380,7 +17257,7 @@ async function runDoctor(parsed) {
16380
17257
  ok: false,
16381
17258
  detail: who.error.status === 404 ? "this Tenant Portal does not serve /api/v1/whoami yet \u2014 it is running an older build." : who.error.message
16382
17259
  });
16383
- return report(checks, json);
17260
+ return report2(checks, json);
16384
17261
  }
16385
17262
  const access = who.data.access;
16386
17263
  const projects = access.projects.map((project) => project.projectId);
@@ -16391,7 +17268,8 @@ async function runDoctor(parsed) {
16391
17268
  access.tenantWide ? {
16392
17269
  name: "Access",
16393
17270
  ok: true,
16394
- detail: `the whole organization \u2014 ${access.tenantPermissions.join(", ")}`
17271
+ detail: `the whole organization \u2014 ${access.tenantPermissions.join(", ")}`,
17272
+ facts: { scope: "organization", grantedProjectIds: null }
16395
17273
  } : projects.length === 0 ? {
16396
17274
  // A FAILURE, NOT A NOTE. This credential authenticates and can do
16397
17275
  // nothing at all — and the check said so in words while `report`
@@ -16421,6 +17299,14 @@ async function runDoctor(parsed) {
16421
17299
  // believes it can deploy and is then refused reports a platform
16422
17300
  // defect, which is precisely the outcome the guide now tells it not
16423
17301
  // to reach for. Scope and role are two questions; this answers both.
17302
+ facts: {
17303
+ scope: "projects",
17304
+ // THE GRANT, and the name says which of the two questions it
17305
+ // answers. A reader gating on `doctor --output json` had no way
17306
+ // to tell this list from the selected Project; now the field
17307
+ // names are the difference.
17308
+ grantedProjectIds: projects.join(",")
17309
+ },
16424
17310
  detail: `${projects.join(", ")} \u2014 and nothing organization-wide. ` + (mutating.length > 0 ? "You can deploy in those Projects, read their catalogue, and list them. " : "Read-only there: catalogue, manifests, resources and operations. An apply or a lifecycle command is refused, and that is what was granted rather than a fault. ") + "Anything ACROSS the organization \u2014 a Project you hold nothing in, or its members \u2014 is refused."
16425
17311
  }
16426
17312
  );
@@ -16431,7 +17317,7 @@ async function runDoctor(parsed) {
16431
17317
  detail: "run `vfac guide` for the platform's own instructions, written for a machine"
16432
17318
  });
16433
17319
  checks.push(await updateAvailable(cliVersion()));
16434
- return report(checks, json);
17320
+ return report2(checks, json);
16435
17321
  }
16436
17322
  function compatibility(local, declared) {
16437
17323
  const name = "Compatibility";
@@ -16511,7 +17397,7 @@ async function updateAvailable(local) {
16511
17397
  facts
16512
17398
  };
16513
17399
  }
16514
- function report(checks, json) {
17400
+ function report2(checks, json) {
16515
17401
  const failed = checks.filter((check) => !check.ok);
16516
17402
  if (json) {
16517
17403
  process.stdout.write(`${JSON.stringify({ ok: failed.length === 0, checks })}
@@ -16887,16 +17773,14 @@ function renderProduct(product) {
16887
17773
  (profile) => `${profile.id}${profile.default ? " (default)" : ""}`
16888
17774
  );
16889
17775
  lines.push(` profile ${profiles.join(", ") || "(none declared)"}`);
16890
- const properties = product.configuration?.properties ?? {};
16891
- const required = product.configuration?.required ?? [];
16892
- if (required.length > 0) {
17776
+ const seed = seedConfiguration(product.configuration);
17777
+ if (seed.open.length > 0 || seed.requiredSecrets.length > 0) {
16893
17778
  lines.push("", " Required configuration");
16894
- for (const name of required) {
16895
- const property = properties[name];
16896
- const secret = property?.writeOnly === true || property?.["x-secret"] === true;
16897
- lines.push(
16898
- ` ${name}${secret ? " (secret \u2014 pass --secret " + name + "=env:VAR)" : ` ${property?.type ?? ""}`}`
16899
- );
17779
+ for (const name of seed.requiredSecrets) {
17780
+ lines.push(` ${name} (secret \u2014 pass --secret ${name}=env:VAR)`);
17781
+ }
17782
+ for (const setting of seed.open) {
17783
+ lines.push(` ${setting.label} ${setting.type ?? ""}`.trimEnd());
16900
17784
  }
16901
17785
  }
16902
17786
  const needed = (product.dependencies ?? []).filter((dependency) => dependency.required);
@@ -17129,9 +18013,7 @@ async function runManifestInit(parsed) {
17129
18013
  const notes = [];
17130
18014
  const key = parsed.flags["key"] ?? exampleResourceKey(name);
17131
18015
  if (!key) {
17132
- notes.push(
17133
- "metadata.key \u2014 a stable identity, 3 to 63 characters, lower case. Required before apply"
17134
- );
18016
+ notes.push(`metadata.key \u2014 a stable identity, required before apply. ${RESOURCE_KEY_RULE}`);
17135
18017
  }
17136
18018
  const chooseable = (product.dependencies ?? []).filter(
17137
18019
  (dependency) => dependency.interface?.cardinality === "tenant-multiple"
@@ -17171,7 +18053,7 @@ async function runManifestInit(parsed) {
17171
18053
  }
17172
18054
  for (const setting of seed.open) {
17173
18055
  notes.push(
17174
- `spec.configuration.${setting.name} \u2014 required${setting.type ? ` (${setting.type})` : ""}`
18056
+ `spec.configuration.${setting.label} \u2014 required${setting.type ? ` (${setting.type})` : ""}`
17175
18057
  );
17176
18058
  }
17177
18059
  for (const group of seed.groups) {
@@ -17260,6 +18142,8 @@ Run \`vfac get product-instance pri_\u2026\` to see which ones this credential m
17260
18142
  if (command === "delete" && !parsed.booleans.has("yes")) {
17261
18143
  process.stderr.write(
17262
18144
  `delete removes ${instanceId} and everything it is serving. This cannot be undone.
18145
+ If it was deployed from a manifest, its \`metadata.key\` stays claimed: a replacement
18146
+ needs a different key, and re-applying the same file will answer GONE.
17263
18147
  Re-run with --yes to confirm: vfac lifecycle delete ${instanceId} --yes
17264
18148
  Or suspend it instead, which stops it and keeps it: \`vfac lifecycle suspend\`.
17265
18149
  `