@varde-flyt/vfac 0.14.1 → 0.15.2

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 +31 -9
  2. package/dist/vfac.mjs +1195 -387
  3. package/package.json +1 -1
package/dist/vfac.mjs CHANGED
@@ -12081,11 +12081,13 @@ function leafShapes(bounds) {
12081
12081
  * particular value could not say so, so `vfac plan`'s `requiredSecrets`
12082
12082
  * could not warn before a deploy, and the refusal arrived at apply.
12083
12083
  *
12084
- * ON A SECRET ONLY, and that is narrower than the mechanism could be. A
12085
- * value-conditioned requirement for an ORDINARY setting is a real gap too,
12086
- * but it changes what every door accepts for every property; this changes
12087
- * what `requiredSecrets` reports, which is one answer with one reader.
12088
- * Opening it wider is a separate decision with its own blast radius.
12084
+ * IT BEGAN ON A ROOT SECRET ONLY, and was widened twice, each time as a
12085
+ * decision of its own: to every ROOT setting, which the number shape below
12086
+ * records, and then to a setting in a GROUP at any depth, which is where a
12087
+ * credential belonging to one sign-in method actually lives. It is still
12088
+ * refused on a setting BELOW A LIST, structurally — the leaf set the chain
12089
+ * uses there does not declare the key — because a setting there has one
12090
+ * path per row, and a condition makes one setting required.
12089
12091
  */
12090
12092
  "x-required-when": ValueConditionSchema.optional(),
12091
12093
  /**
@@ -12109,11 +12111,12 @@ function leafShapes(bounds) {
12109
12111
  maximum: external_exports.number(),
12110
12112
  default: external_exports.number().optional(),
12111
12113
  /**
12112
- * WIDENED FROM SECRETS TO EVERY ROOT SETTING, which this shape's own
12113
- * comment on the string leaf used to record as a known gap: "a
12114
- * value-conditioned requirement for an ORDINARY setting is a real gap
12115
- * too". It is the same keyword, the same evaluator and the same publish
12116
- * rules; what changes is which settings may carry it.
12114
+ * WIDENED FROM SECRETS TO EVERY ROOT SETTING, which the string leaf's
12115
+ * comment used to record as a known gap: "a value-conditioned requirement
12116
+ * for an ORDINARY setting is a real gap too". It is the same keyword, the
12117
+ * same evaluator and the same publish rules; what changes is which
12118
+ * settings may carry it — and a setting in a group may too, since the
12119
+ * chain's group leaf set keeps this key and only its list set omits it.
12117
12120
  */
12118
12121
  "x-required-when": ValueConditionSchema.optional()
12119
12122
  }).strict();
@@ -12158,14 +12161,33 @@ var ConfigurationPropertySchema = external_exports.discriminatedUnion("type", [
12158
12161
  BooleanPropertySchema,
12159
12162
  ArrayPropertySchema
12160
12163
  ]);
12161
- var NestedStringPropertySchema = V2_LEAVES.string.omit({ "x-secret": true, writeOnly: true, "x-required-when": true }).extend({ group: ConfigurationGroupSchema.optional() });
12162
- var V2_NESTED_LEAVES = {
12163
- string: NestedStringPropertySchema,
12164
- number: V2_LEAVES.number.omit({ "x-required-when": true }).extend({ group: ConfigurationGroupSchema.optional() }),
12165
- boolean: V2_LEAVES.boolean.omit({ "x-required-when": true }).extend({ group: ConfigurationGroupSchema.optional() }),
12166
- array: V2_LEAVES.array.omit({ "x-required-when": true }).extend({ group: ConfigurationGroupSchema.optional() }),
12164
+ var INHERITED_GROUP = { group: ConfigurationGroupSchema.optional() };
12165
+ var V2_GROUP_LEAVES = {
12166
+ string: withJsonSchemaAnnex(V2_LEAVES.string.extend(INHERITED_GROUP), SECRET_COUPLING_ANNEX),
12167
+ number: V2_LEAVES.number.extend(INHERITED_GROUP),
12168
+ boolean: V2_LEAVES.boolean.extend(INHERITED_GROUP),
12169
+ array: V2_LEAVES.array.extend(INHERITED_GROUP),
12167
12170
  items: V2_LEAVES.items
12168
12171
  };
12172
+ var V2_LIST_LEAVES = {
12173
+ string: withJsonSchemaAnnex(V2_LEAVES.string.omit({ "x-required-when": true }).extend(INHERITED_GROUP), SECRET_COUPLING_ANNEX),
12174
+ number: V2_LEAVES.number.omit({ "x-required-when": true }).extend(INHERITED_GROUP),
12175
+ boolean: V2_LEAVES.boolean.omit({ "x-required-when": true }).extend(INHERITED_GROUP),
12176
+ array: V2_LEAVES.array.omit({ "x-required-when": true }).extend(INHERITED_GROUP),
12177
+ items: V2_LEAVES.items
12178
+ };
12179
+ var LIST_OF_GROUPS_CONDITION_ANNEX = {
12180
+ allOf: [
12181
+ {
12182
+ $comment: "A list of groups inside a group may not carry x-required-when: the condition makes a setting required, and a list of groups is not one.",
12183
+ if: {
12184
+ properties: { items: { properties: { type: { const: "object" } }, required: ["type"] } },
12185
+ required: ["items"]
12186
+ },
12187
+ then: { not: { required: ["x-required-when"] } }
12188
+ }
12189
+ ]
12190
+ };
12169
12191
  function childBagShape(child) {
12170
12192
  return {
12171
12193
  properties: external_exports.record(PropertyNameSchema, child),
@@ -12190,41 +12212,49 @@ function childBagShape(child) {
12190
12212
  additionalProperties: external_exports.literal(false)
12191
12213
  };
12192
12214
  }
12193
- function propertyUnion(levelsBelow, leafSet) {
12215
+ var LEAF_SETS = { root: V2_LEAVES, group: V2_GROUP_LEAVES, list: V2_LIST_LEAVES };
12216
+ var LEVELS = /* @__PURE__ */ new Map();
12217
+ function levelUnion(levelsBelow, position) {
12218
+ const key = `${position}:${levelsBelow}`;
12219
+ const built = LEVELS.get(key);
12220
+ if (built !== void 0)
12221
+ return built;
12222
+ const level = MAX_CONFIGURATION_DEPTH - levelsBelow;
12223
+ const union = withJsonSchemaDefinition(external_exports.discriminatedUnion("type", propertyUnion(levelsBelow, position)), position === "group" ? `configurationPropertyLevel${level}` : `configurationListPropertyLevel${level}`);
12224
+ LEVELS.set(key, union);
12225
+ return union;
12226
+ }
12227
+ function propertyUnion(levelsBelow, position) {
12228
+ const leafSet = LEAF_SETS[position];
12194
12229
  const leaves = [leafSet.string, leafSet.number, leafSet.boolean];
12195
12230
  if (levelsBelow <= 0) {
12196
12231
  return [...leaves, leafSet.array];
12197
12232
  }
12198
- const level = MAX_CONFIGURATION_DEPTH - levelsBelow + 1;
12199
- const child = withJsonSchemaDefinition(external_exports.discriminatedUnion("type", propertyUnion(levelsBelow - 1, V2_NESTED_LEAVES)), `configurationPropertyLevel${level}`);
12200
- const bag = childBagShape(child);
12201
- const groupItems = external_exports.object({ type: external_exports.literal("object"), ...bag }).strict();
12233
+ const groupChild = levelUnion(levelsBelow - 1, position === "list" ? "list" : "group");
12234
+ const rowChild = levelUnion(levelsBelow - 1, "list");
12235
+ const groupItems = external_exports.object({ type: external_exports.literal("object"), ...childBagShape(rowChild) }).strict();
12236
+ const list = leafSet.array.extend({
12237
+ items: external_exports.discriminatedUnion("type", [V2_LEAVES.items, groupItems]),
12238
+ /**
12239
+ * Rules over the ROWS of this list — see {@link RowRulesSchema}.
12240
+ *
12241
+ * HERE RATHER THAN ON THE LEAF SHAPE, and that placement is the whole
12242
+ * enforcement. `leafShapes().array` is also a v1 list and a last-level
12243
+ * list, both of which hold STRINGS: a rule naming a field of a row would
12244
+ * name nothing there, and `uniqueItems` is already the right keyword for
12245
+ * comparing whole scalar entries. This branch is the only one in the chain
12246
+ * where a list can hold groups, so declaring the keyword here makes "rows
12247
+ * have fields" a property of the shape rather than a rule to remember.
12248
+ *
12249
+ * The refinement still has to refuse a rule on a list of STRINGS, because
12250
+ * `items` is a discriminated union INSIDE this branch — the shape narrows
12251
+ * where a row can exist, not whether this particular list has one.
12252
+ */
12253
+ "x-row-rules": RowRulesSchema.optional()
12254
+ });
12202
12255
  return [
12203
12256
  ...leaves,
12204
- // ONE `array` BRANCH, NOT TWO, because a discriminated union may not carry
12205
- // two options with the same discriminator value ("duplicate value array" —
12206
- // zod refuses it at construction). The item shape is the inner
12207
- // discriminator, which is also what keeps a v1 document's
12208
- // `items: {type: 'string'}` reading exactly as it always did.
12209
- leafSet.array.extend({
12210
- items: external_exports.discriminatedUnion("type", [V2_LEAVES.items, groupItems]),
12211
- /**
12212
- * Rules over the ROWS of this list — see {@link RowRulesSchema}.
12213
- *
12214
- * HERE RATHER THAN ON THE LEAF SHAPE, and that placement is the whole
12215
- * enforcement. `leafShapes().array` is also a v1 list and a last-level
12216
- * list, both of which hold STRINGS: a rule naming a field of a row would
12217
- * name nothing there, and `uniqueItems` is already the right keyword for
12218
- * comparing whole scalar entries. This branch is the only one in the chain
12219
- * where a list can hold groups, so declaring the keyword here makes "rows
12220
- * have fields" a property of the shape rather than a rule to remember.
12221
- *
12222
- * The refinement still has to refuse a rule on a list of STRINGS, because
12223
- * `items` is a discriminated union INSIDE this branch — the shape narrows
12224
- * where a row can exist, not whether this particular list has one.
12225
- */
12226
- "x-row-rules": RowRulesSchema.optional()
12227
- }),
12257
+ position === "group" ? withJsonSchemaAnnex(list, LIST_OF_GROUPS_CONDITION_ANNEX) : list,
12228
12258
  /**
12229
12259
  * A group of related settings.
12230
12260
  *
@@ -12235,20 +12265,23 @@ function propertyUnion(levelsBelow, leafSet) {
12235
12265
  * load-bearing "a defaulted setting is never absent", and what
12236
12266
  * `seedConfiguration` should write into a served example and a file on
12237
12267
  * disk. Defaults are a LEAF concept here.
12268
+ *
12269
+ * NO `x-required-when` EITHER, at any position: it makes a setting required,
12270
+ * and a group is not a setting.
12238
12271
  */
12239
12272
  external_exports.object({
12240
12273
  type: external_exports.literal("object"),
12241
12274
  title: TitleSchema,
12242
12275
  description: helpTextSchema(V2_LEAF_BOUNDS).optional(),
12243
- // INHERITED BELOW THE ROOT, for the reason `V2_NESTED_LEAVES` states: a
12276
+ // INHERITED BELOW THE ROOT, for the reason `INHERITED_GROUP` states: a
12244
12277
  // group is a bucket for a whole subtree, and repeating it is the only
12245
12278
  // value that could be written.
12246
- group: levelsBelow === MAX_CONFIGURATION_DEPTH - 1 ? ConfigurationGroupSchema : ConfigurationGroupSchema.optional(),
12247
- ...bag
12279
+ group: position === "root" ? ConfigurationGroupSchema : ConfigurationGroupSchema.optional(),
12280
+ ...childBagShape(groupChild)
12248
12281
  }).strict()
12249
12282
  ];
12250
12283
  }
12251
- var RootConfigurationPropertySchema = external_exports.discriminatedUnion("type", propertyUnion(MAX_CONFIGURATION_DEPTH - 1, V2_LEAVES));
12284
+ var RootConfigurationPropertySchema = external_exports.discriminatedUnion("type", propertyUnion(MAX_CONFIGURATION_DEPTH - 1, "root"));
12252
12285
  var CustomerConfigurationObjectSchema = external_exports.object({
12253
12286
  type: external_exports.literal("object"),
12254
12287
  properties: external_exports.record(PropertyNameSchema, ConfigurationPropertySchema).describe("Customer-editable settings, keyed by camelCase property name."),
@@ -12314,7 +12347,7 @@ var CustomerConfigurationSchema = CustomerConfigurationObjectSchema.superRefine(
12314
12347
  }
12315
12348
  checkDependentRequired(config, ctx);
12316
12349
  });
12317
- function checkDeclaredProperty(name, property, path, ctx, atRoot) {
12350
+ function checkDeclaredProperty(name, property, path, ctx, atRoot, underList2 = false) {
12318
12351
  const infrastructure = infrastructureVocabularyViolation(name);
12319
12352
  if (infrastructure) {
12320
12353
  ctx.addIssue({
@@ -12348,13 +12381,20 @@ function checkDeclaredProperty(name, property, path, ctx, atRoot) {
12348
12381
  ctx.addIssue({
12349
12382
  code: external_exports.ZodIssueCode.custom,
12350
12383
  path,
12351
- // THE REMEDIATION HAS TO BE ONE THE CONTRACT ALLOWS. At the root, the
12352
- // answer is to declare the two markers. Below it, that answer is
12353
- // IMPOSSIBLE — the nested shape omits both keys and `.strict()` refuses
12354
- // them — so telling a publisher to add them sends them to write a
12355
- // second invalid document, and an agent following the validator loops.
12356
- // A validator's errors are part of the agent-facing contract.
12357
- 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.`
12384
+ // THE REMEDIATION HAS TO BE ONE THE CONTRACT ALLOWS. In a group, as at
12385
+ // the root, the answer is to declare the two markers. Below a LIST that
12386
+ // answer is impossible — a secret is refused there, below — so telling a
12387
+ // publisher to add them sends them to write a second invalid document,
12388
+ // and an agent following the validator loops. A validator's errors are
12389
+ // part of the agent-facing contract.
12390
+ message: underList2 ? `${credential}, and a secret cannot be declared inside a list in this contract format \u2014 move it out of the list and declare it "x-secret": true with "writeOnly": true, or rename it if it is not one.` : `${credential} and must be declared "x-secret": true with "writeOnly": true, or renamed if it is not one.`
12391
+ });
12392
+ }
12393
+ if (underList2 && (secret || property.writeOnly === true)) {
12394
+ ctx.addIssue({
12395
+ code: external_exports.ZodIssueCode.custom,
12396
+ path,
12397
+ message: `"${name}": a secret cannot be declared inside a list in this contract format. Declare it in a group or at the root.`
12358
12398
  });
12359
12399
  }
12360
12400
  if (secret && property.writeOnly !== true) {
@@ -12549,9 +12589,9 @@ function checkDependentRequired(config, ctx, at = []) {
12549
12589
  }
12550
12590
  }
12551
12591
  }
12552
- checkOneSecretPerGroup(config, ctx);
12592
+ checkOneSecretPerGroup(config, ctx, at);
12553
12593
  }
12554
- function checkOneSecretPerGroup(config, ctx) {
12594
+ function checkOneSecretPerGroup(config, ctx, at = []) {
12555
12595
  const clauses = Object.entries(config.dependentRequired ?? {});
12556
12596
  if (clauses.length === 0)
12557
12597
  return;
@@ -12595,11 +12635,238 @@ function checkOneSecretPerGroup(config, ctx) {
12595
12635
  code: external_exports.ZodIssueCode.custom,
12596
12636
  // The component has no single name, so the issue is reported against the
12597
12637
  // map rather than against whichever member happened to be visited first.
12598
- path: ["dependentRequired"],
12638
+ path: [...at, "dependentRequired"],
12599
12639
  message: `These settings are configured together and include more than one secret (${secrets.join(", ")}). Only one secret can be supplied per change, so a group must ask for at most one \u2014 otherwise completing it on a running resource takes several, in an order the customer has to work out.`
12600
12640
  });
12601
12641
  }
12602
12642
  }
12643
+ function checkOptionalGroupActivation(config, ctx) {
12644
+ const context = { root: config, acceptsOutside: /* @__PURE__ */ new Map() };
12645
+ const check = (container, at, segments) => {
12646
+ const required = new Set(container.required ?? []);
12647
+ for (const [name, property] of Object.entries(container.properties)) {
12648
+ if (property.type !== "object")
12649
+ continue;
12650
+ const path = [...at, "properties", name];
12651
+ const inner = [...segments, name];
12652
+ if (!required.has(name)) {
12653
+ const { secrets, conditional } = activationSecrets(context, property, inner);
12654
+ if (secrets.length > 1) {
12655
+ ctx.addIssue({
12656
+ code: external_exports.ZodIssueCode.custom,
12657
+ path,
12658
+ message: `"${name}" is optional, and adding it to a resource that already exists requires more than one secret (${secrets.join(", ")}). Only one secret can be supplied per change, so an optional group must ask for at most one \u2014 otherwise a resource created without it can never add it. Require at most one of them, or make the group required, with every group above it, so it is supplied when the resource is created.` + conditionalActivationSentence(conditional)
12659
+ });
12660
+ }
12661
+ }
12662
+ check(property, path, inner);
12663
+ }
12664
+ };
12665
+ check(config, [], []);
12666
+ }
12667
+ function conditionalActivationSentence(conditional) {
12668
+ if (conditional.length === 0)
12669
+ return "";
12670
+ const one = conditional.length === 1;
12671
+ return ` ${conditional.join(", ")} ${one ? "is" : "are"} counted although ${one ? "it is" : "they are"} conditional: adding the group always turns ${one ? "its" : "their"} "x-required-when" on, because the setting ${one ? "that condition reads" : "each condition reads"} is then present and no value it accepts outside "anyOf" was found. Let ${one ? "that" : "such a"} setting be left out, or declare an "enum" for it that includes a value outside "anyOf".`;
12672
+ }
12673
+ function declarationKey(segments) {
12674
+ return `/${segments.join("/")}`;
12675
+ }
12676
+ function activationSecrets(context, group, segments) {
12677
+ const carriers = conditionCarriersBelow(group, segments);
12678
+ const unavoidable = /* @__PURE__ */ new Set();
12679
+ const unconditioned = walkActivation(group, segments, unavoidable);
12680
+ let walk = unconditioned;
12681
+ for (; ; ) {
12682
+ const found = carriers.filter((carrier) => !unavoidable.has(carrier.key) && conditionUnavoidable(context, segments, carrier.condition, walk));
12683
+ if (found.length === 0)
12684
+ break;
12685
+ for (const carrier of found)
12686
+ unavoidable.add(carrier.key);
12687
+ walk = walkActivation(group, segments, unavoidable);
12688
+ }
12689
+ const secrets = [...walk.secrets].sort();
12690
+ return {
12691
+ secrets,
12692
+ conditional: secrets.filter((path) => unavoidable.has(path) && !unconditioned.secrets.has(path))
12693
+ };
12694
+ }
12695
+ function walkActivation(group, segments, unavoidable) {
12696
+ const secrets = /* @__PURE__ */ new Set();
12697
+ const demanded = /* @__PURE__ */ new Set();
12698
+ const sent = /* @__PURE__ */ new Set();
12699
+ const onTheWay = /* @__PURE__ */ new Set();
12700
+ for (const carrier of unavoidable) {
12701
+ const parts = carrier.slice(1).split("/");
12702
+ for (let depth = segments.length + 1; depth <= parts.length; depth += 1) {
12703
+ onTheWay.add(declarationKey(parts.slice(0, depth)));
12704
+ }
12705
+ }
12706
+ const visit = (node, nodeSegments) => {
12707
+ sent.add(declarationKey(nodeSegments));
12708
+ const pending = [
12709
+ ...node.required ?? [],
12710
+ ...Object.keys(node.properties).filter((name) => onTheWay.has(declarationKey([...nodeSegments, name])))
12711
+ ];
12712
+ const seen = /* @__PURE__ */ new Set();
12713
+ while (pending.length > 0) {
12714
+ const name = pending.pop();
12715
+ if (seen.has(name))
12716
+ continue;
12717
+ seen.add(name);
12718
+ if (!Object.hasOwn(node.properties, name))
12719
+ continue;
12720
+ const property = node.properties[name];
12721
+ const key = declarationKey([...nodeSegments, name]);
12722
+ demanded.add(key);
12723
+ if (isSecretProperty(property)) {
12724
+ secrets.add(key);
12725
+ } else if (property.type === "object") {
12726
+ visit(property, [...nodeSegments, name]);
12727
+ }
12728
+ const clauses = node.dependentRequired;
12729
+ if (clauses !== void 0 && Object.hasOwn(clauses, name))
12730
+ pending.push(...clauses[name]);
12731
+ }
12732
+ };
12733
+ visit(group, segments);
12734
+ return { secrets, demanded, sent };
12735
+ }
12736
+ function conditionCarriersBelow(group, segments) {
12737
+ const out = [];
12738
+ const collect = (node, nodeSegments) => {
12739
+ for (const [name, property] of Object.entries(node.properties)) {
12740
+ const here = [...nodeSegments, name];
12741
+ if (property.type === "object") {
12742
+ collect(property, here);
12743
+ continue;
12744
+ }
12745
+ const condition = property["x-required-when"];
12746
+ if (condition !== void 0)
12747
+ out.push({ key: declarationKey(here), condition });
12748
+ }
12749
+ };
12750
+ collect(group, segments);
12751
+ return out;
12752
+ }
12753
+ function conditionUnavoidable(context, group, condition, walk) {
12754
+ const target = resolveConditionTarget(context.root, condition.path);
12755
+ if (target === null)
12756
+ return false;
12757
+ const { property } = target;
12758
+ if (property.type !== "string" || isSecretProperty(property))
12759
+ return false;
12760
+ const groupOrAbove = (segments) => segments.length <= group.length && segments.every((segment, i) => group[i] === segment);
12761
+ const inside = (segments) => segments.length > group.length && group.every((segment, i) => segments[i] === segment);
12762
+ let belowList = false;
12763
+ for (const container of target.containers) {
12764
+ if (container.kind === "group" && groupOrAbove(container.segments))
12765
+ continue;
12766
+ if (container.kind === "list" && condition.anyRow !== true)
12767
+ return false;
12768
+ const key = declarationKey(container.segments);
12769
+ const present2 = !belowList && inside(container.segments) ? container.kind === "group" ? walk.sent.has(key) : walk.demanded.has(key) : container.required;
12770
+ if (!present2)
12771
+ return false;
12772
+ if (container.kind === "list") {
12773
+ if (container.minItems < 1)
12774
+ return false;
12775
+ belowList = true;
12776
+ }
12777
+ }
12778
+ const defaulted = property.default !== void 0;
12779
+ const present = !belowList && inside(target.segments) ? walk.demanded.has(declarationKey(target.segments)) || defaulted && walk.sent.has(declarationKey(target.segments.slice(0, -1))) : target.required || defaulted;
12780
+ if (!present)
12781
+ return false;
12782
+ let outside = context.acceptsOutside.get(condition);
12783
+ if (outside === void 0) {
12784
+ outside = acceptsValueOutside(property, condition.anyOf);
12785
+ context.acceptsOutside.set(condition, outside);
12786
+ }
12787
+ return !outside;
12788
+ }
12789
+ function resolveConditionTarget(root, path) {
12790
+ const pointer = path.startsWith("/");
12791
+ const segments = pointer ? path.slice(1).split("/") : [path];
12792
+ if (pointer && segments.length < 2)
12793
+ return null;
12794
+ if (!segments.every((segment) => CONFIGURATION_PROPERTY_NAME_REGEX.test(segment)))
12795
+ return null;
12796
+ const containers = [];
12797
+ let holder = root;
12798
+ for (const [index, name] of segments.entries()) {
12799
+ if (!Object.hasOwn(holder.properties, name))
12800
+ return null;
12801
+ const property = holder.properties[name];
12802
+ const here = segments.slice(0, index + 1);
12803
+ const required = (holder.required ?? []).includes(name);
12804
+ const last = index === segments.length - 1;
12805
+ if (property.type === "object") {
12806
+ if (last)
12807
+ return null;
12808
+ containers.push({ segments: here, kind: "group", required, minItems: 0 });
12809
+ holder = property;
12810
+ } else if (property.type === "array" && property.items.type === "object") {
12811
+ if (last)
12812
+ return null;
12813
+ containers.push({ segments: here, kind: "list", required, minItems: property.minItems ?? 0 });
12814
+ holder = property.items;
12815
+ } else {
12816
+ return last ? { segments, containers, property, required } : null;
12817
+ }
12818
+ }
12819
+ return null;
12820
+ }
12821
+ function acceptsValueOutside(property, anyOf) {
12822
+ let pattern = null;
12823
+ if (property.pattern !== void 0) {
12824
+ try {
12825
+ pattern = new RegExp(property.pattern);
12826
+ } catch {
12827
+ return false;
12828
+ }
12829
+ }
12830
+ const accepts = (value) => !anyOf.includes(value) && value.trim().length > 0 && (property.enum === void 0 || property.enum.includes(value)) && (property.minLength === void 0 || value.length >= property.minLength) && (property.maxLength === void 0 || value.length <= property.maxLength) && (pattern === null || pattern.test(value));
12831
+ if (property.enum !== void 0)
12832
+ return property.enum.some(accepts);
12833
+ if (property.default !== void 0 && accepts(property.default))
12834
+ return true;
12835
+ const floor = property.minLength ?? 0;
12836
+ const ceiling = Math.min(property.maxLength ?? floor + 3, floor + 3);
12837
+ for (let length = Math.max(floor, 1); length <= ceiling; length += 1) {
12838
+ for (const fill of ["x", "a", "0"]) {
12839
+ if (accepts(fill.repeat(length)))
12840
+ return true;
12841
+ }
12842
+ }
12843
+ for (const word of property.pattern?.match(/[A-Za-z0-9_-]+/g) ?? []) {
12844
+ if (accepts(word))
12845
+ return true;
12846
+ }
12847
+ for (const value of anyOf) {
12848
+ for (let index = 0; index < value.length; index += 1) {
12849
+ const variant = value.slice(0, index) + sameClassCharacter(value.charAt(index)) + value.slice(index + 1);
12850
+ if (accepts(variant))
12851
+ return true;
12852
+ }
12853
+ }
12854
+ return false;
12855
+ }
12856
+ function sameClassCharacter(character) {
12857
+ const code = character.charCodeAt(0);
12858
+ for (const [first, last] of [
12859
+ ["0", "9"],
12860
+ ["a", "z"],
12861
+ ["A", "Z"]
12862
+ ]) {
12863
+ const start = first.charCodeAt(0);
12864
+ const end = last.charCodeAt(0);
12865
+ if (code >= start && code <= end)
12866
+ return String.fromCharCode(code === end ? start : code + 1);
12867
+ }
12868
+ return "x";
12869
+ }
12603
12870
  var CustomerConfigurationV2ObjectSchema = external_exports.object({
12604
12871
  type: external_exports.literal("object"),
12605
12872
  properties: external_exports.record(PropertyNameSchema, RootConfigurationPropertySchema).describe("Customer-editable settings, keyed by camelCase property name."),
@@ -12683,10 +12950,10 @@ function minimumPropertyBytes(property) {
12683
12950
  function declaredLeafDefault(property) {
12684
12951
  return property.type === "object" ? void 0 : property.default;
12685
12952
  }
12686
- function checkPropertyDeclaration(name, property, path, ctx, atRoot) {
12953
+ function checkPropertyDeclaration(name, property, path, ctx, atRoot, underList2 = false) {
12687
12954
  const children = childrenOf(property);
12688
12955
  if (children === null) {
12689
- checkDeclaredProperty(name, property, path, ctx, atRoot);
12956
+ checkDeclaredProperty(name, property, path, ctx, atRoot, underList2);
12690
12957
  return;
12691
12958
  }
12692
12959
  const infrastructure = infrastructureVocabularyViolation(name);
@@ -12732,8 +12999,15 @@ function checkPropertyDeclaration(name, property, path, ctx, atRoot) {
12732
12999
  message: `"${name}" has minItems greater than maxItems.`
12733
13000
  });
12734
13001
  }
13002
+ if (!atRoot && property.type === "array" && property["x-required-when"] !== void 0) {
13003
+ ctx.addIssue({
13004
+ code: external_exports.ZodIssueCode.custom,
13005
+ path: [...path, "x-required-when"],
13006
+ message: `"${name}" is a list of groups, and "x-required-when" may not be declared on one inside a group. The condition makes one setting required, and a list of groups is not a setting \u2014 declare the condition on a setting instead.`
13007
+ });
13008
+ }
12735
13009
  for (const childName of childNames) {
12736
- checkPropertyDeclaration(childName, children.properties[childName], [...path, "properties", childName], ctx, false);
13010
+ checkPropertyDeclaration(childName, children.properties[childName], [...path, "properties", childName], ctx, false, underList2 || property.type === "array");
12737
13011
  }
12738
13012
  for (const required of children.required ?? []) {
12739
13013
  if (!Object.hasOwn(children.properties, required)) {
@@ -12816,6 +13090,7 @@ var CustomerConfigurationV2Schema = CustomerConfigurationV2ObjectSchema.superRef
12816
13090
  }
12817
13091
  checkDependentRequired(config, ctx);
12818
13092
  checkOneSecretPerGroup(config, ctx);
13093
+ checkOptionalGroupActivation(config, ctx);
12819
13094
  });
12820
13095
  function enumerableValues(property) {
12821
13096
  const out = [];
@@ -12876,13 +13151,16 @@ function parseConfigurationPath(raw) {
12876
13151
  return null;
12877
13152
  return configurationPath(...segments);
12878
13153
  }
13154
+ function secretPathOf(raw) {
13155
+ const path = parseConfigurationPath(raw);
13156
+ if (path === null)
13157
+ return null;
13158
+ return pathSegments(path).some((segment) => typeof segment === "number") ? null : path;
13159
+ }
12879
13160
  function pathSegments(path) {
12880
13161
  const raw = path.startsWith("/") ? path.slice(1).split("/") : [path];
12881
13162
  return raw.map((segment) => isIndexSegment(segment) ? Number(segment) : segment);
12882
13163
  }
12883
- function rootSegment(path) {
12884
- return String(pathSegments(path)[0]);
12885
- }
12886
13164
  function declarationPathOf(path) {
12887
13165
  const segments = pathSegments(path).filter((segment) => typeof segment !== "number");
12888
13166
  return configurationPath(...segments) ?? path;
@@ -12929,9 +13207,29 @@ function canonicalJson(value) {
12929
13207
  function immediateContainer(node) {
12930
13208
  return node.containers[node.containers.length - 1] ?? null;
12931
13209
  }
13210
+ function resolvedGroup(node) {
13211
+ for (const container of node.containers) {
13212
+ const declared = container.property.group;
13213
+ if (declared !== void 0)
13214
+ return declared;
13215
+ }
13216
+ return node.property.group;
13217
+ }
12932
13218
  function underList(node) {
12933
13219
  return node.containers.some((container) => container.kind === "array");
12934
13220
  }
13221
+ function containersRequiredToRoot(schema, node) {
13222
+ const rootRequired = schema.required ?? [];
13223
+ return node.containers.every((container, index) => {
13224
+ const parent = node.containers[index - 1];
13225
+ return (parent === void 0 ? rootRequired : parent.required).includes(container.name);
13226
+ });
13227
+ }
13228
+ function requiredToRoot(schema, node) {
13229
+ const parent = immediateContainer(node);
13230
+ const declared = parent === null ? schema.required ?? [] : parent.required;
13231
+ return declared.includes(node.name) && containersRequiredToRoot(schema, node);
13232
+ }
12935
13233
  function containerChildren(property) {
12936
13234
  if (property.type === "object" && property.properties !== void 0) {
12937
13235
  return {
@@ -12990,6 +13288,9 @@ function walkDeclarations(properties, ancestors, base, onLeaf, onContainer = ()
12990
13288
  walkDeclarations(children.properties, [...ancestors, container], segments, onLeaf, onContainer);
12991
13289
  }
12992
13290
  }
13291
+ function declaredSecretPaths(schema) {
13292
+ return configurationLeaves(schema).filter((leaf) => isSecretProperty(leaf.property) && !underList(leaf)).map((leaf) => leaf.path).sort();
13293
+ }
12993
13294
  function declaredDefault(property) {
12994
13295
  if (property.type === "object")
12995
13296
  return void 0;
@@ -13025,7 +13326,8 @@ function expandDeclarationPath(configuration, declaration) {
13025
13326
  const [head, ...rest] = remaining;
13026
13327
  if (head === void 0)
13027
13328
  return;
13028
- walk(cursor[String(head)], rest, [...soFar, head]);
13329
+ const key = String(head);
13330
+ walk(Object.hasOwn(cursor, key) ? cursor[key] : void 0, rest, [...soFar, head]);
13029
13331
  };
13030
13332
  walk(configuration, segmentsOf(declaration), []);
13031
13333
  return out;
@@ -13039,7 +13341,7 @@ function valueAt(configuration, path) {
13039
13341
  cursor = cursor[segment];
13040
13342
  continue;
13041
13343
  }
13042
- if (!isPlainObject(cursor))
13344
+ if (!isPlainObject(cursor) || !Object.hasOwn(cursor, segment))
13043
13345
  return void 0;
13044
13346
  cursor = cursor[segment];
13045
13347
  }
@@ -13055,7 +13357,7 @@ function valuesAt(configuration, path) {
13055
13357
  next.push(cursor[segment]);
13056
13358
  continue;
13057
13359
  }
13058
- if (!isPlainObject(cursor))
13360
+ if (!isPlainObject(cursor) || !Object.hasOwn(cursor, segment))
13059
13361
  continue;
13060
13362
  const value = cursor[segment];
13061
13363
  if (Array.isArray(value))
@@ -13076,10 +13378,6 @@ function segmentsOf(path) {
13076
13378
  function conditionSetting(condition) {
13077
13379
  return "path" in condition ? condition.path : condition.property;
13078
13380
  }
13079
- function conditionRoot(condition) {
13080
- const path = parseConfigurationPath(conditionSetting(condition));
13081
- return path === null ? conditionSetting(condition) : rootSegment(path);
13082
- }
13083
13381
  function conditionHolds(condition, configuration) {
13084
13382
  const path = parseConfigurationPath(conditionSetting(condition));
13085
13383
  if (path === null)
@@ -13087,6 +13385,28 @@ function conditionHolds(condition, configuration) {
13087
13385
  const matches2 = (held) => typeof held === "string" && condition.anyOf.includes(held);
13088
13386
  return "anyRow" in condition && condition.anyRow === true ? valuesAt(configuration, path).some(matches2) : matches2(valueAt(configuration, path));
13089
13387
  }
13388
+ function conditionOf(property) {
13389
+ return property?.["x-required-when"];
13390
+ }
13391
+ function conditionCarriers(schema) {
13392
+ const settings = configurationLeaves(schema).flatMap((leaf) => {
13393
+ const condition = conditionOf(leaf.property);
13394
+ if (condition === void 0 || underList(leaf))
13395
+ return [];
13396
+ const { path, name, containers, property } = leaf;
13397
+ return [{ kind: "leaf", path, name, containers, property, condition }];
13398
+ });
13399
+ const lists = configurationContainers(schema).flatMap((container) => {
13400
+ const condition = conditionOf(container.property);
13401
+ if (condition === void 0 || container.kind !== "array" || container.containers.length > 0)
13402
+ return [];
13403
+ const { path, name, containers, property } = container;
13404
+ return [{ kind: "container", path, name, containers, property, condition }];
13405
+ });
13406
+ const roots = Object.keys(schema.properties);
13407
+ const rootIndex = (carrier) => roots.indexOf(carrier.containers[0]?.name ?? carrier.name);
13408
+ return [...settings, ...lists].sort((a, b) => rootIndex(a) - rootIndex(b));
13409
+ }
13090
13410
 
13091
13411
  // ../product-configuration/dist/customer-configuration-input.js
13092
13412
  var ABSOLUTE_MAX_STRING = MAX_DECLARABLE_STRING_LENGTH;
@@ -13096,6 +13416,7 @@ function isPlainObject2(value) {
13096
13416
  return typeof value === "object" && value !== null && !Array.isArray(value);
13097
13417
  }
13098
13418
  function report(walk, field, message) {
13419
+ walk.reported.add(field);
13099
13420
  if (walk.issues.length >= MAX_CONFIGURATION_ISSUES) {
13100
13421
  walk.suppressed += 1;
13101
13422
  return;
@@ -13111,7 +13432,10 @@ function validateSubmittedConfiguration(schema, submitted, presence = {}) {
13111
13432
  issues: [],
13112
13433
  suppressed: 0,
13113
13434
  leaves: 0,
13114
- referenced: new Set(presence.referenced ?? [])
13435
+ referenced: new Set(presence.referenced ?? []),
13436
+ present: new Set(presence.present ?? []),
13437
+ unknown: new Set(presence.unknown ?? []),
13438
+ reported: /* @__PURE__ */ new Set()
13115
13439
  };
13116
13440
  const supplied = /* @__PURE__ */ new Set();
13117
13441
  if (submitted !== void 0 && submitted !== null && !isPlainObject2(submitted)) {
@@ -13124,7 +13448,7 @@ function validateSubmittedConfiguration(schema, submitted, presence = {}) {
13124
13448
  const input = isPlainObject2(submitted) ? submitted : {};
13125
13449
  const secrets = {};
13126
13450
  const configuration = validateObject({ properties: schema.properties, required: schema.required }, input, null, walk, { secrets, supplied });
13127
- drain(walk, crossFieldIssues(schema, supplied, presence, configuration));
13451
+ drain(walk, crossFieldIssues(schema, supplied, presence, configuration, walk.reported));
13128
13452
  drain(walk, rowAndReferenceIssues(schema, presence, configuration));
13129
13453
  if (walk.suppressed > 0) {
13130
13454
  walk.issues.push({
@@ -13149,13 +13473,17 @@ function declaresContainer(schema) {
13149
13473
  return Object.values(schema.properties).some((property) => isConfigurationContainer(property));
13150
13474
  }
13151
13475
  function containsReferenced(walk, container) {
13152
- if (walk.referenced.size === 0)
13153
- return false;
13476
+ return containsAny(walk.referenced, container);
13477
+ }
13478
+ function referencedAtOrInside(walk, path) {
13479
+ return walk.referenced.has(path) || containsReferenced(walk, path);
13480
+ }
13481
+ function containsAny(candidates, container) {
13154
13482
  const parsedOuter = parseConfigurationPath(container);
13155
13483
  if (parsedOuter === null)
13156
13484
  return false;
13157
13485
  const outer = pathSegments(parsedOuter);
13158
- for (const candidate of walk.referenced) {
13486
+ for (const candidate of candidates) {
13159
13487
  const parsed = parseConfigurationPath(candidate);
13160
13488
  if (parsed === null)
13161
13489
  continue;
@@ -13167,7 +13495,7 @@ function containsReferenced(walk, container) {
13167
13495
  }
13168
13496
  return false;
13169
13497
  }
13170
- function validateObject(declaration, input, container, walk, root) {
13498
+ function validateObject(declaration, input, container, walk, sink) {
13171
13499
  const required = new Set(declaration.required ?? []);
13172
13500
  const out = {};
13173
13501
  const fieldFor = (name) => {
@@ -13185,11 +13513,13 @@ function validateObject(declaration, input, container, walk, root) {
13185
13513
  const field = fieldFor(name);
13186
13514
  const children = containerDeclaration(property);
13187
13515
  if (children !== null) {
13188
- const value = input[name];
13189
- const filled = validateContainer(name, property, children, value, field, walk);
13516
+ const value = Object.hasOwn(input, name) ? input[name] : void 0;
13517
+ const filled = validateContainer(name, property, children, value, field, walk, sink);
13518
+ if (value === void 0)
13519
+ absentGroupRuleIssues(children, field, walk, sink, required.has(name));
13190
13520
  if (filled !== void 0)
13191
13521
  out[name] = filled;
13192
- else if (required.has(name) && !containsReferenced(walk, field))
13522
+ else if (required.has(name) && !((containsReferenced(walk, field) || containsAny(walk.present, field) || sink !== null && containsAny(Object.keys(sink.secrets), field)) && (value !== void 0 || absentGroupAnswered(children, field, walk))))
13193
13523
  report(walk, field, "This setting is required.");
13194
13524
  continue;
13195
13525
  }
@@ -13204,9 +13534,13 @@ function validateObject(declaration, input, container, walk, root) {
13204
13534
  const raw = input[name];
13205
13535
  const blank = typeof raw === "string" && raw.trim().length === 0;
13206
13536
  const present = Object.hasOwn(input, name) && raw !== void 0 && !blank;
13207
- const secret = root !== null && isSecretProperty(leaf);
13208
- if (present && root !== null && container === null)
13209
- root.supplied.add(name);
13537
+ const secret = isSecretProperty(leaf);
13538
+ if (secret && sink === null) {
13539
+ report(walk, field, "This Product declares a secret inside a list, which this contract format does not support.");
13540
+ continue;
13541
+ }
13542
+ if (present && sink !== null && field !== "")
13543
+ sink.supplied.add(field);
13210
13544
  if (!present) {
13211
13545
  const fallback = secret ? void 0 : defaultOf(leaf);
13212
13546
  if (fallback !== void 0) {
@@ -13225,27 +13559,96 @@ function validateObject(declaration, input, container, walk, root) {
13225
13559
  continue;
13226
13560
  }
13227
13561
  if (secret) {
13228
- if (typeof checked === "string" && checked.length > 0)
13229
- root.secrets[name] = checked;
13562
+ if (typeof checked === "string" && checked.length > 0 && field !== "")
13563
+ sink.secrets[field] = checked;
13230
13564
  else if (required.has(name))
13231
13565
  report(walk, field, "This setting is required.");
13232
13566
  continue;
13233
13567
  }
13234
13568
  out[name] = checked;
13235
13569
  }
13236
- if (declaration.dependentRequired !== void 0) {
13237
- const suppliedHere = new Set(Object.keys(out));
13238
- for (const [trigger, dependents] of Object.entries(declaration.dependentRequired)) {
13239
- if (!suppliedHere.has(trigger) && !containsReferenced(walk, fieldFor(trigger)))
13570
+ groupRuleIssues(declaration, new Set(Object.keys(out)), container, walk, sink);
13571
+ return out;
13572
+ }
13573
+ function groupRuleIssues(declaration, suppliedHere, container, walk, sink) {
13574
+ if (declaration.dependentRequired === void 0)
13575
+ return;
13576
+ const fieldFor = (name) => (container === null ? configurationPath(name) : configurationPath(...pathSegments(container), name)) ?? "";
13577
+ const answered = (name) => {
13578
+ const path = fieldFor(name);
13579
+ return suppliedHere.has(name) || // A MEMBER, NOT A CONTAINER: a referenced leaf is recorded at this very
13580
+ // path, and a member group is answered by a reference inside it. Asking
13581
+ // only "inside" never counted a referenced leaf, so a referenced trigger
13582
+ // turned nothing on and a referenced dependent was reported missing.
13583
+ referencedAtOrInside(walk, path) || walk.present.has(path) || sink !== null && Object.hasOwn(sink.secrets, path);
13584
+ };
13585
+ const unseen = (name) => walk.unknown.has(fieldFor(name));
13586
+ const reported = /* @__PURE__ */ new Set();
13587
+ for (const [trigger, dependents] of Object.entries(declaration.dependentRequired)) {
13588
+ if (unseen(trigger) || !answered(trigger))
13589
+ continue;
13590
+ for (const dependent of dependents) {
13591
+ if (unseen(dependent) || answered(dependent) || reported.has(dependent))
13240
13592
  continue;
13241
- for (const dependent of dependents) {
13242
- if (suppliedHere.has(dependent) || containsReferenced(walk, fieldFor(dependent)))
13243
- continue;
13244
- report(walk, fieldFor(dependent), `This setting is required when ${labelOf(declaration, trigger)} is configured.`);
13245
- }
13593
+ reported.add(dependent);
13594
+ report(walk, fieldFor(dependent), `This setting is required when ${labelOf(declaration, trigger)} is configured.`);
13246
13595
  }
13247
13596
  }
13248
- return out;
13597
+ }
13598
+ function absentGroupRuleIssues(children, field, walk, sink, coveredByParent) {
13599
+ if (children.kind !== "object" || sink === null)
13600
+ return;
13601
+ const path = parseConfigurationPath(field);
13602
+ if (path === null)
13603
+ return;
13604
+ groupRuleIssues(children, /* @__PURE__ */ new Set(), path, walk, sink);
13605
+ const configured = containsReferenced(walk, field) || containsAny(walk.present, field);
13606
+ const required = new Set(children.required ?? []);
13607
+ for (const [name, property] of Object.entries(children.properties)) {
13608
+ const memberField = configurationPath(...pathSegments(path), name);
13609
+ if (memberField === null)
13610
+ continue;
13611
+ const demanded = configured && !coveredByParent && required.has(name) && !walk.unknown.has(memberField);
13612
+ const inner = containerDeclaration(property);
13613
+ if (inner === null) {
13614
+ if (!demanded || walk.referenced.has(memberField) || walk.present.has(memberField))
13615
+ continue;
13616
+ if (!isSecretProperty(property) && defaultOf(property) !== void 0)
13617
+ continue;
13618
+ report(walk, memberField, "This setting is required.");
13619
+ continue;
13620
+ }
13621
+ if (demanded && inner.kind === "object" && !(containsReferenced(walk, memberField) || containsAny(walk.present, memberField))) {
13622
+ report(walk, memberField, "This setting is required.");
13623
+ continue;
13624
+ }
13625
+ absentGroupRuleIssues(inner, memberField, walk, sink, coveredByParent && required.has(name));
13626
+ }
13627
+ }
13628
+ function absentGroupAnswered(children, field, walk) {
13629
+ if (children.kind !== "object")
13630
+ return true;
13631
+ const parent = parseConfigurationPath(field);
13632
+ if (parent === null)
13633
+ return false;
13634
+ for (const name of children.required ?? []) {
13635
+ const property = children.properties[name];
13636
+ const path = configurationPath(...pathSegments(parent), name);
13637
+ if (property === void 0 || path === null)
13638
+ return false;
13639
+ const inner = containerDeclaration(property);
13640
+ if (inner !== null) {
13641
+ if (!(containsReferenced(walk, path) || containsAny(walk.present, path)) || !absentGroupAnswered(inner, path, walk))
13642
+ return false;
13643
+ continue;
13644
+ }
13645
+ if (walk.referenced.has(path) || walk.present.has(path))
13646
+ continue;
13647
+ if (!isSecretProperty(property) && defaultOf(property) !== void 0)
13648
+ continue;
13649
+ return false;
13650
+ }
13651
+ return true;
13249
13652
  }
13250
13653
  function containerDeclaration(property) {
13251
13654
  if (property.type === "object" && property.properties !== void 0) {
@@ -13275,7 +13678,7 @@ function containerDeclaration(property) {
13275
13678
  }
13276
13679
  return null;
13277
13680
  }
13278
- function validateContainer(name, property, children, value, field, walk) {
13681
+ function validateContainer(name, property, children, value, field, walk, sink) {
13279
13682
  const path = parseConfigurationPath(field);
13280
13683
  if (children.kind === "object") {
13281
13684
  if (value === void 0)
@@ -13284,7 +13687,7 @@ function validateContainer(name, property, children, value, field, walk) {
13284
13687
  report(walk, field, "Expected a group of settings.");
13285
13688
  return void 0;
13286
13689
  }
13287
- const filled = validateObject(children, value, path, walk, null);
13690
+ const filled = validateObject(children, value, path, walk, sink);
13288
13691
  return Object.keys(filled).length === 0 ? void 0 : filled;
13289
13692
  }
13290
13693
  if (value === void 0)
@@ -13323,10 +13726,10 @@ function validateContainer(name, property, children, value, field, walk) {
13323
13726
  }
13324
13727
  return out;
13325
13728
  }
13326
- function crossFieldIssues(schema, supplied, presence, configuration = {}) {
13729
+ function crossFieldIssues(schema, supplied, presence, configuration, reportedByWalk) {
13327
13730
  const clauses = Object.entries(schema.dependentRequired ?? {});
13328
- const conditional = Object.entries(schema.properties).filter(([, property]) => conditionOf(property) !== void 0);
13329
- if (clauses.length === 0 && conditional.length === 0)
13731
+ const carriers = conditionCarriers(schema);
13732
+ if (clauses.length === 0 && carriers.length === 0)
13330
13733
  return [];
13331
13734
  const alsoPresent = /* @__PURE__ */ new Set([...presence.present ?? [], ...presence.referenced ?? []]);
13332
13735
  const unknown = new Set(presence.unknown ?? []);
@@ -13349,22 +13752,28 @@ function crossFieldIssues(schema, supplied, presence, configuration = {}) {
13349
13752
  });
13350
13753
  }
13351
13754
  }
13352
- for (const [name, property] of conditional) {
13353
- const condition = conditionOf(property);
13354
- if (condition === void 0 || !known(name) || held(name))
13755
+ for (const { path, condition } of carriers) {
13756
+ if (!known(path) || held(path))
13355
13757
  continue;
13356
- if (!conditionHolds(condition, configuration))
13758
+ const target = parseConfigurationPath(conditionSetting(condition));
13759
+ if (target === null || !conditionHolds(condition, configuration))
13357
13760
  continue;
13358
- if (reported.has(name))
13761
+ if (reported.has(path) || reportedByWalk.has(path))
13359
13762
  continue;
13360
- reported.add(name);
13763
+ reported.add(path);
13361
13764
  issues.push({
13362
- field: name,
13765
+ field: path,
13363
13766
  // THE SETTING THAT MADE IT NECESSARY, never the value it holds. A message
13364
13767
  // naming the value would be fine here and wrong one refactor later, when
13365
13768
  // somebody conditions a secret on another secret — which the publish rules
13366
13769
  // refuse precisely because nothing may read one.
13367
- message: `This setting is required because of what ${labelOf(schema, conditionRoot(condition))} is set to.`
13770
+ //
13771
+ // THE SETTING THE CONDITION READS, not the root it descends from. The two
13772
+ // were one string while a condition could only name a root setting or a
13773
+ // row of a root list; naming a setting in a group by its root would tell
13774
+ // the customer to look at a whole section. A root scalar reads exactly as
13775
+ // it always did, and a row's setting now names its list beside it.
13776
+ message: `This setting is required because of what ${labelOfPath(schema, target)} is set to.`
13368
13777
  });
13369
13778
  }
13370
13779
  return issues;
@@ -13469,9 +13878,6 @@ function labelOfChild(container, name) {
13469
13878
  const title = items?.properties?.[name]?.title;
13470
13879
  return title === void 0 ? `"${name}"` : `"${title}"`;
13471
13880
  }
13472
- function conditionOf(property) {
13473
- return property?.["x-required-when"];
13474
- }
13475
13881
  function labelOf(schema, name) {
13476
13882
  const property = schema.properties[name];
13477
13883
  return property?.title === void 0 ? `"${name}"` : `"${property.title}"`;
@@ -13516,9 +13922,6 @@ function withoutPropertyRules(properties) {
13516
13922
  }
13517
13923
  return out;
13518
13924
  }
13519
- function declaredSecretNames(schema) {
13520
- return Object.entries(schema.properties).filter(([, property]) => isSecretProperty(property)).map(([name]) => name).sort();
13521
- }
13522
13925
  function defaultOf(property) {
13523
13926
  switch (property.type) {
13524
13927
  case "string":
@@ -13675,23 +14078,52 @@ function seedConfiguration(configuration) {
13675
14078
  const required = new Set(configuration?.required ?? []);
13676
14079
  const open = [];
13677
14080
  const requiredSecrets = [];
13678
- const groups = Object.entries(configuration?.dependentRequired ?? {}).map(([trigger, dependents]) => {
14081
+ const groups = [];
14082
+ collectGroups(configuration ?? {}, [], groups);
14083
+ const values = seedProperties(properties, required, [], open, requiredSecrets);
14084
+ return { values, open, requiredSecrets, groups };
14085
+ }
14086
+ function collectGroups(node, ancestors, groups) {
14087
+ const properties = node.properties ?? {};
14088
+ const pathOf = (name) => ancestors.length === 0 ? name : configurationPath(...ancestors, name) ?? name;
14089
+ const labelOf2 = (name) => {
14090
+ const path = configurationPath(...ancestors, name);
14091
+ return path === null ? name : printableConfigurationPath(path);
14092
+ };
14093
+ const container = ancestors.length === 0 ? null : configurationPath(...ancestors);
14094
+ const within = container === null ? null : printableConfigurationPath(container);
14095
+ const clauses = Object.entries(node.dependentRequired ?? {}).map(([trigger, dependents]) => {
13679
14096
  const members = [trigger, ...dependents];
14097
+ const settings = members.filter((name) => !isSecret(properties[name] ?? {}));
13680
14098
  return {
13681
14099
  trigger,
13682
- settings: members.filter((name) => !isSecret(properties[name] ?? {})),
13683
- secrets: members.filter((name) => isSecret(properties[name] ?? {}))
14100
+ group: {
14101
+ trigger: pathOf(trigger),
14102
+ settings: settings.map(pathOf),
14103
+ labels: settings.map(labelOf2),
14104
+ secrets: members.filter((name) => isSecret(properties[name] ?? {})).map(pathOf),
14105
+ within
14106
+ }
13684
14107
  };
13685
14108
  }).sort((a, b) => a.trigger.localeCompare(b.trigger));
13686
- const values = seedProperties(properties, required, [], open, requiredSecrets);
13687
- return { values, open, requiredSecrets, groups };
14109
+ groups.push(...clauses.map((clause) => clause.group));
14110
+ for (const [name, declaration] of Object.entries(properties)) {
14111
+ if (isSecret(declaration))
14112
+ continue;
14113
+ const children = containerChildren2(declaration);
14114
+ if (children === null)
14115
+ continue;
14116
+ collectGroups(children, [...ancestors, name], groups);
14117
+ }
13688
14118
  }
13689
14119
  function seedProperties(properties, required, ancestors, open, requiredSecrets) {
13690
14120
  const values = {};
13691
14121
  for (const [name, declaration] of Object.entries(properties)) {
13692
- if (requiredSecrets !== null && isSecret(declaration)) {
13693
- if (required.has(name))
13694
- requiredSecrets.push(name);
14122
+ if (isSecret(declaration)) {
14123
+ const path = ancestors.length === 0 ? name : configurationPath(...ancestors, name);
14124
+ if (requiredSecrets !== null && required.has(name) && path !== null) {
14125
+ requiredSecrets.push(path);
14126
+ }
13695
14127
  continue;
13696
14128
  }
13697
14129
  const children = containerChildren2(declaration);
@@ -13701,7 +14133,7 @@ function seedProperties(properties, required, ancestors, open, requiredSecrets)
13701
14133
  const childRequired = new Set(children.required);
13702
14134
  const inner = [...ancestors, name];
13703
14135
  if (children.kind === "object") {
13704
- values[name] = seedProperties(children.properties, childRequired, inner, open, null);
14136
+ values[name] = seedProperties(children.properties, childRequired, inner, open, requiredSecrets);
13705
14137
  continue;
13706
14138
  }
13707
14139
  const count = children.minItems ?? 0;
@@ -13732,7 +14164,8 @@ function containerChildren2(declaration) {
13732
14164
  return {
13733
14165
  kind: "object",
13734
14166
  properties: node.properties,
13735
- required: node.required ?? []
14167
+ required: node.required ?? [],
14168
+ ...dependentRequiredOf(node.dependentRequired)
13736
14169
  };
13737
14170
  }
13738
14171
  if (node.type === "array" && node.items !== void 0) {
@@ -13743,11 +14176,15 @@ function containerChildren2(declaration) {
13743
14176
  kind: "array",
13744
14177
  properties: items.properties,
13745
14178
  required: items.required ?? [],
14179
+ ...dependentRequiredOf(items.dependentRequired),
13746
14180
  ...typeof node.minItems === "number" ? { minItems: node.minItems } : {}
13747
14181
  };
13748
14182
  }
13749
14183
  return null;
13750
14184
  }
14185
+ function dependentRequiredOf(raw) {
14186
+ return typeof raw === "object" && raw !== null && !Array.isArray(raw) ? { dependentRequired: raw } : {};
14187
+ }
13751
14188
  function dedupeOpen(open) {
13752
14189
  const seen = /* @__PURE__ */ new Set();
13753
14190
  const unique = open.filter((entry) => {
@@ -13779,6 +14216,117 @@ function exampleResourceKey(name) {
13779
14216
  const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^[^a-z]+/, "").replace(/-+$/, "").slice(0, 63);
13780
14217
  return RESOURCE_KEY_PATTERN.test(slug) ? slug : "";
13781
14218
  }
14219
+ var DEPLOYMENT_EXAMPLE_GUARANTEE = "Product-owned configuration is complete and E2E-proven; tenant-specific secrets and Resource/provider selections are supplied by the tenant.";
14220
+
14221
+ // ../product-configuration/dist/configuration-requirements.js
14222
+ function configurationRequirements(configuration) {
14223
+ if (configuration === void 0 || configuration === null)
14224
+ return [];
14225
+ const schema = configuration;
14226
+ const rootRequired = new Set(schema.required ?? []);
14227
+ const carriers = new Map(conditionCarriers(schema).map((carrier) => [carrier.path, carrier]));
14228
+ const secretPaths = new Set(configurationLeaves(schema).filter((leaf) => isSecretProperty(leaf.property)).map((leaf) => String(leaf.path)));
14229
+ return configurationLeaves(schema).map((leaf) => {
14230
+ const parent = immediateContainer(leaf);
14231
+ const immediatelyRequired = parent === null ? rootRequired.has(leaf.name) : parent.required.includes(leaf.name);
14232
+ const chainRequired = containersRequiredToRoot(schema, leaf);
14233
+ const always = requiredToRoot(schema, leaf);
14234
+ const secret = isSecretProperty(leaf.property);
14235
+ const condition = conditionFor(schema, leaf.path, leaf.containers, carriers);
14236
+ const clause = clauseEdges(schema, leaf, secretPaths);
14237
+ const requiredness = always ? "always" : condition !== null && condition.carrier === "setting" ? "when-value" : immediatelyRequired && !chainRequired ? "when-group" : clause.requiredBy.length > 0 ? "with-siblings" : "optional";
14238
+ const containers = leaf.containers.map((container, index) => settingContainer(container, index === 0 ? rootRequired : new Set(leaf.containers[index - 1]?.required ?? [])));
14239
+ const list = [...containers].reverse().find((container) => container.kind === "array") ?? null;
14240
+ return {
14241
+ path: leaf.path,
14242
+ printablePath: printableConfigurationPath(leaf.path),
14243
+ title: leaf.property.title,
14244
+ description: leaf.property.description,
14245
+ type: leaf.property.type,
14246
+ secret,
14247
+ requiredness,
14248
+ // NEVER A SECRET'S, even though the declaration rules already refuse one.
14249
+ // Being wrong in that direction prints a credential, which is the same
14250
+ // paranoia `product-instance-example.ts` documents for its own reader.
14251
+ defaultValue: secret ? void 0 : declaredDefault(leaf.property),
14252
+ allowed: secret ? [] : allowedValues(leaf.property),
14253
+ condition,
14254
+ requires: clause.requires,
14255
+ requiredBy: clause.requiredBy,
14256
+ containers,
14257
+ perRow: underList(leaf),
14258
+ list,
14259
+ group: resolvedGroup(leaf)
14260
+ };
14261
+ });
14262
+ }
14263
+ function allowedValues(property) {
14264
+ const leaf = property;
14265
+ return [...leaf.enum ?? leaf.items?.enum ?? []];
14266
+ }
14267
+ function conditionFor(schema, path, containers, carriers) {
14268
+ const own = carriers.get(path);
14269
+ const ancestor = [...containers].reverse().map((container) => carriers.get(container.path)).find((carrier2) => carrier2 !== void 0 && carrier2.kind === "container");
14270
+ const carrier = own?.kind === "leaf" ? own : ancestor;
14271
+ if (carrier === void 0)
14272
+ return null;
14273
+ const setting = parseConfigurationPath(conditionSetting(carrier.condition));
14274
+ if (setting === null)
14275
+ return null;
14276
+ return {
14277
+ carrier: carrier.kind === "leaf" ? "setting" : "container",
14278
+ carrierPath: carrier.path,
14279
+ setting,
14280
+ printableSetting: printableConfigurationPath(setting),
14281
+ settingLabel: labelOfPath(schema, setting),
14282
+ anyOf: [...carrier.condition.anyOf],
14283
+ anyRow: "path" in carrier.condition && carrier.condition.anyRow === true
14284
+ };
14285
+ }
14286
+ function clauseEdges(schema, leaf, secretPaths) {
14287
+ const parent = immediateContainer(leaf);
14288
+ const scope = parent === null ? schema : parent.kind === "array" ? parent.property.items ?? parent.property : parent.property;
14289
+ const requires = /* @__PURE__ */ new Set();
14290
+ const requiredBy = /* @__PURE__ */ new Set();
14291
+ for (const [trigger, dependents] of Object.entries(scope.dependentRequired ?? {})) {
14292
+ if (trigger === leaf.name)
14293
+ for (const dependent of dependents)
14294
+ requires.add(dependent);
14295
+ else if (dependents.includes(leaf.name))
14296
+ requiredBy.add(trigger);
14297
+ }
14298
+ const order = Object.keys(scope.properties ?? {});
14299
+ const members = (names) => [...names].sort((a, b) => order.indexOf(a) - order.indexOf(b)).flatMap((name) => {
14300
+ const path = memberPath(leaf, name);
14301
+ return path === null ? [] : [
14302
+ {
14303
+ path,
14304
+ printablePath: printableConfigurationPath(path),
14305
+ secret: secretPaths.has(String(path))
14306
+ }
14307
+ ];
14308
+ });
14309
+ return { requires: members(requires), requiredBy: members(requiredBy) };
14310
+ }
14311
+ function memberPath(leaf, name) {
14312
+ const parent = immediateContainer(leaf);
14313
+ return parent === null ? configurationPath(name) : configurationPath(...pathSegments(parent.path), name);
14314
+ }
14315
+ function settingContainer(container, requiredByParent) {
14316
+ return {
14317
+ path: container.path,
14318
+ printablePath: printableConfigurationPath(container.path),
14319
+ title: container.property.title,
14320
+ kind: container.kind,
14321
+ required: requiredByParent.has(container.name)
14322
+ };
14323
+ }
14324
+
14325
+ // ../product-configuration/dist/manifest-yaml.js
14326
+ var import_yaml = __toESM(require_dist(), 1);
14327
+ function manifestYaml(document) {
14328
+ return (0, import_yaml.stringify)(document, { compat: "yaml-1.1" });
14329
+ }
13782
14330
 
13783
14331
  // ../platform-schemas/dist/index.js
13784
14332
  var TENANT_KEY_REGEX = /^[a-z][a-z0-9-]{2,23}[a-z0-9]$/;
@@ -13921,6 +14469,53 @@ function isBlockedHost(hostname) {
13921
14469
  return BLOCKED_HOSTS.some((pattern) => pattern.test(hostname));
13922
14470
  }
13923
14471
 
14472
+ // ../product-contracts/dist/deployment-example.schema.js
14473
+ var DeploymentExampleSchema = external_exports.object({
14474
+ /** The heading a customer reads above the example. */
14475
+ title: external_exports.string().min(3).max(80),
14476
+ /** What this deployment gives the customer, in a sentence or two. */
14477
+ description: external_exports.string().min(20).max(400),
14478
+ /**
14479
+ * A PLAIN STRING, for the reason the Product Instance Template's is: an
14480
+ * unsupported value then reaches the check that names the regions THIS
14481
+ * Product offers, rather than a zod enum error naming the platform's.
14482
+ */
14483
+ region: external_exports.string().min(1).max(32),
14484
+ /** Plain string, same reasoning as `region`. */
14485
+ profile: external_exports.string().min(1).max(40),
14486
+ /**
14487
+ * The Product's own settings, in the shape `spec.configuration` takes —
14488
+ * nested for a v2 Product. Validated at publish against this release's
14489
+ * configuration schema by the same function a customer's manifest meets.
14490
+ */
14491
+ configuration: external_exports.record(external_exports.unknown()),
14492
+ /**
14493
+ * The secret settings a deployment of this example supplies, BY NAME.
14494
+ *
14495
+ * Every secret the example's configuration makes required must be named
14496
+ * here, so a reader knows every `--secret` flag the create needs before
14497
+ * running it. The values are the tenant's — sent when the Resource is
14498
+ * created and again only to set a new one — and never written into a
14499
+ * manifest. A secret resent on an unchanged re-apply is written again.
14500
+ */
14501
+ secretInputs: external_exports.array(external_exports.string().regex(CONFIGURATION_PROPERTY_NAME_REGEX)).max(MAX_CONFIGURATION_CHILDREN).optional()
14502
+ }).strict().describe("The recommended deployment for this release. Required for a NEW release; optional in the schema because releases published before the field existed are immutable. " + DEPLOYMENT_EXAMPLE_GUARANTEE + " Secret settings are named in secretInputs and never given a value here.");
14503
+ var SECRET_INPUT_SEGMENT = CONFIGURATION_PROPERTY_NAME_REGEX.source.slice(1, -1);
14504
+ var SecretInputV2Schema = external_exports.string().regex(new RegExp(`^(?:${SECRET_INPUT_SEGMENT}|(?:/${SECRET_INPUT_SEGMENT}){2,${MAX_CONFIGURATION_DEPTH}})$`)).refine((raw) => secretPathOf(raw) !== null, {
14505
+ message: 'A secret input is a root setting name, or a configuration path such as "/group/setting" with no list index.'
14506
+ });
14507
+ var DeploymentExampleV2Schema = DeploymentExampleSchema.extend({
14508
+ /**
14509
+ * The secret settings a deployment of this example supplies, BY CANONICAL
14510
+ * PATH — a bare name at the root, a pointer inside a group.
14511
+ *
14512
+ * Every secret the example's configuration makes required must be named
14513
+ * here, so a reader knows every `--secret` flag the create needs before
14514
+ * running it. The values are the tenant's and never written into a manifest.
14515
+ */
14516
+ secretInputs: external_exports.array(SecretInputV2Schema).max(MAX_CONFIGURATION_CHILDREN).optional()
14517
+ });
14518
+
13924
14519
  // ../product-contracts/dist/portfolio.js
13925
14520
  var PORTFOLIO_CATEGORY_IDS = [
13926
14521
  "clients",
@@ -14507,6 +15102,15 @@ var ProductDefinitionObjectSchema = external_exports.object({
14507
15102
  // --- Customer choices -------------------------------------------------
14508
15103
  profiles: external_exports.array(ServiceProfileSchema).min(1).max(SERVICE_PROFILE_IDS.length),
14509
15104
  configuration: CustomerConfigurationSchema,
15105
+ /**
15106
+ * The publisher's recommended deployment of this release. See
15107
+ * `deployment-example.schema.ts`.
15108
+ *
15109
+ * `.optional()`, NEVER `.default(...)`, for the digest reason `conditions`
15110
+ * gives below — and required for a NEW release by `publish-rules.ts`, which
15111
+ * is the only place a rule added after releases were frozen may live.
15112
+ */
15113
+ deploymentExample: DeploymentExampleSchema.optional(),
14510
15114
  // --- Results, relationships, operations -------------------------------
14511
15115
  outputs: external_exports.array(ProductOutputSchema).min(1).max(MAX_PRODUCT_OUTPUTS),
14512
15116
  dependencies: external_exports.array(ProductDependencySchema).max(8),
@@ -14543,7 +15147,10 @@ var ProductDefinitionSchema = ProductDefinitionObjectSchema.superRefine(checkPro
14543
15147
  var ProductDefinitionV2ObjectSchema = ProductDefinitionObjectSchema.extend({
14544
15148
  apiVersion: ProductApiVersionV2Schema,
14545
15149
  configuration: CustomerConfigurationV2Schema,
14546
- dependencies: external_exports.array(ProductDependencyV2Schema).max(8)
15150
+ dependencies: external_exports.array(ProductDependencyV2Schema).max(8),
15151
+ // A v2 secret may sit inside a group, so the example names it by PATH. The v1
15152
+ // example keeps its root-name rule, and the v1 artifact its bytes.
15153
+ deploymentExample: DeploymentExampleV2Schema.optional()
14547
15154
  });
14548
15155
  var ProductDefinitionV2Schema = ProductDefinitionV2ObjectSchema.superRefine(checkProductDefinition);
14549
15156
  function checkProductDefinition(definition, ctx) {
@@ -14720,12 +15327,13 @@ function checkProductDefinition(definition, ctx) {
14720
15327
  });
14721
15328
  }
14722
15329
  });
14723
- for (const [name, property] of Object.entries(definition.configuration.properties ?? {})) {
14724
- const condition = property["x-required-when"];
14725
- if (condition === void 0)
14726
- continue;
14727
- const at = ["configuration", "properties", name, "x-required-when"];
14728
- if ((definition.configuration.required ?? []).includes(name)) {
15330
+ for (const carrier of conditionCarriers(definition.configuration)) {
15331
+ const { condition } = carrier;
15332
+ const name = printableConfigurationPath(carrier.path);
15333
+ const named = conditionSetting(condition);
15334
+ const anyRow = "anyRow" in condition && condition.anyRow === true;
15335
+ const at = [...declarationIssuePath(carrier), "x-required-when"];
15336
+ if (requiredToRoot(definition.configuration, carrier)) {
14729
15337
  ctx.addIssue({
14730
15338
  code: external_exports.ZodIssueCode.custom,
14731
15339
  path: [...at],
@@ -14733,7 +15341,7 @@ function checkProductDefinition(definition, ctx) {
14733
15341
  });
14734
15342
  continue;
14735
15343
  }
14736
- if (declaredDefault(property) !== void 0) {
15344
+ if (declaredDefault(carrier.property) !== void 0) {
14737
15345
  ctx.addIssue({
14738
15346
  code: external_exports.ZodIssueCode.custom,
14739
15347
  path: [...at],
@@ -14748,7 +15356,7 @@ function checkProductDefinition(definition, ctx) {
14748
15356
  message: "Repeated values in `anyOf`."
14749
15357
  });
14750
15358
  }
14751
- if (condition.path === name) {
15359
+ if (named === carrier.path) {
14752
15360
  ctx.addIssue({
14753
15361
  code: external_exports.ZodIssueCode.custom,
14754
15362
  path: [...at, "path"],
@@ -14756,30 +15364,30 @@ function checkProductDefinition(definition, ctx) {
14756
15364
  });
14757
15365
  continue;
14758
15366
  }
14759
- const parsed = parseConfigurationPath(condition.path);
15367
+ const parsed = parseConfigurationPath(named);
14760
15368
  const leaf = parsed === null ? null : leafAt(definition.configuration, parsed);
14761
15369
  if (leaf === null) {
14762
15370
  const isGroup = parsed !== null && declaresPath(definition.configuration, parsed);
14763
15371
  ctx.addIssue({
14764
15372
  code: external_exports.ZodIssueCode.custom,
14765
15373
  path: [...at, "path"],
14766
- message: isGroup ? `"${condition.path}" is a group of settings, which holds no single value to compare. Name one setting inside it.` : `"${condition.path}" is not a declared customer configuration property.`
15374
+ 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.`
14767
15375
  });
14768
15376
  continue;
14769
15377
  }
14770
- if (underList(leaf) && condition.anyRow !== true) {
15378
+ if (underList(leaf) && !anyRow) {
14771
15379
  ctx.addIssue({
14772
15380
  code: external_exports.ZodIssueCode.custom,
14773
15381
  path: [...at, "path"],
14774
- message: `"${condition.path}" is inside a list, so it names one value per item rather than one value. Add "anyRow": true to require the secret when ANY row chose one of these values, or name a setting the instance holds exactly once.`
15382
+ message: `"${named}" is inside a list, so it names one value per item rather than one value. Add "anyRow": true to require the secret when ANY row chose one of these values, or name a setting the instance holds exactly once.`
14775
15383
  });
14776
15384
  continue;
14777
15385
  }
14778
- if (!underList(leaf) && condition.anyRow === true) {
15386
+ if (!underList(leaf) && anyRow) {
14779
15387
  ctx.addIssue({
14780
15388
  code: external_exports.ZodIssueCode.custom,
14781
15389
  path: [...at, "anyRow"],
14782
- message: `"${condition.path}" is not inside a list, so there are no rows to quantify over.`
15390
+ message: `"${named}" is not inside a list, so there are no rows to quantify over.`
14783
15391
  });
14784
15392
  }
14785
15393
  const target = leaf.property;
@@ -14787,7 +15395,7 @@ function checkProductDefinition(definition, ctx) {
14787
15395
  ctx.addIssue({
14788
15396
  code: external_exports.ZodIssueCode.custom,
14789
15397
  path: [...at, "path"],
14790
- message: `"${condition.path}" is a ${target.type} setting; a condition compares string values.`
15398
+ message: `"${named}" is a ${target.type} setting; a condition compares string values.`
14791
15399
  });
14792
15400
  continue;
14793
15401
  }
@@ -14795,21 +15403,16 @@ function checkProductDefinition(definition, ctx) {
14795
15403
  ctx.addIssue({
14796
15404
  code: external_exports.ZodIssueCode.custom,
14797
15405
  path: [...at, "path"],
14798
- message: `"${condition.path}" is a secret. The platform never reads a secret's value, so a condition on one could never be evaluated.`
15406
+ message: `"${named}" is a secret. The platform never reads a secret's value, so a condition on one could never be evaluated.`
14799
15407
  });
14800
15408
  continue;
14801
15409
  }
14802
- const single = withoutCrossFieldRules({
14803
- ...definition.configuration,
14804
- properties: { [leaf.name]: target },
14805
- required: [leaf.name]
14806
- });
14807
- const outside = condition.anyOf.filter((value) => !validateSubmittedConfiguration(single, { [leaf.name]: value }).ok);
15410
+ const outside = valuesOutsideSpace(definition.configuration, leaf, condition.anyOf);
14808
15411
  if (outside.length > 0) {
14809
15412
  ctx.addIssue({
14810
15413
  code: external_exports.ZodIssueCode.custom,
14811
15414
  path: [...at, "anyOf"],
14812
- message: `"${condition.path}" never accepts ${outside.map((v) => `"${v}"`).join(", ")}, so this condition could never make the secret required.`
15415
+ message: `"${named}" never accepts ${outside.map((v) => `"${v}"`).join(", ")}, so this condition could never make the secret required.`
14813
15416
  });
14814
15417
  }
14815
15418
  }
@@ -15142,6 +15745,225 @@ var ANNOUNCED_PRODUCTS = Object.freeze([
15142
15745
  }
15143
15746
  ]);
15144
15747
 
15748
+ // ../product-contracts/dist/contract-issue.js
15749
+ var PATH_SEGMENT = /^[A-Za-z_][A-Za-z0-9_-]{0,63}$/;
15750
+ var UNPRINTABLE_SEGMENT = "<unprintable>";
15751
+ var MAX_SEGMENTS = MAX_CONFIGURATION_PATH_SEGMENTS + 2;
15752
+ function printableContractPath(path) {
15753
+ const printable = path.slice(0, MAX_SEGMENTS).map((segment) => typeof segment === "number" || PATH_SEGMENT.test(segment) ? String(segment) : UNPRINTABLE_SEGMENT);
15754
+ return printable.join(".") || "(document)";
15755
+ }
15756
+ function manifestConfigurationField(field) {
15757
+ if (field === "")
15758
+ return "spec.configuration";
15759
+ const parsed = parseConfigurationPath(field);
15760
+ return printableContractPath([
15761
+ "spec",
15762
+ "configuration",
15763
+ ...parsed === null ? [field] : pathSegments(parsed)
15764
+ ]);
15765
+ }
15766
+
15767
+ // ../product-contracts/dist/resource-output-reference.js
15768
+ var OUTPUT_KEY_RULE = "Output keys are camelCase.";
15769
+ var ResourceOutputReferenceSchema = external_exports.object({
15770
+ valueFrom: external_exports.object({
15771
+ resourceOutput: external_exports.object({
15772
+ /** The producing Resource's `metadata.key`, in the same Project. */
15773
+ resourceKey: ResourceKeySchema,
15774
+ /** The key of an output that Resource's Product declares. */
15775
+ output: external_exports.string().regex(/^[a-z][a-zA-Z0-9]{1,47}$/, OUTPUT_KEY_RULE)
15776
+ }).strict()
15777
+ }).strict()
15778
+ }).strict();
15779
+ function looksLikeReference(value) {
15780
+ return typeof value === "object" && value !== null && !Array.isArray(value) && "valueFrom" in value;
15781
+ }
15782
+
15783
+ // ../product-contracts/dist/dependency-binding.js
15784
+ var DependencyBindingSelectorSchema = external_exports.object({
15785
+ /** The providing Resource's `metadata.key`. */
15786
+ resourceKey: ResourceKeySchema,
15787
+ /**
15788
+ * The Project that Resource lives in. Absent means the consumer's own.
15789
+ *
15790
+ * PRESENT BECAUSE A BINDING IS TENANT-SCOPED AND A PROJECT IS NOT A RUNTIME
15791
+ * BOUNDARY: a provider anywhere in the Tenant can satisfy a consumer
15792
+ * anywhere in it, and that has been true since bindings existed. What the
15793
+ * field does NOT do is grant anything — the Control Plane opens that
15794
+ * Project from the caller's own assertion, and a Project the caller holds
15795
+ * nothing in answers exactly as a Project that does not exist.
15796
+ *
15797
+ * A plain bounded string, exactly as `spec.projectId` is: an id that does
15798
+ * not name a readable Project is refused where the refusal can be phrased
15799
+ * without leaking, which is not here.
15800
+ */
15801
+ projectId: external_exports.string().min(1).max(64).optional()
15802
+ }).strict();
15803
+ var DependencyBindingsSchema = external_exports.record(ServiceBindingKeySchema, DependencyBindingSelectorSchema);
15804
+
15805
+ // ../product-contracts/dist/product-instance-template.schema.js
15806
+ var ProductInstanceTemplateSchema = external_exports.object({
15807
+ apiVersion: ResourceApiVersionSchema,
15808
+ kind: kindSchema(PRODUCT_INSTANCE_KIND),
15809
+ metadata: external_exports.object({
15810
+ /**
15811
+ * The customer-facing Resource name — "Local service".
15812
+ *
15813
+ * Maps to `ProductInstance.displayName`, and deliberately introduces NO
15814
+ * new identity concept. `pri_…` remains the stable identity and
15815
+ * `instanceKey` remains the cloud resource key; adding a third name here
15816
+ * would mean a fourth thing to keep unique and a fifth to explain.
15817
+ * Bounded exactly as the create envelope bounds `displayName`.
15818
+ */
15819
+ // NOT `.trim()`: this schema is PUBLISHED as JSON Schema so a customer
15820
+ // can validate the file before sending it, and a trim is a transform
15821
+ // that JSON Schema cannot express. The value is trimmed when it becomes
15822
+ // a create command instead, so the published document and the platform
15823
+ // agree on what is valid.
15824
+ name: external_exports.string().min(1).max(80).regex(/\S/, "The resource name must contain a non-whitespace character. A name of only spaces trims to nothing, and an empty display name is not a name."),
15825
+ /**
15826
+ * THE STABLE IDENTITY OF THIS RESOURCE INSIDE ITS PROJECT — and the one
15827
+ * thing that makes applying a document twice different from creating
15828
+ * two resources.
15829
+ *
15830
+ * THIS IS THE FOURTH NAME, AND THE HEADER ABOVE ARGUES AGAINST ONE.
15831
+ * That argument stands and is answered rather than ignored: a
15832
+ * declarative apply is not expressible without a key the AUTHOR knows
15833
+ * before the platform has created anything, and nothing that already
15834
+ * exists is one.
15835
+ *
15836
+ * `metadata.name` mutable, not unique, and rule 1 above says it is
15837
+ * display copy rather than identity.
15838
+ * `pri_…` minted by the server. It does not exist when the
15839
+ * first apply is written, which is the only moment
15840
+ * that matters.
15841
+ * `instanceKey` derived from `pri_…` and then frozen — the same
15842
+ * problem one step removed.
15843
+ * idempotency key a de-duplication token that "names nothing,
15844
+ * grants nothing, and selects nothing". Making it
15845
+ * select a resource would falsify the security
15846
+ * argument the signed-request layer rests on.
15847
+ *
15848
+ * So it gets exactly ONE job: to say which resource in this Project the
15849
+ * document is about. It is NEVER authorization — the Project comes from
15850
+ * the request, the row is loaded inside it, and a key naming nothing
15851
+ * there is a not-found rather than a create. And it NEVER becomes a
15852
+ * provider resource name; `instanceKey` owns that, and coupling the two
15853
+ * would let a customer's chosen string reach Scaleway.
15854
+ *
15855
+ * OPTIONAL HERE, REQUIRED AT APPLY. The document is a published `v1`
15856
+ * contract and validating one on its own is a legitimate thing to do
15857
+ * with no Project in sight — the same asymmetry `resolveTemplate`
15858
+ * already has for `routeProjectId`. What refuses a document without a
15859
+ * key is the apply path, because a create that cannot be run again is
15860
+ * not deployment as code.
15861
+ *
15862
+ * Shaped like a DNS label: it becomes a database predicate and a log
15863
+ * field, and neither wants free text.
15864
+ */
15865
+ key: ResourceKeySchema.optional()
15866
+ }).strict(),
15867
+ spec: external_exports.object({
15868
+ productId: external_exports.string().min(1).max(64),
15869
+ /**
15870
+ * REQUIRED, unlike the create envelope's, which may be omitted to take
15871
+ * the newest deployable release.
15872
+ *
15873
+ * A template is a record of a decision, and "newest" is not one. An
15874
+ * optional version would mean the same document produced 1.0.0 today and
15875
+ * 1.1.0 after an unrelated promotion — so re-applying an exported
15876
+ * template would not reproduce what was exported, which is the single
15877
+ * property that makes templates worth having.
15878
+ */
15879
+ productVersion: external_exports.string().min(1).max(64),
15880
+ /** Round-trip only — see rule 3 above. Never authorization. */
15881
+ projectId: external_exports.string().min(1).max(64).optional(),
15882
+ region: external_exports.string().min(1).max(32),
15883
+ profile: external_exports.string().min(1).max(40),
15884
+ /**
15885
+ * The Product's own settings.
15886
+ *
15887
+ * An OBJECT with product-specific keys, not `unknown`: the published
15888
+ * JSON Schema has to say at least that much, and the converter refuses
15889
+ * `unknown` precisely so a published contract cannot silently widen.
15890
+ * The keys themselves are validated against the pinned Definition's
15891
+ * configuration schema by `resolveTemplate`, which is where the real
15892
+ * shape lives — it differs per Product, so it cannot be stated here.
15893
+ */
15894
+ configuration: external_exports.record(external_exports.unknown()).optional(),
15895
+ /**
15896
+ * Which Resource satisfies each declared service dependency.
15897
+ *
15898
+ * A RESOURCE RELATION, NOT A SETTING, which is why it is a sibling of
15899
+ * `configuration` rather than a key inside it. `configuration` is the
15900
+ * Product's own settings and is validated against the Definition's
15901
+ * schema; this names another Resource, and the create envelope made the
15902
+ * identical split for the identical reason.
15903
+ *
15904
+ * OPTIONAL, PERMANENTLY. Every manifest written before this field
15905
+ * existed omits it, and absence has to keep meaning "leave the
15906
+ * connections alone" — otherwise re-applying an unchanged file would
15907
+ * release a binding somebody made in the portal.
15908
+ *
15909
+ * The keys the platform will ACCEPT here are narrower than the shape:
15910
+ * only a dependency whose interface the registry calls
15911
+ * `tenant-multiple` is one a customer chooses. `resolveTemplate` says
15912
+ * so against the pinned Definition, so the refusal names the field.
15913
+ */
15914
+ dependencyBindings: DependencyBindingsSchema.optional()
15915
+ }).strict()
15916
+ }).strict();
15917
+ function secretValuesInDocument(schema, configuration) {
15918
+ if (!isPlainRecord(configuration) || !isPlainRecord(schema.properties))
15919
+ return [];
15920
+ const declarations = plainDeclarations(schema.properties);
15921
+ return declaredSecretPaths({
15922
+ properties: declarations
15923
+ }).filter((path) => {
15924
+ const segments = pathSegments(path).map(String);
15925
+ let parent = configuration;
15926
+ for (const segment of segments.slice(0, -1)) {
15927
+ parent = isPlainRecord(parent) ? parent[segment] : void 0;
15928
+ }
15929
+ const name = segments[segments.length - 1];
15930
+ return isPlainRecord(parent) && Object.hasOwn(parent, name) && !looksLikeReference(parent[name]);
15931
+ });
15932
+ }
15933
+ function plainDeclarations(properties) {
15934
+ const out = {};
15935
+ for (const [name, property] of Object.entries(properties)) {
15936
+ if (!isPlainRecord(property) || typeof property["type"] !== "string")
15937
+ continue;
15938
+ const copy = { ...property };
15939
+ if (Object.hasOwn(copy, "properties")) {
15940
+ if (isPlainRecord(copy["properties"]))
15941
+ copy["properties"] = plainDeclarations(copy["properties"]);
15942
+ else
15943
+ delete copy["properties"];
15944
+ }
15945
+ if (Object.hasOwn(copy, "items")) {
15946
+ const items = copy["items"];
15947
+ if (!isPlainRecord(items) || typeof items["type"] !== "string")
15948
+ delete copy["items"];
15949
+ else if (Object.hasOwn(items, "properties")) {
15950
+ copy["items"] = isPlainRecord(items["properties"]) ? { ...items, properties: plainDeclarations(items["properties"]) } : (({ properties: _dropped, ...rest }) => rest)(items);
15951
+ }
15952
+ }
15953
+ out[name] = copy;
15954
+ }
15955
+ return out;
15956
+ }
15957
+ function secretValueIssue(name) {
15958
+ return {
15959
+ field: manifestConfigurationField(name),
15960
+ message: `"${name}" is secret input and must never appear in a template. Supply it separately when applying, so it goes to the secret manager rather than into a document.`
15961
+ };
15962
+ }
15963
+ function isPlainRecord(value) {
15964
+ return typeof value === "object" && value !== null && !Array.isArray(value);
15965
+ }
15966
+
15145
15967
  // ../product-contracts/dist/publish-rules.js
15146
15968
  var PRODUCT_DISPLAY_NAME_PATTERN = "^[^\\u2014\\s](?:[^\\u2014]*[^\\u2014\\s])? \\u2014 [^\\u2014\\s](?:[^\\u2014]*[^\\u2014\\s])?$";
15147
15969
  var PRODUCT_DISPLAY_NAME_REGEX = new RegExp(PRODUCT_DISPLAY_NAME_PATTERN, "u");
@@ -15464,8 +16286,12 @@ var EnvBindingV2Schema = external_exports.discriminatedUnion("source", [
15464
16286
  *
15465
16287
  * WHAT IT CARRIES: validated, non-secret configuration with resolved
15466
16288
  * ORDINARY resource references materialised in place. Never a secret
15467
- * value — secrets are root-only and travel through `secrets[]`, and a
15468
- * subtree cannot contain one. Serialised with `canonicalJson`, so the same
16289
+ * value — a secret travels through `secrets[]`, and its value is split out
16290
+ * of the configuration before anything is stored. A secret may sit inside
16291
+ * a group, so `validateProductContracts` refuses a `path` naming a secret
16292
+ * or a group that contains one anywhere below it: a component bound to
16293
+ * that group would read it as complete while its credential is absent.
16294
+ * Serialised with `canonicalJson`, so the same
15469
16295
  * logical configuration is the same bytes and the environment fingerprint
15470
16296
  * does not move on key order alone.
15471
16297
  *
@@ -15815,6 +16641,20 @@ var SecretWiringSchema = external_exports.object({
15815
16641
  envVar: EnvVarNameSchema
15816
16642
  }).strict()).min(1).max(12)
15817
16643
  }).strict();
16644
+ var SecretWiringV2Schema = SecretWiringSchema.extend({
16645
+ /**
16646
+ * LEGACY SPELLING of a ROOT secret's path, kept so released v2 Blueprints
16647
+ * still read. Described here rather than in the declaration table, whose note
16648
+ * is what the v1 artifact publishes for the same field.
16649
+ */
16650
+ configurationProperty: external_exports.string().regex(/^[a-z][a-zA-Z0-9]{1,47}$/).describe("Accepted only so releases published before `configurationPath` existed still read. It names a root secret and means the same as `configurationPath` with that name; a new release must use `configurationPath`, and a Blueprint may not name both.").optional(),
16651
+ /**
16652
+ * Set only for `customer-configuration`: the canonical path of the `x-secret`
16653
+ * setting in the Product Definition whose write-only submission populates
16654
+ * this secret — a bare name at the root, a pointer inside a group.
16655
+ */
16656
+ configurationPath: ConfigurationBindingPathSchema.optional()
16657
+ }).strict();
15818
16658
  var DataCategorySchema = external_exports.enum([
15819
16659
  "prompt-content",
15820
16660
  "model-response",
@@ -16244,7 +17084,8 @@ var DeploymentBlueprintV1ObjectSchema = DeploymentBlueprintObjectSchema.extend({
16244
17084
  });
16245
17085
  var DeploymentBlueprintV2ObjectSchema = DeploymentBlueprintObjectSchema.extend({
16246
17086
  apiVersion: ProductApiVersionV2Schema,
16247
- components: external_exports.array(RuntimeComponentV2Schema).min(1).max(MAX_RUNTIME_COMPONENTS)
17087
+ components: external_exports.array(RuntimeComponentV2Schema).min(1).max(MAX_RUNTIME_COMPONENTS),
17088
+ secrets: external_exports.array(SecretWiringV2Schema).max(20)
16248
17089
  });
16249
17090
  var DeploymentBlueprintV1Schema = DeploymentBlueprintV1ObjectSchema.superRefine(checkDeploymentBlueprint);
16250
17091
  var DeploymentBlueprintV2Schema = DeploymentBlueprintV2ObjectSchema.superRefine(checkDeploymentBlueprint);
@@ -16628,18 +17469,28 @@ function checkDeploymentBlueprint(blueprint, ctx) {
16628
17469
  });
16629
17470
  }
16630
17471
  }
16631
- if (secret.source === "customer-configuration" && secret.configurationProperty === void 0) {
17472
+ const pathField = "configurationPath" in secret ? secret.configurationPath : void 0;
17473
+ const namesSetting = secret.configurationProperty !== void 0 || pathField !== void 0;
17474
+ const settingField = blueprint.apiVersion === PRODUCT_API_VERSION ? "configurationProperty" : "configurationPath";
17475
+ if (secret.configurationProperty !== void 0 && pathField !== void 0) {
17476
+ ctx.addIssue({
17477
+ code: external_exports.ZodIssueCode.custom,
17478
+ path,
17479
+ message: `Secret "${secret.key}" names both configurationProperty and configurationPath. Name the setting once, with configurationPath.`
17480
+ });
17481
+ }
17482
+ if (secret.source === "customer-configuration" && !namesSetting) {
16632
17483
  ctx.addIssue({
16633
17484
  code: external_exports.ZodIssueCode.custom,
16634
17485
  path,
16635
- message: `Secret "${secret.key}" is sourced from customer configuration but names no configurationProperty.`
17486
+ message: `Secret "${secret.key}" is sourced from customer configuration but names no ${settingField}.`
16636
17487
  });
16637
17488
  }
16638
- if (secret.source !== "customer-configuration" && secret.configurationProperty !== void 0) {
17489
+ if (secret.source !== "customer-configuration" && namesSetting) {
16639
17490
  ctx.addIssue({
16640
17491
  code: external_exports.ZodIssueCode.custom,
16641
17492
  path,
16642
- message: `Secret "${secret.key}" names a configurationProperty but is not customer-sourced.`
17493
+ message: `Secret "${secret.key}" names a ${pathField !== void 0 ? "configurationPath" : "configurationProperty"} but is not customer-sourced.`
16643
17494
  });
16644
17495
  }
16645
17496
  if (secret.source === "service-binding" && secret.serviceBinding === void 0) {
@@ -17017,193 +17868,6 @@ function isTerminalAction(action) {
17017
17868
  }
17018
17869
  var ManifestActionSchema = external_exports.enum(MANIFEST_ACTIONS);
17019
17870
 
17020
- // ../product-contracts/dist/contract-issue.js
17021
- var PATH_SEGMENT = /^[A-Za-z_][A-Za-z0-9_-]{0,63}$/;
17022
- var UNPRINTABLE_SEGMENT = "<unprintable>";
17023
- var MAX_SEGMENTS = MAX_CONFIGURATION_PATH_SEGMENTS + 2;
17024
- function printableContractPath(path) {
17025
- const printable = path.slice(0, MAX_SEGMENTS).map((segment) => typeof segment === "number" || PATH_SEGMENT.test(segment) ? String(segment) : UNPRINTABLE_SEGMENT);
17026
- return printable.join(".") || "(document)";
17027
- }
17028
- function manifestConfigurationField(field) {
17029
- if (field === "")
17030
- return "spec.configuration";
17031
- const parsed = parseConfigurationPath(field);
17032
- return printableContractPath([
17033
- "spec",
17034
- "configuration",
17035
- ...parsed === null ? [field] : pathSegments(parsed)
17036
- ]);
17037
- }
17038
-
17039
- // ../product-contracts/dist/resource-output-reference.js
17040
- var OUTPUT_KEY_RULE = "Output keys are camelCase.";
17041
- var ResourceOutputReferenceSchema = external_exports.object({
17042
- valueFrom: external_exports.object({
17043
- resourceOutput: external_exports.object({
17044
- /** The producing Resource's `metadata.key`, in the same Project. */
17045
- resourceKey: ResourceKeySchema,
17046
- /** The key of an output that Resource's Product declares. */
17047
- output: external_exports.string().regex(/^[a-z][a-zA-Z0-9]{1,47}$/, OUTPUT_KEY_RULE)
17048
- }).strict()
17049
- }).strict()
17050
- }).strict();
17051
- function looksLikeReference(value) {
17052
- return typeof value === "object" && value !== null && !Array.isArray(value) && "valueFrom" in value;
17053
- }
17054
-
17055
- // ../product-contracts/dist/dependency-binding.js
17056
- var DependencyBindingSelectorSchema = external_exports.object({
17057
- /** The providing Resource's `metadata.key`. */
17058
- resourceKey: ResourceKeySchema,
17059
- /**
17060
- * The Project that Resource lives in. Absent means the consumer's own.
17061
- *
17062
- * PRESENT BECAUSE A BINDING IS TENANT-SCOPED AND A PROJECT IS NOT A RUNTIME
17063
- * BOUNDARY: a provider anywhere in the Tenant can satisfy a consumer
17064
- * anywhere in it, and that has been true since bindings existed. What the
17065
- * field does NOT do is grant anything — the Control Plane opens that
17066
- * Project from the caller's own assertion, and a Project the caller holds
17067
- * nothing in answers exactly as a Project that does not exist.
17068
- *
17069
- * A plain bounded string, exactly as `spec.projectId` is: an id that does
17070
- * not name a readable Project is refused where the refusal can be phrased
17071
- * without leaking, which is not here.
17072
- */
17073
- projectId: external_exports.string().min(1).max(64).optional()
17074
- }).strict();
17075
- var DependencyBindingsSchema = external_exports.record(ServiceBindingKeySchema, DependencyBindingSelectorSchema);
17076
-
17077
- // ../product-contracts/dist/product-instance-template.schema.js
17078
- var ProductInstanceTemplateSchema = external_exports.object({
17079
- apiVersion: ResourceApiVersionSchema,
17080
- kind: kindSchema(PRODUCT_INSTANCE_KIND),
17081
- metadata: external_exports.object({
17082
- /**
17083
- * The customer-facing Resource name — "Local service".
17084
- *
17085
- * Maps to `ProductInstance.displayName`, and deliberately introduces NO
17086
- * new identity concept. `pri_…` remains the stable identity and
17087
- * `instanceKey` remains the cloud resource key; adding a third name here
17088
- * would mean a fourth thing to keep unique and a fifth to explain.
17089
- * Bounded exactly as the create envelope bounds `displayName`.
17090
- */
17091
- // NOT `.trim()`: this schema is PUBLISHED as JSON Schema so a customer
17092
- // can validate the file before sending it, and a trim is a transform
17093
- // that JSON Schema cannot express. The value is trimmed when it becomes
17094
- // a create command instead, so the published document and the platform
17095
- // agree on what is valid.
17096
- name: external_exports.string().min(1).max(80).regex(/\S/, "The resource name must contain a non-whitespace character. A name of only spaces trims to nothing, and an empty display name is not a name."),
17097
- /**
17098
- * THE STABLE IDENTITY OF THIS RESOURCE INSIDE ITS PROJECT — and the one
17099
- * thing that makes applying a document twice different from creating
17100
- * two resources.
17101
- *
17102
- * THIS IS THE FOURTH NAME, AND THE HEADER ABOVE ARGUES AGAINST ONE.
17103
- * That argument stands and is answered rather than ignored: a
17104
- * declarative apply is not expressible without a key the AUTHOR knows
17105
- * before the platform has created anything, and nothing that already
17106
- * exists is one.
17107
- *
17108
- * `metadata.name` mutable, not unique, and rule 1 above says it is
17109
- * display copy rather than identity.
17110
- * `pri_…` minted by the server. It does not exist when the
17111
- * first apply is written, which is the only moment
17112
- * that matters.
17113
- * `instanceKey` derived from `pri_…` and then frozen — the same
17114
- * problem one step removed.
17115
- * idempotency key a de-duplication token that "names nothing,
17116
- * grants nothing, and selects nothing". Making it
17117
- * select a resource would falsify the security
17118
- * argument the signed-request layer rests on.
17119
- *
17120
- * So it gets exactly ONE job: to say which resource in this Project the
17121
- * document is about. It is NEVER authorization — the Project comes from
17122
- * the request, the row is loaded inside it, and a key naming nothing
17123
- * there is a not-found rather than a create. And it NEVER becomes a
17124
- * provider resource name; `instanceKey` owns that, and coupling the two
17125
- * would let a customer's chosen string reach Scaleway.
17126
- *
17127
- * OPTIONAL HERE, REQUIRED AT APPLY. The document is a published `v1`
17128
- * contract and validating one on its own is a legitimate thing to do
17129
- * with no Project in sight — the same asymmetry `resolveTemplate`
17130
- * already has for `routeProjectId`. What refuses a document without a
17131
- * key is the apply path, because a create that cannot be run again is
17132
- * not deployment as code.
17133
- *
17134
- * Shaped like a DNS label: it becomes a database predicate and a log
17135
- * field, and neither wants free text.
17136
- */
17137
- key: ResourceKeySchema.optional()
17138
- }).strict(),
17139
- spec: external_exports.object({
17140
- productId: external_exports.string().min(1).max(64),
17141
- /**
17142
- * REQUIRED, unlike the create envelope's, which may be omitted to take
17143
- * the newest deployable release.
17144
- *
17145
- * A template is a record of a decision, and "newest" is not one. An
17146
- * optional version would mean the same document produced 1.0.0 today and
17147
- * 1.1.0 after an unrelated promotion — so re-applying an exported
17148
- * template would not reproduce what was exported, which is the single
17149
- * property that makes templates worth having.
17150
- */
17151
- productVersion: external_exports.string().min(1).max(64),
17152
- /** Round-trip only — see rule 3 above. Never authorization. */
17153
- projectId: external_exports.string().min(1).max(64).optional(),
17154
- region: external_exports.string().min(1).max(32),
17155
- profile: external_exports.string().min(1).max(40),
17156
- /**
17157
- * The Product's own settings.
17158
- *
17159
- * An OBJECT with product-specific keys, not `unknown`: the published
17160
- * JSON Schema has to say at least that much, and the converter refuses
17161
- * `unknown` precisely so a published contract cannot silently widen.
17162
- * The keys themselves are validated against the pinned Definition's
17163
- * configuration schema by `resolveTemplate`, which is where the real
17164
- * shape lives — it differs per Product, so it cannot be stated here.
17165
- */
17166
- configuration: external_exports.record(external_exports.unknown()).optional(),
17167
- /**
17168
- * Which Resource satisfies each declared service dependency.
17169
- *
17170
- * A RESOURCE RELATION, NOT A SETTING, which is why it is a sibling of
17171
- * `configuration` rather than a key inside it. `configuration` is the
17172
- * Product's own settings and is validated against the Definition's
17173
- * schema; this names another Resource, and the create envelope made the
17174
- * identical split for the identical reason.
17175
- *
17176
- * OPTIONAL, PERMANENTLY. Every manifest written before this field
17177
- * existed omits it, and absence has to keep meaning "leave the
17178
- * connections alone" — otherwise re-applying an unchanged file would
17179
- * release a binding somebody made in the portal.
17180
- *
17181
- * The keys the platform will ACCEPT here are narrower than the shape:
17182
- * only a dependency whose interface the registry calls
17183
- * `tenant-multiple` is one a customer chooses. `resolveTemplate` says
17184
- * so against the pinned Definition, so the refusal names the field.
17185
- */
17186
- dependencyBindings: DependencyBindingsSchema.optional()
17187
- }).strict()
17188
- }).strict();
17189
- function secretValuesInDocument(schema, configuration) {
17190
- if (!isPlainRecord(configuration) || !isPlainRecord(schema.properties))
17191
- return [];
17192
- const declarations = Object.fromEntries(Object.entries(schema.properties).filter(([, property]) => isPlainRecord(property)));
17193
- return declaredSecretNames({
17194
- properties: declarations
17195
- }).filter((name) => Object.hasOwn(configuration, name) && !looksLikeReference(configuration[name]));
17196
- }
17197
- function secretValueIssue(name) {
17198
- return {
17199
- field: manifestConfigurationField(name),
17200
- message: `"${name}" is secret input and must never appear in a template. Supply it separately when applying, so it goes to the secret manager rather than into a document.`
17201
- };
17202
- }
17203
- function isPlainRecord(value) {
17204
- return typeof value === "object" && value !== null && !Array.isArray(value);
17205
- }
17206
-
17207
17871
  // ../product-contracts/dist/product-submission.schema.js
17208
17872
  var PRODUCT_SUBMISSION_KIND = "ProductSubmission";
17209
17873
  var SubmissionPublisherSchema = external_exports.object({
@@ -17297,13 +17961,32 @@ var PUBLISH_RULE_ANNEX = {
17297
17961
  }
17298
17962
  }
17299
17963
  }
17964
+ },
17965
+ {
17966
+ // THE SAME ASYMMETRY, a third time. Presence and shape are all JSON Schema
17967
+ // can say: whether the region and profile are this Product's, whether the
17968
+ // configuration fills every required setting and whether every required
17969
+ // secret is named are questions about the rest of the document, answered
17970
+ // by preflight.
17971
+ $comment: "A new release must declare definition.deploymentExample: one recommended deployment, with region, profile and configuration. Preflight also checks the region and profile are this Product's, the configuration is complete against its schema, no secret carries a value, and every required secret is named in secretInputs.",
17972
+ properties: {
17973
+ definition: {
17974
+ required: ["deploymentExample"],
17975
+ properties: {
17976
+ deploymentExample: {
17977
+ required: ["title", "description", "region", "profile", "configuration"]
17978
+ }
17979
+ }
17980
+ }
17981
+ }
17300
17982
  }
17301
17983
  ]
17302
17984
  };
17303
17985
  withJsonSchemaAnnex(ProductSubmissionSchema, PUBLISH_RULE_ANNEX);
17986
+ withJsonSchemaAnnex(ProductSubmissionV2Schema, PUBLISH_RULE_ANNEX);
17304
17987
 
17305
17988
  // src/manifest.ts
17306
- var import_yaml = __toESM(require_dist(), 1);
17989
+ var import_yaml2 = __toESM(require_dist(), 1);
17307
17990
  function readManifest(path) {
17308
17991
  let text;
17309
17992
  try {
@@ -17313,7 +17996,7 @@ function readManifest(path) {
17313
17996
  }
17314
17997
  let parsed;
17315
17998
  try {
17316
- parsed = (0, import_yaml.parse)(text, { prettyErrors: false });
17999
+ parsed = (0, import_yaml2.parse)(text, { prettyErrors: false });
17317
18000
  } catch (error) {
17318
18001
  return {
17319
18002
  ok: false,
@@ -17333,15 +18016,21 @@ function resolveSecrets(pairs, environment = process.env) {
17333
18016
  if (separator <= 0) {
17334
18017
  return {
17335
18018
  ok: false,
17336
- message: `${label} expects name=env:VARIABLE, and there is no name before "=".`
18019
+ message: `${label} expects NAME=env:VARIABLE, and there is no name before "=".`
17337
18020
  };
17338
18021
  }
17339
- const name = pair.slice(0, separator);
18022
+ const name = secretPathOf(pair.slice(0, separator));
17340
18023
  const source = pair.slice(separator + 1);
18024
+ if (name === null) {
18025
+ return {
18026
+ ok: false,
18027
+ message: `${label} expects a setting name before "=", or a /group/setting path for a setting inside a group. A path names no list item and does not end in "/".`
18028
+ };
18029
+ }
17341
18030
  if (!source.startsWith("env:")) {
17342
18031
  return {
17343
18032
  ok: false,
17344
- message: `${label} must read from the environment: --secret <name>=env:VARIABLE. A value on the command line is in your shell history and in the process table.`
18033
+ message: `${label} must read from the environment: --secret <name-or-path>=env:VARIABLE. A value on the command line is in your shell history and in the process table.`
17345
18034
  };
17346
18035
  }
17347
18036
  const variable = source.slice("env:".length);
@@ -17349,10 +18038,10 @@ function resolveSecrets(pairs, environment = process.env) {
17349
18038
  return { ok: false, message: `${label} names no variable after "env:".` };
17350
18039
  }
17351
18040
  const value = environment[variable];
17352
- if (value === void 0 || value === "") {
18041
+ if (value === void 0 || value.trim() === "") {
17353
18042
  return {
17354
18043
  ok: false,
17355
- message: `${label} reads an environment variable that is not set, or is empty.`
18044
+ message: `${label} reads an environment variable that is not set, or holds nothing but whitespace.`
17356
18045
  };
17357
18046
  }
17358
18047
  secrets[name] = value;
@@ -17470,8 +18159,14 @@ async function ensureSession(args) {
17470
18159
  }
17471
18160
  };
17472
18161
  }
18162
+ function resolveProjectWithSource(flag, stored) {
18163
+ if (flag !== void 0) return { value: flag, source: "--project" };
18164
+ const selected = stored.context?.projectId;
18165
+ if (selected !== void 0) return { value: selected, source: "the stored context" };
18166
+ return null;
18167
+ }
17473
18168
  function resolveProject(flag, stored) {
17474
- return flag ?? stored.context?.projectId ?? null;
18169
+ return resolveProjectWithSource(flag, stored)?.value ?? null;
17475
18170
  }
17476
18171
 
17477
18172
  // src/operations.ts
@@ -17650,6 +18345,9 @@ function secretWriteConsequence(plan) {
17650
18345
  }
17651
18346
  return lines;
17652
18347
  }
18348
+ function requiredSecretLines(names) {
18349
+ return names.length === 0 ? [] : [" required secrets:", ...names.map((name) => ` ${name}`)];
18350
+ }
17653
18351
  function printPlan(plan, json) {
17654
18352
  if (json) {
17655
18353
  process.stdout.write(`${JSON.stringify(plan)}
@@ -17682,11 +18380,7 @@ function printPlan(plan, json) {
17682
18380
  }
17683
18381
  }
17684
18382
  }
17685
- const required = plan.requiredSecrets ?? [];
17686
- if (required.length > 0) {
17687
- lines.push(" required secrets:");
17688
- for (const name of required) lines.push(` ${name}`);
17689
- }
18383
+ lines.push(...requiredSecretLines(plan.requiredSecrets ?? []));
17690
18384
  if ((plan.secretsToWrite ?? []).length > 0) {
17691
18385
  lines.push(` secrets to set: ${plan.secretsToWrite.join(", ")}`);
17692
18386
  for (const line of secretWriteConsequence(plan)) lines.push(` ${line}`);
@@ -17770,10 +18464,15 @@ async function runApply(verb, parsed, deps = {}) {
17770
18464
  body: { verb: "validate", payload: manifest2.document }
17771
18465
  });
17772
18466
  if (!result.ok) return printError(result.error, json);
18467
+ if (json) {
18468
+ process.stdout.write(`${JSON.stringify(result.data)}
18469
+ `);
18470
+ return 0;
18471
+ }
18472
+ const required = result.data["requiredSecrets"];
18473
+ const names = Array.isArray(required) ? required.filter((name) => typeof name === "string") : [];
17773
18474
  process.stdout.write(
17774
- json ? `${JSON.stringify(result.data)}
17775
- ` : `${file} is a valid manifest.
17776
- `
18475
+ [`${file} is a valid manifest.`, ...requiredSecretLines(names)].join("\n") + "\n"
17777
18476
  );
17778
18477
  return 0;
17779
18478
  }
@@ -18117,13 +18816,18 @@ async function runDoctor(parsed) {
18117
18816
  detail: `${endpoint} (from ${resolved.source})`,
18118
18817
  facts: { endpoint, source: resolved.source }
18119
18818
  });
18120
- const selected = stored.context?.projectId ?? null;
18819
+ const project = resolveProjectWithSource(parsed.flags["project"], stored);
18820
+ const storedProjectId = stored.context?.projectId ?? null;
18121
18821
  checks.push({
18122
18822
  name: "Project",
18123
18823
  ok: true,
18124
- note: selected === null,
18125
- 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)`,
18126
- facts: { selectedProjectId: selected }
18824
+ note: storedProjectId === null,
18825
+ detail: project === 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." : project.source === "--project" ? `${project.value} (from --project, for this command only). ` + (storedProjectId === null ? "Nothing is selected in this context, so every other command needs the flag too. " : `The stored context still selects ${storedProjectId}, which is what a command with no --project uses. `) + "Which Projects the credential reaches is a separate question." : `${project.value} (from the stored context; a command with no --project uses it, and which Projects the credential reaches is a separate question)`,
18826
+ facts: {
18827
+ selectedProjectId: project?.value ?? null,
18828
+ source: project?.source ?? null,
18829
+ storedProjectId
18830
+ }
18127
18831
  });
18128
18832
  const probe = await probeMachineApi({ endpoint });
18129
18833
  if (!probe.ok) {
@@ -18158,13 +18862,13 @@ async function runDoctor(parsed) {
18158
18862
  return report2(checks, json);
18159
18863
  }
18160
18864
  const access = who.data.access;
18161
- const projects = access.projects.map((project) => project.projectId);
18865
+ const projects = access.projects.map((project2) => project2.projectId);
18162
18866
  const mutating = access.projects.filter(
18163
- (project) => project.permissions.some((permission) => !permission.endsWith(".read"))
18867
+ (project2) => project2.permissions.some((permission) => !permission.endsWith(".read"))
18164
18868
  );
18165
18869
  const organizationWide = access.tenantPermissions.includes("project.read");
18166
18870
  const alsoOrganizationWide = access.tenantPermissions.length > 0 ? `Organization-wide it holds ${access.tenantPermissions.join(", ")}, which reaches no Project. ` : "";
18167
- const perProject = projects.length === 0 ? "" : `. Granted per Project: ${projects.join(", ")}` + (mutating.length > 0 ? ` \u2014 you can deploy in ${mutating.map((project) => project.projectId).join(", ")}` : " \u2014 read-only there");
18871
+ const perProject = projects.length === 0 ? "" : `. Granted per Project: ${projects.join(", ")}` + (mutating.length > 0 ? ` \u2014 you can deploy in ${mutating.map((project2) => project2.projectId).join(", ")}` : " \u2014 read-only there");
18168
18872
  checks.push(
18169
18873
  organizationWide ? {
18170
18874
  name: "Access",
@@ -18323,7 +19027,6 @@ function report2(checks, json) {
18323
19027
  }
18324
19028
 
18325
19029
  // src/commands/export.ts
18326
- var import_yaml2 = __toESM(require_dist(), 1);
18327
19030
  async function runExport(parsed) {
18328
19031
  if (parsed.command[1] !== "product-instance" && parsed.command[1] !== "resource") {
18329
19032
  process.stderr.write("Usage: vfac export product-instance pri_\u2026\n");
@@ -18351,7 +19054,7 @@ async function runExport(parsed) {
18351
19054
  if (!result.ok) return printError(result.error, parsed.flags["output"] === "json");
18352
19055
  process.stdout.write(
18353
19056
  parsed.flags["output"] === "json" ? `${JSON.stringify(result.data, null, 2)}
18354
- ` : (0, import_yaml2.stringify)(result.data)
19057
+ ` : manifestYaml(result.data)
18355
19058
  );
18356
19059
  const metadata = result.data["metadata"];
18357
19060
  const hasKey = typeof metadata === "object" && metadata !== null && "key" in metadata;
@@ -18763,14 +19466,57 @@ function renderProduct(product) {
18763
19466
  (profile) => `${profile.id}${profile.default ? " (default)" : ""}`
18764
19467
  );
18765
19468
  lines.push(` profile ${profiles.join(", ") || "(none declared)"}`);
18766
- const seed = seedConfiguration(product.configuration);
18767
- if (seed.open.length > 0 || seed.requiredSecrets.length > 0) {
18768
- lines.push("", " Required configuration");
18769
- for (const name of seed.requiredSecrets) {
18770
- lines.push(` ${name} (secret \u2014 pass --secret ${name}=env:VAR)`);
19469
+ const settings = configurationRequirements(product.configuration);
19470
+ if (settings.length > 0) {
19471
+ lines.push("", ` Settings (${settings.length})`);
19472
+ for (const setting of settings) {
19473
+ lines.push(
19474
+ ` ${identityOf(setting)} ${setting.secret ? "secret" : setting.type} ${TOKEN[setting.requiredness]}`
19475
+ );
19476
+ const requirement = requirementOf(setting);
19477
+ if (requirement !== null) lines.push(...continuation(requirement));
19478
+ if (setting.requires.length > 0 && !mutualClause(setting)) {
19479
+ lines.push(
19480
+ ...continuation(
19481
+ `answering it also requires ${listOf(
19482
+ setting.requires.map((member) => identityOf(member)),
19483
+ "and"
19484
+ )}`
19485
+ )
19486
+ );
19487
+ }
19488
+ if (setting.condition?.carrier === "container") {
19489
+ lines.push(
19490
+ ...continuation(
19491
+ `${printableConfigurationPath(setting.condition.carrierPath)} is required when ${setting.condition.printableSetting} is ${valueList(setting.condition.anyOf)}`
19492
+ )
19493
+ );
19494
+ }
19495
+ if (setting.secret) {
19496
+ lines.push(...continuation(`--secret ${String(setting.path)}=env:VAR`));
19497
+ }
19498
+ if (setting.allowed.length > 0) {
19499
+ lines.push(...continuation(`one of ${valueList(setting.allowed)}`));
19500
+ }
19501
+ if (setting.defaultValue !== void 0) {
19502
+ lines.push(...continuation(`default ${JSON.stringify(setting.defaultValue)}`));
19503
+ }
18771
19504
  }
18772
- for (const setting of seed.open) {
18773
- lines.push(` ${setting.label} ${setting.type ?? ""}`.trimEnd());
19505
+ }
19506
+ const seed = seedConfiguration(product.configuration);
19507
+ const together = seed.groups.filter((group) => group.within !== null);
19508
+ if (together.length > 0) {
19509
+ lines.push("", " Settings that go together");
19510
+ for (const group of together) {
19511
+ const index = group.settings.indexOf(group.trigger);
19512
+ const trigger = index === -1 ? `${group.trigger} (secret)` : group.labels[index] ?? group.trigger;
19513
+ const needs = [
19514
+ ...group.labels.filter((_, at) => at !== index),
19515
+ ...group.secrets.filter((path) => path !== group.trigger).map((path) => `--secret ${path}=env:VAR`)
19516
+ ];
19517
+ lines.push(
19518
+ ` ${trigger} also needs ${needs.join(" + ")} (if ${group.within} is configured)`
19519
+ );
18774
19520
  }
18775
19521
  }
18776
19522
  const dependencies = product.dependencies ?? [];
@@ -18807,9 +19553,66 @@ function renderProduct(product) {
18807
19553
  lines.push(` ${condition.key ?? ""} ${condition.displayName ?? ""}`.trimEnd());
18808
19554
  }
18809
19555
  }
18810
- lines.push("", " `--output json` carries the whole declaration, this is a readable subset.");
19556
+ lines.push(
19557
+ "",
19558
+ " `--output json` carries the whole declaration. Every setting is listed above;",
19559
+ " descriptions and value constraints are in the JSON form."
19560
+ );
19561
+ if (settings.some((setting) => setting.secret)) {
19562
+ lines.push(" A credential is supplied per apply and never written into a manifest.");
19563
+ }
18811
19564
  return lines.join("\n");
18812
19565
  }
19566
+ var TOKEN = {
19567
+ always: "required",
19568
+ "when-value": "required-when",
19569
+ "when-group": "required-if",
19570
+ "with-siblings": "required-with",
19571
+ optional: "optional"
19572
+ };
19573
+ var MAX_LISTED_VALUES = 8;
19574
+ var identityOf = (setting) => setting.secret ? String(setting.path) : setting.printablePath;
19575
+ function valueList(values) {
19576
+ const shown = values.slice(0, MAX_LISTED_VALUES).map((value) => JSON.stringify(value));
19577
+ const rest = values.length - shown.length;
19578
+ const joined = shown.length === 2 ? shown.join(" or ") : shown.join(", ");
19579
+ return rest > 0 ? `${joined} and ${rest} more` : joined;
19580
+ }
19581
+ function requirementOf(setting) {
19582
+ const list = setting.list === null ? "" : ` ${setting.list.printablePath}`;
19583
+ switch (setting.requiredness) {
19584
+ case "always":
19585
+ return setting.perRow ? `in every${list} entry` : null;
19586
+ case "when-value": {
19587
+ const condition = setting.condition;
19588
+ if (condition === null) return null;
19589
+ const rows = condition.anyRow ? " in any entry" : "";
19590
+ return `when ${condition.printableSetting} is ${valueList(condition.anyOf)}${rows}`;
19591
+ }
19592
+ case "when-group": {
19593
+ const owner = setting.containers[setting.containers.length - 1];
19594
+ const base = `once ${owner?.printablePath ?? ""} is configured`;
19595
+ return setting.perRow ? `${base}, in every${list} entry` : base;
19596
+ }
19597
+ case "with-siblings": {
19598
+ const names = (members) => members.map((member) => identityOf(member));
19599
+ const triggers = names(setting.requiredBy);
19600
+ return mutualClause(setting) ? `with ${names(setting.requires).join(", ")}` : `once ${listOf(triggers, "or")} is set`;
19601
+ }
19602
+ case "optional":
19603
+ return setting.perRow ? `one per${list} entry` : null;
19604
+ }
19605
+ }
19606
+ function mutualClause(setting) {
19607
+ const requires = setting.requires.map((member) => String(member.path)).sort();
19608
+ const requiredBy = setting.requiredBy.map((member) => String(member.path)).sort();
19609
+ return requires.length > 0 && requires.join("\0") === requiredBy.join("\0");
19610
+ }
19611
+ function listOf(items, conjunction) {
19612
+ if (items.length <= 1) return items[0] ?? "";
19613
+ return `${items.slice(0, -1).join(", ")} ${conjunction} ${items[items.length - 1]}`;
19614
+ }
19615
+ var continuation = (sentence) => wrap(sentence, 72).map((line) => ` ${line}`);
18813
19616
  function wrap(text, width) {
18814
19617
  const lines = [];
18815
19618
  for (const paragraph of text.split(/\n\s*\n/)) {
@@ -18992,7 +19795,6 @@ Only approve a code you are looking at right now. Waiting\u2026
18992
19795
  }
18993
19796
 
18994
19797
  // src/commands/manifest-init.ts
18995
- var import_yaml3 = __toESM(require_dist(), 1);
18996
19798
  async function runManifestInit(parsed) {
18997
19799
  const verb = parsed.command[1];
18998
19800
  if (verb !== "init") {
@@ -19074,7 +19876,7 @@ async function runManifestInit(parsed) {
19074
19876
  );
19075
19877
  }
19076
19878
  for (const group of seed.groups) {
19077
- const settings = group.settings.map((name2) => `spec.configuration.${name2}`);
19879
+ const settings = group.labels.map((label) => `spec.configuration.${label}`);
19078
19880
  const secrets = group.secrets.map((name2) => `--secret ${name2}=env:VAR`);
19079
19881
  notes.push(
19080
19882
  `${[...settings, ...secrets].join(" + ")} \u2014 all of these together, or none of them`
@@ -19097,7 +19899,7 @@ ${where}`);
19097
19899
  }
19098
19900
  process.stdout.write(
19099
19901
  parsed.flags["output"] === "json" ? `${JSON.stringify(document, null, 2)}
19100
- ` : (0, import_yaml3.stringify)(document)
19902
+ ` : manifestYaml(document)
19101
19903
  );
19102
19904
  if (notes.length > 0) {
19103
19905
  process.stderr.write("\nStill yours to fill in:\n");
@@ -19290,7 +20092,9 @@ Options
19290
20092
  --endpoint your organization's portal. Defaults to VFAC_ENDPOINT, then the
19291
20093
  stored context
19292
20094
  --secret plan and apply: NAME=env:VARIABLE \u2014 the value is read from the
19293
- environment. Plan a run that sets a secret with the same
20095
+ environment. NAME is the setting's name, or its path when it
20096
+ sits inside a group: --secret /upstreamAuth/clientSecret=env:VAR.
20097
+ Plan a run that sets a secret with the same
19294
20098
  --secret inputs the apply will carry; without them the plan
19295
20099
  answers about a different request. A value on the command line
19296
20100
  is in your shell history and in the process table, so it is
@@ -19309,12 +20113,16 @@ Options
19309
20113
  --output json, for a pipeline that reads the answer
19310
20114
 
19311
20115
  Diagnosing
19312
- vfac doctor checks the endpoint, the machine API, whether this client is new
19313
- enough for that platform, the credential and what that credential
19314
- can reach \u2014 in that order, so the first thing that is actually
19315
- wrong is the thing it reports. It ends by asking npm whether a
19316
- newer vfac has been published, which is a note and never a
19317
- failure. Set VFAC_NO_UPDATE_CHECK to skip that one request
20116
+ vfac doctor checks the config it could read, the endpoint, which Project this
20117
+ terminal has selected and which one the command will act in, the
20118
+ machine API, whether this client is new enough for that platform,
20119
+ how this terminal is signed in and what that credential can reach
20120
+ \u2014 in that order, so the first thing that is actually wrong is the
20121
+ thing it reports. Pass --project to ask about the Project a
20122
+ command would name, which changes nothing on disk. It ends by
20123
+ asking npm whether a newer vfac has been published, which is a
20124
+ note and never a failure. Set VFAC_NO_UPDATE_CHECK to skip that
20125
+ one request
19318
20126
  vfac whoami what this credential is, and which Project scopes it has been
19319
20127
  GRANTED. A credential scoped to one Project deploys fine and
19320
20128
  cannot make organization-wide reads \u2014 this is where that is said.