diff --git a/README.md b/README.md index 3190338..9a34934 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,8 @@ recorded before they are spread across implementation repos. - [docs/adr/ADR-0004-first-wave-reef-rollout.md](docs/adr/ADR-0004-first-wave-reef-rollout.md) - [docs/adr/ADR-0005-derived-rail-composition.md](docs/adr/ADR-0005-derived-rail-composition.md) - [docs/adr/ADR-0006-reef-production-admission.md](docs/adr/ADR-0006-reef-production-admission.md) +- [docs/adr/ADR-0007-rapp-declaration-contract.md](docs/adr/ADR-0007-rapp-declaration-contract.md) +- [docs/adr/ADR-0008-private-by-default-exposure.md](docs/adr/ADR-0008-private-by-default-exposure.md) ## Current Explorations diff --git a/docs/adr/ADR-0006-reef-production-admission.md b/docs/adr/ADR-0006-reef-production-admission.md index 34c5380..d0fa052 100644 --- a/docs/adr/ADR-0006-reef-production-admission.md +++ b/docs/adr/ADR-0006-reef-production-admission.md @@ -21,9 +21,16 @@ defined split triggers. The detailed contract is [`docs/reef-production-readiness-contract.md`](../reef-production-readiness-contract.md). +Who may reach a listener is a different axis: +[`ADR-0008`](ADR-0008-private-by-default-exposure.md). Production +admission does not imply a public surface. A public surface requires +this admission **and** an exposure grant. + ## Consequences - `hosts_rail` and `binds_rapp` no longer imply deployability. - `reef-railiance` may host Knative in wave 2, but Qonto cannot be called production-approved solely from that declaration. - Repeated evidence collection should become functional automation. +- `production-approved` is not permission to publish a listener. See + ADR-0008. diff --git a/docs/adr/ADR-0008-private-by-default-exposure.md b/docs/adr/ADR-0008-private-by-default-exposure.md new file mode 100644 index 0000000..246fef9 --- /dev/null +++ b/docs/adr/ADR-0008-private-by-default-exposure.md @@ -0,0 +1,92 @@ +# ADR-0008: Private-by-default Exposure + +Date: 2026-08-15 +Status: Accepted + +## Context + +ADR-0006 says a topology binding is not permission to run a workload in +production. It does not say who may reach a listener. A working deploy, +a `hosts_rail` / `binds_rapp` line, or an Ingress object has been enough +to put something on the public internet. + +Family readiness vocabularies are deliberately not unified +(`schemas/README.md`). Reef `lifecycle_state` has no +`production-approved`. Rapp `readiness_state` has no +`production-approved` either. Exposure cannot be derived from those +enums. + +Live public surfaces already exist on `reef-railiance`. They must be +named as grants, not silently grandfathered and not taken down by this +decision. + +Ratified 2026-08-15 under `RMASTER-WP-0023-T01`. + +## Decision + +New reefs, rails, and rapps are unreachable from the public internet +until they are production-safe **and** explicitly granted. The field +name is `exposure`. Missing field means `private`. + +| Posture | Meaning | Who may reach it | +| --- | --- | --- | +| `private` | No extra listener we control | in-cluster DNS, same-reef services | +| `operator` | Same listener as `private`, plus a named ops-bridge / SSH tunnel | operator and approved agents | +| `public` | Internet listener we publish | anyone the Ingress / DNS / UFW surface allows | + +`operator` is an access annotation, not a different packet posture. It +does not open a host port or a public Ingress. + +Default for a new reef, rail, or rapp is `private`. Use `operator` only +for a named admin or API surface that must be reachable before admission +(k3s API, OpenBao UI). Do not prefer `operator` as the debug default. + +`public` requires both an ADR-0006 binding of `production-approved` and +an explicit grant. A deploy, a binding line, or an Ingress object is not +a grant. Do not key `public` off rapp `readiness_state` and do not add +`production-approved` to the rapp enum for this purpose. + +A reef public surface (host port or public DNS we publish) is a +**substrate grant**, not reef `lifecycle_state`. A rapp cannot be +`public` on a reef that has not granted a public surface. Conflicting +declarations fail closed. + +The shared vocabulary is one enum. It is not one schema shape and not +one admission check: + +| Family | What the field means | What makes `public` legal | +| --- | --- | --- | +| `rapp-*` | intended consumer-facing listener | binding `production-approved` + grant | +| `rail-*` | which listener classes the rail may emit | rail may emit public Ingress only when a bound grant exists | +| `reef-*` | host ports and public DNS we publish | substrate grant | + +A grant is accepted residual risk, not a self-serve wish. Required +fields: hostname or port, reason, `approved_on`, residual-risk owner. + +`6443` / the k3s API is not a grantable public surface. + +This contract covers listeners we control: host ports, Ingress, Service +types, and public DNS we publish. Provider-native internet APIs +(Scaleway S3 and other provider-delegated endpoints) are outside this +enum. + +Ops-bridge is the normal path to a shielded thing. + +The detailed per-family reading lives in +[`docs/exposure-posture-contract.md`](../exposure-posture-contract.md) +once `RMASTER-WP-0023-T02` writes it. + +## Consequences + +- ADR-0006 still answers "may this binding run in production?" This ADR + answers "who may reach the listener?" Do not merge the axes. +- The three readiness enums stay distinct on purpose. +- Family schemas grow an additive `exposure` field. Rapp + `data_classification: public` is a different field and must not be + reused as the posture name. +- Implementation stays in the owning repos. This ADR does not install + NetworkPolicy, UFW, Ingress, or tunnels. +- Existing public surfaces on `reef-railiance` remain up until named as + grants. This ADR is not a shutdown plan. +- CoulombCore host inventory and Q7 / Goss reaction stay outside this + decision. diff --git a/docs/reef-production-readiness-contract.md b/docs/reef-production-readiness-contract.md index 9da0f6a..34e5836 100644 --- a/docs/reef-production-readiness-contract.md +++ b/docs/reef-production-readiness-contract.md @@ -11,6 +11,11 @@ critical workload. `hosts_rail` and `binds_rapp` describe intended composition. Production use requires explicit readiness evidence. +This contract does not decide who may reach a listener. That is +[`ADR-0008`](adr/ADR-0008-private-by-default-exposure.md). A +`production-approved` binding is still private until an exposure grant +exists. + ## Admission States Every hosted rail and bound `rapp` uses: diff --git a/workplans/RMASTER-WP-0023-private-by-default-exposure.md b/workplans/RMASTER-WP-0023-private-by-default-exposure.md index a147140..bee6cbb 100644 --- a/workplans/RMASTER-WP-0023-private-by-default-exposure.md +++ b/workplans/RMASTER-WP-0023-private-by-default-exposure.md @@ -4,7 +4,7 @@ type: workplan title: "Private-by-default exposure until production admission" domain: financials repo: railiance-master -status: ready +status: active owner: bernd topic_slug: railiance planning_priority: high @@ -13,6 +13,7 @@ created: "2026-08-15" updated: "2026-08-15" related: - ADR-0006 + - ADR-0008 - RMASTER-WP-0017 - RMASTER-WP-0019 - RMASTER-WP-0021 @@ -93,10 +94,10 @@ It does **not** belong as the system owner in: provider API on the internet. That is a different category from a listener we open. -## Decision to ratify (T01) +## Decision (T01, accepted 2026-08-15) -Recommended default, for review. T01 may amend; it should not start from -a blank page. +Ratified as written. Recorded in +[`docs/adr/ADR-0008-private-by-default-exposure.md`](../docs/adr/ADR-0008-private-by-default-exposure.md). | Posture | Meaning | Who may reach it | | --- | --- | --- | @@ -187,7 +188,7 @@ It must not: ```task id: RMASTER-WP-0023-T01 -status: todo +status: done priority: high state_hub_task_id: "40d50848-d59e-4f7c-98df-a442dce90439" ``` @@ -202,11 +203,16 @@ substrate grant. Confirm `6443` is not grantable as `public`. distinct on purpose, and the per-family meaning of `public` is written down. +Completed 2026-08-15: operator accepted the recommended table as +written. ADR-0008 records the postures, the per-family meaning of +`public`, the grant shape, and the 6443 exclusion. Readiness enums stay +distinct. + ### T02 — Write the exposure contract ```task id: RMASTER-WP-0023-T02 -status: wait +status: todo priority: high state_hub_task_id: "96db2a2d-670c-4b98-b6f3-860f9b3009b3" ``` @@ -322,6 +328,6 @@ with a back-link to this ID. ## Outcome -Reviewed 2026-08-15 against current repo state (`status: ready`). T01 -still needs human ratification of ADR-0008. Implementation stays out of -this repo. +T01 accepted 2026-08-15 (`status: active`). ADR-0008 is the record. +Next: T02 contract and T04 exception snapshot. Implementation stays out +of this repo.