Skip to content

feat(verification): type verification_spec with roles (schema foundation) - #3

Open
geojaz wants to merge 5 commits into
mainfrom
feat/verification-spec-foundation
Open

feat(verification): type verification_spec with roles (schema foundation)#3
geojaz wants to merge 5 commits into
mainfrom
feat/verification-spec-foundation

Conversation

@geojaz

@geojaz geojaz commented Jul 16, 2026

Copy link
Copy Markdown
Member

Type verification_spec with roles (schema foundation)

Where this comes from

We've been working through how to implement the v1.0 scoring framework (the cat_v * c^0.5 * rec_v^0.5 outcome score). Computing those three signals means a task has to declare which checks are objectives (feed correctness c) and which are safeguards (feed rec_v/cat_v depending on severity). Today verification_spec is untyped (Any), so there is nowhere to put that distinction. This PR is the foundational piece: give the field a real schema with a role and severity, so the scoring layer has something to roll up. It is intentionally split from the machinery that consumes it so this part can land as a small, behavior-neutral change.

Vocabulary follows the task/verifier schema doc: roles are objective | safeguard, and a safeguard carries severity: recoverable | catastrophic.

Part 1 of 2.

What this does

  • Adds VerificationEntry (name, role, severity, spec) and types Task.verification_spec as list[VerificationEntry] | None.
  • role is one of objective | safeguard and defaults to objective, so every existing spec stays valid unchanged.
  • severity (recoverable | catastrophic) is required when role: safeguard and forbidden when role: objective, enforced by a model validator.
  • Declares optional mode and weight fields on BaseVerifier (and weight on VerificationResult). Additive metadata; nothing consumes them for scoring in this PR.
  • The runner (VerifierAgent._run_leaf) is authoritative for a leaf's weight: it stamps the verifier's configured weight onto the result after verify() returns, so a custom verifier that builds a VerificationResult directly (bypassing _poll_to_result) can never silently drop it.
  • Adapts the harness parse path to accept typed entries, preserving the existing verification_parse_errors behavior.

What this does NOT do

No new verifiers, no scoring logic, no runtime behavior change. tasks/common/optimize-scale parses and runs exactly as before (regression test included). The verifier vocabulary and the c/rec_v/cat_v rollup that consume role/severity/mode/weight land in Part 2.

@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 introduces a typed schema for verification specifications by defining the VerificationEntry model and execution Mode literals, updating the Task schema to parse verification_spec as a list of VerificationEntry objects, and adding support for weighting verification results. The review feedback highlights several robust improvements: adding defensive checks when dumping verification_spec entries to avoid crashes with raw dictionaries, wrapping placeholder resolution in a try-except block to handle substitution failures gracefully, logging warnings for duplicate verification entry names to prevent silent overwrites, and ensuring that custom leaf verifiers properly propagate their weight to VerificationResult instances.

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 on lines +969 to +973
"verification_spec": (
[e.model_dump() for e in task.verification_spec]
if task.verification_spec is not None
else None
),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

If task.verification_spec contains raw dictionaries (e.g., if the task was loaded bypassing Task.from_dict or using a mock/stub in tests), calling e.model_dump() will raise an AttributeError and crash the harness. Use a defensive check to only call model_dump() if the object has that attribute.

Suggested change
"verification_spec": (
[e.model_dump() for e in task.verification_spec]
if task.verification_spec is not None
else None
),
"verification_spec": (
[
e.model_dump() if hasattr(e, "model_dump") else e
for e in task.verification_spec
]
if task.verification_spec is not None
else None
),

Comment on lines +479 to +491
for entry in raw:
spec_resolved = self._resolve_spec_placeholders(
entry.spec, cluster_name, target_deployment, namespace
)
try:
mapping[entry.name] = VerificationSpec(spec_resolved)
except Exception as exc: # noqa: BLE001 - surface every failure
_log.warning(
"verification entry %r failed to validate; skipping: %s",
entry.name,
exc,
)
errors.append({"name": entry.name, "reason": str(exc)})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Wrap the placeholder resolution (_resolve_spec_placeholders) inside the try-except block as well. If placeholder substitution fails or raises an unexpected exception, it will currently crash the entire harness run instead of being gracefully caught and logged as a verification parse error.

