feat(adr0020): step 3 — per-profile reachability reports Verified
ADR 0020 step 3: profile manifest format + per-profile reports.
Two profiles seeded under docs/release-profiles/:
full.lisp — every capability the cold binary exposes
(capabilities
public-root
runtime-feature repl
runtime-feature compiler
runtime-feature image
runtime-feature diagnostics)
compiler-only.lisp — drops repl, image, diagnostics
(capabilities
public-root
runtime-feature compiler)
Cold-side change: ag-tree-shake-emit also writes
build/cold-edge-graph.sexp — one `(CALLER CALLEE CLASS)' triple
per line, wrapped in `(EDGES ...)'. SYNTHETIC-INSTALL-REFERENCE
edges are excluded (never traversed by any reachability gate;
omitting them halves the file size). ~5937 edges, ~298 KiB on
cold-of-cold.
The per-profile BFS runs in the ert test
(funcl-cold-profile-reachability), not in cold itself. Rationale:
the test consumes both the capability tag manifest (step 2) and
the edge graph; doing the BFS in elisp avoids requiring cold to
parse profile manifests at compile time. When step 4 needs a
`--profile NAME' CLI flag for gated elimination, cold can grow
profile-parsing then.
Test:
Each profile's reachable set =
BFS(roots = tagged_with(cap ∈ profile.capabilities)
∪ profile.retained,
edges = build/cold-edge-graph.sexp)
Asserts:
- main ∈ both (every profile has main as public-root).
- ag-cli-compile-source ∈ both (compiler capability).
- |co-reach| ≤ |full-reach| (monotone in roots).
- Reports counts so the eventual step-4 delta is measurable.
Honest finding the test surfaces:
full : 1224 defuns
compiler-only : 1224 defuns (100.0% of full)
Both profiles reach the SAME set today, because main is
tagged public-root and main itself dispatches on argv to every
CLI entry point (image-save, load-warm, REPL, compile, ...).
BFS from main therefore reaches every reachable defun
regardless of profile.
This isn't a bug — it's the analysis revealing that
capability slicing as currently framed can't separate
runtime-dispatch branches. Step 4 (the next ADR) needs:
(a) a per-profile main refactor (different dispatcher entries
per profile build), OR
(b) finer-grained tagging that splits main's dispatch into
per-feature handler functions, where BFS reaches a
branch only when its feature's capability is in the
profile.
ADR 0020 step 4 was originally framed as gated elimination via
`--strict-link --profile NAME'. Today's finding shifts the
problem statement: the elimination mechanism is straightforward
once the slicing actually carves out per-profile differences.
Out of scope for ADR 0020; gets its own ADR.
Verified:
- 199/199 GREEN
- Profile reachability test runs in ~11s on cold-of-cold
- Edge graph is 297892 bytes (5937 retained edges); ~300 KiB
is fine for periodic CI emit, more than acceptable for
interactive review
Verified:
- 199/199 GREEN