From ea44d54c5a627a84f726885506a3cb722e1165d2 Mon Sep 17 00:00:00 2001 From: Joris Wouter Jonkers Date: Mon, 2 Nov 2026 19:07:33 +0100 Subject: [PATCH] feat(model): let a bucket a browser calls admit its Application's exposures A bucket entry on an edge into an object store may be written as {name, cors}, naming exposures of the consuming Application. Each derives one origin, the bucket catalog carries the origins per bucket, and the catalog method sets one fixed CORS rule on each such bucket and clears it from every other bucket of the catalog. A cors entry that names no exposure is E_UNKNOWN_CORS_EXPOSURE. Closes #836 --- CONTEXT.md | 5 +- docs/adr/README.md | 1 + ...ser-calls-names-the-exposures-it-admits.md | 88 ++++++++++++ docs/requirements/volumes-and-engines.md | 1 + spec/v1/16-dependencies.md | 52 +++++++- spec/v1/20-resolved-deployment.md | 7 +- spec/v1/examples/expected/descriptor.json | 2 +- spec/v1/schemas/project-intent.schema.json | 33 ++++- .../schemas/resolved-deployment.schema.json | 29 ++++ src/check/project.ts | 51 ++++++- src/model/diagnostic.ts | 1 + src/model/project-intent-queries.ts | 15 +++ src/model/project-intent.ts | 13 +- src/model/resolved-deployment.ts | 14 ++ src/resolve/catalog-job.ts | 94 ++++++++++--- test/model/refusals.test.ts | 14 ++ test/model/render.test.ts | 126 ++++++++++++++++++ 17 files changed, 517 insertions(+), 29 deletions(-) create mode 100644 docs/adr/model/0153-a-bucket-a-browser-calls-names-the-exposures-it-admits.md diff --git a/CONTEXT.md b/CONTEXT.md index 568bf83bf..9b78175e5 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -279,7 +279,10 @@ per consuming project: its **bucket key**, minted by the catalog method and written to `secret/data///s3`, and the buckets the key may use, each `-` for a name an edge's `buckets` gives. An edge naming buckets hands its consumer `endpoint`, `accessKeyId` and -`secretAccessKey` ([chapter 16](spec/v1/16-dependencies.md#the-bucket-catalog)). +`secretAccessKey` ([chapter 16](spec/v1/16-dependencies.md#the-bucket-catalog)). A bucket the +consumer's pages call from a browser names the exposures whose origins it +admits, and the catalog method sets its one CORS rule +([chapter 16](spec/v1/16-dependencies.md#a-bucket-a-browser-calls)). **Migration policy**: the Platform document's runner image alias, deadline and resources, which every managed migration builds on diff --git a/docs/adr/README.md b/docs/adr/README.md index 5baf047f7..8d6ff5c8e 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -164,6 +164,7 @@ and linted by the same script with `emf` as its root. | [0149](model/0149-grafanas-instance-and-data-sources-derive-from-the-monitoring-stack.md) | Grafana's instance, its admin credential and its data sources derive from the monitoring stack | open | | [0150](model/0150-k3s-is-scraped-where-the-bootstrap-set-records-its-metrics.md) | k3s is scraped where the bootstrap set records its metrics, as the metrics stack's own ServiceAccount | open | | [0119](model/0119-an-edge-names-its-buckets-and-the-stores-method-mints-each-projects-key.md) | An edge into an object store names its buckets, and the store's catalog method mints each project's key | open | +| [0153](model/0153-a-bucket-a-browser-calls-names-the-exposures-it-admits.md) | A bucket a browser calls names the exposures it admits, and the store's catalog method sets its one CORS rule | open | | [0144](model/0144-the-device-plugin-names-each-card-by-its-index.md) | The device plugin names each card by its index, as the hosts' CDI specification does | open | | [0147](model/0147-the-speakers-find-each-other-by-memberlist-keyed-by-a-generated-key.md) | The speakers find each other by memberlist, keyed by a key the `node` Project's own Vault policy job generates | open | diff --git a/docs/adr/model/0153-a-bucket-a-browser-calls-names-the-exposures-it-admits.md b/docs/adr/model/0153-a-bucket-a-browser-calls-names-the-exposures-it-admits.md new file mode 100644 index 000000000..d3759bedb --- /dev/null +++ b/docs/adr/model/0153-a-bucket-a-browser-calls-names-the-exposures-it-admits.md @@ -0,0 +1,88 @@ +--- +tier: decision +status: accepted +claim: open +owner: joris +date: 2026-11-02 +normative: spec/v1/16-dependencies.md#a-bucket-a-browser-calls +rests-on: ["0004", "0005"] +--- + +# A bucket a browser calls names the exposures it admits, and the store's catalog method sets its one CORS rule + +A bucket entry on an edge into an object store may name the exposures of the +consuming Application whose pages call it from a browser. Each derives one +origin, the catalog carries those origins per bucket, and the store's catalog +method sets one fixed CORS rule on each such bucket and removes any rule from +every other bucket of the catalog. The rules are in +[A bucket a browser calls](../../../spec/v1/16-dependencies.md#a-bucket-a-browser-calls). + +## Rests on + +A bucket's configuration is the object store's, which every consumer of it +contends for, so the platform applies it +([0004](0004-contention-decides-authority.md)), and an origin is a host an +exposure already declares, so it is derived and never written again +([0005](0005-derivation-is-total.md)). + +**False if:** a consumer's pages need a bucket to answer an origin that is no +exposure of its own Application, or need a method beyond the fixed four. +**Settled by:** outline's pages posting an upload to its bucket and reading it +back, with the bucket admitting outline's own origin and no other. + +## Why + +**Most consumers never need it, so it is opt-in per bucket.** Karakeep and +Overleaf reach their buckets from their own Processes alone. A rule derived +from every consumer's exposures would open each of their buckets to their +pages for nothing. The one consumer whose pages call its bucket, outline, +says so on that bucket and no other. + +**The origin is named by exposure, never written as a URL.** The host is +already authored once, on the exposure. A URL in the edge would be a second +copy that drifts the day the host changes, and could name a host the +consuming Application does not serve. + +**The methods are fixed.** A browser posts or puts an upload and reads it +back, so `GET`, `HEAD`, `POST` and `PUT` cover every case the estate has. An +authored method list is a field nobody would set to anything else. Leaving +`DELETE` out keeps a page from deleting an object without its application. + +**The catalog states every bucket's rule, so a removed `cors` leaves nothing +behind.** The method removes a rule from a bucket that admits no origin. A +method that only ever added rules would leave an origin admitted after the +edge stopped naming it, which nobody reading the document could see. + +**The admin API sets the rule.** The store's admin API sets a bucket's CORS +rule with the token the method already signs in with, so no key with +ownership of the bucket is minted to do it. + +## Alternatives + +| option | cost if taken | why rejected | +|---|---|---| +| Derive the rule from every exposure of every consumer | no field | opens every consumer's buckets to its pages, Karakeep's and Overleaf's among them, for no request that needs it | +| An authored origin URL on the bucket | one string | a second copy of a host the exposure already holds, free to name a host the Application does not serve | +| An authored method and header list | full S3 CORS vocabulary | provider vocabulary in Project Intent, for a choice with one answer | +| The method only adds rules | simpler method | a `cors` taken off an edge leaves its origin admitted, invisibly | +| Set the rule from a bootstrap job with an owner key | no model change | a step outside the render, and an owner key on the bucket while it runs | + +## Reversibility + +Undo cost today: one bucket form, one catalog field and one method step: an +hour. Becomes costly once: consumers' pages rely on posting straight to their +buckets, and taking the rule away means routing every upload through the +application. + +## Consequences + +- A bucket entry is a name, or `{name, cors}`. +- A key in the bucket catalog carries `cors` where any of its buckets admits + an origin. +- The catalog method sets and removes bucket CORS rules over the admin API, + and reads each back. +- `E_UNKNOWN_CORS_EXPOSURE` refuses a `cors` entry that names no exposure of + the consuming Application. +- Reaching the bucket from a browser also needs a route to the store's `s3` + surface on a host of its own, which is an ordinary exposure of the store, + restricted by path to the bucket. diff --git a/docs/requirements/volumes-and-engines.md b/docs/requirements/volumes-and-engines.md index 042c4ff9a..cf84d65d7 100644 --- a/docs/requirements/volumes-and-engines.md +++ b/docs/requirements/volumes-and-engines.md @@ -11,6 +11,7 @@ A behaviour of state: Volumes and their Durability Classes, backups and off-clus | REQ-077 | A Process whose `engine` is `mongo` is backed up by the method the Platform document names for `mongo`, which dumps it over the surface that entry names, and its volume's class decides the schedule, the copies kept and the off-cluster copy as for any engine; a `mongo` owns no databases, so an edge into it derives no catalog, no credential and no migration question, `credentials` on that edge is `E_CREDENTIALS_WITHOUT_DATABASE`, and a `mongo` over a volume that derives no backup is `E_ENGINE_WITHOUT_DURABILITY` | [test/model/resolution-derivations.test.ts](../../test/model/resolution-derivations.test.ts) | | REQ-104 | An `irreplaceable` volume's off-cluster copy is encrypted to the age public key its class's policy names: a policy with `offCluster` and no `recipient`, or one that is no age public key, is refused with the Platform document; the volume's backup plan carries the destination, the recipient and `//`, and its backup pod is handed them as `BACKUP_OFF_CLUSTER`, `BACKUP_OFF_CLUSTER_RECIPIENT` and `BACKUP_OFF_CLUSTER_PATH` | [test/model/resolution-derivations.test.ts](../../test/model/resolution-derivations.test.ts) | | REQ-109 | An edge into an `s3` Process that names buckets derives the store's bucket catalog: each project's key at `secret/data///s3`, scoped to `-` for every name its edges give, applied by the engine's catalog method over its `catalogSurface` with the derived admin token; each Process naming buckets holds `read` on its project's key and reads it, and the store's endpoint, through `${dependency:.accessKeyId}`, `.secretAccessKey` and `.endpoint`; `buckets` on an edge that reaches no `s3` Process is `E_BUCKETS_WITHOUT_OBJECT_STORE`, and a `catalogSurface` the Process does not provide is `E_CATALOG_SURFACE_NOT_PROVIDED` | [test/model/render.test.ts](../../test/model/render.test.ts) | +| REQ-171 | A bucket entry `{name, cors}` admits the origin of each exposure of the consuming Application that `cors` names, its tier's scheme and its host; the bucket catalog lists, per key, each bucket that admits an origin with every origin it admits, in bucket and origin order, and lists no `cors` for a key none of whose buckets admits one; a `cors` entry that names no exposure of the consuming Application, or of any Application beneath an edge declared above it, is `E_UNKNOWN_CORS_EXPOSURE` | [test/model/render.test.ts](../../test/model/render.test.ts) | | REQ-124 | A Process that seeds an Asset (`seedAt`) is resolved with the Platform document's `seeder`, its image pinned by digest with the lock's version and its `memory` and `cpu`, which a node must fit beside the Process; the Asset carries its committed text, a `${...}` naming no placeholder source left as written; a seeded Asset the platform names no seeder for is `E_NO_SEEDER`, a seeder the lock does not hold `E_UNLOCKED_IMAGE`, and a `seedAt` that is not a file below one of the Process's volumes or writable paths, whichever level declares it, or that reaches one through `.` or `..`, is `E_SEED_OUTSIDE_VOLUME`, each at the Asset where it is written | [test/model/resolution-derivations.test.ts](../../test/model/resolution-derivations.test.ts) | | REQ-125 | The kubernetes adapter renders a seeded Asset as the first init container `seed`, before the VPN, on the resolved seeder: `/bin/sh -c` a script that makes the directory, keeps the file it replaces as `.superseded`, and writes the Asset at `0600` through a staged copy it moves into place, every path single-quoted; under the posture, with the seeder's resources, mounting the Asset's ConfigMap and only the volumes and writable paths the Assets land below; the Process's own container never mounts a seeded Asset | [test/model/render.test.ts](../../test/model/render.test.ts) | | REQ-158 | A Process whose engine is `mongo` resolves its own Service's name, `..svc.cluster.local`, to itself: it records the name as `selfNames`, and its pod renders a `hostAliases` entry giving that name `127.0.0.1`, so a replica set member configured by the name its consumers reach it by finds itself whatever the Service's type; a Process of any other engine, or of none, records and renders neither | [test/model/resolution-derivations.test.ts](../../test/model/resolution-derivations.test.ts) | diff --git a/spec/v1/16-dependencies.md b/spec/v1/16-dependencies.md index 3af34397f..07f558011 100644 --- a/spec/v1/16-dependencies.md +++ b/spec/v1/16-dependencies.md @@ -23,7 +23,7 @@ dependsOn: | `application` | yes | An Application Id, the only referencable identity ([0010](../../docs/adr/model/0010-flat-application-identity.md)). It must resolve in the composed union: `E_UNRESOLVED_APPLICATION`. | | `surface` | yes | One surface declared by one of that Application's Processes. The port is written once, by the provider, and never restated by a consumer: `E_UNKNOWN_SURFACE` where the name matches nothing. | | `required` | no | Defaults to `true`. | -| `buckets` | no | The buckets the consumer uses in an object store, each a name of up to 32 lower-case letters, digits and hyphens that begins and ends with a letter or a digit ([The bucket catalog](#the-bucket-catalog)). On an edge whose surface no `s3` Process provides it derives nothing, and is `E_BUCKETS_WITHOUT_OBJECT_STORE`. | +| `buckets` | no | The buckets the consumer uses in an object store, each a name of up to 32 lower-case letters, digits and hyphens that begins and ends with a letter or a digit ([The bucket catalog](#the-bucket-catalog)), or `{name, cors}` for one the consumer's pages call from a browser ([A bucket a browser calls](#a-bucket-a-browser-calls)). On an edge whose surface no `s3` Process provides it derives nothing, and is `E_BUCKETS_WITHOUT_OBJECT_STORE`. | **`provides` moved to the Process; the edge did not.** A port is a property of a process, so surfaces are declared by the Process that listens @@ -289,6 +289,51 @@ bucket, all three are `E_UNRESOLVED_PLACEHOLDER`. A bucket the platform owns rather than a consumer, such as one whose name is a public website's hostname, is not a catalog entry: no edge names it. +### A bucket a browser calls + +Most consumers reach their buckets from their own Processes alone. One whose +pages upload to a bucket, or read from it, straight from the browser needs the +store to answer those pages cross-origin, and says so on the bucket itself +([0153](../../docs/adr/model/0153-a-bucket-a-browser-calls-names-the-exposures-it-admits.md)): + +```yaml +dependsOn: + - application: platform-garage + surface: s3 + buckets: + - {name: uploads, cors: [public]} +``` + +`cors` names one or more exposures of the consuming Application, by `name`. +Each admits one origin: the exposure's tier's scheme and its host, as +`${exposure:.#url}` reads it. A bucket written by its name +alone admits none. A bucket two edges of one project name admits every origin +either gives. + +**The rule is fixed, and only the origins are authored.** Every bucket that +admits an origin carries one CORS rule: those origins, the methods `GET`, +`HEAD`, `POST` and `PUT`, any request header, `ETag` exposed, and a preflight +cached for 3600 seconds. The methods are what a browser needs to post or put an +upload and to read it back. `DELETE` is never among them: a page deletes +through its own application, never straight from the browser. + +**CORS grants no access.** The bucket stays private, and every request still +needs a signature the store checks. The rule only lets the named origin's pages +read the answers the store already gives a signed request. + +**The catalog states each bucket's rule exactly.** A key's buckets that admit +an origin are listed in its `cors`, each with every origin it admits, in bucket +and origin order. A bucket of the key absent from that list admits none, and +the method removes any rule the store holds on it, so a `cors` taken off an +edge leaves no origin behind. + +| condition | error | +|---|---| +| a `cors` entry names no exposure of the consuming Application; on an edge declared above the Application, of any Application beneath it | `E_UNKNOWN_CORS_EXPOSURE` | + +`E_UNKNOWN_CORS_EXPOSURE` is scoped to a single document and refused as soon +as the fragment is read: an Application's exposures are its own. + ### Applying the catalog The catalog is applied in the cluster by its engine's method @@ -318,7 +363,7 @@ applying it does not bring up: | object | holds | |---|---| -| the catalog | one JSON document: the datastore's `host` and `port`, the Secret Store's database `connection` its roles issue through, named after the datastore's Process, so two datastores of one engine never share one, the `admin` login's `path`, `user` and `password` keys, and the `databases`, each a `name`, an `owner` and a `data` role, in name order; or, for an object store, its `host` and `port` on the surface the catalog is applied over, the `admin` login's `path` and `token` key, and the `keys`, each a `name`, the `path` the key is written to and its `buckets`, in name order | +| the catalog | one JSON document: the datastore's `host` and `port`, the Secret Store's database `connection` its roles issue through, named after the datastore's Process, so two datastores of one engine never share one, the `admin` login's `path`, `user` and `password` keys, and the `databases`, each a `name`, an `owner` and a `data` role, in name order; or, for an object store, its `host` and `port` on the surface the catalog is applied over, the `admin` login's `path` and `token` key, and the `keys`, each a `name`, the `path` the key is written to, its `buckets` and, where any admits an origin, its `cors`, in name order | | the identity's Vault policy and auth role | `read` on the admin login's document and its metadata, and `create`, `read` and `update` on `database/roles/` for each role the catalog names, or on each key's `path`, written by the Vault policy job like every identity's | | a `ConfigMap`, a `Job` and an hourly `CronJob` | the method image, as the user the lock records, handed the catalog; the policy job's requests, deadline and eligible nodes, and its retries, spelled as its Job and CronJob are ([chapter 30](30-deliverables.md#vault-configuration-is-rendered-not-applied)); named `-catalog-<12 hex>`, the digest of the catalog with what the pod runs with, so a changed catalog or method is a new Job, and marked for the applier to recreate it where only the render changed its template. Its pods carry the [consumer label](#the-consumer-label) of the datastore's surface, which the datastore's policy admits | @@ -352,6 +397,9 @@ catalog names: key named after the project and writes its id and secret there. 2. It creates each of the key's buckets where it is absent. 3. It grants the key read and write on each of its buckets. +4. It sets each of the key's buckets' CORS rule to the one its `cors` derives, + or removes the rule from a bucket that admits no origin, where the store + holds anything else, and reads the rule back. It never deletes a bucket, a key or a grant, and a run applied twice changes nothing the second time. diff --git a/spec/v1/20-resolved-deployment.md b/spec/v1/20-resolved-deployment.md index 96595707b..1735a4c4c 100644 --- a/spec/v1/20-resolved-deployment.md +++ b/spec/v1/20-resolved-deployment.md @@ -1342,7 +1342,7 @@ own Vault role: | `catalog.connection` | derived | a database catalog's: the engine's name, the Secret Store's database connection | | `catalog.admin` | platform-assigned | `secret/data///catalog`, keys `user` and `password`, or `token` for an object store | | `catalog.databases` | derived | a database catalog's: for each project an edge of which reaches the datastore, in name order: `_db`, `-owner`, `-data` | -| `catalog.keys` | derived | a bucket catalog's: for each project an edge of which names buckets in the store, in name order: its `name`, the project; its `path`, `secret/data///s3`; and its `buckets`, `-` for each name its edges give, in name order ([chapter 16](16-dependencies.md#the-bucket-catalog)) | +| `catalog.keys` | derived | a bucket catalog's: for each project an edge of which names buckets in the store, in name order: its `name`, the project; its `path`, `secret/data///s3`; its `buckets`, `-` for each name its edges give, in name order; and, where any of them admits an origin, its `cors`: each such `bucket` with its `origins`, in bucket and origin order ([chapter 16](16-dependencies.md#a-bucket-a-browser-calls)) | | `consumes` | derived | the surface the catalog is applied over, as the [consumer label](16-dependencies.md#the-consumer-label) the method's pods carry names it, which the datastore's policy admits | | `egress` | derived | the datastore on its surface, the Secret Store on its `http` surface, and the cluster's DNS | @@ -1837,6 +1837,10 @@ classDiagram +string path +string[] buckets } + class CatalogBucketCors { + +string bucket + +string[] origins + } class ResolvedEvents { +Namespace namespace +string address @@ -2235,6 +2239,7 @@ classDiagram ResolvedCatalogJob "1" *-- "1" ResolvedBucketCatalog : catalog ResolvedBucketCatalog "1" *-- "1" CatalogAdminToken : admin ResolvedBucketCatalog "1" *-- "1..*" CatalogKey : keys + CatalogKey "1" *-- "0..*" CatalogBucketCors : cors ResolvedDeployment "1" *-- "0..1" ResolvedEvents : events ResolvedDeployment "1" *-- "1..*" ResolvedApplication : applications Provenance "1" *-- "1..*" InputDigest : inputDigests diff --git a/spec/v1/examples/expected/descriptor.json b/spec/v1/examples/expected/descriptor.json index 15c36045d..8c6ad344c 100644 --- a/spec/v1/examples/expected/descriptor.json +++ b/spec/v1/examples/expected/descriptor.json @@ -1 +1 @@ -{"classes":[{"features":[{"many":false,"map":false,"name":"dnsProvider","reference":false,"required":true,"types":["DnsProvider"]},{"many":false,"map":false,"name":"email","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"token","reference":false,"required":true,"types":["string"]}],"name":"AcmePolicy"},{"features":[{"many":false,"map":false,"name":"from","reference":false,"required":true,"types":["string"]}],"name":"AlertRules"},{"features":[{"many":false,"map":false,"name":"errorRate","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"interval","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"iterations","reference":false,"required":true,"types":["int"]},{"many":false,"map":false,"name":"latency","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"threshold","reference":false,"required":true,"types":["int"]}],"name":"AnalysisPolicy"},{"features":[{"many":false,"map":false,"name":"reason","reference":false,"required":true,"types":["string"]},{"many":true,"map":false,"name":"rules","reference":false,"required":false,"types":["ApiRule"]}],"name":"ApiAccess"},{"features":[{"many":true,"map":false,"name":"holders","reference":true,"required":true,"types":["Application"]},{"many":true,"map":false,"name":"namespaceHolders","reference":true,"required":false,"types":["Application"]},{"many":true,"map":false,"name":"server","reference":false,"required":true,"types":["DestinationRange"]}],"name":"ApiAccessPolicy"},{"features":[{"many":false,"map":false,"name":"group","reference":false,"required":true,"types":["string"]},{"many":true,"map":false,"name":"objects","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"scope","reference":false,"required":false,"types":["ApiScope"]},{"many":true,"map":false,"name":"verbs","reference":false,"required":true,"types":["ApiVerb"]}],"name":"ApiRule"},{"features":[{"many":false,"map":false,"name":"port","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"service","reference":false,"required":true,"types":["string"]}],"name":"ApiServerMetrics"},{"features":[{"many":true,"map":false,"name":"alertRules","reference":false,"required":false,"types":["AlertRules"]},{"many":true,"map":false,"name":"assets","reference":false,"required":false,"types":["Asset","SeededAsset"]},{"many":false,"map":false,"name":"client","reference":false,"required":false,"types":["Client"]},{"many":false,"map":false,"name":"cutover","reference":false,"required":false,"types":["Cutover"]},{"many":true,"map":false,"name":"dashboards","reference":false,"required":false,"types":["Dashboard"]},{"many":true,"map":false,"name":"dependsOn","reference":false,"required":false,"types":["DependencyEdge"]},{"many":false,"map":false,"name":"description","reference":false,"required":false,"types":["string"]},{"many":true,"map":false,"name":"exposure","reference":false,"required":false,"types":["Exposure"]},{"many":false,"map":false,"name":"id","reference":false,"required":true,"types":["string"]},{"many":true,"map":false,"name":"l4Exposure","reference":false,"required":false,"types":["L4Exposure"]},{"many":false,"map":false,"name":"migration","reference":false,"required":false,"types":["ManagedMigration","NoMigration","SelfMigration"]},{"many":false,"map":false,"name":"observability","reference":false,"required":false,"types":["Observability"]},{"many":false,"map":false,"name":"placement","reference":false,"required":false,"types":["Placement"]},{"many":true,"map":false,"name":"processes","reference":false,"required":true,"types":["Process"]},{"many":true,"map":false,"name":"secrets","reference":false,"required":false,"types":["DatabaseGrant","KvGrant","TransitGrant"]},{"many":false,"map":false,"name":"startupBudget","reference":false,"required":false,"types":["string"]},{"many":true,"map":false,"name":"writablePaths","reference":false,"required":false,"types":["MemoryPath","string"]}],"name":"Application"},{"features":[{"many":false,"map":false,"name":"issuer","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"subject","reference":false,"required":true,"types":["string"]}],"name":"ArtifactSigner"},{"features":[{"many":false,"map":false,"name":"from","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"mountAt","reference":false,"required":true,"types":["string"]}],"name":"Asset"},{"features":[{"many":true,"map":false,"name":"crds","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"flux","reference":false,"required":true,"types":["FluxSource"]},{"many":false,"map":false,"name":"k3s","reference":false,"required":false,"types":["K3sInstall"]},{"many":false,"map":false,"name":"vault","reference":false,"required":true,"types":["VaultState"]}],"name":"Bootstrap"},{"features":[{"many":false,"map":false,"name":"across","reference":false,"required":false,"types":["Spread"]},{"many":false,"map":false,"name":"count","reference":false,"required":true,"types":["int"]},{"many":false,"map":false,"name":"reason","reference":false,"required":true,"types":["string"]}],"name":"Capacity"},{"features":[{"many":false,"map":false,"name":"backChannelLogoutPath","reference":false,"required":false,"types":["string"]},{"many":false,"map":false,"name":"credential","reference":false,"required":false,"types":["ClientCredential"]},{"many":false,"map":false,"name":"description","reference":false,"required":false,"types":["string"]},{"many":false,"map":false,"name":"exposure","reference":false,"required":false,"types":["string"]},{"many":false,"map":false,"name":"iconPath","reference":false,"required":false,"types":["string"]},{"many":false,"map":false,"name":"label","reference":false,"required":false,"types":["string"]},{"many":false,"map":false,"name":"launchPath","reference":false,"required":false,"types":["string"]},{"many":false,"map":false,"name":"protectedPath","reference":false,"required":false,"types":["string"]},{"many":true,"map":false,"name":"redirectPaths","reference":false,"required":false,"types":["string"]},{"many":true,"map":false,"name":"roles","reference":false,"required":false,"types":["string"]},{"many":false,"map":false,"name":"signIn","reference":false,"required":true,"types":["SignInMode"]}],"name":"Client"},{"features":[{"many":false,"map":false,"name":"port","reference":false,"required":true,"types":["int"]}],"name":"CoreDnsMetrics"},{"features":[{"many":false,"map":false,"name":"delivery","reference":false,"required":false,"types":["Delivery"]},{"many":false,"map":false,"name":"fileMode","reference":false,"required":false,"types":["string"]},{"many":false,"map":false,"name":"mountAt","reference":false,"required":false,"types":["string"]},{"many":false,"map":false,"name":"rotation","reference":false,"required":true,"types":["Rotation"]}],"name":"Credentials"},{"features":[{"many":false,"map":false,"name":"from","reference":false,"required":true,"types":["string"]}],"name":"Dashboard"},{"features":[{"many":false,"map":false,"name":"datasource","reference":false,"required":true,"types":["string"]},{"entry":"undefined","many":false,"map":true,"name":"instance","reference":false,"required":true,"types":["string"]}],"name":"DashboardPolicy"},{"features":[{"many":false,"map":false,"name":"delivery","reference":false,"required":true,"types":["Delivery"]},{"many":false,"map":false,"name":"engine","reference":false,"required":true,"types":["DatabaseEngine"]},{"many":false,"map":false,"name":"fileMode","reference":false,"required":false,"types":["string"]},{"many":false,"map":false,"name":"mountAt","reference":false,"required":false,"types":["string"]},{"many":false,"map":false,"name":"role","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"rotation","reference":false,"required":true,"types":["Rotation"]}],"name":"DatabaseGrant"},{"features":[{"many":false,"map":false,"name":"analysis","reference":false,"required":true,"types":["AnalysisPolicy"]},{"many":false,"map":false,"name":"gate","reference":true,"required":true,"types":["Application"]},{"many":true,"map":false,"name":"machinery","reference":true,"required":true,"types":["Application"]},{"many":false,"map":false,"name":"reporter","reference":true,"required":false,"types":["Application"]}],"name":"DeliveryPolicy"},{"features":[{"many":false,"map":false,"name":"application","reference":false,"required":true,"types":["string"]},{"many":true,"map":false,"name":"buckets","reference":false,"required":false,"types":["string"]},{"many":false,"map":false,"name":"credentials","reference":false,"required":false,"types":["Credentials"]},{"many":false,"map":false,"name":"required","reference":false,"required":false,"types":["boolean"]},{"many":false,"map":false,"name":"surface","reference":false,"required":true,"types":["string"]}],"name":"DependencyEdge"},{"features":[{"many":false,"map":false,"name":"cidr","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"port","reference":false,"required":true,"types":["int"]}],"name":"DestinationRange"},{"features":[{"many":false,"map":false,"name":"webhook","reference":false,"required":true,"types":["VaultValue"]}],"name":"DiscordReceiver"},{"features":[{"many":true,"map":false,"name":"media","reference":false,"required":true,"types":["Media"]}],"name":"DiskRequest"},{"features":[{"many":false,"map":false,"name":"irreplaceable","reference":false,"required":false,"types":["DurabilityPolicy"]},{"many":false,"map":false,"name":"reconstructible","reference":false,"required":false,"types":["DurabilityPolicy"]},{"many":false,"map":false,"name":"recoverable","reference":false,"required":false,"types":["DurabilityPolicy"]}],"name":"DurabilityPolicies"},{"features":[{"many":false,"map":false,"name":"offCluster","reference":false,"required":false,"types":["OffClusterCopy"]},{"many":false,"map":false,"name":"retain","reference":false,"required":false,"types":["int"]},{"many":false,"map":false,"name":"schedule","reference":false,"required":false,"types":["string"]}],"name":"DurabilityPolicy"},{"features":[{"many":false,"map":false,"name":"reason","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"to","reference":false,"required":true,"types":["EgressTarget"]}],"name":"Egress"},{"features":[{"many":false,"map":false,"name":"files","reference":false,"required":false,"types":["EnginePolicy"]},{"many":false,"map":false,"name":"mongo","reference":false,"required":false,"types":["EnginePolicy"]},{"many":false,"map":false,"name":"postgres","reference":false,"required":false,"types":["EnginePolicy"]},{"many":false,"map":false,"name":"rabbitmq","reference":false,"required":false,"types":["EnginePolicy"]},{"many":false,"map":false,"name":"s3","reference":false,"required":false,"types":["EnginePolicy"]},{"many":false,"map":false,"name":"valkey","reference":false,"required":false,"types":["EnginePolicy"]}],"name":"EnginePolicies"},{"features":[{"many":false,"map":false,"name":"backup","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"catalog","reference":false,"required":false,"types":["string"]},{"many":false,"map":false,"name":"catalogSurface","reference":false,"required":false,"types":["string"]},{"many":false,"map":false,"name":"surface","reference":false,"required":false,"types":["string"]}],"name":"EnginePolicy"},{"features":[{"many":true,"map":false,"name":"entries","reference":false,"required":false,"types":["EnvVariable"]}],"name":"EnvFile"},{"features":[{"many":false,"map":false,"name":"text","reference":false,"required":true,"types":["string"]}],"name":"EnvLiteral"},{"features":[{"many":false,"map":false,"name":"name","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"value","reference":false,"required":true,"types":["EnvLiteral","Placeholder"]}],"name":"EnvVariable"},{"features":[{"many":false,"map":false,"name":"size","reference":false,"required":true,"types":["string"]}],"name":"EphemeralPolicy"},{"features":[{"many":false,"map":false,"name":"audience","reference":false,"required":true,"types":["Audience"]},{"many":false,"map":false,"name":"contentPolicy","reference":false,"required":false,"types":["ContentPolicy"]},{"many":false,"map":false,"name":"host","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"name","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"proxied","reference":false,"required":false,"types":["boolean"]},{"many":true,"map":false,"name":"routes","reference":false,"required":true,"types":["Route"]}],"name":"Exposure"},{"features":[{"many":false,"map":false,"name":"path","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"port","reference":false,"required":true,"types":["int"]}],"name":"FluxMetrics"},{"features":[{"many":false,"map":false,"name":"artifacts","reference":false,"required":true,"types":["RenderedArtifacts"]},{"many":false,"map":false,"name":"metrics","reference":false,"required":false,"types":["FluxMetrics"]},{"many":false,"map":false,"name":"sourceRef","reference":false,"required":true,"types":["string"]}],"name":"FluxSource"},{"features":[{"many":false,"map":false,"name":"path","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"provider","reference":true,"required":true,"types":["Application"]},{"many":true,"map":false,"name":"responseHeaders","reference":false,"required":false,"types":["string"]},{"many":false,"map":false,"name":"surface","reference":false,"required":true,"types":["string"]}],"name":"ForwardAuth"},{"features":[{"many":false,"map":false,"name":"adopt","reference":false,"required":false,"types":["string"]},{"many":false,"map":false,"name":"bytes","reference":false,"required":false,"types":["int"]},{"many":false,"map":false,"name":"encoding","reference":false,"required":false,"types":["KeyEncoding"]}],"name":"Generate"},{"features":[{"many":false,"map":false,"name":"generate","reference":false,"required":true,"types":["Generate"]},{"many":false,"map":false,"name":"name","reference":false,"required":true,"types":["string"]}],"name":"GeneratedKey"},{"features":[{"many":false,"map":false,"name":"class","reference":false,"required":false,"types":["string"]},{"many":false,"map":false,"name":"memory","reference":false,"required":false,"types":["string"]},{"many":true,"map":false,"name":"model","reference":false,"required":false,"types":["string"]},{"many":true,"map":false,"name":"vendor","reference":false,"required":false,"types":["string"]}],"name":"GpuRequest"},{"features":[{"many":false,"map":false,"name":"resource","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"runtimeClass","reference":false,"required":true,"types":["string"]}],"name":"GpuVendorPolicy"},{"features":[{"many":false,"map":false,"name":"path","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"port","reference":false,"required":true,"types":["int"]}],"name":"HttpProbe"},{"features":[{"many":false,"map":false,"name":"clusterRole","reference":false,"required":true,"types":["HumanGrantRole"]},{"many":false,"map":false,"name":"group","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"reason","reference":false,"required":true,"types":["string"]}],"name":"HumanGrant"},{"features":[{"many":false,"map":false,"name":"metrics","reference":false,"required":true,"types":["K3sMetrics"]}],"name":"K3sInstall"},{"features":[{"many":false,"map":false,"name":"apiServer","reference":false,"required":false,"types":["ApiServerMetrics"]},{"many":false,"map":false,"name":"coreDns","reference":false,"required":false,"types":["CoreDnsMetrics"]},{"many":false,"map":false,"name":"kubelet","reference":false,"required":false,"types":["KubeletMetrics"]}],"name":"K3sMetrics"},{"features":[{"many":false,"map":false,"name":"port","reference":false,"required":true,"types":["int"]}],"name":"KubeletMetrics"},{"features":[{"many":false,"map":false,"name":"access","reference":false,"required":true,"types":["AccessTier"]},{"many":false,"map":false,"name":"delivery","reference":false,"required":true,"types":["Delivery"]},{"many":false,"map":false,"name":"fileMode","reference":false,"required":false,"types":["string"]},{"many":true,"map":false,"name":"keys","reference":false,"required":true,"types":["GeneratedKey","OptionalKey","string"]},{"many":false,"map":false,"name":"mountAt","reference":false,"required":false,"types":["string"]},{"many":false,"map":false,"name":"path","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"rotation","reference":false,"required":true,"types":["Rotation"]}],"name":"KvGrant"},{"features":[{"many":false,"map":false,"name":"address","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"port","reference":false,"required":true,"types":["int"]},{"many":false,"map":false,"name":"process","reference":true,"required":true,"types":["Process"]},{"many":false,"map":false,"name":"protocol","reference":false,"required":true,"types":["L4Protocol"]},{"many":false,"map":false,"name":"surface","reference":true,"required":true,"types":["Surface"]}],"name":"L4Exposure"},{"features":[{"many":false,"map":false,"name":"gid","reference":false,"required":true,"types":["int"]},{"entry":"LibraryGrant","many":false,"map":true,"name":"grants","reference":false,"required":true,"types":["LibraryAccess"]},{"many":false,"map":false,"name":"node","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"path","reference":false,"required":true,"types":["string"]}],"name":"LibraryDeclaration"},{"features":[{"many":false,"map":false,"name":"access","reference":false,"required":true,"types":["LibraryAccess"]},{"many":false,"map":false,"name":"library","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"mountAt","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"subPath","reference":false,"required":false,"types":["string"]}],"name":"LibraryMount"},{"features":[{"many":false,"map":false,"name":"changelog","reference":false,"required":true,"types":["string"]}],"name":"ManagedMigration"},{"features":[{"many":false,"map":false,"name":"memory","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"path","reference":false,"required":true,"types":["string"]}],"name":"MemoryPath"},{"features":[{"many":true,"map":false,"name":"enrolled","reference":false,"required":true,"types":["string"]}],"name":"Mesh"},{"features":[{"many":false,"map":false,"name":"cpu","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"deadline","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"memory","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"runner","reference":false,"required":true,"types":["string"]}],"name":"MigrationPolicy"},{"features":[{"many":false,"map":false,"name":"interval","reference":false,"required":true,"types":["string"]},{"entry":"undefined","many":false,"map":true,"name":"labels","reference":false,"required":false,"types":["string"]},{"many":false,"map":false,"name":"timeout","reference":false,"required":true,"types":["string"]}],"name":"MonitorCadence"},{"features":[{"many":false,"map":false,"name":"alertmanager","reference":true,"required":true,"types":["Application"]},{"many":false,"map":false,"name":"grafana","reference":true,"required":true,"types":["Application"]},{"many":false,"map":false,"name":"prometheus","reference":true,"required":true,"types":["Application"]},{"many":true,"map":false,"name":"receivers","reference":false,"required":true,"types":["Receiver"]},{"many":true,"map":false,"name":"routes","reference":false,"required":true,"types":["SeverityRoute"]},{"many":false,"map":false,"name":"stores","reference":false,"required":false,"types":["MonitoringStores"]}],"name":"Monitoring"},{"features":[{"many":false,"map":false,"name":"loki","reference":true,"required":false,"types":["Application"]},{"many":false,"map":false,"name":"pyroscope","reference":true,"required":false,"types":["Application"]},{"many":false,"map":false,"name":"tempo","reference":true,"required":false,"types":["Application"]}],"name":"MonitoringStores"},{"features":[],"name":"NoMigration","scalar":"none"},{"features":[],"name":"NoProbes","scalar":"none"},{"features":[{"many":false,"map":false,"name":"kind","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"node","reference":false,"required":true,"types":["string"]}],"name":"NodeAddress"},{"features":[{"many":false,"map":false,"name":"cpu","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"image","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"memory","reference":false,"required":true,"types":["string"]}],"name":"NodeAgentPolicy"},{"features":[{"many":false,"map":false,"name":"gpu-device","reference":false,"required":false,"types":["NodeAgentPolicy"]},{"many":false,"map":false,"name":"gpu-metrics","reference":false,"required":false,"types":["NodeAgentPolicy"]},{"many":false,"map":false,"name":"l4-speaker","reference":false,"required":false,"types":["SpeakerPolicy"]},{"many":false,"map":false,"name":"node-metrics","reference":false,"required":false,"types":["NodeAgentPolicy"]}],"name":"NodeAgents"},{"features":[{"many":false,"map":false,"name":"scrape","reference":false,"required":true,"types":["Scrape"]}],"name":"Observability"},{"features":[{"many":false,"map":false,"name":"credential","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"destination","reference":false,"required":true,"types":["string"]},{"many":true,"map":false,"name":"egress","reference":false,"required":true,"types":["DestinationRange"]},{"many":false,"map":false,"name":"recipient","reference":false,"required":true,"types":["string"]}],"name":"OffClusterCopy"},{"features":[{"many":false,"map":false,"name":"name","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"optional","reference":false,"required":true,"types":["boolean"]}],"name":"OptionalKey"},{"features":[{"many":false,"map":false,"name":"reason","reference":false,"required":true,"types":["string"]}],"name":"Parked"},{"features":[{"many":false,"map":false,"name":"kind","reference":false,"required":true,"types":["PlaceholderKind"]},{"many":false,"map":false,"name":"source","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"suffix","reference":false,"required":false,"types":["string"]}],"name":"Placeholder"},{"features":[{"many":true,"map":false,"name":"arch","reference":false,"required":false,"types":["Arch"]},{"many":true,"map":false,"name":"capabilities","reference":false,"required":false,"types":["string"]},{"many":false,"map":false,"name":"cpu","reference":false,"required":false,"types":["string"]},{"many":false,"map":false,"name":"disk","reference":false,"required":false,"types":["DiskRequest"]},{"many":false,"map":false,"name":"gpu","reference":false,"required":false,"types":["GpuRequest"]},{"many":false,"map":false,"name":"memory","reference":false,"required":false,"types":["string"]},{"many":false,"map":false,"name":"site","reference":false,"required":false,"types":["string"]}],"name":"Placement"},{"features":[{"many":false,"map":false,"name":"acme","reference":false,"required":false,"types":["AcmePolicy"]},{"many":false,"map":false,"name":"apiAccess","reference":false,"required":false,"types":["ApiAccessPolicy"]},{"many":false,"map":false,"name":"apiVersion","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"bootstrap","reference":false,"required":true,"types":["Bootstrap"]},{"many":false,"map":false,"name":"delivery","reference":false,"required":false,"types":["DeliveryPolicy"]},{"many":false,"map":false,"name":"durability","reference":false,"required":true,"types":["DurabilityPolicies"]},{"many":false,"map":false,"name":"engines","reference":false,"required":true,"types":["EnginePolicies"]},{"many":false,"map":false,"name":"ephemeral","reference":false,"required":true,"types":["EphemeralPolicy"]},{"entry":"GpuVendor","many":false,"map":true,"name":"gpus","reference":false,"required":false,"types":["GpuVendorPolicy"]},{"many":false,"map":false,"name":"hardening","reference":false,"required":true,"types":["HardeningClass"]},{"many":true,"map":false,"name":"humanGrants","reference":false,"required":false,"types":["HumanGrant"]},{"many":false,"map":false,"name":"kind","reference":false,"required":true,"types":["string"]},{"entry":"L4Address","many":false,"map":true,"name":"l4Addresses","reference":false,"required":false,"types":["NodeAddress","PoolAddress"]},{"entry":"Library","many":false,"map":true,"name":"libraries","reference":false,"required":false,"types":["LibraryDeclaration"]},{"many":false,"map":false,"name":"mesh","reference":false,"required":false,"types":["Mesh"]},{"many":false,"map":false,"name":"metadata","reference":false,"required":true,"types":["PlatformMetadata"]},{"many":false,"map":false,"name":"migration","reference":false,"required":false,"types":["MigrationPolicy"]},{"many":false,"map":false,"name":"monitoring","reference":false,"required":false,"types":["Monitoring"]},{"many":false,"map":false,"name":"monitors","reference":false,"required":true,"types":["MonitorCadence"]},{"many":false,"map":false,"name":"nodeAgents","reference":false,"required":false,"types":["NodeAgents"]},{"many":false,"map":false,"name":"owner","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"policyJob","reference":false,"required":false,"types":["VaultPolicyJob"]},{"many":false,"map":false,"name":"priority","reference":false,"required":false,"types":["PriorityPolicy"]},{"many":false,"map":false,"name":"probes","reference":false,"required":true,"types":["ProbeCadence"]},{"many":true,"map":false,"name":"providers","reference":false,"required":false,"types":["Provider"]},{"many":false,"map":false,"name":"schemaVersion","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"secretStore","reference":true,"required":false,"types":["Application"]},{"many":false,"map":false,"name":"seeder","reference":false,"required":false,"types":["Seeder"]},{"many":false,"map":false,"name":"signIn","reference":false,"required":false,"types":["SignIn"]},{"many":false,"map":false,"name":"substrate","reference":false,"required":true,"types":["Substrate"]},{"many":false,"map":false,"name":"telemetry","reference":false,"required":false,"types":["TelemetryPolicy"]},{"many":true,"map":false,"name":"tiers","reference":false,"required":true,"types":["Tier"]},{"many":false,"map":false,"name":"vpn","reference":false,"required":false,"types":["VpnPolicy"]}],"name":"Platform"},{"features":[{"many":false,"map":false,"name":"cluster","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"nodeContract","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"project","reference":false,"required":true,"types":["string"]}],"name":"PlatformMetadata"},{"features":[{"many":false,"map":false,"name":"kind","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"range","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"site","reference":false,"required":true,"types":["string"]}],"name":"PoolAddress"},{"features":[{"many":false,"map":false,"name":"name","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"value","reference":false,"required":true,"types":["int"]}],"name":"PriorityClass"},{"features":[{"many":false,"map":false,"name":"critical","reference":false,"required":true,"types":["PriorityClass"]},{"many":true,"map":false,"name":"criticalApplications","reference":true,"required":false,"types":["Application"]},{"many":false,"map":false,"name":"data","reference":false,"required":true,"types":["PriorityClass"]}],"name":"PriorityPolicy"},{"features":[{"many":false,"map":false,"name":"failures","reference":false,"required":true,"types":["int"]},{"many":false,"map":false,"name":"period","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"timeout","reference":false,"required":true,"types":["string"]}],"name":"ProbeCadence"},{"features":[{"many":false,"map":false,"name":"liveness","reference":false,"required":false,"types":["HttpProbe","TcpProbe"]},{"many":false,"map":false,"name":"readiness","reference":false,"required":false,"types":["HttpProbe","TcpProbe"]}],"name":"Probes"},{"features":[{"many":false,"map":false,"name":"api","reference":false,"required":false,"types":["ApiAccess"]},{"many":true,"map":false,"name":"args","reference":false,"required":false,"types":["string"]},{"many":true,"map":false,"name":"assets","reference":false,"required":false,"types":["Asset","SeededAsset"]},{"many":true,"map":false,"name":"command","reference":false,"required":false,"types":["string"]},{"many":false,"map":false,"name":"cutover","reference":false,"required":false,"types":["Cutover"]},{"many":true,"map":false,"name":"dependsOn","reference":false,"required":false,"types":["DependencyEdge"]},{"many":false,"map":false,"name":"description","reference":false,"required":false,"types":["string"]},{"many":false,"map":false,"name":"egress","reference":false,"required":false,"types":["Egress"]},{"many":false,"map":false,"name":"engine","reference":false,"required":false,"types":["Engine"]},{"many":false,"map":false,"name":"image","reference":false,"required":true,"types":["string"]},{"many":true,"map":false,"name":"libraries","reference":false,"required":false,"types":["LibraryMount"]},{"many":false,"map":false,"name":"lifecycle","reference":false,"required":true,"types":["Lifecycle"]},{"many":false,"map":false,"name":"name","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"parked","reference":false,"required":false,"types":["Parked"]},{"many":false,"map":false,"name":"placement","reference":false,"required":false,"types":["Placement"]},{"many":false,"map":false,"name":"probes","reference":false,"required":false,"types":["NoProbes","Probes"]},{"entry":"Surface","many":false,"map":true,"name":"provides","reference":false,"required":false,"types":["int"]},{"many":false,"map":false,"name":"replicas","reference":false,"required":false,"types":["Capacity"]},{"many":false,"map":false,"name":"runtime","reference":false,"required":true,"types":["Runtime"]},{"many":false,"map":false,"name":"schedule","reference":false,"required":false,"types":["string"]},{"many":true,"map":false,"name":"secrets","reference":false,"required":false,"types":["DatabaseGrant","KvGrant","TransitGrant"]},{"many":true,"map":false,"name":"sidecars","reference":false,"required":false,"types":["Sidecar"]},{"many":true,"map":false,"name":"spawns","reference":false,"required":false,"types":["SpawnTemplate"]},{"many":false,"map":false,"name":"startupBudget","reference":false,"required":false,"types":["string"]},{"many":true,"map":false,"name":"volumes","reference":false,"required":false,"types":["Volume"]},{"many":false,"map":false,"name":"vpn","reference":false,"required":false,"types":["VpnAttachment"]},{"many":true,"map":false,"name":"writablePaths","reference":false,"required":false,"types":["MemoryPath","string"]}],"name":"Process"},{"features":[{"many":false,"map":false,"name":"apiVersion","reference":false,"required":true,"types":["string"]},{"many":true,"map":false,"name":"applications","reference":false,"required":true,"types":["Application"]},{"many":true,"map":false,"name":"assets","reference":false,"required":false,"types":["Asset","SeededAsset"]},{"many":false,"map":false,"name":"cutover","reference":false,"required":false,"types":["Cutover"]},{"many":true,"map":false,"name":"dependsOn","reference":false,"required":false,"types":["DependencyEdge"]},{"many":false,"map":false,"name":"kind","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"owner","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"placement","reference":false,"required":false,"types":["Placement"]},{"many":false,"map":false,"name":"project","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"schemaVersion","reference":false,"required":true,"types":["string"]},{"many":true,"map":false,"name":"secrets","reference":false,"required":false,"types":["DatabaseGrant","KvGrant","TransitGrant"]},{"many":false,"map":false,"name":"startupBudget","reference":false,"required":false,"types":["string"]},{"many":true,"map":false,"name":"writablePaths","reference":false,"required":false,"types":["MemoryPath","string"]}],"name":"Project"},{"features":[{"many":false,"map":false,"name":"address","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"name","reference":false,"required":true,"types":["string"]},{"entry":"ProviderSurface","many":false,"map":true,"name":"surfaces","reference":false,"required":true,"types":["int"]}],"name":"Provider"},{"features":[{"many":false,"map":false,"name":"per","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"requests","reference":false,"required":true,"types":["int"]}],"name":"RateLimit"},{"features":[{"many":false,"map":false,"name":"discord","reference":false,"required":true,"types":["DiscordReceiver"]},{"many":false,"map":false,"name":"name","reference":false,"required":true,"types":["string"]}],"name":"Receiver"},{"features":[{"many":false,"map":false,"name":"pullSecret","reference":false,"required":false,"types":["string"]},{"many":false,"map":false,"name":"repository","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"signer","reference":false,"required":true,"types":["ArtifactSigner"]}],"name":"RenderedArtifacts"},{"features":[{"many":false,"map":false,"name":"maxAge","reference":false,"required":false,"types":["string"]},{"many":false,"map":false,"name":"tolerates","reference":false,"required":true,"types":["Tolerance"]}],"name":"Rotation"},{"features":[{"many":false,"map":false,"name":"audience","reference":false,"required":false,"types":["Audience"]},{"many":false,"map":false,"name":"match","reference":false,"required":true,"types":["Match"]},{"many":false,"map":false,"name":"path","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"process","reference":true,"required":true,"types":["Process"]},{"many":false,"map":false,"name":"rateLimit","reference":false,"required":false,"types":["RateLimit"]},{"many":false,"map":false,"name":"redirectTo","reference":false,"required":false,"types":["string"]},{"many":false,"map":false,"name":"surface","reference":true,"required":true,"types":["Surface"]}],"name":"Route"},{"features":[{"many":false,"map":false,"name":"honorLabels","reference":false,"required":false,"types":["boolean"]},{"many":false,"map":false,"name":"path","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"process","reference":true,"required":true,"types":["Process"]},{"many":false,"map":false,"name":"surface","reference":true,"required":true,"types":["Surface"]}],"name":"Scrape"},{"features":[{"many":false,"map":false,"name":"from","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"seedAt","reference":false,"required":true,"types":["string"]}],"name":"SeededAsset"},{"features":[{"many":false,"map":false,"name":"cpu","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"image","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"memory","reference":false,"required":true,"types":["string"]}],"name":"Seeder"},{"features":[],"name":"SelfMigration","scalar":"self"},{"features":[{"many":false,"map":false,"name":"receiver","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"repeat","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"severity","reference":false,"required":true,"types":["string"]}],"name":"SeverityRoute"},{"features":[{"many":true,"map":false,"name":"args","reference":false,"required":false,"types":["string"]},{"many":true,"map":false,"name":"assets","reference":false,"required":false,"types":["SidecarAsset"]},{"many":true,"map":false,"name":"command","reference":false,"required":false,"types":["string"]},{"many":false,"map":false,"name":"cpu","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"image","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"init","reference":false,"required":false,"types":["boolean"]},{"many":false,"map":false,"name":"memory","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"name","reference":false,"required":true,"types":["string"]},{"many":true,"map":false,"name":"volumes","reference":false,"required":false,"types":["SidecarMount"]}],"name":"Sidecar"},{"features":[{"many":false,"map":false,"name":"from","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"mountAt","reference":false,"required":true,"types":["string"]}],"name":"SidecarAsset"},{"features":[{"many":false,"map":false,"name":"claim","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"mountAt","reference":false,"required":true,"types":["string"]}],"name":"SidecarMount"},{"features":[{"many":false,"map":false,"name":"contract","reference":false,"required":false,"types":["string"]},{"many":false,"map":false,"name":"issuer","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"provider","reference":true,"required":true,"types":["Application"]}],"name":"SignIn"},{"features":[{"many":false,"map":false,"name":"cpu","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"memory","reference":false,"required":true,"types":["string"]}],"name":"SpawnPlacement"},{"features":[{"many":true,"map":false,"name":"dependsOn","reference":false,"required":false,"types":["DependencyEdge"]},{"many":false,"map":false,"name":"egress","reference":false,"required":false,"types":["Egress"]},{"many":false,"map":false,"name":"image","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"name","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"placement","reference":false,"required":true,"types":["SpawnPlacement"]},{"entry":"Surface","many":false,"map":true,"name":"provides","reference":false,"required":false,"types":["int"]}],"name":"SpawnTemplate"},{"features":[{"many":false,"map":false,"name":"controller","reference":false,"required":true,"types":["NodeAgentPolicy"]},{"many":false,"map":false,"name":"cpu","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"image","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"memory","reference":false,"required":true,"types":["string"]}],"name":"SpeakerPolicy"},{"features":[{"many":false,"map":false,"name":"clusterDns","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"cni","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"datastore","reference":false,"required":true,"types":["Datastore"]},{"many":false,"map":false,"name":"imagePull","reference":false,"required":false,"types":["string"]},{"many":false,"map":false,"name":"kubernetesVersion","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"networkPolicyController","reference":false,"required":true,"types":["PolicyController"]},{"many":false,"map":false,"name":"secretsEncryption","reference":false,"required":true,"types":["boolean"]},{"many":false,"map":false,"name":"serverCount","reference":false,"required":true,"types":["int"]},{"many":true,"map":false,"name":"upstreamNameservers","reference":false,"required":false,"types":["string"]}],"name":"Substrate"},{"features":[{"many":false,"map":false,"name":"tcp","reference":false,"required":true,"types":["int"]}],"name":"TcpProbe"},{"features":[{"many":false,"map":false,"name":"collector","reference":true,"required":true,"types":["Application"]},{"many":false,"map":false,"name":"dashboards","reference":false,"required":false,"types":["DashboardPolicy"]},{"many":false,"map":false,"name":"metrics","reference":true,"required":true,"types":["Application"]}],"name":"TelemetryPolicy"},{"features":[{"many":false,"map":false,"name":"address","reference":false,"required":false,"types":["string"]},{"many":true,"map":false,"name":"audiences","reference":false,"required":true,"types":["Audience"]},{"many":false,"map":false,"name":"certificateFrom","reference":false,"required":false,"types":["string"]},{"many":false,"map":false,"name":"certificates","reference":false,"required":true,"types":["CertificateSource"]},{"many":false,"map":false,"name":"dns","reference":false,"required":false,"types":["TierDns"]},{"many":false,"map":false,"name":"forwardAuth","reference":false,"required":false,"types":["ForwardAuth"]},{"many":false,"map":false,"name":"ingressClass","reference":false,"required":false,"types":["string"]},{"many":false,"map":false,"name":"listener","reference":false,"required":true,"types":["Listener"]},{"many":false,"map":false,"name":"name","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"proxiedBy","reference":false,"required":false,"types":["ProxiedBy"]},{"many":false,"map":false,"name":"traefik","reference":true,"required":true,"types":["Application"]}],"name":"Tier"},{"features":[{"many":false,"map":false,"name":"direct","reference":false,"required":false,"types":["string"]},{"many":false,"map":false,"name":"proxied","reference":false,"required":true,"types":["boolean"]},{"many":false,"map":false,"name":"target","reference":false,"required":true,"types":["string"]}],"name":"TierDns"},{"features":[{"many":false,"map":false,"name":"delivery","reference":false,"required":true,"types":["Delivery"]},{"many":false,"map":false,"name":"engine","reference":false,"required":true,"types":["TransitEngine"]},{"many":false,"map":false,"name":"fileMode","reference":false,"required":false,"types":["string"]},{"many":false,"map":false,"name":"key","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"mountAt","reference":false,"required":false,"types":["string"]},{"many":true,"map":false,"name":"operations","reference":false,"required":true,"types":["TransitOp"]},{"many":false,"map":false,"name":"rotation","reference":false,"required":true,"types":["Rotation"]}],"name":"TransitGrant"},{"features":[{"many":false,"map":false,"name":"cpu","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"deadline","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"image","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"memory","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"role","reference":false,"required":true,"types":["string"]}],"name":"VaultPolicyJob"},{"features":[{"many":false,"map":false,"name":"unsealed","reference":false,"required":true,"types":["boolean"]}],"name":"VaultState"},{"features":[{"many":false,"map":false,"name":"key","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"path","reference":false,"required":true,"types":["string"]}],"name":"VaultValue"},{"features":[{"many":false,"map":false,"name":"claim","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"durability","reference":false,"required":true,"types":["DurabilityClass"]},{"many":false,"map":false,"name":"mountAt","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"size","reference":false,"required":true,"types":["string"]}],"name":"Volume"},{"features":[{"many":false,"map":false,"name":"forwardedPort","reference":false,"required":true,"types":["boolean"]}],"name":"VpnAttachment"},{"features":[{"many":false,"map":false,"name":"cpu","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"credential","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"image","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"memory","reference":false,"required":true,"types":["string"]},{"entry":"VpnSetting","many":false,"map":true,"name":"settings","reference":false,"required":false,"types":["string"]}],"name":"VpnPolicy"}],"vocabularies":[{"literals":["read","self-renew","self-roll","custody"],"name":"AccessTier"},{"literals":["namespace","cluster"],"name":"ApiScope"},{"literals":["get","list","watch","create","update","patch","delete","deletecollection"],"name":"ApiVerb"},{"literals":["amd64","arm64"],"name":"Arch"},{"literals":["anonymous","authenticated","internal","lan"],"name":"Audience"},{"literals":["acme","none"],"name":"CertificateSource"},{"literals":["secret","pkce"],"name":"ClientCredential"},{"literals":["strict","admin","workflow"],"name":"ContentPolicy"},{"literals":["continuous","interrupted"],"name":"Cutover"},{"literals":["database"],"name":"DatabaseEngine"},{"literals":["sqlite","etcd"],"name":"Datastore"},{"literals":["env","file","self"],"name":"Delivery"},{"literals":["cloudflare"],"name":"DnsProvider"},{"literals":["reconstructible","recoverable","irreplaceable"],"name":"DurabilityClass"},{"literals":["internet"],"name":"EgressTarget"},{"literals":["postgres","rabbitmq","valkey","mongo","files","s3"],"name":"Engine"},{"literals":["restricted"],"name":"HardeningClass"},{"literals":["cluster-admin","admin","edit","view"],"name":"HumanGrantRole"},{"literals":["hex","base64","base64url"],"name":"KeyEncoding"},{"literals":["tcp","udp"],"name":"L4Protocol"},{"literals":["read","write"],"name":"LibraryAccess"},{"literals":["application","job","prepare","scheduled"],"name":"Lifecycle"},{"literals":["tls","plain"],"name":"Listener"},{"literals":["prefix","exact"],"name":"Match"},{"literals":["nvme","ssd","hdd"],"name":"Media"},{"literals":["secret","dependency","exposure","identity","client"],"name":"PlaceholderKind"},{"literals":["none","embedded","cni"],"name":"PolicyController"},{"literals":["cloudflare"],"name":"ProxiedBy"},{"literals":["jvm","python","node","static","none"],"name":"Runtime"},{"literals":["native","gated","token"],"name":"SignInMode"},{"literals":["node"],"name":"Spread"},{"literals":["restart","reload"],"name":"Tolerance"},{"literals":["transit"],"name":"TransitEngine"},{"literals":["sign","verify","encrypt","decrypt","rotate","read"],"name":"TransitOp"}]} \ No newline at end of file +{"classes":[{"features":[{"many":false,"map":false,"name":"dnsProvider","reference":false,"required":true,"types":["DnsProvider"]},{"many":false,"map":false,"name":"email","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"token","reference":false,"required":true,"types":["string"]}],"name":"AcmePolicy"},{"features":[{"many":false,"map":false,"name":"from","reference":false,"required":true,"types":["string"]}],"name":"AlertRules"},{"features":[{"many":false,"map":false,"name":"errorRate","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"interval","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"iterations","reference":false,"required":true,"types":["int"]},{"many":false,"map":false,"name":"latency","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"threshold","reference":false,"required":true,"types":["int"]}],"name":"AnalysisPolicy"},{"features":[{"many":false,"map":false,"name":"reason","reference":false,"required":true,"types":["string"]},{"many":true,"map":false,"name":"rules","reference":false,"required":false,"types":["ApiRule"]}],"name":"ApiAccess"},{"features":[{"many":true,"map":false,"name":"holders","reference":true,"required":true,"types":["Application"]},{"many":true,"map":false,"name":"namespaceHolders","reference":true,"required":false,"types":["Application"]},{"many":true,"map":false,"name":"server","reference":false,"required":true,"types":["DestinationRange"]}],"name":"ApiAccessPolicy"},{"features":[{"many":false,"map":false,"name":"group","reference":false,"required":true,"types":["string"]},{"many":true,"map":false,"name":"objects","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"scope","reference":false,"required":false,"types":["ApiScope"]},{"many":true,"map":false,"name":"verbs","reference":false,"required":true,"types":["ApiVerb"]}],"name":"ApiRule"},{"features":[{"many":false,"map":false,"name":"port","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"service","reference":false,"required":true,"types":["string"]}],"name":"ApiServerMetrics"},{"features":[{"many":true,"map":false,"name":"alertRules","reference":false,"required":false,"types":["AlertRules"]},{"many":true,"map":false,"name":"assets","reference":false,"required":false,"types":["Asset","SeededAsset"]},{"many":false,"map":false,"name":"client","reference":false,"required":false,"types":["Client"]},{"many":false,"map":false,"name":"cutover","reference":false,"required":false,"types":["Cutover"]},{"many":true,"map":false,"name":"dashboards","reference":false,"required":false,"types":["Dashboard"]},{"many":true,"map":false,"name":"dependsOn","reference":false,"required":false,"types":["DependencyEdge"]},{"many":false,"map":false,"name":"description","reference":false,"required":false,"types":["string"]},{"many":true,"map":false,"name":"exposure","reference":false,"required":false,"types":["Exposure"]},{"many":false,"map":false,"name":"id","reference":false,"required":true,"types":["string"]},{"many":true,"map":false,"name":"l4Exposure","reference":false,"required":false,"types":["L4Exposure"]},{"many":false,"map":false,"name":"migration","reference":false,"required":false,"types":["ManagedMigration","NoMigration","SelfMigration"]},{"many":false,"map":false,"name":"observability","reference":false,"required":false,"types":["Observability"]},{"many":false,"map":false,"name":"placement","reference":false,"required":false,"types":["Placement"]},{"many":true,"map":false,"name":"processes","reference":false,"required":true,"types":["Process"]},{"many":true,"map":false,"name":"secrets","reference":false,"required":false,"types":["DatabaseGrant","KvGrant","TransitGrant"]},{"many":false,"map":false,"name":"startupBudget","reference":false,"required":false,"types":["string"]},{"many":true,"map":false,"name":"writablePaths","reference":false,"required":false,"types":["MemoryPath","string"]}],"name":"Application"},{"features":[{"many":false,"map":false,"name":"issuer","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"subject","reference":false,"required":true,"types":["string"]}],"name":"ArtifactSigner"},{"features":[{"many":false,"map":false,"name":"from","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"mountAt","reference":false,"required":true,"types":["string"]}],"name":"Asset"},{"features":[{"many":true,"map":false,"name":"crds","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"flux","reference":false,"required":true,"types":["FluxSource"]},{"many":false,"map":false,"name":"k3s","reference":false,"required":false,"types":["K3sInstall"]},{"many":false,"map":false,"name":"vault","reference":false,"required":true,"types":["VaultState"]}],"name":"Bootstrap"},{"features":[{"many":false,"map":false,"name":"across","reference":false,"required":false,"types":["Spread"]},{"many":false,"map":false,"name":"count","reference":false,"required":true,"types":["int"]},{"many":false,"map":false,"name":"reason","reference":false,"required":true,"types":["string"]}],"name":"Capacity"},{"features":[{"many":false,"map":false,"name":"backChannelLogoutPath","reference":false,"required":false,"types":["string"]},{"many":false,"map":false,"name":"credential","reference":false,"required":false,"types":["ClientCredential"]},{"many":false,"map":false,"name":"description","reference":false,"required":false,"types":["string"]},{"many":false,"map":false,"name":"exposure","reference":false,"required":false,"types":["string"]},{"many":false,"map":false,"name":"iconPath","reference":false,"required":false,"types":["string"]},{"many":false,"map":false,"name":"label","reference":false,"required":false,"types":["string"]},{"many":false,"map":false,"name":"launchPath","reference":false,"required":false,"types":["string"]},{"many":false,"map":false,"name":"protectedPath","reference":false,"required":false,"types":["string"]},{"many":true,"map":false,"name":"redirectPaths","reference":false,"required":false,"types":["string"]},{"many":true,"map":false,"name":"roles","reference":false,"required":false,"types":["string"]},{"many":false,"map":false,"name":"signIn","reference":false,"required":true,"types":["SignInMode"]}],"name":"Client"},{"features":[{"many":false,"map":false,"name":"port","reference":false,"required":true,"types":["int"]}],"name":"CoreDnsMetrics"},{"features":[{"many":false,"map":false,"name":"delivery","reference":false,"required":false,"types":["Delivery"]},{"many":false,"map":false,"name":"fileMode","reference":false,"required":false,"types":["string"]},{"many":false,"map":false,"name":"mountAt","reference":false,"required":false,"types":["string"]},{"many":false,"map":false,"name":"rotation","reference":false,"required":true,"types":["Rotation"]}],"name":"Credentials"},{"features":[{"many":true,"map":false,"name":"cors","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"name","reference":false,"required":true,"types":["string"]}],"name":"CrossOriginBucket"},{"features":[{"many":false,"map":false,"name":"from","reference":false,"required":true,"types":["string"]}],"name":"Dashboard"},{"features":[{"many":false,"map":false,"name":"datasource","reference":false,"required":true,"types":["string"]},{"entry":"undefined","many":false,"map":true,"name":"instance","reference":false,"required":true,"types":["string"]}],"name":"DashboardPolicy"},{"features":[{"many":false,"map":false,"name":"delivery","reference":false,"required":true,"types":["Delivery"]},{"many":false,"map":false,"name":"engine","reference":false,"required":true,"types":["DatabaseEngine"]},{"many":false,"map":false,"name":"fileMode","reference":false,"required":false,"types":["string"]},{"many":false,"map":false,"name":"mountAt","reference":false,"required":false,"types":["string"]},{"many":false,"map":false,"name":"role","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"rotation","reference":false,"required":true,"types":["Rotation"]}],"name":"DatabaseGrant"},{"features":[{"many":false,"map":false,"name":"analysis","reference":false,"required":true,"types":["AnalysisPolicy"]},{"many":false,"map":false,"name":"gate","reference":true,"required":true,"types":["Application"]},{"many":true,"map":false,"name":"machinery","reference":true,"required":true,"types":["Application"]},{"many":false,"map":false,"name":"reporter","reference":true,"required":false,"types":["Application"]}],"name":"DeliveryPolicy"},{"features":[{"many":false,"map":false,"name":"application","reference":false,"required":true,"types":["string"]},{"many":true,"map":false,"name":"buckets","reference":false,"required":false,"types":["CrossOriginBucket","string"]},{"many":false,"map":false,"name":"credentials","reference":false,"required":false,"types":["Credentials"]},{"many":false,"map":false,"name":"required","reference":false,"required":false,"types":["boolean"]},{"many":false,"map":false,"name":"surface","reference":false,"required":true,"types":["string"]}],"name":"DependencyEdge"},{"features":[{"many":false,"map":false,"name":"cidr","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"port","reference":false,"required":true,"types":["int"]}],"name":"DestinationRange"},{"features":[{"many":false,"map":false,"name":"webhook","reference":false,"required":true,"types":["VaultValue"]}],"name":"DiscordReceiver"},{"features":[{"many":true,"map":false,"name":"media","reference":false,"required":true,"types":["Media"]}],"name":"DiskRequest"},{"features":[{"many":false,"map":false,"name":"irreplaceable","reference":false,"required":false,"types":["DurabilityPolicy"]},{"many":false,"map":false,"name":"reconstructible","reference":false,"required":false,"types":["DurabilityPolicy"]},{"many":false,"map":false,"name":"recoverable","reference":false,"required":false,"types":["DurabilityPolicy"]}],"name":"DurabilityPolicies"},{"features":[{"many":false,"map":false,"name":"offCluster","reference":false,"required":false,"types":["OffClusterCopy"]},{"many":false,"map":false,"name":"retain","reference":false,"required":false,"types":["int"]},{"many":false,"map":false,"name":"schedule","reference":false,"required":false,"types":["string"]}],"name":"DurabilityPolicy"},{"features":[{"many":false,"map":false,"name":"reason","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"to","reference":false,"required":true,"types":["EgressTarget"]}],"name":"Egress"},{"features":[{"many":false,"map":false,"name":"files","reference":false,"required":false,"types":["EnginePolicy"]},{"many":false,"map":false,"name":"mongo","reference":false,"required":false,"types":["EnginePolicy"]},{"many":false,"map":false,"name":"postgres","reference":false,"required":false,"types":["EnginePolicy"]},{"many":false,"map":false,"name":"rabbitmq","reference":false,"required":false,"types":["EnginePolicy"]},{"many":false,"map":false,"name":"s3","reference":false,"required":false,"types":["EnginePolicy"]},{"many":false,"map":false,"name":"valkey","reference":false,"required":false,"types":["EnginePolicy"]}],"name":"EnginePolicies"},{"features":[{"many":false,"map":false,"name":"backup","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"catalog","reference":false,"required":false,"types":["string"]},{"many":false,"map":false,"name":"catalogSurface","reference":false,"required":false,"types":["string"]},{"many":false,"map":false,"name":"surface","reference":false,"required":false,"types":["string"]}],"name":"EnginePolicy"},{"features":[{"many":true,"map":false,"name":"entries","reference":false,"required":false,"types":["EnvVariable"]}],"name":"EnvFile"},{"features":[{"many":false,"map":false,"name":"text","reference":false,"required":true,"types":["string"]}],"name":"EnvLiteral"},{"features":[{"many":false,"map":false,"name":"name","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"value","reference":false,"required":true,"types":["EnvLiteral","Placeholder"]}],"name":"EnvVariable"},{"features":[{"many":false,"map":false,"name":"size","reference":false,"required":true,"types":["string"]}],"name":"EphemeralPolicy"},{"features":[{"many":false,"map":false,"name":"audience","reference":false,"required":true,"types":["Audience"]},{"many":false,"map":false,"name":"contentPolicy","reference":false,"required":false,"types":["ContentPolicy"]},{"many":false,"map":false,"name":"host","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"name","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"proxied","reference":false,"required":false,"types":["boolean"]},{"many":true,"map":false,"name":"routes","reference":false,"required":true,"types":["Route"]}],"name":"Exposure"},{"features":[{"many":false,"map":false,"name":"path","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"port","reference":false,"required":true,"types":["int"]}],"name":"FluxMetrics"},{"features":[{"many":false,"map":false,"name":"artifacts","reference":false,"required":true,"types":["RenderedArtifacts"]},{"many":false,"map":false,"name":"metrics","reference":false,"required":false,"types":["FluxMetrics"]},{"many":false,"map":false,"name":"sourceRef","reference":false,"required":true,"types":["string"]}],"name":"FluxSource"},{"features":[{"many":false,"map":false,"name":"path","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"provider","reference":true,"required":true,"types":["Application"]},{"many":true,"map":false,"name":"responseHeaders","reference":false,"required":false,"types":["string"]},{"many":false,"map":false,"name":"surface","reference":false,"required":true,"types":["string"]}],"name":"ForwardAuth"},{"features":[{"many":false,"map":false,"name":"adopt","reference":false,"required":false,"types":["string"]},{"many":false,"map":false,"name":"bytes","reference":false,"required":false,"types":["int"]},{"many":false,"map":false,"name":"encoding","reference":false,"required":false,"types":["KeyEncoding"]}],"name":"Generate"},{"features":[{"many":false,"map":false,"name":"generate","reference":false,"required":true,"types":["Generate"]},{"many":false,"map":false,"name":"name","reference":false,"required":true,"types":["string"]}],"name":"GeneratedKey"},{"features":[{"many":false,"map":false,"name":"class","reference":false,"required":false,"types":["string"]},{"many":false,"map":false,"name":"memory","reference":false,"required":false,"types":["string"]},{"many":true,"map":false,"name":"model","reference":false,"required":false,"types":["string"]},{"many":true,"map":false,"name":"vendor","reference":false,"required":false,"types":["string"]}],"name":"GpuRequest"},{"features":[{"many":false,"map":false,"name":"resource","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"runtimeClass","reference":false,"required":true,"types":["string"]}],"name":"GpuVendorPolicy"},{"features":[{"many":false,"map":false,"name":"path","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"port","reference":false,"required":true,"types":["int"]}],"name":"HttpProbe"},{"features":[{"many":false,"map":false,"name":"clusterRole","reference":false,"required":true,"types":["HumanGrantRole"]},{"many":false,"map":false,"name":"group","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"reason","reference":false,"required":true,"types":["string"]}],"name":"HumanGrant"},{"features":[{"many":false,"map":false,"name":"metrics","reference":false,"required":true,"types":["K3sMetrics"]}],"name":"K3sInstall"},{"features":[{"many":false,"map":false,"name":"apiServer","reference":false,"required":false,"types":["ApiServerMetrics"]},{"many":false,"map":false,"name":"coreDns","reference":false,"required":false,"types":["CoreDnsMetrics"]},{"many":false,"map":false,"name":"kubelet","reference":false,"required":false,"types":["KubeletMetrics"]}],"name":"K3sMetrics"},{"features":[{"many":false,"map":false,"name":"port","reference":false,"required":true,"types":["int"]}],"name":"KubeletMetrics"},{"features":[{"many":false,"map":false,"name":"access","reference":false,"required":true,"types":["AccessTier"]},{"many":false,"map":false,"name":"delivery","reference":false,"required":true,"types":["Delivery"]},{"many":false,"map":false,"name":"fileMode","reference":false,"required":false,"types":["string"]},{"many":true,"map":false,"name":"keys","reference":false,"required":true,"types":["GeneratedKey","OptionalKey","string"]},{"many":false,"map":false,"name":"mountAt","reference":false,"required":false,"types":["string"]},{"many":false,"map":false,"name":"path","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"rotation","reference":false,"required":true,"types":["Rotation"]}],"name":"KvGrant"},{"features":[{"many":false,"map":false,"name":"address","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"port","reference":false,"required":true,"types":["int"]},{"many":false,"map":false,"name":"process","reference":true,"required":true,"types":["Process"]},{"many":false,"map":false,"name":"protocol","reference":false,"required":true,"types":["L4Protocol"]},{"many":false,"map":false,"name":"surface","reference":true,"required":true,"types":["Surface"]}],"name":"L4Exposure"},{"features":[{"many":false,"map":false,"name":"gid","reference":false,"required":true,"types":["int"]},{"entry":"LibraryGrant","many":false,"map":true,"name":"grants","reference":false,"required":true,"types":["LibraryAccess"]},{"many":false,"map":false,"name":"node","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"path","reference":false,"required":true,"types":["string"]}],"name":"LibraryDeclaration"},{"features":[{"many":false,"map":false,"name":"access","reference":false,"required":true,"types":["LibraryAccess"]},{"many":false,"map":false,"name":"library","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"mountAt","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"subPath","reference":false,"required":false,"types":["string"]}],"name":"LibraryMount"},{"features":[{"many":false,"map":false,"name":"changelog","reference":false,"required":true,"types":["string"]}],"name":"ManagedMigration"},{"features":[{"many":false,"map":false,"name":"memory","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"path","reference":false,"required":true,"types":["string"]}],"name":"MemoryPath"},{"features":[{"many":true,"map":false,"name":"enrolled","reference":false,"required":true,"types":["string"]}],"name":"Mesh"},{"features":[{"many":false,"map":false,"name":"cpu","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"deadline","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"memory","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"runner","reference":false,"required":true,"types":["string"]}],"name":"MigrationPolicy"},{"features":[{"many":false,"map":false,"name":"interval","reference":false,"required":true,"types":["string"]},{"entry":"undefined","many":false,"map":true,"name":"labels","reference":false,"required":false,"types":["string"]},{"many":false,"map":false,"name":"timeout","reference":false,"required":true,"types":["string"]}],"name":"MonitorCadence"},{"features":[{"many":false,"map":false,"name":"alertmanager","reference":true,"required":true,"types":["Application"]},{"many":false,"map":false,"name":"grafana","reference":true,"required":true,"types":["Application"]},{"many":false,"map":false,"name":"prometheus","reference":true,"required":true,"types":["Application"]},{"many":true,"map":false,"name":"receivers","reference":false,"required":true,"types":["Receiver"]},{"many":true,"map":false,"name":"routes","reference":false,"required":true,"types":["SeverityRoute"]},{"many":false,"map":false,"name":"stores","reference":false,"required":false,"types":["MonitoringStores"]}],"name":"Monitoring"},{"features":[{"many":false,"map":false,"name":"loki","reference":true,"required":false,"types":["Application"]},{"many":false,"map":false,"name":"pyroscope","reference":true,"required":false,"types":["Application"]},{"many":false,"map":false,"name":"tempo","reference":true,"required":false,"types":["Application"]}],"name":"MonitoringStores"},{"features":[],"name":"NoMigration","scalar":"none"},{"features":[],"name":"NoProbes","scalar":"none"},{"features":[{"many":false,"map":false,"name":"kind","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"node","reference":false,"required":true,"types":["string"]}],"name":"NodeAddress"},{"features":[{"many":false,"map":false,"name":"cpu","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"image","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"memory","reference":false,"required":true,"types":["string"]}],"name":"NodeAgentPolicy"},{"features":[{"many":false,"map":false,"name":"gpu-device","reference":false,"required":false,"types":["NodeAgentPolicy"]},{"many":false,"map":false,"name":"gpu-metrics","reference":false,"required":false,"types":["NodeAgentPolicy"]},{"many":false,"map":false,"name":"l4-speaker","reference":false,"required":false,"types":["SpeakerPolicy"]},{"many":false,"map":false,"name":"node-metrics","reference":false,"required":false,"types":["NodeAgentPolicy"]}],"name":"NodeAgents"},{"features":[{"many":false,"map":false,"name":"scrape","reference":false,"required":true,"types":["Scrape"]}],"name":"Observability"},{"features":[{"many":false,"map":false,"name":"credential","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"destination","reference":false,"required":true,"types":["string"]},{"many":true,"map":false,"name":"egress","reference":false,"required":true,"types":["DestinationRange"]},{"many":false,"map":false,"name":"recipient","reference":false,"required":true,"types":["string"]}],"name":"OffClusterCopy"},{"features":[{"many":false,"map":false,"name":"name","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"optional","reference":false,"required":true,"types":["boolean"]}],"name":"OptionalKey"},{"features":[{"many":false,"map":false,"name":"reason","reference":false,"required":true,"types":["string"]}],"name":"Parked"},{"features":[{"many":false,"map":false,"name":"kind","reference":false,"required":true,"types":["PlaceholderKind"]},{"many":false,"map":false,"name":"source","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"suffix","reference":false,"required":false,"types":["string"]}],"name":"Placeholder"},{"features":[{"many":true,"map":false,"name":"arch","reference":false,"required":false,"types":["Arch"]},{"many":true,"map":false,"name":"capabilities","reference":false,"required":false,"types":["string"]},{"many":false,"map":false,"name":"cpu","reference":false,"required":false,"types":["string"]},{"many":false,"map":false,"name":"disk","reference":false,"required":false,"types":["DiskRequest"]},{"many":false,"map":false,"name":"gpu","reference":false,"required":false,"types":["GpuRequest"]},{"many":false,"map":false,"name":"memory","reference":false,"required":false,"types":["string"]},{"many":false,"map":false,"name":"site","reference":false,"required":false,"types":["string"]}],"name":"Placement"},{"features":[{"many":false,"map":false,"name":"acme","reference":false,"required":false,"types":["AcmePolicy"]},{"many":false,"map":false,"name":"apiAccess","reference":false,"required":false,"types":["ApiAccessPolicy"]},{"many":false,"map":false,"name":"apiVersion","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"bootstrap","reference":false,"required":true,"types":["Bootstrap"]},{"many":false,"map":false,"name":"delivery","reference":false,"required":false,"types":["DeliveryPolicy"]},{"many":false,"map":false,"name":"durability","reference":false,"required":true,"types":["DurabilityPolicies"]},{"many":false,"map":false,"name":"engines","reference":false,"required":true,"types":["EnginePolicies"]},{"many":false,"map":false,"name":"ephemeral","reference":false,"required":true,"types":["EphemeralPolicy"]},{"entry":"GpuVendor","many":false,"map":true,"name":"gpus","reference":false,"required":false,"types":["GpuVendorPolicy"]},{"many":false,"map":false,"name":"hardening","reference":false,"required":true,"types":["HardeningClass"]},{"many":true,"map":false,"name":"humanGrants","reference":false,"required":false,"types":["HumanGrant"]},{"many":false,"map":false,"name":"kind","reference":false,"required":true,"types":["string"]},{"entry":"L4Address","many":false,"map":true,"name":"l4Addresses","reference":false,"required":false,"types":["NodeAddress","PoolAddress"]},{"entry":"Library","many":false,"map":true,"name":"libraries","reference":false,"required":false,"types":["LibraryDeclaration"]},{"many":false,"map":false,"name":"mesh","reference":false,"required":false,"types":["Mesh"]},{"many":false,"map":false,"name":"metadata","reference":false,"required":true,"types":["PlatformMetadata"]},{"many":false,"map":false,"name":"migration","reference":false,"required":false,"types":["MigrationPolicy"]},{"many":false,"map":false,"name":"monitoring","reference":false,"required":false,"types":["Monitoring"]},{"many":false,"map":false,"name":"monitors","reference":false,"required":true,"types":["MonitorCadence"]},{"many":false,"map":false,"name":"nodeAgents","reference":false,"required":false,"types":["NodeAgents"]},{"many":false,"map":false,"name":"owner","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"policyJob","reference":false,"required":false,"types":["VaultPolicyJob"]},{"many":false,"map":false,"name":"priority","reference":false,"required":false,"types":["PriorityPolicy"]},{"many":false,"map":false,"name":"probes","reference":false,"required":true,"types":["ProbeCadence"]},{"many":true,"map":false,"name":"providers","reference":false,"required":false,"types":["Provider"]},{"many":false,"map":false,"name":"schemaVersion","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"secretStore","reference":true,"required":false,"types":["Application"]},{"many":false,"map":false,"name":"seeder","reference":false,"required":false,"types":["Seeder"]},{"many":false,"map":false,"name":"signIn","reference":false,"required":false,"types":["SignIn"]},{"many":false,"map":false,"name":"substrate","reference":false,"required":true,"types":["Substrate"]},{"many":false,"map":false,"name":"telemetry","reference":false,"required":false,"types":["TelemetryPolicy"]},{"many":true,"map":false,"name":"tiers","reference":false,"required":true,"types":["Tier"]},{"many":false,"map":false,"name":"vpn","reference":false,"required":false,"types":["VpnPolicy"]}],"name":"Platform"},{"features":[{"many":false,"map":false,"name":"cluster","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"nodeContract","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"project","reference":false,"required":true,"types":["string"]}],"name":"PlatformMetadata"},{"features":[{"many":false,"map":false,"name":"kind","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"range","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"site","reference":false,"required":true,"types":["string"]}],"name":"PoolAddress"},{"features":[{"many":false,"map":false,"name":"name","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"value","reference":false,"required":true,"types":["int"]}],"name":"PriorityClass"},{"features":[{"many":false,"map":false,"name":"critical","reference":false,"required":true,"types":["PriorityClass"]},{"many":true,"map":false,"name":"criticalApplications","reference":true,"required":false,"types":["Application"]},{"many":false,"map":false,"name":"data","reference":false,"required":true,"types":["PriorityClass"]}],"name":"PriorityPolicy"},{"features":[{"many":false,"map":false,"name":"failures","reference":false,"required":true,"types":["int"]},{"many":false,"map":false,"name":"period","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"timeout","reference":false,"required":true,"types":["string"]}],"name":"ProbeCadence"},{"features":[{"many":false,"map":false,"name":"liveness","reference":false,"required":false,"types":["HttpProbe","TcpProbe"]},{"many":false,"map":false,"name":"readiness","reference":false,"required":false,"types":["HttpProbe","TcpProbe"]}],"name":"Probes"},{"features":[{"many":false,"map":false,"name":"api","reference":false,"required":false,"types":["ApiAccess"]},{"many":true,"map":false,"name":"args","reference":false,"required":false,"types":["string"]},{"many":true,"map":false,"name":"assets","reference":false,"required":false,"types":["Asset","SeededAsset"]},{"many":true,"map":false,"name":"command","reference":false,"required":false,"types":["string"]},{"many":false,"map":false,"name":"cutover","reference":false,"required":false,"types":["Cutover"]},{"many":true,"map":false,"name":"dependsOn","reference":false,"required":false,"types":["DependencyEdge"]},{"many":false,"map":false,"name":"description","reference":false,"required":false,"types":["string"]},{"many":false,"map":false,"name":"egress","reference":false,"required":false,"types":["Egress"]},{"many":false,"map":false,"name":"engine","reference":false,"required":false,"types":["Engine"]},{"many":false,"map":false,"name":"image","reference":false,"required":true,"types":["string"]},{"many":true,"map":false,"name":"libraries","reference":false,"required":false,"types":["LibraryMount"]},{"many":false,"map":false,"name":"lifecycle","reference":false,"required":true,"types":["Lifecycle"]},{"many":false,"map":false,"name":"name","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"parked","reference":false,"required":false,"types":["Parked"]},{"many":false,"map":false,"name":"placement","reference":false,"required":false,"types":["Placement"]},{"many":false,"map":false,"name":"probes","reference":false,"required":false,"types":["NoProbes","Probes"]},{"entry":"Surface","many":false,"map":true,"name":"provides","reference":false,"required":false,"types":["int"]},{"many":false,"map":false,"name":"replicas","reference":false,"required":false,"types":["Capacity"]},{"many":false,"map":false,"name":"runtime","reference":false,"required":true,"types":["Runtime"]},{"many":false,"map":false,"name":"schedule","reference":false,"required":false,"types":["string"]},{"many":true,"map":false,"name":"secrets","reference":false,"required":false,"types":["DatabaseGrant","KvGrant","TransitGrant"]},{"many":true,"map":false,"name":"sidecars","reference":false,"required":false,"types":["Sidecar"]},{"many":true,"map":false,"name":"spawns","reference":false,"required":false,"types":["SpawnTemplate"]},{"many":false,"map":false,"name":"startupBudget","reference":false,"required":false,"types":["string"]},{"many":true,"map":false,"name":"volumes","reference":false,"required":false,"types":["Volume"]},{"many":false,"map":false,"name":"vpn","reference":false,"required":false,"types":["VpnAttachment"]},{"many":true,"map":false,"name":"writablePaths","reference":false,"required":false,"types":["MemoryPath","string"]}],"name":"Process"},{"features":[{"many":false,"map":false,"name":"apiVersion","reference":false,"required":true,"types":["string"]},{"many":true,"map":false,"name":"applications","reference":false,"required":true,"types":["Application"]},{"many":true,"map":false,"name":"assets","reference":false,"required":false,"types":["Asset","SeededAsset"]},{"many":false,"map":false,"name":"cutover","reference":false,"required":false,"types":["Cutover"]},{"many":true,"map":false,"name":"dependsOn","reference":false,"required":false,"types":["DependencyEdge"]},{"many":false,"map":false,"name":"kind","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"owner","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"placement","reference":false,"required":false,"types":["Placement"]},{"many":false,"map":false,"name":"project","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"schemaVersion","reference":false,"required":true,"types":["string"]},{"many":true,"map":false,"name":"secrets","reference":false,"required":false,"types":["DatabaseGrant","KvGrant","TransitGrant"]},{"many":false,"map":false,"name":"startupBudget","reference":false,"required":false,"types":["string"]},{"many":true,"map":false,"name":"writablePaths","reference":false,"required":false,"types":["MemoryPath","string"]}],"name":"Project"},{"features":[{"many":false,"map":false,"name":"address","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"name","reference":false,"required":true,"types":["string"]},{"entry":"ProviderSurface","many":false,"map":true,"name":"surfaces","reference":false,"required":true,"types":["int"]}],"name":"Provider"},{"features":[{"many":false,"map":false,"name":"per","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"requests","reference":false,"required":true,"types":["int"]}],"name":"RateLimit"},{"features":[{"many":false,"map":false,"name":"discord","reference":false,"required":true,"types":["DiscordReceiver"]},{"many":false,"map":false,"name":"name","reference":false,"required":true,"types":["string"]}],"name":"Receiver"},{"features":[{"many":false,"map":false,"name":"pullSecret","reference":false,"required":false,"types":["string"]},{"many":false,"map":false,"name":"repository","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"signer","reference":false,"required":true,"types":["ArtifactSigner"]}],"name":"RenderedArtifacts"},{"features":[{"many":false,"map":false,"name":"maxAge","reference":false,"required":false,"types":["string"]},{"many":false,"map":false,"name":"tolerates","reference":false,"required":true,"types":["Tolerance"]}],"name":"Rotation"},{"features":[{"many":false,"map":false,"name":"audience","reference":false,"required":false,"types":["Audience"]},{"many":false,"map":false,"name":"match","reference":false,"required":true,"types":["Match"]},{"many":false,"map":false,"name":"path","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"process","reference":true,"required":true,"types":["Process"]},{"many":false,"map":false,"name":"rateLimit","reference":false,"required":false,"types":["RateLimit"]},{"many":false,"map":false,"name":"redirectTo","reference":false,"required":false,"types":["string"]},{"many":false,"map":false,"name":"surface","reference":true,"required":true,"types":["Surface"]}],"name":"Route"},{"features":[{"many":false,"map":false,"name":"honorLabels","reference":false,"required":false,"types":["boolean"]},{"many":false,"map":false,"name":"path","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"process","reference":true,"required":true,"types":["Process"]},{"many":false,"map":false,"name":"surface","reference":true,"required":true,"types":["Surface"]}],"name":"Scrape"},{"features":[{"many":false,"map":false,"name":"from","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"seedAt","reference":false,"required":true,"types":["string"]}],"name":"SeededAsset"},{"features":[{"many":false,"map":false,"name":"cpu","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"image","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"memory","reference":false,"required":true,"types":["string"]}],"name":"Seeder"},{"features":[],"name":"SelfMigration","scalar":"self"},{"features":[{"many":false,"map":false,"name":"receiver","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"repeat","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"severity","reference":false,"required":true,"types":["string"]}],"name":"SeverityRoute"},{"features":[{"many":true,"map":false,"name":"args","reference":false,"required":false,"types":["string"]},{"many":true,"map":false,"name":"assets","reference":false,"required":false,"types":["SidecarAsset"]},{"many":true,"map":false,"name":"command","reference":false,"required":false,"types":["string"]},{"many":false,"map":false,"name":"cpu","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"image","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"init","reference":false,"required":false,"types":["boolean"]},{"many":false,"map":false,"name":"memory","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"name","reference":false,"required":true,"types":["string"]},{"many":true,"map":false,"name":"volumes","reference":false,"required":false,"types":["SidecarMount"]}],"name":"Sidecar"},{"features":[{"many":false,"map":false,"name":"from","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"mountAt","reference":false,"required":true,"types":["string"]}],"name":"SidecarAsset"},{"features":[{"many":false,"map":false,"name":"claim","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"mountAt","reference":false,"required":true,"types":["string"]}],"name":"SidecarMount"},{"features":[{"many":false,"map":false,"name":"contract","reference":false,"required":false,"types":["string"]},{"many":false,"map":false,"name":"issuer","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"provider","reference":true,"required":true,"types":["Application"]}],"name":"SignIn"},{"features":[{"many":false,"map":false,"name":"cpu","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"memory","reference":false,"required":true,"types":["string"]}],"name":"SpawnPlacement"},{"features":[{"many":true,"map":false,"name":"dependsOn","reference":false,"required":false,"types":["DependencyEdge"]},{"many":false,"map":false,"name":"egress","reference":false,"required":false,"types":["Egress"]},{"many":false,"map":false,"name":"image","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"name","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"placement","reference":false,"required":true,"types":["SpawnPlacement"]},{"entry":"Surface","many":false,"map":true,"name":"provides","reference":false,"required":false,"types":["int"]}],"name":"SpawnTemplate"},{"features":[{"many":false,"map":false,"name":"controller","reference":false,"required":true,"types":["NodeAgentPolicy"]},{"many":false,"map":false,"name":"cpu","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"image","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"memory","reference":false,"required":true,"types":["string"]}],"name":"SpeakerPolicy"},{"features":[{"many":false,"map":false,"name":"clusterDns","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"cni","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"datastore","reference":false,"required":true,"types":["Datastore"]},{"many":false,"map":false,"name":"imagePull","reference":false,"required":false,"types":["string"]},{"many":false,"map":false,"name":"kubernetesVersion","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"networkPolicyController","reference":false,"required":true,"types":["PolicyController"]},{"many":false,"map":false,"name":"secretsEncryption","reference":false,"required":true,"types":["boolean"]},{"many":false,"map":false,"name":"serverCount","reference":false,"required":true,"types":["int"]},{"many":true,"map":false,"name":"upstreamNameservers","reference":false,"required":false,"types":["string"]}],"name":"Substrate"},{"features":[{"many":false,"map":false,"name":"tcp","reference":false,"required":true,"types":["int"]}],"name":"TcpProbe"},{"features":[{"many":false,"map":false,"name":"collector","reference":true,"required":true,"types":["Application"]},{"many":false,"map":false,"name":"dashboards","reference":false,"required":false,"types":["DashboardPolicy"]},{"many":false,"map":false,"name":"metrics","reference":true,"required":true,"types":["Application"]}],"name":"TelemetryPolicy"},{"features":[{"many":false,"map":false,"name":"address","reference":false,"required":false,"types":["string"]},{"many":true,"map":false,"name":"audiences","reference":false,"required":true,"types":["Audience"]},{"many":false,"map":false,"name":"certificateFrom","reference":false,"required":false,"types":["string"]},{"many":false,"map":false,"name":"certificates","reference":false,"required":true,"types":["CertificateSource"]},{"many":false,"map":false,"name":"dns","reference":false,"required":false,"types":["TierDns"]},{"many":false,"map":false,"name":"forwardAuth","reference":false,"required":false,"types":["ForwardAuth"]},{"many":false,"map":false,"name":"ingressClass","reference":false,"required":false,"types":["string"]},{"many":false,"map":false,"name":"listener","reference":false,"required":true,"types":["Listener"]},{"many":false,"map":false,"name":"name","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"proxiedBy","reference":false,"required":false,"types":["ProxiedBy"]},{"many":false,"map":false,"name":"traefik","reference":true,"required":true,"types":["Application"]}],"name":"Tier"},{"features":[{"many":false,"map":false,"name":"direct","reference":false,"required":false,"types":["string"]},{"many":false,"map":false,"name":"proxied","reference":false,"required":true,"types":["boolean"]},{"many":false,"map":false,"name":"target","reference":false,"required":true,"types":["string"]}],"name":"TierDns"},{"features":[{"many":false,"map":false,"name":"delivery","reference":false,"required":true,"types":["Delivery"]},{"many":false,"map":false,"name":"engine","reference":false,"required":true,"types":["TransitEngine"]},{"many":false,"map":false,"name":"fileMode","reference":false,"required":false,"types":["string"]},{"many":false,"map":false,"name":"key","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"mountAt","reference":false,"required":false,"types":["string"]},{"many":true,"map":false,"name":"operations","reference":false,"required":true,"types":["TransitOp"]},{"many":false,"map":false,"name":"rotation","reference":false,"required":true,"types":["Rotation"]}],"name":"TransitGrant"},{"features":[{"many":false,"map":false,"name":"cpu","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"deadline","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"image","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"memory","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"role","reference":false,"required":true,"types":["string"]}],"name":"VaultPolicyJob"},{"features":[{"many":false,"map":false,"name":"unsealed","reference":false,"required":true,"types":["boolean"]}],"name":"VaultState"},{"features":[{"many":false,"map":false,"name":"key","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"path","reference":false,"required":true,"types":["string"]}],"name":"VaultValue"},{"features":[{"many":false,"map":false,"name":"claim","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"durability","reference":false,"required":true,"types":["DurabilityClass"]},{"many":false,"map":false,"name":"mountAt","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"size","reference":false,"required":true,"types":["string"]}],"name":"Volume"},{"features":[{"many":false,"map":false,"name":"forwardedPort","reference":false,"required":true,"types":["boolean"]}],"name":"VpnAttachment"},{"features":[{"many":false,"map":false,"name":"cpu","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"credential","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"image","reference":false,"required":true,"types":["string"]},{"many":false,"map":false,"name":"memory","reference":false,"required":true,"types":["string"]},{"entry":"VpnSetting","many":false,"map":true,"name":"settings","reference":false,"required":false,"types":["string"]}],"name":"VpnPolicy"}],"vocabularies":[{"literals":["read","self-renew","self-roll","custody"],"name":"AccessTier"},{"literals":["namespace","cluster"],"name":"ApiScope"},{"literals":["get","list","watch","create","update","patch","delete","deletecollection"],"name":"ApiVerb"},{"literals":["amd64","arm64"],"name":"Arch"},{"literals":["anonymous","authenticated","internal","lan"],"name":"Audience"},{"literals":["acme","none"],"name":"CertificateSource"},{"literals":["secret","pkce"],"name":"ClientCredential"},{"literals":["strict","admin","workflow"],"name":"ContentPolicy"},{"literals":["continuous","interrupted"],"name":"Cutover"},{"literals":["database"],"name":"DatabaseEngine"},{"literals":["sqlite","etcd"],"name":"Datastore"},{"literals":["env","file","self"],"name":"Delivery"},{"literals":["cloudflare"],"name":"DnsProvider"},{"literals":["reconstructible","recoverable","irreplaceable"],"name":"DurabilityClass"},{"literals":["internet"],"name":"EgressTarget"},{"literals":["postgres","rabbitmq","valkey","mongo","files","s3"],"name":"Engine"},{"literals":["restricted"],"name":"HardeningClass"},{"literals":["cluster-admin","admin","edit","view"],"name":"HumanGrantRole"},{"literals":["hex","base64","base64url"],"name":"KeyEncoding"},{"literals":["tcp","udp"],"name":"L4Protocol"},{"literals":["read","write"],"name":"LibraryAccess"},{"literals":["application","job","prepare","scheduled"],"name":"Lifecycle"},{"literals":["tls","plain"],"name":"Listener"},{"literals":["prefix","exact"],"name":"Match"},{"literals":["nvme","ssd","hdd"],"name":"Media"},{"literals":["secret","dependency","exposure","identity","client"],"name":"PlaceholderKind"},{"literals":["none","embedded","cni"],"name":"PolicyController"},{"literals":["cloudflare"],"name":"ProxiedBy"},{"literals":["jvm","python","node","static","none"],"name":"Runtime"},{"literals":["native","gated","token"],"name":"SignInMode"},{"literals":["node"],"name":"Spread"},{"literals":["restart","reload"],"name":"Tolerance"},{"literals":["transit"],"name":"TransitEngine"},{"literals":["sign","verify","encrypt","decrypt","rotate","read"],"name":"TransitOp"}]} \ No newline at end of file diff --git a/spec/v1/schemas/project-intent.schema.json b/spec/v1/schemas/project-intent.schema.json index 06ca18b29..d837be223 100644 --- a/spec/v1/schemas/project-intent.schema.json +++ b/spec/v1/schemas/project-intent.schema.json @@ -377,8 +377,15 @@ "minItems": 1, "type": "array", "items": { - "type": "string", - "pattern": "^[a-z0-9](?:[a-z0-9-]{0,30}[a-z0-9])?$" + "anyOf": [ + { + "type": "string", + "pattern": "^[a-z0-9](?:[a-z0-9-]{0,30}[a-z0-9])?$" + }, + { + "$ref": "#/$defs/CrossOriginBucket" + } + ] } } }, @@ -411,6 +418,28 @@ ], "additionalProperties": false }, + "CrossOriginBucket": { + "type": "object", + "properties": { + "name": { + "type": "string", + "pattern": "^[a-z0-9](?:[a-z0-9-]{0,30}[a-z0-9])?$" + }, + "cors": { + "minItems": 1, + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + } + }, + "required": [ + "name", + "cors" + ], + "additionalProperties": false + }, "Asset": { "type": "object", "properties": { diff --git a/spec/v1/schemas/resolved-deployment.schema.json b/spec/v1/schemas/resolved-deployment.schema.json index f6ec9f3d0..3a46e464e 100644 --- a/spec/v1/schemas/resolved-deployment.schema.json +++ b/spec/v1/schemas/resolved-deployment.schema.json @@ -3629,6 +3629,13 @@ "type": "string", "minLength": 1 } + }, + "cors": { + "minItems": 1, + "type": "array", + "items": { + "$ref": "#/$defs/CatalogBucketCors" + } } }, "required": [ @@ -3638,6 +3645,28 @@ ], "additionalProperties": false }, + "CatalogBucketCors": { + "type": "object", + "properties": { + "bucket": { + "type": "string", + "minLength": 1 + }, + "origins": { + "minItems": 1, + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + } + }, + "required": [ + "bucket", + "origins" + ], + "additionalProperties": false + }, "ResolvedEvents": { "type": "object", "properties": { diff --git a/src/check/project.ts b/src/check/project.ts index 09f660010..a17e9ac00 100644 --- a/src/check/project.ts +++ b/src/check/project.ts @@ -17,6 +17,8 @@ import type { SharedIntent as Level, } from "../model/project-intent.ts"; import { + bucketCorsOf, + bucketNameOf, clientExposure, routeFor, declared, @@ -818,6 +820,38 @@ function credentialRefusals( }); } +/** + * Every `cors` entry of a bucket that names no exposure of the Application whose + * pages would call it (spec/v1/16-dependencies.md#a-bucket-a-browser-calls). + * An edge declared above the Application holds for every Application beneath + * it, so each of them is checked against its own exposures. + */ +function corsRefusals( + edges: readonly DependencyEdge[] | undefined, + applications: readonly Application[], + at: string, +): Refusal[] { + const refusals: Refusal[] = []; + // A missing list and an empty one hold no edge alike. + // Stryker disable next-line ArrayDeclaration + for (const [e, edge] of (edges ?? []).entries()) + for (const [b, entry] of (edge.buckets ?? []).entries()) + for (const [c, name] of bucketCorsOf(entry).entries()) { + const lacking = applications.find( + ({ exposure }) => + !(exposure ?? []).some((candidate) => candidate.name === name), + ); + if (lacking !== undefined) + refusals.push({ + code: "E_UNKNOWN_CORS_EXPOSURE", + path: `${at}/dependsOn/${String(e)}/buckets/${String(b)}/cors/${String(c)}`, + message: `${lacking.id} declares no exposure named ${name}, so no origin of its pages can call ${bucketNameOf(entry)}`, + hint: "Name an exposure of the Application whose pages call the bucket, or write the bucket by its name alone.", + }); + } + return refusals; +} + /** Every rule this document breaks, in the order the document reads. */ export function ruleDiagnostics( document: ProjectIntentDocument, @@ -851,14 +885,25 @@ export function ruleDiagnostics( ]), ]), ...credentialRefusals(document.dependsOn, ""), + ...corsRefusals(document.dependsOn, document.applications, ""), ...document.applications.flatMap((application, a) => [ ...credentialRefusals(application.dependsOn, `/applications/${a}`), - ...application.processes.flatMap((process, p) => - credentialRefusals( + ...corsRefusals( + application.dependsOn, + [application], + `/applications/${a}`, + ), + ...application.processes.flatMap((process, p) => [ + ...credentialRefusals( process.dependsOn, `/applications/${a}/processes/${p}`, ), - ), + ...corsRefusals( + process.dependsOn, + [application], + `/applications/${a}/processes/${p}`, + ), + ]), ]), ...migrationOwnerRefusals(document), ...duplicateClaimRefusals(document), diff --git a/src/model/diagnostic.ts b/src/model/diagnostic.ts index b1e579a01..8a2a0a271 100644 --- a/src/model/diagnostic.ts +++ b/src/model/diagnostic.ts @@ -152,6 +152,7 @@ export type RefusalCode = | "E_UNKNOWN_SITE" | "E_UNKNOWN_SURFACE" | "E_UNKNOWN_CRITICAL_APPLICATION" + | "E_UNKNOWN_CORS_EXPOSURE" | "E_UNKNOWN_FORWARD_AUTH_PROVIDER" | "E_UNKNOWN_TELEMETRY_COLLECTOR" | "E_UNKNOWN_TIER_PROXY" diff --git a/src/model/project-intent-queries.ts b/src/model/project-intent-queries.ts index 9dbeae396..4a3dc8c1f 100644 --- a/src/model/project-intent-queries.ts +++ b/src/model/project-intent-queries.ts @@ -206,6 +206,21 @@ export const bucketStoreOf = ( export const bucketOf = (project: string, name: string): string => `${project}-${name}`; +/** One bucket entry of an edge. */ +export type BucketEntry = NonNullable[number]; + +/** The name a bucket entry gives, whichever form it is written in. */ +export const bucketNameOf = (entry: BucketEntry): string => + typeof entry === "string" ? entry : entry.name; + +/** + * The exposures of the consuming Application whose pages may call a bucket + * cross-origin (spec/v1/16-dependencies.md#a-bucket-a-browser-calls): none for + * a bucket written by its name alone. + */ +export const bucketCorsOf = (entry: BucketEntry): readonly string[] => + typeof entry === "string" ? [] : entry.cors; + /** The role that may change a project's schema, which only its migration holds. */ export const ownerRole = (project: string): string => `${project}-owner`; diff --git a/src/model/project-intent.ts b/src/model/project-intent.ts index 2d2533d35..9a90da477 100644 --- a/src/model/project-intent.ts +++ b/src/model/project-intent.ts @@ -256,13 +256,24 @@ const credentials = z // is a valid S3 bucket name. export const BUCKET_NAME = /^[a-z0-9](?:[a-z0-9-]{0,30}[a-z0-9])?$/; +// A bucket an edge names: its name alone, or its name and the exposures of the +// consuming Application whose pages may call it cross-origin +// (spec/v1/16-dependencies.md#a-bucket-a-browser-calls). +const bucketName = z.string().regex(BUCKET_NAME); +const bucket = z.union([ + bucketName, + z + .strictObject({ name: bucketName, cors: z.array(text).min(1) }) + .meta({ id: "CrossOriginBucket" }), +]); + const dependencyEdge = z .strictObject({ application: text, surface: text, required: z.boolean().exactOptional(), credentials: credentials.exactOptional(), - buckets: z.array(z.string().regex(BUCKET_NAME)).min(1).exactOptional(), + buckets: z.array(bucket).min(1).exactOptional(), }) .meta({ id: "DependencyEdge" }); diff --git a/src/model/resolved-deployment.ts b/src/model/resolved-deployment.ts index acab8a786..d7c74f7b1 100644 --- a/src/model/resolved-deployment.ts +++ b/src/model/resolved-deployment.ts @@ -1268,6 +1268,20 @@ const resolvedBucketCatalog = z name: text, path: text, buckets: z.array(text).min(1), + // The buckets of the key that admit an origin, each with every + // origin it admits; a bucket absent here admits none + // (spec/v1/16-dependencies.md#a-bucket-a-browser-calls). + cors: z + .array( + z + .strictObject({ + bucket: text, + origins: z.array(text).min(1), + }) + .meta({ id: "CatalogBucketCors" }), + ) + .min(1) + .exactOptional(), }) .meta({ id: "CatalogKey" }), ) diff --git a/src/resolve/catalog-job.ts b/src/resolve/catalog-job.ts index 4481359ea..64a0cf196 100644 --- a/src/resolve/catalog-job.ts +++ b/src/resolve/catalog-job.ts @@ -6,6 +6,8 @@ import type { EffectiveProject } from "../model/effective-intent.ts"; import { type LockedImage, versionOf } from "../model/images-lock.ts"; import { + bucketCorsOf, + bucketNameOf, bucketOf, dataRole, ownerRole, @@ -90,30 +92,76 @@ interface Owner { readonly process: EffectiveProject["applications"][number]["processes"][number]; } -/** The projects whose Processes name buckets in one object store, with the buckets each names. */ +/** One project's share of an object store's catalog: its buckets, and the origins each admits. */ +interface BucketReader { + readonly project: string; + readonly buckets: readonly string[]; + readonly cors: readonly { + readonly bucket: string; + readonly origins: readonly string[]; + }[]; +} + +/** + * The origin an exposure's pages are served from: its tier's scheme and its + * host, as `${exposure:…#url}` reads it. + */ +function originOf( + set: PinnedSet, + exposure: NonNullable[number], +): string { + // A tier carries the exposure's audience, or E_NO_TIER_FOR_AUDIENCE refused it. + const tier = set.platform.tiers.find(({ audiences }) => + audiences.includes(exposure.audience), + ) as PinnedSet["platform"]["tiers"][number]; + return `${tier.listener === "tls" ? "https" : "http"}://${exposure.host}`; +} + +/** + * The projects whose Processes name buckets in one object store, with the + * buckets each names and the origins each bucket admits cross-origin + * (spec/v1/16-dependencies.md#a-bucket-a-browser-calls). + */ // A missing list and an empty one hold no edge, and no bucket, alike; and no // two readers share a project, so `<=` would order the same list. // Stryker disable ArrayDeclaration,EqualityOperator function bucketReadersOf( set: PinnedSet, { application }: Pick, -): { readonly project: string; readonly buckets: readonly string[] }[] { +): BucketReader[] { return set.projects - .map(({ project, applications }) => ({ - project, - buckets: [ - ...new Set( - applications - .flatMap(({ processes }) => processes) - .flatMap(({ dependsOn }) => dependsOn ?? []) - // Surfaces are unique in an Application, so it runs one store - // that provides the surface its catalog is applied over. - .filter((edge) => edge.application === application.id) - .flatMap((edge) => edge.buckets ?? []) - .map((name) => bucketOf(project, name)), - ), - ].sort(), - })) + .map(({ project, applications }): BucketReader => { + const origins = new Map>(); + const buckets = new Set(); + for (const consumer of applications) + for (const edge of consumer.processes + .flatMap(({ dependsOn }) => dependsOn ?? []) + // Surfaces are unique in an Application, so it runs one store + // that provides the surface its catalog is applied over. + .filter((edge) => edge.application === application.id)) + for (const entry of edge.buckets ?? []) { + const bucket = bucketOf(project, bucketNameOf(entry)); + buckets.add(bucket); + for (const name of bucketCorsOf(entry)) { + // A `cors` entry names an exposure of the consuming + // Application, or E_UNKNOWN_CORS_EXPOSURE refused it. + const exposure = (consumer.exposure ?? []).find( + (candidate) => candidate.name === name, + ) as NonNullable[number]; + const held = origins.get(bucket) ?? new Set(); + held.add(originOf(set, exposure)); + origins.set(bucket, held); + } + } + return { + project, + buckets: [...buckets].sort(), + cors: [...origins.keys()].sort().map((bucket) => ({ + bucket, + origins: [...(origins.get(bucket) as Set)].sort(), + })), + }; + }) .filter(({ buckets }) => buckets.length > 0) .sort((a, b) => (a.project < b.project ? -1 : 1)); } @@ -132,10 +180,20 @@ function catalogOf( return { ...address, admin: { path, ...TOKEN_KEY }, - keys: readers.map(({ project, buckets }) => ({ + keys: readers.map(({ project, buckets, cors }) => ({ name: project, path: bucketKeyOf(project, owner.process.name), buckets: [...buckets], + // Written only where some bucket admits an origin, so a catalog + // whose buckets no browser calls reads as it did before. + ...(cors.length === 0 + ? {} + : { + cors: cors.map(({ bucket, origins }) => ({ + bucket, + origins: [...origins], + })), + }), })), }; } diff --git a/test/model/refusals.test.ts b/test/model/refusals.test.ts index 155fe641e..b38f64d6c 100644 --- a/test/model/refusals.test.ts +++ b/test/model/refusals.test.ts @@ -202,6 +202,20 @@ describe("a generated key that adopts a value", () => { }); }); +describe("a Process's edges refused together", () => { + it("are refused in the order the document reads them", () => { + const text = DOCUMENT.replace( + " cutover: interrupted\n", + " cutover: interrupted\n dependsOn:\n - application: platform-postgres\n surface: postgres\n credentials: { delivery: env, rotation: { tolerates: reload } }\n - application: platform-garage\n surface: s3\n buckets: [{ name: uploads, cors: [public] }]\n", + ).replace("GRANTS", ""); + + expect(refusalsOf(text).map(({ path }) => path)).toStrictEqual([ + "/applications/0/processes/0/dependsOn/0/credentials/rotation", + "/applications/0/processes/0/dependsOn/1/buckets/0/cors/0", + ]); + }); +}); + describe("an edge's credential delivered to the environment", () => { const edge = (tolerates: string) => DOCUMENT.replace( diff --git a/test/model/render.test.ts b/test/model/render.test.ts index f9958d47e..87ad93cda 100644 --- a/test/model/render.test.ts +++ b/test/model/render.test.ts @@ -1860,6 +1860,132 @@ describe("the bucket catalog", () => { expect(policy("hindsight-worker")).toStrictEqual([]); }); + // spec/v1/16-dependencies.md#a-bucket-a-browser-calls + const CORS_EDGE = + " - { application: platform-garage, surface: s3, buckets: [assets] }\n"; + const withCors = ( + buckets: string, + ): Readonly string>> => ({ + [KP]: (document: string) => + document.replace( + CORS_EDGE, + ` - { application: platform-garage, surface: s3, buckets: ${buckets} }\n`, + ), + }); + + it("lists each bucket a browser calls with the origin of every exposure it names", () => { + expect( + catalogIn( + owned(withCors("[{ name: assets, cors: [public] }, litestream]")), + OWNED, + ), + ).toMatchObject({ + keys: [ + { + name: "knowledge-platform", + buckets: [ + "knowledge-platform-assets", + "knowledge-platform-litestream", + ], + cors: [ + { + bucket: "knowledge-platform-assets", + origins: ["https://karakeep.jorisjonkers.dev"], + }, + ], + }, + ], + }); + }); + + it("admits every origin two edges of one project give a bucket, once each and in order", () => { + const keys = catalogIn( + owned({ + [KP]: (document) => + document + .replace( + CORS_EDGE, + " - { application: platform-garage, surface: s3, buckets: [{ name: assets, cors: [public] }] }\n", + ) + .replace( + " - { application: hindsight-embedding, surface: embeddings }\n", + " - { application: hindsight-embedding, surface: embeddings }\n - { application: platform-garage, surface: s3, buckets: [{ name: assets, cors: [console, api, console] }] }\n", + ), + }), + OWNED, + ) as { keys: { cors?: unknown }[] }; + + expect(keys.keys[0]?.cors).toStrictEqual([ + { + bucket: "knowledge-platform-assets", + origins: [ + "https://karakeep.jorisjonkers.dev", + "https://memory-api.jorisjonkers.dev", + "https://memory.jorisjonkers.dev", + ], + }, + ]); + }); + + it("lists no cors for a key none of whose buckets a browser calls", () => { + const keys = catalogIn(owned(), OWNED) as { keys: object[] }; + + expect(keys.keys[0]).not.toHaveProperty("cors"); + }); + + it("refuses a cors entry that names no exposure of the consuming Application", () => { + const result = renderIntentSet( + [ + ...SET.filter((name) => !name.startsWith("minimal/")), + ...KNOWLEDGE_PLATFORM, + ].map((name) => ({ + name, + text: ( + withCors("[{ name: assets, cors: [admin] }]")[name] ?? + ((same: string) => same) + )(text(name)), + })), + { ...OPTIONS, projects: ["data", "knowledge-platform"] }, + ); + + expect( + result.ok + ? [] + : result.diagnostics + .filter(({ code }) => code === "E_UNKNOWN_CORS_EXPOSURE") + .map(({ path }) => path), + ).toStrictEqual([ + expect.stringMatching(/\/dependsOn\/0\/buckets\/0\/cors\/0$/), + ]); + }); + + it("refuses a cors entry above the Application where an Application beneath it declares no such exposure", () => { + const result = renderIntentSet( + [ + ...SET.filter((name) => !name.startsWith("minimal/")), + ...KNOWLEDGE_PLATFORM, + ].map((name) => ({ + name, + text: + name === KP + ? text(name).replace( + "owner: joris\n", + "owner: joris\ndependsOn:\n - { application: platform-garage, surface: s3, buckets: [{ name: shared, cors: [public] }] }\n", + ) + : text(name), + })), + { ...OPTIONS, projects: ["data", "knowledge-platform"] }, + ); + + expect( + result.ok + ? [] + : result.diagnostics + .filter(({ code }) => code === "E_UNKNOWN_CORS_EXPOSURE") + .map(({ path }) => path), + ).toStrictEqual(["/dependsOn/0/buckets/0/cors/0"]); + }); + it("is applied by no method where the engine names none", () => { const garage = ( edits: Readonly string>>,