@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.
- package/README.md +4 -2
- package/dist/vfac.mjs +277 -48
- 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
|
|
95
|
-
file you commit.
|
|
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
|
-
// ../
|
|
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
|
-
*
|
|
16090
|
-
*
|
|
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:
|
|
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:
|
|
16122
|
-
pathPrefix:
|
|
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
|
|
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:
|
|
16724
|
-
*
|
|
16725
|
-
*
|
|
16726
|
-
*
|
|
16727
|
-
*
|
|
16728
|
-
*
|
|
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
|
|
16733
|
-
* `_TENANT_ID
|
|
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
|
|
17248
|
-
|
|
17249
|
-
|
|
17250
|
-
|
|
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:
|
|
17254
|
-
message:
|
|
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
|
-
|
|
17264
|
-
|
|
17265
|
-
|
|
17266
|
-
|
|
17267
|
-
|
|
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
|
|
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
|
|
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(
|
|
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("", "
|
|
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(
|
|
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
|
}
|