Skip to content

feat(verification): typed verification_spec with roles, modes, and score rollup - #2

Closed
geojaz wants to merge 3 commits into
mainfrom
feat/verification-spec-schema
Closed

feat(verification): typed verification_spec with roles, modes, and score rollup#2
geojaz wants to merge 3 commits into
mainfrom
feat/verification-spec-schema

Conversation

@geojaz

@geojaz geojaz commented Jul 16, 2026

Copy link
Copy Markdown
Member

feat: verification_spec schema with roles, modes, and rollup

What this renovates and what is preserved unchanged

Renovates: Task.verification_spec moves from Any to list[VerificationEntry] | None.
Each entry carries a name, role (correctness, safety, catastrophic), and a raw spec
dict. BaseVerifier gains weight, mode, and hold-window fields. VerificationResult gains
weight. run_entry() returns a typed EvaluatedEntry that bundles role, name, and result
so there is no second parallel sequence to misalign; rollup() accepts list[EvaluatedEntry]
and produces (c, rec_v, cat_v) scores. Two new verifiers land: ResourcePropertyVerifier
and HttpProbeVerifier.

Preserved unchanged:

  • BaseVerifier.verify() signature is untouched.
  • pod_healthy.py and scaling_complete.py are untouched.
  • optimize-scale/task.yaml parses without any authoring change (regression test included).
  • The legacy raw-dict path in _build_verification_mapping is still the fallback for tests
    and any task that predates this schema.
  • No existing verifier behavior changed.

The mode insight

Three of the four modes are scheduling strategies layered over the existing verify() contract,
not changes to it:

  • converge: poll verify(N) until success or timeout (existing behavior, now named).
  • assert: call verify(0) exactly once; poll_until evaluates the predicate before checking
    the deadline so timeout_sec=0 means one shot, no sleep.
  • hold: sample verify(0) repeatedly across a window; any failing sample fails the check.

unchanged is designed but intentionally not built. It needs a baseline snapshot and a
diff strategy that depends on verifier type. A ValidationError is raised at task-load
time when any spec contains mode: unchanged (the VerificationEntry validator walks
the raw spec dict recursively); the NotImplementedError in _run_leaf is kept as
defense-in-depth.

What is real in this PR vs. proposed next

Real (in this PR):

  • VerificationEntry model with Role literal
  • Task.verification_spec: list[VerificationEntry] | None
  • BaseVerifier.mode, weight, hold_window_sec, hold_interval_sec
  • VerificationResult.weight
  • Mode dispatch in VerificationRunner.run_entry(): converge, assert, hold
  • run_entry() returns EvaluatedEntry (role + name + result in one object)
  • rollup(list[EvaluatedEntry]) with RollupScores; ordering-safe by construction
  • ResourcePropertyVerifier: kubectl get -o json, dotted path eval, quantifiers
  • HttpProbeVerifier: kubectl run --rm curl pod, status + body matching
  • Full unit test coverage for all of the above

Proposed next (not in this PR):

  • Wire rollup() output into the judge/metrics layer.
  • run_entry() called from the harness (today the harness still uses wait_for_condition).
  • unchanged mode implementation.
  • Additional probe verifiers (see vocabulary roadmap below).

Vocabulary roadmap

Probes that would be cheap to add under the same BaseVerifier contract:

  • dns_probe: resolve a hostname from inside the cluster
  • tcp_probe: TCP connect to host:port
  • env_probe: check env var value inside a running container (kubectl exec)
  • file_probe: check file contents inside a container
  • log_probe: grep recent pod logs for a pattern
  • can_i: kubectl auth can-i for RBAC verification
  • cert_probe: TLS cert expiry / SAN check
  • unchanged_outside: a scoped unchanged mode targeting resources outside a namespace
  • cloud_resource_property: same as resource_property but for GCP/AWS/Azure resources
    via their respective APIs
  • policy_compliance: OPA/Gatekeeper policy evaluation result
  • forbidden_action: assert a named action was NOT taken (audit log or webhook check)

exec_probe (arbitrary shell inside a container) is a last resort. It works but it couples
verification logic to image contents and makes tasks harder to port.

