feat(odr): in-package ODR verification engine (odr_verify) — ODR-3 server core#8389
feat(odr): in-package ODR verification engine (odr_verify) — ODR-3 server core#8389an0mium wants to merge 4 commits into
Conversation
…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
…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>
Aragora Code ReviewAdvisory-only review. No issues found. |
|
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. |
…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.
|
Operator settlement (owner authorization): adversarial model review surfaced only advisory [P2]/[P3] findings — zero [P0]/[P1], |
* 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).
What
The server-side single implementation that the planned
POST /api/receipts/verifyendpoint (and any internal caller) will wrap — completing the ODR-3 verifier (#8226) on the server side. The standalone, zero-Aragora-dependency mirror is thearagora-verifyPyPI 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: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);cryptographyimported lazily and the check gracefully skipped — never failed — when it's absent;"undisclosed"surfaced as non-failing weakening signals.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
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 --checkclean under the repo config (noBLE001/T201— narrow excepts, library-only).Tier / scope
Tier 2 (additive module + tests; no handler/OpenAPI/protected-file changes). Follow-ups: the
POST /api/receipts/verifyHTTP endpoint wrapping this engine, and anaragora receipt verify --format odrCLI path.Part of #8223; advances #8226.
Generated by Claude Code