feat(voyage): S18 — framing-hardening (min-version gate + memory status + pre-2.2 doc + flagship hedge)
Closes devils-advocate audit #4–#6 (MAJOR×MED). Four additive, non-breaking changes to the v5.5 framing-alignment machinery: 1. --min-brief-version <ver> gate (audit #4). Opt-in version floor on /trekplan + /trekresearch, forwarded to brief-validator as --min-version. New opts.minBriefVersion warns BRIEF_VERSION_BELOW_MINIMUM (never blocks) when a brief declares a version below the floor; trekreview exempt; absent opt = no check. CLI shim parses --min-version and skips its value token in filePath detection. 2. memory_alignment.status field (audit #5). brief-reviewer now emits status: verified | n_a | contradictions so a score-5 N/A (no memory) is distinguishable from a score-5 verified-aligned brief — the score≥4 gate passes in both, status reveals whether the wrong-premise defense ran. 3. Document pre-2.2 = zero framing enforcement (audit #4). HANDOVER-CONTRACTS §Handover 1 now states the producer-elective hole + two remedies. Also fixes a stale "current is 2.1" line (current is 2.2). 4. Soften flagship overselling (audit #6). CLAUDE.md Context-Engineering principle now hedges that main-context relief is asserted-by-design, not measured (T1 PoC found Δ≈0); README carried no false claim to fix. TDD: 8 new tests written failing-first (5 validator, 1 trekbrief status pin, 2 doc-consistency cross-file pins). Suite 691→699 (697 pass / 2 skip / 0 fail). No flagship prose-pin added (deliberate, per S19 anti-bloat). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LqBYc8Ltrk7LipyJmGxXiB
This commit is contained in:
parent
eb5a7324a4
commit
987e847ea1
11 changed files with 145 additions and 13 deletions
|
|
@ -13,8 +13,9 @@ The coordinated release held since the framing work. Supersedes the unreleased i
|
|||
Three version-gated layers ship at `brief_version ≥ 2.2`, implementing the `CLAUDE.md` cross-cutting invariant "brief framing must match operator intent":
|
||||
|
||||
- **`framing` enum** (`preserve | refine | replace | new-direction`) — required in frontmatter, collected in `/trekbrief` Phase 2.5 *before* any prose is written, non-skippable even in `--quick`. Enum-checked on any version (`BRIEF_INVALID_FRAMING`); missing at ≥ 2.2 → `BRIEF_MISSING_FRAMING`.
|
||||
- **Memory-alignment dimension** — new `brief-reviewer` dimension 6 compares brief Intent/Goal + framing against operator memory, flagging *explicit* contradictions only (scores N/A when no memory is supplied); wired to the Phase 4e gate (`memory_alignment.score ≥ 4`).
|
||||
- **Memory-alignment dimension** — new `brief-reviewer` dimension 6 compares brief Intent/Goal + framing against operator memory, flagging *explicit* contradictions only (scores N/A when no memory is supplied); wired to the Phase 4e gate (`memory_alignment.score ≥ 4`). Emits a `status` field (`verified | n_a | contradictions`) so a score-5 N/A (no memory available) is distinguishable from a score-5 verified-aligned brief — the gate passes in both cases, and `status` is what tells the operator whether the wrong-premise defense actually ran.
|
||||
- **Obligatory `## TL;DR`** — ≤ 5 content lines at the top of every 2.2 brief (soft cap → `BRIEF_TLDR_TOO_LONG`).
|
||||
- **Opt-in version floor** — `--min-brief-version <ver>` on `/trekplan` + `/trekresearch` (forwarded to the validator as `--min-version`) raises a `BRIEF_VERSION_BELOW_MINIMUM` *warning* — never a block — when a brief declares a version below the floor. Closes the producer-elective hole where a `2.0`/`2.1` brief silently sidesteps framing enforcement; `docs/HANDOVER-CONTRACTS.md` §Handover 1 now documents pre-2.2 = zero framing enforcement and the two remedies (require `2.2` upstream, or pass the floor flag).
|
||||
|
||||
Existing `2.0`/`2.1` briefs stay valid (forward + backward compatible), mirroring the `phase_signals ≥ 2.1` precedent. `trekreview` briefs are exempt.
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
Voyage — a contract-driven Claude Code pipeline: brief, research, plan, execute, review, continue. Deep implementation planning and research with specialized agent swarms, external research, adversarial review, session decomposition, disciplined execution, and headless support.
|
||||
|
||||
**Design principle: Context Engineering** — build the right context by orchestrating specialized agents. Each step in the pipeline (brief → research → plan → execute) produces a structured artifact that the next step consumes.
|
||||
**Design principle: Context Engineering** — build the right context by orchestrating specialized agents. Each step in the pipeline (brief → research → plan → execute) produces a structured artifact that the next step consumes. (The claim that fanning out to a swarm *relieves* the main context is asserted-by-design, not measured end-to-end: the one delegation actually measured — the Phase-7 synthesis PoC — found Δ main-context ≈ 0, see `docs/T1-synthesis-poc-results.md`. The structured-artifact handoffs are the load-bearing benefit; main-context relief is not yet demonstrated.)
|
||||
|
||||
> **v3.0.0 — architect step extracted from this plugin.** The plan command still auto-discovers `architecture/overview.md` if present, so any compatible producer (architect plugin no longer publicly distributed; the architecture/overview.md slot remains available for any compatible producer) plugs into the same slot. See [CHANGELOG.md](CHANGELOG.md) for migration history.
|
||||
|
||||
|
|
|
|||
|
|
@ -164,9 +164,17 @@ The caller MAY supply operator memory excerpts or file paths in your prompt.
|
|||
- **If memory context IS supplied:** compare the brief — especially `## Intent`,
|
||||
`## Goal`, and the frontmatter `framing:` value — against those facts. Report
|
||||
**EXPLICIT contradictions only** (a brief claim that a memory fact directly
|
||||
negates), never vibes or soft mismatches.
|
||||
- **If NO memory context is supplied:** score `5`, set `contradictions: []`, and
|
||||
note "no memory context supplied". Do not speculate or invent contradictions.
|
||||
negates), never vibes or soft mismatches. With no contradiction found, set
|
||||
`status: "verified"`; with one or more, set `status: "contradictions"`.
|
||||
- **If NO memory context is supplied:** score `5`, set `contradictions: []`,
|
||||
`status: "n_a"`, and note "no memory context supplied". Do not speculate or
|
||||
invent contradictions.
|
||||
|
||||
The `status` field is load-bearing: a `score: 5` alone is ambiguous because it is
|
||||
emitted both when memory was checked and aligned (`verified`) and when no memory
|
||||
was available to check (`n_a`). Downstream gates that read `memory_alignment.score
|
||||
≥ 4` therefore pass in *both* cases — `status` is what tells the operator whether
|
||||
the wrong-premise defense actually ran or was simply absent.
|
||||
|
||||
Flag as **memory-misaligned** if:
|
||||
- The frontmatter `framing:` value contradicts memory (e.g. `framing: preserve`
|
||||
|
|
@ -256,6 +264,7 @@ information that would strengthen the brief. List only if actionable.}
|
|||
},
|
||||
"memory_alignment": {
|
||||
"score": 1-5,
|
||||
"status": "verified | n_a | contradictions",
|
||||
"contradictions": [
|
||||
{ "brief_claim": "{quoted brief text}", "memory_fact": "{quoted memory fact}", "file": "{source file}" }
|
||||
]
|
||||
|
|
|
|||
|
|
@ -63,8 +63,11 @@ Parse `$ARGUMENTS` for mode flags. Order of precedence:
|
|||
- Set **project_dir = {dir}**, **brief_path = {dir}/brief.md**.
|
||||
- **Validate inputs** (soft mode — warnings do not block, errors do):
|
||||
```bash
|
||||
# Brief schema sanity check (frontmatter + state machine, soft on body sections)
|
||||
node ${CLAUDE_PLUGIN_ROOT}/lib/validators/brief-validator.mjs --soft --json "{dir}/brief.md"
|
||||
# Brief schema sanity check (frontmatter + state machine, soft on body sections).
|
||||
# When --min-brief-version was passed, append --min-version {min_brief_version}
|
||||
# so the validator emits BRIEF_VERSION_BELOW_MINIMUM (warn, never block) for an
|
||||
# older brief that sidesteps framing enforcement.
|
||||
node ${CLAUDE_PLUGIN_ROOT}/lib/validators/brief-validator.mjs --soft --json [--min-version {min_brief_version}] "{dir}/brief.md"
|
||||
|
||||
# v5.1.1 — resolve per-phase brief-signal for plan phase. Result is
|
||||
# captured as phase_signal_result and used at Agent-spawn sites below
|
||||
|
|
@ -120,7 +123,15 @@ Parse `$ARGUMENTS` for mode flags. Order of precedence:
|
|||
autonomy-gate state machine via the CLI shim:
|
||||
`node ${CLAUDE_PLUGIN_ROOT}/lib/util/autonomy-gate.mjs --state X --event Y --gates {true|false}`.
|
||||
|
||||
9. If neither `--brief` nor `--project` is present after flag parsing,
|
||||
9. **`--min-brief-version <ver>`** — (optional) version floor. Set
|
||||
**min_brief_version = {ver}** (e.g. `2.2`). Forwarded to the brief-validator
|
||||
as `--min-version {ver}`; when the brief declares an older version it emits a
|
||||
`BRIEF_VERSION_BELOW_MINIMUM` **warning** (never blocks) — framing enforcement
|
||||
only fires at `≥ 2.2`, so an older brief sidesteps the framing-alignment
|
||||
defense silently. Absent → no version check. See `docs/HANDOVER-CONTRACTS.md`
|
||||
§Handover 1 for the pre-2.2 enforcement hole this guards.
|
||||
|
||||
10. If neither `--brief` nor `--project` is present after flag parsing,
|
||||
output usage and stop:
|
||||
|
||||
```
|
||||
|
|
@ -140,6 +151,7 @@ Modes:
|
|||
--research Add up to 3 extra research briefs as planning context
|
||||
--fg No-op alias (foreground is the only mode as of v2.4.0)
|
||||
--quick Skip exploration agent swarm; plan directly
|
||||
--min-brief-version <ver> Warn (never block) if the brief is older than <ver> (e.g. 2.2)
|
||||
--export Generate shareable output from an existing plan (no new planning)
|
||||
--decompose Split an existing plan into self-contained headless sessions
|
||||
|
||||
|
|
|
|||
|
|
@ -60,7 +60,9 @@ Supported flags:
|
|||
at Agent-spawn sites in Phase 4 to inject the brief-resolved model:
|
||||
|
||||
```bash
|
||||
node ${CLAUDE_PLUGIN_ROOT}/lib/validators/brief-validator.mjs --soft --json "{dir}/brief.md"
|
||||
# When --min-brief-version was passed, append --min-version {min_brief_version}
|
||||
# so an older brief raises BRIEF_VERSION_BELOW_MINIMUM (warn, never block).
|
||||
node ${CLAUDE_PLUGIN_ROOT}/lib/validators/brief-validator.mjs --soft --json [--min-version {min_brief_version}] "{dir}/brief.md"
|
||||
node ${CLAUDE_PLUGIN_ROOT}/lib/profiles/phase-signal-resolver.mjs --brief "{dir}/brief.md" --phase research --json
|
||||
```
|
||||
|
||||
|
|
@ -71,6 +73,13 @@ Supported flags:
|
|||
state machine via the CLI shim:
|
||||
`node ${CLAUDE_PLUGIN_ROOT}/lib/util/autonomy-gate.mjs --state X --event Y --gates {true|false}`.
|
||||
|
||||
7. `--min-brief-version <ver>` — (optional) version floor for an attached
|
||||
`--project` brief. Set **min_brief_version = {ver}** (e.g. `2.2`). Forwarded
|
||||
to the brief-validator below as `--min-version {ver}`; an older brief emits a
|
||||
`BRIEF_VERSION_BELOW_MINIMUM` **warning** (never blocks) because framing
|
||||
enforcement only fires at `≥ 2.2`. Absent → no version check. See
|
||||
`docs/HANDOVER-CONTRACTS.md` §Handover 1 for the pre-2.2 enforcement hole.
|
||||
|
||||
Flags can be combined:
|
||||
- `--local` — local-only research
|
||||
- `--external --quick` — external-only, lightweight
|
||||
|
|
|
|||
|
|
@ -96,10 +96,12 @@ Optional but standard sections: `## Non-Goals`, `## Constraints`, `## Preference
|
|||
|
||||
**State machine** detail: a brief that says it has research topics but skipped them must explicitly admit it (via `brief_quality: partial`). This is the most common failure mode the validator catches.
|
||||
|
||||
**Versioning:** current is `2.1` (v5.1 — adds optional `phase_signals` + `phase_signals_partial`). The forward-compat policy in `brief-validator.mjs` header still applies: unknown frontmatter keys flow through silently, so a `2.1` brief still validates against pre-v5.1 consumers. The version bump exists because v2.1 activates the **version-conditional sequencing gate** (above) — the only check in the validator that triggers on `brief_version` rather than field-presence. There are no live `1.x` briefs; remove legacy paths in next major. The v5.5.0 contract formalization **established `2.1` as the public-contract baseline** (see the PUBLIC CONTRACT callout under the Handover 1 heading): per the S3 effort-axis decision, `phase_signals` stays **optional**, and the v2.1 sequencing gate (`phase_signals` **or** `phase_signals_partial`) is the stability mechanism. Promoting `phase_signals` to required would be a future breaking change under the protocol above — explicitly *not* part of the v5.5.0 formalization.
|
||||
**Versioning:** the `2.1` schema axis (v5.1 — adds optional `phase_signals` + `phase_signals_partial`; current is `2.2`, see the next paragraph). The forward-compat policy in `brief-validator.mjs` header still applies: unknown frontmatter keys flow through silently, so a `2.1` brief still validates against pre-v5.1 consumers. The version bump exists because v2.1 activates the **version-conditional sequencing gate** (above) — the only check in the validator that triggers on `brief_version` rather than field-presence. There are no live `1.x` briefs; remove legacy paths in next major. The v5.5.0 contract formalization **established `2.1` as the public-contract baseline** (see the PUBLIC CONTRACT callout under the Handover 1 heading): per the S3 effort-axis decision, `phase_signals` stays **optional**, and the v2.1 sequencing gate (`phase_signals` **or** `phase_signals_partial`) is the stability mechanism. Promoting `phase_signals` to required would be a future breaking change under the protocol above — explicitly *not* part of the v5.5.0 formalization.
|
||||
|
||||
**v5.5 → `2.2` (framing enforcement).** `2.2` adds two **required-at-2.2** elements — the `framing` enum field and the `## TL;DR` body section — gated identically to the v2.1 mechanism: the new requirements fire only on `brief_version ≥ 2.2`, so every existing `2.0` / `2.1` brief still validates (forward- and backward-compatible). Because adding required elements is a breaking change for any producer that declares `2.2`, this is a controlled version bump under the breaking-change protocol: downstream producers (e.g. a Tier-2 per-app producer) that emit `brief_version: "2.2"` MUST also emit `framing` + `## TL;DR`. The framing enum is additionally enforced on *any* version when the field is present (`BRIEF_INVALID_FRAMING`). The three framing-alignment layers (framing field, `brief-reviewer` memory-alignment dimension, obligatory TL;DR) implement the `CLAUDE.md` cross-cutting invariant. Note: the *plugin* version bump + CHANGELOG entry for this schema change land at the coordinated release (matrix §S10), separate from this schema axis.
|
||||
|
||||
**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.
|
||||
|
||||
**Failure modes:**
|
||||
- `BRIEF_NOT_FOUND` → consumer halts with a usage message
|
||||
- `FM_MISSING` → file has no frontmatter; halt
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ Always interactive. Phase 3 is a section-driven completeness loop (no hard cap o
|
|||
| `--external` | Only external research agents (skip codebase analysis) |
|
||||
| `--fg` | No-op alias (foreground is default since v2.4.0) |
|
||||
| `--gates {open\|closed\|adaptive}` | (v3.4.0) Autonomy-checkpoint policy. Default `adaptive` |
|
||||
| `--min-brief-version <ver>` | (S18) Warn — never block — if an attached `--project` brief declares a version below `<ver>` (e.g. `2.2`), i.e. sidesteps framing enforcement |
|
||||
| `--profile <name>` | (v4.1.0) Model profile for the research phase. |
|
||||
|
||||
Flags combine: `--project <dir> --local`, `--external --quick`.
|
||||
|
|
@ -37,6 +38,7 @@ Flags combine: `--project <dir> --local`, `--external --quick`.
|
|||
| `--research <brief> [brief2]` | Enrich with extra research briefs beyond what is in `{project_dir}/research/` |
|
||||
| `--fg` | No-op alias (foreground is default since v2.4.0) |
|
||||
| `--quick` | Plan directly (no agent swarm) |
|
||||
| `--min-brief-version <ver>` | (S18) Warn — never block — if the brief declares a version below `<ver>` (e.g. `2.2`), i.e. sidesteps framing enforcement |
|
||||
| `--export <pr\|issue\|markdown\|headless> <plan>` | Generate shareable output from existing plan |
|
||||
| `--decompose <plan>` | Split plan into self-contained headless sessions |
|
||||
| `--gates {open\|closed\|adaptive}` | (v3.4.0) Autonomy-checkpoint policy. Default `adaptive` |
|
||||
|
|
|
|||
|
|
@ -155,6 +155,32 @@ export function validateBriefContent(text, opts = {}) {
|
|||
}
|
||||
}
|
||||
|
||||
// S18 — opt-in version floor. When the caller passes minBriefVersion (commands
|
||||
// forward --min-brief-version), WARN (never block) if the brief declares a
|
||||
// version below it: framing enforcement only fires at ≥ 2.2, so an older brief
|
||||
// sidesteps the framing-alignment defense silently. Absent opt → no check.
|
||||
// trekreview briefs have no framing and are exempt.
|
||||
if (opts.minBriefVersion && fm.type !== 'trekreview') {
|
||||
const mm = String(opts.minBriefVersion).match(/^(\d+)\.(\d+)$/);
|
||||
if (mm) {
|
||||
const minMajor = Number(mm[1]);
|
||||
const minMinor = Number(mm[2]);
|
||||
const bm = fm.brief_version === undefined
|
||||
? null
|
||||
: String(fm.brief_version).match(/^(\d+)\.(\d+)$/);
|
||||
const below = bm
|
||||
? (Number(bm[1]) < minMajor || (Number(bm[1]) === minMajor && Number(bm[2]) < minMinor))
|
||||
: true; // absent or unparseable version is below any floor
|
||||
if (below) {
|
||||
warnings.push(issue(
|
||||
'BRIEF_VERSION_BELOW_MINIMUM',
|
||||
`brief_version ${fm.brief_version ?? '(absent)'} is below the requested minimum ${opts.minBriefVersion} — framing enforcement (≥ 2.2) is bypassed`,
|
||||
'Re-run /trekbrief to produce a brief_version 2.2 brief, or drop --min-brief-version to accept the older brief as-is.',
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fm.type !== undefined && !BRIEF_TYPE_VALUES.includes(fm.type)) {
|
||||
errors.push(issue(
|
||||
'BRIEF_WRONG_TYPE',
|
||||
|
|
@ -223,12 +249,15 @@ export function validateBrief(filePath, opts = {}) {
|
|||
if (import.meta.url === `file://${process.argv[1]}`) {
|
||||
const args = process.argv.slice(2);
|
||||
const strict = !args.includes('--soft');
|
||||
const filePath = args.find(a => !a.startsWith('--'));
|
||||
const minIdx = args.indexOf('--min-version');
|
||||
const minBriefVersion = minIdx >= 0 ? args[minIdx + 1] : undefined;
|
||||
// filePath is the first positional, skipping the --min-version value token.
|
||||
const filePath = args.find((a, i) => !a.startsWith('--') && i !== minIdx + 1);
|
||||
if (!filePath) {
|
||||
process.stderr.write('Usage: brief-validator.mjs [--soft] <brief.md>\n');
|
||||
process.stderr.write('Usage: brief-validator.mjs [--soft] [--min-version <x.y>] <brief.md>\n');
|
||||
process.exit(2);
|
||||
}
|
||||
const r = validateBrief(filePath, { strict });
|
||||
const r = validateBrief(filePath, { strict, minBriefVersion });
|
||||
if (args.includes('--json')) {
|
||||
process.stdout.write(JSON.stringify({ valid: r.valid, errors: r.errors, warnings: r.warnings }, null, 2) + '\n');
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -147,6 +147,12 @@ test('trekbrief — v5.5 brief-reviewer declares the memory-alignment dimension'
|
|||
'brief-reviewer must define the no-memory-context N/A fallback');
|
||||
});
|
||||
|
||||
test('trekbrief — S18 brief-reviewer memory_alignment carries a status field', () => {
|
||||
const reviewer = readFileSync(REVIEWER_FILE, 'utf8');
|
||||
assert.ok(reviewer.includes('"status": "verified | n_a | contradictions"'),
|
||||
'memory_alignment must emit a status enum (verified | n_a | contradictions) so a score-5 N/A (no memory) is distinguishable from a score-5 verified-aligned brief');
|
||||
});
|
||||
|
||||
test('trekbrief — v5.5 template carries framing field, 2.2, and TL;DR section', () => {
|
||||
const tpl = readFileSync(TEMPLATE_FILE, 'utf8');
|
||||
assert.ok(/brief_version: "2\.2"/.test(tpl), 'template must declare brief_version 2.2');
|
||||
|
|
|
|||
|
|
@ -869,3 +869,30 @@ test('S17: NW2 bake-off verdict is not labeled unqualified POSITIVE (raw data un
|
|||
'T2-bakeoff-results.md must disclose the raw per-run data is un-archived / un-reproducible.',
|
||||
);
|
||||
});
|
||||
|
||||
// ── S18: framing-hardening cross-file invariants ───────────────────────────
|
||||
|
||||
test('S18: --min-brief-version is documented at the trekplan + trekresearch boundary', () => {
|
||||
// The opt-in version floor must be discoverable wherever flags live: both
|
||||
// consuming commands and the central flag reference.
|
||||
for (const f of ['commands/trekplan.md', 'commands/trekresearch.md', 'docs/command-modes.md']) {
|
||||
assert.ok(
|
||||
read(f).includes('--min-brief-version'),
|
||||
`${f} must document the --min-brief-version flag (S18 framing-hardening)`,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test('S18: HANDOVER-CONTRACTS documents the pre-2.2 zero-framing-enforcement hole', () => {
|
||||
// The framing defense is producer-elective: a brief declaring ≤ 2.1 sidesteps
|
||||
// it entirely. Handover 1 (PUBLIC CONTRACT) must disclose this and name the remedy.
|
||||
const h = read('docs/HANDOVER-CONTRACTS.md');
|
||||
assert.ok(
|
||||
/BRIEF_VERSION_BELOW_MINIMUM/.test(h),
|
||||
'HANDOVER-CONTRACTS.md must name the BRIEF_VERSION_BELOW_MINIMUM gate as the pre-2.2 remedy',
|
||||
);
|
||||
assert.ok(
|
||||
/zero framing enforcement|no framing enforcement|sidestep/i.test(h),
|
||||
'HANDOVER-CONTRACTS.md must state that pre-2.2 briefs receive zero framing enforcement',
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -358,3 +358,38 @@ test('validateBrief — v5.5 trekreview brief not subject to framing/TL;DR gate'
|
|||
assert.ok(!r.errors.find(e => e.code === 'BRIEF_MISSING_FRAMING'));
|
||||
assert.ok(!r.errors.find(e => e.code === 'BRIEF_MISSING_SECTION' && /TL;DR/.test(e.message)));
|
||||
});
|
||||
|
||||
// S18 — opt-in --min-brief-version gate. Warns (never blocks) when a brief
|
||||
// declares a version below the caller's floor, sidestepping framing enforcement.
|
||||
const BRIEF_21 = GOOD_BRIEF
|
||||
.replace('brief_version: "2.0"', 'brief_version: "2.1"')
|
||||
.replace('source: interview\n', 'source: interview\nphase_signals_partial: true\n');
|
||||
|
||||
test('validateBrief — S18 min-version: older brief below floor warns but does not block', () => {
|
||||
const r = validateBriefContent(BRIEF_21, { strict: true, minBriefVersion: '2.2' });
|
||||
assert.equal(r.valid, true, `must warn, not block: ${JSON.stringify(r.errors)}`);
|
||||
assert.ok(
|
||||
r.warnings.find(w => w.code === 'BRIEF_VERSION_BELOW_MINIMUM'),
|
||||
`expected BRIEF_VERSION_BELOW_MINIMUM; warnings=${JSON.stringify(r.warnings)}`,
|
||||
);
|
||||
});
|
||||
|
||||
test('validateBrief — S18 min-version: absent opt means no version-floor check', () => {
|
||||
const r = validateBriefContent(BRIEF_21, { strict: true });
|
||||
assert.ok(!r.warnings.find(w => w.code === 'BRIEF_VERSION_BELOW_MINIMUM'));
|
||||
});
|
||||
|
||||
test('validateBrief — S18 min-version: brief meeting the floor does not warn', () => {
|
||||
const r = validateBriefContent(GOOD_BRIEF_22, { strict: true, minBriefVersion: '2.2' });
|
||||
assert.ok(!r.warnings.find(w => w.code === 'BRIEF_VERSION_BELOW_MINIMUM'));
|
||||
});
|
||||
|
||||
test('validateBrief — S18 min-version: 2.0 brief below 2.2 floor warns', () => {
|
||||
const r = validateBriefContent(GOOD_BRIEF, { minBriefVersion: '2.2' });
|
||||
assert.ok(r.warnings.find(w => w.code === 'BRIEF_VERSION_BELOW_MINIMUM'));
|
||||
});
|
||||
|
||||
test('validateBrief — S18 min-version: trekreview brief is exempt (no framing to bypass)', () => {
|
||||
const r = validateBriefContent(REVIEW_AS_BRIEF, { minBriefVersion: '2.2' });
|
||||
assert.ok(!r.warnings.find(w => w.code === 'BRIEF_VERSION_BELOW_MINIMUM'));
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue