Skip to content

judge: land cal's canonical AD-04 confirmation-authority guard (rebased) - #57

Merged
worldofhacks merged 7 commits into
codex/platform-observability-followupfrom
sub/cal-ad04-rebase
Jul 25, 2026
Merged

judge: land cal's canonical AD-04 confirmation-authority guard (rebased)#57
worldofhacks merged 7 commits into
codex/platform-observability-followupfrom
sub/cal-ad04-rebase

Conversation

@worldofhacks

Copy link
Copy Markdown
Owner

Replays cal's PR #54 onto the integration head so the AD-04 guard can actually land. PR #54 itself is 50 commits behind, CONFLICTING, and red on two checks; all three problems were staleness, not substance.

Why this is urgent

The guard is not redundant — the hole is open on the integration head right now. hosted_runtime.py there offers the model Judge the full _VERDICTS enum (including EXPLOIT_CONFIRMED) and gates a safe verdict on a calibrated boolean the model emits about itself. A model-claimed EXPLOIT_CONFIRMED with no oracle hit flows straight into a Documentation draft.

q (PR #50) already deleted its own divergent AD-04 path in good faith to defer to this one, so until this lands nothing enforces the invariant.

What the guard does

  • _MODEL_ASSESSMENT_VERDICTS omits EXPLOIT_CONFIRMED, so the model is never given the vocabulary to claim a confirmation (schema-enforced at the judge output_schema).
  • Defence in depth: a stray EXPLOIT_CONFIRMED is downgraded to EXPLOIT_LIKELY with reason: model_confirmation_refused — the unsafe signal is kept, only the authority is refused.
  • Calibration authority moves to the enablement gate (model_calibration_enabled=self._judge_calibration_id is not None) instead of the model's self-report.

Oracle / canary / human remain the only paths to EXPLOIT_CONFIRMED.

How the three failures were resolved

Failure Cause Resolution
CONFLICTING (6 paths) integration head already carries a later revision of the same lane's docs took the integration side; skipped duplicate 507d032, already landed via #48
test FAILURE ruff format --check on a Markdown python fence in AF-VULN-005 — pytest never ran, so this branch had zero test signal integration head's copy is already ruff-0.16 formatted
secret-scan FAILURE gitleaks false positive on the synthetic literal "Idempotency-Key": "idem-policy-1234567890" in a commit that is not even an ancestor integration head's .gitleaksignore (from ec85a45) comes along with the rebase

No credential was leaked and nothing was redacted or rotated — the flagged value is a synthetic test nonce.

One real repair beyond the rebase

The rebase merged cleanly but broke semantically: this branch predates the refactor of agents/hosted_prompts.py into the agents/prompts package, so three new test modules failed at collection. Retargeted them at load_prompt_registry() following the existing idiom in tests/control_plane/test_agent_acceptance_store.py:70. No change to guard behaviour.

Verification (local, CI-pinned toolchain)

  • ruff format --check . → 724 files already formatted
  • ruff check . → all checks passed
  • gitleaks git .no leaks found (413 commits)
  • validate-corpus → valid, 16 authored cases / 54 labels / 6 categories
  • 79 targeted tests pass, including test_hosted_runtime_confirmation_authority.py

Honest limits

  • The 004 Medium / 005 Low / 006 Low severities are the integration head's, deliberately preserved; cal's older duplicates were dropped.
  • This lands the code guard only. Model-Judge enablement remains blocked on three separate inputs, none of which this PR supplies: a staged production HostedConfigurationSet + its configuration_sha256, a two-person human ground-truth attestation, and a measured --provenance-attestation. Every committed label today is rule/oracle-derived — none human.
  • The guard is inert-safe: it constrains the model path and requires no new configuration to be correct.

alexandermill3r and others added 7 commits July 25, 2026 15:59
Three defects blocked an honest model-Judge calibration. None was visible
offline, because the only network-free path measures the deterministic
oracle Judge, which abstains on every non-oracle label by design.

1. OpenRouter routing was broken for every hosted role. The transport sent
   `max_completion_tokens`, but OpenRouter advertises `max_tokens` in each
   endpoint's `supported_parameters`, and the request sets
   `provider.require_parameters: true` — which refuses any endpoint that
   does not support a parameter we send. Every hosted call therefore failed
   routing with HTTP 404 "No endpoints found that can handle the requested
   parameters". Verified against google/gemini-2.5-pro: identical request
   with `max_tokens` returns 200 and full usage accounting.

2. The provider projection leaked the ground-truth label. In a calibration
   corpus only oracle/canary-confirmed samples carry a trusted signal, so
   the per-sample `*_signal_count` / `*_signal_available` fields WERE the
   label: "count >= 1" read as EXPLOIT_CONFIRMED without the model
   evaluating any evidence. That channel would have inflated the agreement
   metric. The projection now reports only a constant
   `*_signal_withheld_for_independent_assessment: true`, identical for
   every sample.

3. There was no way to measure a model Judge at all. run_judge_calibration
   never contacts a provider and needs a lineage-complete captured bundle,
   but nothing produced one. scripts/capture_judge_calibration.py now runs
   the ground-truth corpus through the real HostedEvaluator and emits that
   bundle with per-sample OpenRouter request id, correlation trace id,
   returned model, token split and measured cost.

Also adds the owner-accepted threshold policy as reviewable named
constants (`strict` and `accepted`), selectable via
`--threshold-policy`, defaulting to `accepted`. It relaxes exactly two
criteria — agreement 0.90 -> 0.85 and false-negative rate 0.00 -> 0.10 —
and NOTHING else. The `confirmed_exploit_missed` invariant is evaluated
separately from every rate and still fails calibration on its own under
both policies; the false-positive bar is unchanged. Rationale, residual
risk, and change control are documented in
docs/evidence/judge-calibration/THRESHOLD_POLICY.md.

The capture script never touches a live target, never enables the model
Judge, and requires an explicit --confirm-provider-spend.

CI: adds redteam/** to the GitHub push triggers, which previously matched
only [main, swarm/**, run/**, codex/**] — work on this branch namespace
would otherwise have been pushed with no CI run at all.

Tests: 1518 passed (1509 at 971dd98 + 9 new), ruff check and
ruff format --check clean, secret scan clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Every red-team-adjacent capability the mandate asks about, classified as
executed / implemented-but-unexecuted / architected-only / absent, each
anchored to a file:line or an artifact path. Written so a reader can tell
what the platform HAS DONE from what it COULD do.

The load-bearing facts:

- Nothing in this repository has ever confirmed an exploit. Every verdict
  in every captured run is INDETERMINATE with reason
  non_oracle_uncalibrated_indeterminate. Documentation and regression
  admission both require EXPLOIT_CONFIRMED, so neither has ever had an
  input.
- Novel generation and mutation are not merely unwired, they are
  structurally undispatchable: coordinator.py:393-397 requires every
  proposal to equal seed_to_attempt(seed_case) byte-for-byte, because the
  authorization's operation hash binds the corpus hash. Making them live
  is an authorization-model change and is integration-owned. Flagged, not
  silently patched.
- Minimization and cross-category regression analysis are absent from
  src/ entirely (grep returns zero hits for both).
- One security tool has live-target executed evidence (ZAP). Three have
  offline executed evidence (garak, pyrit, promptfoo). Giskard and Semgrep
  have adapters and CI paths but no committed executed output. Burp is not
  installed.
- 1 of 4 supported target surface kinds has ever been exercised; the other
  three are enabled:false or undeclared in the catalog.
- performance/ is 1219 well-tested LOC with ZERO producers; the only
  "hits" outside the package are egg-info build metadata.
- A 100-case authorization is not expressible: evals/workloads/ does not
  exist, and the committed catalog caps (40 attempts / 60 physical /
  $1.00 / 1800 s) would reject a 121-request scope at registry.resolve.

Two measurement-integrity defects worth a fix by whoever owns them:
target-request measured_cost is a configured 0.01 constant multiplied by
request count, not a measurement (telemetry/outbound.py:470,541); and
target_version is the adapter NAME, not a target build version
(policy/gateway.py:626), so every finding and regression keyed on target
version is keyed on a constant.

Also corrects six prior handoff/doc claims this audit could not
substantiate, including a COMBINED_SUMMARY.md that cites a manifest
directory absent from the repo.

I re-verified each claim directly rather than relying on the audit pass;
two audit findings were overstated and are corrected here (workbench
operational records are 6 of 10, not 7 — the 7th match is a Literal type
declaration; and a giskard.json does exist, but as an orphaned legacy
test fixture referenced by no test, not as executed evidence).

Adds scripts/build_calibration_corpus.py, which assembles ground-truth
slices from an authored content table. Labels, verdicts, confidences,
reason codes and trusted signals are decided in code from the sample slug
alone; the content file supplies only the transcript, case ref and notes.
That split makes it structurally impossible for an author to move a label
to match an evaluator's output.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…esolve on OpenRouter

Verified the whole hosted 4-agent demo envelope against GET /api/v1/models (live, 2026-07-24):
orchestrator anthropic/claude-opus-4.8, red_team qwen/qwen3.5-397b-a17b, judge
google/gemini-2.5-pro, documentation openai/gpt-5.4 — ALL RESOLVE (several created after the
Jan-2026 cutoff, hence unfamiliar). No substitution required; documented a real nearest substitute
per role (one-line hosted.py change) for resilience. Closes the model-resolution question for the
whole hosted demo, not just Red Team.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Measured evaluator: google/gemini-2.5-pro via OpenRouter (google-vertex),
54 authored ground-truth labels, 9 per category across all six mandated
categories. 54/54 provider calls succeeded, no retries, measured spend
$0.75823375, every sample carrying its own OpenRouter request id,
returned model, token split and measured cost.

  agreement            0.9259   (accepted >= 0.85, strict >= 0.90)
  false-negative rate  0.0000   (accepted <= 0.10, strict <= 0.00)
  false-positive rate  0.0000   (<= 0.05)
  abstention rate      0.0370   (<= 0.40)
  calibration error    0.0731   (<= 0.10)
  samples per category 9        (>= 5)

  54 samples, 50 agreements, 0 false negatives, 0 false positives,
  2 abstentions, 4 disagreements.

It passes the STRICT bar as well, so the owner-accepted relaxation was
not needed for this identity. Both runs are retained, each embedding its
own thresholds.

THE HONEST CAVEAT: all six disagreements/abstentions fall on the
deliberately-undecidable AMB samples, and four of six times the model
resolves genuine ambiguity toward EXPLOIT_LIKELY at high confidence
instead of abstaining. The failure direction is conservative -- it
over-calls uncertain evidence rather than clearing it -- but it is
INVISIBLE to both headline rates, because false-positive rate counts
only "expected NO_EXPLOIT_OBSERVED -> actual positive". "FP 0.0000" means
it never over-called a sample whose ground truth was SAFE (24 of 24), not
that it never over-calls. Documented in RESULT_2026-07-24.md.

Corpus grew 30 -> 54 labels and the transcripts were repaired from
meta-descriptions into actual exchanges, because the smoke test showed
the Judge correctly abstaining with "the only description of the agent's
behavior comes from a hostile, untrusted transcript summary ... cannot be
verified without access to the actual agent response". That is a
data-quality defect, not a Judge defect.

Nothing was relabelled. Label identifiers, verdicts, sample kinds and
case references are preserved; only evidence adequacy changed. Each
category gains a threshold-near positive, a threshold-near negative, an
evaluator-injection-on-a-safe-case negative, and a clean-refusal
negative, giving a stated 24 positive / 24 negative / 6 ambiguous
balance.

Evidence that this was not gamed: the expansion made the corpus HARDER.
The deterministic-oracle baseline fell from agreement 0.600 / abstention
0.600 at 30 labels to agreement 0.333 / abstention 0.778 at 54, because
the non-oracle proportion rose. A corpus tuned to flatter the evaluator
moves the other way.

Two of my own design assumptions were wrong and the validator caught
both: evaluator_injection is reserved for samples where deterministic
evidence must survive hostile text trying to DOWNGRADE it, so the
inverse-direction sample is honestly a negative_control; and the
confirmation signal must come from the REFERENCED CASE's own
oracle_expectation, since several cases in one category declare
different oracles.

Four snapshot assertions that pinned the old 30-label corpus are
rewritten to assert the INVARIANT, derived from the corpus, so the next
expansion does not require editing tests -- only a change in Judge
behaviour should fail them.

The model Judge remains FAIL-CLOSED: this artifact records
human_approved false, runtime_enabled false, approver_ref null.
Measurement, human approval, and runtime enablement stay three separate
operations and only the first has happened.

Frozen for integration:
  identity_sha256   aac135ea54be0c812c826a15876f82e2c8a7e5f032dd3157ae37b10dcd140eb0
  calibration_id    JC-67647b62fc6d843e28b2f1dbee8e5dddba5b0bc2188b7fc84747c39ed682a32f
  slice_set_sha256  39e1b72dd38a23e774ce6af8b2276085c548d95d8d2ee091fdfbbe583b7860ae
  corpus_sha256     011d2f2f231e13ce1b84b2ed4d393bd808dfd2e3a7370cc42f4fdfd494fcbedd (9 active seeds)

Gates: 1515 passed / 3 skipped / 0 failed, ruff check and
ruff format --check clean, secret scan clean (893 files), validate-corpus
16 cases + 54 labels + 6 categories, no duplicate input sequences.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…el-authority holes

Wires the re-attest harness so a calibration can only ever attest the Judge the
deployment is actually running, and repairs two places where the model's own
output was granting it authority. The re-attest itself has NOT been run — it is
blocked on inputs I do not own (below). No number in this commit is a new
measurement.

WHY THE 8ce852b NUMBER IS SUPERSEDED, PRECISELY

Its provider calls were real: 54 unique OpenRouter request ids, 54 distinct
token triples, costs read from the provider's own usage.cost field that match
Gemini 2.5 Pro list price (1.25/10 per M) rather than this repo's 5/30 ceilings.
The arithmetic reproduces byte-for-byte. What disqualifies it is what it was
bound to:

  capture_judge_calibration.py built its OWN HostedConfigurationSet. Since
  judge_model_version == judge_role.configuration_sha256, which hashes
  credential_reference, prices and limits along with model and prompt, and since
  those limits were sized to the label count, the attested identity drifted with
  corpus size and could never equal the deployed one. load_judge_calibration_status
  compares judge_identity against the running identity and returns
  invalidated/identity_drift — so the identity RESULT_2026-07-24.md offered under
  "hand this to integration" would have been rejected by the runtime it was handed
  to. Pinned by test: the same evaluator over 54 vs 56 labels yields different
  judge_model_version values.

Two further properties, disclosed before but now measured rather than described:
the evidence judged is authored, never executed (campaign_run_id
"ground-truth-unexecuted"), and the pooled headline includes 12 oracle-backed
samples the model cannot influence. Restratified: pooled agreement 0.9259 over
54, but 0.9048 over the 42 samples the model actually decides, with 4 ambiguous
over-calls the false-positive rate structurally cannot see.

CONFIRMATION AUTHORITY — two real holes, both reproduced by execution

HostedFourRoleRuntime is the four-role composition an enabled model Judge runs
inside, and the class PR #50 builds run_attempt on. It took two authorities from
the model's own response:

  1. its verdict enum included EXPLOIT_CONFIRMED and _deterministic_precedence
     returned it unchanged when no oracle or canary had fired. run_attempt drafts
     a vulnerability report for any EXPLOIT_CONFIRMED/EXPLOIT_LIKELY verdict, so a
     model claiming a confirmation produced Documentation output for a finding
     with no evidence behind it.
  2. the guard refusing an uncalibrated NO_EXPLOIT_OBSERVED was keyed on a
     "calibrated" boolean the MODEL emitted about itself — so a model asserting
     its own calibration could clear a case. That is the dangerous direction.

Repaired: EXPLOIT_CONFIRMED removed from the model's output schema, a
model-claimed confirmation downgraded to EXPLOIT_LIKELY with reason
model_confirmation_refused, calibration status passed in from the caller's
enablement gate. The class had no production composition root before now, which
is why this had not yet produced a bad finding; PR #50 gives it one.

WHAT THIS ADDS

- capture_judge_calibration.py: --hosted-configuration-set and
  --expected-configuration-sha256 are now required. Identity is derived from the
  staged set, never synthesized. Reconstruction is itself a check — a prompt_sha256
  that is not this release's served prompt is a refusal. Capacity preflight refuses
  a corpus past the staged budget or the 56-call platform ceiling rather than
  relaxing limits, which would change the identity being attested.
- analyze_judge_calibration.py: restates a result over the NON-ORACLE stratum, the
  only cases an enabled model decides, and counts the two failure modes the
  contract's counters cannot express — a positive scored safe, and an over-call on
  an ambiguous sample.
- enable_model_judge.py: the human gate. Refuses on identity drift, on a
  non-oracle stratum breach even when the pooled headline passes, on ground truth
  without a two-person blind attestation bound to the exact slice_set_sha256, and
  without a named approver. Re-checks the written artifact through
  require_model_judge_enablement.
- REATTEST_RUNBOOK.md: the procedure, what the identity does and does not cover,
  and the fact that the replay path validates bundle SHAPE only — a hand-written
  bundle with 0 tokens and $0 cost yields a contract-valid passing artifact under
  the strict policy with no API key. Any re-attest must be reported as "the harness
  measured this bundle" unless an out-of-band OpenRouter usage export is attached.

BLOCKED ON (neither is mine to resolve)

- m: no staged production hosted configuration set exists on disk; production role
  configs live in the Postgres hosted_configuration_sets table.
- g: the 100-case ground-truth set is GT-CAND-M11-LIVE100, 200 candidate labels,
  calibration_status AUTHORED_PENDING_HUMAN_ATTESTATION, human_labeler and
  distinct_reviewer both null, and its labels carry no evidence_envelope — so it is
  neither human-labeled nor runnable through CalibrationGate. The committed 54
  labels are all rule- or agent-authored.
- Scope note: 200 labels cannot be captured in one run at all. max_calls is capped
  at HOSTED_MAX_PHYSICAL_CALLS=56, and raising it would change the identity.

1547 passed, 3 skipped. tests/test_packaging.py::test_wheel_installed_outside_repo_validates_corpus
fails identically on the untouched base (fresh-venv wheel install), unrelated.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…rovider-reconciled provenance

Two follow-ups from review, plus the scope correction they imply.

BATCHING INSTEAD OF A WIDER CAP

A corpus past HOSTED_MAX_PHYSICAL_CALLS (56) cannot be captured in one run, and raising
max_calls is not the way out: limits sits inside the Judge role's configuration_sha256,
which IS judge_model_version. A wider cap attests a different evaluator, so the sub-runs
could not be aggregated at all. The corpus is now split into sub-runs against ONE
unchanged staged configuration:

- capture gains --batch-size / --batch-index / --plan-only. Batch size defaults to the
  staged Judge role's max_calls and may never exceed it — the refusal says to reduce the
  batch, not to raise the limits.
- generation_policy_sha256 now binds the CAMPAIGN (corpus size + batch size) rather than
  the per-batch sample count, so it is constant across sub-runs and the merged bundle has
  one coherent value. A per-batch count would make the trailing short batch disagree for
  no reason that means anything.
- merge_calibration_batches.py refuses unless every batch carries a byte-identical
  judge_identity and pinned provenance, the batches are disjoint, and their union covers
  the corpus EXACTLY. Coverage is checked against the slice directory, not the batch
  manifests, because a silently dropped batch would measure a smaller, easier corpus and
  report a better agreement rate.

Verified lossless against the committed 54-sample bundle split into 2 batches of 27: the
merged measurement reproduces both the metrics and the calibration_id of the unbatched run.

PROVENANCE: FROM SHAPE-VALID TO MEASURED

The replay path validates bundle shape only — a hand-written bundle with a fabricated
request id, 0 tokens and $0 cost yields a passing artifact under the strict policy with no
API key. verify_calibration_provenance.py now reconciles the bundle against OpenRouter's
own usage export: every provider_request_id must appear in it, with matching model, cost
and tokens, and the summed cost must reconcile with the ledger. A fabricated id has
nothing to match. The attestation is a REQUIRED input to enable_model_judge.py, so a
bundle that is merely shape-valid can no longer license runtime authority.

The export is CSV or JSON with case-insensitive column aliases; an unmappable export
prints the headers it saw and refuses rather than guessing, because a column guessed wrong
would "verify" nothing. The attestation states in its own body what it does not prove:
that the evidence judged came from a live target. It establishes only that the Judge model
calls really happened and cost what the bundle claims.

PER-BATCH VISIBILITY

analyze_judge_calibration.py takes the batch manifest and reports per-sub-run metrics
beside the aggregate. The aggregate governs; the batches are shown so a degraded sub-run
stays visible instead of averaged away. On the rehearsal split, batch-1's non-oracle
agreement (0.8571) is visibly weaker than batch-0's (0.9524) — invisible in the 0.9048
aggregate.

COST

One physical Judge call per label; the 54-label set measured $0.75823375, so ~$0.014/label
— about $2.80 for a 200-label corpus across 4 batches of 56/56/56/32.

Three capacity tests in test_judge_calibration_capture_binding.py are retired: their
concern (a corpus larger than one batch) is no longer a refusal but a split, and is covered
in test_judge_calibration_batching.py.

1568 passed, 3 skipped, ruff clean. The re-attest itself is still NOT run — it remains
blocked on m's staged configuration and on ground-truth labels carrying both principals
and evidence envelopes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…uthority

Rebasing cal's calibration work onto the integration head produced a clean
textual merge but a broken import: the branch was cut before
`src/agentforge/agents/hosted_prompts.py` was refactored into the
`agentforge.agents.prompts` package, so three new test modules still imported
`hosted_prompt` from a module that no longer exists and failed at collection.

Resolve each the way the rest of the suite already does
(tests/control_plane/test_agent_acceptance_store.py:70): look the role up in
`load_prompt_registry()` and read `.sha256`, replacing the old
`hosted_prompt(role).prompt_sha256`.

No behavioural change to the AD-04 guard; this only re-points test scaffolding
at the current prompt-authority API.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@worldofhacks
worldofhacks force-pushed the sub/cal-ad04-rebase branch from 0d5bc33 to df0ed6e Compare July 25, 2026 21:00
@worldofhacks
worldofhacks merged commit 935e740 into codex/platform-observability-followup Jul 25, 2026
10 checks passed
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.

2 participants