feat(research-loop-cap): give the discovery ceiling a reader, not just a sentence
The bounded-cost NFR asks for explicit ceilings on BOTH axes - max
conversation turns and max discovered dimensions. The turn axis got
MAX_CONV_TURNS, a ledger-backed reader and a PreToolUse enforcer. The
discovery axis got one sentence in Phase 4.5 prose ("append candidates only
while the whole list stays at or below maxDimensions: 8") with no constant
of its own, no reader, and no test that a run exceeding it is caught. That
is the brief_reviewer_iter_cap shape the operator decision warned about: a
cap nothing reads.
checkDimensionCeiling() is the reader, exposed on the CLI as
--check-dimensions N (exit 0 within, exit 1 rejected), and Phase 4.5 step 3
now calls it once the final list is settled instead of merely describing the
bound.
Three deliberate choices:
- The ceiling IS MAX_TOTAL_DIMENSIONS, the constant that sizes the turn
budget. Both axes read one settings.json:16 value, so they cannot end up
enforcing different numbers - a second constant is how that drift starts.
- An unreadable count is REJECTED ('abc', null, undefined, {}, -1, NaN,
non-integers). A cost ceiling that waves through what it cannot measure is
not a ceiling.
- --check-dimensions requires no run id, effort or VOYAGE_STORM_ENABLED.
Phase 4.5 never calls the budget gate - that is why its skip-guard reads
the flag directly - so the ceiling check must not inherit the gate's
preconditions.
The mitigation the review already verified still holds and is unchanged:
MAX_TOTAL_DIMENSIONS bounds actual retrieval cost regardless of how many
dimensions discovery appends. What was missing was anything that FAILS on a
list over the bound, and now a run over it is rejected by exit code.
Review finding 96a3ee51152dfe72aca703f771843f2f3639e7b6 (MINOR).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LuGhWAbWyRFBFeemfhxoVv
This commit is contained in:
parent
22cb7df403
commit
84612b2641
4 changed files with 137 additions and 2 deletions
|
|
@ -418,6 +418,23 @@ agent surfaced that no interview dimension claims.
|
|||
raised here, so the documented 3–8 dimension range stays true and the
|
||||
README prose about it stays untouched. If the interview already produced 8
|
||||
dimensions, this phase discovers nothing and says so.
|
||||
|
||||
**The ceiling has a reader — use it.** Once the final list is settled, run
|
||||
the check below. Exit 1 means the list exceeded the ceiling: drop discovered
|
||||
dimensions until it passes. Do not proceed to Phase 5 on a rejected list —
|
||||
the turn budget is sized against this same ceiling, so a list over it spends
|
||||
a budget that was never approved for it.
|
||||
|
||||
```bash
|
||||
# Same VOYAGE_ROOT resolution as the per-turn protocol in Phase 5. Exit 0 =
|
||||
# within the ceiling, exit 1 = rejected. JSON on stdout: {ok, count, ceiling, reason?}
|
||||
node "$VOYAGE_ROOT/lib/util/research-loop-cap.mjs" --check-dimensions {final dimension count}
|
||||
```
|
||||
|
||||
The ceiling constant is `MAX_TOTAL_DIMENSIONS` in
|
||||
`lib/util/research-loop-cap.mjs` — deliberately the same constant that sizes
|
||||
the Phase 5 turn budget, so the two axes of the bounded-cost NFR cannot end
|
||||
up enforcing different numbers for one `settings.json:16` value.
|
||||
4. **Record the baseline.** Keep the interview-derived count as
|
||||
`dimensions_baseline` so the discovered delta is machine-readable against
|
||||
the final `dimensions` (Phase 8 stats).
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue