ouroboros/devtools/benchmarks/common/secrets.py
Anton Razzhigaev a5bdf5e010 v6.81.0: benchmark artefacts must not carry or claim what did not happen
Two provenance fixes in the same class as the release itself, plus the
round-11/12 review findings that land in the same files.

FIX A — an isolated benchmark container carries only the providers the run
declared. `build_isolated_settings` copied EVERY provider credential present
in the live settings file regardless of which providers the run's model slots
declared, so a run pinned to OpenRouter still received direct ANTHROPIC /
OPENAI / Cloud.ru / GigaChat keys. Two consequences: a routing fallback could
spend outside the declared bucket while the manifest said otherwise, and the
reachable provider set was a function of whatever happened to be in the live
file at launch — a pinned seed that pins the code but not the environment is
not reproducible. Provider credentials are now gated on the run's declared
slots, derived from the routing SSOT (`provider_models.PROVIDER_PREFIXES` /
`provider_for_model`, the same registry `llm._resolve_remote_target` routes
on), and travel in whole groups so a key never arrives without the endpoint or
auth fields it is useless without. Owner/control secrets were never copied and
still are not. Ambiguity fails OPEN (carry a spare, disclose it) — never
closed. `benchmark_run_manifest` now records `provider_credentials`: which
credentials the container actually received, by fingerprint, never by value.

FIX B — a task the cost rail truncated says so. `usage_accounting.reserve_-
attempt` refuses on a worst-case reservation bound that reached a $6.00 rail at
$0.45 of actual spend in the v6.81.0 OSWorld smoke, stopping two of three tasks
at 13 and 22 rounds; the artefacts published `status=completed`,
`reason_code=official_evaluate` and the string `budget_exhausted` appeared
nowhere. `task_result_row` gains an always-present `runtime_outcome` projected
by the new shared `runtime_terminal_disclosure`, and every writer holding a
runtime task result now publishes it: OSWorld cu_bridge, SWE-bench and
ProgramBench success rows (the failure rows already did), the Terminal-Bench
in-container summary and disclosure ledger, the GAIA solver, the harness-bench
wrapper, and the CL-Bench per-question writer. Reward, `official_eval_status`
and adapter-stage `status` are untouched: disclosure ADDED, fact not
subtracted. TB's ledger gains a `cost_truncated` bucket — `genuine` asserts a
fair shot, which a rail-truncated trial did not get.

Review findings folded in:
- `_amend_manifest` emitted `output_paths.task_outcome` unconditionally, so the
  finalized attempt manifest kept pointing at an outcome whose write failed.
  The previous round fixed the ledger row and left the manifest lying; both
  sides now follow the same rule.
- `ADAPTER_PATCH_MARKERS` keyed two of three detections on bare env-var names,
  which the unpatched adapter may mention in a comment or a `-e` passthrough
  list. That false positive OVERSTATES enforcement. Markers are now
  patch-unique tokens and the uniqueness requirement is recorded beside them.
  (One marker legitimately covers all three env knobs: they arrive in one loop
  in one hunk.)
- CLB fidelity overstated enforcement on the DEFAULT `--path standard`:
  `_docker_launcher.submit()` hardcodes `disabled_tools: []` and never imports
  the patched bridge module, so the evidence is now entrypoint-specific.
- `runtime_attested` renamed to `runtime_attestation_available`: it is a tree
  probe, and a definition existing is not evidence that it ran.
- README version badge alt text said 6.80.0 while the URL said 6.81.0, which
  `version_carrier_desyncs` flags and the advisory preflight blocks on.

Bug-pinning tests inverted, and said so in the test docstrings:
- `test_dry_run_claims_attestation_only_when_the_patch_is_in_the_execution_-
  clone` asserted `runtime_attested is True` for a DRY RUN against a clone that
  merely contained an `_attest_runtime` definition — it demanded the false
  positive as the contract.
- the CLB fidelity fixtures wrote bare env-var names as "the patch", which is
  precisely the marker weakness above.

Docs corrected where the code falsified them: OSWorld METHODOLOGY §7.4 claimed
`OUROBOROS_MAX_ROUNDS` plus the timeout were the only per-task caps (the USD
rail binds first), §6 now says scoring reads `official_eval_status` /
`details.outcome_status` rather than filtering on `status == "completed"` and
that `output_paths.task_outcome` may be absent; CLB METHODOLOGY §3 documents
when an exported runtime mode overrides the adapter's hard-set `advanced`, and
§6 distinguishes the `--runner-path` adapter checkout from the
`--ouroboros-clone` execution seed and points at the field the code actually
writes.

Known ordering debt noted in place for the v6.82 backlog, deliberately not
restructured here: `_auto_sync_release_metadata_if_needed` runs ~87 lines after
the `_release_metadata_preflight` gate it would satisfy.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-26 05:04:14 +00:00

101 lines
3.8 KiB
Python

"""Secret-loading helpers that never print credential values."""
from __future__ import annotations
import hashlib
import json
import os
import pathlib
from typing import Any, Iterable
SECRET_KEYS = (
"OPENROUTER_API_KEY",
"OPENAI_API_KEY",
"ANTHROPIC_API_KEY",
"GITHUB_TOKEN",
)
def settings_path(default_home: pathlib.Path | None = None) -> pathlib.Path:
home = default_home or pathlib.Path(__file__).resolve().parents[4]
return pathlib.Path(os.environ.get("OUROBOROS_SETTINGS_PATH") or home / "data" / "settings.json")
def load_secret_env(path: pathlib.Path | None = None) -> dict[str, str]:
values: dict[str, str] = {}
for key in SECRET_KEYS:
value = os.environ.get(key)
if value:
values[key] = value
settings_file = path or settings_path()
try:
loaded = json.loads(settings_file.read_text(encoding="utf-8"))
except Exception:
loaded = {}
if isinstance(loaded, dict):
for key in SECRET_KEYS:
value = loaded.get(key)
if value and key not in values:
values[key] = str(value)
return values
def redacted_env_summary(env: dict[str, str], keys: Iterable[str] | None = None) -> dict[str, bool]:
return {key: bool(env.get(key)) for key in (SECRET_KEYS if keys is None else keys)}
def credential_fingerprint(value: Any) -> str:
"""Stable, non-reversible identity for a credential value — NEVER the value itself.
A truncated SHA-256 over a high-entropy API key is not brute-forceable, and it is what
lets an auditor answer the only question that matters across two runs: was this the SAME
key? Empty/absent values fingerprint to the empty string, not to the hash of ""."""
text = str(value or "")
if not text:
return ""
return "sha256:" + hashlib.sha256(text.encode("utf-8")).hexdigest()[:16]
def isolated_credential_grants(cfg: dict) -> dict:
"""Describe, by FINGERPRINT and never by value, which provider credentials a benchmark
settings mapping actually carries — and which its declared model slots called for.
``planned_keys`` is the derivation (``ouroboros.provider_models.provider_credential_plan``,
which reads the same prefix->provider registry ``llm._resolve_remote_target`` routes on);
``granted`` is the truth about the file. The two are reported separately on purpose: a
caller may hand an explicit credential override the slots did not ask for, and an auditor
must SEE that rather than infer it. Prevention without evidence is half a fix."""
from ouroboros.provider_models import ALL_PROVIDER_CREDENTIAL_KEYS, provider_credential_plan
settings = cfg or {}
plan = provider_credential_plan(settings)
present = {
key: settings.get(key)
for key in sorted(ALL_PROVIDER_CREDENTIAL_KEYS)
if str(settings.get(key) or "").strip()
}
return {
"schema": "ouroboros.benchmark.provider_credentials.v1",
"declared_model_slots": plan["declared_model_slots"],
"providers": plan["providers"],
"planned_keys": plan["planned_keys"],
"fail_open": plan["fail_open"],
"granted": credential_disclosure(present, sorted(present)),
}
def credential_disclosure(env: dict[str, Any], keys: Iterable[str] | None = None) -> dict[str, dict[str, Any]]:
"""Fingerprint-level extension of ``redacted_env_summary``: ``{key: {present, fingerprint}}``.
Same mechanism, one rung more auditable — a bare ``True`` cannot distinguish "the run
reached the declared bucket" from "the run reached some other key that happened to be in
the live settings file"."""
names = sorted(env) if keys is None else list(keys)
return {
str(key): {
"present": bool(str(env.get(key) or "").strip()),
"fingerprint": credential_fingerprint(env.get(key)),
}
for key in names
}