This commit is contained in:
commit
8f085a28d3
3220 files changed
+985495
No files matched your search
@@ -0,0 +1 @@
|
||||
{"body": "[Claude #3] Stage 3 code-edit IMP-46 \u2014 u3 fingerprint invalidation\n\nExecuting unit: u3\n\n\u25a0 unit_executed\n- id: u3\n- summary: Add fingerprint-based read-side invalidation to `src/phase_z2_ai_fallback/cache.py`. `save_proposal` already persisted the `fingerprints` dict verbatim (u2); u3 extends `read_proposal` with an optional `fingerprints` kwarg. When supplied, the stored `fingerprints` dict must equal the caller's dict by strict equality \u2014 any mismatch (single-axis drift, extra key, missing key, non-dict stored value, legacy entry with no `fingerprints` field) returns `None`. Default `fingerprints=None` performs no comparison (back-compat). Fingerprint *computation* stays outside `cache.py`: the module is a content-addressed *comparator*, not a *hasher* \u2014 preserves AI isolation and keeps the cache schema-agnostic.\n\n\u25a0 files_changed\n- src/phase_z2_ai_fallback/cache.py (modified, +47 / -10 \u2014 module docstring expanded with u3 contract; `read_proposal` gains keyword-only `fingerprints: dict | None = None`)\n- tests/phase_z2_ai_fallback/test_cache_invalidation.py (new, 21 test cases, 305 SLOC)\n\n\u25a0 diff_summary\n- `src/phase_z2_ai_fallback/cache.py`\n - Module docstring header renamed `u2` \u2192 `u2 + u3`; new section \"u3 invalidation contract (this module is a *comparator*, not a *computer*)\" documents:\n - `save_proposal` persists `fingerprints` verbatim \u2014 no fingerprint axis names are hardcoded in cache.py\n - `read_proposal(fingerprints=...)` enforces strict equality (NOT subset); default `None` skips comparison\n - Rationale for keeping computation outside: AI isolation + schema-agnostic (new fingerprint axes don't require editing cache.py)\n - `read_proposal` signature changed from `(key: str)` to `(key: str, *, fingerprints: dict | None = None)`. The new kwarg is keyword-only so positional callers cannot accidentally pass an axis-extension dict.\n - New input validation: `fingerprints is not None and not isinstance(fingerprints, dict)` \u2192 `TypeError` (symmetric with `save_proposal`'s fingerprints validation).\n - New comparison block placed AFTER file existence + JSON load + non-dict-root checks, but BEFORE the `proposal` validation: if `fingerprints is not None` and `stored = data.get(\"fingerprints\")` is not a dict OR `stored != fingerprints` \u2192 return `None`. This ordering means missing-file / corrupt-JSON precedence is preserved (no false hit through a phantom equality check) and a hand-corrupted `fingerprints` field (e.g. serialized as a list) is treated as an invalidation, not as a `TypeError`.\n - No new module-level imports. No new constants. No fingerprint computation, hashing primitives, axis enumeration, or Phase Z runtime references introduced.\n- `tests/phase_z2_ai_fallback/test_cache_invalidation.py` (new \u2014 21 cases, all pass)\n - Save-side: `test_save_persists_fingerprints_verbatim` re-asserts the u2 round-trip foundation u3 depends on.\n - Back-compat (read without kwarg):\n - `test_read_without_fingerprints_kwarg_returns_proposal` \u2014 legacy callers still hit.\n - `test_read_without_fingerprints_kwarg_ignores_stored_mismatch` \u2014 entry saved with `{\"contract_sha\": \"old\"}` still readable by legacy reader.\n - Matching path:\n - `test_read_with_matching_fingerprints_returns_proposal` \u2014 exact dict equality hits.\n - `test_read_with_empty_fingerprints_matches_empty_stored` \u2014 both sides `{}` is a valid match, not a special-case None.\n - Invalidation (the 11-case core):\n - Parametrized over the three declared shas: `test_read_invalidates_on_single_axis_drift[contract_sha|partial_sha|catalog_sha]` \u2014 each axis individually breaks the match.\n - `test_read_invalidates_when_caller_supplies_extra_key` \u2014 strict equality (not subset) is the locked semantic.\n - `test_read_invalidates_when_caller_supplies_subset` \u2014 same, from the other direction.\n - `test_read_invalidates_when_entry_saved_without_fingerprints` \u2014 caller demands proof of freshness; empty stored set is not \"compatible by default\".\n - `test_read_invalidates_when_stored_fingerprints_not_dict` \u2014 hand-corrupted payload (`fingerprints: [...]`) misses cleanly.\n - `test_read_invalidates_when_stored_fingerprints_field_missing` \u2014 legacy payload (no `fingerprints` field at all) invalidates under fingerprint-aware lookup.\n - Precedence locks:\n - `test_read_with_matching_fingerprints_still_loses_to_missing_file` \u2014 file-missing wins.\n - `test_read_with_matching_fingerprints_still_loses_to_corrupt_json` \u2014 JSON-corrupt wins.\n - Input validation symmetry: `test_read_rejects_non_dict_fingerprints[list|string|int]` (3 cases) \u2014 TypeError matches save-side surface.\n - Module isolation lock: `test_cache_module_has_no_fingerprint_computer` \u2014 public surface of `cache.py` is grepped for `hash`/`sha`/`fingerprint` substrings; passes iff zero functions Line truncated
|
||||
Reference in new issue
Block a user