Files
computer-playbook/utils/cache/__init__.py
T
kevinveenbirkenbachandClaude Opus 5 064f2fc4c7 fix(cache): drop unsound id-keyed fingerprint memo
_fingerprint_mapping memoised digests under id(obj) while holding no
reference to obj, so CPython reused the address of a freed mapping and
served its digest for an unrelated live one. Measured 398/400 wrong
digests for short-lived mappings; in-place mutation was invisible and
the domain-index collision guard could be bypassed entirely.

Two unit tests pinned that behaviour as the contract —
test_id_cache_short_circuits_recompute asserted that mutating a dict in
place leaves its fingerprint unchanged — so the defect shipped green.
Both now assert the opposite.

build_domain_index rebuilt the whole reverse index on every lookup,
scanning every application's domains and aliases to answer one key. It
is now memoised on that fingerprint, which is only sound because of the
fix above. 4x on a 100-app payload.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-06 10:27:04 +02:00

58 lines
2.2 KiB
Python

"""In-process caches for runtime data (applications, users, domains).
Domain modules:
- ``utils.cache.base`` — cross-cutting helpers (constants,
deep-merge, signatures, render machinery).
- ``utils.cache.applications`` — variants + ``get_merged_applications``
(ansible-free at import time, runner-host friendly).
- ``utils.cache.users`` — ``get_user_defaults``, ``get_merged_users``.
- ``utils.cache.domains`` — ``get_merged_domains``.
- ``utils.cache.yaml`` — process-wide ``load_yaml`` /
``load_yaml_any`` parser cache shared by everything that reads YAML
off-disk.
- ``utils.cache.files`` — process-wide project-tree walk +
file-content cache (``read_text``, ``iter_project_files``,
``iter_non_ignored_files``).
- ``utils.cache.gitignore`` — cached ``.gitignore`` pattern loader
+ path matcher used by lint/integration tests that need a
"files git would track" view without spawning git.
Use the domain module directly. The package-level
``_reset_cache_for_tests`` clears every domain's caches plus the shared
fingerprint memo in one call — preferred over hand-rolling N
``_reset()`` calls per test fixture.
``PROJECT_ROOT`` is the single source of truth for the repository root.
Every callsite that needs it — CLI tools, plugins, tests — MUST import
this value rather than re-derive it via ``Path(__file__).parents[N]`` or
``os.pardir`` chains; ``tests/lint/repository/test_project_root_import.py``
enforces that.
"""
from __future__ import annotations
from pathlib import Path
PROJECT_ROOT: Path = Path(__file__).resolve().parents[2]
ROLES_DIR: Path = PROJECT_ROOT / "roles"
def _reset_cache_for_tests() -> None:
"""Orchestrate per-domain cache resets plus the shared fingerprint
memo. Test fixtures rely on a single entry point so they can stay
agnostic of how the cache is partitioned across modules."""
from utils.domains import application_domain_index
from . import applications, base, domains, files, gitignore, users
from . import yaml as _yaml_cache
application_domain_index._reset()
applications._reset()
users._reset()
domains._reset()
base._reset()
_yaml_cache._reset()
files._reset()
gitignore._reset()