Draft family workplan for review. Default reefs, rails, and rapps stay off the public internet until production-approved plus an explicit grant. Implementation is routed, not done here.
8.4 KiB
| id | type | title | domain | repo | status | owner | topic_slug | created | updated | related | related_repos | |||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| RMASTER-WP-0023 | workplan | Private-by-default exposure until production admission | financials | railiance-master | proposed | bernd | railiance | 2026-08-15 | 2026-08-15 |
|
|
RMASTER-WP-0023 — private-by-default exposure until production admission
Goal
Make every new reef, rail, and rapp unreachable from the public internet until it is production-safe, by one family rule that the implementation repos can enforce.
"Production-safe" already has a name: ADR-0006 admission
(declared → installed → verified → production-approved). What is
missing is the default-private exposure rule and a field the schemas can
check. Topology and a working deploy must not imply a public listener.
Why this is here
railiance-master owns framework contracts that span reef-*, rail-*,
and rapp-*. Shielding "whatever we spin up" is that kind of rule.
It does not belong as the system owner in:
railiance-infra— host UFW. Necessary for a new reef with no 80/443; useless for a rapp that sharesrailiance01ingress. RAIL-HO-WP-0009 closed the hand-opened k3s API; do not make S1 the admission desk for rapps.railiance-cluster/rail-kubernetes— Ingress, NetworkPolicy, Service types. This is where most packets will actually be blocked. They cannot define what "production-safe" means for a rapp or for a non-k8s reef (reef-storage).- each
rapp-*— must declare intended exposure; must not punch its own public hole. railiance-enablement— should ship private-by-default templates; does not own the rule.reef-railiancealone — too local; the next reef must inherit the same rule.
What already exists
- ADR-0006 and
docs/reef-production-readiness-contract.md— admission states; network isolation and extra gates for internet-facing or high-value workloads. - Family schemas (
schemas/{reef,rail,rapp}.schema.json) — noexposure/network_posturefield. - Readiness vocabularies are deliberately not unified
(
schemas/README.md):- rail:
declared | installed | verified | production-approved | deprecated - rapp:
draft | declared | deployed | verified | retired - reef:
lifecycle_stateplanned | active | retired - bindings: ADR-0006 states on the reef
- rail:
- RAIL-HO-WP-0009 — operator access to a shielded k3s API is ops-bridge
(
k3s-api-railiance01on:16444). That is the intended pattern for anything not yet public. - Live public exceptions already exist on
reef-railiance(Forgejo, Coulomb Social, Host Europe Nydus on 2224). They must be named as grants, not silently grandfathered.
Decision to ratify (T01)
Proposed default, for review:
| Posture | Meaning | Who may reach it |
|---|---|---|
private |
Cluster / reef-internal only | in-cluster DNS, same-reef services |
operator |
Reachable via ops-bridge / SSH tunnel | operator and approved agents |
public |
Internet | anyone the Ingress / DNS / UFW surface allows |
Rules:
- New reefs, rails, and rapps default to
privateoroperator. Preferoperatorwhen a human must debug before admission. publicrequires bothproduction-approved(ADR-0006 binding, or the equivalent rail/rapp state this workplan chooses) and an explicit exposure grant in the declaration.- A working deploy, a
hosts_rail/binds_rappline, or an Ingress object is not a grant. - Ops-bridge is the normal path to a shielded thing. That is the same
trade as ADR-005 in
railiance-infra: convenience depends on the tunnel; the Internet does not see the service.
Do not unify the three readiness enums in this workplan unless T01 explicitly decides to. Exposure can sit beside them.
Boundaries
This workplan may:
- amend ADR-0006 or add a sibling ADR for exposure posture
- extend family schemas and
tools/validate-family-declarations.py - write the exposure contract and the exception register
- route implementation workplans to owning repos
It must not:
- implement NetworkPolicy, Ingress classes, or UFW rules here
- take down live public services as a side effect of the contract
- put booked cost, credentials, or customer numbers in declarations
Tasks
T01 — Ratify the exposure rule
id: RMASTER-WP-0023-T01
status: todo
priority: high
Review and accept or amend the decision table above. Record it as an ADR
(amend ADR-0006 or add ADR-0008). Name the default posture for a new
reef, a new rail, and a new rapp. Decide whether public keys off the
binding admission state (preferred) or off rapp.yaml
readiness_state (currently has no production-approved).
Done when: the ADR is accepted and the three readiness enums are either left distinct on purpose or given a written unification plan.
T02 — Write the exposure contract
id: RMASTER-WP-0023-T02
status: todo
priority: high
One contract, docs/exposure-posture-contract.md, that says what each
posture means per family:
| Family | private |
operator |
public |
|---|---|---|---|
reef-* |
no extra host ports; no public DNS for new members | ops-bridge / SSH only | declared host/DNS surface |
rail-* |
no public Ingress class; default-deny NetworkPolicy | tunnel to the rail's API or admin path | rail may emit public Ingress |
rapp-* |
ClusterIP / in-cluster DNS only | named tunnel (local port) | named public hostname |
Include the fail-closed rule: missing field means private.
Done when: implementation repos can implement without inventing vocabulary.
T03 — Schema and validator
id: RMASTER-WP-0023-T03
status: wait
priority: high
Depends on T01.
Add exposure (or the T01 name) to rapp.schema.json, rail.schema.json,
and reef.schema.json. Validator must fail when:
- posture is
publicand the relevant admission/readiness state is not production-approved - posture is
publicand no grant object is present (hostname / port / reason / approved_on) - a live family declaration omits the field after the migration window
Keep the field additive first so existing declarations do not break the day the schema lands; then require it.
Done when: tools/validate-family-declarations.py rejects a public
rapp that is not production-approved, and the testdata tree has good and
bad fixtures.
T04 — Exception register for already-public surfaces
id: RMASTER-WP-0023-T04
status: todo
priority: medium
List current public listeners on reef-railiance (and CoulombCore if in
scope) as explicit grants: Forgejo, Coulomb Social, Host Europe Nydus
2224, anything else found. Each grant: hostname or port, owning rapp or
provider, why it is public, residual risk owner.
Do not invent a shutdown plan here. The register exists so the contract does not pretend the reef is already private.
Done when: every known public surface is either a grant or a routed task to close it.
T05 — Route enforcement workplans
id: RMASTER-WP-0023-T05
status: wait
priority: high
Depends on T01 and T02.
Write the handoff workplan files (or precise intake notes) in the owning repos. Do not implement there from this repo.
| Owner | What to enforce |
|---|---|
rail-kubernetes (or railiance-cluster until the rail owns networking) |
default-deny NetworkPolicy; no public Ingress unless the grant exists; ClusterIP as the paved Service |
reef-railiance |
first live admission: new binds stay operator; existing public binds appear in the T04 register |
railiance-infra |
new reefs do not get 80/443/6443 until the reef grant exists; not the rapp gate |
railiance-enablement |
templates emit private Services + tunnel docs, never a public Ingress by default |
Done when: each owning repo has a workplan or an accepted intake, with a back-link to this ID.
Suggested review order
- T01 (you) — accept or change the table.
- T02 + T04 can proceed in parallel after T01.
- T03 after the field names are stable.
- T05 last; it is the only task that creates work in other repos.
Outcome
Pending review. This file is a draft (status: proposed). Register it
with statehub fix-consistency in railiance-master after you accept the
shape. Implementation stays out of this repo.