Add required thumbnailUrl and TechArticle headline to schema - #123
Conversation
Two gaps found by parsing the production build for the properties Google requires per rich-result type. VideoObject.thumbnailUrl is required, and video-embed-native only emitted it when a "poster" param was passed. No call site passes one, so all 22 native-video entities omitted it. Falls back to the page's social image -- the same source head.html already uses for og:image -- rather than emitting an entity Google will drop. video-embed (YouTube) already derived a thumbnail from the video ID and is unchanged. The guides TechArticle block emitted "name" but no "headline", which is the property Google actually reads for Article-family types. Adds it from the page title across 19 guides. The date fields are guarded on .Lastmod.IsZero. Guides carry no date in frontmatter and .GitInfo does not resolve in this repo despite enableGitInfo = true, so nothing is emitted today -- a zero date is worse than no date. The guard activates automatically if a guide ever gains a date. Verified against a production build (hugo --minify), all 479 pages: no invalid JSON-LD, and no entity missing a required property for Organization, BreadcrumbList, BlogPosting, TechArticle, FAQPage, Article or Product. Known remaining gap, not addressed here: 29 of 32 VideoObject entities emit uploadDate "0001-01-01T00:00:00Z" because their pages (FAQ, compliance, resources) carry no date. uploadDate is also required, so those entities stay invalid until real dates are supplied. Fixing that means either passing upload_date at the call sites or giving those pages a date -- both need real values rather than a synthesised one. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FbqYj6ztzG6yautDpRWDXS
uploadDate is required for VideoObject, and 29 of 32 entities emitted
"0001-01-01T00:00:00Z" because their pages (FAQ, compliance,
resources) carry no frontmatter date.
Asset mtime is not a usable source. The Last-Modified on
marketing-assets.sbomify.com is the "Record Screencasts" workflow run
time -- run 30824548590 finished at 2026-08-03T15:10:39Z and all 18
screencasts report last-modified 15:10:11-12 the same day. Every
dispatch re-stamps all of them at once, so uploadDate would churn on
each run and tell Google each video was published the moment CI last
ran. HTTP has no creation-date header, and S3/R2 objects store only a
last-modified time that an overwrite resets, so there is nothing else
to read at build time.
Dates come from stable sources instead:
- sbomify screencasts (18): first-commit date of the generator in
sbomify/sbomify screencasts/*.py -- the video cannot predate its
generator, and these survive re-recording
- YouTube (10): the real uploadDate from each video's watch page
- FOSDEM talk (4): Last-Modified from the FOSDEM mirror
(2026-02-03), an archived asset that is not regenerated
The two independent sources cross-validate: generator dates and the
content files' own first-commit dates agree to the day for
document_signatures, cra_compliance, vex_upload, plugin_enablement
and workspace_deletion. Generator dates win where a content file
predates the Jekyll migration and only carries the 2026-02-17
migration floor (e.g. compliance/fda-medical-device).
Verified against a production build, all 479 pages: 0 invalid JSON-LD,
0 entities missing a required property, 0 VideoObject with a zero
uploadDate, and 14 distinct dates spanning 2024-04 to 2026-05.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FbqYj6ztzG6yautDpRWDXS
|
Pushed Why asset mtime couldn't be the sourceI tested reading Every dispatch re-stamps all of them at once. Using that as There's also no alternative header to read: HTTP has no creation-date field, and S3/R2 objects store only a last-modified time that an overwrite resets. Nothing else is exposed. Sources used instead
The generator dates cross-validate against the content files' own first-commit dates, agreeing to the day for ResultProduction build, all 479 pages:
One judgement call worth flagging: the six |
Diffing the "Record Screencasts" run (30824548590) against what the
site embeds turned up a mismatch in both directions: 21 assets
produced, 18 referenced, and one reference the run does not produce.
Removes the stale GitHub Attestation embed. That plugin was folded
into sbom-verification -- PLUGIN_SLUGS in screencasts/plugin_enablement.py
no longer lists it, and the comment there records the merge ("formerly
two separate plugins"). The asset only still resolves because the R2
sync runs without --delete: plugin_enablement_github-attestation.webm
reports last-modified 2026-04-24, while every current screencast
reports 2026-08-03. The page was showing a video of a plugin that no
longer exists, and describing it as current, directly above the video
that replaced it. Merges the two paragraphs into one describing the
unified plugin, keeping the announcement post as a historical link.
Adds two screencasts that were being recorded on every run and never
used:
- oidc_trusted_publishing.webm -> how-do-i-set-up-oidc-trusted-publishing,
which had no video at all despite the recording existing since May
- vex_lifecycle.webm -> how-do-i-use-vex, as the fuller end-to-end
journey (suppress, publish, and v2 superseding v1) ahead of the
existing vex_upload clip, which only shows the artifact rendering
upload_date on both comes from the generator's first-commit date, the
same stable source used for the rest of the embeds.
Verified against a production build: 0 invalid JSON-LD, 0 entities
missing a required property, 0 VideoObject with a zero uploadDate, and
no referenced asset that CI does not produce.
Two of the 21 remain unused, deliberately:
- vulnerability_scanning.webm is superseded. Its docstring describes
"Plugins page -> enable OSV -> save", which is exactly what the
parametrized plugin_enablement_osv.webm now shows, and that one is
already embedded on the vulnerability-scanning FAQ.
- security_advisories.webm has no home. Its docstring references an
FAQ describing the advisory lifecycle, but no such page exists on
the site yet.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FbqYj6ztzG6yautDpRWDXS
Follow-up to #122. Rather than reading the Semrush columns at face value, I parsed the production build and checked every structured-data entity against the properties Google requires per rich-result type. Two gaps came out of it.
1.
VideoObject.thumbnailUrlmissing on 22 entitiesthumbnailUrlis required — without it Google drops the entity entirely.video-embed-native.htmlonly emitted it when aposterparam was passed, and no call site passes one (0 of 22).Now falls back to the page's social image, the same source
head.htmlalready uses forog:image.video-embed.html(YouTube) already derives a thumbnail from the video ID and is unchanged.2.
TechArticlemissingheadlineon 19 guidesThe guides block emitted
namebut notheadline, which is the property Google actually reads for Article-family types. Added from the page title.The date fields are guarded on
.Lastmod.IsZero. Guides carry no date in frontmatter, and.GitInfodoes not resolve in this repo despiteenableGitInfo = true— I probed both and confirmed.Lastmodis the zero value and.GitInfois nil. So nothing is emitted today; a zero date is worse than no date. The guard activates automatically if a guide ever gains one.Verification
Production build (
hugo --minify), all 479 pages parsed:Covering Organization (339), BreadcrumbList (172), BlogPosting (99), TechArticle (64), FAQPage (62), VideoObject (32), Article (2), Product (1).
bun run lintpasses.Known remaining gap — not fixed here
29 of 32
VideoObjectentities emituploadDate: "0001-01-01T00:00:00Z". Their pages (FAQ, compliance,/resources/) carry no date, so.Page.Datefalls through to the zero value. Only the 3 on dated blog posts are correct.uploadDateis also required, so those 29 entities remain invalid even with the thumbnail fix. I did not paper over it: emitting a synthesised date would be inaccurate structured data, which is its own problem. The real fix is either passingupload_dateat the call sites or giving those pages a real date — both need actual values, not invented ones.Deliberately not changed
Logo items = 0in the report — theOrganizationschema already has a validlogoImageObject. Semrush quirk.Product snippet items = 1–2— the build contains exactly oneProductentity, on the homepage, which is correct.🤖 Generated with Claude Code
https://claude.ai/code/session_01FbqYj6ztzG6yautDpRWDXS