From caa6cb9d5c3a6379da89cd250e437acb33e9edc5 Mon Sep 17 00:00:00 2001 From: Joris Wouter Jonkers Date: Mon, 14 Sep 2026 18:27:16 +0200 Subject: [PATCH] fix: restore the external names the hierarchy rename rewrote --- docs/adr/deferred/examples/reapply-cronjob.yaml | 2 +- .../adr/model/0004-contention-decides-authority.md | 2 +- .../0011-configuration-env-files-per-process.md | 4 ++-- docs/adr/model/0014-probes-are-siblings.md | 2 +- docs/adr/model/0016-pod-hardening.md | 2 +- .../model/0022-grants-live-on-the-application.md | 4 ++-- docs/adr/model/0024-identity-per-process.md | 6 +++--- docs/adr/model/0030-runtime-mechanics-derived.md | 2 +- docs/adr/model/0053-adapter-port-contract.md | 4 ++-- docs/adr/model/0054-adapter-attribution.md | 2 +- .../model/0073-vault-policy-is-a-deliverable.md | 2 +- .../0083-privileged-port-needs-the-capability.md | 6 +++--- ...1-identity-placeholders-not-framework-wiring.md | 6 +++--- docs/adr/model/0098-one-publication-path.md | 2 +- spec/v1/10-project-intent.md | 14 +++++++------- spec/v1/20-resolved-deployment.md | 6 +++--- spec/v1/30-deliverables.md | 2 +- spec/v1/examples/auth/auth.project.yml | 2 +- spec/v1/examples/auth/rendered/README.md | 4 ++-- spec/v1/examples/data/rendered/README.md | 2 +- spec/v1/examples/knowledge/rendered/README.md | 4 ++-- spec/v1/examples/minimal/notes.project.yml | 2 +- 22 files changed, 41 insertions(+), 41 deletions(-) diff --git a/docs/adr/deferred/examples/reapply-cronjob.yaml b/docs/adr/deferred/examples/reapply-cronjob.yaml index b0f4935..75d3ab0 100644 --- a/docs/adr/deferred/examples/reapply-cronjob.yaml +++ b/docs/adr/deferred/examples/reapply-cronjob.yaml @@ -28,7 +28,7 @@ spec: restartPolicy: OnFailure # The same identity as the merge apply, so the re-apply cannot touch # anything the merge apply could not. - applicationAccountName: deployer-auth-federation + serviceAccountName: deployer-auth-federation containers: - name: reapply image: ghcr.io/jorisjonkers-dev/deploy-config-schema:1.0.0 diff --git a/docs/adr/model/0004-contention-decides-authority.md b/docs/adr/model/0004-contention-decides-authority.md index a9488e5..cb2a124 100644 --- a/docs/adr/model/0004-contention-decides-authority.md +++ b/docs/adr/model/0004-contention-decides-authority.md @@ -33,7 +33,7 @@ the change that introduced them was being made. One hostname, three repositories: `homelab-inventory/catalog/reachability.yml`, three `fleet-infra` edge and knowledge manifests, a bearer-token secret, and the application's own `platform/deployment.yml`, plus hardcoded in -`ApplicationPermission.kt`. Two conformance tests exist for no purpose other than +`ServicePermission.kt`. Two conformance tests exist for no purpose other than detecting when those seven disagree. The guard was cheaper to write than the fix, which is how the estate arrived here. diff --git a/docs/adr/model/0011-configuration-env-files-per-process.md b/docs/adr/model/0011-configuration-env-files-per-process.md index 04daa76..12cea32 100644 --- a/docs/adr/model/0011-configuration-env-files-per-process.md +++ b/docs/adr/model/0011-configuration-env-files-per-process.md @@ -60,7 +60,7 @@ three rightful owners: **app knobs** (`SPRING_PROFILES_ACTIVE`, `KNOWLEDGE_MODE=lite`, uncontended, application-owned literals), **dependency coordinates** (`DB_HOST`, `DB_PORT`, `RABBITMQ_HOST`, entirely derivable from `dependsOn`), and **runtime boilerplate** (ten `OTEL_*` variables byte-identical -across `auth-api`, `agents-api` and `knowledge-api` except `OTEL_APPLICATION_NAME`; +across `auth-api`, `agents-api` and `knowledge-api` except `OTEL_SERVICE_NAME`; `knowledge-ingest-worker`, being Python, carries a different but equally fixed set, two Runtime Profiles, one derived value, sixty duplicated lines). @@ -89,7 +89,7 @@ env file. | option | cost if taken | why rejected | |---|---|---| | Per-Application env files (the old ADRs' scoping) | Processes share an environment they do not have (`knowledge-api` and `knowledge-ingest-worker` overlap on RabbitMQ coordinates and nothing else) and the Process-level joins catching dead grants and unauthorised secret references lose their subject | Finding X4: spec and examples were already per Process; the joins are the only checks between a `secrets` list and an unauthorised read | -| Typed source-declaring map in `application.yml` | A new schema for what dotenv already expresses; a format the estate has zero instances of | The estate's only real config-bearing `.env` file is already dotenv | +| Typed source-declaring map in `service.yml` | A new schema for what dotenv already expresses; a format the estate has zero instances of | The estate's only real config-bearing `.env` file is already dotenv | | Defaulted-but-overridable derived values | Every override must be audited against staleness by hand | A permitted override is indistinguishable from a stale copy | | General template language in env files | Configuration becomes a program; values stop being statically derivable and diffable | Placeholders are named-source references, nothing else | diff --git a/docs/adr/model/0014-probes-are-siblings.md b/docs/adr/model/0014-probes-are-siblings.md index ba8f1fc..a12072e 100644 --- a/docs/adr/model/0014-probes-are-siblings.md +++ b/docs/adr/model/0014-probes-are-siblings.md @@ -31,7 +31,7 @@ window, and read falls back to the other. The endpoints are the part only the owning Application knows ([0005](0005-derivation-is-total.md)); timings, thresholds and deadlines stay derived ([0030](0030-runtime-mechanics-derived.md)). The v2 model instead -made liveness a fallback (`src/adapters/kubernetes-process-fragment.ts:166` +made liveness a fallback (`src/adapters/kubernetes-workload-fragment.ts:166` renders `livenessProbe: probe(health.livenessPath ?? health.path)`), and two live processes rely on it: `app-ui` declares only `/`, `agents-login` only `/healthz`. For both, liveness silently probes the readiness endpoint. diff --git a/docs/adr/model/0016-pod-hardening.md b/docs/adr/model/0016-pod-hardening.md index f84eeec..6c04948 100644 --- a/docs/adr/model/0016-pod-hardening.md +++ b/docs/adr/model/0016-pod-hardening.md @@ -59,7 +59,7 @@ The vocabulary does not exist and neither renderer emits the fields. src/ schemas/` returns **0 hits** (verified 2026-08-31; the grep spans *both* renderer generations, so choosing one under [0052](0052-registered-adapters-are-v1.md) does not rescue it). -`src/deployment/render/processes.ts:130` builds a container from name, image, +`src/deployment/render/workloads.ts:130` builds a container from name, image, pullPolicy, ports, command, args, env, envFrom, volumeMounts, probes and resources, and stops. Rendered pods run as their image's UID, with a writable root filesystem and the default capability set. diff --git a/docs/adr/model/0022-grants-live-on-the-application.md b/docs/adr/model/0022-grants-live-on-the-application.md index 41c980b..b9ef225 100644 --- a/docs/adr/model/0022-grants-live-on-the-application.md +++ b/docs/adr/model/0022-grants-live-on-the-application.md @@ -31,7 +31,7 @@ identity presenting it to the store is per Process ([0024](0024-identity-per-process.md)), so the level a grant is written at is the level the store enforces. False if: two Processes of one Application authenticate as the same principal. Settled by: render a two-Process Application -into the lab cluster, confirm `.spec.applicationAccountName` differs between the two +into the lab cluster, confirm `.spec.serviceAccountName` differs between the two pods, then exchange each ServiceAccount token for a store token and `vault kv get` the *sibling's* path, the decision falls if that read succeeds. This claim inherits [0009](0009-vault-read-is-per-path.md) only through @@ -62,7 +62,7 @@ split is already achieved by the env-file placeholder beside the `dependsOn` edges that motivate them, where a reviewer looks. The two levels are an access boundary **only** because identity is per Process. -At review time they were not one: `applicationAccountName()` in +At review time they were not one: `serviceAccountName()` in `src/adapters/kubernetes.ts:665-669` returns `applicationName` for any Application holding a non-Kubernetes secret, and the previous `16-dependencies.md` derived the account from `id` to match. Two Processes of one Application therefore diff --git a/docs/adr/model/0024-identity-per-process.md b/docs/adr/model/0024-identity-per-process.md index 6268ddc..d16cadb 100644 --- a/docs/adr/model/0024-identity-per-process.md +++ b/docs/adr/model/0024-identity-per-process.md @@ -30,7 +30,7 @@ a role can bind below the ServiceAccount (to a Pod name, label or controller) and give two Pods of one ServiceAccount different policies. Settled by: `vault read auth/kubernetes/role/` and the parameters its create path accepts on the pinned Vault version, the claim falls if any binding parameter selects finer -than `bound_application_account_names` × `bound_application_account_namespaces`; then the +than `bound_service_account_names` × `bound_service_account_namespaces`; then the review's tiebreaker, rendering the two-Process `knowledge` example and counting ServiceAccounts and Vault roles, which must be two of each. @@ -41,7 +41,7 @@ boundary. The old credential-provisioning record let a grant sit on the Applicat (*"every Process receives them"*) or on a Process (*"only it does"*), while `spec/v1/16-dependencies.md:104` and `:122` derive the ServiceAccount from `id`, an Application field, drawing `d_id --> k_sa`. The implementation agrees: -`src/adapters/kubernetes.ts:665-669`, `applicationAccountName`, returns +`src/adapters/kubernetes.ts:665-669`, `serviceAccountName`, returns `applicationName`, one ServiceAccount per Application. Two Processes of one Application therefore authenticated as the same Vault principal and received the union of both policies regardless of which level the grant was declared at. Verifying the @@ -81,7 +81,7 @@ enforced where a reader can check it. ## Reversibility -Undo cost today: one adapter function (`applicationAccountName`, +Undo cost today: one adapter function (`serviceAccountName`, `src/adapters/kubernetes.ts:665-669`), the Vault role and policy derivation beside it, and `## Process identity` in `../../spec/v1/16-dependencies.md`, hours, blast radius is object count, not authoring. Becomes irreversible once: diff --git a/docs/adr/model/0030-runtime-mechanics-derived.md b/docs/adr/model/0030-runtime-mechanics-derived.md index 03d778e..d61a77a 100644 --- a/docs/adr/model/0030-runtime-mechanics-derived.md +++ b/docs/adr/model/0030-runtime-mechanics-derived.md @@ -72,7 +72,7 @@ retention can only be an application-level backup job, both falling out of the durability class of [0015](0015-durability-class-per-volume.md) as probe timings fall out of [0014](0014-probes-are-siblings.md). The renderer does not do this yet: `src/adapters/kubernetes.ts:608` reads an authored enum -(`src/schemas/project-intent.ts:165`), inspecting no volume. +(`src/schemas/service-intent.ts:165`), inspecting no volume. ## Alternatives | option | cost if taken | why rejected | diff --git a/docs/adr/model/0053-adapter-port-contract.md b/docs/adr/model/0053-adapter-port-contract.md index 235df71..2cfb342 100644 --- a/docs/adr/model/0053-adapter-port-contract.md +++ b/docs/adr/model/0053-adapter-port-contract.md @@ -64,8 +64,8 @@ while `tsconfig.json:11` sets `"strict": true` and `eslint.config.js:33` sets violated. `grep -rn E_PATH_COLLISION src/` returns nothing, while the writer applies each prepared file in turn (`src/render-plan/writer.ts:60-66`): two adapters sharing a path both write, second wins, silently, both reporting -`action: "create"`. `kubernetes-process-fragment` reads raw manifests from disk -inside render (`src/adapters/kubernetes-process-fragment.ts:48-49`, `:236-247`); +`action: "create"`. `kubernetes-workload-fragment` reads raw manifests from disk +inside render (`src/adapters/kubernetes-workload-fragment.ts:48-49`, `:236-247`); under the port that read moves to the caller, which passes the parsed documents in, as `loadFragmentInput` already does at `src/adapters/fragment-model.ts:95-98`. Hence the contract: documents in, attributed Deliverables out, deterministic, no diff --git a/docs/adr/model/0054-adapter-attribution.md b/docs/adr/model/0054-adapter-attribution.md index 1ab8972..3d1275c 100644 --- a/docs/adr/model/0054-adapter-attribution.md +++ b/docs/adr/model/0054-adapter-attribution.md @@ -56,7 +56,7 @@ consumers to shape it. One owner per path is asserted today and enforced nowhere. Five of the sixteen registered names end in `-fragment` and shadow an earlier adapter, chapter 30 -names four such pairs and misses `kubernetes-process-fragment`; the twins avoid +names four such pairs and misses `kubernetes-workload-fragment`; the twins avoid a literal collision only because they write under `fragments/` while the first generation writes under `platform/cluster/flux/`. Three adapters (`kubernetes`, `flux-packs`, `flux-source`) declare the same `platform/cluster/flux/apps` prefix. diff --git a/docs/adr/model/0073-vault-policy-is-a-deliverable.md b/docs/adr/model/0073-vault-policy-is-a-deliverable.md index 3f4c4e9..72cd3aa 100644 --- a/docs/adr/model/0073-vault-policy-is-a-deliverable.md +++ b/docs/adr/model/0073-vault-policy-is-a-deliverable.md @@ -42,7 +42,7 @@ instead. Either way something must exist in Vault that the derivation describes. The unit is the **Process identity**, not the Application. [0024](0024-identity-per-process.md) makes the ServiceAccount and the Vault role the Process name alone, and the estate has already paid for getting this -wrong: `applicationAccountName()` returned the Application name, so two Processes of one +wrong: `serviceAccountName()` returned the Application name, so two Processes of one Application authenticated as the same principal and received the union of both policies whatever level a grant was written at. One document per identity is what makes that impossible to reintroduce, and it makes a diff say which diff --git a/docs/adr/model/0083-privileged-port-needs-the-capability.md b/docs/adr/model/0083-privileged-port-needs-the-capability.md index 806b1dc..f9a8451 100644 --- a/docs/adr/model/0083-privileged-port-needs-the-capability.md +++ b/docs/adr/model/0083-privileged-port-needs-the-capability.md @@ -25,11 +25,11 @@ no Process declaring a port below 1024. ## Why `auth-ui` declared port 80 under the `restricted` class and therefore rendered a pod that cannot bind its own port: non-root plus `capabilities.drop: [ALL]` -removes `CAP_NET_BIND_APPLICATION`. The render was internally consistent and the +removes `CAP_NET_BIND_SERVICE`. The render was internally consistent and the process could not start. The model said nothing either way. Refusing it is the reading that keeps the class honest. Deriving -`NET_BIND_APPLICATION` wherever a low port appears would re-add a dropped capability +`NET_BIND_SERVICE` wherever a low port appears would re-add a dropped capability for every Process that happens to declare one, silently, and the class would mean less than it says. @@ -53,7 +53,7 @@ during serialisation that the process then contradicts. ## Alternatives | option | cost if taken | why rejected | |---|---|---| -| Derive `NET_BIND_APPLICATION` where a low port is declared | Nothing in any project file changes | Silently re-adds a capability the class dropped, for every Process that happens to declare a low port | +| Derive `NET_BIND_SERVICE` where a low port is declared | Nothing in any project file changes | Silently re-adds a capability the class dropped, for every Process that happens to declare a low port | | Derive an unprivileged `targetPort` | Nothing authored changes and nothing is refused | The process still listens where its image says, so the rendered object and the running pod disagree | | Leave it to review | No new error code | The render is internally consistent, so review has nothing to notice; the failure appears as a crash-looping pod | diff --git a/docs/adr/model/0091-identity-placeholders-not-framework-wiring.md b/docs/adr/model/0091-identity-placeholders-not-framework-wiring.md index 44c8ce9..01b331a 100644 --- a/docs/adr/model/0091-identity-placeholders-not-framework-wiring.md +++ b/docs/adr/model/0091-identity-placeholders-not-framework-wiring.md @@ -46,14 +46,14 @@ file, in its own repository. That leaves one real problem, which is the reason this is a decision rather than a shrug. One of those four lines is `VAULT_KUBERNETES_ROLE: auth-api`, and the role name is **derived** ([0024](0024-identity-per-process.md)). Written as a literal it is precisely -the staleness that produced the `applicationAccountName()` defect, where a +the staleness that produced the `serviceAccountName()` defect, where a hand-maintained name and a derived one disagreed and nothing noticed until two Processes shared a principal. Hence `${identity:…}`, a fourth named source beside `${dependency:…}`, `${secret:…}` and `${exposure:…}`. The first three name something else; this one names what the platform decided about **this** Process (`vaultRole`, -`applicationAccount`, `namespace`), as a closed key set, with no template language, +`serviceAccount`, `namespace`), as a closed key set, with no template language, exactly like the others. A literal where a placeholder belongs is already a build error, so the drift closes. @@ -83,7 +83,7 @@ though that is a rename, not a redesign. outcome worth having, paid in four boilerplate lines per self-delivering Process, in the repository that owns the framework. - `VAULT_KUBERNETES_ROLE` can no longer disagree with the derived role, so the - `applicationAccountName()` class of defect is closed on the authoring side too, + `serviceAccountName()` class of defect is closed on the authoring side too, paid by nobody. - A fourth placeholder source is a fourth thing to validate, complete in an editor and resolve; the key set is closed so the validation is a lookup, paid diff --git a/docs/adr/model/0098-one-publication-path.md b/docs/adr/model/0098-one-publication-path.md index f642be2..9ac87f1 100644 --- a/docs/adr/model/0098-one-publication-path.md +++ b/docs/adr/model/0098-one-publication-path.md @@ -28,7 +28,7 @@ byte for byte. publication one mechanism: everything authored enters composition as an Intent Fragment by digest. That leaves the five `*-fragment` adapters as the one remaining second path into the render, and the deletion test decides them. -Remove `traefik-route-fragment`, `kubernetes-process-fragment`, +Remove `traefik-route-fragment`, `kubernetes-workload-fragment`, `gatus-endpoint-fragment`, `edge-catalog-fragment` and `image-metadata-fragment` together with `adapter-compat.ts`, and nothing a consumer observes changes: the central run already derives every one of those kinds from the composed intent. diff --git a/spec/v1/10-project-intent.md b/spec/v1/10-project-intent.md index 1bc1c77..f572cef 100644 --- a/spec/v1/10-project-intent.md +++ b/spec/v1/10-project-intent.md @@ -452,19 +452,19 @@ derived about **this** Process | key | value | |---|---| | `${identity:vaultRole}` | the Process's Vault role, its own name ([0024](../../docs/adr/model/0024-identity-per-process.md)) | -| `${identity:applicationAccount}` | the Process's ServiceAccount name | +| `${identity:serviceAccount}` | the Process's ServiceAccount name | | `${identity:namespace}` | `-system` | The key set is closed. It exists because a self-delivering Process has to wire its own Vault client, and one of the values it wires (the role name) is derived: written as a literal it is the same staleness class as the -`applicationAccountName()` defect, where a hand-maintained name and a derived one +`serviceAccountName()` defect, where a hand-maintained name and a derived one disagreed and nothing noticed. Writing a derived value as a literal is a Writing a derived value as a literal is a build error, and so is writing a Runtime Profile key at all: `OTEL_*` and `PYROSCOPE_*` come from `runtime`, and an exceptional value is not a layer-1 concept: there is no `overrides` field to put it in. Ten `OTEL_*` variables are byte-identical today across `auth-api`, -`agents-api` and `knowledge-api` except `OTEL_APPLICATION_NAME`, sixty duplicated +`agents-api` and `knowledge-api` except `OTEL_SERVICE_NAME`, sixty duplicated lines that leave the project repositories under this rule. Placeholders are named-source references and never a template language: no @@ -547,7 +547,7 @@ fallback** ([0014](../../docs/adr/model/0014-probes-are-siblings.md)). Readiness *can I serve traffic*; liveness means *is my process wedged*. A liveness probe pointed at a readiness endpoint turns a dependency outage into a crash-loop, and the v2 model made that the default for anyone declaring one path: -`src/adapters/kubernetes-process-fragment.ts:166` renders +`src/adapters/kubernetes-workload-fragment.ts:166` renders `livenessProbe: probe(health.livenessPath ?? health.path)`, and `app-ui` and `agents-login` both rely on it today. @@ -724,7 +724,7 @@ writablePaths: [/var/cache/nginx, /var/run] The field does not exist today, in either renderer generation: `grep -rniE 'securityContext|runAsNonRoot|readOnlyRootFilesystem|seccompProfile' src/ schemas/` -returns **0 hits**, and `src/deployment/render/processes.ts:130` builds a +returns **0 hits**, and `src/deployment/render/workloads.ts:130` builds a container from name, image, pullPolicy, ports, command, args, env, envFrom, volumeMounts, probes and resources, and stops. Rendered pods run as their image's UID, with a writable root and default capabilities, and the standing QoS class for @@ -807,7 +807,7 @@ nginx writes, and `platform-postgres`, whose UID comes from the lock. ### A privileged port needs the capability that binds it A `provides` port below 1024 cannot be bound by a non-root process without -`CAP_NET_BIND_APPLICATION`, and the `restricted` class drops all capabilities. A +`CAP_NET_BIND_SERVICE`, and the `restricted` class drops all capabilities. A Process declaring one is `E_PRIVILEGED_PORT_UNDER_NONROOT` ([0083](../../docs/adr/model/0083-privileged-port-needs-the-capability.md)), and the answer is a port above 1024. Deriving the capability silently would @@ -1428,7 +1428,7 @@ The two levels are an access boundary **only** because identity is per Process. The ServiceAccount and Vault role are derived as the **Process name alone** ( `auth-system.auth-api`, never `auth-system.auth-auth-api`) unique within the project file ([0024](../../docs/adr/model/0024-identity-per-process.md), specified in -chapter 16). At review time they were not: `applicationAccountName()` in +chapter 16). At review time they were not: `serviceAccountName()` in `src/adapters/kubernetes.ts:665-669` returned `applicationName`, so two Processes of one Application authenticated as the same principal and received the union of both policies whatever level a grant was written at. The nesting was documentation. The diff --git a/spec/v1/20-resolved-deployment.md b/spec/v1/20-resolved-deployment.md index 667c963..7ba9ce4 100644 --- a/spec/v1/20-resolved-deployment.md +++ b/spec/v1/20-resolved-deployment.md @@ -112,7 +112,7 @@ The estate is the argument for having a rule at all. One hostname, `kb.jorisjonkers.dev`, ended up declared in seven authoritative places across three repositories (`homelab-inventory/catalog/reachability.yml`, three `fleet-infra` manifests, a bearer-token secret and the application's own -`platform/deployment.yml`) plus hardcoded in `ApplicationPermission.kt`, with two +`platform/deployment.yml`) plus hardcoded in `ServicePermission.kt`, with two conformance tests existing for no purpose but detecting when the seven disagree. The guard was cheaper to write than the fix. @@ -824,7 +824,7 @@ assigned: processes: knowledge-api: - applicationAccount: knowledge-api # the Process name alone + serviceAccount: knowledge-api # the Process name alone objectKind: Deployment image: ghcr.io/jorisjonkers-dev/knowledge/knowledge-api@sha256:1ad39d5… probes: @@ -850,7 +850,7 @@ assigned: - {kind: VaultStaticSecret, path: secret/data/platform/postgres/kb} knowledge-ingest-worker: - applicationAccount: knowledge-ingest-worker + serviceAccount: knowledge-ingest-worker objectKind: Deployment strategy: {type: Recreate} # forced: RWO volume resources: diff --git a/spec/v1/30-deliverables.md b/spec/v1/30-deliverables.md index 9badeea..71276a4 100644 --- a/spec/v1/30-deliverables.md +++ b/spec/v1/30-deliverables.md @@ -307,7 +307,7 @@ in Project Intent implies a dashboard's panels; deriving one would be inventing dashboard DSL. It is ledgered rather than permanent: 14 application dashboards become Assets on the owning Application ([0012](../../docs/adr/model/0012-assets-not-code.md)), 3 runtime-family dashboards ship with the Runtime Profile, 14 platform dashboards -become Assets of the declared observability Applications, and `application-overview` / `application-template` derive +become Assets of the declared observability Applications, and `service-overview` / `service-template` derive per Application from the scrape surface and exposure ([0021](../../docs/adr/model/0021-observability-scrape-and-alert-class.md)). diff --git a/spec/v1/examples/auth/auth.project.yml b/spec/v1/examples/auth/auth.project.yml index 6980a58..444c2c0 100644 --- a/spec/v1/examples/auth/auth.project.yml +++ b/spec/v1/examples/auth/auth.project.yml @@ -148,7 +148,7 @@ applications: # never a Process. provides: http: 8080 # not 80: a port below 1024 cannot be bound by a - # non-root process without CAP_NET_BIND_APPLICATION, + # non-root process without CAP_NET_BIND_SERVICE, # which `restricted` drops # (docs/adr/model/0083-privileged-port-needs-the-capability.md). # A route names a surface, not a number, so the diff --git a/spec/v1/examples/auth/rendered/README.md b/spec/v1/examples/auth/rendered/README.md index 00be491..107bed8 100644 --- a/spec/v1/examples/auth/rendered/README.md +++ b/spec/v1/examples/auth/rendered/README.md @@ -5,7 +5,7 @@ What a renderer must produce from [`../env/auth-api.base.env`](../env/auth-api.base.env), rendered by hand against the model as decided: chapter 10 (intent), chapter 16 (identity, edges, policy), chapter 20 (the derivations), chapter 30 (adapters and attribution), and the -two amendments in `review/PLACEMENT-PROJECT-MANIFEST.md` and +two amendments in `review/PLACEMENT-DOMAIN-MANIFEST.md` and `review/EXPOSURE-MANIFEST.md`. It is the GOAL STATE, not today's output. Today's renderer emits no @@ -191,7 +191,7 @@ renumbered: every other reference in this file keeps pointing where it did. | G-22 | **No token TTLs.** `token_ttl`, `token_max_ttl` and `token_period` on the Kubernetes auth role have no field and no derivation; the mount default applies | | G-23 | **The forward-auth `Middleware` object has no producer.** The registry says `traefik` emits IngressRoutes "with middleware references": references only. Every `audience: authenticated` route in every other project resolves against a Middleware pointing at this Application, and nothing renders it | | G-24 | **A project's Deliverables land in another project's namespace.** The Gatus endpoints ConfigMap is one estate-wide object in `utility-system`, contributed to by every project. `E_FOREIGN_NAMESPACE` is satisfied only because the adapter owns the path rather than the Application, worth stating explicitly before someone tightens the rule | -| G-25 | **auth-ui cannot bind port 80 as rendered.** `provides: {http: 80}` with `runAsNonRoot: true` and `capabilities.drop: [ALL]`, and the only declared exception is `writableRootFilesystem`. Binding below 1024 needs `CAP_NET_BIND_APPLICATION`, which the exception vocabulary can express (`capability:NET_BIND_APPLICATION`) and this Process does not declare. Nothing checks it: the model has every fact needed to refuse this at build time (an exposed or provided port < 1024, non-root, no capability exception), and no rule that does | +| G-25 | **auth-ui cannot bind port 80 as rendered.** `provides: {http: 80}` with `runAsNonRoot: true` and `capabilities.drop: [ALL]`, and the only declared exception is `writableRootFilesystem`. Binding below 1024 needs `CAP_NET_BIND_SERVICE`, which the exception vocabulary can express (`capability:NET_BIND_SERVICE`) and this Process does not declare. Nothing checks it: the model has every fact needed to refuse this at build time (an exposed or provided port < 1024, non-root, no capability exception), and no rule that does | | G-26 | **`runAsNonRoot: true` with no UID.** Chapter 10 renders the control "with the UID from the image", and no pinned input carries a UID: the images lock carries digests. If the image's `USER` is a name rather than a number, the kubelet cannot verify non-root and the pod fails with `CreateContainerConfigError`. Either the lock grows a UID or the model grows a field | | G-27 | **The Flux health timeout class contradicts the startup budget.** The class table gives `stateless: 5m`; auth-api's `startupBudget` is 600 s and its derived `progressDeadlineSeconds` is 1800. The Kustomization gives up at 5 minutes on a Process the model says may legitimately take ten. Two derivations over the same declaration disagree | **Closed** by [0071](../../../../../docs/adr/model/0071-release-gate-inputs-are-layer-2.md): the class table is deleted and the Application-scoped number is the release-gate deadline, max over members of progressDeadlineSeconds. | | G-28 | **The hardening class does not say where its controls land.** `runAsNonRoot` and `seccompProfile` are rendered at pod level, `readOnlyRootFilesystem` and `capabilities` at container level (the latter two have no pod-level form). The split is a serialisation choice, and it matters the moment `sidecars` is graded: a pod-level control covers a sidecar the Process did not declare | diff --git a/spec/v1/examples/data/rendered/README.md b/spec/v1/examples/data/rendered/README.md index b781fb5..b057775 100644 --- a/spec/v1/examples/data/rendered/README.md +++ b/spec/v1/examples/data/rendered/README.md @@ -4,7 +4,7 @@ What a renderer must produce from [`../data.project.yml`](../data.project.yml) a [`../env/platform-postgres.base.env`](../env/platform-postgres.base.env), rendered by hand against the model as decided: chapter 10 (intent), chapter 16 (identity, edges, policy), chapter 20 (the derivations), chapter 30 (adapters and -attribution), and the two amendments in `review/PLACEMENT-PROJECT-MANIFEST.md` +attribution), and the two amendments in `review/PLACEMENT-DOMAIN-MANIFEST.md` and `review/EXPOSURE-MANIFEST.md`. It is the **goal state**, not today's output. Today's renderer emits no diff --git a/spec/v1/examples/knowledge/rendered/README.md b/spec/v1/examples/knowledge/rendered/README.md index 2526795..4293ff7 100644 --- a/spec/v1/examples/knowledge/rendered/README.md +++ b/spec/v1/examples/knowledge/rendered/README.md @@ -140,7 +140,7 @@ As declared, the process cannot read its own deploy key. Repairing it needs `fsGroup` or a known UID, neither of which layer 1 can express, and the two declarations that collide are in the same Process block. -**G-09** `applicationAccountName()` returns the Application name today, so both pods +**G-09** `serviceAccountName()` returns the Application name today, so both pods authenticate as one principal and receive the union of both policies. That gives the internet-facing API `read` on the ingest worker's SSH deploy key. The declaration and the identity must ship together. @@ -162,7 +162,7 @@ no chapter. The set of legal coordinate names (`host`, `port`, `database`, …) not enumerated anywhere either. **G-13** `runtime: jvm` injects 10 `OTEL_*` and 6 `PYROSCOPE_*` keys; `runtime: -python` injects 6. Exactly one of them, `OTEL_APPLICATION_NAME`, is a function of +python` injects 6. Exactly one of them, `OTEL_SERVICE_NAME`, is a function of anything declared. The other fifteen are constants held in a **Runtime Profile**, and chapter 20's pinned input set does not include one, and it lists Intent Fragments, the Platform document and node contract, the images lock and the diff --git a/spec/v1/examples/minimal/notes.project.yml b/spec/v1/examples/minimal/notes.project.yml index db8d8c9..7b55d19 100644 --- a/spec/v1/examples/minimal/notes.project.yml +++ b/spec/v1/examples/minimal/notes.project.yml @@ -44,7 +44,7 @@ applications: runtime: node provides: - http: 8080 # above 1024: restricted drops CAP_NET_BIND_APPLICATION + http: 8080 # above 1024: restricted drops CAP_NET_BIND_SERVICE # Two dimensions are required. Both are matched against what the node # contract publishes, never against free capacity