Files
Open_Ultra_SAST/tests/test_unseen_bank.py
T
Marius CiepluchandClaude Opus 4.8 06b8a40c46 unseen: cache enumeration + resolution to ousast-corpus (restarts skip the ~26-min prefix)
The decision bank made extraction durable, but every restart still
re-paid the ~26-minute advisory enumeration + per-advisory resolution,
since those lived only in the local journal. Mirror them into the bucket
so a restart reads them back and goes essentially straight to any
remaining extraction.

bank.py: bank_resolution/resolved at <prefix>/<seed>/resolved/<sha256(ghsa)>.json
(streamed one-by-one as each advisory is decided), cache_advisories/
cached_advisories at <prefix>/<seed>/advisories/list.json, and
bank_repository/repositories (caches the advisory_index repo-resolution
pass, preserving renames and 404s). All gated by RESOLUTION_CONTRACT="v1"
(extract.py) -- bumped only when enumeration/eligibility/source.candidate
output changes; recent fetch/parse/pool changes do not bump it.

draft.py run(): with --bank, advisories load from the cache (else
enumerate then cache); eligibility decisions load from the bucket
(contract-matched) to skip re-resolution; new resolutions stream to the
bucket before the local checkpoint write. Repository lookups reuse the
repositories/ cache. No --bank => local-journal behavior unchanged.

Net: advisories + resolutions + extraction all resume from ousast-corpus;
restarts no longer re-enumerate or re-resolve. Fresh and resumed runs
produce byte-identical manifests at 1 and 4 workers. Bank methods proven
live against ousast-corpus. 356 tests pass, ruff clean.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-10-10 16:33:29 +02:00

318 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Offline decision-bank contract: durability, identity and plain object transport."""
import hashlib
import io
import json
import sys
from datetime import datetime
from types import SimpleNamespace
import pytest
from benchmarks.unseen.bank import CorpusBank, CorpusObjects, corpus_settings, split_decided
class FakeObjects:
def __init__(self):
self.objects = {}
self.labels = {}
self.calls = []
self.failures = {}
def call(self, operation):
self.calls.append(operation)
if self.failures.get(operation, 0):
self.failures[operation] -= 1
raise OSError("offline failure")
def get(self, key):
self.call("get")
return (self.objects[key], "version") if key in self.objects else None
def put(self, key, data, labels):
self.call("put")
self.objects[key] = data
self.labels[key] = labels
def keys(self, prefix):
return [key for key in reversed(self.objects) if key.startswith(prefix)]
def settings_env():
return {
"CORPUS_S3_ENDPOINT": "https://objects.invalid",
"CORPUS_S3_BUCKET": "ousast-corpus",
"CORPUS_AWS_ACCESS_KEY_ID": "private-access",
"CORPUS_AWS_SECRET_ACCESS_KEY": "private-secret",
}
@pytest.mark.parametrize("missing", list(settings_env()))
def test_settings_names_missing_key_without_values(missing):
env = settings_env()
del env[missing]
with pytest.raises(ValueError) as error:
corpus_settings(env)
assert missing in str(error.value)
assert all(value not in str(error.value) for value in env.values())
def test_settings_and_optional_region():
env = settings_env()
assert corpus_settings(env) == {
"endpoint": env["CORPUS_S3_ENDPOINT"],
"bucket": "ousast-corpus",
"access_key": "private-access",
"secret_key": "private-secret",
}
assert corpus_settings({**env, "CORPUS_S3_REGION": "region-1"})["region"] == "region-1"
def test_bank_roundtrip_and_idempotent_bytes():
objects = FakeObjects()
bank = CorpusBank(objects, seed=42)
entry = {"repository": "org/a", "fix": "abc", "advisory": "GHSA-a", "changes": [{"code": "text"}]}
result = {"entry": entry, "ordinary_exclusions": {"merge": 2}, "counts": {"eligible": 13}}
key = bank.key_for("org/a", "abc")
assert key == hashlib.sha256(b"org/a\nabc").hexdigest()
bank.bank("org/a", "abc", result, contract="v1")
original = objects.objects.copy()
bank.bank("org/a", "abc", result, contract="v1")
assert objects.objects == original
assert list(objects.objects) == [f"unseen/42/decisions/{key}.json"]
row = bank.decided()[key]
assert row == {
"repository": "org/a",
"fix": "abc",
"decision": "accept",
"reason": None,
"entry": entry,
"ordinary_exclusions": {"merge": 2},
"counts": {"eligible": 13},
"contract": "v1",
"seed": 42,
"ts": row["ts"],
}
assert datetime.fromisoformat(row["ts"]).utcoffset().total_seconds() == 0
assert objects.labels[next(iter(objects.labels))] == {"decision": "accept", "contract": "v1"}
def test_rejection_contract_update_and_split():
objects = FakeObjects()
bank = CorpusBank(objects, seed=3, prefix="custom")
for repo, advisory in (("z", "GHSA-z"), ("a", "GHSA-a")):
bank.bank(repo, "fix", {"entry": {"repository": repo, "fix": "fix", "advisory": advisory}}, contract="old")
bank.bank("no", "fix", {"rejected": "reason"}, contract="v1")
bank.bank("stale", "fix", {"rejected": "reason"}, contract="old")
accepted, keys = split_decided(bank.decided(), contract="v1")
assert [entry["repository"] for entry in accepted] == ["a", "z"]
assert keys == {bank.key_for(repo, "fix") for repo in ("a", "z", "no")}
row = bank.decided()[bank.key_for("no", "fix")]
assert row["reason"] == "reason" and row["decision"] == "reject"
assert row["entry"] is None and row["counts"] == row["ordinary_exclusions"] == {}
bank.bank("stale", "fix", {"rejected": "different"}, contract="v1")
assert len(objects.objects) == 4
assert bank.decided()[bank.key_for("stale", "fix")]["contract"] == "v1"
@pytest.mark.parametrize("operation", ["get", "put"])
@pytest.mark.parametrize("failures", [1, 2])
def test_bank_retries_once_then_surfaces_failure(operation, failures):
objects = FakeObjects()
objects.failures[operation] = failures
bank = CorpusBank(objects, seed=1)
if failures == 1:
bank.bank("repo", "fix", {"rejected": "reason"}, contract="v1")
assert len(objects.objects) == 1
else:
with pytest.raises(OSError, match="offline failure"):
bank.bank("repo", "fix", {"rejected": "reason"}, contract="v1")
assert objects.calls.count(operation) == 2
def test_decided_retries_get_and_fails_on_missing_listed_object():
objects = FakeObjects()
bank = CorpusBank(objects, seed=1)
bank.bank("repo", "fix", {"rejected": "reason"}, contract="v1")
objects.failures["get"] = 1
assert len(bank.decided()) == 1
objects.get = lambda key: None
with pytest.raises(ValueError, match="missing"):
bank.decided()
def test_plain_s3_adapter_without_bucket_probes(monkeypatch):
calls = []
class Missing(Exception):
response = {"Error": {"Code": "NoSuchKey"}}
class S3:
def put_object(self, **kwargs):
calls.append(("put", kwargs))
def get_object(self, **kwargs):
calls.append(("get", kwargs))
if kwargs["Key"] == "missing":
raise Missing()
return {"Body": io.BytesIO(b"data"), "ETag": "tag"}
def get_paginator(self, name):
assert name == "list_objects_v2"
return SimpleNamespace(paginate=self.pages)
def pages(self, **kwargs):
calls.append(("list", kwargs))
return [{"Contents": [{"Key": "p/a"}]}, {}, {"Contents": [{"Key": "p/b"}]}]
def client(service, **kwargs):
calls.append((service, kwargs))
return S3()
monkeypatch.setitem(sys.modules, "boto3", SimpleNamespace(client=client))
monkeypatch.setitem(sys.modules, "botocore.config", SimpleNamespace(Config=lambda **kwargs: SimpleNamespace(**kwargs)))
adapter = CorpusObjects(**corpus_settings(settings_env()))
adapter.put("p/a", b"data", {"decision": "accept", "contract": "v1"})
assert adapter.get("p/a") == (b"data", "tag")
assert adapter.get("missing") is None
assert adapter.keys("p/") == ["p/a", "p/b"]
assert calls[0][0] == "s3"
assert calls[0][1]["endpoint_url"] == "https://objects.invalid"
assert calls[0][1]["config"].retries == {"total_max_attempts": 1}
assert calls[1][1]["Bucket"] == "ousast-corpus"
assert calls[1][1]["Body"] == b"data"
assert calls[-1] == ("list", {"Bucket": "ousast-corpus", "Prefix": "p/"})
def test_decided_rejects_invalid_identity():
objects = FakeObjects()
bank = CorpusBank(objects, seed=1)
bank.bank("repo", "fix", {"rejected": "reason"}, contract="v1")
key = next(iter(objects.objects))
row = json.loads(objects.objects[key])
row["repository"] = "other"
objects.objects[key] = json.dumps(row).encode()
with pytest.raises(ValueError, match="identity"):
bank.decided()
@pytest.mark.parametrize("endpoint", ["missing-scheme-private", "https://private[", "http://private/secret"])
def test_invalid_endpoint_does_not_expose_value(endpoint):
with pytest.raises(ValueError) as error:
corpus_settings({**settings_env(), "CORPUS_S3_ENDPOINT": endpoint})
assert "CORPUS_S3_ENDPOINT" in str(error.value)
assert endpoint not in str(error.value)
def test_import_does_not_require_boto3(monkeypatch):
import importlib.util
from benchmarks.unseen import bank
monkeypatch.setitem(sys.modules, "boto3", None)
spec = importlib.util.spec_from_file_location("offline_bank", bank.__file__)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
assert module.CorpusBank.key_for("a", "b") == CorpusBank.key_for("a", "b")
@pytest.mark.parametrize(
"result",
[
{"candidate": {"repository": "org/é"}, "network": "org/root"},
{"rejected": "used_pairs"},
{"rejected": "family", "network": "org/root"},
],
)
def test_resolution_roundtrip_idempotent_and_contract_replacement(result):
objects = FakeObjects()
bank = CorpusBank(objects, seed=42, prefix="custom")
advisory = "GHSA-test/advisory"
key = f"custom/42/resolved/{hashlib.sha256(advisory.encode()).hexdigest()}.json"
assert bank.advisory_key(advisory) == hashlib.sha256(advisory.encode()).hexdigest()
bank.bank_resolution(advisory, result, contract="old")
first = objects.objects.copy()
bank.bank_resolution(advisory, result, contract="old")
assert objects.objects == first and objects.calls.count("put") == 1
row = bank.resolved()[advisory]
assert row == {
"advisory": advisory,
"result": result,
"decision": "candidate" if "candidate" in result else "reject",
"reason": result.get("rejected"),
"resolution_contract": "old",
"seed": 42,
"ts": row["ts"],
}
assert list(objects.objects) == [key]
assert datetime.fromisoformat(row["ts"]).utcoffset().total_seconds() == 0
bank.bank_resolution(advisory, result, contract="v1")
assert bank.resolved()[advisory]["resolution_contract"] == "v1"
assert objects.calls.count("put") == 2
def test_advisory_snapshot_written_once_per_contract():
objects = FakeObjects()
bank = CorpusBank(objects, seed=7)
rows = [{"ghsa_id": "GHSA-é", "references": ["a", "b"]}]
assert bank.cached_advisories(contract="v1") is None
bank.cache_advisories(rows, contract="v1")
original = objects.objects.copy()
assert bank.cached_advisories(contract="v1") == rows
assert bank.cached_advisories(contract="v2") is None
bank.cache_advisories([], contract="v1")
assert objects.objects == original and objects.calls.count("put") == 1
body = json.loads(objects.objects["unseen/7/advisories/list.json"])
assert body == {"advisories": rows, "advisories_contract": "v1", "seed": 7, "ts": body["ts"]}
bank.cache_advisories([], contract="v2")
assert bank.cached_advisories(contract="v2") == []
assert bank.cached_advisories(contract="v1") is None
@pytest.mark.parametrize("method", ["resolution", "advisories", "repository"])
@pytest.mark.parametrize("operation", ["get", "put"])
@pytest.mark.parametrize("failures", [1, 2])
def test_new_bank_writes_retry_once(method, operation, failures):
objects = FakeObjects()
bank = CorpusBank(objects, seed=7)
objects.failures[operation] = failures
def write():
if method == "resolution":
bank.bank_resolution("GHSA-a", {"rejected": "family"}, contract="v1")
elif method == "advisories":
bank.cache_advisories([], contract="v1")
else:
bank.bank_repository("org/repo", None, contract="v1")
if failures == 2:
with pytest.raises(OSError):
write()
assert objects.calls.count(operation) == 2
else:
write()
assert len(objects.objects) == 1
assert objects.failures[operation] == 0
@pytest.mark.parametrize("method", ["resolved", "cached_advisories", "repositories"])
@pytest.mark.parametrize("failures", [1, 2])
def test_new_bank_reads_retry_once(method, failures):
objects = FakeObjects()
bank = CorpusBank(objects, seed=7)
bank.bank_resolution("GHSA-a", {"rejected": "family"}, contract="v1")
bank.cache_advisories([], contract="v1")
bank.bank_repository("org/old", {"full_name": "org/new"}, contract="v1")
bank.bank_repository("org/missing", None, contract="old")
objects.failures["get"] = failures
read = getattr(bank, method)
kwargs = {} if method == "resolved" else {"contract": "v1"}
if failures == 2:
with pytest.raises(OSError):
read(**kwargs)
else:
result = read(**kwargs)
assert result is not None
if method == "repositories":
assert result == {"org/old": {"full_name": "org/new"}}