# Construction plan format The artifact phase 1 (`INTENT.md`) produces. One markdown file per plan, stored at `plans/.md` in this repo — plain, versioned, reviewable in a diff, not a database. `` 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. ## 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 `, 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.