Operator direction: an ungraded lane inherits the default its maturity context implies — accepted in experimental context, high or critical in production. M0-M3 is the right ladder and already carries rank, phase, max_dataclass and promotion gates; what it lacks is a join to lanes, which is T02's gap. .repo-classification.yaml category cannot carry it: railiance-platform, which runs production OpenBao and owns three of RISK-F-0003's five exposed lanes, is category tooling, while net-kingdom, a canon docs repo, is product. It orders work mode, not blast radius. Also records that maturity must come from the lane's owner, not the repo holding the catalog, and that 'accepted' is an acceptance rather than a grade — it needs an owner and an expiry, so it is a second field, not a rung. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
487 lines
26 KiB
Markdown
487 lines
26 KiB
Markdown
---
|
||
id: ZONE-WP-0001
|
||
type: workplan
|
||
title: "The security zone model — derive it from the estate, draft it as canon"
|
||
domain: infotech
|
||
repo: zone-engine
|
||
status: proposed
|
||
owner: zone-engine
|
||
topic_slug: netkingdom
|
||
planning_priority: P1
|
||
created: "2026-08-19"
|
||
updated: "2026-08-19"
|
||
state_hub_workstream_id: "a75c8c9c-c108-4831-8d72-4343cce49879"
|
||
---
|
||
|
||
# ZONE-WP-0001 — The security zone model
|
||
|
||
Ported from `WARDEN-WP-0032` (ops-warden), which deferred `policy.enabled: true`
|
||
under `ADR-0006` and named this repo as its successor. ops-warden keeps only its
|
||
consumer-side task.
|
||
|
||
**This workplan produces a model and a canon draft. It does not produce an API,
|
||
a schema, or storage.** An engine that ships a schema before it has partitioned
|
||
the real estate defines the model by accident, through whatever its first wire
|
||
format happened to be. Whether a runtime is warranted is an *output* of T02–T03,
|
||
not an assumption of T01.
|
||
|
||
## The gap in one table
|
||
|
||
| Axis | Shipped | Answers |
|
||
| --- | --- | --- |
|
||
| Environment posture (`dev`/`test`/`prod`) | ops-warden WP-0015 | Where does this run? |
|
||
| Workload maturity (`M0`–`M3`) | ops-warden WP-0015 | How ready is this workload? |
|
||
| `organization_posture: build` | ops-warden WP-0029 | What state is the org in? |
|
||
| **Enforcement stance** | **missing** | **Is this control enforced here, and what happens when it fails?** |
|
||
|
||
The first three describe. Nothing decides. That is why `policy.enabled` is a
|
||
repo-wide boolean, and why turning it on would have hardened the access needed
|
||
to perform the refactors currently underway.
|
||
|
||
## Do not reinvent these
|
||
|
||
**`net-kingdom/canon/standards/tenancy-posture_v0.1.md`** — the structural model
|
||
to follow: orthogonal axes, graduated levels, a per-repo declaration
|
||
(`tenancy.yaml`) with current/implemented/target vectors, evidence, and a review
|
||
date; §6 *conformance is accuracy, not altitude*; §14 adoption stance
|
||
*structure, not tooling*. That last one is why this workplan is shaped as it is.
|
||
|
||
**Reefs** (`repo-manager/docs/RailianceAppDeploymentGuide.md`) — `reef-railiance`
|
||
carries a single-node/shared-control-plane residual risk requiring explicit
|
||
acceptance. Substrate placement with risk attached is *adjacent* to a zone and
|
||
must be reconciled with it, not duplicated. repo-manager's own rule — *topology
|
||
is not readiness* — restates here as **placement is not posture**.
|
||
|
||
**flex-auth** — the PDP, `service_class: latency-critical`, already consuming a
|
||
compiled registry snapshot built by
|
||
`ops-warden/scripts/build_flex_auth_registry.py`. Zone membership reaches the
|
||
decision point by **compilation**. A synchronous zone lookup in the decision
|
||
path would be a regression dressed as an architecture.
|
||
|
||
> **flex-auth reviewed this workplan on 2026-08-19** as the PDP that would have
|
||
> to live with the result, and amended T01, T03, T04 and T05 in place. The three
|
||
> substantive changes: the "only PDP" invariant needs to bind *authority* and not
|
||
> only latency (T01); membership compiles to the registry but **stance belongs in
|
||
> the policy package**, and fail-open is not expressible by a PDP at all (T03);
|
||
> and flex-auth has **no reload path**, which decides how a time-boxed exception
|
||
> can possibly expire (T04). Membership itself needs **no registry schema
|
||
> change** — but do not land it on the existing `trust_zone` field (T05).
|
||
|
||
**`warden plan`** (ops-warden WP-0029) — already returns
|
||
`autonomous`/`founder_required`/`unroutable` from properties of the thing, with
|
||
the `reasons` that produced the verdict. Zone-aware enforcement extends that
|
||
machinery; it does not grow a second vocabulary beside it.
|
||
|
||
## Tasks
|
||
|
||
```task
|
||
id: ZONE-WP-0001-T01
|
||
status: todo
|
||
priority: high
|
||
state_hub_task_id: "ea74316e-b711-4888-9b38-553ebc9ad856"
|
||
```
|
||
|
||
**Confirm ownership on the record, and say what publishes where.** This repo was
|
||
seeded on a recommendation, not yet on agreement. Take to `net-kingdom` (canon)
|
||
and `flex-auth` (the enforcing PDP): zone-engine owns the model and the
|
||
exception lifecycle; canon publishes the standard, in the family of
|
||
`tenancy-posture_v0.1` and the `*-engine` boundary contracts; flex-auth stays
|
||
the only PDP and consumes compiled membership.
|
||
|
||
If either disagrees, the model moves and this repo is archived rather than kept
|
||
for its own sake. Record the outcome either way.
|
||
|
||
**flex-auth answered 2026-08-19 (amended by flex-auth).** Confirmed: zone-engine
|
||
owns zone identity, membership and exception lifecycle; flex-auth stays the only
|
||
PDP. But the invariant as written in `GOAL.md` guards the wrong property. *"Never
|
||
a synchronous lookup in a decision path"* is a **latency** guarantee. It does not
|
||
guarantee flex-auth remains the only decision point, because compiled data that
|
||
determines an outcome is still deciding — it just decided earlier. Under the
|
||
invariant as written, zone-engine could compile `enforced: false` for a lane and
|
||
flip `warden sign` from deny to allow without a flex-auth policy change, and
|
||
still be literally compliant.
|
||
|
||
The invariant flex-auth confirms is the tighter one:
|
||
|
||
> zone-engine is the authority over zone **identity and membership**. The
|
||
> **effect** of a zone on any decision flex-auth renders is expressed in a
|
||
> flex-auth policy package — versioned, fixtured, tested, and stamped into the
|
||
> decision record. Membership is data and is compiled. Stance is a rule and is
|
||
> policy.
|
||
|
||
Rationale is in T03. Please carry the tightened wording into `GOAL.md`; the
|
||
current wording is not wrong, it is just not load-bearing.
|
||
|
||
**net-kingdom answered 2026-08-19 (amended by net-kingdom, as canon owner of
|
||
`tenancy-posture_v0.1`).** Confirmed on both counts, and one part of the
|
||
question is taken off this repo.
|
||
|
||
**1. Enforcement stance is a separate standard, not a seventh axis of
|
||
`tenancy-posture`.** Recorded as that standard's Decision 5.6 (draft-9). The
|
||
reason is structural, not territorial, and it matters for how the zone model is
|
||
shaped:
|
||
|
||
- **Every one of the six axes is monotone** — higher is stronger and higher is
|
||
what a service wants. `current`/`target`/`gap`, §12's *improve* and *guard*
|
||
steps, and §6's special allowance for a permanently-low level all depend on
|
||
it. **Enforcement stance is not monotone.** `ADR-0006` is precisely the
|
||
finding that the top rung is the wrong answer for the SSH lane the tunnels
|
||
depend on. A ladder whose top is sometimes wrong is an enumeration, and
|
||
embedding one in the vector would break the six that are ladders. §8.3
|
||
refused a QoS axis on a weaker version of this test.
|
||
- **`tenancy-posture` is descriptive; stance is prescriptive.** §6 is *accuracy,
|
||
not altitude*, and it is safe only because the framework blocks nothing by
|
||
itself. Prescription enters through Decision 8.2, where a **requirer** sets a
|
||
minimum and a machine reconciles it against declarations. Fold stance in as an
|
||
axis and an accurately declared `exempt` becomes conformant *and* exempt — a
|
||
conformance rule handing out the exemption it exists to audit.
|
||
|
||
This is the same split flex-auth argued in T03, arrived at from the canon side:
|
||
**membership is data and is declared; stance is a rule and belongs to the
|
||
control's owner.** Membership behaves like a level. Stance behaves like a tier
|
||
minimum.
|
||
|
||
**Binding on the declaration format (this pre-empts part of T05).** To avoid the
|
||
parallel-framework outcome this message was sent to prevent, canon rules that
|
||
**zone membership is declared in `tenancy.yaml` under a reserved top-level
|
||
`zones:` key** — sibling to `tenancy:` and `provider:`, never inside
|
||
`tenancy.current`. §5.4 makes that file the repo's single posture declaration
|
||
surface and a second root file recreates the divergence §5.4 ended. The key is
|
||
already reserved in `canon/schemas/tenancy-posture_v0.1.schema.json` and is
|
||
deliberately unconstrained there, so the validator will not reject a combined
|
||
declaration while `security-zones_v0.1` is still being drafted. One file, one
|
||
review cadence, one validator; two standards, because they have different owners
|
||
and different conformance semantics.
|
||
|
||
**On `organization_posture` (asked in T02 and in `WARDEN-WP-0032-T01`):** it does
|
||
not belong in a per-repo declaration under either key. It is a fleet-wide,
|
||
time-varying scalar describing the *estate*, not a property of the declaring
|
||
service, and a per-repo copy of a global goes stale in as many places as there
|
||
are repos. Read it as an **input** to stance selection; do not absorb it.
|
||
|
||
**2. Reefs — `zone-engine` is right, and the reconciliation is not yours.**
|
||
*Placement is not posture* is correct and should stay. But the pairing has been
|
||
misidentified: the unreconciled pair is not zone ↔ reef, it is **reef ↔ `P` and
|
||
`V`**, and it is a defect in `tenancy-posture`, not a gap in the zone model.
|
||
`P` grades tenant data isolation *within a datastore*; a reef is a named compute
|
||
substrate with an accepted residual risk, and there is no rung for that. The
|
||
live consequence is on `V`: `reef-railiance` is single-node with a shared
|
||
control plane, which caps `V` for everything bound to it under Decision 4.6.1,
|
||
and nothing joins those facts today. That is Decision 5.5's provider-declaration
|
||
finding one layer down — a reef is a provider with nowhere to state what it
|
||
makes reachable.
|
||
|
||
Recorded as `tenancy-posture` Decisions 8.4.1 and 8.4.2 and taken as
|
||
`net-kingdom`'s `NK-WP-0027`. **`ZONE-WP-0001` is not blocked on it.** Delete the
|
||
reef question from T02's list of things this model must answer; citing
|
||
Decision 8.4.1 is sufficient.
|
||
|
||
One further canon ruling, because three repos had written the same rule in three
|
||
vocabularies — *topology is not readiness* (`railiance-master`), *placement is
|
||
not posture* (here), and §3.1's naming requirement. Decision 8.4.1 now states it
|
||
once: **the substrate a workload sits on is never, by itself, evidence for a
|
||
level on any ladder.** Cite it rather than restating it locally.
|
||
|
||
**Ownership confirmed.** `zone-engine` owns zone identity, membership and the
|
||
exception lifecycle. Canon publishes `security-zones_v0.1.md` beside
|
||
`tenancy-posture_v0.1` and the `*-engine` boundary contracts, owner-driven, as
|
||
WP-0015's maturity model was. The repo is not archived.
|
||
|
||
Carry into `GOAL.md`: the tightened flex-auth invariant, and that the declaration
|
||
surface is `tenancy.yaml`'s `zones:` key rather than a new file.
|
||
|
||
```task
|
||
id: ZONE-WP-0001-T02
|
||
status: done
|
||
priority: high
|
||
state_hub_task_id: "f1016c71-ccb0-46a7-9143-e81c7ac34cad"
|
||
```
|
||
|
||
**Derive the zones from the real estate.** Walk ops-warden's 27 catalog lanes,
|
||
the `adm`/`agt`/`atm` actor inventory, and the workloads carrying posture
|
||
descriptors. The model's test is that it partitions what exists without a
|
||
residue of unexplained exceptions — and the exceptions found are the most
|
||
informative output, not noise to tidy away.
|
||
|
||
Answer explicitly:
|
||
|
||
- Is a zone a property of the **actor**, the **lane**, the **workload**, or the
|
||
**moment**? The CoulombCore decommission and the issue-core move show the last
|
||
is real and hardest to model. A time-boxed relaxation is a *different object*
|
||
from a standing zone; conflating them yields a permanent hole with a
|
||
temporary-sounding name.
|
||
- ~~How do zones relate to **reefs**?~~ **Struck by net-kingdom 2026-08-19.**
|
||
Not this model's question. Cite `tenancy-posture` Decision 8.4.1 — substrate
|
||
location is never by itself evidence for a level — and stop there. The real
|
||
unreconciled pair is reef ↔ `P`/`V` and it is canon's, tracked as `NK-WP-0027`.
|
||
- ~~Do zones **compose with or fold in** `organization_posture`?~~ **Answered by
|
||
net-kingdom 2026-08-19:** do not fold it in. It is a fleet-wide time-varying
|
||
scalar, not a per-service property; consume it as an input to stance selection.
|
||
The *other* two axes (environment posture, `M0`–`M3`) remain an open question
|
||
for this task — they are per-workload and genuinely composable.
|
||
|
||
**Done 2026-08-19.** Full result: `docs/estate-partition-2026-08-19.md`.
|
||
|
||
Four bands, each of the 27 lanes in exactly one — **Decided** (1, the only lane
|
||
ops-warden decides), **Fronted** (10, owner has a front door), **Covered** (12,
|
||
ops-warden proxies what it does not own), **Signposted** (4, routed, nothing
|
||
flows). Derived from declared fields, not invented.
|
||
|
||
The discriminator is `delegation.mode`, not `risk`: 10 of 11 high-risk lanes are
|
||
interim and 8 of those are `exec_capable`. A field added to answer a governance
|
||
question predicts operational danger better than the one named for it.
|
||
|
||
Three results that bind the later tasks:
|
||
|
||
- **Stance is per control per zone, never per zone alone.** The three existing
|
||
controls cut the estate differently — pre-sign gate 1 lane, fetch gate 13,
|
||
read-boundary 11, with only 8 in the overlap. They were built independently
|
||
and their scopes were never reconciled.
|
||
- **Zone is a property of the lane.** The caller (`WARDEN_AGENT_ID`) is a
|
||
*modifier*, not the zone — collapsing them would make one lane need two. And
|
||
the moment is confirmed as a separate object: nothing in the corpus represents
|
||
it, and `delegation.blocked_on` is exactly the expiry-by-intention shape T04
|
||
must not reproduce.
|
||
- **Environment posture and `M0`–`M3` compose but cannot be joined today** —
|
||
they are per-workload, zones are per-lane, and no registry carries a join key.
|
||
Inventing one would be fabrication under §6.
|
||
|
||
**Residue, and the most valuable output:** 14 of 27 lanes carry no `risk` value,
|
||
and `is_high_risk` is `risk == "high"`, so the agent read-boundary never fires
|
||
for them; 5 are `exec_capable`. Routed to `risk-nexus` as **`RISK-F-0003`**.
|
||
Consequence for this workplan: `risk` is not usable as a membership input until
|
||
those lanes are graded.
|
||
|
||
```task
|
||
id: ZONE-WP-0001-T03
|
||
status: todo
|
||
priority: high
|
||
state_hub_task_id: "7f47b750-1ae4-467d-b3c4-064f4ec07dbd"
|
||
```
|
||
|
||
**Model stance and failure mode, per control per zone.** Enforced / advisory /
|
||
exempt, *and* fail-closed or fail-open. The failure mode is where the bite is:
|
||
`fail_closed` is what turns a dead tunnel into an outage, and is precisely why
|
||
the ops-warden flip was deferred.
|
||
|
||
Controls in scope at minimum: the flex-auth pre-sign gate (`policy.enabled`,
|
||
`fail_closed`), the agent read-boundary on `risk: high` lanes (ops-warden
|
||
`ADR-0004`), and the `warden plan` escalation verdicts.
|
||
|
||
**Operator direction 2026-08-19 — defaults derive from maturity, not from
|
||
nothing.** An ungraded lane must not inherit the safest-for-the-tool default; it
|
||
should inherit the default its *maturity context* implies. Early or experimental
|
||
context: an absent grade is tolerable and explicitly accepted. Production
|
||
context: an absent grade is `high`, or `critical` where the context is critical.
|
||
This is the same rule `RISK-F-0003` needs and is why that finding's fix is a
|
||
model question, not a cleanup.
|
||
|
||
Three things established while checking what this can attach to:
|
||
|
||
- **The right ladder already exists: `M0`–`M3`** in ops-warden's
|
||
`registry/policy/security-posture.yaml`. It is ordered (`rank`), phased
|
||
(`experimental-poc` → `alpha-early-access` → …), and already carries a data
|
||
ceiling per rung (`max_dataclass`: `synthetic` → `internal` → …) plus
|
||
`promotion_gate`. It is exactly the shape this rule needs. What it lacks is a
|
||
**join** to lanes or repos — the gap T02 found.
|
||
- **`.repo-classification.yaml` `category` cannot carry it.** It is populated
|
||
estate-wide (54 `project`, 23 `tooling`, 14 `product`, 7 `research`, 4
|
||
`experimental`) and canon calls it "work mode, maturity, or organizational
|
||
purpose" — but it is unordered, and the actual values invert the property we
|
||
need: `railiance-platform`, which runs production OpenBao and owns three of
|
||
the five lanes `RISK-F-0003` exposes, is `tooling`; `ops-warden`, a production
|
||
SSH CA, is `tooling`; `net-kingdom`, a canon-documentation repo, is `product`.
|
||
Deriving a safety default from it would grade production credential
|
||
infrastructure below a documentation repo. `criticality` exists in the rapp
|
||
vocabulary (`low`/`medium`/`high`/`critical`) but is unset on every repo
|
||
checked.
|
||
- **Whose maturity — the owner's, not the holder's.** All 27 lanes live in
|
||
ops-warden's catalog, but their `owner_repo` is elsewhere. Deriving from the
|
||
repo that holds the pointer would give every lane ops-warden's classification,
|
||
which says nothing about the credential. Membership must derive from the lane's
|
||
owner or the workload it serves.
|
||
|
||
**The principle validates against the data:** all five lanes `RISK-F-0003`
|
||
exposes are owned by production-serving components, so "unclassified in a
|
||
production context → high" would have caught every one.
|
||
|
||
**One vocabulary caution for T05.** `accepted` is not a grade. A grade says how
|
||
dangerous a lane is; an acceptance says who agreed to carry that danger, and
|
||
therefore needs an owner and a date. Putting an acceptance in a descriptive
|
||
field repeats the error net-kingdom rejected when it refused stance as a
|
||
tenancy axis. Model them as two fields: grade (derived, defaultable) and
|
||
acceptance (asserted, owned, expiring).
|
||
|
||
**Amended by flex-auth 2026-08-19 — split membership from stance.** ops-warden's
|
||
option (c) ("zones as policy-package data") was rejected wholesale because the
|
||
model must govern two controls flex-auth does not evaluate. flex-auth agrees with
|
||
the premise and disagrees with the conclusion: (c) was rejected at the wrong
|
||
granularity. *Where a stance is evaluated belongs with the control's enforcement
|
||
point*, and there are three enforcement points here, not one:
|
||
|
||
| Control | Enforcement point | Where its per-zone stance belongs |
|
||
| --- | --- | --- |
|
||
| flex-auth pre-sign gate | flex-auth (PDP) + `warden sign` (PEP) | **flex-auth policy package** |
|
||
| Agent read-boundary (`ADR-0004`) | ops-warden CLI | ops-warden — flex-auth never sees it |
|
||
| `warden plan` escalation verdicts | ops-warden CLI | ops-warden — flex-auth never sees it |
|
||
|
||
So (b) is right for two of three controls and wrong for the one that is
|
||
flex-auth's. Concretely, for the pre-sign gate:
|
||
|
||
- **Membership → compiled registry.** Which zone an actor/lane is in is data,
|
||
varies per resource, changes as the estate changes, and is already produced by
|
||
`build_flex_auth_registry.py`. Compile it.
|
||
- **Stance → policy package.** "In zone X the gate is advisory" is a rule about
|
||
outcomes. It belongs in the artifact that already carries fixtures, rego tests,
|
||
`Validate()`, a version, and a status.
|
||
|
||
The concrete defect in compiling stance: every flex-auth decision envelope stamps
|
||
`matched_policy_version`, `policy_package` and `policy_status`, and the decision
|
||
id is derived from policy metadata plus request
|
||
(`internal/decision/engine.go:358`). Registry content is **not** in that
|
||
provenance. If zone stance lives in the registry, a stance change alters the
|
||
verdict while the decision record still reports the same policy version — two
|
||
decisions, same version, same request, different effect, and nothing in the audit
|
||
trail explains why. That breaks flex-auth's reproducibility contract for exactly
|
||
the control this workplan exists to make safe.
|
||
|
||
**Second correction: flex-auth cannot express fail-open.** A PDP returns an
|
||
effect. "Fail-open" describes what the caller does when flex-auth is
|
||
*unreachable* — that is a property of ops-warden's client (`fail_closed` in
|
||
`warden.yaml`), and no zone data compiled into flex-auth can influence it. Model
|
||
the failure-mode axis as **PEP-side**, owned by each consumer's declaration, not
|
||
as something the PDP carries. `enforced`/`advisory`/`exempt` *is* expressible by
|
||
flex-auth (as effect plus an advisory annotation in the envelope); fail-open is
|
||
not, and a model that implies otherwise will be built and then not work.
|
||
|
||
```task
|
||
id: ZONE-WP-0001-T04
|
||
status: todo
|
||
priority: high
|
||
state_hub_task_id: "1c542ca5-2b82-4778-a3e4-c76e932423c9"
|
||
```
|
||
|
||
**Design the time-boxed exception — this is the load-bearing task.** A standing
|
||
classification needs no engine; canon plus a declaration file covers it. An
|
||
exception with an expiry is *state*, and state wants an owner. Whether this repo
|
||
earns a runtime is decided here.
|
||
|
||
Must specify: who may grant, against which zone and control, for how long, with
|
||
what recorded justification; how expiry is **enforced rather than intended**;
|
||
how an active exception is visible to someone who did not grant it; and what
|
||
happens at expiry — auto-revert, or block until renewed.
|
||
|
||
If T04 concludes the lifecycle can live as reviewed declarations in git, say so
|
||
plainly and drop the runtime. That is a valid and cheaper outcome.
|
||
|
||
**Hard constraint from flex-auth 2026-08-19 — the PDP has no reload path.**
|
||
`cmd/flex-auth/main.go:447` calls `registry.LoadFile` once at process start.
|
||
There is no watcher, no SIGHUP, no reload endpoint. Both the registry snapshot
|
||
*and* the policy package are baked into the container image and the production
|
||
pins are digest-pinned (`values/ops-warden.yaml`,
|
||
`sha256:138aa347…`). Changing either means: rebuild image → new digest → helm
|
||
upgrade → pod restart.
|
||
|
||
This is the input T04 most needs, and it kills one design outright:
|
||
|
||
- **An exception compiled as inert registry data cannot have an enforced
|
||
expiry.** It expires when a human rebuilds and redeploys. That is expiry by
|
||
intention — precisely what this task says it must not be.
|
||
- **An exception carrying its own `not_after` timestamp, evaluated against
|
||
decision time in rego, does have an enforced expiry.** OPA has a clock
|
||
(`time.now_ns()`); the grant costs a redeploy but the *lapse* is automatic and
|
||
needs nobody. The zone widens by deploy and narrows by itself, which is the
|
||
correct asymmetry.
|
||
|
||
That is a third argument for stance-in-policy independent of the audit one in
|
||
T03, and it also bears on whether this repo earns a runtime: if expiry is
|
||
evaluated at decision time from a compiled timestamp, the lifecycle needs a
|
||
*record and a review*, not a service. flex-auth's read is that this points at the
|
||
cheaper outcome, but it is T04's call.
|
||
|
||
```task
|
||
id: ZONE-WP-0001-T05
|
||
status: todo
|
||
priority: medium
|
||
state_hub_task_id: "f4556ca0-cb26-46ed-9351-d00270bd2309"
|
||
```
|
||
|
||
**Declaration format and conformance rule.** Follow `tenancy.yaml`: per-repo,
|
||
with evidence, `reviewed`, and `review_due`; carry over *accuracy, not
|
||
altitude*. Include how a zone assignment changes and how that change is
|
||
observed — a zone that can be quietly widened is not a boundary.
|
||
|
||
**Carrier file settled by net-kingdom 2026-08-19 (see T01).** The declaration
|
||
rides `tenancy.yaml` under a reserved top-level `zones:` key, not a new root
|
||
file. The key is already permitted and deliberately unconstrained in
|
||
`net-kingdom/canon/schemas/tenancy-posture_v0.1.schema.json`, so this task
|
||
defines the shape inside `zones:` and nothing else. What remains open here is
|
||
unchanged: the `trust_zone` collision, and whether membership rides the subject
|
||
or the resource.
|
||
|
||
**Two carrier facts from flex-auth 2026-08-19 (no schema change needed).**
|
||
Compiled zone membership is expressible in flex-auth's registry format **today**,
|
||
with zero Go changes. Every registry entity carries `metadata`; `api.Resource`
|
||
additionally carries `labels`, `owner`, `attributes` and `trust_zone`, and
|
||
`internal/decision/engine.go:275` flattens all of them into
|
||
`input.resource.attributes` before evaluation. `subject.metadata` and
|
||
`subject.claims` are flattened the same way (`engine.go:257`). A compiler writing
|
||
`"security_zone": "..."` into a resource's `attributes` makes it readable from
|
||
rego as `input.resource.attributes.security_zone`. Nothing to build.
|
||
|
||
Two things to decide anyway, and both belong in this task:
|
||
|
||
1. **Do not reuse `trust_zone` — this collision has already happened inside the
|
||
PDP.** `api.Resource.TrustZone` is a first-class field, is surfaced into rego
|
||
input, and `ops-warden/scripts/build_flex_auth_registry.py:77` sets it to the
|
||
hardcoded constant `"platform"` on every ssh-certificate resource. No policy
|
||
package reads it. It is a dormant, plausibly-named, semantically-empty field
|
||
sitting exactly where a reader would expect zone membership to go. `SCOPE.md`
|
||
warns that "zone" is overloaded and names network segmentation as the hazard;
|
||
the actual live collision is this one. Name the new concept `security_zone`
|
||
explicitly, or deliberately repurpose `trust_zone` and say so loudly — but do
|
||
not let both exist meaning different things.
|
||
|
||
2. **Say whether membership rides the subject or the resource.** In the
|
||
ops-warden registry an actor appears as *both*: as a subject
|
||
(`agt-state-hub-bridge`) and as a per-actor resource
|
||
(`ssh-cert:actor/agt-state-hub-bridge`). If T02 concludes a zone is a property
|
||
of the actor, the compiler must be told which record carries it — or write
|
||
both and make that the contract. Leaving it implicit produces two compilers
|
||
that disagree. This is a compiler contract, not a schema change.
|
||
|
||
```task
|
||
id: ZONE-WP-0001-T06
|
||
status: todo
|
||
priority: medium
|
||
state_hub_task_id: "8a000c18-6ee7-4328-99c7-53c490435316"
|
||
```
|
||
|
||
**Draft the canon standard and offer it.** `security-zones_v0.1.md` for
|
||
`net-kingdom/canon/standards/`, drafted here, published there. Owner-driven
|
||
landing, as ops-warden's WP-0015 maturity model was.
|
||
|
||
```task
|
||
id: ZONE-WP-0001-T07
|
||
status: todo
|
||
priority: medium
|
||
state_hub_task_id: "c7ea24df-8aa9-4ead-ba3a-033aa5201c21"
|
||
```
|
||
|
||
**Prove it on two consumers.** A model only its author honours is not adopted.
|
||
The first is ops-warden: retire `policy.enabled` for a zone-aware control,
|
||
closing `WARDEN-WP-0031-T05`. Re-run
|
||
`ops-warden/scripts/check_policy_caller_identity.py` first — the 2026-08-19
|
||
evidence (`decision:f3f7c88f9585582a`) will be stale, and re-establishing it is
|
||
cheap by design. The second consumer comes from T01.
|
||
|
||
## Related
|
||
|
||
- ops-warden `ADR-0006` — enforcement is zone-scoped, never a global flag
|
||
- ops-warden `WARDEN-WP-0032` — the ops-warden-side stub this was ported from
|
||
- ops-warden `WARDEN-WP-0031` — the deferred flip, and the readiness evidence
|
||
- flex-auth `FLEX-WP-0016` — the enforcing pin with no enforcing consumer
|
||
- `net-kingdom/canon/standards/tenancy-posture_v0.1.md` — draft-9 Decisions 5.6,
|
||
8.4.1, 8.4.2 are net-kingdom's answer to T01
|
||
- `net-kingdom` `NK-WP-0027` — the reef ↔ `P`/`V` reconciliation, taken off this repo
|
||
- `repo-manager/docs/RailianceAppDeploymentGuide.md` — reefs
|