4.2 KiB
4.2 KiB
T4.1 — Verifier port
| Field | Value |
|---|---|
| Phase | P4 — Verification |
| Size | S — under 1 day |
| Status | Not started |
| Flags | — |
| Spec | inlined below |
| Blocks | T4.2 |
Goal
The verification port with a fail-closed contract enforced by the framework, not by the verifier author.
Facts (inlined — no spec read needed)
#[async_trait]
pub trait Verifier: Send + Sync {
fn id(&self) -> VerifierId;
/// Any error, panic or timeout MUST resolve to `Fail`. A verifier that
/// throws or hangs can never report `Pass`.
async fn verify(&self, cx: &VerifierCtx) -> VerifierOutcome;
}
- Verification returns ground truth. Grading attributes cause and ranks. Collapse them and the system grades its own homework.
- The framework cannot break the agent it runs. Observation, verification and grading failures degrade the record, never the work. Any code path where a verifier or grader can fail into an agent's execution is a defect.
- Fail-closed means the framework converts panic, error and timeout into
Fail. A contract documented in a doc comment and implemented by each author is not a contract. - Never let a rubric judge what a verifier can check. Every criterion that can be
made mechanical should be a
Verifier— deterministic, cheap, and not subject to judge drift.
Steps
- Define
VerifierId,VerifierOutcome(pass/fail plus detail) and the trait above. - Write the framework-side runner that wraps every
verifycall:tokio::time::timeoutatcx.deadline→FailFutureExt::catch_unwind(or a spawn-and-join boundary) for panics →Fail- any
Err→Fail
- Make the runner the only call path.
Verifier::verifyis never invoked directly from the executor. - Record which failure mode produced a
Fail— timeout, panic and error are the same outcome but different operational signals. - Run verifiers for one attempt as a fan-out/join; they are independent checks.
- Fault-inject all three modes in tests with real verifiers that panic, hang and error.
Acceptance
- Verifiers that panic, hang past deadline, and return an error all resolve to
Fail— fault-injected, not asserted by comment.
Verify
Harness: three real misbehaving verifiers — one that panics, one that loops
past the deadline, one that returns Err. Fault-injected, not mocked.
Integration test — tests/it_verifier_fail_closed.rs:
- Run a real run with each bad verifier in turn.
- Assert each resolves to
Fail. - Assert the run itself completes and reaches a terminal state — the framework must not break the agent it runs. A panicking verifier taking the run down is the primary failure being tested for.
- Assert the recorded failure mode distinguishes timeout / panic / error, since they are the same outcome but different operational signals.
- Assert a verifier that panics does not poison shared state: run a second,
healthy verifier afterwards in the same process and assert it returns
Pass. - Fan-out: three verifiers on one attempt, one of them panicking; assert the other two still return their own results.
- Assert no code path can produce
Passfrom anErr— atrybuildcase ifverifyis only reachable through the framework runner.
Command: cargo test -p verify fail_closed
False pass:
- Testing the runner in isolation with a mock verifier rather than in a real run. Step 3 — the run surviving — only means something end to end.
- Catching the panic at the test boundary with
catch_unwindin the test, which makes a framework that does not catch it look fine. - Step 5 omitted: a panic that leaves a mutex poisoned passes steps 1–4 and breaks every subsequent verification in the process.
Traps
- Letting a panic unwind into the executor task. It takes the run with it, which is the exact inversion the first principle forbids.
- A
Result<VerifierOutcome, E>signature that pushes the decision onto callers. One caller will mapErrtoPassby accident.
Background (not required to do this task): rust-agentic-sys.md §1, §10, §11.7 · rust-agentic-task.md