feat(brief): optional threat model in the brief (decision 164, 218)
A brief may carry an optional `## Threat Model` with one line per bullet, `- Inside: <text>` or `- Outside: <text>`. Present and malformed (no such bullet, another label or form, empty text, two sections) is BRIEF_THREAT_MODEL_MALFORMED; absent is today's rule. No brief_version bump (the ceremony / artifact_store precedent). Sections are found fence- and comment-aware through intent-approval's new sectionTexts, so the validator and the hash count the same sections; brief-validator exports parseThreatModel for the reader. The intent hash covers `## Threat Model` when present (two sections -> null hash, as Intent and Goal), so an edited, added or removed threat model makes the approval stale; a brief without one hashes exactly as before. The stale message and the /trekplan halt table name the section. /trekbrief offers the section in its question round when the task touches a guard, a gate, untrusted input, secrets or publishing, writes it only when given, shows it verbatim at the Phase 4h approval, and --approve reads it out. The template documents it; brief-gate-coverage pairs the new code. Part 2, step 2.1 of the E plan. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
07a3c06c57
commit
74f75dcb7d
7 changed files with 350 additions and 23 deletions
|
|
@ -89,7 +89,7 @@ Usage: /trekbrief <task description>
|
|||
Modes:
|
||||
default Dynamic interview until quality gates pass — brief with research plan
|
||||
--quick Compact start; still escalates on weak sections — brief with research plan
|
||||
--approve Approve an existing brief's ## Intent + ## Goal so /trekplan will plan it
|
||||
--approve Approve an existing brief's ## Intent + ## Goal (+ ## Threat Model) so /trekplan will plan it
|
||||
|
||||
Examples:
|
||||
/trekbrief Add user authentication with JWT tokens
|
||||
|
|
@ -210,7 +210,8 @@ The round runs in the form of `voyage:interviewing`. What `/trekbrief` adds:
|
|||
Success Criteria, Research Plan, Non-Goals, Constraints, Preferences, NFRs,
|
||||
Prior Attempts), plus three fixed questions every round carries: framing
|
||||
(Phase 2.5, first), the size class (Step 3b) and where the brief is stored
|
||||
(`artifact_store`, below).
|
||||
(`artifact_store`, below); and the threat model (below) when the task
|
||||
touches a guard, a gate, untrusted input, secrets or publishing.
|
||||
2. **The budget is the class budget** (table below). A `bounded` or
|
||||
`architectural` round is consecutive `AskUserQuestion` calls with no
|
||||
drafting in between.
|
||||
|
|
@ -247,6 +248,17 @@ the repo is public or you cannot tell:
|
|||
The approval log is committed either way; it holds hashes, times and file
|
||||
names, never the text.
|
||||
|
||||
**The threat model** (`## Threat Model`, optional; E, decision 164). Offer it
|
||||
as one of the round's questions only when the task touches a guard, a gate,
|
||||
untrusted input, secrets or publishing; otherwise the brief has none and every
|
||||
security finding counts. Recommend what is inside (what the work must defend
|
||||
against) and what is outside (what it deliberately does not), one line each.
|
||||
Voyage's own security findings (plan-critic, scope-guardian, the code
|
||||
reviewer's part for command texts) block inside it; one that quotes an
|
||||
`Outside:` line becomes a listed known issue instead of a blocker. Findings
|
||||
from the security engine block as before. The intent approval (Phase 4h)
|
||||
covers the section.
|
||||
|
||||
**Borrowed, with credit.** The size classes, "heaviest in doubt" and the
|
||||
one-way ratchet: obra/superpowers@8ca22db (MIT, © 2025 Jesse Vincent),
|
||||
skills/brainstorming. The class ladder: Kiro's docs (kiro.dev — Plan mode,
|
||||
|
|
@ -272,6 +284,7 @@ state = {
|
|||
prior_attempts: { content: "", probes: 0 }, # optional
|
||||
ceremony: "small | bounded | architectural", # Step 3b; only moves up
|
||||
artifact_store: "git | local", # Step 3c
|
||||
threat_model: { inside: [], outside: [] }, # Step 3c; optional, only when offered
|
||||
assumptions: [], # predicted, hollow or force-stopped answers
|
||||
deferred: [], # over budget, with why
|
||||
question_history: [] # list of questions asked
|
||||
|
|
@ -503,6 +516,11 @@ Build the brief text from Phase 3 state by filling the template:
|
|||
(what you said apart from what the operator said); and the Deferred list,
|
||||
one line of why each.
|
||||
- **Prior Attempts:** from state, or "None — fresh task."
|
||||
- **Threat Model:** only when the operator gave one in the round (Step 3c):
|
||||
`## Threat Model` with one line per bullet, each `- Inside: <text>` or
|
||||
`- Outside: <text>`, and nothing else as a bullet (the validator blocks any
|
||||
other bullet with `BRIEF_THREAT_MODEL_MALFORMED`). Otherwise leave the
|
||||
section out. Optional in the schema, so no `brief_version` bump.
|
||||
|
||||
**Step 4b — Write draft to disk**
|
||||
|
||||
|
|
@ -715,8 +733,9 @@ criterion runs plain `bash`: offer `/bin/bash`).
|
|||
brief is validated, run `scripts/annotate.mjs` to build the page where the
|
||||
operator writes their own notes. The loop that follows is the skill
|
||||
`voyage:annotating`: call it when the operator pastes the notes back (a
|
||||
Question is answered, not applied; after Phase 4h, a note on `## Intent` or
|
||||
`## Goal` makes the approval stale, and that is said before the edit).
|
||||
Question is answered, not applied; after Phase 4h, a note on `## Intent`,
|
||||
`## Goal` or `## Threat Model` makes the approval stale, and that is said
|
||||
before the edit).
|
||||
|
||||
```bash
|
||||
ANNOT_HTML=$(node ${CLAUDE_PLUGIN_ROOT}/scripts/annotate.mjs "{PROJECT_DIR}/brief.md" 2>&1)
|
||||
|
|
@ -759,16 +778,18 @@ is given. It runs on EVERY brief, before the Phase 5 manual/auto fork, so
|
|||
both paths pass through it — and it is **asked even in `--quick` mode**; there
|
||||
is no skip path and no default answer.
|
||||
|
||||
1. Show the operator the `## Intent` and `## Goal` sections of
|
||||
`{BRIEF_PATH}` **verbatim** — the exact text the approval will
|
||||
bind to. Do not summarize them.
|
||||
1. Show the operator the `## Intent`, `## Goal` and, when present,
|
||||
`## Threat Model` sections of `{BRIEF_PATH}` **verbatim** — the exact text
|
||||
the approval will bind to. Do not summarize them. In `--approve` mode too:
|
||||
a brief from another producer may carry a `## Threat Model`, and it is read
|
||||
out with the rest, since the approval covers it.
|
||||
2. Ask via `AskUserQuestion` — one question:
|
||||
**"Is this the intent /trekplan should plan against?"**
|
||||
|
||||
| Option | Effect |
|
||||
|--------|--------|
|
||||
| **Approve** | Check the answer (step 3), then stamp, log and commit (steps 4–6). |
|
||||
| **Revise first** | The operator states the change; edit `## Intent` / `## Goal` in `{BRIEF_PATH}`, re-run the Phase 4g validator, then return to step 1. |
|
||||
| **Revise first** | The operator states the change; edit `## Intent` / `## Goal` / `## Threat Model` in `{BRIEF_PATH}`, re-run the Phase 4g validator, then return to step 1. |
|
||||
| **Leave unapproved** | Write nothing. `/trekplan` will halt on this brief until `/trekbrief --approve {BRIEF_PATH}` is run. |
|
||||
|
||||
3. **Is the answer an approval?** Only an explicit "Approve" is. Check the
|
||||
|
|
@ -794,9 +815,9 @@ node ${CLAUDE_PLUGIN_ROOT}/lib/validators/intent-approval.mjs --stamp "{BRIEF_PA
|
|||
```
|
||||
|
||||
This writes `intent_approved_hash` (sha256 over the normalized `## Intent`
|
||||
+ `## Goal`) and `intent_approved_at` into the frontmatter. It prints JSON; if `stamped` is `false`
|
||||
+ `## Goal`, + `## Threat Model` when present) and `intent_approved_at` into the frontmatter. It prints JSON; if `stamped` is `false`
|
||||
(e.g. `## Goal` is empty), report the `reason` and return to step 1. Editing
|
||||
`## Intent` or `## Goal` afterwards makes the approval stale — `/trekplan`
|
||||
`## Intent`, `## Goal` or `## Threat Model` afterwards makes the approval stale — `/trekplan`
|
||||
halts until the operator approves the new text.
|
||||
|
||||
5. **Log the approval** (K2), whatever `artifact_store` says. `{INTENT_HASH}`
|
||||
|
|
|
|||
|
|
@ -197,9 +197,9 @@ agent, write no plan. Print each error's `message` and `hint`, then stop:
|
|||
|
||||
| Code | Meaning | What the operator does |
|
||||
|------|---------|------------------------|
|
||||
| `BRIEF_INTENT_NOT_APPROVED` | No approval marker (`intent_approved_hash`) in the brief | `/trekbrief --approve {brief_path}` — read `## Intent` + `## Goal`, answer "Approve" |
|
||||
| `BRIEF_INTENT_APPROVAL_STALE` | `## Intent` or `## Goal` changed after approval | Same command — the approval covers the old text, not this one |
|
||||
| `BRIEF_INTENT_APPROVAL_INVALID` | Marker malformed, or `## Intent` / `## Goal` missing, empty or present more than once | Fix the brief, then the same command |
|
||||
| `BRIEF_INTENT_NOT_APPROVED` | No approval marker (`intent_approved_hash`) in the brief | `/trekbrief --approve {brief_path}` — read `## Intent` + `## Goal` (+ `## Threat Model` when present), answer "Approve" |
|
||||
| `BRIEF_INTENT_APPROVAL_STALE` | `## Intent`, `## Goal` or `## Threat Model` changed (or a `## Threat Model` was added or removed) after approval | Same command — the approval covers the old text, not this one |
|
||||
| `BRIEF_INTENT_APPROVAL_INVALID` | Marker malformed, or `## Intent` / `## Goal` missing, empty or present more than once, or `## Threat Model` present more than once | Fix the brief, then the same command |
|
||||
|
||||
If the command does not print the JSON report (module not found, node
|
||||
missing, an unresolved `${CLAUDE_PLUGIN_ROOT}`), **halt** as well and show the
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue