Skip to content

Attempt log — S1-02 @register_index_freshness_check decorator-registry (02-ADR-0006)

Attempt 1 — 2026-05-15 — GREEN on first cycle

Strategy. Small kernel-tier registry primitive; single red→green→refactor cycle. The story was hardened by phase-story-validator to 14 ACs (HARDENED verdict) — the test sketch was load-bearing and copy-pasted nearly verbatim with one bugfix (see "Sequence" below).

Sequence.

  1. RED — wrote tests/unit/indices/test_freshness_registry.py (11 tests covering AC-1 through AC-14). First pytest run failed at collection time with ImportError: cannot import name 'FreshnessRegistryError' from 'codegenie.errors' — confirms no shadow registration on master.

  2. TEST BUG — the story-sketch RED block for AC-3 used decorator syntax inside with pytest.raises(...) to register check_b; when the decorator raises, the local name check_b never binds, so the post-raises assertion f".{check_b.__qualname__}" in msg raises UnboundLocalError. Fix: define check_b as a regular function above the with block, then call reg.register(name)(check_b) inside it. Decoration-time semantics preserved (qualname includes the test function as enclosing scope). See L11 below.

  3. GREEN — created src/codegenie/indices/registry.py with FreshnessRegistry, register_index_freshness_check, default_freshness_registry, the FreshnessCheck type alias, and the test-only unregister_for_tests. Appended FreshnessRegistryError to src/codegenie/errors.py (markers-only, mirrors ProbeError). Re-exported the registry surface from src/codegenie/indices/__init__.py. 11 new tests went green.

  4. __all__ closure tests — two pre-existing closure tests caught the additions and required updates:

  5. tests/unit/test_errors.py::test_all_closure_pins_public_surface — added PHASE2_NEW = {"FreshnessRegistryError"} to EXPECTED_SUBCLASSES and added "indices" to DOCUMENTED_MODULE_SLUGS (the new marker docstring names the indices.registry module slug per the existing per-marker docstring convention).
  6. tests/unit/indices/test_freshness.py::test_all_exports_full_variant_set — narrowed equality to subset (s1_01_names <= set(m.__all__)); the S1-02 registry surface is checked independently in test_freshness_registry.py::test_registry_public_surface_has_expected_names.

  7. Gatesruff check (one E501 in module docstring + 11 UP037 quoted-annotation removals, all auto-fixed except the docstring which I rewrapped manually); ruff format (idempotent after auto-fix); mypy --strict src/ (62 source files, no issues); pytest full suite (1674 passed, 3 deselected, 2 xfailed). The pre-existing tests/unit/indices/test_freshness.py mypy var-annotated errors are unrelated and not in CI scope (make typecheck is mypy --strict src/, not tests).

Refactor decisions.

  • No kernel KernelRegistry[K, V] extract. This is the 2nd registry of the family in this phase (probes/registry.py is the 1st precedent; depgraph/registry.py at S1-10 is the 3rd). The story Notes-for-implementer (Rule-of-Three observation) explicitly defers the kernel extract to whoever validates S1-10 with all three concrete consumers in hand. Pre-extracting now would violate Rule 2 (simplicity first).
  • No JSONValue redefinition. The signature carries dict[str, object] per the AC-4 + Notes-for-implementer §JSONValue forward-reference clause; if Phase 0's codegenie.output.sanitizer later promotes JSONValue to a public alias, this module rebinds by import, never by redefinition.
  • No exception wrapping in dispatch_all. AC-13 pins this with a runtime test; the registry intentionally lets check-function exceptions propagate so the coordinator at S4-01 catches at the right layer (Phase 0 coordinator-isolation precedent). No try/except in the comprehension.
  • Iteration order via dict.items() walk. Python ≥ 3.7 dict insertion-order semantics + the comprehension iterating self._checks.items() give registration-order dispatch for free. AC-14 pins the contract; do not sort, frozenset(), or otherwise re-permute.

Runtime evidence (Ralph Wiggum pass).

AC Evidence
1 tests/unit/indices/test_freshness_registry.py::test_registry_public_surface_has_expected_names + module file src/codegenie/indices/registry.py
2 test_decorator_returns_function_unchanged_on_local_registry + test_module_level_decorator_returns_function_unchanged
3 test_duplicate_name_rejected_at_registration_time (asserts both module.qualname dotted forms)
4 src/codegenie/indices/registry.py:55 (FreshnessCheck = Callable[[dict[str, object], str], "IndexFreshness"])
5 test_dispatch_invokes_check_with_empty_dict_when_slice_missing + test_dispatch_all_threads_head_unchanged_to_every_check
6 test_dispatch_invokes_check_with_empty_dict_when_slice_missing (registered name appears in result with slices={})
7 test_register_and_dispatch_routes_each_slice_to_its_own_check
8 RED step succeeded (ImportError at collection); GREEN step ships 11 passing tests
9 Test file uses local FreshnessRegistry() instances; the two singleton tests guard with default_freshness_registry.unregister_for_tests(name) in finally:
10 ruff check ✔ · ruff format --check ✔ · mypy --strict src/ ✔ · pytest tests/unit/indices/test_freshness_registry.py
11 test_dispatch_all_on_empty_registry_returns_empty_dict
12 test_dispatch_all_threads_head_unchanged_to_every_check (captured-list identity assertion)
13 test_dispatch_all_propagates_check_exception
14 test_dispatch_all_iteration_is_registration_order

Diff scope. src/codegenie/indices/registry.py (new), src/codegenie/indices/__init__.py (extended re-exports), src/codegenie/errors.py (appended marker), tests/unit/indices/test_freshness_registry.py (new), tests/unit/test_errors.py (PHASE2_NEW + indices slug), tests/unit/indices/test_freshness.py (subset-narrow on __all__ equality). No edits to freshness.py (location-deviation contract preserved); no edits to probes/registry.py (kernel-extract deferred per L-this); no edits to probes/layer_b/index_health.py (does not exist yet — S4-01).