How a solo archival system gets built in public
Superseded by ADR 0004 — Two-layer lifecycle model and ADR 0005 — Evidence-based lifecycle transitions. Retained as historical record of the implementation plan and TDD approach used to deliver the lifecycle corrections.
For Hermes: implement this with strict TDD. Add the failing tests first, watch them fail, then make the smallest code changes needed.
Goal: tighten three semantics problems surfaced by live pressure-testing: status-only misclassification of withdrawn roles, manifest/projection drift when closed roles reappear in search, and over-strong promotion on expired_sid_redirect.
Architecture: keep the existing two-layer status / lifecycle_status model for now, but make downstream code prefer lifecycle_status where the distinction matters. Preserve run manifests as immutable corpus-membership evidence, and make the mutable current projection track observed reappearance in search. Treat expired_sid_redirect as medium-confidence evidence, not hard withdrawal confirmation.
Files likely touched:
tests/test_tiering.pytests/test_run_module.pytests/test_lifecycle.pytests/test_repair_mode.pycsj/tiering.pycsj/run.pycsj/lifecycle.pydocs/current-status.mddocs/maintainer-guide.mdSKILL.mdlifecycle_status='withdrawn_confirmed' must outrank broad status='active'.withdrawn_confirmed records must not be selected by collect_refresh_refs().expired_sid_redirect remains missing_unconfirmed even after the repeated-missing threshold.expired_sid_redirect evidence is not enough to promote to withdrawn_confirmed.Verification:
csj/tiering.py, classify closed / withdrawn_confirmed before broad status='active' handling.csj/run.py, make collect_refresh_refs() key off resolved lifecycle state rather than broad status.csj/run.py, make refresh_existing_listing_state() reopen any non-active record seen in search, including closed.csj/lifecycle.py, stop promoting expired_sid_redirect to withdrawn_confirmed; keep it as missing_unconfirmed with medium/low confidence.csj/lifecycle.py, harden evaluate_repair_action() so only strong verification (http_404, withdrawn_text, or other future high-confidence evidence) can produce withdrawn_confirmed.scripts/collector.py --help.scripts/collector.py --repair-lifecycle --dry-run.lifecycle_status when distinguishing live vs missing/withdrawnexpired_sid_redirect is medium-confidence evidence onlyAfter this follow-up:
expired_sid_redirect stays a warning/evidence signal, not hard withdrawal confirmation