Two changes the operator asked for after noticing custom-html-tiny PR #6 has no
backup/restore or functional coverage:
1) Intentional-vs-accidental N/A. A recipe can now declare
recipe_meta.EXPECTED_NA = {rung: reason} to mark a tier as deliberately not
applicable (e.g. a stateless static server has no backup surface). N/A still
caps the level — the harness never claims a rung it did not verify — but the
run is now annotated 'intentional · <reason>' instead of being indistinguishable
from a forgotten test. An *undeclared* N/A on a gap-sensitive rung
(backup_restore, functional) is surfaced as a 'possible coverage gap', and a
stale EXPECTED_NA (declared N/A but actually exercised) is surfaced too. All
non-blocking (R7): results.json gains level_cap_intent + an block, the
summary card shows the clause, and the CI log prints the gap/stale warnings.
(results.classify_na/cap_intent are pure + unit-tested; level.py untouched.)
custom-html-tiny declares backup_restore intentionally N/A.
2) custom-html-tiny functional test: writes a random file into the served content
volume (via the volume mountpoint, like install_steps.sh, since the SWS image
is shell-less), asserts exact-byte round-trip + a real 404 on a missing path —
proving the static-web-server actually serves the volume, not a 200-everything
fallback.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>