4 KiB
| id | title | status | revision | owner | last_reviewed | review_interval |
|---|---|---|---|---|---|---|
| RMASTER-ADR-0008 | Private-by-default Exposure | accepted | accepted-1 | railiance-master | 2026-08-15 | 6m |
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.
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
exposurefield. Rappdata_classification: publicis 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-railianceremain up until named as grants. This ADR is not a shutdown plan. - CoulombCore host inventory and Q7 / Goss reaction stay outside this decision.