2026-07-27 00:47:15 +02:00
|
|
|
# Construction plan format
|
|
|
|
|
|
|
|
|
|
The artifact phase 1 (`INTENT.md`) produces. One markdown file per plan,
|
|
|
|
|
stored at `plans/<plan-id>.md` in this repo — plain, versioned, reviewable
|
|
|
|
|
in a diff, not a database. `<plan-id>` is kebab-case and traceable to the
|
|
|
|
|
demand that triggered it (usually a workplan task id from the requesting
|
|
|
|
|
repo, e.g. `rein-openweights-openrouter-approle` for
|
|
|
|
|
`glas-harness/GLAS-WP-0002-T02`).
|
|
|
|
|
|
|
|
|
|
A plan file is the single source of truth across all four phases: phase 1
|
|
|
|
|
writes it, phase 2 edits it in place (self-review, not a separate
|
|
|
|
|
document), phase 3's executive summary is generated *from* it (not a
|
|
|
|
|
parallel artifact that can drift), and phase 4 reads the same file to
|
|
|
|
|
execute and appends a result section when done.
|
|
|
|
|
|
|
|
|
|
## Frontmatter
|
|
|
|
|
|
|
|
|
|
```yaml
|
|
|
|
|
---
|
|
|
|
|
id: rein-openweights-openrouter-approle
|
|
|
|
|
demand_source: glas-harness/workplans/GLAS-WP-0002-T02
|
|
|
|
|
consumer_repo: rein-openweights
|
|
|
|
|
credential_type: openbao-approle-kv
|
|
|
|
|
status: draft # draft -> reviewed -> approved -> built -> catalogued
|
|
|
|
|
approved_by: null # founder identity string, set only at phase 3 -> 4
|
|
|
|
|
approved_at: null # ISO date, set alongside approved_by
|
|
|
|
|
created: "2026-07-27"
|
|
|
|
|
updated: "2026-07-27"
|
|
|
|
|
---
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
`status` is the phase tracker for the plan itself, distinct from any
|
|
|
|
|
State Hub task status — it's what the build executor (`MASON-WP-0001-T04`)
|
|
|
|
|
checks before doing anything: **refuses to execute unless
|
|
|
|
|
`status: approved` and both `approved_by`/`approved_at` are set.** No
|
|
|
|
|
other field or file can substitute for this — that's the one hard gate.
|
|
|
|
|
|
|
|
|
|
## Body sections
|
|
|
|
|
|
|
|
|
|
### 1. Demand
|
|
|
|
|
|
|
|
|
|
Plain description: who needs what, why, and what they need it *for* (one
|
|
|
|
|
paragraph — this feeds the executive summary's plain-language framing
|
|
|
|
|
directly, so write it for the founder, not for `bao`).
|
|
|
|
|
|
|
|
|
|
### 2. Existing-structure survey
|
|
|
|
|
|
|
|
|
|
What already exists that might cover this, checked *before* proposing
|
|
|
|
|
anything new:
|
|
|
|
|
|
|
|
|
|
- Relevant `bao policy list` / `bao auth list` / `bao secrets list`
|
|
|
|
|
entries (paste the actual output, not a paraphrase)
|
|
|
|
|
- Relevant `ops-warden/registry/routing/catalog.yaml` entries (grep by
|
|
|
|
|
`owner_repo`/`subsystem`/`need_keywords`)
|
|
|
|
|
- Explicit answer: **does an existing lane already satisfy this demand?**
|
|
|
|
|
If yes, the plan's proposed changes should be "bind to existing policy
|
|
|
|
|
X" or "reuse existing path Y," not "create new." (See
|
|
|
|
|
`binky-control/integrations/executor-worker-secrets.md`'s own Lane 1 —
|
|
|
|
|
reused the existing `llm-connect-provider-secrets` secret instead of
|
|
|
|
|
minting a new one, because the scoping already covered the new
|
|
|
|
|
consumer.)
|
|
|
|
|
|
2026-10-05 13:22:26 +02:00
|
|
|
Generate the section rather than gathering it by hand (MASON-WP-0008). The
|
|
|
|
|
founder mints the scoped grant; the agent then runs the survey with it:
|
|
|
|
|
|
|
|
|
|
```bash
|
2026-10-07 10:44:24 +02:00
|
|
|
./scripts/bao-session.sh grant <task> # founder: ops-mason-survey (read-only), 45m, claude-<task>
|
2026-10-05 13:22:26 +02:00
|
|
|
./scripts/plan-survey.py \
|
|
|
|
|
--kv-path platform/workloads/<consumer>/<name> \
|
|
|
|
|
--policy workload-kv-read-<consumer>-<name> \
|
|
|
|
|
--approle <role> [--k8s-role <role>] \
|
|
|
|
|
--consumer <consumer-repo> [--keyword <word> ...] \
|
|
|
|
|
[--catalog ~/ops-warden/registry/routing/catalog.yaml]
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
It prints a Markdown block headed `## 2. Existing-structure survey`: the
|
|
|
|
|
relevant lines and counts of `bao secrets list`, `bao auth list`,
|
|
|
|
|
`bao policy list` and `bao list auth/<mount>/role` (approle and kubernetes
|
|
|
|
|
mounts), `bao policy read` stanzas covering the target for name-matched
|
|
|
|
|
policies only, KV metadata existence for the target and its siblings,
|
|
|
|
|
matching ops-warden catalog entries, a UTC timestamp, `BAO_ADDR`, the token
|
|
|
|
|
`display_name`, and a draft yes/no/partial answer with reasons. Edit the
|
|
|
|
|
draft answer into a decision; it is generated evidence, not a review.
|
|
|
|
|
|
|
|
|
|
The survey refuses to run without a grant, refuses a token whose policies
|
2026-10-07 10:44:24 +02:00
|
|
|
are anything but `ops-mason-survey` (plus `default`), and never prints the
|
2026-10-05 13:22:26 +02:00
|
|
|
token. Every bao call passes an allowlist that rejects `kv get` and any
|
|
|
|
|
`*/data/*` path before a process starts. A query the grant does not permit
|
2026-10-07 10:44:24 +02:00
|
|
|
is reported as `not permitted by ops-mason-survey` and listed under
|
2026-10-05 13:22:26 +02:00
|
|
|
"incomplete" rather than read as an empty answer. Policies with names that
|
|
|
|
|
do not match the demand are not read, so a broadly named policy covering
|
|
|
|
|
the path is not detected; the output says so.
|
|
|
|
|
|
2026-07-27 00:47:15 +02:00
|
|
|
### 3. Proposed changes
|
|
|
|
|
|
|
|
|
|
A list, each item tagged `create` / `modify` / `tear-down`, each with a
|
|
|
|
|
one-line reuse-vs-new rationale referencing section 2. Every item names
|
|
|
|
|
exactly what OpenBao object it touches (policy name, AppRole name, KV
|
|
|
|
|
path) — no vague "provision access" line items.
|
|
|
|
|
|
2026-10-11 23:41:31 +02:00
|
|
|
Before any new credential, answer SecurityCanon `SEC-DSM` §37 in order and
|
|
|
|
|
name the preference-hierarchy level (§5) the proposal lands on:
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
Q1 Can the operation use identity directly? If yes, do not create a secret.
|
|
|
|
|
Q2 Can the target accept federated identity? If yes, federate.
|
|
|
|
|
Q3 Can the target issue an ephemeral credential? If yes and viable, issue dynamically.
|
|
|
|
|
Q4 Can an existing static credential be rotated automatically? If yes, manage it centrally.
|
|
|
|
|
Q5 Must a persistent static credential exist? If yes, vault it and name owner,
|
|
|
|
|
consumer, authority, delivery, copies, rotation, revocation, detection, and
|
|
|
|
|
what would eliminate it.
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
A plan that creates a static secret or AppRole without those answers is not
|
|
|
|
|
ready for section 4. `DSM-TRANS-003`: do not add an issuance layer only to
|
|
|
|
|
claim dynamic maturity.
|
|
|
|
|
|
2026-07-27 00:47:15 +02:00
|
|
|
### 4. Review notes (phase 2)
|
|
|
|
|
|
|
|
|
|
Filled in during the self-review pass, before a human ever sees the
|
|
|
|
|
plan: naming/TTL/scoping convention check against existing lanes (e.g.
|
|
|
|
|
the `agent-harness-binky-mail` shape — `token_ttl=15m`,
|
|
|
|
|
`token_max_ttl=30m`, bounded `token_num_uses`), redundancy check, and any
|
|
|
|
|
compaction opportunity spotted along the way. If this section is empty,
|
|
|
|
|
the plan is not ready for phase 3.
|
|
|
|
|
|
|
|
|
|
### 5. Executive summary (phase 3, generated from sections 1-4)
|
|
|
|
|
|
|
|
|
|
See `docs/executive-summary-format.md` (`MASON-WP-0001-T03`) for the
|
|
|
|
|
exact rendering. Lives in the same plan file so there is one document to
|
|
|
|
|
read, not two to cross-reference.
|
|
|
|
|
|
|
|
|
|
### 6. Build result (phase 4, appended after execution)
|
|
|
|
|
|
|
|
|
|
What was actually created/modified/torn down (object names, not values),
|
|
|
|
|
the ops-warden catalog entry proposed (id + PR/commit reference once
|
|
|
|
|
filed), and the exact `paste_once_provision` step the founder still needs
|
|
|
|
|
to run (path, field) if the plan involved a new secret value. This
|
|
|
|
|
section does not exist until phase 4 has run.
|
|
|
|
|
|
|
|
|
|
## Worked example (also the input to `MASON-WP-0001-T05`)
|
|
|
|
|
|
|
|
|
|
See `plans/rein-openweights-openrouter-approle.md` once T01 is
|
|
|
|
|
implemented against this format — the first real plan, not a synthetic
|
|
|
|
|
one, per `MASON-WP-0001`'s own scoping.
|
2026-09-28 11:55:39 +02:00
|
|
|
|
|
|
|
|
## Binding OpenBao approval to execution inputs
|
|
|
|
|
|
2026-10-04 11:01:47 +02:00
|
|
|
The AppRole, Kubernetes-auth and attended OIDC matrix builders require two frontmatter
|
2026-09-28 11:55:39 +02:00
|
|
|
fields: `build_spec` and `approved_spec_sha256`. `build_spec` is the complete
|
|
|
|
|
value-free output of `ops_mason.executor.build_spec_document(spec)`. It includes
|
|
|
|
|
the engine, exact policy/role/path and identity bindings, token bounds, policy
|
|
|
|
|
reuse and content pin, delivery destination, audit destination, and executable.
|
|
|
|
|
`credential_type` must match its `engine`.
|
|
|
|
|
|
|
|
|
|
During drafting, render the specification and its digest for review:
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
from ops_mason.executor import AppRoleKVSpec, build_spec_document, build_spec_digest
|
|
|
|
|
|
|
|
|
|
spec = AppRoleKVSpec(
|
|
|
|
|
policy_name="workload-kv-read-example",
|
|
|
|
|
kv_path="platform/workloads/example/runtime",
|
|
|
|
|
approle_name="example",
|
|
|
|
|
token_num_uses=8,
|
|
|
|
|
delivery_dir=Path("/home/consumer/.local/example/approle"),
|
|
|
|
|
audit_log_path=Path("/home/builder/ops-mason/audit/build-log.jsonl"),
|
|
|
|
|
)
|
|
|
|
|
document = build_spec_document(spec) # put this mapping under build_spec
|
|
|
|
|
candidate_digest = build_spec_digest(document) # present alongside the review
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
The digest is SHA-256 of UTF-8 JSON with sorted keys, compact separators and
|
|
|
|
|
no NaN values. Paths render as absolute strings; tuples render as lists. All
|
|
|
|
|
specification fields, including explicit defaults and null values, participate.
|
|
|
|
|
After the founder approves that exact specification, record the digest as
|
|
|
|
|
`approved_spec_sha256` alongside `approved_by`, `approved_at` and
|
|
|
|
|
`status: approved`. Quote approval dates in YAML. A candidate digest alone
|
|
|
|
|
is not approval, and the digest is an integrity marker, not a signature.
|
|
|
|
|
|
|
|
|
|
Before any OpenBao command, the builder reloads the plan from disk, checks
|
|
|
|
|
approval and engine, validates the specification, and compares both the
|
|
|
|
|
frontmatter mapping and approved digest to the actual inputs. Duplicate YAML
|
|
|
|
|
keys, missing build specifications, changed scope or changed destinations are
|
|
|
|
|
refused. Editing the mapping without renewing its approval digest cannot reuse
|
|
|
|
|
the previous approval. The plan file remains the trust boundary: this does not
|
|
|
|
|
protect against someone authorized to rewrite the approval record itself.
|
|
|
|
|
|
|
|
|
|
Historical plans stay historical. They are not automatically given new
|
|
|
|
|
specifications or approvals. Before re-executing one, draft and review the exact
|
|
|
|
|
new inputs through the existing four-phase process. No live lane was changed
|
|
|
|
|
by this format migration.
|
|
|
|
|
|
|
|
|
|
### Input and policy constraints
|
|
|
|
|
|
|
|
|
|
The existing AppRole engine creates only exact KV-v2 read lanes: one literal
|
|
|
|
|
entry path, `read` on data and metadata, no wildcard, empty/parent path segment,
|
2026-10-04 11:01:47 +02:00
|
|
|
HCL injection, or write capability. The separate attended OIDC matrix engine is implemented under MASON-WP-0005;
|
|
|
|
|
its live construction remains blocked on identity, platform review and plan approval. Role and policy names must be single
|
2026-09-28 11:55:39 +02:00
|
|
|
literal names; Kubernetes service-account names/namespaces must be explicit,
|
|
|
|
|
nonempty, unique DNS labels. Token use counts must be explicit nonnegative
|
|
|
|
|
integers (zero is deliberately unlimited when approved); TTLs are positive
|
|
|
|
|
integer durations in `s`, `m` or `h`, with maximum TTL at least initial TTL.
|
|
|
|
|
`secret_id_ttl: "0"` remains an explicit reviewable choice.
|
|
|
|
|
|
|
|
|
|
For `AppRoleKVSpec(reuse_policy=True)`, also provide
|
|
|
|
|
`reuse_policy_sha256`. For `KubernetesKVSpec`, provide `policy_sha256`, since
|
|
|
|
|
that builder always binds an existing policy. During the scoped structure
|
|
|
|
|
survey, hash the exact UTF-8 output of `bao policy read <name>`, including its
|
|
|
|
|
trailing newline, and review that policy's full scope. The builder reads only
|
|
|
|
|
policy text, checks its hash before auth-role writes, and refuses drift. A
|
|
|
|
|
reused policy may deliberately cover more than the AppRole's nominal KV path;
|
|
|
|
|
its reviewed contents, not that path field alone, define the grant. Rechecking
|
|
|
|
|
a pin is not an atomic lock against later administrative policy changes.
|
|
|
|
|
|
|
|
|
|
### Credential delivery and failed builds
|
|
|
|
|
|
|
|
|
|
AppRole delivery requires an absolute directory. Every directory component is
|
|
|
|
|
opened without following symlinks; missing directories are created privately.
|
|
|
|
|
An existing final directory must belong to the executing account with mode
|
|
|
|
|
0700. Both credential files are reserved exclusively at mode 0600 before any
|
|
|
|
|
policy/auth write or credential issuance. Existing files, hardlinks and symlink
|
|
|
|
|
paths are refused rather than overwritten or permission-repaired. Delivery
|
|
|
|
|
uses the held file descriptors and flushes credentials to disk.
|
|
|
|
|
|
|
|
|
|
OpenBao error and timeout output is suppressed so a failed credential command
|
|
|
|
|
cannot echo sensitive material. Audit output contains object names, the
|
|
|
|
|
approved specification digest and approval attribution, never credentials.
|
|
|
|
|
A failed build can leave private empty or partially delivered files, and may
|
|
|
|
|
have changed policy/auth structure or issued a credential before failing.
|
|
|
|
|
Do not blindly retry or delete them: inspect structure and audit metadata,
|
|
|
|
|
resolve/revoke any partial issuance through the scoped custody procedure, and
|
|
|
|
|
review a fresh delivery destination before retrying. The executor does not
|
|
|
|
|
claim transactional rollback or automatically widen its revocation authority.
|
2026-10-04 11:01:47 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
### Attended OIDC operator matrix
|
|
|
|
|
|
|
|
|
|
`OIDCKVSpec` and `build_oidc_kv_lane` implement the ops-mason side of the
|
|
|
|
|
fluid-telegram four-entry matrix. Draft and review `build_spec_document(spec)`
|
|
|
|
|
and its digest through the same exact-spec approval process. The spec requires
|
|
|
|
|
campaign coordinates, one owner-confirmed group, identity-confirmation and
|
|
|
|
|
platform-matrix-review references, explicit loopback CLI callbacks, policy/role
|
|
|
|
|
names and token bounds. These references are review inputs, not authorization
|
|
|
|
|
tokens or proof that an owner accepted a draft.
|
|
|
|
|
|
|
|
|
|
The renderer emits four literal KV-v2 data stanzas: operator-app read;
|
|
|
|
|
operator-session and bot-token create/read/update; redaction-salt create/read.
|
|
|
|
|
It adds only the exact token revoke-self update endpoint. There are no metadata,
|
|
|
|
|
parent, sibling, wildcard, deletion or salt-update grants. The engine writes
|
|
|
|
|
policy/auth-role structure only; it never reads/writes KV values or issues a
|
|
|
|
|
login token. The approved role has one bound group, no default policy, explicit
|
|
|
|
|
15-minute initial / 30-minute maximum lifetime caps and non-periodic tokens.
|
|
|
|
|
|
|
|
|
|
Initial callback support is deliberately limited to owner-reviewed
|
|
|
|
|
`http://localhost:8250/oidc/callback` and
|
|
|
|
|
`http://127.0.0.1:8250/oidc/callback`. A private UI callback or additional MFA
|
|
|
|
|
claim shape requires matching owner-confirmed contract work before extending
|
|
|
|
|
this engine. A group parameter or a local test does not establish installed
|
|
|
|
|
issuer/MFA assurance. MASON-WP-0005-T01/T03/T05 keep those live obligations.
|
|
|
|
|
|
|
|
|
|
On failure the engine suppresses Bao output and does not claim a successful
|
|
|
|
|
audit receipt. It can leave a written policy if the auth-role write fails;
|
|
|
|
|
inspect object/audit metadata and use the approved rollback procedure before a
|
|
|
|
|
retry. Metadata-only local tests do not prove installed KV create/update/CAS
|
|
|
|
|
behavior or agent-policy effective denial. Those remain native owner checks.
|
|
|
|
|
Platform CCR request-type support and review remain a separate T02 dependency;
|
|
|
|
|
the generic read-lane CCR renderer still must refuse this write matrix.
|