@varde-flyt/vfac 0.15.2 → 0.15.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +4 -2
  2. package/dist/vfac.mjs +277 -48
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -91,8 +91,10 @@ operations of a resource are listed newest first, one page at a time; the
91
91
  vfac get operations --resource <productInstanceId>
92
92
  ```
93
93
 
94
- Secrets are supplied per apply and never belong in the manifest — a manifest is a
95
- file you commit. A value for a setting the Product declares secret is refused
94
+ Secrets are passed as `--secret` inputs, beside the manifest and never in it — a manifest is a
95
+ file you commit. Pass one when a resource is created, and afterwards only on a run that sets or
96
+ changes one: a secret sent is a secret written, so a run that repeats one plans `UPDATE` rather
97
+ than `NO_CHANGE`. A value for a setting the Product declares secret is refused
96
98
  at every door by the platform, and by `vfac` before the file is sent when the
97
99
  file pins the release the catalog currently offers. **Plan the run that sets one with the same `--secret` inputs the
98
100
  apply will carry**: `plan` takes the flag exactly as `apply` does, and without it
package/dist/vfac.mjs CHANGED
@@ -14328,6 +14328,52 @@ function manifestYaml(document) {
14328
14328
  return (0, import_yaml.stringify)(document, { compat: "yaml-1.1" });
14329
14329
  }
14330
14330
 
14331
+ // ../platform-schemas/dist/governance-feed.js
14332
+ var GOVERNANCE_FEED_CATEGORIES = ["connections", "resources"];
14333
+ var OPAQUE_ID = /^[a-z]{2,4}_[0-9a-z]{10,40}$/;
14334
+ var opaqueId = external_exports.string().regex(OPAQUE_ID);
14335
+ var GovernanceFeedActorSchema = external_exports.discriminatedUnion("type", [
14336
+ external_exports.object({ type: external_exports.literal("person"), ref: external_exports.string().min(1).max(128) }),
14337
+ external_exports.object({ type: external_exports.literal("platform") }),
14338
+ external_exports.object({ type: external_exports.literal("resource"), id: opaqueId }),
14339
+ external_exports.object({ type: external_exports.literal("system") })
14340
+ ]);
14341
+ var GovernanceFeedEventSchema = external_exports.object({
14342
+ /** The audit row's id. Stable, so a workspace can recognise a re-delivery. */
14343
+ id: opaqueId,
14344
+ /** The Control Plane's own event name, from its allowlist. */
14345
+ event: external_exports.string().regex(/^[a-z][a-z0-9_]*(?:\.[a-z][a-z0-9_]*){1,4}$/).max(80),
14346
+ category: external_exports.enum(GOVERNANCE_FEED_CATEGORIES),
14347
+ /** RFC 3339: when it happened. */
14348
+ occurredAt: external_exports.string().datetime({ offset: true }),
14349
+ actor: GovernanceFeedActorSchema,
14350
+ action: external_exports.string().regex(/^[a-z][a-z0-9_]{0,31}$/),
14351
+ outcome: external_exports.enum(["success", "failure"]),
14352
+ projectId: opaqueId.optional(),
14353
+ /** The Resource the event is about — for a connection, the PROVIDER. */
14354
+ resourceId: opaqueId.optional(),
14355
+ /** For a connection between two Resources, the one that consumes it. */
14356
+ consumerResourceId: opaqueId.optional(),
14357
+ operationId: opaqueId.optional(),
14358
+ target: external_exports.object({
14359
+ type: external_exports.enum(["service_binding", "resource"]),
14360
+ id: opaqueId.optional()
14361
+ })
14362
+ });
14363
+ var GovernanceFeedPositionSchema = external_exports.object({
14364
+ at: external_exports.string().datetime({ offset: true }),
14365
+ id: external_exports.union([external_exports.literal(""), opaqueId])
14366
+ });
14367
+ var GOVERNANCE_FEED_MAX_LIMIT = 100;
14368
+ var GovernanceFeedRequestSchema = external_exports.object({
14369
+ after: GovernanceFeedPositionSchema,
14370
+ limit: external_exports.number().int().min(1).max(GOVERNANCE_FEED_MAX_LIMIT).optional()
14371
+ }).strict();
14372
+ var GovernanceFeedResponseSchema = external_exports.object({
14373
+ events: external_exports.array(GovernanceFeedEventSchema).max(GOVERNANCE_FEED_MAX_LIMIT),
14374
+ position: GovernanceFeedPositionSchema
14375
+ });
14376
+
14331
14377
  // ../platform-schemas/dist/index.js
14332
14378
  var TENANT_KEY_REGEX = /^[a-z][a-z0-9-]{2,23}[a-z0-9]$/;
14333
14379
  var RESERVED_TENANT_KEYS = /* @__PURE__ */ new Set([
@@ -14453,7 +14499,7 @@ var kindSchema = (kind) => external_exports.literal(kind, {
14453
14499
  errorMap: () => ({ message: `kind must be "${kind}".` })
14454
14500
  });
14455
14501
 
14456
- // ../product-contracts/dist/blocked-hosts.js
14502
+ // ../public-fetch/dist/index.js
14457
14503
  var BLOCKED_HOSTS = [
14458
14504
  /^localhost$/i,
14459
14505
  /^169\.254\./,
@@ -14686,6 +14732,7 @@ var IDENTITY_OIDC_CLIENT_V1 = {
14686
14732
  }
14687
14733
  };
14688
14734
  var DIRECTORY_SCOPES = [
14735
+ "directory.group-members.list",
14689
14736
  "directory.groups.search",
14690
14737
  "directory.membership.check",
14691
14738
  "directory.principals.resolve",
@@ -14737,9 +14784,58 @@ var IDENTITY_DIRECTORY_V1 = {
14737
14784
  }
14738
14785
  }
14739
14786
  };
14787
+ var OBSERVABILITY_LOGS_SCOPES = ["logs.read", "logs.write"];
14788
+ var OBSERVABILITY_LOGS_V1 = {
14789
+ kind: "service-api",
14790
+ id: "observability.logs",
14791
+ version: "v1",
14792
+ // TENANT-MULTIPLE, for the reason the two identity interfaces are: an
14793
+ // organization may run more than one log workspace — one per environment, one
14794
+ // per department — and which one a Product emits to is the customer's answer
14795
+ // rather than the platform's. There is deliberately no fallback to "the only
14796
+ // one" when a Tenant happens to have exactly one, because that rule would
14797
+ // change the day a second workspace is deployed and bind existing consumers
14798
+ // by accident.
14799
+ cardinality: "tenant-multiple",
14800
+ summary: "Send operational log records to a log workspace in this organization, and optionally query them back. The provider returns an ingest address and a credential; the consumer calls it directly.",
14801
+ maxRequestedScopes: OBSERVABILITY_LOGS_SCOPES.length,
14802
+ scopes: OBSERVABILITY_LOGS_SCOPES,
14803
+ // `logs.write` IS REQUIRED, and it is the only required scope on any
14804
+ // interface in this file. The reason is that the alternative is incoherent
14805
+ // rather than merely narrow: a binding to a log workspace that may not write
14806
+ // to it has asked for a connection it cannot use for the thing the connection
14807
+ // is for. A consumer that only wants to READ somebody else's records is not
14808
+ // this interface's case — it is a query surface the providing Product
14809
+ // exposes in its own right.
14810
+ requiredScopes: ["logs.write"],
14811
+ protocol: {
14812
+ authScheme: "bearer",
14813
+ credentialTransport: "authorization-header"
14814
+ },
14815
+ fields: {
14816
+ ingestUrl: {
14817
+ kind: "attribute",
14818
+ always: true,
14819
+ format: "https-url",
14820
+ description: 'The base address records are sent to: `POST {ingestUrl}/records` with `{"records": [...]}`, each record `{message, severity, observedAt?, fields?}`. Operations are relative to it, so the provider can move them without a new binding.'
14821
+ },
14822
+ grantedScopes: {
14823
+ kind: "attribute",
14824
+ always: true,
14825
+ format: "space-separated-tokens",
14826
+ description: "Space-separated scopes actually granted, which may be narrower than those requested. Read this rather than assuming the request was honoured."
14827
+ },
14828
+ apiToken: {
14829
+ kind: "secret",
14830
+ always: true,
14831
+ description: "The credential for the address above, presented as a bearer token. Delivered as a secret reference, never as an environment value, and rotatable without a new binding."
14832
+ }
14833
+ }
14834
+ };
14740
14835
  var SERVICE_INTERFACE_CONTRACTS = {
14741
14836
  "identity.oidc-client/v1": IDENTITY_OIDC_CLIENT_V1,
14742
- "identity.directory/v1": IDENTITY_DIRECTORY_V1
14837
+ "identity.directory/v1": IDENTITY_DIRECTORY_V1,
14838
+ "observability.logs/v1": OBSERVABILITY_LOGS_V1
14743
14839
  };
14744
14840
  var SERVICE_INTERFACE_KEYS = Object.keys(SERVICE_INTERFACE_CONTRACTS).sort();
14745
14841
  var SERVICE_INTERFACE_IDS = [
@@ -14998,11 +15094,35 @@ var ProductDependencySchema = external_exports.object({
14998
15094
  var ProductDependencyV2Schema = ProductDependencySchema.extend({
14999
15095
  requiredWhen: RequiredWhenV2Schema.optional()
15000
15096
  });
15097
+ function hasControlCharacter(value) {
15098
+ for (let i = 0; i < value.length; i += 1) {
15099
+ const code = value.charCodeAt(i);
15100
+ if (code < 32 || code === 127)
15101
+ return true;
15102
+ }
15103
+ return false;
15104
+ }
15105
+ var GuidanceTextSchema = (min, max) => external_exports.string().min(min).max(max).refine((value) => !hasControlCharacter(value), {
15106
+ message: "Guidance is one line, with no control characters."
15107
+ });
15108
+ var ConsumerGuidanceSchema = external_exports.object({
15109
+ /** One paragraph: what this provider does with what a consumer sends or asks. */
15110
+ summary: GuidanceTextSchema(20, 600),
15111
+ /** Concrete, provider-specific practices, one per entry. */
15112
+ notes: external_exports.array(GuidanceTextSchema(10, 400)).min(1).max(12).optional()
15113
+ }).strict();
15001
15114
  var ServiceInterfaceProvideSchema = external_exports.object({
15002
15115
  key: ServiceBindingKeySchema,
15003
15116
  interfaceId: ServiceInterfaceIdSchema,
15004
15117
  interfaceVersion: ServiceInterfaceVersionSchema,
15005
- cardinality: ServiceInterfaceCardinalitySchema
15118
+ cardinality: ServiceInterfaceCardinalitySchema,
15119
+ /**
15120
+ * `.optional()`, NEVER `.default()`. A default materialises the key,
15121
+ * `contractDigest` digests the parsed object, and every release published
15122
+ * before this field existed would be refused on republish as
15123
+ * `RELEASED_CONTRACT_MUTATED`.
15124
+ */
15125
+ consumerGuidance: ConsumerGuidanceSchema.optional()
15006
15126
  }).strict();
15007
15127
  var LifecycleCapabilitiesSchema = external_exports.object({
15008
15128
  upgrade: external_exports.boolean(),
@@ -16062,10 +16182,38 @@ var ComponentHealthSchema = external_exports.object({
16062
16182
  readiness: ProbeSchema,
16063
16183
  liveness: ProbeSchema
16064
16184
  }).strict();
16185
+ var PUBLIC_INGRESS_AUTHENTICATION = external_exports.enum([
16186
+ "api-key",
16187
+ "oauth2",
16188
+ "mtls",
16189
+ "signed-request",
16190
+ "tenant-human-session"
16191
+ ]);
16192
+ var HUMAN_SESSION_MECHANISMS = external_exports.array(external_exports.enum(["federated-oidc", "email-one-time-link", "local-password"])).min(1).max(3);
16193
+ var INGRESS_PATH_PREFIX = external_exports.string().regex(/^\/[a-z0-9\-/]*$/).max(64);
16194
+ var IngressSurfaceSchema = external_exports.object({
16195
+ pathPrefix: INGRESS_PATH_PREFIX,
16196
+ authentication: PUBLIC_INGRESS_AUTHENTICATION,
16197
+ /** Required and refused exactly as the component-level field is. */
16198
+ humanSessionMechanisms: HUMAN_SESSION_MECHANISMS.optional(),
16199
+ /** Who calls this part of the component, for the reviewer. */
16200
+ purpose: external_exports.string().min(20).max(300)
16201
+ }).strict();
16202
+ var MAX_INGRESS_SURFACES = 8;
16065
16203
  var ComponentIngressSchema = external_exports.discriminatedUnion("exposure", [
16066
16204
  external_exports.object({
16067
16205
  exposure: external_exports.literal("public"),
16068
16206
  /**
16207
+ * `oauth2`: the routes it names accept OAuth access tokens. The
16208
+ * Authorization Server that issues them may be an external authority, or
16209
+ * the Product may implement it itself — an MCP server that runs its own
16210
+ * OAuth 2.1 discovery, authorization, consent, PKCE and token issuance
16211
+ * declares `oauth2` for the endpoint those tokens reach. Either way
16212
+ * the Product must truthfully implement and expose the flow it declares.
16213
+ * The platform provides none of it implicitly: it runs no Authorization
16214
+ * Server, registers no client, issues no token and validates none in
16215
+ * front of the container.
16216
+ *
16069
16217
  * `tenant-human-session` is the one that needs explaining: a console the
16070
16218
  * Product itself signs humans into, rather than delegating to a caller's
16071
16219
  * credential.
@@ -16086,16 +16234,18 @@ var ComponentIngressSchema = external_exports.discriminatedUnion("exposure", [
16086
16234
  * defaulted to, it runs with mail disabled entirely, and the temporary
16087
16235
  * credential it bootstraps has its own list of properties in that section.
16088
16236
  *
16089
- * `oauth2` remains correct for a component that accepts a token somebody
16090
- * else minted. Declare this one when the Product owns the session.
16237
+ * WHERE THE TWO MEET: a Product that is its own Authorization Server. The
16238
+ * person approving a client must first be signed in through the Product's
16239
+ * ordinary `tenant-human-session` — approving is something a signed-in
16240
+ * person does, never a sign-in of its own — so the routes serving that
16241
+ * sign-in and the approval declare `tenant-human-session` and its
16242
+ * mechanisms, as the component's own `authentication` or as a surface,
16243
+ * and the routes accepting the resulting tokens declare `oauth2`. `oauth2`
16244
+ * describes how a token is PRESENTED, never how a person signs in:
16245
+ * whoever issued the token, a sign-in the Product runs carries the
16246
+ * obligation above.
16091
16247
  */
16092
- authentication: external_exports.enum([
16093
- "api-key",
16094
- "oauth2",
16095
- "mtls",
16096
- "signed-request",
16097
- "tenant-human-session"
16098
- ]),
16248
+ authentication: PUBLIC_INGRESS_AUTHENTICATION,
16099
16249
  /**
16100
16250
  * WHICH of those three mechanisms this Product actually offers.
16101
16251
  *
@@ -16118,8 +16268,16 @@ var ComponentIngressSchema = external_exports.discriminatedUnion("exposure", [
16118
16268
  * which — never a failure of another. Listing all three means all three
16119
16269
  * are implemented, not that the Product will fall back through them.
16120
16270
  */
16121
- humanSessionMechanisms: external_exports.array(external_exports.enum(["federated-oidc", "email-one-time-link", "local-password"])).min(1).max(3).optional(),
16122
- pathPrefix: external_exports.string().regex(/^\/[a-z0-9\-/]*$/).max(64),
16271
+ humanSessionMechanisms: HUMAN_SESSION_MECHANISMS.optional(),
16272
+ pathPrefix: INGRESS_PATH_PREFIX,
16273
+ /**
16274
+ * Parts of this component's routes that authenticate differently from
16275
+ * `authentication` above. Optional, and `.optional()` rather than a
16276
+ * default: a released Blueprint is re-parsed on the read path and its
16277
+ * digest is taken over the parsed object, so a materialised empty list
16278
+ * would refuse a republish of unchanged bytes.
16279
+ */
16280
+ surfaces: external_exports.array(IngressSurfaceSchema).min(1).max(MAX_INGRESS_SURFACES).optional(),
16123
16281
  /**
16124
16282
  * Why this component is public at all. docs/SECURITY.md §8 makes adding
16125
16283
  * public ingress a security-approval trigger; requiring the author to
@@ -16678,6 +16836,20 @@ var DataStoreSchema = external_exports.object({
16678
16836
  * or push it outside the contract — where nothing governs its retention,
16679
16837
  * its data categories or its deletion.
16680
16838
  *
16839
+ * `log-store` was added for the same reason one level along: a Product that
16840
+ * emits structured operational records about its own running — requests
16841
+ * served, durations, outcomes — had nowhere in this contract to say so, and
16842
+ * a store it cannot name is a store whose retention, data categories and
16843
+ * deletion nothing governs. It is LOGS, deliberately and only. Metrics and
16844
+ * traces are different capabilities with different shapes, and naming this
16845
+ * one after the signal it carries is what keeps a later `metric-store` from
16846
+ * being a silent widening of this one.
16847
+ *
16848
+ * PROVIDER-NEUTRAL, like every other name here. Nothing in this enum names
16849
+ * an engine, a vendor or a wire format, and `log-store` must not become the
16850
+ * exception — which implementation backs it is a platform promise published
16851
+ * in `DATA_STORE_INTERFACES`, not a choice a publisher makes.
16852
+ *
16681
16853
  * Naming a kind is a CONTRACT question. Whether this Control Plane can
16682
16854
  * provision it is a DEPLOYABILITY question, answered separately by
16683
16855
  * `validateDeployability` — see deployability.ts.
@@ -16689,7 +16861,8 @@ var DataStoreSchema = external_exports.object({
16689
16861
  "vector-index",
16690
16862
  "queue",
16691
16863
  "document-database",
16692
- "search-index"
16864
+ "search-index",
16865
+ "log-store"
16693
16866
  ]),
16694
16867
  purpose: external_exports.string().min(10).max(300),
16695
16868
  /**
@@ -16711,7 +16884,7 @@ var DataStoreSchema = external_exports.object({
16711
16884
  }).strict(),
16712
16885
  deleteBehavior: external_exports.enum(["purge-with-instance", "purge-after-retention"]),
16713
16886
  /**
16714
- * Ask for RUNTIME access to the store's own objects.
16887
+ * Ask for RUNTIME access to the store's own contents.
16715
16888
  *
16716
16889
  * OPT-IN, AND PRESENT-OR-ABSENT RATHER THAN AN ENUM. A `mode` field would
16717
16890
  * have to be optional with a default to keep published documents parseable,
@@ -16720,17 +16893,31 @@ var DataStoreSchema = external_exports.object({
16720
16893
  * and a legitimate republish would be refused as an immutability conflict.
16721
16894
  * Presence is a cleaner discriminator and reads better in a diff.
16722
16895
  *
16723
- * WHAT IT DOES NOT GRANT: S3 credentials. The component receives an address,
16724
- * its own key prefix and a signing key for the platform's storage broker,
16725
- * which checks every key against the INSTANCE the signature proved. A
16726
- * Scaleway key cannot be scoped to a prefix, and the provider's quota is 50
16727
- * API keys per Organization, so one per Product Instance would cap the
16728
- * platform at roughly 45 of them.
16896
+ * WHAT IT DOES NOT GRANT: a provider credential, on either kind that
16897
+ * accepts it. The component receives an address, what identifies it, and a
16898
+ * signing key for the platform service that brokers the access — and that
16899
+ * service checks every request against the INSTANCE the signature proved.
16900
+ *
16901
+ * For `object-storage` the reason is that a Scaleway key cannot be scoped
16902
+ * to a prefix or a bucket, and the provider's quota is 50 API keys per
16903
+ * Organization, so one per Product Instance would cap the platform at
16904
+ * roughly 45 of them. For `log-store` the reason is sharper: a Cockpit
16905
+ * token carries a Project and a scope and has NO data-source field, and the
16906
+ * whole platform lives in one Scaleway Project — so a token delivered to
16907
+ * one Tenant's component would read and write every other Tenant's logs.
16908
+ * Neither is a limit to work around; both are why the brokering service
16909
+ * exists.
16910
+ *
16911
+ * WHICH KINDS MAY DECLARE IT is `RUNTIME_ACCESS_KINDS`, and the variables
16912
+ * each one then receives are `RUNTIME_ACCESS_SUFFIXES`. The cross-field
16913
+ * check below reads both.
16729
16914
  */
16730
16915
  runtimeAccess: external_exports.object({
16731
16916
  /**
16732
- * Names the variables the platform injects — `{PREFIX}_ENDPOINT_URL`,
16733
- * `_TENANT_ID`, `_PREFIX` and `_SIGNING_KEY`.
16917
+ * Names the variables the platform injects: `{PREFIX}_ENDPOINT_URL`,
16918
+ * `_TENANT_ID` and `_SIGNING_KEY` on every kind that accepts runtime
16919
+ * access, plus `_PREFIX` on `object-storage`, which is the only one
16920
+ * addressed by a key prefix.
16734
16921
  *
16735
16922
  * Bounded at 32 so every derived name fits `EnvVarNameSchema`'s 64, and
16736
16923
  * REFUSED at authoring time rather than silently truncated at deploy —
@@ -16749,6 +16936,17 @@ var DataStoreSchema = external_exports.object({
16749
16936
  access: external_exports.enum(["read-write", "read-only"])
16750
16937
  }).strict()).min(1).max(12)
16751
16938
  }).strict();
16939
+ var RUNTIME_ACCESS_SUFFIXES = {
16940
+ "object-storage": ["_ENDPOINT_URL", "_TENANT_ID", "_PREFIX", "_SIGNING_KEY"],
16941
+ "log-store": ["_ENDPOINT_URL", "_TENANT_ID", "_SIGNING_KEY"]
16942
+ };
16943
+ var RUNTIME_ACCESS_KINDS = new Set(Object.keys(RUNTIME_ACCESS_SUFFIXES));
16944
+ function runtimeAccessEnvNames(kind, envPrefix) {
16945
+ const suffixes = RUNTIME_ACCESS_SUFFIXES[kind];
16946
+ if (suffixes === void 0)
16947
+ return [];
16948
+ return suffixes.map((suffix) => `${envPrefix}${suffix}`);
16949
+ }
16752
16950
  var EgressRuleSchema = external_exports.object({
16753
16951
  componentId: ComponentIdSchema,
16754
16952
  destination: external_exports.discriminatedUnion("kind", [
@@ -17244,29 +17442,34 @@ function checkDeploymentBlueprint(blueprint, ctx) {
17244
17442
  });
17245
17443
  blueprint.components.forEach((component, index) => {
17246
17444
  if (component.ingress.exposure === "public") {
17247
- const mechanismPath = ["components", index, "ingress", "humanSessionMechanisms"];
17248
- const declared = component.ingress.humanSessionMechanisms;
17249
- if (component.ingress.authentication === "tenant-human-session") {
17250
- if (declared === void 0) {
17445
+ const ingress = component.ingress;
17446
+ checkHumanSessionMechanisms(ctx, ["components", index, "ingress"], ingress.authentication, ingress.humanSessionMechanisms);
17447
+ const surfaces = ingress.surfaces ?? [];
17448
+ const base = ingress.pathPrefix.replace(/\/+$/, "");
17449
+ surfaces.forEach((surface, surfaceIndex) => {
17450
+ const surfacePath = ["components", index, "ingress", "surfaces", surfaceIndex];
17451
+ checkHumanSessionMechanisms(ctx, surfacePath, surface.authentication, surface.humanSessionMechanisms);
17452
+ const prefix = surface.pathPrefix.replace(/\/+$/, "");
17453
+ if (!(prefix.startsWith(`${base}/`) && prefix.length > base.length + 1)) {
17251
17454
  ctx.addIssue({
17252
17455
  code: external_exports.ZodIssueCode.custom,
17253
- path: mechanismPath,
17254
- message: "A component declaring `tenant-human-session` must name which approved mechanisms it implements, so a customer can tell whether it fits their organization before they deploy it."
17255
- });
17256
- } else if (new Set(declared).size !== declared.length) {
17257
- ctx.addIssue({
17258
- code: external_exports.ZodIssueCode.custom,
17259
- path: mechanismPath,
17260
- message: "Each mechanism may be named once."
17456
+ path: [...surfacePath, "pathPrefix"],
17457
+ message: `A surface covers part of the component's routes, so its prefix must lie under "${ingress.pathPrefix}" and differ from it.`
17261
17458
  });
17262
17459
  }
17263
- } else if (declared !== void 0) {
17264
- ctx.addIssue({
17265
- code: external_exports.ZodIssueCode.custom,
17266
- path: mechanismPath,
17267
- message: "Only a component whose ingress authentication is `tenant-human-session` signs people in itself; remove this, or declare that authentication."
17460
+ surfaces.forEach((other, otherIndex) => {
17461
+ if (otherIndex <= surfaceIndex)
17462
+ return;
17463
+ const otherPrefix = other.pathPrefix.replace(/\/+$/, "");
17464
+ if (otherPrefix === prefix || otherPrefix.startsWith(`${prefix}/`) || prefix.startsWith(`${otherPrefix}/`)) {
17465
+ ctx.addIssue({
17466
+ code: external_exports.ZodIssueCode.custom,
17467
+ path: ["components", index, "ingress", "surfaces", otherIndex, "pathPrefix"],
17468
+ message: `Surfaces "${surface.pathPrefix}" and "${other.pathPrefix}" overlap; a path may be claimed by one surface only.`
17469
+ });
17470
+ }
17268
17471
  });
17269
- }
17472
+ });
17270
17473
  }
17271
17474
  const envNames = component.env.map((binding) => binding.name);
17272
17475
  const duplicateEnv = envNames.filter((name, i) => envNames.indexOf(name) !== i);
@@ -17596,11 +17799,11 @@ function checkDeploymentBlueprint(blueprint, ctx) {
17596
17799
  if (!access)
17597
17800
  return;
17598
17801
  const path = ["dataStores", index, "runtimeAccess"];
17599
- if (store.kind !== "object-storage") {
17802
+ if (!RUNTIME_ACCESS_KINDS.has(store.kind)) {
17600
17803
  ctx.addIssue({
17601
17804
  code: external_exports.ZodIssueCode.custom,
17602
17805
  path,
17603
- message: `Runtime access is only available for an object-storage store; "${store.id}" is a ${store.kind}.`
17806
+ message: `Runtime access is only available for a ${[...RUNTIME_ACCESS_KINDS].sort().join(" or ")} store; "${store.id}" is a ${store.kind}.`
17604
17807
  });
17605
17808
  }
17606
17809
  const first = usedPrefixes.get(access.envPrefix);
@@ -17612,7 +17815,7 @@ function checkDeploymentBlueprint(blueprint, ctx) {
17612
17815
  });
17613
17816
  }
17614
17817
  usedPrefixes.set(access.envPrefix, index);
17615
- const derived = new Set(["_ENDPOINT_URL", "_TENANT_ID", "_PREFIX", "_SIGNING_KEY"].map((suffix) => `${access.envPrefix}${suffix}`));
17818
+ const derived = new Set(runtimeAccessEnvNames(store.kind, access.envPrefix));
17616
17819
  const binders = new Set(store.bindings.map((binding) => binding.componentId));
17617
17820
  for (const component of blueprint.components) {
17618
17821
  if (!binders.has(component.id))
@@ -17813,6 +18016,30 @@ function checkDeploymentBlueprint(blueprint, ctx) {
17813
18016
  }
17814
18017
  }
17815
18018
  }
18019
+ function checkHumanSessionMechanisms(ctx, at, authentication, declared) {
18020
+ const mechanismPath = [...at, "humanSessionMechanisms"];
18021
+ if (authentication === "tenant-human-session") {
18022
+ if (declared === void 0) {
18023
+ ctx.addIssue({
18024
+ code: external_exports.ZodIssueCode.custom,
18025
+ path: mechanismPath,
18026
+ message: "A component declaring `tenant-human-session` must name which approved mechanisms it implements, so a customer can tell whether it fits their organization before they deploy it."
18027
+ });
18028
+ } else if (new Set(declared).size !== declared.length) {
18029
+ ctx.addIssue({
18030
+ code: external_exports.ZodIssueCode.custom,
18031
+ path: mechanismPath,
18032
+ message: "Each mechanism may be named once."
18033
+ });
18034
+ }
18035
+ } else if (declared !== void 0) {
18036
+ ctx.addIssue({
18037
+ code: external_exports.ZodIssueCode.custom,
18038
+ path: mechanismPath,
18039
+ message: "Only a component whose ingress authentication is `tenant-human-session` signs people in itself; remove this, or declare that authentication."
18040
+ });
18041
+ }
18042
+ }
17816
18043
 
17817
18044
  // ../product-contracts/dist/deployability.js
17818
18045
  var VIRTUAL_MACHINE_PROFILE_INTERFACE = {
@@ -18380,7 +18607,6 @@ function printPlan(plan, json) {
18380
18607
  }
18381
18608
  }
18382
18609
  }
18383
- lines.push(...requiredSecretLines(plan.requiredSecrets ?? []));
18384
18610
  if ((plan.secretsToWrite ?? []).length > 0) {
18385
18611
  lines.push(` secrets to set: ${plan.secretsToWrite.join(", ")}`);
18386
18612
  for (const line of secretWriteConsequence(plan)) lines.push(` ${line}`);
@@ -19548,7 +19774,7 @@ function renderProduct(product) {
19548
19774
  }
19549
19775
  const conditions = product.conditions ?? [];
19550
19776
  if (conditions.length > 0) {
19551
- lines.push("", " Needs setting up outside the platform");
19777
+ lines.push("", " Conditions");
19552
19778
  for (const condition of conditions) {
19553
19779
  lines.push(` ${condition.key ?? ""} ${condition.displayName ?? ""}`.trimEnd());
19554
19780
  }
@@ -19559,7 +19785,10 @@ function renderProduct(product) {
19559
19785
  " descriptions and value constraints are in the JSON form."
19560
19786
  );
19561
19787
  if (settings.some((setting) => setting.secret)) {
19562
- lines.push(" A credential is supplied per apply and never written into a manifest.");
19788
+ lines.push(
19789
+ " A credential is passed with --secret when a resource is created, and afterwards only",
19790
+ " when you set or change one \u2014 never written into a manifest."
19791
+ );
19563
19792
  }
19564
19793
  return lines.join("\n");
19565
19794
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@varde-flyt/vfac",
3
- "version": "0.15.2",
3
+ "version": "0.15.3",
4
4
  "description": "Deploy and manage Varde Flyt Products from a manifest.",
5
5
  "repository": {
6
6
  "type": "git",