From 63669ffe082ca2ffb9db3830e3797b608e2c3e33 Mon Sep 17 00:00:00 2001 From: Joris Wouter Jonkers Date: Tue, 8 Sep 2026 19:57:18 +0200 Subject: [PATCH 1/4] docs(diagrams): the class diagram was short a Grant edge and the Scrape class MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two relations the chapter states and the drawing did not. A grant sits at whichever level the secret is shared: on the Service when every Workload holds it, on the Workload when only that one does ([0022](docs/adr/model/0022-grants-live-on-the-service.md)). The SVG drew only the Service composition, so it read as though a Workload cannot hold a secret of its own. Both edges are now drawn, and the Service one says which case it is rather than the bare word "shared", which invited reading it as shared between Services — no such level exists. `Scrape` was missing entirely. It joins the Workload band as an eleventh composite, and the mermaid at the foot of the chapter already carried both, so this closes a gap between the two. --- spec/v1/diagrams/10-service-intent-model.drawio.svg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/v1/diagrams/10-service-intent-model.drawio.svg b/spec/v1/diagrams/10-service-intent-model.drawio.svg index f573a6d..370ad90 100644 --- a/spec/v1/diagrams/10-service-intent-model.drawio.svg +++ b/spec/v1/diagrams/10-service-intent-model.drawio.svg @@ -1,4 +1,4 @@ -Enumerations — every closed vocabulary in layer 1Domain+ DomainName domain+ string owner+ SemVer schemaVersionService+ ServiceId id+ AlertClass alertClassGrant+ SecretEngine engine+ VaultPath path+ string[] keys+ AccessTier access+ string role+ string key+ TransitOp[] operations+ Delivery delivery+ Path mountAt+ FileMode fileModeWorkload+ string name+ Lifecycle lifecycle+ ImageAlias image+ Runtime runtime+ Engine engine+ Duration startupBudget+ bool zeroDowntime+ bool stateful+ Path[] writablePaths+ HardeningClass hardeningExposure+ ExposureName name+ Fqdn host+ Audience audience+ ContentPolicy contentPolicy0..* secrets — shared1..* workloads0..* exposure1..* servicesRotation+ Tolerance tolerates+ Duration maxAge0..1 rotationRoute+ Path path+ Match match+ string workload+ string surface+ Audience audience+ Path redirectTo1..* routesSurface+ string name+ int port0..* providesSidecar+ string name+ ImageAlias image+ Quantity memory+ Quantity cpu0..* sidecarsDependencyEdge+ ServiceId service+ string surface+ bool required0..* dependsOnProbe+ Path path+ int port+ int tcp0..2 probesAsset+ Path from+ Path mountAt+ map substitute0..* assetsVolume+ ClaimName claim+ Path mountAt+ Quantity size+ DurabilityClass durability0..* volumesPlacement+ Quantity memory+ Quantity cpu+ Arch[] arch+ Site site+ Capability[] capabilities1 placementEnvFile+ ClusterTarget cluster+ dotenv entries1..* envOverride+ Derivation derivation+ any value+ string reason0..* overridesHardeningException+ Control allow+ string reason0..* exceptionsDiskRequest+ Media[] media+ Quantity sizeGpuRequest+ GpuClass class+ Quantity memoryPlaceholder+ PlaceholderKind kind+ string source0..1 disk0..1 gpu0..* resolvesnames a Surface of another Servicenames a Surface this Service providesa secret placeholder byte-matches a grant's derived read pathAn attribute's type names its enumeration. Values are exhaustive: a value absent from a list herefails schema validation.«enumeration»Lifecycleservicejob— on Workload«enumeration»Runtimejvmpythonnodestaticnone— on Workload«enumeration»Enginepostgresrabbitmqvalkeyfiles— on Workload«enumeration»HardeningClassrestricted— on Workload«enumeration»ControlrunAsRootwritableRootFilesystemcapability:<NAME>seccompUnconfined— on HardeningException«enumeration»DurabilityClassreconstructiblerecoverableirreplaceable— on Volume«enumeration»AlertClassnonebusiness-hoursurgentpage— on Service«enumeration»Audienceanonymousauthenticatedinternallan— on Exposure, Route«enumeration»ContentPolicystrictadminworkflow— on Exposure«enumeration»Matchprefixexact— on Route«enumeration»SecretEnginekvdatabasetransit— on Grant«enumeration»AccessTierreadself-renewself-rollcustody— on Grant — kv only«enumeration»TransitOpsignverifyencryptdecryptrotate— on Grant — transit only«enumeration»Deliveryenvfileself— on Grant«enumeration»Tolerancerestartreload— on Rotation«enumeration»PlaceholderKindsecretdependencyexposureidentity— on Placeholder«enumeration»Medianvmessdhdd— on DiskRequest«enumeration»Archamd64arm64— on Placement \ No newline at end of file +Enumerations — every closed vocabulary in layer 1Domain+ DomainName domain+ string owner+ SemVer schemaVersionService+ ServiceId id+ AlertClass alertClassGrant+ SecretEngine engine+ VaultPath path+ string[] keys+ AccessTier access+ string role+ string key+ TransitOp[] operations+ Delivery delivery+ Path mountAt+ FileMode fileModeWorkload+ string name+ Lifecycle lifecycle+ ImageAlias image+ Runtime runtime+ Engine engine+ Duration startupBudget+ bool zeroDowntime+ bool stateful+ Path[] writablePaths+ HardeningClass hardeningExposure+ ExposureName name+ Fqdn host+ Audience audience+ ContentPolicy contentPolicy0..* secrets — every Workload holds it1..* workloads0..* exposure1..* services0..* secrets — this Workload onlyRotation+ Tolerance tolerates+ Duration maxAge0..1 rotationRoute+ Path path+ Match match+ string workload+ string surface+ Audience audience+ Path redirectTo1..* routesSurface+ string name+ int port0..* providesSidecar+ string name+ ImageAlias image+ Quantity memory+ Quantity cpu0..* sidecarsDependencyEdge+ ServiceId service+ string surface+ bool required0..* dependsOnProbe+ Path path+ int port+ int tcp0..2 probesAsset+ Path from+ Path mountAt+ map substitute0..* assetsVolume+ ClaimName claim+ Path mountAt+ Quantity size+ DurabilityClass durability0..* volumesPlacement+ Quantity memory+ Quantity cpu+ Arch[] arch+ Site site+ Capability[] capabilities1 placementScrape+ int port+ Path path0..1 scrapeEnvFile+ ClusterTarget cluster+ dotenv entries1..* envOverride+ Derivation derivation+ any value+ string reason0..* overridesHardeningException+ Control allow+ string reason0..* exceptionsDiskRequest+ Media[] media+ Quantity sizeGpuRequest+ GpuClass class+ Quantity memoryPlaceholder+ PlaceholderKind kind+ string source0..1 disk0..1 gpu0..* resolvesnames a Surface of another Servicenames a Surface this Service providesa secret placeholder byte-matches a grant's derived read pathAn attribute's type names its enumeration. Values are exhaustive: a value absent from a list herefails schema validation.«enumeration»Lifecycleservicejob— on Workload«enumeration»Runtimejvmpythonnodestaticnone— on Workload«enumeration»Enginepostgresrabbitmqvalkeyfiles— on Workload«enumeration»HardeningClassrestricted— on Workload«enumeration»ControlrunAsRootwritableRootFilesystemcapability:<NAME>seccompUnconfined— on HardeningException«enumeration»DurabilityClassreconstructiblerecoverableirreplaceable— on Volume«enumeration»AlertClassnonebusiness-hoursurgentpage— on Service«enumeration»Audienceanonymousauthenticatedinternallan— on Exposure, Route«enumeration»ContentPolicystrictadminworkflow— on Exposure«enumeration»Matchprefixexact— on Route«enumeration»SecretEnginekvdatabasetransit— on Grant«enumeration»AccessTierreadself-renewself-rollcustody— on Grant — kv only«enumeration»TransitOpsignverifyencryptdecryptrotate— on Grant — transit only«enumeration»Deliveryenvfileself— on Grant«enumeration»Tolerancerestartreload— on Rotation«enumeration»PlaceholderKindsecretdependencyexposureidentity— on Placeholder«enumeration»Medianvmessdhdd— on DiskRequest«enumeration»Archamd64arm64— on Placement \ No newline at end of file From 4ccc2b73ba56238672227445aac514ede9f1ceed Mon Sep 17 00:00:00 2001 From: Joris Wouter Jonkers Date: Tue, 8 Sep 2026 20:40:25 +0200 Subject: [PATCH 2/4] docs(spec): bring the mermaid mirror back in line with the drawing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A verifier pass over the exported SVG against the chapter turned up four places where the text mirror had drifted, plus one where the drawing was wrong. Chapter precedence says the SVG is the diagram and the mermaid is what gets fixed, so this is mostly the mermaid. - `Override` authored `field`. An override names a derivation by its own name and never a Kubernetes field ([0097](docs/adr/model/0097-authored-values-name-model-concepts.md)), which is what the drawing and the prose already said. - `Grant` carried only the `kv` arm. It is a discriminated union on `engine` ([0085](docs/adr/model/0085-a-grant-is-a-union-on-engine.md)), so `engine`, `role`, `key` and `operations` join it. - `Sidecar` was marked proposed and held no quantities. It is graded ([0064](docs/adr/model/0064-sidecars-are-workload-vocabulary.md)) and a sidecar declares its own `memory` and `cpu`, because resources are container-level in the API this renders to. - The eighteen closed vocabularies were in the drawing's enumeration panel and in the field tables but not in the mermaid. They are generated from the same list the drawing uses, and the block was rendered with mermaid-cli to check it parses. The drawing's own error: `DiskRequest` listed `size` as an authored attribute. `placement.disk.size` is derived — the sum of the Workload's volume sizes — so showing it in a layer-1 diagram contradicts chapter 16's single-authority property. It is gone; `media` stays authored. --- spec/v1/10-service-intent.md | 127 +++++++++++++++++- .../10-service-intent-model.drawio.svg | 2 +- 2 files changed, 125 insertions(+), 4 deletions(-) diff --git a/spec/v1/10-service-intent.md b/spec/v1/10-service-intent.md index 0b99a9d..fea20a6 100644 --- a/spec/v1/10-service-intent.md +++ b/spec/v1/10-service-intent.md @@ -1845,9 +1845,10 @@ classDiagram +int port } class Sidecar { - <> +string name - +ImageRef image + +ImageAlias image + +Quantity memory + +Quantity cpu } class DependencyEdge { +ServiceId service @@ -1903,7 +1904,7 @@ classDiagram +Path path } class Override { - +string field + +Derivation derivation +any value +string reason } @@ -1918,9 +1919,13 @@ classDiagram } class Grant { + +SecretEngine engine +VaultPath path +string[] keys +AccessTier access + +string role + +string key + +TransitOp[] operations +Delivery delivery +Path mountAt +FileMode fileMode @@ -1930,6 +1935,122 @@ classDiagram +Duration maxAge } +%% Every closed vocabulary in layer 1. The authored form of a capability +%% control is capability:; the angle brackets are dropped here because +%% mermaid reads them as markup. + class AccessTier { + <> + read + self-renew + self-roll + custody + } + class AlertClass { + <> + none + business-hours + urgent + page + } + class Arch { + <> + amd64 + arm64 + } + class Audience { + <> + anonymous + authenticated + internal + lan + } + class ContentPolicy { + <> + strict + admin + workflow + } + class Control { + <> + runAsRoot + writableRootFilesystem + capability:NAME + seccompUnconfined + } + class Delivery { + <> + env + file + self + } + class DurabilityClass { + <> + reconstructible + recoverable + irreplaceable + } + class Engine { + <> + postgres + rabbitmq + valkey + files + } + class HardeningClass { + <> + restricted + } + class Lifecycle { + <> + service + job + } + class Match { + <> + prefix + exact + } + class Media { + <> + nvme + ssd + hdd + } + class PlaceholderKind { + <> + secret + dependency + exposure + identity + } + class Runtime { + <> + jvm + python + node + static + none + } + class SecretEngine { + <> + kv + database + transit + } + class Tolerance { + <> + restart + reload + } + class TransitOp { + <> + sign + verify + encrypt + decrypt + rotate + } + Domain "1" *-- "1..*" Service : services Service "1" *-- "1..*" Workload : workloads diff --git a/spec/v1/diagrams/10-service-intent-model.drawio.svg b/spec/v1/diagrams/10-service-intent-model.drawio.svg index 370ad90..b918ce1 100644 --- a/spec/v1/diagrams/10-service-intent-model.drawio.svg +++ b/spec/v1/diagrams/10-service-intent-model.drawio.svg @@ -1,4 +1,4 @@ -Enumerations — every closed vocabulary in layer 1Domain+ DomainName domain+ string owner+ SemVer schemaVersionService+ ServiceId id+ AlertClass alertClassGrant+ SecretEngine engine+ VaultPath path+ string[] keys+ AccessTier access+ string role+ string key+ TransitOp[] operations+ Delivery delivery+ Path mountAt+ FileMode fileModeWorkload+ string name+ Lifecycle lifecycle+ ImageAlias image+ Runtime runtime+ Engine engine+ Duration startupBudget+ bool zeroDowntime+ bool stateful+ Path[] writablePaths+ HardeningClass hardeningExposure+ ExposureName name+ Fqdn host+ Audience audience+ ContentPolicy contentPolicy0..* secrets — every Workload holds it1..* workloads0..* exposure1..* services0..* secrets — this Workload onlyRotation+ Tolerance tolerates+ Duration maxAge0..1 rotationRoute+ Path path+ Match match+ string workload+ string surface+ Audience audience+ Path redirectTo1..* routesSurface+ string name+ int port0..* providesSidecar+ string name+ ImageAlias image+ Quantity memory+ Quantity cpu0..* sidecarsDependencyEdge+ ServiceId service+ string surface+ bool required0..* dependsOnProbe+ Path path+ int port+ int tcp0..2 probesAsset+ Path from+ Path mountAt+ map substitute0..* assetsVolume+ ClaimName claim+ Path mountAt+ Quantity size+ DurabilityClass durability0..* volumesPlacement+ Quantity memory+ Quantity cpu+ Arch[] arch+ Site site+ Capability[] capabilities1 placementScrape+ int port+ Path path0..1 scrapeEnvFile+ ClusterTarget cluster+ dotenv entries1..* envOverride+ Derivation derivation+ any value+ string reason0..* overridesHardeningException+ Control allow+ string reason0..* exceptionsDiskRequest+ Media[] media+ Quantity sizeGpuRequest+ GpuClass class+ Quantity memoryPlaceholder+ PlaceholderKind kind+ string source0..1 disk0..1 gpu0..* resolvesnames a Surface of another Servicenames a Surface this Service providesa secret placeholder byte-matches a grant's derived read pathAn attribute's type names its enumeration. Values are exhaustive: a value absent from a list herefails schema validation.«enumeration»Lifecycleservicejob— on Workload«enumeration»Runtimejvmpythonnodestaticnone— on Workload«enumeration»Enginepostgresrabbitmqvalkeyfiles— on Workload«enumeration»HardeningClassrestricted— on Workload«enumeration»ControlrunAsRootwritableRootFilesystemcapability:<NAME>seccompUnconfined— on HardeningException«enumeration»DurabilityClassreconstructiblerecoverableirreplaceable— on Volume«enumeration»AlertClassnonebusiness-hoursurgentpage— on Service«enumeration»Audienceanonymousauthenticatedinternallan— on Exposure, Route«enumeration»ContentPolicystrictadminworkflow— on Exposure«enumeration»Matchprefixexact— on Route«enumeration»SecretEnginekvdatabasetransit— on Grant«enumeration»AccessTierreadself-renewself-rollcustody— on Grant — kv only«enumeration»TransitOpsignverifyencryptdecryptrotate— on Grant — transit only«enumeration»Deliveryenvfileself— on Grant«enumeration»Tolerancerestartreload— on Rotation«enumeration»PlaceholderKindsecretdependencyexposureidentity— on Placeholder«enumeration»Medianvmessdhdd— on DiskRequest«enumeration»Archamd64arm64— on Placement \ No newline at end of file +Enumerations — every closed vocabulary in layer 1Domain+ DomainName domain+ string owner+ SemVer schemaVersionService+ ServiceId id+ AlertClass alertClassGrant+ SecretEngine engine+ VaultPath path+ string[] keys+ AccessTier access+ string role+ string key+ TransitOp[] operations+ Delivery delivery+ Path mountAt+ FileMode fileModeWorkload+ string name+ Lifecycle lifecycle+ ImageAlias image+ Runtime runtime+ Engine engine+ Duration startupBudget+ bool zeroDowntime+ bool stateful+ Path[] writablePaths+ HardeningClass hardeningExposure+ ExposureName name+ Fqdn host+ Audience audience+ ContentPolicy contentPolicy0..* secrets — every Workload holds it1..* workloads0..* exposure1..* services0..* secrets — this Workload onlyRotation+ Tolerance tolerates+ Duration maxAge0..1 rotationRoute+ Path path+ Match match+ string workload+ string surface+ Audience audience+ Path redirectTo1..* routesSurface+ string name+ int port0..* providesSidecar+ string name+ ImageAlias image+ Quantity memory+ Quantity cpu0..* sidecarsDependencyEdge+ ServiceId service+ string surface+ bool required0..* dependsOnProbe+ Path path+ int port+ int tcp0..2 probesAsset+ Path from+ Path mountAt+ map substitute0..* assetsVolume+ ClaimName claim+ Path mountAt+ Quantity size+ DurabilityClass durability0..* volumesPlacement+ Quantity memory+ Quantity cpu+ Arch[] arch+ Site site+ Capability[] capabilities1 placementScrape+ int port+ Path path0..1 scrapeEnvFile+ ClusterTarget cluster+ dotenv entries1..* envOverride+ Derivation derivation+ any value+ string reason0..* overridesHardeningException+ Control allow+ string reason0..* exceptionsDiskRequest+ Media[] mediaGpuRequest+ GpuClass class+ Quantity memoryPlaceholder+ PlaceholderKind kind+ string source0..1 disk0..1 gpu0..* resolvesnames a Surface of another Servicenames a Surface this Service providesa secret placeholder byte-matches a grant's derived read pathAn attribute's type names its enumeration. Values are exhaustive: a value absent from a list herefails schema validation.«enumeration»Lifecycleservicejob— on Workload«enumeration»Runtimejvmpythonnodestaticnone— on Workload«enumeration»Enginepostgresrabbitmqvalkeyfiles— on Workload«enumeration»HardeningClassrestricted— on Workload«enumeration»ControlrunAsRootwritableRootFilesystemcapability:<NAME>seccompUnconfined— on HardeningException«enumeration»DurabilityClassreconstructiblerecoverableirreplaceable— on Volume«enumeration»AlertClassnonebusiness-hoursurgentpage— on Service«enumeration»Audienceanonymousauthenticatedinternallan— on Exposure, Route«enumeration»ContentPolicystrictadminworkflow— on Exposure«enumeration»Matchprefixexact— on Route«enumeration»SecretEnginekvdatabasetransit— on Grant«enumeration»AccessTierreadself-renewself-rollcustody— on Grant — kv only«enumeration»TransitOpsignverifyencryptdecryptrotate— on Grant — transit only«enumeration»Deliveryenvfileself— on Grant«enumeration»Tolerancerestartreload— on Rotation«enumeration»PlaceholderKindsecretdependencyexposureidentity— on Placeholder«enumeration»Medianvmessdhdd— on DiskRequest«enumeration»Archamd64arm64— on Placement \ No newline at end of file From ed78204ec971f76334d69d54ea0c162ba0994114 Mon Sep 17 00:00:00 2001 From: Joris Wouter Jonkers Date: Tue, 8 Sep 2026 20:52:54 +0200 Subject: [PATCH 3/4] docs(spec): specify what a sidecar carries, and draw it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 0064 graded sidecars and pointed `normative:` at `#workload`, but that section never said what a sidecar declares. The ADR carried the field list, which is backwards: an ADR justifies and the chapter states. `### Sidecars` now carries the table — `name`, `image`, `memory`, `cpu`, `hardening` — plus the three rules that were only in the ADR: - The split follows Kubernetes, not the model: `nodeSelector` and affinity are pod-level, so the node dimensions stay on the Workload and a sidecar declares no `placement`; `resources` and `securityContext` are container-level, so a sidecar declares its own quantities and hardening. - Nothing is inherited. A Workload's exception list does not reach its sidecars, because `postgres-exporter` meets `restricted` while `postgres` does not. - Eligibility sums: the placement check adds every sidecar's `memory` and `cpu` to the Workload's, so `postgres` at 2Gi with a 64Mi exporter needs 2112Mi free rather than 2Gi. It also records what a sidecar is not: no identity, no probes, no exposure, no release semantics of its own — so `provides` stays on the Workload even when the listener is the sidecar, which is the attribution the model could not state before the field existed. `hardening` and the `Sidecar *-- HardeningException` composition are in both drawings. HardeningException moves next to Sidecar in the child band, so its second parent is a short hop rather than an edge across eight columns. 0064's `normative:` pointer moves to the section that now specifies it. --- .../0064-sidecars-are-workload-vocabulary.md | 2 +- spec/v1/10-service-intent.md | 56 +++++++++++++++++++ .../10-service-intent-model.drawio.svg | 2 +- 3 files changed, 58 insertions(+), 2 deletions(-) diff --git a/docs/adr/model/0064-sidecars-are-workload-vocabulary.md b/docs/adr/model/0064-sidecars-are-workload-vocabulary.md index b396bf3..37b7cc8 100644 --- a/docs/adr/model/0064-sidecars-are-workload-vocabulary.md +++ b/docs/adr/model/0064-sidecars-are-workload-vocabulary.md @@ -3,7 +3,7 @@ tier: decision status: proposed claim: settled date: 2026-09-07 -normative: spec/v1/10-service-intent.md#workload +normative: spec/v1/10-service-intent.md#sidecars rests-on: ["0005"] --- diff --git a/spec/v1/10-service-intent.md b/spec/v1/10-service-intent.md index fea20a6..0e7f0ab 100644 --- a/spec/v1/10-service-intent.md +++ b/spec/v1/10-service-intent.md @@ -344,6 +344,60 @@ required. on; which hostname reaches it, and on what path, is stated once on the Service ([Exposure](#exposure)). +### Sidecars + +A Workload is one pod, and a pod holds more than one container three times in +this estate. `sidecars` names the others +([0064](../../docs/adr/model/0064-sidecars-are-workload-vocabulary.md)): + +```yaml +- name: postgres + image: postgres-17 + engine: postgres + provides: {postgres: 5432, metrics: 9187} # the exporter serves 9187 + placement: {memory: 2Gi, cpu: 500m} + sidecars: + - name: postgres-exporter + image: postgres-exporter + memory: 64Mi + cpu: 50m + hardening: {} # restricted, no exceptions +``` + +| field | required | shape | notes | +|---|---|---|---| +| `name` | yes | one value | The container's own name, unique among the Workload's containers — the Workload is one of them, so a sidecar may not take its name. A collision is refused at composition (chapter 40). | +| `image` | yes | an alias | Resolved to a digest through the images lock, exactly as a Workload's is. A tag would put a mutable reference in a Deliverable, which `E_FLOATING_IMAGE` (chapter 30) refuses. | +| `memory` | yes | one quantity | This container's request. Shape rules are the Workload's ([Placement](#placement)). | +| `cpu` | yes | one quantity | The same. | +| `hardening` | yes | `{exceptions: [...]}` | This container's own class and its own exception list, the same shape as the Workload's ([Pod hardening](#pod-hardening)). `{}` is `restricted` with no exceptions. | + +The split follows Kubernetes rather than a rule of the model's own: `nodeSelector` +and affinity are **pod**-level, `resources` and `securityContext` are +**container**-level. So the node dimensions — `arch`, `site`, `disk`, `gpu`, +`capabilities` — stay on the Workload and describe the pod, and a sidecar +declares neither them nor a `placement` block. `memory`, `cpu` and `hardening` +are per container, and a sidecar declares its own. + +Nothing is inherited. `postgres-exporter` meets `restricted` while `postgres` +does not, so a Workload's exception list does not reach its sidecars — pushing +one container's exception onto another would widen the estate's inventory of +what it cannot harden by containers that never needed it. + +**Eligibility sums.** A node must fit the pod's containers together, so the +placement check adds every sidecar's `memory` and `cpu` to the Workload's before +matching against allocatable +([0061](../../docs/adr/model/0061-placement-is-hard-dimensions.md)). `postgres` at +2Gi with a 64Mi exporter needs a node with 2112Mi free, not 2Gi. This is the one +place the addition matters and the one place it is easy to miss. + +A sidecar has no identity, no probes, no exposure and no release semantics of +its own: it is not independently deployable, which is what makes it a sidecar +rather than a Workload. `provides` therefore stays on the **Workload** even when +the listener is a sidecar — `platform-postgres` declares `metrics: 9187` and the +exporter is the container that serves it, which is exactly the attribution the +model could not state before this field existed. + ### Dependencies ```yaml @@ -1849,6 +1903,7 @@ classDiagram +ImageAlias image +Quantity memory +Quantity cpu + +HardeningClass hardening } class DependencyEdge { +ServiceId service @@ -2056,6 +2111,7 @@ classDiagram Workload "1" *-- "0..*" Surface : provides Workload "1" *-- "0..*" Sidecar : sidecars + Sidecar "1" *-- "0..*" HardeningException : hardening.exceptions Workload "1" *-- "0..*" HardeningException : hardening.exceptions Workload "1" *-- "0..*" DependencyEdge : dependsOn Workload "1" *-- "0..1" Probe : probes.readiness diff --git a/spec/v1/diagrams/10-service-intent-model.drawio.svg b/spec/v1/diagrams/10-service-intent-model.drawio.svg index b918ce1..38ca18f 100644 --- a/spec/v1/diagrams/10-service-intent-model.drawio.svg +++ b/spec/v1/diagrams/10-service-intent-model.drawio.svg @@ -1,4 +1,4 @@ -Enumerations — every closed vocabulary in layer 1Domain+ DomainName domain+ string owner+ SemVer schemaVersionService+ ServiceId id+ AlertClass alertClassGrant+ SecretEngine engine+ VaultPath path+ string[] keys+ AccessTier access+ string role+ string key+ TransitOp[] operations+ Delivery delivery+ Path mountAt+ FileMode fileModeWorkload+ string name+ Lifecycle lifecycle+ ImageAlias image+ Runtime runtime+ Engine engine+ Duration startupBudget+ bool zeroDowntime+ bool stateful+ Path[] writablePaths+ HardeningClass hardeningExposure+ ExposureName name+ Fqdn host+ Audience audience+ ContentPolicy contentPolicy0..* secrets — every Workload holds it1..* workloads0..* exposure1..* services0..* secrets — this Workload onlyRotation+ Tolerance tolerates+ Duration maxAge0..1 rotationRoute+ Path path+ Match match+ string workload+ string surface+ Audience audience+ Path redirectTo1..* routesSurface+ string name+ int port0..* providesSidecar+ string name+ ImageAlias image+ Quantity memory+ Quantity cpu0..* sidecarsDependencyEdge+ ServiceId service+ string surface+ bool required0..* dependsOnProbe+ Path path+ int port+ int tcp0..2 probesAsset+ Path from+ Path mountAt+ map substitute0..* assetsVolume+ ClaimName claim+ Path mountAt+ Quantity size+ DurabilityClass durability0..* volumesPlacement+ Quantity memory+ Quantity cpu+ Arch[] arch+ Site site+ Capability[] capabilities1 placementScrape+ int port+ Path path0..1 scrapeEnvFile+ ClusterTarget cluster+ dotenv entries1..* envOverride+ Derivation derivation+ any value+ string reason0..* overridesHardeningException+ Control allow+ string reason0..* exceptionsDiskRequest+ Media[] mediaGpuRequest+ GpuClass class+ Quantity memoryPlaceholder+ PlaceholderKind kind+ string source0..1 disk0..1 gpu0..* resolvesnames a Surface of another Servicenames a Surface this Service providesa secret placeholder byte-matches a grant's derived read pathAn attribute's type names its enumeration. Values are exhaustive: a value absent from a list herefails schema validation.«enumeration»Lifecycleservicejob— on Workload«enumeration»Runtimejvmpythonnodestaticnone— on Workload«enumeration»Enginepostgresrabbitmqvalkeyfiles— on Workload«enumeration»HardeningClassrestricted— on Workload«enumeration»ControlrunAsRootwritableRootFilesystemcapability:<NAME>seccompUnconfined— on HardeningException«enumeration»DurabilityClassreconstructiblerecoverableirreplaceable— on Volume«enumeration»AlertClassnonebusiness-hoursurgentpage— on Service«enumeration»Audienceanonymousauthenticatedinternallan— on Exposure, Route«enumeration»ContentPolicystrictadminworkflow— on Exposure«enumeration»Matchprefixexact— on Route«enumeration»SecretEnginekvdatabasetransit— on Grant«enumeration»AccessTierreadself-renewself-rollcustody— on Grant — kv only«enumeration»TransitOpsignverifyencryptdecryptrotate— on Grant — transit only«enumeration»Deliveryenvfileself— on Grant«enumeration»Tolerancerestartreload— on Rotation«enumeration»PlaceholderKindsecretdependencyexposureidentity— on Placeholder«enumeration»Medianvmessdhdd— on DiskRequest«enumeration»Archamd64arm64— on Placement \ No newline at end of file +Enumerations — every closed vocabulary in layer 1Domain+ DomainName domain+ string owner+ SemVer schemaVersionService+ ServiceId id+ AlertClass alertClassGrant+ SecretEngine engine+ VaultPath path+ string[] keys+ AccessTier access+ string role+ string key+ TransitOp[] operations+ Delivery delivery+ Path mountAt+ FileMode fileModeWorkload+ string name+ Lifecycle lifecycle+ ImageAlias image+ Runtime runtime+ Engine engine+ Duration startupBudget+ bool zeroDowntime+ bool stateful+ Path[] writablePaths+ HardeningClass hardeningExposure+ ExposureName name+ Fqdn host+ Audience audience+ ContentPolicy contentPolicy0..* secrets — every Workload holds it1..* workloads0..* exposure1..* services0..* secrets — this Workload onlyRotation+ Tolerance tolerates+ Duration maxAge0..1 rotationRoute+ Path path+ Match match+ string workload+ string surface+ Audience audience+ Path redirectTo1..* routesSurface+ string name+ int port0..* providesSidecar+ string name+ ImageAlias image+ Quantity memory+ Quantity cpu+ HardeningClass hardening0..* sidecarsHardeningException+ Control allow+ string reason0..* exceptionsDependencyEdge+ ServiceId service+ string surface+ bool required0..* dependsOnProbe+ Path path+ int port+ int tcp0..2 probesAsset+ Path from+ Path mountAt+ map substitute0..* assetsVolume+ ClaimName claim+ Path mountAt+ Quantity size+ DurabilityClass durability0..* volumesPlacement+ Quantity memory+ Quantity cpu+ Arch[] arch+ Site site+ Capability[] capabilities1 placementScrape+ int port+ Path path0..1 scrapeEnvFile+ ClusterTarget cluster+ dotenv entries1..* envOverride+ Derivation derivation+ any value+ string reason0..* overridesDiskRequest+ Media[] mediaGpuRequest+ GpuClass class+ Quantity memoryPlaceholder+ PlaceholderKind kind+ string source0..1 disk0..1 gpu0..* resolves0..* exceptionsnames a Surface of another Servicenames a Surface this Service providesa secret placeholder byte-matches a grant's derived read pathAn attribute's type names its enumeration. Values are exhaustive: a value absent from a list herefails schema validation.«enumeration»Lifecycleservicejob— on Workload«enumeration»Runtimejvmpythonnodestaticnone— on Workload«enumeration»Enginepostgresrabbitmqvalkeyfiles— on Workload«enumeration»HardeningClassrestricted— on Workload«enumeration»ControlrunAsRootwritableRootFilesystemcapability:<NAME>seccompUnconfined— on HardeningException«enumeration»DurabilityClassreconstructiblerecoverableirreplaceable— on Volume«enumeration»AlertClassnonebusiness-hoursurgentpage— on Service«enumeration»Audienceanonymousauthenticatedinternallan— on Exposure, Route«enumeration»ContentPolicystrictadminworkflow— on Exposure«enumeration»Matchprefixexact— on Route«enumeration»SecretEnginekvdatabasetransit— on Grant«enumeration»AccessTierreadself-renewself-rollcustody— on Grant — kv only«enumeration»TransitOpsignverifyencryptdecryptrotate— on Grant — transit only«enumeration»Deliveryenvfileself— on Grant«enumeration»Tolerancerestartreload— on Rotation«enumeration»PlaceholderKindsecretdependencyexposureidentity— on Placeholder«enumeration»Medianvmessdhdd— on DiskRequest«enumeration»Archamd64arm64— on Placement \ No newline at end of file From 3f7f2751235ef8ee2b617c2c8394121ceaabf511 Mon Sep 17 00:00:00 2001 From: Joris Wouter Jonkers Date: Wed, 9 Sep 2026 06:46:42 +0200 Subject: [PATCH 4/4] docs(diagrams): a relation carries a role name, not a sentence MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The two `secrets` compositions were labelled "every Workload holds it" and "this Workload only", which is chapter prose sitting on a connector: a class diagram states the role name and the multiplicity, and which level a given secret belongs at is a rule the Secrets section already carries. Both now read `0..* secrets`, and the two relations are told apart by their ends, which is what UML does. The three dependencies get stereotypes for the same reason — «resolves by name» and «byte-matches» rather than a clause each. The mermaid mirror follows. --- spec/v1/10-service-intent.md | 10 +++++----- spec/v1/diagrams/10-service-intent-model.drawio.svg | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/spec/v1/10-service-intent.md b/spec/v1/10-service-intent.md index 0e7f0ab..0394de4 100644 --- a/spec/v1/10-service-intent.md +++ b/spec/v1/10-service-intent.md @@ -2127,15 +2127,15 @@ classDiagram Service "1" *-- "0..*" Exposure : exposure Exposure "1" *-- "1..*" Route : routes - Route ..> Surface : names a Surface a Workload of this Service provides - DependencyEdge ..> Surface : names a Surface of another Service + Route ..> Surface : resolves by name + DependencyEdge ..> Surface : resolves by name Workload "1" *-- "1..*" EnvFile : env per workload EnvFile "1" *-- "0..*" Placeholder : resolves - Service "1" *-- "0..*" Grant : secrets (shared) - Workload "1" *-- "0..*" Grant : secrets (workload-specific) + Service "1" *-- "0..*" Grant : secrets + Workload "1" *-- "0..*" Grant : secrets Grant "1" *-- "0..1" Rotation : rotation - Placeholder ..> Grant : a secret placeholder byte-matches a granted path + Placeholder ..> Grant : byte-matches Placeholder ..> Exposure : an exposure placeholder addresses service.name ``` diff --git a/spec/v1/diagrams/10-service-intent-model.drawio.svg b/spec/v1/diagrams/10-service-intent-model.drawio.svg index 38ca18f..8db08af 100644 --- a/spec/v1/diagrams/10-service-intent-model.drawio.svg +++ b/spec/v1/diagrams/10-service-intent-model.drawio.svg @@ -1,4 +1,4 @@ -Enumerations — every closed vocabulary in layer 1Domain+ DomainName domain+ string owner+ SemVer schemaVersionService+ ServiceId id+ AlertClass alertClassGrant+ SecretEngine engine+ VaultPath path+ string[] keys+ AccessTier access+ string role+ string key+ TransitOp[] operations+ Delivery delivery+ Path mountAt+ FileMode fileModeWorkload+ string name+ Lifecycle lifecycle+ ImageAlias image+ Runtime runtime+ Engine engine+ Duration startupBudget+ bool zeroDowntime+ bool stateful+ Path[] writablePaths+ HardeningClass hardeningExposure+ ExposureName name+ Fqdn host+ Audience audience+ ContentPolicy contentPolicy0..* secrets — every Workload holds it1..* workloads0..* exposure1..* services0..* secrets — this Workload onlyRotation+ Tolerance tolerates+ Duration maxAge0..1 rotationRoute+ Path path+ Match match+ string workload+ string surface+ Audience audience+ Path redirectTo1..* routesSurface+ string name+ int port0..* providesSidecar+ string name+ ImageAlias image+ Quantity memory+ Quantity cpu+ HardeningClass hardening0..* sidecarsHardeningException+ Control allow+ string reason0..* exceptionsDependencyEdge+ ServiceId service+ string surface+ bool required0..* dependsOnProbe+ Path path+ int port+ int tcp0..2 probesAsset+ Path from+ Path mountAt+ map substitute0..* assetsVolume+ ClaimName claim+ Path mountAt+ Quantity size+ DurabilityClass durability0..* volumesPlacement+ Quantity memory+ Quantity cpu+ Arch[] arch+ Site site+ Capability[] capabilities1 placementScrape+ int port+ Path path0..1 scrapeEnvFile+ ClusterTarget cluster+ dotenv entries1..* envOverride+ Derivation derivation+ any value+ string reason0..* overridesDiskRequest+ Media[] mediaGpuRequest+ GpuClass class+ Quantity memoryPlaceholder+ PlaceholderKind kind+ string source0..1 disk0..1 gpu0..* resolves0..* exceptionsnames a Surface of another Servicenames a Surface this Service providesa secret placeholder byte-matches a grant's derived read pathAn attribute's type names its enumeration. Values are exhaustive: a value absent from a list herefails schema validation.«enumeration»Lifecycleservicejob— on Workload«enumeration»Runtimejvmpythonnodestaticnone— on Workload«enumeration»Enginepostgresrabbitmqvalkeyfiles— on Workload«enumeration»HardeningClassrestricted— on Workload«enumeration»ControlrunAsRootwritableRootFilesystemcapability:<NAME>seccompUnconfined— on HardeningException«enumeration»DurabilityClassreconstructiblerecoverableirreplaceable— on Volume«enumeration»AlertClassnonebusiness-hoursurgentpage— on Service«enumeration»Audienceanonymousauthenticatedinternallan— on Exposure, Route«enumeration»ContentPolicystrictadminworkflow— on Exposure«enumeration»Matchprefixexact— on Route«enumeration»SecretEnginekvdatabasetransit— on Grant«enumeration»AccessTierreadself-renewself-rollcustody— on Grant — kv only«enumeration»TransitOpsignverifyencryptdecryptrotate— on Grant — transit only«enumeration»Deliveryenvfileself— on Grant«enumeration»Tolerancerestartreload— on Rotation«enumeration»PlaceholderKindsecretdependencyexposureidentity— on Placeholder«enumeration»Medianvmessdhdd— on DiskRequest«enumeration»Archamd64arm64— on Placement \ No newline at end of file +Enumerations — every closed vocabulary in layer 1Domain+ DomainName domain+ string owner+ SemVer schemaVersionService+ ServiceId id+ AlertClass alertClassGrant+ SecretEngine engine+ VaultPath path+ string[] keys+ AccessTier access+ string role+ string key+ TransitOp[] operations+ Delivery delivery+ Path mountAt+ FileMode fileModeWorkload+ string name+ Lifecycle lifecycle+ ImageAlias image+ Runtime runtime+ Engine engine+ Duration startupBudget+ bool zeroDowntime+ bool stateful+ Path[] writablePaths+ HardeningClass hardeningExposure+ ExposureName name+ Fqdn host+ Audience audience+ ContentPolicy contentPolicy0..* secrets1..* workloads0..* exposure1..* services0..* secretsRotation+ Tolerance tolerates+ Duration maxAge0..1 rotationRoute+ Path path+ Match match+ string workload+ string surface+ Audience audience+ Path redirectTo1..* routesSurface+ string name+ int port0..* providesSidecar+ string name+ ImageAlias image+ Quantity memory+ Quantity cpu+ HardeningClass hardening0..* sidecarsHardeningException+ Control allow+ string reason0..* exceptionsDependencyEdge+ ServiceId service+ string surface+ bool required0..* dependsOnProbe+ Path path+ int port+ int tcp0..2 probesAsset+ Path from+ Path mountAt+ map substitute0..* assetsVolume+ ClaimName claim+ Path mountAt+ Quantity size+ DurabilityClass durability0..* volumesPlacement+ Quantity memory+ Quantity cpu+ Arch[] arch+ Site site+ Capability[] capabilities1 placementScrape+ int port+ Path path0..1 scrapeEnvFile+ ClusterTarget cluster+ dotenv entries1..* envOverride+ Derivation derivation+ any value+ string reason0..* overridesDiskRequest+ Media[] mediaGpuRequest+ GpuClass class+ Quantity memoryPlaceholder+ PlaceholderKind kind+ string source0..1 disk0..1 gpu0..* resolves0..* exceptions«resolves by name»«resolves by name»«byte-matches»An attribute's type names its enumeration. Values are exhaustive: a value absent from a list herefails schema validation.«enumeration»Lifecycleservicejob— on Workload«enumeration»Runtimejvmpythonnodestaticnone— on Workload«enumeration»Enginepostgresrabbitmqvalkeyfiles— on Workload«enumeration»HardeningClassrestricted— on Workload«enumeration»ControlrunAsRootwritableRootFilesystemcapability:<NAME>seccompUnconfined— on HardeningException«enumeration»DurabilityClassreconstructiblerecoverableirreplaceable— on Volume«enumeration»AlertClassnonebusiness-hoursurgentpage— on Service«enumeration»Audienceanonymousauthenticatedinternallan— on Exposure, Route«enumeration»ContentPolicystrictadminworkflow— on Exposure«enumeration»Matchprefixexact— on Route«enumeration»SecretEnginekvdatabasetransit— on Grant«enumeration»AccessTierreadself-renewself-rollcustody— on Grant — kv only«enumeration»TransitOpsignverifyencryptdecryptrotate— on Grant — transit only«enumeration»Deliveryenvfileself— on Grant«enumeration»Tolerancerestartreload— on Rotation«enumeration»PlaceholderKindsecretdependencyexposureidentity— on Placeholder«enumeration»Medianvmessdhdd— on DiskRequest«enumeration»Archamd64arm64— on Placement \ No newline at end of file