mirror of
https://github.com/razzant/ouroboros.git
synced 2026-08-05 00:29:46 +00:00
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>
101 lines
3.8 KiB
Python
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
|
|
}
|