mirror of
https://github.com/razzant/ouroboros.git
synced 2026-10-03 04:07:04 +00:00
P8-2: disclose each preflight pass's seconds and its budget
A green hermetic gate discards its pytest output: stdout is piped and rendered
only on timeout, containment loss or failure, so the one run that proves the
suite is healthy is also the one run that leaves no record of how long it took.
That is exactly the evidence the gate budget needs, and it did not exist.
The seconds were already computed as a local (`elapsed`, measured from
`pass_started` for each pass's own wall clock). They are now collected into one
list of `(label, seconds)` and handed to the disclosure call the green path
already makes, which adds two keys to the event dict it already builds:
`pass_seconds` and `budget_sec` (the resolved total timeout, read off the proof
workload). No new file, event, timer, reader or pytest-text parsing.
`preflight_test_proof` is declared disclosure-only and is never read as
authority, and neither key feeds `preflight_test_workload`, so an in-flight
proof stays reusable across this change.
Verified on a real green hermetic run over a trivial candidate: the row carries
{"parallel": 1.3, "serial": 0.4} against budget_sec 777.
This commit is contained in:
parent
5e0c2c0b68
commit
3b4461dfd4
4 changed files with 33 additions and 4 deletions
|
|
@ -243,7 +243,7 @@ server.py (Starlette+uvicorn) ← HTTP + WebSocket on configurable host:port (de
|
|||
├── review_actor_aggregation.py ← Contract aggregation for completed review actor rows; demotes non-contract-valid responses
|
||||
├── review_session_usage.py ← UsageScope-to-custody attribution for delegated review sessions
|
||||
├── review_thread_continuity.py ← Thin Claudexor thread operations for delegated plan reviewers
|
||||
├── commit_admission.py ← Deterministic commit-admission SSOT: release checks + auto-sync, staged-Python syntax compile, `run_tests_preflight_with_proof` forwarding actual runner evidence to ordinary/managed consumers; the advisory and commit gates both delegate here
|
||||
├── commit_admission.py ← Deterministic commit-admission SSOT: release checks + auto-sync, staged-Python syntax compile, `run_tests_preflight_with_proof` forwarding actual runner evidence to ordinary/managed consumers; the advisory and commit gates both delegate here; the disclosure-only `preflight_test_proof` row also carries `pass_seconds` (each executed pass's own wall clock, empty on a reused proof) and `budget_sec` (the resolved total timeout), since a green gate renders no pytest output to record it
|
||||
├── reviewer_slot_config.py ← Structured reviewer-slot SSOT: stable ids, route targets, per-slot effort, disclosure-only execution records, save/runtime validation (`reviewer_slot_save_check`, whose only disclosure is the one-time R12 notice `acceptance_delivery_disclosure` when a save first makes the triad retrieve); a row is EITHER an inline route OR a `subagent_id` (materialized at load; unresolvable = typed refusal); `is_session` is transport while `retrieves` is delivery class; advisory shares the row vocabulary (+`enabled`, `disabled_reason`), and so does the optional `deep_review` singleton (fixed id `deep_review_slot_1`, no `enabled`; `deep_review_slot()` returns the saved row or the packed api row synthesized from the legacy `OUROBOROS_MODEL_DEEP_SELF_REVIEW` key, whose own effort outranks `OUROBOROS_EFFORT_DEEP_SELF_REVIEW` only when set); malformed config refuses EVERY surface — commit/scope/advisory/plan/skill review, deep self-review and task acceptance (owner R3; the former legacy/default API panel residual is gone); `triad_delivery_slots` is THE triad-row builder: plan review, skill/commit review (as aligned vectors through `commit_triad_delivery`) and task acceptance all read the rows through it, so no surface reads a projection of the panel instead of the panel; the legacy comma keys remain a runtime projection of api model ids for legacy consumers only
|
||||
├── review_state.py ← Durable advisory pre-review state (`state/advisory_review.json`)
|
||||
├── review_state_model.py, review_state_records.py, review_state_custody.py, review_records.py, review_verdict.py, review_projection.py, review_evidence_sections.py ← The review ledger and its vocabularies, re-exported through `review_state.py`: the in-memory ledger and every transition it permits; the record types and the pure rules that shape them; durable custody of in-flight invocations plus attempt-history hygiene; the typed panel records and hardness vocabulary shared by every review surface; the pure reducers from panel actor rows to a verdict, a tier and a capsule; panel identity and the compact redacted projection of a run; and the bounded, provenance-tagged sections of the task-acceptance evidence packet
|
||||
|
|
|
|||
|
|
@ -256,12 +256,19 @@ def _executable_identity(executable: str) -> tuple:
|
|||
return str(invocation), str(path), stat.st_dev, stat.st_ino, stat.st_size, stat.st_mtime_ns
|
||||
|
||||
|
||||
def log_preflight_test_proof(ctx, proof: PreflightTestProof, *, reused: bool, phase: str) -> None:
|
||||
"""Disclose the runner's actual proof on the existing event log, never read it as authority."""
|
||||
def log_preflight_test_proof(ctx, proof: PreflightTestProof, *, reused: bool, phase: str,
|
||||
passes: list[tuple[str, float]] | None = None) -> None:
|
||||
"""Disclose the runner's actual proof on the existing event log, never read it as authority.
|
||||
|
||||
``passes`` are the executed passes' own `(label, seconds)`. A green run renders
|
||||
no pytest output at all, so this row is the only durable record of what the gate
|
||||
cost against `budget_sec`; a reused proof executed nothing and reports none.
|
||||
"""
|
||||
event = {
|
||||
"ts": utc_now_iso(), "type": "preflight_test_proof",
|
||||
"action": "reused" if reused else "created", "phase": phase,
|
||||
"task_id": str(getattr(ctx, "task_id", "") or ""),
|
||||
"pass_seconds": dict(passes or ()), "budget_sec": proof.workload[1],
|
||||
"head": proof.head, "tree": proof.tree, "index_tree": proof.index_tree,
|
||||
"workload_fingerprint": hashlib.sha256(json.dumps(
|
||||
proof.workload, sort_keys=True, separators=(",", ":"),
|
||||
|
|
|
|||
|
|
@ -1377,6 +1377,7 @@ def run_hermetic_pytest(
|
|||
if node_error := (node_result or {}).get("error"):
|
||||
return node_error
|
||||
empty_passes = 0
|
||||
timings: list[tuple[str, float]] = []
|
||||
for spec in passes:
|
||||
# Keep the exact float remainder; rounding up would exceed the
|
||||
# shared total budget (including the preceding node lane).
|
||||
|
|
@ -1394,6 +1395,7 @@ def run_hermetic_pytest(
|
|||
agent_python, worktree, temp_root, spec.args, remaining
|
||||
)
|
||||
elapsed = time.monotonic() - pass_started
|
||||
timings.append((spec.label, round(elapsed, 1)))
|
||||
# Sweep between passes so a pass-1 escapee cannot touch pass 2.
|
||||
kill_processes_referencing(str(temp_root))
|
||||
if reap_error:
|
||||
|
|
@ -1471,7 +1473,7 @@ def run_hermetic_pytest(
|
|||
and preflight_test_workload_unchanged(subject, worktree, timeout=timeout, pytest_args=pytest_args)
|
||||
and (not subject.workload[4] or (node_result or {}).get("returncode") == 0)):
|
||||
ctx._preflight_test_proof = subject
|
||||
log_preflight_test_proof(ctx, subject, reused=False, phase=phase)
|
||||
log_preflight_test_proof(ctx, subject, reused=False, phase=phase, passes=timings)
|
||||
return None
|
||||
except subprocess.TimeoutExpired:
|
||||
return f"⚠️ PRE_PUSH_TEST_ERROR: pytest timed out after {timeout} seconds"
|
||||
|
|
|
|||
|
|
@ -205,6 +205,26 @@ def test_proof_logging_failure_does_not_change_runner_authority(candidate, monke
|
|||
assert all(row["task_id"] == "" and row["head"] == proof.head for row in events)
|
||||
|
||||
|
||||
def test_green_proof_row_discloses_the_passes_it_ran_and_its_budget(candidate, monkeypatch):
|
||||
"""A GREEN gate renders no pytest output at all — stdout is piped and printed
|
||||
only on timeout, containment loss or failure — so this disclosure row is the
|
||||
only durable record of what a healthy run cost against the budget that would
|
||||
have killed it. A reused proof executed nothing, so it reports no passes while
|
||||
still naming the budget it was taken under."""
|
||||
monkeypatch.setenv("OUROBOROS_PREFLIGHT_TIMEOUT_SEC", "1234")
|
||||
monkeypatch.setattr(pr, "_execute_pytest_pass", lambda *a: (0, "green fixture", ""))
|
||||
monkeypatch.setattr(pr, "_observed_worker_ids", lambda *a: {"gw0", "gw1"})
|
||||
assert pr.run_hermetic_pytest(candidate.repo_dir, ctx=candidate) is None
|
||||
assert pr.run_hermetic_pytest(candidate.repo_dir, ctx=candidate) is None
|
||||
created, reused = _proof_events(candidate.drive_root)
|
||||
assert (created["action"], reused["action"]) == ("created", "reused")
|
||||
assert list(created["pass_seconds"]) == [spec.label for spec in pr._preflight_pass_specs()]
|
||||
assert all(isinstance(seconds, float) and seconds >= 0.0
|
||||
for seconds in created["pass_seconds"].values())
|
||||
assert created["budget_sec"] == reused["budget_sec"] == 1234
|
||||
assert reused["pass_seconds"] == {}
|
||||
|
||||
|
||||
def test_interpreter_identity_preserves_real_venv_invocation_path(candidate, tmp_path, monkeypatch):
|
||||
from ouroboros.commit_admission import _executable_identity
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue