- Status: accepted · Date: 2026-07-22 · Deciders: Davor Runje
Context
research-init scaffolded a project’s .gitignore with .datasets-cache/, but
defendable_science/cli.py hardcoded the directory it actually wrote to as
.defendable-science/cache/datasets (the dataset content-addressed cache,
_DATASET_CACHE) and .defendable-science/cache/http (the literature HTTP cache,
inside _lit_client) — two independent literals that were never reconciled with
the scaffold. This surfaced during onboarding of a real consumer repo
(davorrunje/mononet): after dataset fetch/verify and a literature run,
cached bytes landed under .defendable-science/cache/…, which the scaffolded
.gitignore did not exclude (risk of committing large cached blobs), while
the gitignored .datasets-cache/ was dead — never written to, and misleading
about where the cache actually lives (defendable-science#65). This was a known open
item — sub-spec 4 §7 flagged “.datasets-cache/ vs .defendable-science/” as
needing reconciliation before this fix.
Decision drivers
- The path
research-initgitignores and the path the CLI writes to must be the same path, by construction — not by two hand-synced literals. - Consumers occasionally need the cache elsewhere (a scratch volume, a CI cache mount) — worth a one-line override, not a code change.
- Keep the change minimal and consistent with the existing config-loading
pattern (
_lit_clientalready readsliterature.mailtofrom.defendable-science/config.yml, with the same invalid-YAML/mapping error handling this ADR reuses). - No new dependency (stdlib + the existing
pyyaml-backedload_config); no Pydantic (ADR rejecting it stands).
Considered options
- Minimal: point
research-init’s scaffold at whatevercli.pyalready hardcodes (.defendable-science/cache/), leaving both sides as literals. - Config-driven (chosen): add a
cache_dir:key to.defendable-science/config.yml(default.defendable-science/cache/);cli.pyresolves the dataset cache (<cache_dir>/datasets) and the literature HTTP cache (<cache_dir>/http) from it;research-initgitignores exactly that configured path. - Separate
dataset.cache_dir/literature.cache_dirkeys per capability.
Decision
Option 2. A single top-levelcache_dir: key in .defendable-science/config.yml,
default .defendable-science/cache/ when absent. defendable_science/cli.py gains a
shared _cache_root(config) resolver (invalid-type → a clean typer.Exit(1),
matching the existing config-error convention, never a raw traceback) that both
_dataset_cache_dir() (<cache_dir>/datasets) and _lit_client()
(<cache_dir>/http) call — one source of truth for both capabilities’
sub-caches. research-init’s scaffold and prose now reference
.defendable-science/cache/ (via cache_dir:) instead of the dead
.datasets-cache/, and .gitignore excludes exactly that configured path.
Consequences
- A fresh
research-init+dataset fetch/verify+ aliteraturerun leaves no un-ignored cache files — scaffold and runtime cannot drift apart, because both read from the same config key. - One config key covers both capabilities’ caches; a consumer who wants the
cache elsewhere sets
cache_dir:once. .datasets-cache/is retired from the scaffold — not left as a second, inert path (the “both-and-neither” failure mode this ADR closes).- Reads
.defendable-science/config.ymlon everydataset fetch/verify/mirror/auditinvocation (previously these commands did not touch config at all); a malformedconfig.ymlnow surfaces as a clean exit 1 on those commands too, consistent with howliteraturecommands already behaved.
Rejected alternatives
- Minimal (point the scaffold at the hardcoded literal) — fixes today’s drift but leaves two independent literals that can drift again on the next edit; does not answer the acceptance criterion that scaffold and runtime cannot drift by construction.
- Per-capability cache keys — no present need for dataset and literature caches to live in different places, and it multiplies the scaffold/runtime reconciliation surface this ADR exists to shrink.
Links
defendable-science/defendable_science/cli.py (_cache_root, _dataset_cache_dir,
_lit_client); defendable-science/defendable_science/core/config.py (load_config);
skills/research-init/SKILL.md; docs/design/03-dataset.md §7 (the open item
this closes); defendable-science#65; ADR-0029 (the config-error-handling
convention this reuses).