feat(listing): define how a description image resolves after ingest - #47
Merged
Merged
Conversation
§4.1 holds a description image to §5's media-path grammar, so a remote image is unspellable, and it binds the renderer as well as the author. Both are right. Together they left no third spelling: `icon` and `screenshots[].file` are fields, so an ingesting storefront reads them, uploads the bytes, and rewrites each to whatever it serves them from — but a description image is inside a Markdown blob. Nothing enumerates it, so nothing rewrites it, and `media/overview.png` resolves against an item root that exists in the authoring repository and nowhere downstream. A conforming image was un-renderable; a renderable one was ERR_IMAGE_NOT_LOCAL. §5.1 names the mapping every such consumer already holds — the published media set — and says a description image is entitled to it. The mapping is keyed on the whole media path rather than on the basename, which is what lets LIST-MEDIA-003 keep stopping at the screenshot gallery and lets §4.1 keep exempting a description image from it. §5's rationale for that rule said "published assets are addressed by basename", which would have contradicted the key chosen here; it now describes the gallery it governs. The clause a consumer will feel is the last one: an image that will not resolve is omitted, never made remote. Substituting a reachable URL for a broken one is the *helpful* failure mode, which is why it needs saying — a storefront doing it is repairing a page, not attacking anyone, and it reopens the beacon §4.1 closed on a page the author no longer controls. §10 carries it too, because that is where it is a security rule rather than a resolution one. LIST-MEDIA-004 constrains an implementation's output, so no fixture can observe it — the limit ADR 0004 already recorded for §4.1's renderer clause. It goes in the runner's UNPINNED list with that reason rather than looking covered, and §9 records it as a debt of a kind that rejecting nothing will close. No ADR: this closes a gap inside ADR 0004 §2's image rule rather than deciding new policy, and that section's table reads true afterwards. No previously valid document is affected — §5.1 constrains consumers, not documents. Refs #44 Signed-off-by: Justin Merrell <merrelljustin@gmail.com>
§4.1 has said since ADR 0004 that §5's semantic rules reach a description image — that it MUST resolve to a file that exists and MUST lie inside the item root, reported at /spec/description — and tools/src/semantic.ts has implemented it: mediaPaths() collects every well-formed description image destination alongside icon and screenshots[].file. Nothing tested it. All five description cases are case.yaml cases, which get no itemRoot, so checkMediaOnDisk never ran against one. LIST-MEDIA-001 and LIST-MEDIA-002 looked covered because cases 004 and 005 exercise them through a screenshot and an icon; coverage is per-requirement, not per pointer, so the corpus was green over a rule it never reached. Three tree cases close that: 013 a description image the item does not ship → ERR_MEDIA_NOT_FOUND 014 one escaping the root by symlink → ERR_PATH_ESCAPE 015 one the item ships → passes all reported at /spec/description. 015 references the same file as its screenshot on purpose: one asset named twice is one entry in the published media set, and basename uniqueness has nothing to say about it. These pass on arrival. They are a corpus backfill rather than the usual red-then-green sequence, because the behaviour was already implemented and only the fixture was missing. 013 was checked against a vacuous pass by shipping media/diagram.png into its tree and confirming the runner then reports it as validating cleanly. docs/traceability.md is regenerated: LIST-MEDIA-001/002 and LIST-MD-003 gain the new cases, and LIST-MEDIA-004 appears with no case, as UNPINNED says it should. Refs #44 Signed-off-by: Justin Merrell <merrelljustin@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changes
Listing §5 gains §5.1 Resolution after ingest, which names the mapping an ingesting consumer already holds — the published media set — and obliges it to resolve a
descriptionimage through the same mapping it applies toiconandscreenshots[].file(LIST-MEDIA-004). The mapping is keyed on the whole media path rather than on the basename, which is what letsLIST-MEDIA-003keep stopping at the screenshot gallery and lets §4.1 keep exempting a description image from it; §5's rationale for that rule said "published assets are addressed by basename" and now describes the gallery it actually governs. A consumer that cannot resolve an image MUST omit it rather than substitute a remote URL — carried in §10 as well, because there it is a security rule rather than a resolution one. Three conformance cases pin the half of this the corpus can observe: a description image against the filesystem, which §4.1 has required since ADR 0004 and nothing tested.Why
Closes #44.
§4.1 holds a description image to §5's media-path grammar and also binds the renderer. Both are right, and together they left no third spelling.
iconandscreenshots[].fileare fields, so an ingesting storefront reads them, uploads the bytes, and rewrites each to a CDN URL. A description image is inside a Markdown blob — nothing enumerates it, so nothing rewrites it, andmedia/overview.pngresolves against an item root that exists in the authoring repository and nowhere downstream. A conforming image was un-renderable; a renderable one wasERR_IMAGE_NOT_LOCAL. Left unstated, the resolution gets invented once per consumer and in private, which is the objection ADR 0004 opens with.This is issue option 1. Option 3 turned out to already hold in code —
mediaPaths()has always collected well-formed description images — but no fixture reached it: all five description cases arecase.yamlcases, which get noitemRoot, socheckMediaOnDisknever ran against one.LIST-MEDIA-001/002looked covered only because cases 004 and 005 exercise them through a screenshot and an icon. That gap is closed here.No ADR. This closes a gap inside ADR 0004 §2's image rule rather than deciding new policy, and that section's table reads true afterwards — the same test
e5a3f85(#45) applied.Compatibility
No previously valid document is affected: §5.1 constrains consumers, not documents.
LIST-MEDIA-004carries no diagnostic and no schema change;schemas/src/,schemas/dist/, andcatalog.jsonare all untouched.Checklist
task checkpasses locally (ci:test,ci:lint,check:typesall exit 0)schemas/dist/regenerated withtask bundleand committed (never edited by hand) — no schema change;check:driftconfirms the bundles are byte-identicalclausespec.md— schemadescriptions are explanatory, not normativegit commit -s)Fixtures
semantic/013-description-image-absentmedia/diagram.png; the item does not ship itERR_MEDIA_NOT_FOUND@/spec/descriptionsemantic/014-description-image-escaping-the-itemmetadata.jsonper ADR 0002 §3ERR_PATH_ESCAPE@/spec/descriptionsemantic/015-description-image-resolvesThese pass on arrival. They are a corpus backfill rather than the usual red-then-green sequence, because the behaviour was already implemented and only the fixture was missing — worth saying plainly rather than implying CONTRIBUTING rule 2 was exercised. 013 was checked against a vacuous pass by shipping
media/diagram.pnginto its tree and confirming the runner then reports it as validating cleanly.The one rule with no case behind it
LIST-MEDIA-004constrains an implementation's output, so no fixture can observe it — the limit ADR 0004 already recorded for §4.1's renderer clause. It goes in the runner'sUNPINNEDlist with that reason rather than looking covered, and §9 records it as a debt of a kind that rejecting nothing will close. Removing theUNPINNEDentry was verified to failcheck:conformancewithLIST-MEDIA-004 is declared … but no case cites it, so the ID is genuinely indexed.Note for
musher-dev/platformThe storefront's current behaviour — rendering alt text in place of the
img— is conforming under §5.1 and stays conforming. What the section adds is that once the platform resolves description images through its existing media mapping, it MUST NOT fall back to a remote URL for one it cannot resolve.🤖 Generated with Claude Code