Implement grade-0 (ghost) erasure checking per D-093

Real, enforced grade-0 semantics: a ghost value must never reach a
hardware sink. ghost fn and ghost let already parsed but were fully
discarded downstream (item_name skipped the keyword, GhostStmt fell
to StatementKind::Unsupported) — this finishes that existing dead end
per D-093's posture, rather than adding new surface area.

- strata-hir: ItemHeader.is_ghost (mirrors the unsafe_qual precedent
  from the CDC work), and a real GhostStmt lowering arm producing
  StatementKind::GhostLet instead of Unsupported.
- strata-check: a second one-bit taint pass (local_grade +
  ghost_tainted), structurally identical to mem_tainted/
  reject_if_tainted from the D-092 mem work. Unlike mem's taint, there
  is no legal crossing point for grade 0 — it's rejected unconditionally
  at every sink (register-update value, mem-write value, component
  output) via ghost_value_reaches_hardware_sink. An ordinary let that
  merely forwards a ghost-tainted value also taints and is excluded
  from the lowered artifact, so it can't create a dangling reference to
  a binding that (by design) never entered strata-check's `lets`.
  ghost fn calls are also caught via a syntax-tree walk, since general
  function calls have no checked-IR representation in this checker at
  all yet (a pre-existing gap, not introduced here).
- strata-arch-ir/strata-circt: no changes needed — grade-0 bindings
  are absent from lowered artifacts by construction, confirmed via
  real strata compile output (no trace of a ghost binding surviving
  to MLIR).

Verified end-to-end via the real CLI: ghost-let.strata checks clean
and compiles with no ghost trace in the output MLIR;
ghost-let-bug-reaches-hardware.strata is rejected by both check and
compile with a real diagnostic (correct span, judgment, repair
message).

Honest scope note: ghost fn calls nested inside if/block tail
expressions aren't specially walked by the syntax-tree fallback, since
such expressions already have no checked-artifact representation for
ghost-fn calls either way — falls back to the existing generic
UnsupportedExpression ineligibility rather than the new named
diagnostic. Narrow, pre-existing-shaped gap, not a soundness hole.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
04eb9cf6eeversecafe committed on 8/20/2026, 3:15:09 AMparent5fb0097
5 files changed+167-0