Compare commits
2 commits
f45fe7afa7
...
df2873ce54
| Author | SHA1 | Date | |
|---|---|---|---|
| df2873ce54 | |||
| cf0bd0da20 |
3 changed files with 213 additions and 0 deletions
|
|
@ -0,0 +1,91 @@
|
|||
---
|
||||
id: NK-ADR-0017
|
||||
type: architecture-decision-record
|
||||
title: "The Root Grant Is Derived From the Custody Record, Scoped to tenant:platform, and Observed as Non-Secret Evidence"
|
||||
status: proposed
|
||||
owner: net-kingdom
|
||||
revision: "1"
|
||||
decided: "2026-09-29"
|
||||
last_reviewed: "2026-09-29"
|
||||
review_interval: 12m
|
||||
---
|
||||
|
||||
# ADR-0017 - Root-Grant Custody and Account Authority
|
||||
|
||||
**Status:** Proposed (drafted on the defaults agreed with the operator; accept after user-engine review)
|
||||
**Date:** 2026-09-29
|
||||
**Deciders:** Bernd Worsch, Claude
|
||||
|
||||
## Context
|
||||
|
||||
user-engine implemented `account.authority.read` (USER-WP-0037-T01) and a
|
||||
workload-authenticated lookup endpoint. It will not derive
|
||||
`AccountAuthority.root-grant` from JWT roles, a username, or a consumer's
|
||||
configured root subject, and USER-WP-0037-T02 is blocked on NetKingdom
|
||||
specifying the authority. `docs/platform-root-custody.md` covers custody of the
|
||||
platform-root credential and OpenBao material, but not how a workload-facing
|
||||
"subject X holds the root grant" fact is derived, revoked, or evidenced.
|
||||
|
||||
## Decision
|
||||
|
||||
### 1. Authority: the custody record; directory membership is a projection
|
||||
|
||||
The **platform-root custody record** (the non-secret ceremony record: custodian
|
||||
assignment, quorum plan, root-token disposition, signoff) is the single
|
||||
authority for who holds the root grant.
|
||||
|
||||
- A root grant exists for a subject only while a custody record names that
|
||||
`(issuer, subject)` as king/platform-root custodian and no later record
|
||||
revokes it.
|
||||
- LLDAP `platform-root` membership and IAM claims (`groups: platform-root`,
|
||||
`roles: platform-root-custodian`) are **projections** of the record. They are
|
||||
authentication facts and MUST NOT be used alone to derive a root grant.
|
||||
- **Revocation** is a recorded custody action: a new record entry that
|
||||
supersedes the grant, with actor, reason, and time. Revocation must not
|
||||
remove recovery authority (break-glass under king custody stays intact).
|
||||
- If the record and a projection disagree, the record wins and the mismatch is
|
||||
a posture finding, not a grant.
|
||||
|
||||
### 2. Mapping: `platform:root` grants `tenant:platform` only
|
||||
|
||||
- A root grant is expressed as relation `platform-root-custodian` in tenant
|
||||
context `tenant:platform`. It is scoped to the platform control plane.
|
||||
- It is never inherited by, or implied for, any other tenant, and tenant admin
|
||||
authority never implies it (consistent with the user-engine boundary
|
||||
contract and IAM Profile v0.3).
|
||||
- The grant is a distinct grant type in the membership envelope
|
||||
(`scope_type: tenant`, `scope_id: platform`, `relation:
|
||||
platform-root-custodian`, `owner_system: net-kingdom-custody`). Consumers
|
||||
such as the Hub read it from user-engine; they do not mint it.
|
||||
|
||||
### 3. Evidence: non-secret observations with explicit freshness
|
||||
|
||||
An account/entitlement observation retained by a consumer contains only:
|
||||
`subject` (`issuer`, `subject`), `grant`, `source` (custody record id and
|
||||
revision), `observed_at`, and `revoked_at` or `expires_at` when known. It never
|
||||
contains tokens, keys, unseal shares, recovery codes, or reset links.
|
||||
|
||||
- Observations are read models: they carry `observed_at` and are re-derived from
|
||||
the custody record rather than treated as durable truth.
|
||||
- A consumer MAY cache an observation for at most **15 minutes**; sensitive
|
||||
platform actions (root-grant-dependent writes) MUST re-check and fail closed
|
||||
when the record cannot be read or the observation is stale.
|
||||
- Revocation takes effect at the next re-check; the cache bound is the maximum
|
||||
revocation latency.
|
||||
|
||||
## Consequences
|
||||
|
||||
- user-engine can represent `AccountAuthority.root-grant` deterministically from
|
||||
the custody record id and revision, and unblocks USER-WP-0037-T02.
|
||||
- The custody record needs a machine-readable, non-secret form that a workload
|
||||
can read. Its shape and location are an open follow-up (below); until it
|
||||
exists, user-engine returns "no root grant" rather than inferring one.
|
||||
- The 15-minute bound and the projection-versus-record rule are decisions of
|
||||
this ADR and may be tightened at review.
|
||||
|
||||
## Open follow-ups
|
||||
|
||||
- Define the machine-readable custody-record schema and read surface (owner:
|
||||
net-kingdom; delivery of the read path may involve railiance-platform).
|
||||
- Confirm with user-engine that the membership-envelope mapping above is
|
||||
sufficient for `AccountAuthority`.
|
||||
|
|
@ -305,6 +305,21 @@ admin for `tenant:coulomb` or another tenant must not imply OpenBao root,
|
|||
NetKingdom identity-admin, flex-auth platform-policy, or Railiance
|
||||
platform-service authority.
|
||||
|
||||
## Root Grant as a Workload-Facing Fact
|
||||
|
||||
Consumers such as the Hub and user-engine need to know whether a subject holds
|
||||
the root grant. That fact is derived from the custody record, not from claims.
|
||||
See [ADR-0017](adr/ADR-0017-root-grant-custody-and-account-authority.md):
|
||||
|
||||
- the custody record is the authority; LLDAP membership and the claims above are
|
||||
projections and never sufficient alone;
|
||||
- revocation is a recorded custody action that does not remove recovery
|
||||
authority;
|
||||
- the grant is `platform-root-custodian` in `tenant:platform` only, and never
|
||||
implied by tenant admin; and
|
||||
- retained observations are non-secret, carry `observed_at`, may be cached for at
|
||||
most 15 minutes, and fail closed for root-dependent writes when stale.
|
||||
|
||||
## OpenBao Bootstrap Sequence
|
||||
|
||||
The Railiance OpenBao runbook owns the live commands. NetKingdom owns the
|
||||
|
|
|
|||
107
workplans/NK-WP-0045-root-grant-custody-record.md
Normal file
107
workplans/NK-WP-0045-root-grant-custody-record.md
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
---
|
||||
id: NK-WP-0045
|
||||
type: workplan
|
||||
title: "Make the platform-root custody record readable as a non-secret root-grant fact"
|
||||
domain: infotech
|
||||
repo: net-kingdom
|
||||
status: proposed
|
||||
flavor: implementation
|
||||
owner: claude-code
|
||||
topic_slug: netkingdom
|
||||
created: "2026-09-29"
|
||||
updated: "2026-09-29"
|
||||
related: [NK-ADR-0017, NK-WP-0042]
|
||||
---
|
||||
|
||||
NK-ADR-0017 makes the platform-root custody record the authority for the root
|
||||
grant, but the record has no machine-readable, non-secret form a workload can
|
||||
read. Until it does, user-engine returns "no root grant" and USER-WP-0037-T02
|
||||
stays blocked. This workplan defines that form and its read surface. It does
|
||||
not change custody itself, and no secret material enters the record.
|
||||
|
||||
## Accept ADR-0017
|
||||
|
||||
```task
|
||||
id: NK-WP-0045-T01
|
||||
status: wait
|
||||
needs_human: true
|
||||
blocking_reason: "Bernd accepts or amends NK-ADR-0017 (proposed) after user-engine confirms the AccountAuthority mapping."
|
||||
priority: high
|
||||
```
|
||||
|
||||
Resolve the open points (15-minute cache bound, envelope mapping) and move the
|
||||
ADR to `accepted`.
|
||||
|
||||
## Define the custody-record schema
|
||||
|
||||
```task
|
||||
id: NK-WP-0045-T02
|
||||
status: todo
|
||||
depends_on: [NK-WP-0045-T01]
|
||||
priority: high
|
||||
```
|
||||
|
||||
Add `canon/schemas/custody-record_v0.1.schema.json`: record id and revision,
|
||||
`(issuer, subject)`, relation `platform-root-custodian`, `tenant:platform`,
|
||||
granted-at, superseded-by, revoked-at with actor and reason. Non-secret only;
|
||||
the schema rejects token, key, share and recovery-code fields.
|
||||
|
||||
## Add a validator and fixtures
|
||||
|
||||
```task
|
||||
id: NK-WP-0045-T03
|
||||
status: todo
|
||||
depends_on: [NK-WP-0045-T02]
|
||||
priority: medium
|
||||
```
|
||||
|
||||
Validator with grant, revocation and supersession fixtures, plus negative cases:
|
||||
projection-only grants, tenant-admin implication, secret-bearing fields.
|
||||
Follow the existing tool layout under `tools/`.
|
||||
|
||||
## Decide the read surface
|
||||
|
||||
```task
|
||||
id: NK-WP-0045-T04
|
||||
status: wait
|
||||
depends_on: [NK-WP-0045-T02]
|
||||
needs_human: true
|
||||
blocking_reason: "Needs a decision on where a workload reads the record (OpenBao-held, platform service, or repository artifact) and which owner delivers it."
|
||||
priority: high
|
||||
```
|
||||
|
||||
Options and the owner (net-kingdom versus railiance-platform) go to Bernd; the
|
||||
answer fixes T05.
|
||||
|
||||
## Deliver the read path with the owner
|
||||
|
||||
```task
|
||||
id: NK-WP-0045-T05
|
||||
status: wait
|
||||
depends_on: [NK-WP-0045-T04]
|
||||
blocking_reason: "Depends on the read-surface decision and on the delivering owner."
|
||||
priority: medium
|
||||
```
|
||||
|
||||
Hand off the schema and read contract to the owner; NetKingdom does not absorb
|
||||
the runtime. Include the fail-closed and staleness rules from ADR-0017.
|
||||
|
||||
## Close the loop with user-engine
|
||||
|
||||
```task
|
||||
id: NK-WP-0045-T06
|
||||
status: wait
|
||||
depends_on: [NK-WP-0045-T05]
|
||||
blocking_reason: "Needs the delivered read path."
|
||||
priority: medium
|
||||
```
|
||||
|
||||
Tell user-engine the read path is live so USER-WP-0037-T02 can derive
|
||||
`AccountAuthority.root-grant` from it, and record the evidence.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- The custody-record schema and validator exist and reject secret-bearing and
|
||||
projection-only inputs.
|
||||
- A workload can read the root-grant fact from an owner-delivered surface.
|
||||
- user-engine derives the root grant without inferring it from claims.
|
||||
Loading…
Add table
Add a link
Reference in a new issue