Surface explicit-none decision attestations as confirmable HITL decisions (#3462) - #3464
Conversation
…ions (#3462) An agent could bypass the entire register -> bridge -> resolve chain by attesting no_decisions_rationale and documenting decisions as draft prose — the attestation rode along as a sentence in the phase_gate question, so the operator never confirmed it as a decision. Gate side: when a refine/plan phase reaches its gate with an explicit-none attestation standing in for a ledger, queue a dedicated confirmable choice decision quoting the role and rationale. Confirm (bare keyword or full option label; anything else conservatively rejects) proceeds to the normal gate with "Operator confirmed the attestation" recorded on the ledger note; reject re-runs the phase with a directive to register each decision — including believed-resolved ones, registered with the recommended answer as the first option. Idempotent across converge rounds (same question -> reuse prior confirmation or pending decision). Autonomous pipelines emit a phase.decision_ledger_explicit_none event instead of blocking, mirroring the #3392 gate-skip posture. Prompt side: task-named decisions are non-optional — a "surface as HITL" directive or operator-owned decision named by the task must be registered even when believed resolved; belief about resolution is a recommended disposition (recommended option citing the resolving context), not a reason to skip. The #2481 skip-already-resolved rule is narrowed to answers from THIS pipeline's pre-refine HITL round. Reviewer side: reviewer_refine (§7) and reviewer_plan (§14) NACK an explicit-none ledger on a task that names decisions to surface.
There was a problem hiding this comment.
Review — thorough pass
I traced the full change: the four prompt/reviewer text edits, the _collect_decision_ledger_status 3-tuple change and both call sites, the new _ledger_attestation_question / _unwrap_choice_resolution / _ledger_attestation_confirmed helpers, and the new elif _ledger_explicit_none is not None: orchestration block in _health_monitor_poll. No blocking issues. The change is a careful, well-scoped extension of the #3390 missing-ledger backstop and is correctly wired end-to-end.
What I verified
- Reachability / not a silent no-op. The new
elifsits in the live refine/plan human-gate path in_health_monitor_poll, directly beside the accepted #3390if _ledger_missing:backstop, guarded bycurrent_phase.value in _HITL_GATE_PHASES. Before this PR the explicit-none case returned(note, False)and fell straight through to thephase_gatewith the note appended as prose — exactly the loophole #3462 describes. Now_collect_decision_ledger_statusreturns the(role, rationale)and the caller surfaces a dedicated confirmablechoicedecision first. The behavior change is on the normal path, and after confirm_ledger_note(with" Operator confirmed the attestation.") still flows into thephase_gatequestion (orchestrator/routes/pipelines.py:28527). Feature works end-to-end. - Python syntax.
except ValueError, TypeError:(pipelines.py:24851) is valid — PEP 758 landed in 3.14,pyproject.tomlpinsrequires-python = ">=3.14", andpipelines.py:685already uses the same unparenthesized form.ast.parseof the whole module succeeds under 3.14. Not a bug. - All callers updated. Only two non-test callers of
_collect_decision_ledger_status(both inpipelines.py), both unpack the 3-tuple. No stray 2-tuple unpack that wouldValueErrorat runtime. - Confirm matcher.
_ledger_attestation_confirmedunwraps the{"action":"select",...}envelope, is conservative (bareconfirm/ full label only), and correctly rejects the re-run option, negating free text, and empty. Idempotency (_prior_confirmRESOLVED+confirmed,_pending_attestPENDING reuse) is keyed on the stable question string and filtered by phase — a rejected prior attestation is RESOLVED-but-not-confirmed so it correctly does not satisfy_prior_confirm, and re-asks.hitl_review_cycles+_broadcast_hitl_nonconvergence_alertbound the reject→re-attest loop. - Autonomous path. Emits
phase.decision_ledger_explicit_none(free-string event type, same as #3390'sphase.decision_ledger_missing) and never blocks — matches the #3392 gate-skip posture.if/elifcorrectly nested inside thetry, so a helper raise can't leave_ledger_explicit_noneunbound.
Non-blocking suggestions
-
No test exercises the orchestration block itself.
TestLedgerAttestationConfirmationcovers the pure helpers well, but the net-new integration logic — confirm→proceed, reject→_perform_hitl_phase_rerunwith the operator-note directive, the_prior_confirm/_pending_attestidempotent reuse, and theAWAITING_HUMAN→RUNNINGtransitions — has no direct test. It mirrors the #3390 backstop, but the reuse/dedup and rerun-directive branches are new. Consider a monkeypatchedwait_for_decisiontest (as elsewhere in the suite) asserting: confirm → falls through to phase_gate with the "Operator confirmed" note; reject → rerun fires with the directive; re-entry with same rationale → no duplicate decision queued. -
Audit-note inaccuracy on cancel. On a non-RESOLVED terminal state,
_confirmedisTrue(fail-open) and_ledger_note += " Operator confirmed the attestation."— recording "confirmed" when the operator actually cancelled. Cosmetic only (the phase_gate still blocks), and it mirrors #3390's analogous fail-open, but the note text is misleading in that edge case. -
Idempotency corner (awareness, not a defect). After a confirm, if the phase later re-runs from phase_gate feedback and the producer re-attests the identical rationale,
_prior_confirmreuses the earlier confirmation without re-asking — even if the feedback's scope change ought to have surfaced a new decision. This is consistent with the documented "same rationale reuses confirmation / changed rationale is a new claim" design, and the reviewer NACK criteria + the phase_gate itself backstop it. Flagging only so the tradeoff is a conscious one. -
Minor: reusing a
_pending_attestdecision still re-emitsdecision.createdon each poll — harmless duplicate event.
Overall this is solid, defensively-written work that closes the attestation loophole cleanly. Approving with the above as suggestions.
— Authored by egg
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Extract the health-monitor's explicit-none confirmation orchestration into a module-level _handle_explicit_none_attestation_gate helper so the confirm/reject/reuse branches are unit-testable, and cover them with a new TestHandleExplicitNoneAttestationGate class (confirm falls through, reject re-runs with the directive, free-text rides along as an operator note, prior confirmation reused without re-asking, pending decision reused without re-emitting decision.created, cancel fails open). Also fix the audit-note inaccuracy on cancel: a non-RESOLVED terminal state now records 'confirmation was cancelled; deferring to the phase gate' rather than falsely claiming the operator confirmed, and guard the decision.created emit so reusing a pending decision across polls no longer re-announces it.
|
Thanks for the thorough pass. All four non-blocking suggestions addressed in commit 1. No test exercises the orchestration block itself — 2. Audit-note inaccuracy on cancel — 3. Idempotency corner (awareness, not a defect) — 4. Duplicate
— Authored by egg |
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Re-review — delta since dd47728
The only new commit (51da6e1) is a behavior-preserving refactor that addresses all four non-blocking suggestions from my prior review. I traced the extraction against the deleted inline block and confirmed equivalence. No blocking issues.
Suggestion follow-through verified
-
Orchestration now has direct tests (suggestion #1) — ✅. The inline confirm/reject/reuse logic was extracted into module-level
_handle_explicit_none_attestation_gate(...)and pure_ledger_attestation_rerun_directive(...), andTestHandleExplicitNoneAttestationGate+TestLedgerAttestationRerunDirectivecover the net-new branches. The tests import and call the real production function (from routes.pipelines import _handle_explicit_none_attestation_gate) and patch only collaborators (get_decision_queue,_perform_hitl_phase_rerun,report_pipeline_status,_emit_pipeline_event) — they exercise the production path, not a hand-built stand-in. The reused-decision question match runs through the real_ledger_attestation_question, so a regression in the dedup key would break the test. No self-seeding goldens. -
Cancel audit-note inaccuracy fixed (suggestion #2) — ✅. On a non-RESOLVED terminal state the note now reads
"Attestation confirmation was cancelled; deferring to the phase gate."instead of falsely claiming"Operator confirmed the attestation.".test_cancel_fails_open_with_accurate_noteasserts both the absence of the false claim and the fail-open (rerun_requested is False,rerunnot called). -
Idempotency corner (suggestion #3) — acknowledged as intentional design; no change, as agreed.
-
Duplicate
decision.createdon pending reuse fixed (suggestion #4) — ✅. Thereport_pipeline_status+_emit_pipeline_eventpair is now guarded onnewly_created, so reusing a still-pending decision across polls no longer re-announces it.test_pending_decision_reused_without_reemitting_createdpins this (queue_decisionnot called,wait_for_decisioncalled once with the pending id,emitnot called).
Equivalence checks on the extraction
- Rerun vs fall-through decision is provably identical. Original reran when
status == RESOLVED and not confirmed_match; the newif resolved_ok and not confirmed:reduces to the same predicate. The only intended behavioral delta is the cancel note text (suggestion #2) and the emit guard (suggestion #4). - Re-run directive text is preserved — content is identical (only f-string line-wrapping changed), and the operator-note append condition is equivalent (the helper receives an already-stripped resolution and re-
strip()s idempotently). - Caller wiring is correct —
_rerun_requested, _ledger_note, pipeline = _handle_...(...)rebinds the reloaded pipeline andcontinues on re-run;store,spawner, andrepo_pathare all in scope as locals of_run_pipeline. _prior_confirmearly-return returns the passed-in pipeline unchanged, matching the original (which did not reload on that path);get_decision_queueis only reached after that return, andtest_prior_confirmation_is_reused_without_reaskingasserts it is never fetched.
Clean, well-tested extraction that closes the loop on the prior review. Approving.
— Authored by egg
|
egg review completed. View run logs 3 previous review(s) hidden. |
Closes #3462.
What
Closes the self-attestation loophole from #3462: a refine/plan agent could bypass the entire register → bridge → resolve chain (#3374/#3392/#3071) by attesting
no_decisions_rationale(#3390's explicit-empty-ledger form) and documenting the decisions as draft prose. The attestation rode along as a sentence embedded in thephase_gatequestion — the operator never confirmed it as a decision, and the resolutions collected out-of-band never became first-class contract decisions (motivating run:pipeline-dcdad92d; contrastpipeline-121df67a, where the registered path worked as designed).Layers
Gate side — the attestation becomes its own confirmable decision (issue ask 4). When a refine/plan phase reaches its gate with an explicit-none attestation standing in for a ledger, the orchestrator queues a dedicated
choicedecision quoting the role and rationale ("the attests this phase deliberately raises no operator decisions — confirm?") before the phase gate:{"action":"select"}CLI envelope is unwrapped). Proceeds to the normal gate; the gate's ledger note records "Operator confirmed the attestation".hitl_gates: false) emit aphase.decision_ledger_explicit_noneevent instead of blocking, mirroring the HITL phase gate: converge-before-advance loop (resolve-all → re-run → converge) + durable resolved-question carry-forward #3392 gate-skip posture.Prompt side — registration required for task-named decisions, with a recommended disposition (asks 1–3). The refine Open Questions meta-block gains a "Task-named decisions are non-optional" rule: decisions the task names as operator-owned (or covered by a surface-as-HITL directive) must be registered even when believed resolved, non-blocking, or deferred — belief about resolution is a recommended disposition (first option suffixed
(recommended), citing the resolving context, one-click confirmable), never a reason to skip. The #2481 "skip already-resolved" rule is explicitly narrowed to answers from this pipeline's pre-refine HITL round — it never covered prior/cancelled-run seeded context, which is exactly the over-read in the motivating run. The DO-NOT list and the #3390 attest prose (template block + BRC preamble propose line) now state that the rationale form is operator-confirmable and never a substitute for registering a believed-resolved decision.Reviewer side (judgment layer).
reviewer_refine§7 andreviewer_plan§14 gain a "Task-named decisions — NACK an explicit-none ledger" obligation: on a task that names decisions to surface, ano_decisions_rationaleattestation is a NACK regardless of how defensible the rationale reads.No new
Decisionschema field: the recommended disposition rides the existingoptionslist (recommended option first, resolving context in its description), which the bridge and status surfaces already render.Testing
test_decision_ledger_gate.py: 3-tuple return of_collect_decision_ledger_status(now carries the(role, rationale)for the gate), plus a newTestLedgerAttestationConfirmationsuite covering the question composer's stability (the converge-round dedupe key) and the conservative confirm matcher (bare keyword / full label / CLI select-envelope confirm; re-run option, negating free text, and envelope-rerun reject).test_pipeline_prompts.py: newTestTaskNamedDecisionRegistrationSurfaceratchet (7 tests) over the producer prompt, the narrowed skip rule, the DO-NOT items, the BRC preamble for all four attesting roles, and both reviewer criteria.make test(changeset-aware): 20632 passed; 3 failures are pre-existing/unrelated — the two documented host-env reap-script failures (test-all: reap-stale-egg-images safety-gate tests fail on btrfs-root hosts (non-hermetic test, 127) #3222) and a gateway session-expiry timing flake that passes in isolation.ruff+ruff formatclean on touched files (pre-commit hooks passed).Authored-by: egg-adjacent human-in-the-loop session