Two defects in the resolve cascade that made cache and asset-reuse
misbehave in practice:
- Prompt matching was byte-exact and case-sensitive. findByPrompt and
cacheGet compared provenance.prompt with ===, so "Calm piano" and
"calm piano" re-searched and re-downloaded instead of reusing the
cached asset (same project and cross-project). Add normalizePrompt
(trim + lowercase + collapse whitespace) and key both lookups on it;
the raw prompt is still stored for audit.
- findExistingAsset matched with name.includes(intent) ||
intent.includes(name), which silently returned the WRONG local file:
intent "whoosh" grabbed a stray who.mp3, and a one-letter filename
matched every intent. Require a shared word token (>= 3 chars, minus
stopwords) so a false negative just falls through to a catalog search
rather than shipping the wrong asset.
Adds lib/adopt.test.mjs and extends manifest.test.mjs. Full media-use
suite green; verified e2e against the live catalog (case-variant
cross-project resolve now reuses; whoosh no longer grabs who.mp3).