RMASTER-ADR-0008 accepted · accepted-1 railiance-master reviewed 2026-08-15generated from canonical source — do not edit

Private-by-default Exposure

Source: railiance-master · docs/adr/ADR-0008-private-by-default-exposure.md · debbc13b4018f20db0997516b99afc1cc21084d7

Review due: 2027-02-15

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.

PostureMeaningWho may reach it
privateNo extra listener we controlin-cluster DNS, same-reef services
operatorSame listener as private, plus a named ops-bridge / SSH tunneloperator and approved agents
publicInternet listener we publishanyone 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:

FamilyWhat the field meansWhat makes public legal
rapp-*intended consumer-facing listenerbinding production-approved + grant
rail-*which listener classes the rail may emitrail may emit public Ingress only when a bound grant exists
reef-*host ports and public DNS we publishsubstrate 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 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.