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.
-
RED — wrote
tests/unit/indices/test_freshness_registry.py(11 tests covering AC-1 through AC-14). Firstpytestrun failed at collection time withImportError: cannot import name 'FreshnessRegistryError' from 'codegenie.errors'— confirms no shadow registration on master. -
TEST BUG — the story-sketch RED block for AC-3 used decorator syntax inside
with pytest.raises(...)to registercheck_b; when the decorator raises, the local namecheck_bnever binds, so the post-raisesassertionf".{check_b.__qualname__}" in msgraisesUnboundLocalError. Fix: definecheck_bas a regular function above thewithblock, then callreg.register(name)(check_b)inside it. Decoration-time semantics preserved (qualname includes the test function as enclosing scope). See L11 below. -
GREEN — created
src/codegenie/indices/registry.pywithFreshnessRegistry,register_index_freshness_check,default_freshness_registry, theFreshnessChecktype alias, and the test-onlyunregister_for_tests. AppendedFreshnessRegistryErrortosrc/codegenie/errors.py(markers-only, mirrorsProbeError). Re-exported the registry surface fromsrc/codegenie/indices/__init__.py. 11 new tests went green. -
__all__closure tests — two pre-existing closure tests caught the additions and required updates: tests/unit/test_errors.py::test_all_closure_pins_public_surface— addedPHASE2_NEW = {"FreshnessRegistryError"}toEXPECTED_SUBCLASSESand added"indices"toDOCUMENTED_MODULE_SLUGS(the new marker docstring names theindices.registrymodule slug per the existing per-marker docstring convention).-
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 intest_freshness_registry.py::test_registry_public_surface_has_expected_names. -
Gates —
ruff 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);pytestfull suite (1674 passed, 3 deselected, 2 xfailed). The pre-existingtests/unit/indices/test_freshness.pymypyvar-annotatederrors are unrelated and not in CI scope (make typecheckismypy --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.pyis the 1st precedent;depgraph/registry.pyat 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
JSONValueredefinition. The signature carriesdict[str, object]per the AC-4 + Notes-for-implementer §JSONValue forward-reference clause; if Phase 0'scodegenie.output.sanitizerlater promotesJSONValueto 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 iteratingself._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).