From aaa4f2d44868263098b9477e96abf3231ab0006c Mon Sep 17 00:00:00 2001 From: Matthew Cochran Date: Tue, 4 Aug 2026 18:33:50 -0400 Subject: [PATCH] feat(design): verify that referenced assets exist and are local MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Allowing local assets (#4) came with two rules and the pack could check neither. Both failed live, in the same run. BROKEN REFERENCES. The operator's brief named the artwork, but the filename list was lost to a copy/paste slip, so the generator invented plausible names from the story titles — art/dragon-under-stairs.png, art/gran-1953.png, art/tent-leaked.png, art/bramble-vet.png — and wrote eight tags pointing at nothing. The candidate parsed as HTML, passed verify.candidate-renderable, scored, and was carried toward the human as a legitimate direction. Two sibling candidates gave up on imagery entirely rather than guess. FOUR generations were spent before anyone noticed, and only because a person looked at the pixels — no gate in the pipeline could see it. NETWORK REFERENCES. "Renders with the network unplugged" is stated in both prompt layers and verified nowhere. A CDN stylesheet or a hosted webfont renders perfectly on the machine that made it and degrades silently everywhere else, including in a headless screenshotter. verify.assets-resolve scans src / srcset / href / poster and CSS url(...) in both inline `); + assert.equal(out.assets_ok, false); + assert.equal(out.network_count, 1); +}); + +// Network is the stricter rule and the one that fails on someone else's +// machine, so it names the reason when both are present. +test("network takes precedence over broken when both are present", () => { + const out = check(``); + assert.equal(out.reason, "NETWORK_ASSET_REFERENCE"); + assert.equal(out.missing_count, 1, "the broken one is still reported"); + assert.equal(out.network_count, 1); +}); + +// --- things that are not file references at all ------------------------------ + +test("data: URIs, anchors and mailto are ignored, not resolved", () => { + const out = check(` + + skip + mail`); + assert.equal(out.assets_ok, true); + assert.equal(out.checked, 0, "none of these are files to look for"); +}); + +test("an inline needs no assets and passes cleanly", () => { + const out = check(``); + assert.equal(out.assets_ok, true); +}); + +test("an unreadable artifact is a hard error, not a silent pass", () => { + assert.throws(() => + execFileSync("node", [CHECK, join(tmpdir(), "no-such-candidate.html")], { encoding: "utf8", stdio: "pipe" }), + ); +}); diff --git a/scripts-library/verify.assets-resolve.yaml b/scripts-library/verify.assets-resolve.yaml new file mode 100644 index 0000000..258ca0f --- /dev/null +++ b/scripts-library/verify.assets-resolve.yaml @@ -0,0 +1,118 @@ +# scripts-library/verify.assets-resolve.yaml +# +# Make the two asset rules DETERMINISTIC instead of merely instructed. +# +# Allowing local assets (#4) traded one failure mode for two, and the pack could +# detect neither: +# +# 1. A REFERENCE TO A FILE THAT IS NOT THERE. Observed live: the operator's +# brief named the artwork but a copy/paste slip dropped the filename list, +# so the generator invented plausible names from the story titles — +# `art/dragon-under-stairs.png`, `art/gran-1953.png` — and wrote eight +# tags pointing at nothing. The candidate parsed as HTML, passed +# verify.candidate-renderable, scored, and was carried toward the human as +# a legitimate direction. Four generations were spent before anyone noticed, +# and only because a human looked at the pixels. +# +# 2. A NETWORK REFERENCE. "Renders with the network unplugged" is stated in +# both prompt layers and checked by nothing. A CDN stylesheet or a Google +# Fonts link would render beautifully on the machine that made it and +# degrade silently anywhere else — including in the headless screenshotter. +# +# Both are the pack's own discipline applied to the newest surface: the agent's +# claim is not trusted, deterministic code re-opens the artifact, and a +# described-but-absent thing is a failure rather than a shrug. +# +# WHAT IS SCANNED: src / srcset / href / poster on any element, CSS url(...) in +# both inline