geojaz added 2 commits July 16, 2026 08:32
…h, rollup, and two new verifiers

- VerificationEntry(name, role, spec) with Role = correctness | safety | catastrophic
- Task.verification_spec typed as list[VerificationEntry] | None; legacy raw-dict path preserved
- BaseVerifier gains mode, weight, hold_window_sec, hold_interval_sec; VerificationResult gains weight
- VerificationRunner.run_entry() dispatches converge/assert/hold; unchanged raises NotImplementedError
- rollup() produces RollupScores(c, rec_v, cat_v) from paired entries + results
- ResourcePropertyVerifier: kubectl get -o json, dotted path eval, eq/ne/gt/gte/lt/lte/exists/absent/contains/matches, all/any/none quantifiers
- HttpProbeVerifier: kubectl run --rm curl pod, HTTP status + body matching
- optimize-scale task.yaml regression test: parses as list[VerificationEntry] without authoring change
…anch rename

EvaluatedEntry replaces the parallel (entries, results) contract in rollup():
run_entry() now returns an EvaluatedEntry that bundles role+name+result, and
rollup() accepts a single list[EvaluatedEntry] with no second sequence to
misalign. Added a regression test that would have caught the original bug.

VerificationEntry now rejects mode='unchanged' with a ValidationError at
task-load time rather than raising NotImplementedError mid-eval. The validator
walks the raw spec dict recursively so nested occurrences (e.g. inside a
parallel) are caught too. The _run_leaf guard is kept as defense-in-depth.

Branch renamed from worktree-agent-ad828774e2fb42332 to
feat/verification-spec-schema.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request refactors the verification harness by introducing a typed schema with VerificationEntry, execution modes (converge, assert, hold), and a scoring rollup mechanism. It also adds two new verifiers: HttpProbeVerifier and ResourcePropertyVerifier. The review feedback highlights several critical issues: HttpProbeVerifier needs the -i flag in kubectl run to capture stdout; the rollup scoring requires a guard against division by zero; the hold-mode logic in VerifierAgent needs to correctly handle 0.0 values for windows and intervals; and the regex matching in ResourcePropertyVerifier needs to handle None values safely to avoid false positives.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread devops_bench/verification/verifiers/http_probe.py Outdated
Comment thread devops_bench/verification/rollup.py Outdated
Comment thread devops_bench/verification/runner.py Outdated
Comment thread devops_bench/verification/runner.py Outdated
Comment thread devops_bench/verification/verifiers/resource_property.py
…llup and hold-mode zero guards, matches None)

- Fix 1 (CRITICAL): add k8s.run_pod() with -i flag and route http_probe
  through it; the previous hand-rolled kubectl run lacked -i so kubectl
  exited without attaching/capturing stdout on real clusters. run_pod is
  exported from devops_bench.k8s and follows the same _run_kubectl /
  kubeconfig-resolution pattern as the other wrappers.

- Fix 2 (HIGH): guard both denominators in rollup.py against zero.
  Safety entries whose leaves all carry weight=0 now yield rec_v=None
  (consistent with no-safety-entries behavior) instead of ZeroDivisionError.
  Correctness leaves with total weight=0 raise a clear ValueError naming
  the problem.

- Fix 3+4 (MEDIUM): replace truthy-or fallback with is None checks for
  hold_window_sec and hold_interval_sec so an explicit 0.0 is honored.
  Separate the loop-termination decision (time remaining in window) from
  the sleep call so interval=0.0 keeps sampling until the window elapses
  instead of breaking after one sample.

- Fix 5 (MEDIUM): guard matches operator in resource_property.py against
  None values; str(None)=="None" previously caused false positives (e.g.
  pattern "on" matching a null field). None value now always returns False.
@geojaz

geojaz commented Jul 16, 2026

Copy link
Copy Markdown
Member Author

Superseded by the split into two smaller, reviewable PRs: #3 (schema foundation, behavior-neutral) and #4 (verifier vocabulary + rollup, stacked on the foundation). Same code, reviewed in two chunks. Closing this combined PR in favor of those.

@geojaz geojaz closed this Jul 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant