@varde-flyt/vfac 0.14.0 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -0
- package/dist/vfac.mjs +992 -386
- 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
|
|
12085
|
-
*
|
|
12086
|
-
*
|
|
12087
|
-
*
|
|
12088
|
-
*
|
|
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
|
|
12113
|
-
* comment
|
|
12114
|
-
*
|
|
12115
|
-
*
|
|
12116
|
-
*
|
|
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
|
|
12162
|
-
var
|
|
12163
|
-
string:
|
|
12164
|
-
number: V2_LEAVES.number.
|
|
12165
|
-
boolean: V2_LEAVES.boolean.
|
|
12166
|
-
array: V2_LEAVES.array.
|
|
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
|
-
|
|
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
|
|
12199
|
-
const
|
|
12200
|
-
const
|
|
12201
|
-
const
|
|
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
|
-
|
|
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 `
|
|
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:
|
|
12247
|
-
...
|
|
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,
|
|
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.
|
|
12352
|
-
// answer is to declare the two markers. Below
|
|
12353
|
-
//
|
|
12354
|
-
//
|
|
12355
|
-
//
|
|
12356
|
-
//
|
|
12357
|
-
message:
|
|
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;
|
|
@@ -12932,6 +13210,18 @@ function immediateContainer(node) {
|
|
|
12932
13210
|
function underList(node) {
|
|
12933
13211
|
return node.containers.some((container) => container.kind === "array");
|
|
12934
13212
|
}
|
|
13213
|
+
function containersRequiredToRoot(schema, node) {
|
|
13214
|
+
const rootRequired = schema.required ?? [];
|
|
13215
|
+
return node.containers.every((container, index) => {
|
|
13216
|
+
const parent = node.containers[index - 1];
|
|
13217
|
+
return (parent === void 0 ? rootRequired : parent.required).includes(container.name);
|
|
13218
|
+
});
|
|
13219
|
+
}
|
|
13220
|
+
function requiredToRoot(schema, node) {
|
|
13221
|
+
const parent = immediateContainer(node);
|
|
13222
|
+
const declared = parent === null ? schema.required ?? [] : parent.required;
|
|
13223
|
+
return declared.includes(node.name) && containersRequiredToRoot(schema, node);
|
|
13224
|
+
}
|
|
12935
13225
|
function containerChildren(property) {
|
|
12936
13226
|
if (property.type === "object" && property.properties !== void 0) {
|
|
12937
13227
|
return {
|
|
@@ -12990,6 +13280,9 @@ function walkDeclarations(properties, ancestors, base, onLeaf, onContainer = ()
|
|
|
12990
13280
|
walkDeclarations(children.properties, [...ancestors, container], segments, onLeaf, onContainer);
|
|
12991
13281
|
}
|
|
12992
13282
|
}
|
|
13283
|
+
function declaredSecretPaths(schema) {
|
|
13284
|
+
return configurationLeaves(schema).filter((leaf) => isSecretProperty(leaf.property) && !underList(leaf)).map((leaf) => leaf.path).sort();
|
|
13285
|
+
}
|
|
12993
13286
|
function declaredDefault(property) {
|
|
12994
13287
|
if (property.type === "object")
|
|
12995
13288
|
return void 0;
|
|
@@ -13025,7 +13318,8 @@ function expandDeclarationPath(configuration, declaration) {
|
|
|
13025
13318
|
const [head, ...rest] = remaining;
|
|
13026
13319
|
if (head === void 0)
|
|
13027
13320
|
return;
|
|
13028
|
-
|
|
13321
|
+
const key = String(head);
|
|
13322
|
+
walk(Object.hasOwn(cursor, key) ? cursor[key] : void 0, rest, [...soFar, head]);
|
|
13029
13323
|
};
|
|
13030
13324
|
walk(configuration, segmentsOf(declaration), []);
|
|
13031
13325
|
return out;
|
|
@@ -13039,7 +13333,7 @@ function valueAt(configuration, path) {
|
|
|
13039
13333
|
cursor = cursor[segment];
|
|
13040
13334
|
continue;
|
|
13041
13335
|
}
|
|
13042
|
-
if (!isPlainObject(cursor))
|
|
13336
|
+
if (!isPlainObject(cursor) || !Object.hasOwn(cursor, segment))
|
|
13043
13337
|
return void 0;
|
|
13044
13338
|
cursor = cursor[segment];
|
|
13045
13339
|
}
|
|
@@ -13055,7 +13349,7 @@ function valuesAt(configuration, path) {
|
|
|
13055
13349
|
next.push(cursor[segment]);
|
|
13056
13350
|
continue;
|
|
13057
13351
|
}
|
|
13058
|
-
if (!isPlainObject(cursor))
|
|
13352
|
+
if (!isPlainObject(cursor) || !Object.hasOwn(cursor, segment))
|
|
13059
13353
|
continue;
|
|
13060
13354
|
const value = cursor[segment];
|
|
13061
13355
|
if (Array.isArray(value))
|
|
@@ -13076,10 +13370,6 @@ function segmentsOf(path) {
|
|
|
13076
13370
|
function conditionSetting(condition) {
|
|
13077
13371
|
return "path" in condition ? condition.path : condition.property;
|
|
13078
13372
|
}
|
|
13079
|
-
function conditionRoot(condition) {
|
|
13080
|
-
const path = parseConfigurationPath(conditionSetting(condition));
|
|
13081
|
-
return path === null ? conditionSetting(condition) : rootSegment(path);
|
|
13082
|
-
}
|
|
13083
13373
|
function conditionHolds(condition, configuration) {
|
|
13084
13374
|
const path = parseConfigurationPath(conditionSetting(condition));
|
|
13085
13375
|
if (path === null)
|
|
@@ -13087,6 +13377,28 @@ function conditionHolds(condition, configuration) {
|
|
|
13087
13377
|
const matches2 = (held) => typeof held === "string" && condition.anyOf.includes(held);
|
|
13088
13378
|
return "anyRow" in condition && condition.anyRow === true ? valuesAt(configuration, path).some(matches2) : matches2(valueAt(configuration, path));
|
|
13089
13379
|
}
|
|
13380
|
+
function conditionOf(property) {
|
|
13381
|
+
return property?.["x-required-when"];
|
|
13382
|
+
}
|
|
13383
|
+
function conditionCarriers(schema) {
|
|
13384
|
+
const settings = configurationLeaves(schema).flatMap((leaf) => {
|
|
13385
|
+
const condition = conditionOf(leaf.property);
|
|
13386
|
+
if (condition === void 0 || underList(leaf))
|
|
13387
|
+
return [];
|
|
13388
|
+
const { path, name, containers, property } = leaf;
|
|
13389
|
+
return [{ kind: "leaf", path, name, containers, property, condition }];
|
|
13390
|
+
});
|
|
13391
|
+
const lists = configurationContainers(schema).flatMap((container) => {
|
|
13392
|
+
const condition = conditionOf(container.property);
|
|
13393
|
+
if (condition === void 0 || container.kind !== "array" || container.containers.length > 0)
|
|
13394
|
+
return [];
|
|
13395
|
+
const { path, name, containers, property } = container;
|
|
13396
|
+
return [{ kind: "container", path, name, containers, property, condition }];
|
|
13397
|
+
});
|
|
13398
|
+
const roots = Object.keys(schema.properties);
|
|
13399
|
+
const rootIndex = (carrier) => roots.indexOf(carrier.containers[0]?.name ?? carrier.name);
|
|
13400
|
+
return [...settings, ...lists].sort((a, b) => rootIndex(a) - rootIndex(b));
|
|
13401
|
+
}
|
|
13090
13402
|
|
|
13091
13403
|
// ../product-configuration/dist/customer-configuration-input.js
|
|
13092
13404
|
var ABSOLUTE_MAX_STRING = MAX_DECLARABLE_STRING_LENGTH;
|
|
@@ -13096,6 +13408,7 @@ function isPlainObject2(value) {
|
|
|
13096
13408
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
13097
13409
|
}
|
|
13098
13410
|
function report(walk, field, message) {
|
|
13411
|
+
walk.reported.add(field);
|
|
13099
13412
|
if (walk.issues.length >= MAX_CONFIGURATION_ISSUES) {
|
|
13100
13413
|
walk.suppressed += 1;
|
|
13101
13414
|
return;
|
|
@@ -13111,7 +13424,10 @@ function validateSubmittedConfiguration(schema, submitted, presence = {}) {
|
|
|
13111
13424
|
issues: [],
|
|
13112
13425
|
suppressed: 0,
|
|
13113
13426
|
leaves: 0,
|
|
13114
|
-
referenced: new Set(presence.referenced ?? [])
|
|
13427
|
+
referenced: new Set(presence.referenced ?? []),
|
|
13428
|
+
present: new Set(presence.present ?? []),
|
|
13429
|
+
unknown: new Set(presence.unknown ?? []),
|
|
13430
|
+
reported: /* @__PURE__ */ new Set()
|
|
13115
13431
|
};
|
|
13116
13432
|
const supplied = /* @__PURE__ */ new Set();
|
|
13117
13433
|
if (submitted !== void 0 && submitted !== null && !isPlainObject2(submitted)) {
|
|
@@ -13124,7 +13440,7 @@ function validateSubmittedConfiguration(schema, submitted, presence = {}) {
|
|
|
13124
13440
|
const input = isPlainObject2(submitted) ? submitted : {};
|
|
13125
13441
|
const secrets = {};
|
|
13126
13442
|
const configuration = validateObject({ properties: schema.properties, required: schema.required }, input, null, walk, { secrets, supplied });
|
|
13127
|
-
drain(walk, crossFieldIssues(schema, supplied, presence, configuration));
|
|
13443
|
+
drain(walk, crossFieldIssues(schema, supplied, presence, configuration, walk.reported));
|
|
13128
13444
|
drain(walk, rowAndReferenceIssues(schema, presence, configuration));
|
|
13129
13445
|
if (walk.suppressed > 0) {
|
|
13130
13446
|
walk.issues.push({
|
|
@@ -13149,13 +13465,17 @@ function declaresContainer(schema) {
|
|
|
13149
13465
|
return Object.values(schema.properties).some((property) => isConfigurationContainer(property));
|
|
13150
13466
|
}
|
|
13151
13467
|
function containsReferenced(walk, container) {
|
|
13152
|
-
|
|
13153
|
-
|
|
13468
|
+
return containsAny(walk.referenced, container);
|
|
13469
|
+
}
|
|
13470
|
+
function referencedAtOrInside(walk, path) {
|
|
13471
|
+
return walk.referenced.has(path) || containsReferenced(walk, path);
|
|
13472
|
+
}
|
|
13473
|
+
function containsAny(candidates, container) {
|
|
13154
13474
|
const parsedOuter = parseConfigurationPath(container);
|
|
13155
13475
|
if (parsedOuter === null)
|
|
13156
13476
|
return false;
|
|
13157
13477
|
const outer = pathSegments(parsedOuter);
|
|
13158
|
-
for (const candidate of
|
|
13478
|
+
for (const candidate of candidates) {
|
|
13159
13479
|
const parsed = parseConfigurationPath(candidate);
|
|
13160
13480
|
if (parsed === null)
|
|
13161
13481
|
continue;
|
|
@@ -13167,7 +13487,7 @@ function containsReferenced(walk, container) {
|
|
|
13167
13487
|
}
|
|
13168
13488
|
return false;
|
|
13169
13489
|
}
|
|
13170
|
-
function validateObject(declaration, input, container, walk,
|
|
13490
|
+
function validateObject(declaration, input, container, walk, sink) {
|
|
13171
13491
|
const required = new Set(declaration.required ?? []);
|
|
13172
13492
|
const out = {};
|
|
13173
13493
|
const fieldFor = (name) => {
|
|
@@ -13185,11 +13505,13 @@ function validateObject(declaration, input, container, walk, root) {
|
|
|
13185
13505
|
const field = fieldFor(name);
|
|
13186
13506
|
const children = containerDeclaration(property);
|
|
13187
13507
|
if (children !== null) {
|
|
13188
|
-
const value = input[name];
|
|
13189
|
-
const filled = validateContainer(name, property, children, value, field, walk);
|
|
13508
|
+
const value = Object.hasOwn(input, name) ? input[name] : void 0;
|
|
13509
|
+
const filled = validateContainer(name, property, children, value, field, walk, sink);
|
|
13510
|
+
if (value === void 0)
|
|
13511
|
+
absentGroupRuleIssues(children, field, walk, sink, required.has(name));
|
|
13190
13512
|
if (filled !== void 0)
|
|
13191
13513
|
out[name] = filled;
|
|
13192
|
-
else if (required.has(name) && !containsReferenced(walk, field))
|
|
13514
|
+
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
13515
|
report(walk, field, "This setting is required.");
|
|
13194
13516
|
continue;
|
|
13195
13517
|
}
|
|
@@ -13204,9 +13526,13 @@ function validateObject(declaration, input, container, walk, root) {
|
|
|
13204
13526
|
const raw = input[name];
|
|
13205
13527
|
const blank = typeof raw === "string" && raw.trim().length === 0;
|
|
13206
13528
|
const present = Object.hasOwn(input, name) && raw !== void 0 && !blank;
|
|
13207
|
-
const secret =
|
|
13208
|
-
if (
|
|
13209
|
-
|
|
13529
|
+
const secret = isSecretProperty(leaf);
|
|
13530
|
+
if (secret && sink === null) {
|
|
13531
|
+
report(walk, field, "This Product declares a secret inside a list, which this contract format does not support.");
|
|
13532
|
+
continue;
|
|
13533
|
+
}
|
|
13534
|
+
if (present && sink !== null && field !== "")
|
|
13535
|
+
sink.supplied.add(field);
|
|
13210
13536
|
if (!present) {
|
|
13211
13537
|
const fallback = secret ? void 0 : defaultOf(leaf);
|
|
13212
13538
|
if (fallback !== void 0) {
|
|
@@ -13225,27 +13551,96 @@ function validateObject(declaration, input, container, walk, root) {
|
|
|
13225
13551
|
continue;
|
|
13226
13552
|
}
|
|
13227
13553
|
if (secret) {
|
|
13228
|
-
if (typeof checked === "string" && checked.length > 0)
|
|
13229
|
-
|
|
13554
|
+
if (typeof checked === "string" && checked.length > 0 && field !== "")
|
|
13555
|
+
sink.secrets[field] = checked;
|
|
13230
13556
|
else if (required.has(name))
|
|
13231
13557
|
report(walk, field, "This setting is required.");
|
|
13232
13558
|
continue;
|
|
13233
13559
|
}
|
|
13234
13560
|
out[name] = checked;
|
|
13235
13561
|
}
|
|
13236
|
-
|
|
13237
|
-
|
|
13238
|
-
|
|
13239
|
-
|
|
13562
|
+
groupRuleIssues(declaration, new Set(Object.keys(out)), container, walk, sink);
|
|
13563
|
+
return out;
|
|
13564
|
+
}
|
|
13565
|
+
function groupRuleIssues(declaration, suppliedHere, container, walk, sink) {
|
|
13566
|
+
if (declaration.dependentRequired === void 0)
|
|
13567
|
+
return;
|
|
13568
|
+
const fieldFor = (name) => (container === null ? configurationPath(name) : configurationPath(...pathSegments(container), name)) ?? "";
|
|
13569
|
+
const answered = (name) => {
|
|
13570
|
+
const path = fieldFor(name);
|
|
13571
|
+
return suppliedHere.has(name) || // A MEMBER, NOT A CONTAINER: a referenced leaf is recorded at this very
|
|
13572
|
+
// path, and a member group is answered by a reference inside it. Asking
|
|
13573
|
+
// only "inside" never counted a referenced leaf, so a referenced trigger
|
|
13574
|
+
// turned nothing on and a referenced dependent was reported missing.
|
|
13575
|
+
referencedAtOrInside(walk, path) || walk.present.has(path) || sink !== null && Object.hasOwn(sink.secrets, path);
|
|
13576
|
+
};
|
|
13577
|
+
const unseen = (name) => walk.unknown.has(fieldFor(name));
|
|
13578
|
+
const reported = /* @__PURE__ */ new Set();
|
|
13579
|
+
for (const [trigger, dependents] of Object.entries(declaration.dependentRequired)) {
|
|
13580
|
+
if (unseen(trigger) || !answered(trigger))
|
|
13581
|
+
continue;
|
|
13582
|
+
for (const dependent of dependents) {
|
|
13583
|
+
if (unseen(dependent) || answered(dependent) || reported.has(dependent))
|
|
13240
13584
|
continue;
|
|
13241
|
-
|
|
13242
|
-
|
|
13243
|
-
continue;
|
|
13244
|
-
report(walk, fieldFor(dependent), `This setting is required when ${labelOf(declaration, trigger)} is configured.`);
|
|
13245
|
-
}
|
|
13585
|
+
reported.add(dependent);
|
|
13586
|
+
report(walk, fieldFor(dependent), `This setting is required when ${labelOf(declaration, trigger)} is configured.`);
|
|
13246
13587
|
}
|
|
13247
13588
|
}
|
|
13248
|
-
|
|
13589
|
+
}
|
|
13590
|
+
function absentGroupRuleIssues(children, field, walk, sink, coveredByParent) {
|
|
13591
|
+
if (children.kind !== "object" || sink === null)
|
|
13592
|
+
return;
|
|
13593
|
+
const path = parseConfigurationPath(field);
|
|
13594
|
+
if (path === null)
|
|
13595
|
+
return;
|
|
13596
|
+
groupRuleIssues(children, /* @__PURE__ */ new Set(), path, walk, sink);
|
|
13597
|
+
const configured = containsReferenced(walk, field) || containsAny(walk.present, field);
|
|
13598
|
+
const required = new Set(children.required ?? []);
|
|
13599
|
+
for (const [name, property] of Object.entries(children.properties)) {
|
|
13600
|
+
const memberField = configurationPath(...pathSegments(path), name);
|
|
13601
|
+
if (memberField === null)
|
|
13602
|
+
continue;
|
|
13603
|
+
const demanded = configured && !coveredByParent && required.has(name) && !walk.unknown.has(memberField);
|
|
13604
|
+
const inner = containerDeclaration(property);
|
|
13605
|
+
if (inner === null) {
|
|
13606
|
+
if (!demanded || walk.referenced.has(memberField) || walk.present.has(memberField))
|
|
13607
|
+
continue;
|
|
13608
|
+
if (!isSecretProperty(property) && defaultOf(property) !== void 0)
|
|
13609
|
+
continue;
|
|
13610
|
+
report(walk, memberField, "This setting is required.");
|
|
13611
|
+
continue;
|
|
13612
|
+
}
|
|
13613
|
+
if (demanded && inner.kind === "object" && !(containsReferenced(walk, memberField) || containsAny(walk.present, memberField))) {
|
|
13614
|
+
report(walk, memberField, "This setting is required.");
|
|
13615
|
+
continue;
|
|
13616
|
+
}
|
|
13617
|
+
absentGroupRuleIssues(inner, memberField, walk, sink, coveredByParent && required.has(name));
|
|
13618
|
+
}
|
|
13619
|
+
}
|
|
13620
|
+
function absentGroupAnswered(children, field, walk) {
|
|
13621
|
+
if (children.kind !== "object")
|
|
13622
|
+
return true;
|
|
13623
|
+
const parent = parseConfigurationPath(field);
|
|
13624
|
+
if (parent === null)
|
|
13625
|
+
return false;
|
|
13626
|
+
for (const name of children.required ?? []) {
|
|
13627
|
+
const property = children.properties[name];
|
|
13628
|
+
const path = configurationPath(...pathSegments(parent), name);
|
|
13629
|
+
if (property === void 0 || path === null)
|
|
13630
|
+
return false;
|
|
13631
|
+
const inner = containerDeclaration(property);
|
|
13632
|
+
if (inner !== null) {
|
|
13633
|
+
if (!(containsReferenced(walk, path) || containsAny(walk.present, path)) || !absentGroupAnswered(inner, path, walk))
|
|
13634
|
+
return false;
|
|
13635
|
+
continue;
|
|
13636
|
+
}
|
|
13637
|
+
if (walk.referenced.has(path) || walk.present.has(path))
|
|
13638
|
+
continue;
|
|
13639
|
+
if (!isSecretProperty(property) && defaultOf(property) !== void 0)
|
|
13640
|
+
continue;
|
|
13641
|
+
return false;
|
|
13642
|
+
}
|
|
13643
|
+
return true;
|
|
13249
13644
|
}
|
|
13250
13645
|
function containerDeclaration(property) {
|
|
13251
13646
|
if (property.type === "object" && property.properties !== void 0) {
|
|
@@ -13275,7 +13670,7 @@ function containerDeclaration(property) {
|
|
|
13275
13670
|
}
|
|
13276
13671
|
return null;
|
|
13277
13672
|
}
|
|
13278
|
-
function validateContainer(name, property, children, value, field, walk) {
|
|
13673
|
+
function validateContainer(name, property, children, value, field, walk, sink) {
|
|
13279
13674
|
const path = parseConfigurationPath(field);
|
|
13280
13675
|
if (children.kind === "object") {
|
|
13281
13676
|
if (value === void 0)
|
|
@@ -13284,7 +13679,7 @@ function validateContainer(name, property, children, value, field, walk) {
|
|
|
13284
13679
|
report(walk, field, "Expected a group of settings.");
|
|
13285
13680
|
return void 0;
|
|
13286
13681
|
}
|
|
13287
|
-
const filled = validateObject(children, value, path, walk,
|
|
13682
|
+
const filled = validateObject(children, value, path, walk, sink);
|
|
13288
13683
|
return Object.keys(filled).length === 0 ? void 0 : filled;
|
|
13289
13684
|
}
|
|
13290
13685
|
if (value === void 0)
|
|
@@ -13323,10 +13718,10 @@ function validateContainer(name, property, children, value, field, walk) {
|
|
|
13323
13718
|
}
|
|
13324
13719
|
return out;
|
|
13325
13720
|
}
|
|
13326
|
-
function crossFieldIssues(schema, supplied, presence, configuration
|
|
13721
|
+
function crossFieldIssues(schema, supplied, presence, configuration, reportedByWalk) {
|
|
13327
13722
|
const clauses = Object.entries(schema.dependentRequired ?? {});
|
|
13328
|
-
const
|
|
13329
|
-
if (clauses.length === 0 &&
|
|
13723
|
+
const carriers = conditionCarriers(schema);
|
|
13724
|
+
if (clauses.length === 0 && carriers.length === 0)
|
|
13330
13725
|
return [];
|
|
13331
13726
|
const alsoPresent = /* @__PURE__ */ new Set([...presence.present ?? [], ...presence.referenced ?? []]);
|
|
13332
13727
|
const unknown = new Set(presence.unknown ?? []);
|
|
@@ -13349,22 +13744,28 @@ function crossFieldIssues(schema, supplied, presence, configuration = {}) {
|
|
|
13349
13744
|
});
|
|
13350
13745
|
}
|
|
13351
13746
|
}
|
|
13352
|
-
for (const
|
|
13353
|
-
|
|
13354
|
-
if (condition === void 0 || !known(name) || held(name))
|
|
13747
|
+
for (const { path, condition } of carriers) {
|
|
13748
|
+
if (!known(path) || held(path))
|
|
13355
13749
|
continue;
|
|
13356
|
-
|
|
13750
|
+
const target = parseConfigurationPath(conditionSetting(condition));
|
|
13751
|
+
if (target === null || !conditionHolds(condition, configuration))
|
|
13357
13752
|
continue;
|
|
13358
|
-
if (reported.has(
|
|
13753
|
+
if (reported.has(path) || reportedByWalk.has(path))
|
|
13359
13754
|
continue;
|
|
13360
|
-
reported.add(
|
|
13755
|
+
reported.add(path);
|
|
13361
13756
|
issues.push({
|
|
13362
|
-
field:
|
|
13757
|
+
field: path,
|
|
13363
13758
|
// THE SETTING THAT MADE IT NECESSARY, never the value it holds. A message
|
|
13364
13759
|
// naming the value would be fine here and wrong one refactor later, when
|
|
13365
13760
|
// somebody conditions a secret on another secret — which the publish rules
|
|
13366
13761
|
// refuse precisely because nothing may read one.
|
|
13367
|
-
|
|
13762
|
+
//
|
|
13763
|
+
// THE SETTING THE CONDITION READS, not the root it descends from. The two
|
|
13764
|
+
// were one string while a condition could only name a root setting or a
|
|
13765
|
+
// row of a root list; naming a setting in a group by its root would tell
|
|
13766
|
+
// the customer to look at a whole section. A root scalar reads exactly as
|
|
13767
|
+
// it always did, and a row's setting now names its list beside it.
|
|
13768
|
+
message: `This setting is required because of what ${labelOfPath(schema, target)} is set to.`
|
|
13368
13769
|
});
|
|
13369
13770
|
}
|
|
13370
13771
|
return issues;
|
|
@@ -13469,9 +13870,6 @@ function labelOfChild(container, name) {
|
|
|
13469
13870
|
const title = items?.properties?.[name]?.title;
|
|
13470
13871
|
return title === void 0 ? `"${name}"` : `"${title}"`;
|
|
13471
13872
|
}
|
|
13472
|
-
function conditionOf(property) {
|
|
13473
|
-
return property?.["x-required-when"];
|
|
13474
|
-
}
|
|
13475
13873
|
function labelOf(schema, name) {
|
|
13476
13874
|
const property = schema.properties[name];
|
|
13477
13875
|
return property?.title === void 0 ? `"${name}"` : `"${property.title}"`;
|
|
@@ -13516,9 +13914,6 @@ function withoutPropertyRules(properties) {
|
|
|
13516
13914
|
}
|
|
13517
13915
|
return out;
|
|
13518
13916
|
}
|
|
13519
|
-
function declaredSecretNames(schema) {
|
|
13520
|
-
return Object.entries(schema.properties).filter(([, property]) => isSecretProperty(property)).map(([name]) => name).sort();
|
|
13521
|
-
}
|
|
13522
13917
|
function defaultOf(property) {
|
|
13523
13918
|
switch (property.type) {
|
|
13524
13919
|
case "string":
|
|
@@ -13675,23 +14070,52 @@ function seedConfiguration(configuration) {
|
|
|
13675
14070
|
const required = new Set(configuration?.required ?? []);
|
|
13676
14071
|
const open = [];
|
|
13677
14072
|
const requiredSecrets = [];
|
|
13678
|
-
const groups =
|
|
14073
|
+
const groups = [];
|
|
14074
|
+
collectGroups(configuration ?? {}, [], groups);
|
|
14075
|
+
const values = seedProperties(properties, required, [], open, requiredSecrets);
|
|
14076
|
+
return { values, open, requiredSecrets, groups };
|
|
14077
|
+
}
|
|
14078
|
+
function collectGroups(node, ancestors, groups) {
|
|
14079
|
+
const properties = node.properties ?? {};
|
|
14080
|
+
const pathOf = (name) => ancestors.length === 0 ? name : configurationPath(...ancestors, name) ?? name;
|
|
14081
|
+
const labelOf2 = (name) => {
|
|
14082
|
+
const path = configurationPath(...ancestors, name);
|
|
14083
|
+
return path === null ? name : printableConfigurationPath(path);
|
|
14084
|
+
};
|
|
14085
|
+
const container = ancestors.length === 0 ? null : configurationPath(...ancestors);
|
|
14086
|
+
const within = container === null ? null : printableConfigurationPath(container);
|
|
14087
|
+
const clauses = Object.entries(node.dependentRequired ?? {}).map(([trigger, dependents]) => {
|
|
13679
14088
|
const members = [trigger, ...dependents];
|
|
14089
|
+
const settings = members.filter((name) => !isSecret(properties[name] ?? {}));
|
|
13680
14090
|
return {
|
|
13681
14091
|
trigger,
|
|
13682
|
-
|
|
13683
|
-
|
|
14092
|
+
group: {
|
|
14093
|
+
trigger: pathOf(trigger),
|
|
14094
|
+
settings: settings.map(pathOf),
|
|
14095
|
+
labels: settings.map(labelOf2),
|
|
14096
|
+
secrets: members.filter((name) => isSecret(properties[name] ?? {})).map(pathOf),
|
|
14097
|
+
within
|
|
14098
|
+
}
|
|
13684
14099
|
};
|
|
13685
14100
|
}).sort((a, b) => a.trigger.localeCompare(b.trigger));
|
|
13686
|
-
|
|
13687
|
-
|
|
14101
|
+
groups.push(...clauses.map((clause) => clause.group));
|
|
14102
|
+
for (const [name, declaration] of Object.entries(properties)) {
|
|
14103
|
+
if (isSecret(declaration))
|
|
14104
|
+
continue;
|
|
14105
|
+
const children = containerChildren2(declaration);
|
|
14106
|
+
if (children === null)
|
|
14107
|
+
continue;
|
|
14108
|
+
collectGroups(children, [...ancestors, name], groups);
|
|
14109
|
+
}
|
|
13688
14110
|
}
|
|
13689
14111
|
function seedProperties(properties, required, ancestors, open, requiredSecrets) {
|
|
13690
14112
|
const values = {};
|
|
13691
14113
|
for (const [name, declaration] of Object.entries(properties)) {
|
|
13692
|
-
if (
|
|
13693
|
-
|
|
13694
|
-
|
|
14114
|
+
if (isSecret(declaration)) {
|
|
14115
|
+
const path = ancestors.length === 0 ? name : configurationPath(...ancestors, name);
|
|
14116
|
+
if (requiredSecrets !== null && required.has(name) && path !== null) {
|
|
14117
|
+
requiredSecrets.push(path);
|
|
14118
|
+
}
|
|
13695
14119
|
continue;
|
|
13696
14120
|
}
|
|
13697
14121
|
const children = containerChildren2(declaration);
|
|
@@ -13701,7 +14125,7 @@ function seedProperties(properties, required, ancestors, open, requiredSecrets)
|
|
|
13701
14125
|
const childRequired = new Set(children.required);
|
|
13702
14126
|
const inner = [...ancestors, name];
|
|
13703
14127
|
if (children.kind === "object") {
|
|
13704
|
-
values[name] = seedProperties(children.properties, childRequired, inner, open,
|
|
14128
|
+
values[name] = seedProperties(children.properties, childRequired, inner, open, requiredSecrets);
|
|
13705
14129
|
continue;
|
|
13706
14130
|
}
|
|
13707
14131
|
const count = children.minItems ?? 0;
|
|
@@ -13732,7 +14156,8 @@ function containerChildren2(declaration) {
|
|
|
13732
14156
|
return {
|
|
13733
14157
|
kind: "object",
|
|
13734
14158
|
properties: node.properties,
|
|
13735
|
-
required: node.required ?? []
|
|
14159
|
+
required: node.required ?? [],
|
|
14160
|
+
...dependentRequiredOf(node.dependentRequired)
|
|
13736
14161
|
};
|
|
13737
14162
|
}
|
|
13738
14163
|
if (node.type === "array" && node.items !== void 0) {
|
|
@@ -13743,11 +14168,15 @@ function containerChildren2(declaration) {
|
|
|
13743
14168
|
kind: "array",
|
|
13744
14169
|
properties: items.properties,
|
|
13745
14170
|
required: items.required ?? [],
|
|
14171
|
+
...dependentRequiredOf(items.dependentRequired),
|
|
13746
14172
|
...typeof node.minItems === "number" ? { minItems: node.minItems } : {}
|
|
13747
14173
|
};
|
|
13748
14174
|
}
|
|
13749
14175
|
return null;
|
|
13750
14176
|
}
|
|
14177
|
+
function dependentRequiredOf(raw) {
|
|
14178
|
+
return typeof raw === "object" && raw !== null && !Array.isArray(raw) ? { dependentRequired: raw } : {};
|
|
14179
|
+
}
|
|
13751
14180
|
function dedupeOpen(open) {
|
|
13752
14181
|
const seen = /* @__PURE__ */ new Set();
|
|
13753
14182
|
const unique = open.filter((entry) => {
|
|
@@ -13779,6 +14208,7 @@ function exampleResourceKey(name) {
|
|
|
13779
14208
|
const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^[^a-z]+/, "").replace(/-+$/, "").slice(0, 63);
|
|
13780
14209
|
return RESOURCE_KEY_PATTERN.test(slug) ? slug : "";
|
|
13781
14210
|
}
|
|
14211
|
+
var DEPLOYMENT_EXAMPLE_GUARANTEE = "Product-owned configuration is complete and E2E-proven; tenant-specific secrets and Resource/provider selections are supplied by the tenant.";
|
|
13782
14212
|
|
|
13783
14213
|
// ../platform-schemas/dist/index.js
|
|
13784
14214
|
var TENANT_KEY_REGEX = /^[a-z][a-z0-9-]{2,23}[a-z0-9]$/;
|
|
@@ -13921,6 +14351,53 @@ function isBlockedHost(hostname) {
|
|
|
13921
14351
|
return BLOCKED_HOSTS.some((pattern) => pattern.test(hostname));
|
|
13922
14352
|
}
|
|
13923
14353
|
|
|
14354
|
+
// ../product-contracts/dist/deployment-example.schema.js
|
|
14355
|
+
var DeploymentExampleSchema = external_exports.object({
|
|
14356
|
+
/** The heading a customer reads above the example. */
|
|
14357
|
+
title: external_exports.string().min(3).max(80),
|
|
14358
|
+
/** What this deployment gives the customer, in a sentence or two. */
|
|
14359
|
+
description: external_exports.string().min(20).max(400),
|
|
14360
|
+
/**
|
|
14361
|
+
* A PLAIN STRING, for the reason the Product Instance Template's is: an
|
|
14362
|
+
* unsupported value then reaches the check that names the regions THIS
|
|
14363
|
+
* Product offers, rather than a zod enum error naming the platform's.
|
|
14364
|
+
*/
|
|
14365
|
+
region: external_exports.string().min(1).max(32),
|
|
14366
|
+
/** Plain string, same reasoning as `region`. */
|
|
14367
|
+
profile: external_exports.string().min(1).max(40),
|
|
14368
|
+
/**
|
|
14369
|
+
* The Product's own settings, in the shape `spec.configuration` takes —
|
|
14370
|
+
* nested for a v2 Product. Validated at publish against this release's
|
|
14371
|
+
* configuration schema by the same function a customer's manifest meets.
|
|
14372
|
+
*/
|
|
14373
|
+
configuration: external_exports.record(external_exports.unknown()),
|
|
14374
|
+
/**
|
|
14375
|
+
* The secret settings a deployment of this example supplies, BY NAME.
|
|
14376
|
+
*
|
|
14377
|
+
* Every secret the example's configuration makes required must be named
|
|
14378
|
+
* here, so a reader knows every `--secret` flag the create needs before
|
|
14379
|
+
* running it. The values are the tenant's — sent when the Resource is
|
|
14380
|
+
* created and again only to set a new one — and never written into a
|
|
14381
|
+
* manifest. A secret resent on an unchanged re-apply is written again.
|
|
14382
|
+
*/
|
|
14383
|
+
secretInputs: external_exports.array(external_exports.string().regex(CONFIGURATION_PROPERTY_NAME_REGEX)).max(MAX_CONFIGURATION_CHILDREN).optional()
|
|
14384
|
+
}).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.");
|
|
14385
|
+
var SECRET_INPUT_SEGMENT = CONFIGURATION_PROPERTY_NAME_REGEX.source.slice(1, -1);
|
|
14386
|
+
var SecretInputV2Schema = external_exports.string().regex(new RegExp(`^(?:${SECRET_INPUT_SEGMENT}|(?:/${SECRET_INPUT_SEGMENT}){2,${MAX_CONFIGURATION_DEPTH}})$`)).refine((raw) => secretPathOf(raw) !== null, {
|
|
14387
|
+
message: 'A secret input is a root setting name, or a configuration path such as "/group/setting" with no list index.'
|
|
14388
|
+
});
|
|
14389
|
+
var DeploymentExampleV2Schema = DeploymentExampleSchema.extend({
|
|
14390
|
+
/**
|
|
14391
|
+
* The secret settings a deployment of this example supplies, BY CANONICAL
|
|
14392
|
+
* PATH — a bare name at the root, a pointer inside a group.
|
|
14393
|
+
*
|
|
14394
|
+
* Every secret the example's configuration makes required must be named
|
|
14395
|
+
* here, so a reader knows every `--secret` flag the create needs before
|
|
14396
|
+
* running it. The values are the tenant's and never written into a manifest.
|
|
14397
|
+
*/
|
|
14398
|
+
secretInputs: external_exports.array(SecretInputV2Schema).max(MAX_CONFIGURATION_CHILDREN).optional()
|
|
14399
|
+
});
|
|
14400
|
+
|
|
13924
14401
|
// ../product-contracts/dist/portfolio.js
|
|
13925
14402
|
var PORTFOLIO_CATEGORY_IDS = [
|
|
13926
14403
|
"clients",
|
|
@@ -14507,6 +14984,15 @@ var ProductDefinitionObjectSchema = external_exports.object({
|
|
|
14507
14984
|
// --- Customer choices -------------------------------------------------
|
|
14508
14985
|
profiles: external_exports.array(ServiceProfileSchema).min(1).max(SERVICE_PROFILE_IDS.length),
|
|
14509
14986
|
configuration: CustomerConfigurationSchema,
|
|
14987
|
+
/**
|
|
14988
|
+
* The publisher's recommended deployment of this release. See
|
|
14989
|
+
* `deployment-example.schema.ts`.
|
|
14990
|
+
*
|
|
14991
|
+
* `.optional()`, NEVER `.default(...)`, for the digest reason `conditions`
|
|
14992
|
+
* gives below — and required for a NEW release by `publish-rules.ts`, which
|
|
14993
|
+
* is the only place a rule added after releases were frozen may live.
|
|
14994
|
+
*/
|
|
14995
|
+
deploymentExample: DeploymentExampleSchema.optional(),
|
|
14510
14996
|
// --- Results, relationships, operations -------------------------------
|
|
14511
14997
|
outputs: external_exports.array(ProductOutputSchema).min(1).max(MAX_PRODUCT_OUTPUTS),
|
|
14512
14998
|
dependencies: external_exports.array(ProductDependencySchema).max(8),
|
|
@@ -14543,7 +15029,10 @@ var ProductDefinitionSchema = ProductDefinitionObjectSchema.superRefine(checkPro
|
|
|
14543
15029
|
var ProductDefinitionV2ObjectSchema = ProductDefinitionObjectSchema.extend({
|
|
14544
15030
|
apiVersion: ProductApiVersionV2Schema,
|
|
14545
15031
|
configuration: CustomerConfigurationV2Schema,
|
|
14546
|
-
dependencies: external_exports.array(ProductDependencyV2Schema).max(8)
|
|
15032
|
+
dependencies: external_exports.array(ProductDependencyV2Schema).max(8),
|
|
15033
|
+
// A v2 secret may sit inside a group, so the example names it by PATH. The v1
|
|
15034
|
+
// example keeps its root-name rule, and the v1 artifact its bytes.
|
|
15035
|
+
deploymentExample: DeploymentExampleV2Schema.optional()
|
|
14547
15036
|
});
|
|
14548
15037
|
var ProductDefinitionV2Schema = ProductDefinitionV2ObjectSchema.superRefine(checkProductDefinition);
|
|
14549
15038
|
function checkProductDefinition(definition, ctx) {
|
|
@@ -14720,12 +15209,13 @@ function checkProductDefinition(definition, ctx) {
|
|
|
14720
15209
|
});
|
|
14721
15210
|
}
|
|
14722
15211
|
});
|
|
14723
|
-
for (const
|
|
14724
|
-
const condition =
|
|
14725
|
-
|
|
14726
|
-
|
|
14727
|
-
const
|
|
14728
|
-
|
|
15212
|
+
for (const carrier of conditionCarriers(definition.configuration)) {
|
|
15213
|
+
const { condition } = carrier;
|
|
15214
|
+
const name = printableConfigurationPath(carrier.path);
|
|
15215
|
+
const named = conditionSetting(condition);
|
|
15216
|
+
const anyRow = "anyRow" in condition && condition.anyRow === true;
|
|
15217
|
+
const at = [...declarationIssuePath(carrier), "x-required-when"];
|
|
15218
|
+
if (requiredToRoot(definition.configuration, carrier)) {
|
|
14729
15219
|
ctx.addIssue({
|
|
14730
15220
|
code: external_exports.ZodIssueCode.custom,
|
|
14731
15221
|
path: [...at],
|
|
@@ -14733,7 +15223,7 @@ function checkProductDefinition(definition, ctx) {
|
|
|
14733
15223
|
});
|
|
14734
15224
|
continue;
|
|
14735
15225
|
}
|
|
14736
|
-
if (declaredDefault(property) !== void 0) {
|
|
15226
|
+
if (declaredDefault(carrier.property) !== void 0) {
|
|
14737
15227
|
ctx.addIssue({
|
|
14738
15228
|
code: external_exports.ZodIssueCode.custom,
|
|
14739
15229
|
path: [...at],
|
|
@@ -14748,7 +15238,7 @@ function checkProductDefinition(definition, ctx) {
|
|
|
14748
15238
|
message: "Repeated values in `anyOf`."
|
|
14749
15239
|
});
|
|
14750
15240
|
}
|
|
14751
|
-
if (
|
|
15241
|
+
if (named === carrier.path) {
|
|
14752
15242
|
ctx.addIssue({
|
|
14753
15243
|
code: external_exports.ZodIssueCode.custom,
|
|
14754
15244
|
path: [...at, "path"],
|
|
@@ -14756,30 +15246,30 @@ function checkProductDefinition(definition, ctx) {
|
|
|
14756
15246
|
});
|
|
14757
15247
|
continue;
|
|
14758
15248
|
}
|
|
14759
|
-
const parsed = parseConfigurationPath(
|
|
15249
|
+
const parsed = parseConfigurationPath(named);
|
|
14760
15250
|
const leaf = parsed === null ? null : leafAt(definition.configuration, parsed);
|
|
14761
15251
|
if (leaf === null) {
|
|
14762
15252
|
const isGroup = parsed !== null && declaresPath(definition.configuration, parsed);
|
|
14763
15253
|
ctx.addIssue({
|
|
14764
15254
|
code: external_exports.ZodIssueCode.custom,
|
|
14765
15255
|
path: [...at, "path"],
|
|
14766
|
-
message: isGroup ? `"${
|
|
15256
|
+
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
15257
|
});
|
|
14768
15258
|
continue;
|
|
14769
15259
|
}
|
|
14770
|
-
if (underList(leaf) &&
|
|
15260
|
+
if (underList(leaf) && !anyRow) {
|
|
14771
15261
|
ctx.addIssue({
|
|
14772
15262
|
code: external_exports.ZodIssueCode.custom,
|
|
14773
15263
|
path: [...at, "path"],
|
|
14774
|
-
message: `"${
|
|
15264
|
+
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
15265
|
});
|
|
14776
15266
|
continue;
|
|
14777
15267
|
}
|
|
14778
|
-
if (!underList(leaf) &&
|
|
15268
|
+
if (!underList(leaf) && anyRow) {
|
|
14779
15269
|
ctx.addIssue({
|
|
14780
15270
|
code: external_exports.ZodIssueCode.custom,
|
|
14781
15271
|
path: [...at, "anyRow"],
|
|
14782
|
-
message: `"${
|
|
15272
|
+
message: `"${named}" is not inside a list, so there are no rows to quantify over.`
|
|
14783
15273
|
});
|
|
14784
15274
|
}
|
|
14785
15275
|
const target = leaf.property;
|
|
@@ -14787,7 +15277,7 @@ function checkProductDefinition(definition, ctx) {
|
|
|
14787
15277
|
ctx.addIssue({
|
|
14788
15278
|
code: external_exports.ZodIssueCode.custom,
|
|
14789
15279
|
path: [...at, "path"],
|
|
14790
|
-
message: `"${
|
|
15280
|
+
message: `"${named}" is a ${target.type} setting; a condition compares string values.`
|
|
14791
15281
|
});
|
|
14792
15282
|
continue;
|
|
14793
15283
|
}
|
|
@@ -14795,21 +15285,16 @@ function checkProductDefinition(definition, ctx) {
|
|
|
14795
15285
|
ctx.addIssue({
|
|
14796
15286
|
code: external_exports.ZodIssueCode.custom,
|
|
14797
15287
|
path: [...at, "path"],
|
|
14798
|
-
message: `"${
|
|
15288
|
+
message: `"${named}" is a secret. The platform never reads a secret's value, so a condition on one could never be evaluated.`
|
|
14799
15289
|
});
|
|
14800
15290
|
continue;
|
|
14801
15291
|
}
|
|
14802
|
-
const
|
|
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);
|
|
15292
|
+
const outside = valuesOutsideSpace(definition.configuration, leaf, condition.anyOf);
|
|
14808
15293
|
if (outside.length > 0) {
|
|
14809
15294
|
ctx.addIssue({
|
|
14810
15295
|
code: external_exports.ZodIssueCode.custom,
|
|
14811
15296
|
path: [...at, "anyOf"],
|
|
14812
|
-
message: `"${
|
|
15297
|
+
message: `"${named}" never accepts ${outside.map((v) => `"${v}"`).join(", ")}, so this condition could never make the secret required.`
|
|
14813
15298
|
});
|
|
14814
15299
|
}
|
|
14815
15300
|
}
|
|
@@ -15120,27 +15605,246 @@ function checkRowRules(configuration, ctx) {
|
|
|
15120
15605
|
message: `"${rule.property}" never accepts ${outside.map((value) => `"${value}"`).join(", ")}, so this rule could never count a row.`
|
|
15121
15606
|
});
|
|
15122
15607
|
}
|
|
15123
|
-
});
|
|
15608
|
+
});
|
|
15609
|
+
}
|
|
15610
|
+
}
|
|
15611
|
+
|
|
15612
|
+
// ../product-contracts/dist/announcements.js
|
|
15613
|
+
var ANNOUNCED_PRODUCTS = Object.freeze([
|
|
15614
|
+
{
|
|
15615
|
+
productId: "hosted-models",
|
|
15616
|
+
displayName: "Hosted Models",
|
|
15617
|
+
summary: "Build, customize and deploy your own AI models",
|
|
15618
|
+
category: "ai-models",
|
|
15619
|
+
order: 0
|
|
15620
|
+
},
|
|
15621
|
+
{
|
|
15622
|
+
productId: "vern",
|
|
15623
|
+
displayName: "Vern",
|
|
15624
|
+
summary: "Protect sensitive data in AI workloads",
|
|
15625
|
+
category: "data-security",
|
|
15626
|
+
order: 0
|
|
15627
|
+
}
|
|
15628
|
+
]);
|
|
15629
|
+
|
|
15630
|
+
// ../product-contracts/dist/contract-issue.js
|
|
15631
|
+
var PATH_SEGMENT = /^[A-Za-z_][A-Za-z0-9_-]{0,63}$/;
|
|
15632
|
+
var UNPRINTABLE_SEGMENT = "<unprintable>";
|
|
15633
|
+
var MAX_SEGMENTS = MAX_CONFIGURATION_PATH_SEGMENTS + 2;
|
|
15634
|
+
function printableContractPath(path) {
|
|
15635
|
+
const printable = path.slice(0, MAX_SEGMENTS).map((segment) => typeof segment === "number" || PATH_SEGMENT.test(segment) ? String(segment) : UNPRINTABLE_SEGMENT);
|
|
15636
|
+
return printable.join(".") || "(document)";
|
|
15637
|
+
}
|
|
15638
|
+
function manifestConfigurationField(field) {
|
|
15639
|
+
if (field === "")
|
|
15640
|
+
return "spec.configuration";
|
|
15641
|
+
const parsed = parseConfigurationPath(field);
|
|
15642
|
+
return printableContractPath([
|
|
15643
|
+
"spec",
|
|
15644
|
+
"configuration",
|
|
15645
|
+
...parsed === null ? [field] : pathSegments(parsed)
|
|
15646
|
+
]);
|
|
15647
|
+
}
|
|
15648
|
+
|
|
15649
|
+
// ../product-contracts/dist/resource-output-reference.js
|
|
15650
|
+
var OUTPUT_KEY_RULE = "Output keys are camelCase.";
|
|
15651
|
+
var ResourceOutputReferenceSchema = external_exports.object({
|
|
15652
|
+
valueFrom: external_exports.object({
|
|
15653
|
+
resourceOutput: external_exports.object({
|
|
15654
|
+
/** The producing Resource's `metadata.key`, in the same Project. */
|
|
15655
|
+
resourceKey: ResourceKeySchema,
|
|
15656
|
+
/** The key of an output that Resource's Product declares. */
|
|
15657
|
+
output: external_exports.string().regex(/^[a-z][a-zA-Z0-9]{1,47}$/, OUTPUT_KEY_RULE)
|
|
15658
|
+
}).strict()
|
|
15659
|
+
}).strict()
|
|
15660
|
+
}).strict();
|
|
15661
|
+
function looksLikeReference(value) {
|
|
15662
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && "valueFrom" in value;
|
|
15663
|
+
}
|
|
15664
|
+
|
|
15665
|
+
// ../product-contracts/dist/dependency-binding.js
|
|
15666
|
+
var DependencyBindingSelectorSchema = external_exports.object({
|
|
15667
|
+
/** The providing Resource's `metadata.key`. */
|
|
15668
|
+
resourceKey: ResourceKeySchema,
|
|
15669
|
+
/**
|
|
15670
|
+
* The Project that Resource lives in. Absent means the consumer's own.
|
|
15671
|
+
*
|
|
15672
|
+
* PRESENT BECAUSE A BINDING IS TENANT-SCOPED AND A PROJECT IS NOT A RUNTIME
|
|
15673
|
+
* BOUNDARY: a provider anywhere in the Tenant can satisfy a consumer
|
|
15674
|
+
* anywhere in it, and that has been true since bindings existed. What the
|
|
15675
|
+
* field does NOT do is grant anything — the Control Plane opens that
|
|
15676
|
+
* Project from the caller's own assertion, and a Project the caller holds
|
|
15677
|
+
* nothing in answers exactly as a Project that does not exist.
|
|
15678
|
+
*
|
|
15679
|
+
* A plain bounded string, exactly as `spec.projectId` is: an id that does
|
|
15680
|
+
* not name a readable Project is refused where the refusal can be phrased
|
|
15681
|
+
* without leaking, which is not here.
|
|
15682
|
+
*/
|
|
15683
|
+
projectId: external_exports.string().min(1).max(64).optional()
|
|
15684
|
+
}).strict();
|
|
15685
|
+
var DependencyBindingsSchema = external_exports.record(ServiceBindingKeySchema, DependencyBindingSelectorSchema);
|
|
15686
|
+
|
|
15687
|
+
// ../product-contracts/dist/product-instance-template.schema.js
|
|
15688
|
+
var ProductInstanceTemplateSchema = external_exports.object({
|
|
15689
|
+
apiVersion: ResourceApiVersionSchema,
|
|
15690
|
+
kind: kindSchema(PRODUCT_INSTANCE_KIND),
|
|
15691
|
+
metadata: external_exports.object({
|
|
15692
|
+
/**
|
|
15693
|
+
* The customer-facing Resource name — "Local service".
|
|
15694
|
+
*
|
|
15695
|
+
* Maps to `ProductInstance.displayName`, and deliberately introduces NO
|
|
15696
|
+
* new identity concept. `pri_…` remains the stable identity and
|
|
15697
|
+
* `instanceKey` remains the cloud resource key; adding a third name here
|
|
15698
|
+
* would mean a fourth thing to keep unique and a fifth to explain.
|
|
15699
|
+
* Bounded exactly as the create envelope bounds `displayName`.
|
|
15700
|
+
*/
|
|
15701
|
+
// NOT `.trim()`: this schema is PUBLISHED as JSON Schema so a customer
|
|
15702
|
+
// can validate the file before sending it, and a trim is a transform
|
|
15703
|
+
// that JSON Schema cannot express. The value is trimmed when it becomes
|
|
15704
|
+
// a create command instead, so the published document and the platform
|
|
15705
|
+
// agree on what is valid.
|
|
15706
|
+
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."),
|
|
15707
|
+
/**
|
|
15708
|
+
* THE STABLE IDENTITY OF THIS RESOURCE INSIDE ITS PROJECT — and the one
|
|
15709
|
+
* thing that makes applying a document twice different from creating
|
|
15710
|
+
* two resources.
|
|
15711
|
+
*
|
|
15712
|
+
* THIS IS THE FOURTH NAME, AND THE HEADER ABOVE ARGUES AGAINST ONE.
|
|
15713
|
+
* That argument stands and is answered rather than ignored: a
|
|
15714
|
+
* declarative apply is not expressible without a key the AUTHOR knows
|
|
15715
|
+
* before the platform has created anything, and nothing that already
|
|
15716
|
+
* exists is one.
|
|
15717
|
+
*
|
|
15718
|
+
* `metadata.name` mutable, not unique, and rule 1 above says it is
|
|
15719
|
+
* display copy rather than identity.
|
|
15720
|
+
* `pri_…` minted by the server. It does not exist when the
|
|
15721
|
+
* first apply is written, which is the only moment
|
|
15722
|
+
* that matters.
|
|
15723
|
+
* `instanceKey` derived from `pri_…` and then frozen — the same
|
|
15724
|
+
* problem one step removed.
|
|
15725
|
+
* idempotency key a de-duplication token that "names nothing,
|
|
15726
|
+
* grants nothing, and selects nothing". Making it
|
|
15727
|
+
* select a resource would falsify the security
|
|
15728
|
+
* argument the signed-request layer rests on.
|
|
15729
|
+
*
|
|
15730
|
+
* So it gets exactly ONE job: to say which resource in this Project the
|
|
15731
|
+
* document is about. It is NEVER authorization — the Project comes from
|
|
15732
|
+
* the request, the row is loaded inside it, and a key naming nothing
|
|
15733
|
+
* there is a not-found rather than a create. And it NEVER becomes a
|
|
15734
|
+
* provider resource name; `instanceKey` owns that, and coupling the two
|
|
15735
|
+
* would let a customer's chosen string reach Scaleway.
|
|
15736
|
+
*
|
|
15737
|
+
* OPTIONAL HERE, REQUIRED AT APPLY. The document is a published `v1`
|
|
15738
|
+
* contract and validating one on its own is a legitimate thing to do
|
|
15739
|
+
* with no Project in sight — the same asymmetry `resolveTemplate`
|
|
15740
|
+
* already has for `routeProjectId`. What refuses a document without a
|
|
15741
|
+
* key is the apply path, because a create that cannot be run again is
|
|
15742
|
+
* not deployment as code.
|
|
15743
|
+
*
|
|
15744
|
+
* Shaped like a DNS label: it becomes a database predicate and a log
|
|
15745
|
+
* field, and neither wants free text.
|
|
15746
|
+
*/
|
|
15747
|
+
key: ResourceKeySchema.optional()
|
|
15748
|
+
}).strict(),
|
|
15749
|
+
spec: external_exports.object({
|
|
15750
|
+
productId: external_exports.string().min(1).max(64),
|
|
15751
|
+
/**
|
|
15752
|
+
* REQUIRED, unlike the create envelope's, which may be omitted to take
|
|
15753
|
+
* the newest deployable release.
|
|
15754
|
+
*
|
|
15755
|
+
* A template is a record of a decision, and "newest" is not one. An
|
|
15756
|
+
* optional version would mean the same document produced 1.0.0 today and
|
|
15757
|
+
* 1.1.0 after an unrelated promotion — so re-applying an exported
|
|
15758
|
+
* template would not reproduce what was exported, which is the single
|
|
15759
|
+
* property that makes templates worth having.
|
|
15760
|
+
*/
|
|
15761
|
+
productVersion: external_exports.string().min(1).max(64),
|
|
15762
|
+
/** Round-trip only — see rule 3 above. Never authorization. */
|
|
15763
|
+
projectId: external_exports.string().min(1).max(64).optional(),
|
|
15764
|
+
region: external_exports.string().min(1).max(32),
|
|
15765
|
+
profile: external_exports.string().min(1).max(40),
|
|
15766
|
+
/**
|
|
15767
|
+
* The Product's own settings.
|
|
15768
|
+
*
|
|
15769
|
+
* An OBJECT with product-specific keys, not `unknown`: the published
|
|
15770
|
+
* JSON Schema has to say at least that much, and the converter refuses
|
|
15771
|
+
* `unknown` precisely so a published contract cannot silently widen.
|
|
15772
|
+
* The keys themselves are validated against the pinned Definition's
|
|
15773
|
+
* configuration schema by `resolveTemplate`, which is where the real
|
|
15774
|
+
* shape lives — it differs per Product, so it cannot be stated here.
|
|
15775
|
+
*/
|
|
15776
|
+
configuration: external_exports.record(external_exports.unknown()).optional(),
|
|
15777
|
+
/**
|
|
15778
|
+
* Which Resource satisfies each declared service dependency.
|
|
15779
|
+
*
|
|
15780
|
+
* A RESOURCE RELATION, NOT A SETTING, which is why it is a sibling of
|
|
15781
|
+
* `configuration` rather than a key inside it. `configuration` is the
|
|
15782
|
+
* Product's own settings and is validated against the Definition's
|
|
15783
|
+
* schema; this names another Resource, and the create envelope made the
|
|
15784
|
+
* identical split for the identical reason.
|
|
15785
|
+
*
|
|
15786
|
+
* OPTIONAL, PERMANENTLY. Every manifest written before this field
|
|
15787
|
+
* existed omits it, and absence has to keep meaning "leave the
|
|
15788
|
+
* connections alone" — otherwise re-applying an unchanged file would
|
|
15789
|
+
* release a binding somebody made in the portal.
|
|
15790
|
+
*
|
|
15791
|
+
* The keys the platform will ACCEPT here are narrower than the shape:
|
|
15792
|
+
* only a dependency whose interface the registry calls
|
|
15793
|
+
* `tenant-multiple` is one a customer chooses. `resolveTemplate` says
|
|
15794
|
+
* so against the pinned Definition, so the refusal names the field.
|
|
15795
|
+
*/
|
|
15796
|
+
dependencyBindings: DependencyBindingsSchema.optional()
|
|
15797
|
+
}).strict()
|
|
15798
|
+
}).strict();
|
|
15799
|
+
function secretValuesInDocument(schema, configuration) {
|
|
15800
|
+
if (!isPlainRecord(configuration) || !isPlainRecord(schema.properties))
|
|
15801
|
+
return [];
|
|
15802
|
+
const declarations = plainDeclarations(schema.properties);
|
|
15803
|
+
return declaredSecretPaths({
|
|
15804
|
+
properties: declarations
|
|
15805
|
+
}).filter((path) => {
|
|
15806
|
+
const segments = pathSegments(path).map(String);
|
|
15807
|
+
let parent = configuration;
|
|
15808
|
+
for (const segment of segments.slice(0, -1)) {
|
|
15809
|
+
parent = isPlainRecord(parent) ? parent[segment] : void 0;
|
|
15810
|
+
}
|
|
15811
|
+
const name = segments[segments.length - 1];
|
|
15812
|
+
return isPlainRecord(parent) && Object.hasOwn(parent, name) && !looksLikeReference(parent[name]);
|
|
15813
|
+
});
|
|
15814
|
+
}
|
|
15815
|
+
function plainDeclarations(properties) {
|
|
15816
|
+
const out = {};
|
|
15817
|
+
for (const [name, property] of Object.entries(properties)) {
|
|
15818
|
+
if (!isPlainRecord(property) || typeof property["type"] !== "string")
|
|
15819
|
+
continue;
|
|
15820
|
+
const copy = { ...property };
|
|
15821
|
+
if (Object.hasOwn(copy, "properties")) {
|
|
15822
|
+
if (isPlainRecord(copy["properties"]))
|
|
15823
|
+
copy["properties"] = plainDeclarations(copy["properties"]);
|
|
15824
|
+
else
|
|
15825
|
+
delete copy["properties"];
|
|
15826
|
+
}
|
|
15827
|
+
if (Object.hasOwn(copy, "items")) {
|
|
15828
|
+
const items = copy["items"];
|
|
15829
|
+
if (!isPlainRecord(items) || typeof items["type"] !== "string")
|
|
15830
|
+
delete copy["items"];
|
|
15831
|
+
else if (Object.hasOwn(items, "properties")) {
|
|
15832
|
+
copy["items"] = isPlainRecord(items["properties"]) ? { ...items, properties: plainDeclarations(items["properties"]) } : (({ properties: _dropped, ...rest }) => rest)(items);
|
|
15833
|
+
}
|
|
15834
|
+
}
|
|
15835
|
+
out[name] = copy;
|
|
15124
15836
|
}
|
|
15837
|
+
return out;
|
|
15838
|
+
}
|
|
15839
|
+
function secretValueIssue(name) {
|
|
15840
|
+
return {
|
|
15841
|
+
field: manifestConfigurationField(name),
|
|
15842
|
+
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.`
|
|
15843
|
+
};
|
|
15844
|
+
}
|
|
15845
|
+
function isPlainRecord(value) {
|
|
15846
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
15125
15847
|
}
|
|
15126
|
-
|
|
15127
|
-
// ../product-contracts/dist/announcements.js
|
|
15128
|
-
var ANNOUNCED_PRODUCTS = Object.freeze([
|
|
15129
|
-
{
|
|
15130
|
-
productId: "hosted-models",
|
|
15131
|
-
displayName: "Hosted Models",
|
|
15132
|
-
summary: "Build, customize and deploy your own AI models",
|
|
15133
|
-
category: "ai-models",
|
|
15134
|
-
order: 0
|
|
15135
|
-
},
|
|
15136
|
-
{
|
|
15137
|
-
productId: "vern",
|
|
15138
|
-
displayName: "Vern",
|
|
15139
|
-
summary: "Protect sensitive data in AI workloads",
|
|
15140
|
-
category: "data-security",
|
|
15141
|
-
order: 0
|
|
15142
|
-
}
|
|
15143
|
-
]);
|
|
15144
15848
|
|
|
15145
15849
|
// ../product-contracts/dist/publish-rules.js
|
|
15146
15850
|
var PRODUCT_DISPLAY_NAME_PATTERN = "^[^\\u2014\\s](?:[^\\u2014]*[^\\u2014\\s])? \\u2014 [^\\u2014\\s](?:[^\\u2014]*[^\\u2014\\s])?$";
|
|
@@ -15464,8 +16168,12 @@ var EnvBindingV2Schema = external_exports.discriminatedUnion("source", [
|
|
|
15464
16168
|
*
|
|
15465
16169
|
* WHAT IT CARRIES: validated, non-secret configuration with resolved
|
|
15466
16170
|
* ORDINARY resource references materialised in place. Never a secret
|
|
15467
|
-
* value —
|
|
15468
|
-
*
|
|
16171
|
+
* value — a secret travels through `secrets[]`, and its value is split out
|
|
16172
|
+
* of the configuration before anything is stored. A secret may sit inside
|
|
16173
|
+
* a group, so `validateProductContracts` refuses a `path` naming a secret
|
|
16174
|
+
* or a group that contains one anywhere below it: a component bound to
|
|
16175
|
+
* that group would read it as complete while its credential is absent.
|
|
16176
|
+
* Serialised with `canonicalJson`, so the same
|
|
15469
16177
|
* logical configuration is the same bytes and the environment fingerprint
|
|
15470
16178
|
* does not move on key order alone.
|
|
15471
16179
|
*
|
|
@@ -15815,6 +16523,20 @@ var SecretWiringSchema = external_exports.object({
|
|
|
15815
16523
|
envVar: EnvVarNameSchema
|
|
15816
16524
|
}).strict()).min(1).max(12)
|
|
15817
16525
|
}).strict();
|
|
16526
|
+
var SecretWiringV2Schema = SecretWiringSchema.extend({
|
|
16527
|
+
/**
|
|
16528
|
+
* LEGACY SPELLING of a ROOT secret's path, kept so released v2 Blueprints
|
|
16529
|
+
* still read. Described here rather than in the declaration table, whose note
|
|
16530
|
+
* is what the v1 artifact publishes for the same field.
|
|
16531
|
+
*/
|
|
16532
|
+
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(),
|
|
16533
|
+
/**
|
|
16534
|
+
* Set only for `customer-configuration`: the canonical path of the `x-secret`
|
|
16535
|
+
* setting in the Product Definition whose write-only submission populates
|
|
16536
|
+
* this secret — a bare name at the root, a pointer inside a group.
|
|
16537
|
+
*/
|
|
16538
|
+
configurationPath: ConfigurationBindingPathSchema.optional()
|
|
16539
|
+
}).strict();
|
|
15818
16540
|
var DataCategorySchema = external_exports.enum([
|
|
15819
16541
|
"prompt-content",
|
|
15820
16542
|
"model-response",
|
|
@@ -16244,7 +16966,8 @@ var DeploymentBlueprintV1ObjectSchema = DeploymentBlueprintObjectSchema.extend({
|
|
|
16244
16966
|
});
|
|
16245
16967
|
var DeploymentBlueprintV2ObjectSchema = DeploymentBlueprintObjectSchema.extend({
|
|
16246
16968
|
apiVersion: ProductApiVersionV2Schema,
|
|
16247
|
-
components: external_exports.array(RuntimeComponentV2Schema).min(1).max(MAX_RUNTIME_COMPONENTS)
|
|
16969
|
+
components: external_exports.array(RuntimeComponentV2Schema).min(1).max(MAX_RUNTIME_COMPONENTS),
|
|
16970
|
+
secrets: external_exports.array(SecretWiringV2Schema).max(20)
|
|
16248
16971
|
});
|
|
16249
16972
|
var DeploymentBlueprintV1Schema = DeploymentBlueprintV1ObjectSchema.superRefine(checkDeploymentBlueprint);
|
|
16250
16973
|
var DeploymentBlueprintV2Schema = DeploymentBlueprintV2ObjectSchema.superRefine(checkDeploymentBlueprint);
|
|
@@ -16628,18 +17351,28 @@ function checkDeploymentBlueprint(blueprint, ctx) {
|
|
|
16628
17351
|
});
|
|
16629
17352
|
}
|
|
16630
17353
|
}
|
|
16631
|
-
|
|
17354
|
+
const pathField = "configurationPath" in secret ? secret.configurationPath : void 0;
|
|
17355
|
+
const namesSetting = secret.configurationProperty !== void 0 || pathField !== void 0;
|
|
17356
|
+
const settingField = blueprint.apiVersion === PRODUCT_API_VERSION ? "configurationProperty" : "configurationPath";
|
|
17357
|
+
if (secret.configurationProperty !== void 0 && pathField !== void 0) {
|
|
17358
|
+
ctx.addIssue({
|
|
17359
|
+
code: external_exports.ZodIssueCode.custom,
|
|
17360
|
+
path,
|
|
17361
|
+
message: `Secret "${secret.key}" names both configurationProperty and configurationPath. Name the setting once, with configurationPath.`
|
|
17362
|
+
});
|
|
17363
|
+
}
|
|
17364
|
+
if (secret.source === "customer-configuration" && !namesSetting) {
|
|
16632
17365
|
ctx.addIssue({
|
|
16633
17366
|
code: external_exports.ZodIssueCode.custom,
|
|
16634
17367
|
path,
|
|
16635
|
-
message: `Secret "${secret.key}" is sourced from customer configuration but names no
|
|
17368
|
+
message: `Secret "${secret.key}" is sourced from customer configuration but names no ${settingField}.`
|
|
16636
17369
|
});
|
|
16637
17370
|
}
|
|
16638
|
-
if (secret.source !== "customer-configuration" &&
|
|
17371
|
+
if (secret.source !== "customer-configuration" && namesSetting) {
|
|
16639
17372
|
ctx.addIssue({
|
|
16640
17373
|
code: external_exports.ZodIssueCode.custom,
|
|
16641
17374
|
path,
|
|
16642
|
-
message: `Secret "${secret.key}" names a configurationProperty but is not customer-sourced.`
|
|
17375
|
+
message: `Secret "${secret.key}" names a ${pathField !== void 0 ? "configurationPath" : "configurationProperty"} but is not customer-sourced.`
|
|
16643
17376
|
});
|
|
16644
17377
|
}
|
|
16645
17378
|
if (secret.source === "service-binding" && secret.serviceBinding === void 0) {
|
|
@@ -17017,193 +17750,6 @@ function isTerminalAction(action) {
|
|
|
17017
17750
|
}
|
|
17018
17751
|
var ManifestActionSchema = external_exports.enum(MANIFEST_ACTIONS);
|
|
17019
17752
|
|
|
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
17753
|
// ../product-contracts/dist/product-submission.schema.js
|
|
17208
17754
|
var PRODUCT_SUBMISSION_KIND = "ProductSubmission";
|
|
17209
17755
|
var SubmissionPublisherSchema = external_exports.object({
|
|
@@ -17297,10 +17843,29 @@ var PUBLISH_RULE_ANNEX = {
|
|
|
17297
17843
|
}
|
|
17298
17844
|
}
|
|
17299
17845
|
}
|
|
17846
|
+
},
|
|
17847
|
+
{
|
|
17848
|
+
// THE SAME ASYMMETRY, a third time. Presence and shape are all JSON Schema
|
|
17849
|
+
// can say: whether the region and profile are this Product's, whether the
|
|
17850
|
+
// configuration fills every required setting and whether every required
|
|
17851
|
+
// secret is named are questions about the rest of the document, answered
|
|
17852
|
+
// by preflight.
|
|
17853
|
+
$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.",
|
|
17854
|
+
properties: {
|
|
17855
|
+
definition: {
|
|
17856
|
+
required: ["deploymentExample"],
|
|
17857
|
+
properties: {
|
|
17858
|
+
deploymentExample: {
|
|
17859
|
+
required: ["title", "description", "region", "profile", "configuration"]
|
|
17860
|
+
}
|
|
17861
|
+
}
|
|
17862
|
+
}
|
|
17863
|
+
}
|
|
17300
17864
|
}
|
|
17301
17865
|
]
|
|
17302
17866
|
};
|
|
17303
17867
|
withJsonSchemaAnnex(ProductSubmissionSchema, PUBLISH_RULE_ANNEX);
|
|
17868
|
+
withJsonSchemaAnnex(ProductSubmissionV2Schema, PUBLISH_RULE_ANNEX);
|
|
17304
17869
|
|
|
17305
17870
|
// src/manifest.ts
|
|
17306
17871
|
var import_yaml = __toESM(require_dist(), 1);
|
|
@@ -17333,15 +17898,21 @@ function resolveSecrets(pairs, environment = process.env) {
|
|
|
17333
17898
|
if (separator <= 0) {
|
|
17334
17899
|
return {
|
|
17335
17900
|
ok: false,
|
|
17336
|
-
message: `${label} expects
|
|
17901
|
+
message: `${label} expects NAME=env:VARIABLE, and there is no name before "=".`
|
|
17337
17902
|
};
|
|
17338
17903
|
}
|
|
17339
|
-
const name = pair.slice(0, separator);
|
|
17904
|
+
const name = secretPathOf(pair.slice(0, separator));
|
|
17340
17905
|
const source = pair.slice(separator + 1);
|
|
17906
|
+
if (name === null) {
|
|
17907
|
+
return {
|
|
17908
|
+
ok: false,
|
|
17909
|
+
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 "/".`
|
|
17910
|
+
};
|
|
17911
|
+
}
|
|
17341
17912
|
if (!source.startsWith("env:")) {
|
|
17342
17913
|
return {
|
|
17343
17914
|
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.`
|
|
17915
|
+
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
17916
|
};
|
|
17346
17917
|
}
|
|
17347
17918
|
const variable = source.slice("env:".length);
|
|
@@ -17349,10 +17920,10 @@ function resolveSecrets(pairs, environment = process.env) {
|
|
|
17349
17920
|
return { ok: false, message: `${label} names no variable after "env:".` };
|
|
17350
17921
|
}
|
|
17351
17922
|
const value = environment[variable];
|
|
17352
|
-
if (value === void 0 || value === "") {
|
|
17923
|
+
if (value === void 0 || value.trim() === "") {
|
|
17353
17924
|
return {
|
|
17354
17925
|
ok: false,
|
|
17355
|
-
message: `${label} reads an environment variable that is not set, or
|
|
17926
|
+
message: `${label} reads an environment variable that is not set, or holds nothing but whitespace.`
|
|
17356
17927
|
};
|
|
17357
17928
|
}
|
|
17358
17929
|
secrets[name] = value;
|
|
@@ -17650,6 +18221,9 @@ function secretWriteConsequence(plan) {
|
|
|
17650
18221
|
}
|
|
17651
18222
|
return lines;
|
|
17652
18223
|
}
|
|
18224
|
+
function requiredSecretLines(names) {
|
|
18225
|
+
return names.length === 0 ? [] : [" required secrets:", ...names.map((name) => ` ${name}`)];
|
|
18226
|
+
}
|
|
17653
18227
|
function printPlan(plan, json) {
|
|
17654
18228
|
if (json) {
|
|
17655
18229
|
process.stdout.write(`${JSON.stringify(plan)}
|
|
@@ -17682,11 +18256,7 @@ function printPlan(plan, json) {
|
|
|
17682
18256
|
}
|
|
17683
18257
|
}
|
|
17684
18258
|
}
|
|
17685
|
-
|
|
17686
|
-
if (required.length > 0) {
|
|
17687
|
-
lines.push(" required secrets:");
|
|
17688
|
-
for (const name of required) lines.push(` ${name}`);
|
|
17689
|
-
}
|
|
18259
|
+
lines.push(...requiredSecretLines(plan.requiredSecrets ?? []));
|
|
17690
18260
|
if ((plan.secretsToWrite ?? []).length > 0) {
|
|
17691
18261
|
lines.push(` secrets to set: ${plan.secretsToWrite.join(", ")}`);
|
|
17692
18262
|
for (const line of secretWriteConsequence(plan)) lines.push(` ${line}`);
|
|
@@ -17770,10 +18340,15 @@ async function runApply(verb, parsed, deps = {}) {
|
|
|
17770
18340
|
body: { verb: "validate", payload: manifest2.document }
|
|
17771
18341
|
});
|
|
17772
18342
|
if (!result.ok) return printError(result.error, json);
|
|
18343
|
+
if (json) {
|
|
18344
|
+
process.stdout.write(`${JSON.stringify(result.data)}
|
|
18345
|
+
`);
|
|
18346
|
+
return 0;
|
|
18347
|
+
}
|
|
18348
|
+
const required = result.data["requiredSecrets"];
|
|
18349
|
+
const names = Array.isArray(required) ? required.filter((name) => typeof name === "string") : [];
|
|
17773
18350
|
process.stdout.write(
|
|
17774
|
-
|
|
17775
|
-
` : `${file} is a valid manifest.
|
|
17776
|
-
`
|
|
18351
|
+
[`${file} is a valid manifest.`, ...requiredSecretLines(names)].join("\n") + "\n"
|
|
17777
18352
|
);
|
|
17778
18353
|
return 0;
|
|
17779
18354
|
}
|
|
@@ -18162,21 +18737,32 @@ async function runDoctor(parsed) {
|
|
|
18162
18737
|
const mutating = access.projects.filter(
|
|
18163
18738
|
(project) => project.permissions.some((permission) => !permission.endsWith(".read"))
|
|
18164
18739
|
);
|
|
18740
|
+
const organizationWide = access.tenantPermissions.includes("project.read");
|
|
18741
|
+
const alsoOrganizationWide = access.tenantPermissions.length > 0 ? `Organization-wide it holds ${access.tenantPermissions.join(", ")}, which reaches no Project. ` : "";
|
|
18742
|
+
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");
|
|
18165
18743
|
checks.push(
|
|
18166
|
-
|
|
18744
|
+
organizationWide ? {
|
|
18167
18745
|
name: "Access",
|
|
18168
18746
|
ok: true,
|
|
18169
|
-
detail: `the whole organization \u2014 ${access.tenantPermissions.join(", ")}`,
|
|
18170
|
-
facts: {
|
|
18747
|
+
detail: `the whole organization \u2014 ${access.tenantPermissions.join(", ")}${perProject}`,
|
|
18748
|
+
facts: {
|
|
18749
|
+
scope: "organization",
|
|
18750
|
+
grantedProjectIds: projects.length > 0 ? projects.join(",") : null
|
|
18751
|
+
}
|
|
18171
18752
|
} : projects.length === 0 ? {
|
|
18172
18753
|
// A FAILURE, NOT A NOTE. This credential authenticates and can do
|
|
18173
18754
|
// nothing at all — and the check said so in words while `report`
|
|
18174
18755
|
// computed the exit code from `ok` alone, so `doctor` printed
|
|
18175
18756
|
// "nothing else will work" and exited 0. A pipeline reads the code,
|
|
18176
18757
|
// not the sentence.
|
|
18758
|
+
//
|
|
18759
|
+
// STILL A FAILURE WITH KNOWLEDGE READ AND NO PROJECT. That gate is a
|
|
18760
|
+
// legitimate one — it serves an MCP client — but every `vfac`
|
|
18761
|
+
// command that reads a Project is refused under it, and a pipeline
|
|
18762
|
+
// pointed at it has the wrong credential.
|
|
18177
18763
|
name: "Access",
|
|
18178
18764
|
ok: false,
|
|
18179
|
-
detail: "none. This credential holds no Project and nothing organization-wide, so every command below it will be refused. Ask an organization administrator for access."
|
|
18765
|
+
detail: access.tenantPermissions.length > 0 ? `no Project. ${alsoOrganizationWide}Every command below that reads a Project will be refused. A gate holding only that serves an MCP client, not vfac; ask an organization administrator for a Project role on the gate vfac uses.` : "none. This credential holds no Project and nothing organization-wide, so every command below it will be refused. Ask an organization administrator for access."
|
|
18180
18766
|
} : {
|
|
18181
18767
|
name: "Access",
|
|
18182
18768
|
ok: true,
|
|
@@ -18205,7 +18791,7 @@ async function runDoctor(parsed) {
|
|
|
18205
18791
|
// names are the difference.
|
|
18206
18792
|
grantedProjectIds: projects.join(",")
|
|
18207
18793
|
},
|
|
18208
|
-
detail: `${projects.join(", ")} \u2014 and nothing organization-wide.
|
|
18794
|
+
detail: `${projects.join(", ")}` + (access.tenantPermissions.length > 0 ? ". " : " \u2014 and nothing organization-wide. ") + alsoOrganizationWide + (mutating.length > 0 ? "You can deploy in those Projects, read their catalogue, and list them. " : "Read-only there: catalogue, manifests, resources and operations. An apply or a lifecycle command is refused, and that is what was granted rather than a fault. ") + "Anything ACROSS the organization \u2014 a Project you hold nothing in, or its members \u2014 is refused."
|
|
18209
18795
|
}
|
|
18210
18796
|
);
|
|
18211
18797
|
checks.push({
|
|
@@ -18762,6 +19348,21 @@ function renderProduct(product) {
|
|
|
18762
19348
|
lines.push(` ${setting.label} ${setting.type ?? ""}`.trimEnd());
|
|
18763
19349
|
}
|
|
18764
19350
|
}
|
|
19351
|
+
const together = seed.groups.filter((group) => group.within !== null);
|
|
19352
|
+
if (together.length > 0) {
|
|
19353
|
+
lines.push("", " Settings that go together");
|
|
19354
|
+
for (const group of together) {
|
|
19355
|
+
const index = group.settings.indexOf(group.trigger);
|
|
19356
|
+
const trigger = index === -1 ? `${group.trigger} (secret)` : group.labels[index] ?? group.trigger;
|
|
19357
|
+
const needs = [
|
|
19358
|
+
...group.labels.filter((_, at) => at !== index),
|
|
19359
|
+
...group.secrets.filter((path) => path !== group.trigger).map((path) => `--secret ${path}=env:VAR`)
|
|
19360
|
+
];
|
|
19361
|
+
lines.push(
|
|
19362
|
+
` ${trigger} also needs ${needs.join(" + ")} (if ${group.within} is configured)`
|
|
19363
|
+
);
|
|
19364
|
+
}
|
|
19365
|
+
}
|
|
18765
19366
|
const dependencies = product.dependencies ?? [];
|
|
18766
19367
|
if (dependencies.length > 0) {
|
|
18767
19368
|
lines.push("", " Connections to another resource");
|
|
@@ -19063,7 +19664,7 @@ async function runManifestInit(parsed) {
|
|
|
19063
19664
|
);
|
|
19064
19665
|
}
|
|
19065
19666
|
for (const group of seed.groups) {
|
|
19066
|
-
const settings = group.
|
|
19667
|
+
const settings = group.labels.map((label) => `spec.configuration.${label}`);
|
|
19067
19668
|
const secrets = group.secrets.map((name2) => `--secret ${name2}=env:VAR`);
|
|
19068
19669
|
notes.push(
|
|
19069
19670
|
`${[...settings, ...secrets].join(" + ")} \u2014 all of these together, or none of them`
|
|
@@ -19216,10 +19817,13 @@ async function runWhoami(parsed) {
|
|
|
19216
19817
|
}
|
|
19217
19818
|
const { principal, access } = result.data;
|
|
19218
19819
|
const lines = [`${principal.name} ${principal.id} (${principal.type})`];
|
|
19219
|
-
if (access.
|
|
19820
|
+
if (access.tenantPermissions.length > 0) {
|
|
19220
19821
|
lines.push("", "Granted across the whole organization");
|
|
19221
19822
|
lines.push(` ${access.tenantPermissions.join(", ")}`);
|
|
19222
|
-
}
|
|
19823
|
+
}
|
|
19824
|
+
if (access.projects.length > 0 && access.tenantPermissions.length > 0) {
|
|
19825
|
+
lines.push("", "Granted in these Projects");
|
|
19826
|
+
} else if (!access.tenantWide) {
|
|
19223
19827
|
lines.push("", "Granted in these Projects only \u2014 nothing organization-wide.");
|
|
19224
19828
|
lines.push(" Listing these works. Reading ACROSS the organization does not.");
|
|
19225
19829
|
}
|
|
@@ -19227,7 +19831,7 @@ async function runWhoami(parsed) {
|
|
|
19227
19831
|
lines.push("", ` ${project.projectId}`);
|
|
19228
19832
|
lines.push(` ${project.permissions.join(", ")}`);
|
|
19229
19833
|
}
|
|
19230
|
-
if (
|
|
19834
|
+
if (access.projects.length === 0 && !access.tenantPermissions.includes("project.read")) {
|
|
19231
19835
|
lines.push("", " No Projects. Ask an organization administrator for access.");
|
|
19232
19836
|
}
|
|
19233
19837
|
lines.push(
|
|
@@ -19276,7 +19880,9 @@ Options
|
|
|
19276
19880
|
--endpoint your organization's portal. Defaults to VFAC_ENDPOINT, then the
|
|
19277
19881
|
stored context
|
|
19278
19882
|
--secret plan and apply: NAME=env:VARIABLE \u2014 the value is read from the
|
|
19279
|
-
environment.
|
|
19883
|
+
environment. NAME is the setting's name, or its path when it
|
|
19884
|
+
sits inside a group: --secret /upstreamAuth/clientSecret=env:VAR.
|
|
19885
|
+
Plan a run that sets a secret with the same
|
|
19280
19886
|
--secret inputs the apply will carry; without them the plan
|
|
19281
19887
|
answers about a different request. A value on the command line
|
|
19282
19888
|
is in your shell history and in the process table, so it is
|