Suggested change
for entry in raw:
spec_resolved = self._resolve_spec_placeholders(
entry.spec, cluster_name, target_deployment, namespace
)
try:
mapping[entry.name] = VerificationSpec(spec_resolved)
except Exception as exc: # noqa: BLE001 - surface every failure
_log.warning(
"verification entry %r failed to validate; skipping: %s",
entry.name,
exc,
)
errors.append({"name": entry.name, "reason": str(exc)})
for entry in raw:
try:
spec_resolved = self._resolve_spec_placeholders(
entry.spec, cluster_name, target_deployment, namespace
)
mapping[entry.name] = VerificationSpec(spec_resolved)
except Exception as exc: # noqa: BLE001 - surface every failure
_log.warning(
"verification entry %r failed to validate; skipping: %s",
entry.name,
exc,
)
errors.append({"name": entry.name, "reason": str(exc)})

entry.spec, cluster_name, target_deployment, namespace
)
try:
mapping[entry.name] = VerificationSpec(spec_resolved)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

If multiple verification entries share the same name, the subsequent entries will silently overwrite the previous ones in mapping. This is likely an authoring error (e.g., copy-paste mistake) and can lead to verification checks being silently skipped. Consider logging a warning when a duplicate name is detected.

                    if entry.name in mapping:
                        _log.warning(
                            "Duplicate verification entry name %r detected; overwriting previous entry.",
                            entry.name,
                        )
                    mapping[entry.name] = VerificationSpec(spec_resolved)


name: str | None = None
kubeconfig: str | None = None
weight: float = 1.0

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Since VerificationResult defaults weight to 1.0, any custom leaf verifier that overrides verify and instantiates VerificationResult directly (instead of using _poll_to_result) must explicitly forward self.weight to the result (i.e., VerificationResult(..., weight=self.weight)). Otherwise, the configured weight on the verifier will be lost in the result tree. Consider documenting this requirement or ensuring that the runner/agent automatically overrides the result's weight with the verifier's weight during evaluation.

richackard and others added 3 commits July 16, 2026 10:53
Introduce the models package foundation: the LLMClient interface, the
MODELS registry with its get_model factory (provider resolution via
core.model_providers), and run_tool_loop, the shared agentic tool-call
loop that drives an LLMClient against a set of tools.

The loop tests are async, so pytest-asyncio joins the dev dependency
group.

Signed-off-by: Richard Huang <richackard@gmail.com>
GitOrigin-RevId: a78c8b3
Waves 4 and 5 collapse from 25 PRs to 10. Each batch is authored by
someone new to the area, with its main past contributors reviewing.
…ion)

Give verification_spec a real schema so a task can declare which checks
feed the correctness, safety, and catastrophic scoring signals. Today the
field is untyped (Any), leaving nowhere to express that distinction.

- Add VerificationEntry (name, role, spec) and type Task.verification_spec
  as list[VerificationEntry] | None. role defaults to correctness, so
  existing specs stay valid unchanged.
- Declare optional mode and weight on BaseVerifier (and weight on
  VerificationResult); additive metadata, not yet consumed.
- Adapt the harness parse path to typed entries, preserving
  verification_parse_errors, with defensive handling for raw dicts,
  placeholder-resolution failures, and duplicate entry names.

Behavior-neutral: no new verifiers and no scoring logic. optimize-scale
parses and runs unchanged.
jegaths added 2 commits July 23, 2026 16:18
Align the verification_spec role vocabulary with the task/verifier schema
doc: roles are objective | safeguard, and a safeguard carries an explicit
severity (recoverable | catastrophic) that decides how a violation scores.

- Role: correctness|safety|catastrophic -> objective|safeguard. role still
  defaults to objective, so existing specs stay valid unchanged.
- Add Severity (recoverable|catastrophic) and a severity field on
  VerificationEntry, enforced by a validator: required when role='safeguard',
  forbidden when role='objective'.
- Update the BaseVerifier.mode role->mode mapping docstring accordingly.
- Update entry-schema and task-schema tests for the new vocabulary and add
  severity coverage.

Behavior-neutral: still schema only, no new verifiers and no scoring logic
(Part 1 of 2).
Address review: a custom verifier that overrides verify() and builds a
VerificationResult directly (bypassing _poll_to_result) would silently drop
its configured weight, leaving the default 1.0 and corrupting the rollup.

Make the runner the single source of truth: _run_leaf re-stamps the leaf's
configured weight onto the result after verify() returns, so no verify()
implementation has to remember to forward it. Nodes without a weight
attribute (e.g. compounds) are left untouched.

- runner._run_leaf: apply node.weight to the result post-verify.
- base.py: update the VerificationResult.weight docstring (runner is now
  authoritative; direct-build verifiers need not forward weight).
- tests: assert the runner stamps weight even when verify() omits it, and
  preserves the default 1.0 when unset.
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.

4 participants