fix(intent): M1 + M2 — the parser fails closed; --approve reaches any brief file
M1: headings inside fenced code blocks and HTML comments are text, not headings, and ## Intent / ## Goal must each appear exactly once. A second section, an example section in a code block or comment, or a '## ' line in a code block inside Intent no longer carries the old approval: check gives INVALID (duplicate) or STALE, stamp refuses a duplicate. A plain brief hashes exactly as before (pinned), so no stamped brief goes stale. Re-run of the PM's 29 probes: vi-a, vi-b, vii-a, vii-b now stop; the rest unchanged. The PM's p7c probe was a no-op (its replace string is not in the base brief); vii-c is covered by its own test, red on66e1fa1. M2: /trekbrief --approve takes <project-dir | brief-file>. New intent-approval.mjs --resolve decides: a directory → <dir>/brief.md and /trekplan --project; a file → itself and /trekplan --brief. Phase 4h and the stamp line use {BRIEF_PATH}; /trekplan's halt table names /trekbrief --approve {brief_path}, which works on both brief modes, and so does the gate's hint. HANDOVER-CONTRACTS' "open to every producer's brief" is now true. Chose fixing the path over rewording the contract because two real briefs (docs/*-brief.md) had no approval path at all. Redff760ed9/29 → green 29/29. Suite 1219: 1217 pass / 0 fail / 2 skip. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
ff760edba2
commit
20f32bb06b
7 changed files with 129 additions and 45 deletions
|
|
@ -74,7 +74,7 @@ Every validator exposes a CLI: `node lib/validators/<name>.mjs --json <path>` re
|
|||
| `phase_signals` | list | optional (v5.1+) | list of `{phase, effort?, model?}` entries | Per-phase effort + model commitment from Phase 3.5. Mutually exclusive with `phase_signals_partial`. |
|
||||
| `phase_signals_partial` | bool | optional (v5.1+) | `true` | Force-stop record from Phase 3.5. Mutually exclusive with `phase_signals`. |
|
||||
| `framing` | string | **required at ≥ 2.2** (v5.5) | `preserve \| refine \| replace \| new-direction` | How this brief relates to prior operator intent. Enum-checked on any version when present; missing → `BRIEF_MISSING_FRAMING` at `brief_version ≥ 2.2`. Layer 1 of the framing-alignment defense. |
|
||||
| `intent_approved_hash` | string | optional in the schema; **required by `/trekplan`** (veikart steg 1) | `"sha256:<64 hex>"` | Hash over the normalized `## Intent` + `## Goal`. Written only by `lib/validators/intent-approval.mjs --stamp`, which `/trekbrief` runs on the operator's explicit "Approve" (Phase 4h, or `--approve <project-dir>`). |
|
||||
| `intent_approved_hash` | string | optional in the schema; **required by `/trekplan`** (veikart steg 1) | `"sha256:<64 hex>"` | Hash over the normalized `## Intent` + `## Goal`. Written only by `lib/validators/intent-approval.mjs --stamp`, which `/trekbrief` runs on the operator's explicit "Approve" (Phase 4h, or `--approve <project-dir \| brief-file>`). |
|
||||
| `intent_approved_at` | string | optional | ISO-8601 | When the stamp was written. Informational; the gate reads the hash. |
|
||||
|
||||
**Body invariants:** required sections (validator runs in strict mode at write-time, soft mode at read-time):
|
||||
|
|
@ -106,10 +106,10 @@ Optional but standard sections: `## Non-Goals`, `## Constraints`, `## Preference
|
|||
|
||||
**Pre-2.2 briefs receive zero framing enforcement (producer-elective defense).** The framing-alignment defense is gated at `brief_version ≥ 2.2`, so a brief declaring `2.0` or `2.1` gets **none** of it: `framing` is optional and a missing value is not an error, the `## TL;DR` section is not required, and the `brief-reviewer` memory-alignment dimension scores N/A. The backward-compatibility this buys is deliberate, but it also means the framing defense is **producer-elective**: any producer can sidestep the entire defense — deliberately or by shipping a legacy template — simply by declaring `brief_version: "2.1"`, and the pipeline raises no signal by default. This is a documented property of the contract, not a bug. Two remedies close it for a given consumer: (1) require upstream producers to emit `brief_version: "2.2"` (which forces `framing` + `## TL;DR`); or (2) pass the opt-in **`--min-brief-version <ver>`** flag to `/trekplan` or `/trekresearch` (forwarded to the validator as `--min-version`), which raises a `BRIEF_VERSION_BELOW_MINIMUM` **warning** — never a block — when a brief declares a version below the floor. The flag defaults to off, so the N-1 compatibility window for `2.0`/`2.1` briefs is preserved unless a consumer explicitly opts into the stricter floor.
|
||||
|
||||
**Intent approval gate (veikart steg 1) — additive schema, consumer-side requirement.** The two `intent_approved_*` fields are additive and optional in the schema: `brief-validator.mjs` does not require them, `/trekresearch` does not read them, and no `brief_version` bump is involved. The requirement lives in `/trekplan` Phase 1 (`### Read the brief`), which runs `intent-approval.mjs --check` and **halts** on `BRIEF_INTENT_NOT_APPROVED` (no marker), `BRIEF_INTENT_APPROVAL_STALE` (`## Intent` or `## Goal` changed after approval) or `BRIEF_INTENT_APPROVAL_INVALID` (malformed marker, or a section missing). It is version-independent, so a brief from ANY producer, of any `brief_version`, stops at `/trekplan` until approved. No producer has to emit anything new: the operator approves through `/trekbrief --approve <project-dir>`, a Voyage-side step open to every producer's brief, so no producer is privileged. `trekreview` briefs are exempt. **What the marker does not prove:** same user, same machine. Any session can run `--stamp`. The marker is a trace that the approval step ran against this exact text, not a signature (see the header of `lib/validators/intent-approval.mjs`). `--stamp` also emits `brief-approved` to `trekexecute-stats.jsonl` under `CLAUDE_PLUGIN_DATA`, falling back to the plugin data dir the yardstick reads, on both the manual and the auto path.
|
||||
**Intent approval gate (veikart steg 1) — additive schema, consumer-side requirement.** The two `intent_approved_*` fields are additive and optional in the schema: `brief-validator.mjs` does not require them, `/trekresearch` does not read them, and no `brief_version` bump is involved. The requirement lives in `/trekplan` Phase 1 (`### Read the brief`), which runs `intent-approval.mjs --check` and **halts** on `BRIEF_INTENT_NOT_APPROVED` (no marker), `BRIEF_INTENT_APPROVAL_STALE` (`## Intent` or `## Goal` changed after approval) or `BRIEF_INTENT_APPROVAL_INVALID` (malformed marker, or a section missing). It is version-independent, so a brief from ANY producer, of any `brief_version`, stops at `/trekplan` until approved. No producer has to emit anything new: the operator approves through `/trekbrief --approve <project-dir | brief-file>`, a Voyage-side step open to every producer's brief, so no producer is privileged. `trekreview` briefs are exempt. **What the marker does not prove:** same user, same machine. Any session can run `--stamp`. The marker is a trace that the approval step ran against this exact text, not a signature (see the header of `lib/validators/intent-approval.mjs`). `--stamp` also emits `brief-approved` to `trekexecute-stats.jsonl` under `CLAUDE_PLUGIN_DATA`, falling back to the plugin data dir the yardstick reads, on both the manual and the auto path.
|
||||
|
||||
**Failure modes:**
|
||||
- `BRIEF_INTENT_NOT_APPROVED` / `BRIEF_INTENT_APPROVAL_STALE` / `BRIEF_INTENT_APPROVAL_INVALID` → `/trekplan` halts (intent-approval gate, above); remedy `/trekbrief --approve <project-dir>`
|
||||
- `BRIEF_INTENT_NOT_APPROVED` / `BRIEF_INTENT_APPROVAL_STALE` / `BRIEF_INTENT_APPROVAL_INVALID` → `/trekplan` halts (intent-approval gate, above); remedy `/trekbrief --approve <project-dir | brief-file>`
|
||||
- `BRIEF_NOT_FOUND` → consumer halts with a usage message
|
||||
- `FM_MISSING` → file has no frontmatter; halt
|
||||
- `BRIEF_WRONG_TYPE` → file is not a brief; halt
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue