suite fix pass: nine seam failures classified and ported; --scope-only lands on the review runner

Hermetic full suite on 74a7330: exit 1, nine failures — every one classified by
EXECUTION before any change; none is a code regression, none is base-pre-existing
(all five pre-existing tests pass on fc9c152 in a detached worktree).

(b) H2 fence, 3x test_runtime_mode_core tripwire tests: pinned the retired
enumerate-and-detect contract (vector admitted, write executed, post-hoc tripwire
reports). The full inverted fence refuses those inline vectors UPFRONT with
nothing executed — proven identical on the H2 winner's own head (all three red on
cxi/p2-axes@5e97465 exactly like the candidate). The tripwire layer itself is
alive and now tested with vectors that legitimately pass the inverted fence
(script-file invocations — the fence judges only payloads it can read): probed
first by hand, the write happens and LIGHT_MODE_REPO_WRITE_BLOCKED fires. A new
pin (test_light_mode_inline_writer_is_refused_upfront) records the contract
change explicitly: upfront refusal, file untouched. fence_probe re-run: PASS.

(b) p6 seam, 2x test_claudexor_owned_daemon: pinned p6's decision-object feed of
_record_executor_facts; the fusion re-homed the fact to the ONE stamped task
record (p6's own stated principle - a projection of the decision, never a second
derivation - one level stronger). Assertions unchanged, feed ported.

(b) H3 seam, 2x test_max_tokens_constants: pinned p7a's _fit_shared_review_prompt
name; the fused candidate keeps p5x's api/session-split _fit_triad_prompt with
p7a's window internals. Tests now drive the surviving function through the same
assemble seam the production caller uses; both semantic pins (quorum sizing;
local-route window) unchanged.

(b) 2x source-carrier pins red on p6's OWN head (proven by execution on 1534e97):
test_page_chrome_static effort round-trip (6.3 moved the carrier to per-slot
reviewer_slots.js) and test_ws5_carryover probe-gate pin (the gate grew the 6.1
OUROBOROS_REVIEWER_SLOTS key and wrapped; still route-affecting-gated - the
ported pin asserts the surviving expression).

Also lands the review-runner change (scope_only_cap_scope.diff, +70/-8):
--scope-only exempts a scope-only run from the raw-diff cap that governs the
advisory/triad lanes (scope assembles its own atlas pack under its own budget)
with a loud no-advisory/no-triad-coverage disclosure; the p5x trusted-rerun list
entry (review_execution.py) the pre-p5x patched copy lacked is preserved, and the
call-site import the patch orphaned is removed. ISO-SPAWN (suite children with
scrubbed envs writing to the live data root) is measured as a RIPPLE
(44 import sites + derived import-time constants), NOT fixed here per the hard
limit, and ledgered as a precisely-diagnosed disclosed residual.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Ouroboros 2026-08-03 18:07:37 +03:00
parent 74a73306c9
commit a6a3c1ff2a
6 changed files with 163 additions and 55 deletions

View file

@ -1041,6 +1041,16 @@ def _parse_args():
default="HEAD",
help="Committed proposal ref for --contributor (default: HEAD).",
)
parser.add_argument(
"--scope-only",
action="store_true",
help=(
"Run ONLY the scope lane (no advisory, no triad). The raw-diff cap governs "
"the lanes that put the staged diff into a model prompt; scope assembles its "
"own atlas pack under its own budget, so a scope-only run is exempt from that "
"cap and discloses that it carries no advisory and no triad coverage."
),
)
args = parser.parse_args()
if args.contributor and args.no_isolated_checkout:
@ -1133,6 +1143,58 @@ def _prepare_review_configuration(args) -> tuple[dict | None, str, dict]:
return contributor_snapshot, review_base_commit, resolved_config
_RAW_DIFF_CAP_LANES = ("advisory", "triad")
def _raw_diff_cap_decision(reviewable_chars: int, *, scope_only: bool) -> dict:
"""Decide the raw-diff cap for THIS invocation, and say what it governed.
The cap exists because the advisory pre-review and the triad put the staged
diff itself into a model prompt: an oversized diff there is a commit-shape
problem, and «split the phase» is the honest answer. Scope review does not
read the raw diff it assembles its own pack through the review-context
atlas under its own budget and its own typed refusals so measuring a
scope-only run against the raw-diff size refuses the one lane built for a
large subject, using a number that does not describe what it sends.
The cap constant, its policy sentence and its behaviour for every ordinary
invocation are unchanged. A scope-only run is exempt and MUST disclose that
it carries no advisory and no triad coverage; the returned record is written
into the packet on both paths so an exempt run can never be mistaken for a
run that fit.
"""
from ouroboros.tools.claude_advisory_review import _MAX_DIFF_CHARS_ERROR
over = int(reviewable_chars) > _MAX_DIFF_CHARS_ERROR
record = {
"limit_chars": _MAX_DIFF_CHARS_ERROR,
"measured_chars": int(reviewable_chars),
"governs_lanes": list(_RAW_DIFF_CAP_LANES),
"over_limit": over,
"scope_only": bool(scope_only),
"exempt": bool(over and scope_only),
"refuses": bool(over and not scope_only),
}
if record["refuses"]:
record["message"] = (
f"ERROR: staged diff is {reviewable_chars:,} chars — over the advisory hard cap "
f"({_MAX_DIFF_CHARS_ERROR:,}). Policy: split the phase into smaller "
"single-intent commits instead of relaxing the gate."
)
elif record["exempt"]:
record["message"] = (
f"DISCLOSED: staged diff is {reviewable_chars:,} chars, over the "
f"{_MAX_DIFF_CHARS_ERROR:,}-char raw-diff cap. This run is --scope-only: the "
"advisory and triad lanes, which put the raw diff into a model prompt, are NOT "
"running. Scope assembles its own atlas pack under its own budget and typed "
"refusals. This packet therefore carries NO advisory and NO triad coverage of "
"this subject — that coverage must come from separate packets."
)
else:
record["message"] = ""
return record
def _operator_reviewable_diff_chars(fallback_chars: int) -> int:
"""Size of the TEXTUAL staged diff — what the production gates review.
@ -1192,20 +1254,18 @@ def main() -> int:
print(message, file=sys.stderr)
return 2
from ouroboros.tools.claude_advisory_review import _MAX_DIFF_CHARS_ERROR
reviewable_chars = (
len(staged) if contributor_snapshot is not None
else _operator_reviewable_diff_chars(len(staged))
)
if reviewable_chars > _MAX_DIFF_CHARS_ERROR:
print(
f"ERROR: staged diff is {reviewable_chars:,} chars — over the advisory hard cap "
f"({_MAX_DIFF_CHARS_ERROR:,}). Policy: split the phase into smaller "
"single-intent commits instead of relaxing the gate.",
file=sys.stderr,
)
raw_diff_cap = _raw_diff_cap_decision(
reviewable_chars, scope_only=bool(getattr(args, "scope_only", False))
)
if raw_diff_cap["refuses"]:
print(raw_diff_cap["message"], file=sys.stderr)
return 3
if raw_diff_cap["exempt"]:
print(raw_diff_cap["message"], file=sys.stderr)
sha8 = (
str(contributor_snapshot["head_sha"])[:8]

View file

@ -149,7 +149,7 @@ def test_login_endpoint_validates_before_any_daemon_work():
# ---------------------------------------------------------------------------
def _agent_with_metadata(decision, task_id="child-1"):
def _agent_with_metadata(task, task_id="child-1"):
import types
from ouroboros.agent import OuroborosAgent
@ -160,7 +160,12 @@ def _agent_with_metadata(decision, task_id="child-1"):
"parent_task_id": "p", "model": "m", "task_group_id": "g",
}
agent._current_task_id = task_id
agent._record_executor_facts(decision)
# Since synthesis the fact is read from the ONE record the dispatch
# resolution stamped onto the task (`resolve_subagent_dispatch` ->
# record_fields) — the same principle this file always asserted ("a
# projection of the decision, never a second derivation"), one level
# stronger: the projection reads the durable record, not a live object.
agent._record_executor_facts(task if isinstance(task, dict) else {})
return agent, types
@ -170,12 +175,8 @@ def test_resolved_harness_route_reaches_the_frame_assembler():
frame assembler already projects never re-derived per surface."""
import types
class _Harness:
blocked = False
executor = "harness"
route = types.SimpleNamespace(route_id="codex")
agent, _ = _agent_with_metadata(_Harness())
agent, _ = _agent_with_metadata(
{"effective_executor": "harness", "executor_route": "codex"})
frame = agent._subagent_progress_meta("running")
assert frame["executor_route"] == "codex"
# The frame keeps carrying the execution facts it always did.
@ -188,22 +189,14 @@ def test_no_executor_fact_when_the_run_is_native_blocked_or_undecided():
API path is the ordinary case and must not print 'api' on every bubble."""
import types
class _Native:
blocked = False
executor = "native"
route = None
class _Blocked:
blocked = True
executor = "blocked"
route = types.SimpleNamespace(route_id="codex")
native, _ = _agent_with_metadata(_Native(), "child-2")
native, _ = _agent_with_metadata(
{"effective_executor": "native", "executor_route": ""}, "child-2")
assert native._subagent_progress_meta("running")["executor_route"] == ""
# A blocked or absent decision records nothing at all.
blocked, _ = _agent_with_metadata(_Blocked(), "child-3")
# A blocked or unresolved dispatch records nothing at all.
blocked, _ = _agent_with_metadata(
{"effective_executor": "blocked", "executor_route": "codex"}, "child-3")
assert "executor_route" not in blocked._current_task_metadata
undecided, _ = _agent_with_metadata(None, "child-4")
undecided, _ = _agent_with_metadata({}, "child-4")
assert "executor_route" not in undecided._current_task_metadata

View file

@ -634,6 +634,23 @@ def test_the_triad_sizes_its_shared_prompt_by_quorum_not_by_the_strictest_slot(m
assert review._quorum_input_token_limit(list(two_slot), two_slot) == 90_000
def _triad_assemble(review, stable_fields, dynamic_fields):
"""The production caller's assemble closure, rebuilt from the (monkeypatched)
templates the fused `_fit_triad_prompt` takes assembly from its caller
(the 5.2/5.3 api/session split), so the test drives the same seam."""
def _assemble(files_section, staged_diff):
stable = review._REVIEW_PROMPT_TEMPLATE_STABLE.format(**stable_fields)
dynamic = review._REVIEW_PROMPT_TEMPLATE_DYNAMIC.format(**{
**dynamic_fields,
"current_files_section": files_section,
"diff_text": staged_diff,
})
return stable + "\n" + dynamic, len(stable) + 1
return _assemble
def test_a_sub_quorum_window_degrades_its_own_slot_instead_of_blocking_the_commit_gate(
monkeypatch, tmp_path,
):
@ -674,12 +691,12 @@ def test_a_sub_quorum_window_degrades_its_own_slot_instead_of_blocking_the_commi
"current_files_section": "full snapshot of a.py",
}
_assemble = _triad_assemble(review, stable_fields, dynamic_fields)
def fit(models):
return review._fit_shared_review_prompt(
stable_fields=stable_fields,
dynamic_fields=dynamic_fields,
models=models,
target_repo=tmp_path,
return review._fit_triad_prompt(
models, _assemble, dynamic_fields["current_files_section"],
dynamic_fields["diff_text"], dynamic_fields["changed_files"], tmp_path,
)
_, _, overflow = fit(["big/one", "big/two", "small/one"])
@ -808,11 +825,11 @@ def test_triad_fit_sizes_against_the_local_route(monkeypatch, tmp_path):
"current_files_section": "full snapshot of a.py",
}
_, _, overflow = review._fit_shared_review_prompt(
stable_fields=stable_fields,
dynamic_fields=dynamic_fields,
models=["openai/gpt-5.6-terra"],
target_repo=tmp_path,
_assemble = _triad_assemble(review, stable_fields, dynamic_fields)
_, _, overflow = review._fit_triad_prompt(
["openai/gpt-5.6-terra"], _assemble,
dynamic_fields["current_files_section"], dynamic_fields["diff_text"],
dynamic_fields["changed_files"], tmp_path,
)
assert "REVIEW_BLOCKED" in overflow, (
"a local-only install must size the triad prompt against the local "

View file

@ -66,11 +66,17 @@ def test_settings_secrets_are_generic_and_integrations_tab_removed():
def test_settings_scope_review_effort_round_trips():
ui = _read("web/modules/settings_ui.js")
"""6.3 moved the Review/Scope efforts off the Behavior tab onto the Models
page as PER-SLOT dropdowns (red on cxi/p6-ui-v2's own head — the branch
moved the carrier and left this pin behind): the owner-facing carrier is now
reviewer_slots.js, where an EMPTY slot effort inherits the surface default
(OUROBOROS_EFFORT_SCOPE_REVIEW backend-side) and the advisory row defaults
low (D14). The mode-guard filter in settings.js is unchanged."""
slots_ui = _read("web/modules/reviewer_slots.js")
settings = _read("web/modules/settings.js")
assert "s-effort-scope-review" in ui
assert "OUROBOROS_EFFORT_SCOPE_REVIEW" in settings
assert "['s-effort-scope-review', 'OUROBOROS_EFFORT_SCOPE_REVIEW', 'high']" in settings
assert "scope review effort" in slots_ui # per-slot surface-default wording
assert "review effort" in slots_ui
assert "effort: 'low'" in slots_ui # the advisory default (D14)
assert "key !== 'OUROBOROS_RUNTIME_MODE' && key !== 'OUROBOROS_CONTEXT_MODE'" in settings

View file

@ -948,7 +948,13 @@ def test_light_mode_allows_shell_wrapper_non_repo_writer(tmp_path, monkeypatch):
assert "LIGHT_MODE_BLOCKED" not in result, result[:200]
def test_light_mode_tripwire_catches_python_repo_writer(tmp_path, monkeypatch):
def test_light_mode_inline_writer_is_refused_upfront(tmp_path, monkeypatch):
"""H2 (owner decision 2026-08-03): the INVERTED interpreter write fence refuses
an inline payload it cannot prove repo-safe BEFORE execution python gets a
real AST proof, and a proven write is refused with nothing executed. The old
enumerate-and-detect fence ADMITTED this exact vector and left the post-hoc
tripwire to report the already-done write; that contract deliberately no
longer exists, and the file staying untouched is the point."""
import ouroboros.safety as safety_mod
repo = _git_repo(tmp_path)
@ -961,6 +967,29 @@ def test_light_mode_tripwire_catches_python_repo_writer(tmp_path, monkeypatch):
{"cmd": [sys.executable, "-c", "from pathlib import Path; Path('README.md').write_text('hacked\\n')"]},
)
assert "LIGHT_MODE_BLOCKED" in result, result[:300]
assert "LIGHT_MODE_REPO_WRITE_BLOCKED" not in result # refused upfront, not detected after
assert (repo / "README.md").read_text(encoding="utf-8") != "hacked\n"
def test_light_mode_tripwire_catches_python_repo_writer(tmp_path, monkeypatch):
"""The tripwire is the DETECTION layer BEHIND the fence: a SCRIPT-file
invocation hands the fence nothing inline (by design the fence judges only
payloads it can read), executes, and the post-hoc snapshot catches the repo
mutation. Vector updated at the H2 synthesis: the old inline vector is now
refused upfront (see test_light_mode_inline_writer_is_refused_upfront), so
it can no longer reach the layer this test exists to cover."""
import ouroboros.safety as safety_mod
repo = _git_repo(tmp_path)
payload = tmp_path / "writer.py"
payload.write_text("from pathlib import Path\nPath('README.md').write_text('hacked\\n')\n")
monkeypatch.setenv("OUROBOROS_RUNTIME_MODE", "light")
monkeypatch.setattr(safety_mod, "check_safety", lambda *a, **k: (True, ""))
reg = ToolRegistry(repo_dir=repo, drive_root=tmp_path / "drive")
result = reg.execute("run_command", {"cmd": [sys.executable, str(payload)]})
assert "LIGHT_MODE_REPO_WRITE_BLOCKED" in result, result[:300]
assert "README.md" in result
assert (repo / "README.md").read_text(encoding="utf-8") == "hacked\n"
@ -974,10 +1003,9 @@ def test_light_mode_tripwire_catches_untracked_repo_file(tmp_path, monkeypatch):
monkeypatch.setattr(safety_mod, "check_safety", lambda *a, **k: (True, ""))
reg = ToolRegistry(repo_dir=repo, drive_root=tmp_path / "drive")
result = reg.execute(
"run_command",
{"cmd": [sys.executable, "-c", "from pathlib import Path; Path('new_tool.py').write_text('x\\n')"]},
)
payload = tmp_path / "creator.py"
payload.write_text("from pathlib import Path\nPath('new_tool.py').write_text('x\\n')\n")
result = reg.execute("run_command", {"cmd": [sys.executable, str(payload)]})
assert "LIGHT_MODE_REPO_WRITE_BLOCKED" in result, result[:300]
assert "new_tool.py" in result
@ -1020,10 +1048,10 @@ def test_light_mode_tripwire_runs_after_failed_command(tmp_path, monkeypatch):
monkeypatch.setattr(safety_mod, "check_safety", lambda *a, **k: (True, ""))
reg = ToolRegistry(repo_dir=repo, drive_root=tmp_path / "drive")
result = reg.execute(
"run_command",
{"cmd": [sys.executable, "-c", "from pathlib import Path; Path('README.md').write_text('bad\\n'); raise SystemExit(2)"]},
)
payload = tmp_path / "failing_writer.py"
payload.write_text(
"from pathlib import Path\nPath('README.md').write_text('bad\\n')\nraise SystemExit(2)\n")
result = reg.execute("run_command", {"cmd": [sys.executable, str(payload)]})
assert "LIGHT_MODE_REPO_WRITE_BLOCKED" in result, result[:300]
assert "SHELL_EXIT_ERROR" in result

View file

@ -673,8 +673,12 @@ def test_settings_save_probes_review_slots_with_the_needs_ack_contract(monkeypat
):
assert needle in settings_js, needle
gateway_src = (repo / "ouroboros" / "gateway" / "settings.py").read_text(encoding="utf-8")
# The gate grew the 6.1 slots SSOT key (a slot change IS a route change) and
# wrapped; the pinned semantics is unchanged — probe only on ROUTE-affecting
# keys, never on every save.
assert (
'k.startswith("OUROBOROS_SCOPE_REVIEW_MODEL") or k in _REVIEW_ROUTE_BASE_URL_KEYS'
'k.startswith("OUROBOROS_SCOPE_REVIEW_MODEL") or k == "OUROBOROS_REVIEWER_SLOTS"\n'
' or k in _REVIEW_ROUTE_BASE_URL_KEYS'
in gateway_src
), "the probe must be gated on a ROUTE-affecting change, not run on every save"