Route public bao.coulomb.social as a close, not a grant. Record the filed child workplans for private-by-default enforcement.
14 KiB
| id | type | title | domain | repo | status | owner | topic_slug | planning_priority | planning_order | created | updated | related | related_repos | state_hub_workstream_id | |||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| RMASTER-WP-0023 | workplan | Private-by-default exposure until production admission | financials | railiance-master | finished | grok | railiance | high | 23 | 2026-08-15 | 2026-08-15 |
|
|
50303bdf-aada-4c38-8782-d4e63b603e65 |
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. Do not use this workplan to re-public6443.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.ops-bridge— the paved path to a shielded thing; not the policy owner.
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. Admission answers "may this binding run in production?" Exposure answers "who may reach the listener?" Keep them beside each other. - Family schemas (
schemas/{reef,rail,rapp}.schema.json) — noexposure/network_posturefield. Rapp already usespublicondata_classification; the new field must not reuse that name as a top-level enum collision. - 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. - Provider-delegated reefs (
reef-storage, Scaleway S3) already expose a provider API on the internet. That is a different category from a listener we open.
Decision (T01, accepted 2026-08-15)
Ratified as written. Recorded in
docs/adr/ADR-0008-private-by-default-exposure.md.
| 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. A ClusterIP service
plus a named tunnel is still private at the packet layer.
Rules:
- New reefs, rails, and rapps default to
private. Useoperatoronly when a named admin or API surface must be reachable before admission (k3s API, OpenBao UI). Do not preferoperatoras the debug default for every new rapp — that grows an ops-bridge catalog by accident. publicrequires both an ADR-0006 binding ofproduction-approvedand an explicit exposure grant. Do not keypublicoffrapp.yamlreadiness_state(it has noproduction-approved) and do not add that value to the rapp enum in this workplan.- A reef has no
production-approvedlifecycle. A public reef surface (host port, public DNS we publish) is a substrate grant, not a binding admission. T01 must name that grant shape separately. - A working deploy, a
hosts_rail/binds_rappline, or an Ingress object is not a grant. - A grant is accepted residual risk (reason, approved_on, residual-risk owner, hostname or port). It is not a self-serve wish in the declaration.
- 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. 6443/ the k3s API is not a grantable public surface. RAIL-HO-WP-0009 already closed it; this contract must not become a way to reopen it.
Do not unify the three readiness enums in this workplan. Exposure sits beside them. Record the rule as ADR-0008 (sibling), not as an edit that mixes "is this binding production-safe?" with "who may reach it?"
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 rapp/rail grant exists |
reef-* |
host ports and public DNS we publish | 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.
Boundaries
This workplan may:
- add ADR-0008 for exposure posture and point ADR-0006 at it
- extend family schemas and
tools/validate-family-declarations.py - write the exposure contract and a dated exception snapshot
- route implementation work to owning repos (intake notes or State Hub messages). The owning-repo agent files the workplan. This repo does not implement there and does not silently create four sibling workplans as a side effect of registration.
It must not:
- implement NetworkPolicy, Ingress classes, UFW rules, or ops-bridge tunnels here
- take down live public services as a side effect of the contract
- put booked cost, credentials, or customer numbers in declarations
- inventory CoulombCore host ports (that stays with RAIL-HO-WP-0009 /
railiance-infrauntil CoulombCore is a declared reef) - absorb Q7 / Goss declared-vs-live reaction (page a human, open a
task, block a rollout). Inbox
ae73593ahanded that torailiance-master; it is a separate workplan - treat provider-native internet APIs (Scaleway S3, other provider-delegated endpoints) as the same grant as a listener we open. This contract covers listeners we control: host ports, Ingress, Service types, and public DNS we publish
- migrate live family declarations onto the new field (owning repos, via T05)
- become a living port inventory. After T04, updates live in the owning declarations
Tasks
T01 — Ratify the exposure rule
id: RMASTER-WP-0023-T01
status: done
priority: high
state_hub_task_id: "40d50848-d59e-4f7c-98df-a442dce90439"
Human review. Accept or amend the recommended table above. Record ADR-0008.
Name the default posture for a new reef, a new rail, and a new rapp
(recommended: private for all three). Confirm that public keys off
the binding admission state and that a reef public surface uses a
substrate grant. Confirm 6443 is not grantable as public.
Done when: ADR-0008 is accepted, the three readiness enums are left
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
id: RMASTER-WP-0023-T02
status: done
priority: high
state_hub_task_id: "96db2a2d-670c-4b98-b6f3-860f9b3009b3"
Depends on T01.
One contract, docs/exposure-posture-contract.md, that says what each
posture means per family:
| Family | private |
operator |
public |
|---|---|---|---|
reef-* compute |
no extra host ports; no public DNS for new members | ops-bridge / SSH only | declared host/DNS surface plus substrate grant |
reef-* provider-delegated |
we publish no extra name | n/a | out of this enum; provider API is not a listener we open |
rail-* |
no public Ingress class; default-deny NetworkPolicy | tunnel to the rail's API or admin path | rail may emit public Ingress for a granted binding |
rapp-* |
ClusterIP / in-cluster DNS only | named tunnel (local port) on the same ClusterIP | named public hostname plus binding grant |
Include the fail-closed rule: missing field means private.
Include the conflict rule: rapp public + reef without a public
substrate grant is invalid.
Done when: implementation repos can implement without inventing vocabulary.
Completed 2026-08-15: docs/exposure-posture-contract.md names the
per-family reading, grant shape, fail-closed default, conflict rule,
6443 exclusion, and provider-delegated carve-out.
T03 — Schema and validator
id: RMASTER-WP-0023-T03
status: done
priority: high
state_hub_task_id: "03070be7-925e-4aee-9938-85b03b8cbe37"
Depends on T01.
Add exposure (or the T01 name) to the family schemas. Share the
vocabulary; do not pretend one object shape fits all three. Validator
must fail when:
- a rapp or rail posture is
publicand the relevant binding is notproduction-approved - posture is
publicand no grant object is present (hostname / port / reason / approved_on / residual-risk owner) - a rapp is
publicand its reef has not granted a public surface
Keep the field additive so existing declarations do not break the day the schema lands. Requiring the field on live declarations is a T05 migration in the owning repos, not a T03 done-when.
Name the field so it cannot be confused with rapp
data_classification: public.
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. Live sibling repos are unchanged.
Completed 2026-08-15: additive exposure on all three family schemas.
Validator rejects unapproved public rapps, missing grants, public rapps
on a private reef, port 6443, and public provider-delegated reefs.
Fixtures: good-exposure and bad-exposure. Live declarations omitted
the field and still validate.
T04 — Exception snapshot for already-public surfaces
id: RMASTER-WP-0023-T04
status: done
priority: medium
state_hub_task_id: "6576cc7c-6c85-4d3b-b0cc-dfba4e9b4774"
Dated snapshot in this repo so the contract does not pretend
reef-railiance is already private. Seed from what is already named:
Forgejo, Coulomb Social, Host Europe Nydus 2224, then fill anything else
found on that reef.
Each entry: hostname or port, owning rapp or provider, why it is public, residual-risk owner. Same shape T01 names for a grant.
This snapshot is not the living source of truth. T05 files the grant objects into the owning declarations. Later changes happen there.
Out of scope: CoulombCore, shutdown plans, provider-native APIs.
Done when: every known public surface on reef-railiance is either a
snapshot grant or a routed task to close it.
Completed 2026-08-15:
docs/evidence/reef-railiance-exposure-snapshot-2026-08-15.md.
Grants: Forgejo, Coulomb Social, reuse-surface, Nydus 2224. Routed:
bao.coulomb.social verify/close; CoulombCore names stay out.
T05 — Route enforcement work
id: RMASTER-WP-0023-T05
status: done
priority: high
state_hub_task_id: "d780df59-c67d-486e-8e8a-15e5043cd513"
Depends on T01 and T02.
Write intake notes here or send State Hub messages to the owning-repo agents. Those agents file the workplan in their repo. 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 private (or operator only for a named admin path); existing public binds receive the T04 grants |
railiance-infra |
new reefs do not get 80/443 until a reef substrate grant exists; 6443 stays operator-only; this is 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.
Completed 2026-08-15: intakes in
docs/exposure-enforcement-intakes.md. Child workplans filed:
RAIL-K8S-WP-0003, REEF-RAILIANCE-WP-0004, RAIL-HO-WP-0010,
RAIL-EN-WP-0001.
Suggested review order
- T01 (human) — accept or change the recommended table.
- T04 can start now as a census; its grant fields lock after T01.
- T02 after T01.
- T03 after the field names are stable.
- T05 last; it is the only task that creates work in other repos.
Outcome
Finished 2026-08-15. ADR-0008, the exposure contract, additive schemas, validator fixtures, the reef-railiance snapshot, and routed intakes are in this repo. Packet-level enforcement stays with the owning repos.