feat(voyage): W2 impl (S4) — gate resolver model, adopt native effort:, doc-truth
S4 of the 2.1.181 upgrade — implementation, not a gate. TDD: failing test written first for the resolver gate, then the fix; suite green throughout. - Resolver MAJOR (FIX): lib/profiles/phase-signal-resolver.mjs now imports BASE_ALLOWED_MODELS from profile-validator and gates `model` (if 'model' in entry && BASE_ALLOWED_MODELS.includes(entry.model)), mirroring the EFFORT_LEVELS gate one line up. Out-of-allowlist models (gpt-4, haiku) are dropped instead of handed to an agent spawn — defense-in-depth behind brief-validator's validation-time check. No circular import (brief-validator already imports the same symbol). +2 tests (drops-invalid / keeps-valid). - Native effort: (SHIP, static additive): effort: frontmatter on 8 agents — retrieval (task-finder, git-historian, dependency-tracer, architecture-mapper) = medium; adversarial-reasoning (plan-critic, risk-assessor, contrarian-researcher, review-coordinator) = high. The other 15 stay unset -> inherit Opus-4.8 default (high). This per-spawn REASONING effort is a different axis from brief phase_signals.effort (ORCHESTRATION shape) per the S3 decision. - Doc-truth + axis distinction: new canonical docs/profiles.md §Model & effort axes (opus->Opus 4.8 default-high; orchestration vs reasoning effort table; native-effort precedence; per-agent levels). Short notes in CLAUDE.md (after Agents table) and README.md (Cost profile), both pointing to profiles.md. - Open (non-blocking, unchanged): only STATIC effort shipped — the verified-safe minimum. Profile-driven DYNAMIC effort still needs verification of the per-spawn effort param or env-var injection. Matrix: new "S4 resolutions" section. Tests 582 total / 580 pass / 0 fail / 2 skip (was 578 pass; +2). claude plugin validate passes (only pre-existing root-CLAUDE.md warning). 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
fbf83f2271
commit
fdd3ad80d7
14 changed files with 102 additions and 1 deletions
|
|
@ -12,6 +12,7 @@
|
|||
import { readFileSync, existsSync } from 'node:fs';
|
||||
import { parseDocument } from '../util/frontmatter.mjs';
|
||||
import { PHASE_SIGNAL_PHASES, EFFORT_LEVELS } from '../validators/brief-validator.mjs';
|
||||
import { BASE_ALLOWED_MODELS } from '../validators/profile-validator.mjs';
|
||||
|
||||
/**
|
||||
* Resolve a brief's phase_signal entry for one phase.
|
||||
|
|
@ -27,6 +28,8 @@ import { PHASE_SIGNAL_PHASES, EFFORT_LEVELS } from '../validators/brief-validato
|
|||
* - No entry for the requested phase
|
||||
*
|
||||
* Returns partial `{effort}` (with `model: undefined`) when the signal omits model.
|
||||
* `effort` is gated against EFFORT_LEVELS and `model` against BASE_ALLOWED_MODELS;
|
||||
* values outside those allowlists are dropped (treated as absent).
|
||||
*/
|
||||
export function resolvePhaseSignal(briefFrontmatter, phase) {
|
||||
if (!briefFrontmatter || typeof briefFrontmatter !== 'object') return null;
|
||||
|
|
@ -37,7 +40,11 @@ export function resolvePhaseSignal(briefFrontmatter, phase) {
|
|||
if (entry && typeof entry === 'object' && entry.phase === phase) {
|
||||
const out = {};
|
||||
if ('effort' in entry && EFFORT_LEVELS.includes(entry.effort)) out.effort = entry.effort;
|
||||
if ('model' in entry) out.model = entry.model;
|
||||
// MAJOR fix (S4): gate `model` against BASE_ALLOWED_MODELS, mirroring the
|
||||
// effort gate above. brief-validator already rejects out-of-allowlist
|
||||
// models at validation time; this is defense-in-depth so a brief that
|
||||
// slipped validation cannot hand a junk model to an agent spawn.
|
||||
if ('model' in entry && BASE_ALLOWED_MODELS.includes(entry.model)) out.model = entry.model;
|
||||
return out;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue