workplan: refine RMASTER-WP-0023 after review
Accept the workplan home and tighten scope: exposure sits beside ADR-0006 as ADR-0008, public keys off binding admission, T04 is a reef-railiance snapshot, enforcement is routed, and Q7 / provider APIs stay out.
This commit is contained in:
parent
48dba891b5
commit
fb1b7dae73
1 changed files with 144 additions and 64 deletions
|
|
@ -4,22 +4,29 @@ type: workplan
|
|||
title: "Private-by-default exposure until production admission"
|
||||
domain: financials
|
||||
repo: railiance-master
|
||||
status: proposed
|
||||
status: ready
|
||||
owner: bernd
|
||||
topic_slug: railiance
|
||||
planning_priority: high
|
||||
planning_order: 23
|
||||
created: "2026-08-15"
|
||||
updated: "2026-08-15"
|
||||
related:
|
||||
- ADR-0006
|
||||
- RMASTER-WP-0017
|
||||
- RMASTER-WP-0019
|
||||
- RMASTER-WP-0021
|
||||
- RMASTER-WP-0022
|
||||
- RAIL-HO-WP-0009
|
||||
related_repos:
|
||||
- railiance-master
|
||||
- rail-kubernetes
|
||||
- railiance-cluster
|
||||
- reef-railiance
|
||||
- reef-storage
|
||||
- railiance-infra
|
||||
- railiance-enablement
|
||||
- ops-bridge
|
||||
---
|
||||
|
||||
# RMASTER-WP-0023 — private-by-default exposure until production admission
|
||||
|
|
@ -45,7 +52,7 @@ 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 shares `railiance01` ingress. RAIL-HO-WP-0009
|
||||
closed the hand-opened k3s API; do not make S1 the admission desk for
|
||||
rapps.
|
||||
rapps. Do not use this workplan to re-public `6443`.
|
||||
- `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
|
||||
|
|
@ -56,14 +63,19 @@ It does **not** belong as the system owner in:
|
|||
not own the rule.
|
||||
- `reef-railiance` alone — 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.
|
||||
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`) — no
|
||||
`exposure` / `network_posture` field.
|
||||
`exposure` / `network_posture` field. Rapp already uses `public` on
|
||||
`data_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`
|
||||
|
|
@ -76,47 +88,97 @@ It does **not** belong as the system owner in:
|
|||
- 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 to ratify (T01)
|
||||
|
||||
Proposed default, for review:
|
||||
Recommended default, for review. T01 may amend; it should not start from
|
||||
a blank page.
|
||||
|
||||
| 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 |
|
||||
| `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:
|
||||
|
||||
1. New reefs, rails, and rapps default to `private` or `operator`.
|
||||
Prefer `operator` when a human must debug before admission.
|
||||
2. `public` requires **both** `production-approved` (ADR-0006 binding, or
|
||||
the equivalent rail/rapp state this workplan chooses) **and** an
|
||||
explicit exposure grant in the declaration.
|
||||
3. A working deploy, a `hosts_rail` / `binds_rapp` line, or an Ingress
|
||||
1. New reefs, rails, and rapps default to `private`. Use `operator` only
|
||||
when a named admin or API surface must be reachable before admission
|
||||
(k3s API, OpenBao UI). Do not prefer `operator` as the debug default
|
||||
for every new rapp — that grows an ops-bridge catalog by accident.
|
||||
2. `public` requires **both** an ADR-0006 binding of
|
||||
`production-approved` **and** an explicit exposure grant. Do not key
|
||||
`public` off `rapp.yaml` `readiness_state` (it has no
|
||||
`production-approved`) and do not add that value to the rapp enum in
|
||||
this workplan.
|
||||
3. A reef has no `production-approved` lifecycle. 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.
|
||||
4. A working deploy, a `hosts_rail` / `binds_rapp` line, or an Ingress
|
||||
object is not a grant.
|
||||
4. Ops-bridge is the normal path to a shielded thing. That is the same
|
||||
5. 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.
|
||||
6. 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.
|
||||
7. `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 unless T01
|
||||
explicitly decides to. Exposure can sit beside them.
|
||||
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:
|
||||
|
||||
- amend ADR-0006 or add a sibling ADR for exposure posture
|
||||
- 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 the exception register
|
||||
- **route** implementation workplans to owning repos
|
||||
- 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, or UFW rules here
|
||||
- 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-infra` until CoulombCore is a declared reef)
|
||||
- absorb Q7 / Goss declared-vs-live *reaction* (page a human, open a
|
||||
task, block a rollout). Inbox `ae73593a` handed that to
|
||||
`railiance-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
|
||||
|
||||
|
|
@ -128,33 +190,39 @@ 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`).
|
||||
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:** the ADR is accepted and the three readiness enums are
|
||||
either left distinct on purpose or given a written unification plan.
|
||||
**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.
|
||||
|
||||
### T02 — Write the exposure contract
|
||||
|
||||
```task
|
||||
id: RMASTER-WP-0023-T02
|
||||
status: todo
|
||||
status: wait
|
||||
priority: high
|
||||
```
|
||||
|
||||
Depends on T01.
|
||||
|
||||
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 |
|
||||
| `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.
|
||||
|
|
@ -169,23 +237,28 @@ 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:
|
||||
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:
|
||||
|
||||
- posture is `public` and the relevant admission/readiness state is not
|
||||
production-approved
|
||||
- a rapp or rail posture is `public` and the relevant **binding** is not
|
||||
`production-approved`
|
||||
- posture is `public` and no grant object is present (hostname / port /
|
||||
reason / approved_on)
|
||||
- a live family declaration omits the field after the migration window
|
||||
reason / approved_on / residual-risk owner)
|
||||
- a rapp is `public` and its reef has not granted a public surface
|
||||
|
||||
Keep the field additive first so existing declarations do not break the
|
||||
day the schema lands; then require it.
|
||||
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.
|
||||
bad fixtures. Live sibling repos are unchanged.
|
||||
|
||||
### T04 — Exception register for already-public surfaces
|
||||
### T04 — Exception snapshot for already-public surfaces
|
||||
|
||||
```task
|
||||
id: RMASTER-WP-0023-T04
|
||||
|
|
@ -193,18 +266,23 @@ 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.
|
||||
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.
|
||||
|
||||
Do not invent a shutdown plan here. The register exists so the contract
|
||||
does not pretend the reef is already private.
|
||||
Each entry: hostname or port, owning rapp or provider, why it is public,
|
||||
residual-risk owner. Same shape T01 names for a grant.
|
||||
|
||||
**Done when:** every known public surface is either a grant or a routed
|
||||
task to close it.
|
||||
This snapshot is not the living source of truth. T05 files the grant
|
||||
objects into the owning declarations. Later changes happen there.
|
||||
|
||||
### T05 — Route enforcement workplans
|
||||
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.
|
||||
|
||||
### T05 — Route enforcement work
|
||||
|
||||
```task
|
||||
id: RMASTER-WP-0023-T05
|
||||
|
|
@ -214,28 +292,30 @@ 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.
|
||||
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 `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 |
|
||||
| `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.
|
||||
**Done when:** each owning repo has a workplan or an accepted intake,
|
||||
with a back-link to this ID.
|
||||
|
||||
## Suggested review order
|
||||
|
||||
1. T01 (you) — accept or change the table.
|
||||
2. T02 + T04 can proceed in parallel after T01.
|
||||
3. T03 after the field names are stable.
|
||||
4. T05 last; it is the only task that creates work in other repos.
|
||||
1. T01 (human) — accept or change the recommended table.
|
||||
2. T04 can start now as a census; its grant fields lock after T01.
|
||||
3. T02 after T01.
|
||||
4. T03 after the field names are stable.
|
||||
5. 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.
|
||||
Reviewed 2026-08-15 against current repo state (`status: ready`). T01
|
||||
still needs human ratification of ADR-0008. Implementation stays out of
|
||||
this repo.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue