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.)
|
|
|
|
|
|
|
|
|
|
### 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.
|
|
|
|
|
|
|
|
|
|
### 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
|
|
|
|
|
|
|
|
|
|
The AppRole and Kubernetes-auth builders additionally require two frontmatter
|
|
|
|
|
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,
|
|
|
|
|
HCL injection, or write capability. OIDC matrix writers remain a separate,
|
|
|
|
|
blocked contract under MASON-WP-0005. Role and policy names must be single
|
|
|
|
|
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.
|