From 9e5f1efb6e9d36ae471fa4af3ba3e0a5da55d9b5 Mon Sep 17 00:00:00 2001 From: Joris Wouter Jonkers Date: Tue, 15 Sep 2026 13:15:46 +0200 Subject: [PATCH 1/4] docs: fix the platform example README's leftover Traefik Application and missing hardening row The tiers table cell still read "Application" where #100's rename should have left the Kubernetes Service alone; #107 restored other external spellings but missed this one. Also add the row for platform.intent.yml's hardening block, which the table never carried: one posture for every container, no per-control exception surface, decided by 0016. --- spec/v1/examples/platform/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spec/v1/examples/platform/README.md b/spec/v1/examples/platform/README.md index ebea3fd..ae84153 100644 --- a/spec/v1/examples/platform/README.md +++ b/spec/v1/examples/platform/README.md @@ -16,10 +16,11 @@ nothing in it names a Kubernetes field, a Traefik key, a k3s flag or a command |---|---|---| | `substrate` | cluster facts that decide other decisions, named for what they are | [0057](../../../../docs/adr/model/0057-datastore-and-restore.md), [0028](../../../../docs/adr/model/0028-secrets-at-rest-gate.md) | | `bootstrap` | what must exist before the first rendered object can apply | [0099](../../../../docs/adr/model/0099-bootstrap-set-is-recorded.md) | -| `tiers` | the shared edge is finite; four edge facts, and the Traefik Application each tier is | [0076](../../../../docs/adr/model/0076-middleware-has-one-producer.md), [0097](../../../../docs/adr/model/0097-authored-values-name-model-concepts.md) | +| `tiers` | the shared edge is finite; four edge facts, and the Traefik Service each tier is | [0076](../../../../docs/adr/model/0076-middleware-has-one-producer.md), [0097](../../../../docs/adr/model/0097-authored-values-name-model-concepts.md) | | `durability` | a backup window is one node's IO, a destination one remote target | [0077](../../../../docs/adr/model/0077-durability-derives-a-backup.md) | | `engines` | the method is an image; nothing authored is executable | [0097](../../../../docs/adr/model/0097-authored-values-name-model-concepts.md) | | `monitors` | one scrape cadence for the estate, because ingest is shared and no Application knows better. It is the whole observability surface of this document: no receiver map, no severity mapping, no rule catalog ([chapter 14](../../14-platform-intent.md#monitor-cadence)) | [0021](../../../../docs/adr/model/0021-observability-scrape-and-alert-class.md), [0079](../../../../docs/adr/model/0079-alert-class-derives-from-a-rule-catalog.md) | +| `hardening` | one posture for every container the estate renders: thirty declarations of the only legal value are thirty copies of one decision. A Process authors no hardening at all, and there is no per-control exception surface to author, because a relaxation carried with a reason is an override under another name ([chapter 14](../../14-platform-intent.md#hardening-policy)) | [0016](../../../../docs/adr/model/0016-pod-hardening.md) | | `probes`, `ephemeral` | one cadence and one size for the estate | [0088](../../../../docs/adr/model/0088-startup-probe-targets-liveness.md), [0092](../../../../docs/adr/model/0092-writable-paths-are-declared.md) | | `providers` | something the estate reaches and does not deploy: a fact, not a hole | [0090](../../../../docs/adr/model/0090-edges-resolve-against-the-register.md), [0095](../../../../docs/adr/model/0095-platform-intent-is-the-second-authored-document.md) | From d9f941831e0decb3704c3f4d89bdae890e9119df Mon Sep 17 00:00:00 2001 From: Joris Wouter Jonkers Date: Tue, 15 Sep 2026 13:15:53 +0200 Subject: [PATCH 2/4] docs: stop the data example's rendered README from writing alertClass: none Chapter 10 is explicit that Alert Class has no none member: an omitted observability block already says "no monitoring", and a member that means "I wrote the field to say I did not want the field" is ceremony. valkey's row said the Application "declares alertClass: none", which is not a value this schema accepts; it declares no observability block at all. --- spec/v1/examples/data/rendered/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/v1/examples/data/rendered/README.md b/spec/v1/examples/data/rendered/README.md index 3637c0d..ca01b4e 100644 --- a/spec/v1/examples/data/rendered/README.md +++ b/spec/v1/examples/data/rendered/README.md @@ -114,7 +114,7 @@ contributes routes and exposures to both; it owns neither object. | `hpa.yaml` | The registered `kubernetes` adapter can emit one; nothing in layer 1 declares autoscaling | | `vso.yaml` for rabbitmq and valkey | Neither declares `secrets` at either level. Their clients hold the credentials, which is what a provider looks like | | `configmap.yaml` for rabbitmq and valkey | No `assets`, and their env files are not in this example set ([G-28](#g-28)) | -| `servicemonitor.yaml` for valkey | No `scrape`. No exporter runs beside it, so there is no metrics surface and none is invented, which is why the Application declares `alertClass: none` rather than a class it could not signal | +| `servicemonitor.yaml` for valkey | No `scrape`. No exporter runs beside it, so there is no metrics surface and none is invented, which is why the Application declares no `observability` block at all rather than a class it could not signal | | a backup job for `valkey-data` | `durability: reconstructible` derives none. **The one absence in this project that is a decision rather than a hole** | | `PrometheusRule`, anywhere | Not the model's to render. `alertClass` is published as a resolved fact and the monitoring stack that owns PromQL, severity and receivers reads it from the projection ([chapter 10](../../../10-project-intent.md#observability)) | | `Role` / `RoleBinding` | No `rbac` adapter ([G-35](#g-35)) | From a623b102a75adc78e5d540bec66eefe4399ec1f7 Mon Sep 17 00:00:00 2001 From: Joris Wouter Jonkers Date: Tue, 15 Sep 2026 13:16:01 +0200 Subject: [PATCH 3/4] docs: make the negative fixtures' missing .project.yml suffix a stated convention spec/v1/examples/negative/*/intent*/ fixtures stand in for Intent Fragments already handed to composition, not files in a repository's platform/ tree, and renaming them to .project.yml would pull them into three tests that discover project files by that suffix, at least one of them (duplicate-process-name) failing where those tests expect every file to parse clean. Renaming is left for its own change; this commit instead states the convention in chapter 10 so the suffix difference reads as a rule rather than an oversight, and none of the three tests' selections move: 14 .project.yml files for the diagram test, 4 cases for the Project Intent test, and (by inspection of its source, without running Maven) 4 files for the EMF worked-examples test, which walks the same suffix excluding refusals/. --- spec/v1/10-project-intent.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/spec/v1/10-project-intent.md b/spec/v1/10-project-intent.md index 29b0cf1..1ed99b8 100644 --- a/spec/v1/10-project-intent.md +++ b/spec/v1/10-project-intent.md @@ -55,6 +55,20 @@ repository holding three Applications rather than three repositories with three publish workflows) and a project never spans repositories, so composition unions fragments and never has to union a project (chapter 40). +**A composition fixture that stands in for an already-published fragment does +not carry `.project.yml`.** `spec/v1/examples/negative/*/intent*/` holds +documents fed directly to composition to exercise an invariant at the union +(chapter 40), never rendered and never read out of a `platform/` tree. The +suffix above names a file a publish step reads from a project's own +repository; putting it on a fixture that is published by nothing and has no +`platform/` tree to sit in would claim a layout that does not exist. The +difference is deliberate: a directory under `spec/v1/examples//` is a +worked project meant to render, and one under `spec/v1/examples/negative/` is +meant to make composition fail, at least one of them +(`duplicate-process-name`) before a union of two fragments ever runs. A test +that discovers project files by the `.project.yml` suffix is meant to find +only the former, and must keep finding only the former. + The split that matters is not file-level but concern-level. A secret's **access** is declared in the project file, beside the `dependsOn` edge that motivates it; the **environment variable** that carries it is a placeholder in the env file. Each From 0a5c6b66d19b205d3c4899f79a884d2c1288a070 Mon Sep 17 00:00:00 2001 From: Joris Wouter Jonkers Date: Tue, 15 Sep 2026 13:21:14 +0200 Subject: [PATCH 4/4] docs: finish the hierarchy rename in chapter 10's selector sentence The label-set section's hypothetical alternate labelling reads "would make every Process of a multi-Process Service selector-ambiguous". Before #100 this said "multi-Workload Service"; the rename converted Workload to Process but left this one "Service" unconverted, where it names what the estate now calls an Application (compare "a two-Process Application" in chapter 30) rather than a Kubernetes Service, which the surrounding paragraph never mentions: it is about a Deployment's pod selector. It now reads "multi-Process Application". Re-grepped spec/v1/*.md for Service used as the retired hierarchy word: the three remaining occurrences (10-project-intent.md's IngressRoute paragraph, 30-deliverables.md's adapter table, 16-dependencies.md's diagram node) are all the Kubernetes object and are left alone. --- spec/v1/10-project-intent.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/v1/10-project-intent.md b/spec/v1/10-project-intent.md index 1ed99b8..4f50bb3 100644 --- a/spec/v1/10-project-intent.md +++ b/spec/v1/10-project-intent.md @@ -268,7 +268,7 @@ the others. `name` and `instance` are both the Process name rather than one naming the Application, because the selector must match exactly one controller's pods. A `name` of the Application and an `instance` of the Process would read better and -would make every Process of a multi-Process Service selector-ambiguous the +would make every Process of a multi-Process Application selector-ambiguous the moment anything selected on `name` alone. No `app.kubernetes.io/version`. A version label would have to come from the