From 0e6225925f55d988f1ae5eb368e5feac95682c9e Mon Sep 17 00:00:00 2001 From: Gabriel Spadon Date: Sat, 5 Sep 2026 08:20:19 -0300 Subject: [PATCH] fix: derive refresh test freshness epochs from the clock RefreshEngine.run compares the bound discovery policy epoch against its own wall clock, and the ledger's commit_initial_round then requires the inventory authority to agree with that bound policy. Four tests in test_refresh_discovery.py wrote the literal month they were authored in, so they agreed with the engine for exactly one calendar month. On 2026-09-01 the clock rolled over and every one of them began raising "inventory authority conflicts with bound discovery policy" on all five interpreters at once, leaving the Required CI aggregate red and every open pull request unmergeable through no fault of its own. Both engine-driving test modules now derive the epoch the same way the engine does, and the staleness test builds its rejected month as an explicit offset from today rather than naming one. Two guard tests were passing on the right status for the wrong reason. test_generation_start_binds_discovery_preflight_before_inventory_send was tripping the epoch mismatch instead of the missing s2 credential it exists to prove, so both it and the staleness test now pin the exact detail string their guard must return. A new contract test fails on any double-quoted YYYY-MM literal in a test_refresh_*.py module that drives RefreshEngine, so this cannot be reintroduced silently. Single-quoted months inside the SQL of test_refresh_corpus.py are deliberate drift-guard mismatches and stay. Verified against a simulated 2027-02 clock, and the full suite runs 2077 passed, 2 skipped, 7 deselected with ruff and mypy clean. --- tests/test_refresh_discovery.py | 16 +++++++----- tests/test_refresh_engine.py | 44 ++++++++++++++++++++++++++++++--- 2 files changed, 50 insertions(+), 10 deletions(-) diff --git a/tests/test_refresh_discovery.py b/tests/test_refresh_discovery.py index 29d764c9..a540d3c9 100644 --- a/tests/test_refresh_discovery.py +++ b/tests/test_refresh_discovery.py @@ -48,6 +48,10 @@ from citeforge.refresh.types import GenerationSpec, TaskDisposition NOW = datetime(2026, 8, 12, 12, tzinfo=timezone.utc) +# RefreshEngine.run compares the bound discovery epoch against its own +# wall clock, so a literal month here is a time bomb that detonates in the +# next calendar month. Derive it exactly as citeforge/refresh/engine.py does. +CURRENT_EPOCH = datetime.now(timezone.utc).strftime("%Y-%m") _GIT = shutil.which("git") or "git" @@ -94,7 +98,7 @@ def _real_corpus_authority( def _policy(*, max_scholar_pages: int = 10, max_html_probe_waves: int = 8) -> DiscoveryPolicy: return DiscoveryPolicy( - freshness_epoch="2026-08", + freshness_epoch=CURRENT_EPOCH, adapter_versions={ "arxiv": "1", "crossref": "1", @@ -215,7 +219,7 @@ def test_known_doi_wave_adopts_exact_task5b_csl_identity() -> None: {"doi": "10.1234/existing"}, ("metadata",), "1", - "2026-08", + CURRENT_EPOCH, "doi", ) existing = TaskSpec("author-ada", "pub-one", "doi_csl", "csl_lookup", existing_request) @@ -888,7 +892,7 @@ def send_once(_operation: SendOperation) -> requests.Response: ledger.bind_discovery_policy(policy, DiscoveryCredentials()) result = RefreshEngine( ledger, - InventoryPolicy(2020, 1000, 10, s2_adapter_version="2", freshness_epoch="2026-08"), + InventoryPolicy(2020, 1000, 10, s2_adapter_version="2", freshness_epoch=CURRENT_EPOCH), LedgerTransport(ledger, send_once=send_once), ).run(spec, RefreshCredentials(serpapi_key="wire-only"), lambda: False) assert result.status.value == "continuation", result.detail @@ -955,7 +959,7 @@ def send_once(_operation: SendOperation) -> requests.Response: ledger.bind_discovery_policy(policy, credentials) result = RefreshEngine( ledger, - InventoryPolicy(2020, 1000, 10, s2_adapter_version="2", freshness_epoch="2026-08"), + InventoryPolicy(2020, 1000, 10, s2_adapter_version="2", freshness_epoch=CURRENT_EPOCH), LedgerTransport(ledger, send_once=send_once), ).run(spec, RefreshCredentials(serpapi_key="wire-only"), lambda: False) assert result.status.value == "continuation" @@ -1165,7 +1169,7 @@ def send_empty(_operation: SendOperation) -> requests.Response: ledger.bind_discovery_policy(policy, DiscoveryCredentials()) result = RefreshEngine( ledger, - InventoryPolicy(2020, 1000, 10, s2_adapter_version="2", freshness_epoch="2026-08"), + InventoryPolicy(2020, 1000, 10, s2_adapter_version="2", freshness_epoch=CURRENT_EPOCH), LedgerTransport(ledger, send_once=send_empty), ).run(spec, RefreshCredentials(serpapi_key="wire-only"), lambda: False) assert result.status.value == "continuation" @@ -1304,7 +1308,7 @@ def send_empty(_operation: SendOperation) -> requests.Response: ledger.bind_discovery_policy(policy, DiscoveryCredentials(s2_key="wire-only")) result = RefreshEngine( ledger, - InventoryPolicy(2020, 1000, 10, s2_adapter_version="2", freshness_epoch="2026-08"), + InventoryPolicy(2020, 1000, 10, s2_adapter_version="2", freshness_epoch=CURRENT_EPOCH), LedgerTransport(ledger, send_once=send_empty), ).run(spec, RefreshCredentials(serpapi_key="wire-only"), lambda: False) assert result.status.value == "continuation" diff --git a/tests/test_refresh_engine.py b/tests/test_refresh_engine.py index 97d5527e..219221cb 100644 --- a/tests/test_refresh_engine.py +++ b/tests/test_refresh_engine.py @@ -2,6 +2,7 @@ import hashlib import json +import re import shutil import subprocess from dataclasses import replace @@ -26,6 +27,13 @@ from citeforge.refresh.transport import LedgerTransport, SendOperation from citeforge.refresh.types import GenerationSpec, GenerationState, RunStatus, TaskDisposition +# RefreshEngine.run compares the bound discovery epoch against its own +# wall clock, so a literal month here is a time bomb that detonates in the +# next calendar month. Derive it exactly as citeforge/refresh/engine.py does. +CURRENT_EPOCH = datetime.now(timezone.utc).strftime("%Y-%m") +# A month the engine can never consider current, for the staleness guard. +STALE_EPOCH = (datetime.now(timezone.utc).replace(day=1) - timedelta(days=1)).strftime("%Y-%m") + def _spec() -> GenerationSpec: census = AuthorCensus( @@ -438,7 +446,7 @@ def send_once(_operation: SendOperation) -> requests.Response: "serply": "1", } policy = DiscoveryPolicy( - "2026-08", + CURRENT_EPOCH, adapters, { "arxiv": 10, @@ -466,7 +474,7 @@ def send_once(_operation: SendOperation) -> requests.Response: with Ledger.open(tmp_path / "ledger.db") as ledger: result = RefreshEngine( ledger, - InventoryPolicy(2020, 1000, 10, s2_adapter_version="2", freshness_epoch="2026-08"), + InventoryPolicy(2020, 1000, 10, s2_adapter_version="2", freshness_epoch=CURRENT_EPOCH), LedgerTransport(ledger, send_once=send_once), ).run( full_spec, @@ -476,6 +484,7 @@ def send_once(_operation: SendOperation) -> requests.Response: discovery_credentials=DiscoveryCredentials(), ) assert result.status is RunStatus.INVALID_CONFIGURATION + assert result.detail == "required s2 discovery credential is unavailable" assert calls == 0 assert ledger.manifest().data["tasks"] == [] @@ -497,7 +506,7 @@ def test_generation_start_rejects_stale_discovery_epoch_before_binding(tmp_path: } spec = GenerationSpec(base.census, base.refresh_policy_version, {**adapters, "scholar": "1"}, base.base_commit) policy = DiscoveryPolicy( - "2026-07", + STALE_EPOCH, adapters, { "arxiv": 10, @@ -519,7 +528,7 @@ def test_generation_start_rejects_stale_discovery_epoch_before_binding(tmp_path: with Ledger.open(tmp_path / "ledger.db") as ledger: result = RefreshEngine( ledger, - InventoryPolicy(2020, 1000, 10, s2_adapter_version="2", freshness_epoch="2026-08"), + InventoryPolicy(2020, 1000, 10, s2_adapter_version="2", freshness_epoch=CURRENT_EPOCH), ).run( spec, RefreshCredentials(serpapi_key="inventory-secret"), @@ -528,6 +537,7 @@ def test_generation_start_rejects_stale_discovery_epoch_before_binding(tmp_path: discovery_credentials=DiscoveryCredentials(s2_key="wire-only"), ) assert result.status is RunStatus.INVALID_CONFIGURATION + assert result.detail == "discovery freshness does not match the code-owned inventory epoch" assert ledger._connection.execute("SELECT COUNT(*) FROM discovery_policy_authority").fetchone()[0] == 0 assert ledger.plan_status().revision == 0 @@ -1323,3 +1333,29 @@ def test_every_blocked_return_seals_a_checkpoint_first() -> None: if "_save_checkpoint" not in window and not entry_guard and not inside_blocker: unsealed.append(lines[index].strip()) assert not unsealed, f"blocked returns with no checkpoint seal above them: {unsealed}" + + +def test_no_refresh_test_pins_a_literal_freshness_epoch() -> None: + """No engine-driving test may hardcode the month it was written in. + + ``RefreshEngine.run`` compares the bound discovery epoch against its own + wall clock, so a literal ``"YYYY-MM"`` in a test that reaches that guard + passes for exactly one calendar month and then fails every run afterwards + on every interpreter at once. On 2026-09-01 four tests in + ``test_refresh_discovery.py`` detonated this way and blocked every open + pull request. Epochs are derived from ``datetime.now`` or built as an + explicit relative offset, never written down. + """ + # Double-quoted only. The single-quoted months in test_refresh_corpus.py + # live inside SQL that deliberately writes a mismatching epoch to trip a + # drift guard, and are not policy values the engine compares to its clock. + literal = re.compile(r'"\d{4}-(?:0[1-9]|1[0-2])"') + offenders = [] + for path in sorted((Path(__file__).parent).glob("test_refresh_*.py")): + source = path.read_text(encoding="utf-8") + if "RefreshEngine" not in source: + continue + for number, line in enumerate(source.splitlines(), start=1): + if literal.search(line) and "noqa: epoch-literal" not in line: + offenders.append(f"{path.name}:{number}: {line.strip()}") + assert not offenders, "literal freshness epochs in engine-driving tests: " + "; ".join(offenders)