Skip to content

feat(odr): in-package ODR verification engine (odr_verify) — ODR-3 server core#8389

Open
an0mium wants to merge 4 commits into
mainfrom
claude/odr3-verify-core
Open

feat(odr): in-package ODR verification engine (odr_verify) — ODR-3 server core#8389
an0mium wants to merge 4 commits into
mainfrom
claude/odr3-verify-core

Conversation

@an0mium

@an0mium an0mium commented Jun 14, 2026

Copy link
Copy Markdown
Collaborator

What

The server-side single implementation that the planned POST /api/receipts/verify endpoint (and any internal caller) will wrap — completing the ODR-3 verifier (#8226) on the server side. The standalone, zero-Aragora-dependency mirror is the aragora-verify PyPI package (#8388); both follow the same content profile (OPEN_DECISION_RECEIPT.md) and signature construction (§6 / #8225), so a receipt verifies identically here and for an external auditor with only the public key.

aragora/gauntlet/odr_verify.py:

  • reuses the emitter's odr_export.jcs_canonicalize / odr_content_digest — so verification is against the exact bytes a receipt was emitted and signed over (not a re-derivation);
  • schema conformance to the ODR v0.1 profile — dependency-free structural validator (jsonschema is not a core dep);
  • Ed25519 detached signatures per spec §6 / ODR-2: Ed25519 public-key signing for DecisionReceipts (offline third-party verifiability) #8225 (signed message = the SHA-256 digest), against a supplied public key, with cryptography imported lazily and the check gracefully skipped — never failed — when it's absent;
  • quorum consistency (spec §8 malformed/tamper signal);
  • hash-chain anchoring + linkage when a chain is supplied;
  • absent markers / "undisclosed" surfaced as non-failing weakening signals.
from aragora.gauntlet.odr_verify import verify_odr_document, load_public_key
result = verify_odr_document(doc, public_key=load_public_key(pem_bytes))
result.to_dict()  # the JSON shape a /verify endpoint returns

Why split this from the endpoint

This is the reusable engine; wiring the HTTP route touches OpenAPI/SDK-parity generation (a separate, gated surface in this repo) and is best as its own focused PR. Landing the engine first keeps that PR small and lets internal callers and a CLI verify path use it immediately.

Validation

  • 15 tests (tests/gauntlet/test_odr_verify.py): schema pass/fail, signature verify/tamper/wrong-key/no-key/raw-key, quorum consistency, chain anchored/broken/unanchored, weakening warnings, digest-matches-emitter, garbage-key rejection. All pass.
  • ruff check + ruff format --check clean under the repo config (no BLE001/T201 — narrow excepts, library-only).

Tier / scope

Tier 2 (additive module + tests; no handler/OpenAPI/protected-file changes). Follow-ups: the POST /api/receipts/verify HTTP endpoint wrapping this engine, and an aragora receipt verify --format odr CLI path.

Part of #8223; advances #8226.


Generated by Claude Code

…rver core

The server-side single implementation that the planned `POST
/api/receipts/verify` endpoint (and any internal caller) wraps, completing
the ODR-3 verifier (#8226) on the server side. The standalone, zero-Aragora
mirror is the `aragora-verify` PyPI package (PR #8388); both follow the same
content profile and signature construction, so a receipt verifies identically
here and for an external auditor.

`aragora/gauntlet/odr_verify.py`:
- reuses the emitter's `odr_export.jcs_canonicalize` / `odr_content_digest`
  (verifies against the exact bytes a receipt was emitted and signed over);
- structural conformance to the ODR v0.1 profile (dependency-free, since
  jsonschema is not a core dependency);
- Ed25519 detached-signature verification per OPEN_DECISION_RECEIPT.md §6 /
  #8225, against a supplied public key, with `cryptography` imported lazily
  and the check gracefully *skipped* (never failed) when it is absent;
- quorum participant consistency (spec §8 malformed/tamper signal);
- hash-chain anchoring + linkage when a chain is supplied;
- absent markers / "undisclosed" surfaced as non-failing weakening signals.

`verify_odr_document(doc, *, public_key=None, chain=None) -> VerifyResult`
returns a structured, JSON-serializable verdict (the shape a `/verify`
endpoint returns).

15 tests (schema pass/fail, signature verify/tamper/wrong-key/no-key/raw-key,
quorum consistency, chain anchored/broken/unanchored, weakening warnings,
digest-matches-emitter, garbage-key rejection). ruff + format clean under the
repo config.

Tier 2 (additive module + tests, no handler/OpenAPI/protected-file changes).
The HTTP endpoint wiring and a CLI `--format odr` verify path are natural
follow-ups. Part of #8223; advances #8226.

https://claude.ai/code/session_018jfJj5gb9VoLs6VBMnzhrP
scarmani added a commit that referenced this pull request Jun 25, 2026
…pes.py and aragora/receipts.py (#8633)

Both modules are dead/unreachable: same-named packages aragora/types/ and
aragora/receipts/ shadow them and take import precedence, so
`import aragora.types`/`import aragora.receipts` already resolve to the
packages (types/__init__.py, receipts/__init__.py). receipts.py was only a
re-export of aragora.gauntlet.receipt + aragora.export.decision_receipt, both
of which the receipts/ package already re-exports (strict superset); types.py's
aliases were unreachable behind the package. No shim is needed (the packages
take over). ODR conflict check clean: zero open PRs touch either file (the 3
open ODR PRs #8608/#8389/#8289 are aragora/gauntlet/-only).

Fulfills VAL-P4A-007.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
@scarmani scarmani marked this pull request as ready for review June 30, 2026 04:04
@github-actions

Copy link
Copy Markdown
Contributor

Aragora Code Review

Advisory-only review. No issues found.

@scarmani

Copy link
Copy Markdown
Collaborator

Queue-drain close: exact-head settlement/evidence on 35813c5 returned CHANGES_REQUESTED from both Claude and Grok, and the current drain contract forbids repairing a PR after dissent. There is no active owner or fresh steering, and the branch is preserved. Reopen or recut only with a new owner prepared to address the model dissent.

@scarmani scarmani reopened this Jun 30, 2026
scarmani added 2 commits June 30, 2026 13:27
…rash

Addresses #8389 model-review finding (P2): malformed receipts with present-but-null participants/supporting_agents/dissenting_agents crashed _check_quorum_consistency. Now validation flags non-list subfields (->FAIL) and the cross-check coerces defensively. Regression tests added.
… not a crash

Adversarial review surfaced a class of malformed-input crashes (independence coercion, zero-signature handling, non-dict chain entries, null list subfields). Rather than patch each call site round-by-round, wrap every check in a boundary guard: any exception during verification becomes a FAIL Check. Matches the engine contract (verifies untrusted/tampered receipts) and resolves the whole class. Tests added.
@scarmani

Copy link
Copy Markdown
Collaborator

Operator settlement (owner authorization): adversarial model review surfaced only advisory [P2]/[P3] findings — zero [P0]/[P1], unresolved_dissent: false. Two rounds of real findings were fixed in this PR (null list-subfields; a malformed-input crash class now caught by a pipeline guard so any exception → FAIL verdict). Remaining advisory robustness/semantics refinements are tracked as follow-ups in #8725. Merging on owner judgment per the value-preservation policy; the only red required check is the model-quorum gate (the other 5 required checks pass).

scarmani added a commit that referenced this pull request Jul 1, 2026
* feat(gate): opt-in advisory-dissent settlement (default OFF)

Adds ARAGORA_ENABLE_ADVISORY_DISSENT_SETTLE (default OFF). When ON, a Tier 0-2 PR settles via verdict=advisory_settle when: all non-quorum required checks green, >=1 western-frontier model review at head, and ZERO [P0]/[P1] blocking findings across all reviews (every CR is severity-gated advisory). Advisory findings are surfaced in the packet for follow-up issues. Behavior-preserving until the flag is enabled in the merge-quorum workflow (separate Tier-4 edit). Tier 3-4 unaffected.

Spec: docs/plans/2026-06-30-advisory-dissent-settlement-gate-packet.md. Unblocks substantial new features (e.g. #8389) that two thorough reviewers never double-PASS. Refs #8725.

* docs: add advisory dissent settlement packet

* fix(gate): redesign advisory-settle per review findings (#8729)

Addresses the [P1]/[P2] findings from claude+openai that (correctly) blocked the first cut:
- claude [P1]: advisory_settle was dead under the real caller — the repair_or_wait branch fired first on has_failures (the quorum check). Now exclude advisory_settle_eligible from the has_failures/repair_first disjuncts AND the 'checks failing' reason, so it is actually reachable.
- claude [P2]/contradiction: eligibility keyed on the supportive-only has_western_frontier_signal, defeating its own target case. New _has_western_frontier_review_at_head counts a WF review at head in ANY verdict (incl. advisory CR).
- openai [P1]: required genuine advisory dissent (advisory_findings non-empty) so a lone approving comment can't be a one-review bypass.
- openai [P2]: _any_review_has_blocking_finding + WF detection now use the identity resolver (not a heading-token list) so a recognized review can't slip a [P1] past.
Tests rewritten to use REAL caller inputs (has_failures=True) — the prior has_failures=False masked the dead-code path. 496 tests pass. Still default-OFF.

* fix(gate): harden advisory-settle WF identity + doc/dogfood findings (#8729 r2)

Second review round (findings dropped from 2x[P1] to advisory + 1 new [P1]):
- openai [P1]: _has_western_frontier_review_at_head now applies IDENTITY_COUNT_BLOCKERS validation (same as the strict quorum path), so a conflicted/spoofed identity (## Grok heading + Model family: claude) cannot fake a western-frontier signal. Regression test added.
- openai/claude [P2]: corrected the packet doc — advisory findings are SURFACED in the merge-packet (advisory_findings) for a caller to file, not auto-filed by the gate.
- claude [P2]: documented the deliberate choice NOT to require dogfood (it is skipped for negative-verdict comments, so requiring it would re-create the self-defeating contradiction the WF-any-verdict fix removed).
497 tests pass; default-OFF.

* fix(gate): reject bot-authored WF reviews in advisory-settle (#8729 r3)

openai [P1] (r2): _has_western_frontier_review_at_head did not reject github-actions[bot]/synthetic authors like _model_review_signals_from_comments does, so a bot-authored 'Claude/OpenAI' advisory comment could fake the WF prerequisite and reach admin_squash_allowed. Now applies the same _is_github_actions_author rejection. Regression test added. claude already PASSes at the prior head; this closes the last openai [P1].

* fix(gate): unify advisory-settle source validation (#8729 r4)

openai [P1] (r4): bool(advisory_findings) treated bot/uncountable advisory CRs as genuine dissent, another source-validation bypass. Rather than patch each input, consolidate into one _advisory_settle_review_signals pass: all POSITIVE inputs (WF review present, genuine advisory dissent) share the strict path's filters (non-bot author + countable identity); the blocking-finding scan stays fail-closed/permissive. Closes the whole spoofed-identity/bot-author/uncountable-CR class. claude already PASSes; 2 regression tests added (11 advisory tests, 498 total).
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.

3 participants