Date: 2026-05-15 Issue: #105 (v3.7.3 release completeness follow-up) Status: design — ready for TDD implementation Scope: minimal — single tool, directory-scan, scalar provenance, ruamel round-trip. No batch mode, no resumable checkpoint, no structured backfill provenance.
v3.7.3 spec §3.2 R-L3-2-B says: "bibliography_agent computes contamination_signals at ingest time, not at audit time. Re-running the check post-hoc on existing entries is a separate batch operation (deferred to user invocation; not part of v3.7.3 finalizer)."
Pre-v3.7.3 passport entries lack contamination_signals. Without a backfill tool:
year >= 2024 AND venue ∈ {arXiv, bioRxiv, medRxiv, SSRN, Research Square, Preprints.org, ChemRxiv, EarthArXiv, OSF Preprints, TechRxiv} (10-server closed list)deep-research/references/semantic_scholar_api_protocol.md; OMIT field on obtained_via=manual (exemption); OMIT field on API degradation (absence ≠ negative confirmation)false when computed-and-clean; emit partial object when only one signal computable; never set semantic_scholar_unmatched: false on API degradationallOf branches already cover (a) manual + semantic_scholar_unmatched present = INVALID; (b) preprint_post_llm_inflection=true + year<2024 = INVALID| # | Question | Resolution | Rationale |
|---|---|---|---|
| Q1 | API rate-limit handling | backoff-only (existing SS protocol: 429→2s×3); no resumable checkpoint | YAGNI for minimal scope; large-corpus users add checkpoint in follow-up if needed |
| Q2 | Schema field naming | scalar contamination_signals_backfilled_at: ISO-8601 timestamp string |
issue body itself suggested "start with scalar"; strictly additive upgrade path to structured if v3.7.4 needs it |
| Q3 | Multi-passport batch mode | directory-scan only (no --input-list) |
YAGNI for minimal scope; user passport is typically 1 active session |
| Q4 | YAML library | ruamel.yaml | passport is user-owned file; preserve comments + key order (memory feedback_toml_duplicate_table_corruption discipline spirit) |
scripts/contamination_signals.py — pure functions:
PREPRINT_VENUES = frozenset({
"arXiv", "bioRxiv", "medRxiv", "SSRN", "Research Square",
"Preprints.org", "ChemRxiv", "EarthArXiv", "OSF Preprints", "TechRxiv",
})
def compute_preprint_signal(entry: Mapping) -> bool:
"""Signal 1 per spec §3.2 Vector 1."""
def compute_ss_unmatched_signal(entry, ss_client) -> bool | None:
"""Signal 2 per spec §3.2 Vector 2. Returns None on (a) manual exemption,
(b) API degradation; returns True/False otherwise."""
The two signals are independently testable. SS API call is dependency-injected so tests use a mock; production uses a real client.
scripts/migrate_literature_corpus_to_v3_7_3.py — CLI tool:
usage: migrate_literature_corpus_to_v3_7_3.py [--dry-run] [--verbose] <passport_or_dir>
Arguments:
passport_or_dir Path to a single passport YAML or a directory containing passports.
Directory scan finds *.yaml files non-recursively.
Options:
--dry-run Print proposed changes; write no files.
--verbose Per-entry logging.
Behavior:
ruamel.yaml (round-trip — preserve comments + key order)literature_corpus[]:contamination_signals already present → SKIP (idempotency)contamination_signals object per emission rulescontamination_signals_backfilled_at to current UTC ISO-8601 timestamp--dry-run, print diff and exit. Else, write back via ruamel.yaml round-trip.processed=N patched=M skipped=K errored=E, plus per-skip-category counts.SS API client: reuse existing references/semantic_scholar_api_protocol.md logic. Implementation: thin wrapper around requests with 429 backoff per protocol. Single shared client across the migration run (HTTP keepalive).
shared/contracts/passport/literature_corpus_entry.schema.json — add one optional field:
"contamination_signals_backfilled_at": {
"type": "string",
"format": "date-time",
"description": "v3.7.3 backfill provenance. ISO-8601 UTC timestamp set by scripts/migrate_literature_corpus_to_v3_7_3.py when contamination_signals was computed post-hoc rather than at ingest. Absence means signals were either computed at ingest (bibliography_agent v3.7.3+) or not computed at all. Backward compat: pre-v3.7.3 entries lack both contamination_signals and this field; ingest-time entries lack only this field."
}
Pure additive — existing v3.7.3 ingest-time entries (which don't carry this field) remain valid.
docs/migration/v3.7.3-contamination-signals-backfill.md — user-facing:
| File | Change kind |
|---|---|
scripts/contamination_signals.py |
new — Signal 1 + Signal 2 resolvers |
scripts/migrate_literature_corpus_to_v3_7_3.py |
new — CLI migration tool |
scripts/test_contamination_signals.py |
new — unit tests for resolvers |
scripts/test_migrate_literature_corpus_to_v3_7_3.py |
new — pytest for migration tool (mocks SS API) |
shared/contracts/passport/literature_corpus_entry.schema.json |
additive — new optional contamination_signals_backfilled_at field |
scripts/adapters/tests/test_literature_corpus_entry_schema.py |
extend — 2-3 tests for the new field (valid present / valid absent / invalid type) |
docs/migration/v3.7.3-contamination-signals-backfill.md |
new — user-facing migration guide |
requirements-dev.txt |
add ruamel.yaml>=0.18 |
CHANGELOG.md |
Unreleased entry under #105 |
| File | Reason |
|---|---|
deep-research/agents/bibliography_agent.md |
v3.7.3 spec frozen; ingest-time computation unchanged |
academic-pipeline/agents/pipeline_orchestrator_agent.md |
Finalizer behavior unchanged; migration is offline |
Existing scripts/adapters/* |
Adapters produce ingest-time entries; migration is downstream |
Per superpowers:test-driven-development:
Resolver tests (test_contamination_signals.py):
Migration tests (test_migrate_literature_corpus_to_v3_7_3.py):
obtained_via=manual → signals computed but semantic_scholar_unmatched omittedcontamination_signals): skipped, no re-computationSchema tests (extend test_literature_corpus_entry_schema.py):
contamination_signals_backfilled_at present + valid ISO-8601 → validadditionalProperties: false permission)--input-list batch mode (deferred until multi-corpus user emerges)_backfill_provenance: {at, version, by} object (upgrade path: scalar _backfilled_at → structured object remains backward-compatible if needed)docs/design/2026-05-12-ars-v3.7.3-claim-faithfulness-and-contaminated-source-spec.md §3.2 (Vector 1 + Vector 2 + R-L3-2-B)shared/contracts/passport/literature_corpus_entry.schema.json (existing contamination_signals + 4 allOf invariants)4cc880f — added contamination_signals to schema + bibliography_agent computationfeedback_toml_duplicate_table_corruption — round-trip parser principle (applied here via ruamel.yaml for YAML)