docs(33): phase 33 re-verification after gap closure (human_needed)

This commit is contained in:
Jakub Zych
2026-11-05 00:55:53 +01:00
parent 6e99b1886a
commit 9c11b7064d

View File

@@ -1,8 +1,8 @@
---
phase: 33-seo-output-audit-and-content
verified: 2026-11-04T20:20:34Z
status: gaps_found
score: "Roadmap success criteria 2/4 verified (SC3, SC4); SC1 pending the release plus partial gap G1; SC2 partial gap G3. Plan must-haves 191/202 truths verified: 2 failed (G3), 5 pending the 33-14 Task 4 release, 3 not applicable by Jakub's 2026-11-04 decisions, 1 human"
verified: 2026-11-04T23:53:40Z
status: human_needed
score: "Roadmap success criteria 3/4 verified (SC2, SC3, SC4); SC1 verified at code level, its production part (grzybyfunkcjonalne.pl on the new format) pending the 33-14 Task 4 release. Plan must-haves 226/235 truths verified (202 of 33-01..33-14 plus 23 of 33-15 and 10 of 33-16): 0 failed, 5 pending the release, 3 superseded by Jakub's 2026-11-04 decisions, 1 human (visual)"
covered_files:
- .planning/phases/33-seo-output-audit-and-content/33-01-PLAN.md
- .planning/phases/33-seo-output-audit-and-content/33-01-SUMMARY.md
@@ -31,12 +31,18 @@ covered_files:
- .planning/phases/33-seo-output-audit-and-content/33-13-PLAN.md
- .planning/phases/33-seo-output-audit-and-content/33-13-SUMMARY.md
- .planning/phases/33-seo-output-audit-and-content/33-14-PLAN.md
- .planning/phases/33-seo-output-audit-and-content/33-15-PLAN.md
- .planning/phases/33-seo-output-audit-and-content/33-15-SUMMARY.md
- .planning/phases/33-seo-output-audit-and-content/33-16-PLAN.md
- .planning/phases/33-seo-output-audit-and-content/33-16-SUMMARY.md
- admin/openapi/admin.json
- admin/src/api/schema.d.ts
- docs/backend/field-types.md
- docs/database/attachments.md
- docs/database/casts-and-validation.md
- docs/plugins/menu-item-types.md
- go.mod
- go.sum
- modules/cabana/README.md
- modules/cabana/admin_openapi.go
- modules/cabana/backend_users_admin.go
@@ -55,60 +61,202 @@ covered_files:
- modules/phrasebook/lang/pl/validation.yaml
- modules/signpost/README.md
- scripts/check-phase33.sh
covered_digest: "v2:sha256:26f6b27b662a671e43464f8c68ec483f4712b0c68be80466e07a95b00c59654d"
covered_digest: "v2:sha256:9f0a6381baf9ae704a9f2f164e838bafb0b226dbe0b2b7461fe028436b0532d3"
behavior_unverified: 0
overrides_applied: 0
gaps:
- truth: "D-57 / SC2 hreflang: a page without a translation has no hreflang for that locale (33-06: 'a locale without a translation is absent'; 33-03: alternates 'for each translated locale')"
status: failed
reason: "The SEO and sitemap plugins drop only locales without a URL (sm-seo-plugin classes/alternates.go translatedLocales; sm-sitemap-plugin classes/rebuild.go). Journal, the main content type, gives every enabled translate locale a URL: classes/content_descriptors.go loops items[i].Alternates, which menuAlternates (classes/menu_item_types.go:686) builds for every enabled locale with fallback values. So on a site with an enabled locale that a record has no translation for, the record gets hreflang links and sitemap addresses for that locale, serving default-locale content under the other locale's URL (or a 404). 33-13 observed exactly this on grzyby (every post listed twice, /en/ addresses all 404) and fixed it by data only. The tests that prove D-57 (TestHreflangAlternates, TestAlternates) use fixture records with an empty URL, which the Journal never produces (fixture-only proof). deferred-items.md: status open."
artifacts:
- path: "../sm-journal-plugin/classes/content_descriptors.go"
issue: "post, category and tag descriptors emit a pact.ContentLocale for every enabled locale, translated or not"
- path: "../sm-journal-plugin/classes/menu_item_types.go"
issue: "menuAlternates: one alternate per enabled locale with fallback (Phase 17 PHP parity, used by the descriptors)"
missing:
- "Either the Journal descriptors list a locale only when the record has content in it (the default locale or a translation row), leaving the menu item alternates unchanged, or Jakub accepts the data-only mitigation (disable locales without content) with an override"
- "A test on the real Journal descriptor path (not a fixture) asserting no alternate for an untranslated enabled locale"
- truth: "SC1 / D-24: the sitemaps stay consistent with noindex (noindex pages leave the XML and HTML sitemaps)"
status: partial
reason: "Recorded by Jakub as a gap, not fixed in 33-14 (deferred-items.md, 33-12 'An additional address that the site serves noindex stays in the sitemap'). CMS-side noindex, canonical and redirect exclusion works and is tested (TestFilterExcludes, TestSitemapFilterTracer, TestFilterReasons), and Nuxt-side noindex pages never enter the registry because the route report leaves them out (vue-seo-layer lib/routes.ts staticRoutes). But a page whose noindex lives only in Nuxt (seo.noindexRoutes, requiresAuth, seo.robots) and that an editor or migration 202610280002 puts into Additional addresses stays in the sitemap with X-Robots-Tag noindex. The crawler reports it as sitemap_noindex, but routes it to seo_set_url_seo (classes/crawl/checks.go:355), which cannot fix a Nuxt-side noindex; the working fix is removing the address with sitemap_update_settings. The demo and grzyby deploy steps remove the migrated addresses by hand."
artifacts:
- path: "../vue-seo-layer/server/plugins/seo-route-report.ts"
issue: "the route report carries no noindex paths, so the CMS cannot know a Nuxt-only noindex"
- path: "../sm-seo-plugin/classes/sitemap_filter.go"
issue: "the filter judges the CMS's effective SEO only"
- path: "../sm-seo-plugin/classes/crawl/checks.go"
issue: "sitemap_noindex names seo_set_url_seo, which does not fix this case"
missing:
- "Route report sends the noindex static paths; sm-seo-plugin stores them and the filter excludes them (contract change across vue-seo-layer and sm-seo-plugin), or an accepted override for this release"
- "Optionally: sitemap_noindex on an additional address names sitemap_update_settings"
overrides_applied: 2
overrides:
- must_have: "G2 (33-08 drift check): no false journal-tag drift warning while golem15.journal.tag_pages is false"
reason: "Log noise only, no must-have violated. The fix needs a framework change: signpost.Activate refuses a menu item type whose DefaultPattern does not start with /, so sm-journal-plugin cannot drop the journal-tag pattern while tag pages are off (an additive pact field such as ContentDescriptor.Routable, or a signpost rule). Deferred to v0.6.2 (deferred-items.md, 33-12 entry and 'From 33-15')"
accepted_by: "Jakub Zych"
accepted_at: "2026-11-04"
- must_have: "D-106 / W5: framework v0.6.1 is strictly additive (patch); lagoon.Validate's stricter min/max length check ships in it"
reason: "Kept in v0.6.1 with an explicit 'Changed' entry in 33-14-notes-summercms-v0.6.1.md (a stored value longer than its max fails its next admin save) and a production SQL check (33-14-risk-max.sql) on every production database before the tag"
accepted_by: "Jakub Zych"
accepted_at: "2026-11-04"
re_verification:
previous_status: gaps_found
previous_verified: 2026-11-04T20:20:34Z
previous_commit: 97abc77
previous_score: "Roadmap SC 2/4; plan must-haves 191/202 (2 failed: G3)"
gaps_closed:
- "D-57 / SC2 hreflang: a page without a translation has no hreflang for that locale (G3; sm-journal-plugin 0a62090, TestContentDescriptorsTranslatedLocales on the real descriptor path)"
- "SC1 / D-24: the sitemaps stay consistent with noindex (G1; sm-seo-plugin 9de6b2e, sm-sitemap-plugin 6b67ab7, vue-seo-layer 7a26692; TestNoindexPathsTracer, TestSitemapNoindexTool, routes.test.ts, smoke routereport noindex=)"
gaps_remaining: []
regressions: []
human_verification:
- test: "Release decision on the gaps (escalation): G3 (Journal fallback hreflang, D-57) and G1 (Nuxt-only noindex address in the sitemap). For each, fix before the tags or accept with an override in this file's frontmatter (ready-to-paste blocks in 'Gaps Summary')."
expected: "Both resolved or overridden; then re-run verification, which should report human_needed for the release-time items below"
why_human: "Product and release decision: G3's fix changes a core plugin's descriptor output; G1 is a recorded contract gap Jakub chose not to fix in 33-14"
- test: "SC1 production, after 33-14 Task 4 (tags, pins, 33-13 patches, deploy to grzybyfunkcjonalne.pl per DEPLOY.md steps 1-12): run the 33-13-deploy-log.md checklist items 1-8 on https://grzybyfunkcjonalne.pl"
expected: "/sitemap.xml is a sitemapindex of sitemap-posts-1, -categories-1, -authors-1, -pages-1 and -images-1; posts file lists the 62 posts once each with lastmod and no /en/ or hreflang; /sitemap.xsl 404; a post has one canonical, one JSON-LD script, no hreflang=en, breadcrumbs; SEO › Pages and Settings › Sitemaps as listed; a crawl finishes without /en/ 404s or duplicates; GSC reads the index"
why_human: "Pending the release: the production deploy happens after the tags by Jakub's decision (33-13-deploy-log.md, after-tags; there is no uat any more). Not a failure."
- test: "Before the grzyby deploy, on the tagged build: scripts/urls.txt check of sm-grzybyfunkcjonalne-app (every public URL answers as before)"
expected: "Every urls.txt line passes (33-13 truth 1, prohibition D-32)"
why_human: "Pending the release: 33-13 proved it on local links only; it must be repeated on the tagged versions in Task 4"
- test: "33-14 Task 4 release steps, in the release-log order: framework v0.6.1, sm-journal-plugin v0.4.0, sm-sitemap-plugin v0.2.0, sm-seo-plugin v0.2.0 (its go.mod must require sm-sitemap-plugin v0.2.0: today it requires v0.1.2, which has no contract package, so GOWORK=off builds fail), vue-seo-layer v0.2.0, then the link:../vue-seo-layer swap in vue-journal-layer, vue-menu-layer and vue-faq-layer, `check-git-install.sh --check-links` over the four layers (must exit 0) and `check-git-install.sh <tag>`, then their tags; app pins and patches (33-12, 33-13, then 33-14-*-locale.patch on the demo); -tags nodynamic in the WebP-linking app builds with the 33-13 DEPLOY.md 'Binary' row corrected; recreate the 33-12 and 33-13 rehearsal databases from their source dumps before the rehearsal (migration 202610280002 was edited); changelog posts"
expected: "Every step logged in 33-release-log.md; release binaries statically linked; nothing tagged off the default branch"
why_human: "Pending the release by design: Task 4 runs after this verification and needs Jakub's go-ahead for tags, pushes and deploys"
- test: "W5 before the v0.6.1 tag: run 33-14-risk-max.sql (read-only) against every production database whose app takes v0.6.1"
expected: "No row longer than its rule's max, or each listed row shortened or accepted; results logged in 33-release-log.md"
why_human: "Condition of the accepted W5 override; needs production database access"
- test: "SC1 production, after the tags (33-13 after-tags decision): deploy grzybyfunkcjonalne.pl per its DEPLOY.md, first the scripts/urls.txt check on the tagged build, then the 33-13-deploy-log.md checklist items 1-8"
expected: "Every urls.txt line passes; /sitemap.xml is a sitemapindex of sitemap-posts-1, -categories-1, -authors-1, -pages-1 and -images-1; the posts file lists the 62 posts once each with lastmod and no /en/ or hreflang; /sitemap.xsl 404; a post has one canonical, one JSON-LD script, no hreflang=en, breadcrumbs; SEO › Pages and Settings › Sitemaps as listed; a crawl finishes without /en/ 404s or duplicates; GSC reads the index"
why_human: "Pending the release (production deploy after the tags), not a failure"
- test: "G1 on a deployed site (demo after its patches): check the server log line 'route report: N routes and M noindex paths sent', then that the migrated Additional address /subscriptions (noindex in Nuxt) is excluded with reason noindex on Settings › Sitemaps and absent from sitemap-pages-1.xml without the manual DEPLOY.md removal"
expected: "The noindex address leaves the XML and HTML sitemaps after the debounced rebuild; a crawl reports no sitemap_noindex for it"
why_human: "Proven by TestNoindexPathsTracer and the layer smoke on fixtures; the live round trip needs the released versions on a running site"
- test: "SC3 live workflow: run a crawl from SEO › Crawls on a deployed site, then from a real Claude client linked over MCP ask it to fix the reported issues with dry_run, review the preview, apply"
expected: "Every data issue names a tool the client can call; dry runs write nothing; applying fixes the data issues; fix: code issues are named as template work"
why_human: "External agent-driven workflow (33-VALIDATION.md manual-only row). TestCrawlerChecks and TestEveryAdminOperationHasTool prove the routing, not a live client session"
expected: "Every data issue names a tool the client can call (sitemap_update_settings for a noindex Additional address); dry runs write nothing; applying fixes the data issues; fix: code issues are named as template work"
why_human: "External agent-driven workflow (33-VALIDATION.md manual-only row)"
- test: "D-46: Google's Rich Results Test for a post, a category and an FAQ page from the admin's Rich Results link"
expected: "No errors for BlogPosting, CollectionPage/ItemList and FAQPage"
why_human: "External Google tool (33-VALIDATION.md manual-only row)"
- test: "Visual check after the deploys: grzyby home, /blog, a category, a post, /autor/admin on desktop and mobile (whole-card links, no 'more' link, post title markup, breadcrumbs, footer 'Mapa strony'); the demo on prefix_except_default (/pl/... addresses, LanguageSwitcher, /pl/mapa-strony); the new admin screens (SEO tab E8 to E10, Site audit, Crawls, Auto-links, Pages, Settings › Sitemaps) including the Blog Editor outline with long headings at 360px (33-02 planner assumption)"
- test: "Visual check after the deploys: grzyby home, /blog, a category, a post, /autor/admin on desktop and mobile; the demo on prefix_except_default (/pl/... addresses, LanguageSwitcher, /pl/mapa-strony and /sitemap each in its own locale, footer link to the visitor's locale); the new admin screens (SEO tab, Site audit, Crawls with the cancelling state, Auto-links, Pages, Settings › Sitemaps with the headings left empty) including the Blog Editor outline with long headings at 360px (33-02 J2)"
expected: "Layouts render as in 33-UI-SPEC.md; long outline headings wrap with a hanging indent and are not truncated"
why_human: "Visual rendering; element tests run under happy-dom without layout"
- test: "33-14 Task 4 release items not yet in any patch: add -tags nodynamic to the WebP-linking app builds (sm-demo-app, sm-grzybyfunkcjonalne-app, sm-summercmsio-app scripts/build.sh or summer.yaml build.tags) and correct the 33-13 patch's DEPLOY.md 'dynamically linked' row; apply 33-14-*-locale.patch on top of the 33-12 demo patches; vue-journal-layer check-git-install.sh against the tag; changelog posts"
expected: "Release builds are static (file bin/<app>: statically linked); every step logged in 33-release-log.md"
why_human: "Pending the release by design (Task 4 runs after this verification)"
---
# Phase 33: SEO output, audit and content Verification Report
**Phase Goal:** As a site editor and as an SEO specialist auditing a SummerCMS site, I want to have the rendered sites emit complete SEO output (sitemaps, structured data, pagination, hreflang, headings, breadcrumbs) from the CMS, and an audit that finds the remaining problems, so that sites are indexed correctly and issues can be fixed through MCP.
**Verified:** 2026-11-04T23:53:40Z
**Status:** human_needed
**Re-verification:** Yes, after gap closure (33-15, 33-16, quick 261104-xae). The initial verification (2026-11-04T20:20:34Z, gaps_found, commit 97abc77) is kept below.
**Mode:** mvp.
Repository HEADs checked (none tagged, nothing pushed; commits since the base tag): summercms.go 6e99b18 (84 since v0.6.0, planning docs included), sm-sitemap-plugin b2b3a1d (23 since v0.1.2), sm-seo-plugin c71b58e (37 since v0.1.1), sm-journal-plugin cf13c60 (21 since v0.3.1), vue-seo-layer 3868f3d (24 since v0.1.1), vue-journal-layer 761c7fc (16 since v0.7.1), vue-menu-layer a7602c9 (2 since v0.4.0), vue-faq-layer 5e5950a (4 since v0.4.0).
## Re-verification result (2026-11-04T23:53:40Z)
Both gaps are closed in code, each with a test on the production path. No earlier must-have regressed. The full gate passed again in this run. G2 and W5 are recorded as accepted overrides, following Jakub's decisions of 2026-11-04. What remains is the release (33-14 Task 4) and the checks that can only run after it, so the status is human_needed.
### User Flow Coverage
| Step | Expected | Evidence | Status |
|------|----------|----------|--------|
| Editor publishes content | Sitemap index with per-type files, real lastmod, stable numbering | as before; plus the rebuild lock (TestConcurrentRebuilds) and kept lastmods (TestRebuildKeepsMovedLastmod) | ✓ |
| Crawler/visitor opens a page | SSR head from the CMS, one JSON-LD @graph, hreflang for translated locales only, breadcrumbs, pagination | G3 closed: Journal descriptors give a URL only to locales with content (TestContentDescriptorsTranslatedLocales, Postgres); the SEO and sitemap plugins drop URL "" (alternates.go:72, rebuild.go:251) | ✓ |
| Nuxt-only noindex page | Leaves the XML and HTML sitemaps | G1 closed: the route report sends `noindex`, and the plugin stores the set and excludes those paths with reason noindex (TestNoindexPathsTracer) | ✓ (live round trip: human) |
| SEO specialist runs the audit | Issues per URL, each with an MCP tool or fix: code | sitemap_noindex on an Additional address names sitemap_update_settings (TestSitemapNoindexTool); the crawl lifecycle is fixed (TestCrawlTimeout, TestCrawlCancelRunning, TestCrawlStaleRecovery, TestCrawlStoreTimeout) | ✓ (live MCP session: human) |
| Outcome | "sites are indexed correctly and issues can be fixed through MCP" | Holds in code and on the fixture and rehearsal sites; production grzybyfunkcjonalne.pl is pending the release | ⚠ pending the release (human) |
### Roadmap Success Criteria
| # | Truth | Status | Evidence |
|---|-------|--------|----------|
| SC1 | Sitemap index with per-type files, configurable size, stable numbering, real lastmod, consistent with noindex and robots.txt; grzybyfunkcjonalne.pl on the new format | ✓ code level; production ⚠ PENDING RELEASE (human) | Noindex consistency now covers Nuxt-only noindex pages: vue-seo-layer `noindexStaticRoutes` (lib/routes.ts) → `#seo-routes` `noindexPaths` → `routeReportBody(staticRoutes, noindexPaths)` (server/plugins/seo-route-report.ts:26) → controllers/api/registry.go `Noindex *[]string` → `ReportNoindex` (classes/registry.go:832, `golem15_seo_noindex_paths`, Rebuilder after commit) → filter `b.noindex[norm.Path]` (classes/sitemap_filter.go:174). The grzyby deploy happens after the tags |
| SC2 | Server-side head, one JSON-LD @graph, hreflang, breadcrumbs from the CMS; build fails on an indexable ssr:false route | ✓ VERIFIED | G3 closed: `tr.hasContent` (classes/menu_item_types.go:1134) sets URL "" for a locale without content in describePosts (content_descriptors.go:178) and describeCategories (:498); tags and authors go through `postContentLocales`. The real-path Postgres test asserts `de=` (no URL) for untranslated records and still gives Resolve all three locales. Post pages also keep og:type article (L4 WR-02) |
| SC3 | Crawler reports issues per URL, each fixable through MCP | ✓ VERIFIED (automated) | The caveat recorded inside G1 is gone: sitemap_noindex on a listed address without a record names `sitemap_update_settings` (`crawl.SitemapTool`, checks.go:351). Live client session: human item |
| SC4 | New code has unit tests in the phase's last plan | ✓ VERIFIED | 33-14's 39 test files as before. Each gap-closure fix carries its own test (33-15 table of 21 findings, 33-16 table of 8, xae RED/GREEN for WR-01 to WR-04); `check-phase33.sh --all` green in this run |
**Score:** 3/4 roadmap criteria verified, SC1's production part pending the release. Plan must-haves: 226/235 truths verified, 0 failed, 0 present-but-behaviour-unverified.
### Previously failed truths
| Plan | Truth | Now | Evidence |
|------|-------|-----|----------|
| 33-03 #4 | "…xhtml:link alternates for each translated locale…; a locale without a URL gets no alternate…" | ✓ VERIFIED | The Journal descriptors now produce URL "" for untranslated locales on the real path (TestContentDescriptorsTranslatedLocales passed in this run). TestHreflangAlternates (sm-sitemap-plugin b2b3a1d) now uses Journal-shaped records with every enabled locale present; it passed in this run. The fixture-only reliance recorded in the first verification is gone |
| 33-06 #7 | "hreflang comes from one source…; a locale without a translation is absent…" | ✓ VERIFIED | Same descriptor rule; `translatedLocales` (sm-seo-plugin classes/alternates.go:65-79) drops URL ""; TestAlternates (sm-seo-plugin 2a288a9, Journal-shaped records) passed in this run |
### Plan must-haves of the gap-closure plans
| Plan | Truths | Result | Notes |
|------|--------|--------|-------|
| 33-15 Go plugins | 23 | 23 verified | One wording superseded. The L1 CR-02 truth said markdown content is "kept byte for byte". Quick 261104-xae (WR-01) reworded it: content is always read as markdown, code ranges stay untouched (also when the body starts with an HTML block), and a raw `<h1>` outside code is converted, as in v0.3.1. The intent (no code sample rewritten) holds and is tested (TestConvertContentH1HTMLLead, TestConvertTranslationH1, Docker TestHeadingConversion). The v0.4.0 notes state the rule. The G2 truth holds as worded: the reason is recorded in deferred-items.md and the SUMMARY |
| 33-16 Nuxt layers | 10 | 10 verified | L4 WR-08 (503 while the backend has not answered since start) keeps its meaning after xae WR-04, which remembers the last definite answer (`lastKnown`, server/lib/site.ts:32-66). The L4 CR-01 release guard is in place. The link: swap is a Task 4 step (human item 1) |
Totals (235): 191 earlier + 2 previously failed (now verified) + 23 + 10 = 226 verified. Still pending the release: 5 (33-13 truth 1 repeated on the tags; 33-14 release order, patches with app builds and check-pins, changelog post, release-log steps). Superseded by Jakub's 2026-11-04 decisions: 3 (two uat truths of 33-13, the uat check of 33-14). Human: 1 (J2 at 360px).
### Overrides applied
| Item | Kind | Disposition |
|------|------|-------------|
| G2 false journal-tag drift warning | warning (no must-have violated) | Accepted deferral to v0.6.2 (Jakub, 2026-11-04). It needs a framework change in signpost.Activate or an additive pact field. Not a gap |
| W5 stricter min/max length check in v0.6.1 (D-106 "strictly additive") | judgment-tier prohibition | Accepted for v0.6.1 (Jakub, 2026-11-04). The "Changed" note is present in 33-14-notes-summercms-v0.6.1.md:19, and 33-14-risk-max.sql exists. Running it on production before the tag is human item 2 |
### Review findings in scope
**33-REVIEW.md, as filtered by the 33-15 and 33-16 plans.** In scope were:
- L1: CR-01, CR-02, WR-01, WR-02 (docs), WR-03, WR-05;
- L2: WR-01 to WR-05, IN-01;
- L3: CR-01, WR-01, WR-03, WR-04, WR-06;
- L4: CR-01, WR-01 to WR-05, WR-08;
- G1 and G3.
All are resolved, with these three notes:
- **L1 CR-02 and L1 WR-05.** The re-review rated them partial. Quick 261104-xae completed them (WR-01 and WR-02 below).
- **L4 CR-01.** The guard, the versions and the pins are done. The link: swap is a Task 4 step by plan.
The 33-REVIEW-GAPFIX.md verdict table has 26 rows: 19 resolved and 3 partial, and the 3 partial ones are now closed as described above.
**33-REVIEW-GAPFIX.md warnings:**
| Finding | Fix (commit) | Code checked | Test (passed in this run) |
|---------|--------------|--------------|---------------------------|
| WR-01 `<`-prefixed markdown, h1 in code | sm-journal-plugin a41d537 | `ConvertContentH1` calls `convertH1OutsideCode(content)` without a first-byte gate (classes/headings.go:79-93) | TestConvertContentH1HTMLLead (named run), plus the Docker suite |
| WR-02 images/alt content_html-only and `<`-prefixed markdown | sm-journal-plugin cf13c60 | `strings.TrimSpace(text.content) == ""` branch, else SetMarkdownImageAlt and SetImageAltOutsideCode (controllers/api/maintenance.go:173-195) | TestSetImageAltBodyShapes (named run, Postgres) |
| WR-03 terminal write shares the store deadline | sm-seo-plugin c71b58e | `finalWriteTimeout` 30 s on a detached parent, `WithFinishTimeout` seam, error "storing the crawl results took longer than …" (classes/crawl/job.go:62-74, 426-429) | TestCrawlStoreTimeout (named run, Postgres) |
| WR-04 root files hijacked after an outage on SEO-less backends | vue-seo-layer 3868f3d | `lastKnown` remembers ok or absent; unknown answers the remembered state (server/lib/site.ts:32-66) | rootFiles.test.ts (vue-seo-layer 156/156 in the layers stage) |
Out of scope, deferred to the v0.6.2 backlog by the plans (deferred-items.md "From 33-15"):
- L1 WR-04, WR-06, WR-07;
- L2 WR-06 to WR-08;
- L3 WR-02 and WR-05;
- L4 WR-06 and WR-07;
- the info items;
- the verifier warnings W1 to W4.
### Behavioral spot-checks, gate and patches
| Behavior | Command | Result | Status |
|----------|---------|--------|--------|
| Full phase gate, Docker and Chromium present | `PHASE33_REQUIRE_DOCKER=1 bash scripts/check-phase33.sh --all` | All 11 stages PASS: self-test, go, plugins, layers (seo 156, journal 202, menu 24, faq 19 TAP, no failures; smokes; check-git-install self-test), consumers, admin, docs, marker, docker (sitemap 62, seo 154, journal 142 top-level tests on full `./...` runs that refuse any skip; framework attach 57), additive (5 packages against v0.6.0), compat. Ends "Phase 33 gate passed", exit 0. Compat skipped sm-feedback-plugin as before (fails at v0.6.0 too) | ✓ PASS |
| G1, G3 and xae tests by name | `go test -count=1 -run '^(…)$' ./...` in each plugin | PASS: TestContentDescriptorsTranslatedLocales, TestSetImageAltBodyShapes, TestConvertContentH1HTMLLead, TestNoindexPathsTracer, TestCrawlStoreTimeout, TestAlternates, TestSitemapNoindexTool, TestHreflangAlternates, TestListing | ✓ PASS |
| Gap-closure tests exist | `go test -short -list` | Present: TestCrawlTimeout, TestCrawlCancelRunning, TestCrawlStaleRecovery, TestRenderTabTimeout, TestFetcherPortAndKey, TestCrawlerNonASCII, TestAllowRequest, TestConcurrentRebuilds, TestRebuildKeepsMovedLastmod, TestIndexNowStaging, TestTypeTitlesNotPinned, TestRecordSavedNilEvent, TestConvertLegacyItems, TestFixHeadingsCommandCLI, TestImageVariantsTallOriginal, TestSetMarkdownImageAlt, TestSetImageAltEdges (all run inside the green docker and short suites) | ✓ PASS |
| App patches on current HEADs | `git apply --check --allow-empty` | OK for each patch on its app's HEAD: 33-12-sm-demo-app (sm-demo-app da481c9), 33-12-vue-demo-app (vue-demo-app 9a12701), 33-12-sm-summercmsio-app (ca06049), 33-12-vue-summercmsio-app (f36cbf1), 33-13-sm-grzybyfunkcjonalne-app (35d3664), 33-13-vue-grzybyfunkcjonalne-app (e45ffe8) | ✓ PASS |
| Demo locale patches stacked | scratch worktrees: apply the 33-12 patch, then `git apply --check` of 33-14-*-locale.patch | OK for both; vue-demo-app ends on `strategy: 'prefix_except_default'` with the `/pl/mapa-strony` sitemap path; worktrees removed | ✓ PASS |
| Journal stays additive | `git diff v0.3.1..HEAD` of non-test Go files: removed or changed exported `func` lines | 0; menu_item_types_test.go and the smoke test unchanged since 311c28a | ✓ PASS |
### Anti-patterns and remaining notes
| File | Pattern | Severity | Impact |
|------|---------|----------|--------|
| all phase repositories | No TBD/FIXME/XXX/TODO/HACK added since each base tag | — | — |
| sm-journal-plugin controllers/api/posts.go | `gofmt -l`: import order (`compass` above `bouncer`), introduced by phase 33 (the file was gofmt-clean at v0.3.1) | ℹ️ Info (known leftover) | Formatting only; fix with `gofmt -w` before the v0.4.0 tag |
| sm-journal-plugin classes/blog_editor_test.go | `gofmt -l`: map literal alignment. The file was already unformatted at v0.3.1, and phase 33 changed it | ℹ️ Info (known leftover) | Test file, formatting only |
| sm-seo-plugin go.mod | Requires sm-sitemap-plugin v0.1.2, which has no `contract` package; works only through go.work | ⚠️ Release step | Pin v0.2.0 in Task 4 (human item 1); the 33-15 SUMMARY records it |
| vue-journal-layer, vue-menu-layer, vue-faq-layer package.json | `"vue-seo-layer": "link:../vue-seo-layer"` | ⚠️ Release step | Do not tag before the Task 4 swap; `--check-links` blocks it |
| sm-journal-plugin (G3 follow-up) | An untranslated locale's address still answers with the default-locale content and a self canonical; it is no longer advertised in hreflang or the sitemaps | ℹ️ Info | Deferred decision (canonical to the default locale or a 404), deferred-items "From 33-15" |
| xae residuals | images/alt answers 422 for an `<img>` in an HTML body after a blank line and 4+ spaces of indent (an indented code block to goldmark). A backend whose /site answers 200 HTML or 401/403 stays unknown | ℹ️ Info | Recorded in the 261104-xae SUMMARY; the workarounds are documented there |
| W1 to W4 | Home title template, swr localhost canonicals, route-report names without diacritics, GORM local timestamps | ℹ️ Info | Deferred to v0.6.2 as before |
| 33-REVIEW-GAPFIX notes below the warning bar | Shutdown during a crawl maps to cancelled; DeleteCrawls does not end stale conga records; collect runs inside an idle transaction; the `IN` list in postContentLocales hits the parameter limit past about 65k posts; repeat IndexNow pings for excluded rows | ℹ️ Info | Not counted by the reviewer; candidates for the v0.6.2 backlog |
The first verification's info rows are settled. The stale "Saving Settings › Journal answers 500" entry is now marked resolved (deferred-items.md:14). The W5 warning became the override above.
### Human Verification Required
These are listed in the frontmatter `human_verification`. Everything except item 7 depends on the release.
1. 33-14 Task 4 release steps: tags in order, the sm-seo-plugin pin, the link: swap with `--check-links`, app pins and patches, `-tags nodynamic` with the DEPLOY.md row fixed, rehearsal databases recreated, changelog posts.
2. W5: 33-14-risk-max.sql on the production databases before the v0.6.1 tag.
3. SC1 on production grzybyfunkcjonalne.pl after the tags: the urls.txt check first, then deploy-log items 1-8.
4. G1 live round trip on the deployed demo (noindex Additional address leaves the sitemap without manual removal).
5. SC3 live MCP crawl, dry run and apply.
6. D-46 Rich Results Test.
7. Visual checks (grzyby, demo on prefix_except_default, admin screens, J2 at 360px).
### Gaps Summary
None. G1 and G3 are closed in code, each with a test on the production path. G2 and W5 are accepted overrides. The phase can proceed to the release step (33-14 Task 4), followed by the human items above.
---
# Initial verification (2026-11-04T20:20:34Z, gaps_found, commit 97abc77)
The report below is kept unchanged, with its headings moved down one level. Its frontmatter `gaps:` block is reproduced here, and the gaps it lists are closed above.
```yaml
gaps:
- truth: "D-57 / SC2 hreflang: a page without a translation has no hreflang for that locale (33-06: 'a locale without a translation is absent'; 33-03: alternates 'for each translated locale')"
status: failed
artifacts: [../sm-journal-plugin/classes/content_descriptors.go, ../sm-journal-plugin/classes/menu_item_types.go]
- truth: "SC1 / D-24: the sitemaps stay consistent with noindex (noindex pages leave the XML and HTML sitemaps)"
status: partial
artifacts: [../vue-seo-layer/server/plugins/seo-route-report.ts, ../sm-seo-plugin/classes/sitemap_filter.go, ../sm-seo-plugin/classes/crawl/checks.go]
```
## Phase 33: SEO output, audit and content Verification Report
**Phase Goal:** As a site editor and as an SEO specialist auditing a SummerCMS site, I want to have the rendered sites emit complete SEO output (sitemaps, structured data, pagination, hreflang, headings, breadcrumbs) from the CMS, and an audit that finds the remaining problems, so that sites are indexed correctly and issues can be fixed through MCP.
**Verified:** 2026-11-04T20:20:34Z
**Status:** gaps_found
@@ -117,7 +265,7 @@ human_verification:
Repositories checked (commits since the base tag; none tagged, all ahead of origin): summercms.go 72 since v0.6.0, sm-sitemap-plugin 17 since v0.1.2, sm-seo-plugin 29 since v0.1.1, sm-journal-plugin 14 since v0.3.1, vue-seo-layer 18 since v0.1.1, vue-journal-layer 11 since v0.7.1, vue-menu-layer 1 and vue-faq-layer 3 since v0.4.0, sm-bm-app bd7ee84 (local D-72 commit).
## User Flow Coverage
### User Flow Coverage
| Step | Expected | Evidence | Status |
|------|----------|----------|--------|
@@ -127,9 +275,9 @@ Repositories checked (commits since the base tag; none tagged, all ahead of orig
| SEO specialist runs the audit | Issues per URL, each with an MCP tool or fix: code | classes/crawl/{crawler,checks,fix}.go; TestCrawlerChecks, TestCrawlerTracer; MCP seo_start_crawl … seo_unignore_issues | ✓ (live MCP session: human) |
| Outcome | "sites are indexed correctly and issues can be fixed through MCP" | Holds on the code and fixture sites; production grzybyfunkcjonalne.pl pending the release; G1 and G3 remain | ⚠ gaps + pending release |
## Goal Achievement
### Goal Achievement
### Roadmap Success Criteria
#### Roadmap Success Criteria
| # | Truth | Status | Evidence |
|---|-------|--------|----------|
@@ -140,7 +288,7 @@ Repositories checked (commits since the base tag; none tagged, all ahead of orig
**Score:** 2/4 roadmap criteria verified; plan must-haves 191/202 truths verified (0 present-but-behaviour-unverified).
### Plan Must-Haves (202 truths, 15 backstops, 27 prohibitions)
#### Plan Must-Haves (202 truths, 15 backstops, 27 prohibitions)
| Plan | Truths | Result | Exceptions |
|------|--------|--------|-----------|
@@ -163,11 +311,11 @@ Backstop truths (15): each has a named passing test in the green gate (for examp
Prohibitions (27): test-tier items have wired enforcement in green suites (marker and additive stages for D-90/D-106; TestStableNumbering D-30; TestIndexNow D-31; TestRouteReport D-33; TestCustomJSONLD D-44; headings_test.go "switching the setting never rewrites stored content" D-50; crawler_test.go refusing evil.example.com, 169.254.169.254 and plain http D-14; TestRendererGuards D-70; related_media_test.go upload answers before encoding D-85; guard:check D-70; check-noindex.sh D-72). Judgment-tier (non-authoritative LLM check): D-96 Journal imports no SEO plugin (holds); D-95 SEO imports only `sm-sitemap-plugin/contract` outside tests (holds); D-70 demo noindexRoutes cover only `/p/**` and `/presentations/**` (holds); D-32/D-106 nothing deployed, pushed or tagged (holds so far); changelog token never printed (not exercised yet).
### Required Artifacts
#### Required Artifacts
All 64 artifacts named in the plans exist and contain their patterns (checked by script): framework pact/content.go, capabilities.go, signpost and cabana smoke tests, attach/webp; sm-journal-plugin headings.go, fix_headings.go, content_facts.go, image_variants.go; sm-sitemap-plugin contract.go, slots.go, writer.go, indexnow.go, htmlgroups.go, mcp.go, migration 202610280002; sm-seo-plugin sitemap_filter.go, registry.go, site.go, schema/graph.go, validate.go, pagination.go, crawl/{crawler,checks,compare,render_chrome}.go, autolinks.go, elements/links.ts, _issue_details.htm; vue-seo-layer middleware, useCmsSeo.ts, seo-root.ts, guard.ts, Pagination.vue, Breadcrumbs.vue, bodyLinks.ts; vue-journal-layer htmlGate.ts, PostCard.vue; vue-faq-layer faqSchema.ts; the patches, notes, COVERAGE.md and release log. None is a stub (no TBD/FIXME/XXX added in any repository since its base tag).
### Key Link Verification
#### Key Link Verification
| From | To | Status | Details |
|------|----|--------|---------|
@@ -189,7 +337,7 @@ All 64 artifacts named in the plans exist and contain their patterns (checked by
| FeaturedImage.vue | variants | WIRED | `<source type="image/webp">` |
| bm nginx | every response | WIRED | `add_header X-Robots-Tag "noindex, nofollow" always` |
### Data-Flow Trace (Level 4)
#### Data-Flow Trace (Level 4)
| Artifact | Data | Source | Real data | Status |
|----------|------|--------|-----------|--------|
@@ -198,7 +346,7 @@ All 64 artifacts named in the plans exist and contain their patterns (checked by
| Breadcrumbs, pagination | crumbs, page form | resolve answer and /_seo/api/v1/site | Yes | FLOWING |
| Site audit | issues | crawl tables + CMS checks | Yes (TestCrawlerTracer) | FLOWING |
### Behavioral Spot-Checks and Gate
#### Behavioral Spot-Checks and Gate
| Behavior | Command | Result | Status |
|----------|---------|--------|--------|
@@ -208,11 +356,11 @@ All 64 artifacts named in the plans exist and contain their patterns (checked by
| App patches apply | `git apply --check` of the 33-12/33-13 patches on each app's HEAD (summercmsio note patch with `--allow-empty`) | all OK | ✓ PASS |
| Demo locale patches stack | 33-12 demo patches applied in scratch copies, then `git apply --check` of 33-14-sm-demo-app-locale.patch and 33-14-vue-demo-app-locale.patch | OK; vue-demo-app ends on `strategy: 'prefix_except_default'` and `/pl/mapa-strony` | ✓ PASS |
### Probe Execution
#### Probe Execution
No `probe-*.sh` declared by the phase; the phase gate is `scripts/check-phase33.sh` (run above).
### Requirements Coverage
#### Requirements Coverage
REQUIREMENTS.md holds no Phase 33 IDs (ROADMAP: Requirements TBD). The IDs are the SC-n of the ROADMAP and the D-n decisions of 33-CONTEXT.md (text in 32-CONTEXT.md). Every one of the 48 decision IDs listed in 33-CONTEXT.md is claimed by at least one plan; the plans add D-90 and D-106 (named in CONTEXT's planning section) and SC-1..SC-4. No orphaned ID.
@@ -236,11 +384,11 @@ REQUIREMENTS.md holds no Phase 33 IDs (ROADMAP: Requirements TBD). The IDs are t
| D-72 | 12 | ✓ | check-noindex.sh |
| D-90, D-106 | 01, 11, 14 | ✓ (see warning W5) | marker and additive stages PASS |
### Decision Coverage
#### Decision Coverage
The `check.decision-coverage-verify` handler could not parse 33-CONTEXT.md (decisions are tables, not `- **D-NN:**` bullets). Checked by hand: 48/48 CONTEXT decision IDs appear in plan `requirements:` fields. The 2026-10-28 planning decisions (Journal H1 setting, postTitleTag, links_new_tab, journal.breadcrumbs, WebP in Go, optional chromedp, SC3 reading, release cadence) are all present in code. Jakub's 2026-11-04 decisions: no uat (production after tags), demo on prefix_except_default (patches ready), WebP builds with -tags nodynamic (not yet in any app patch or build script: Task 4), the two recorded gaps (G1 here, G2 under warnings).
### Anti-Patterns and Warnings
#### Anti-Patterns and Warnings
| File | Pattern | Severity | Impact |
|------|---------|----------|--------|
@@ -254,7 +402,7 @@ The `check.decision-coverage-verify` handler could not parse 33-CONTEXT.md (deci
| deferred-items.md | Stale: "Saving Settings › Journal answers 500" is fixed on master by d2ce3a2 (ships in v0.6.1) but still says open | ℹ️ Info | Update the entry |
| 33-13-sm-grzybyfunkcjonalne-app.patch | Its DEPLOY.md "Binary" row describes a dynamically linked build, contrary to the -tags nodynamic decision | ⚠️ Warning | Correct in Task 4 when adding the tag |
### Test Quality Audit
#### Test Quality Audit
| Area | Active | Skipped | Circular | Assertion level | Verdict |
|------|--------|---------|----------|-----------------|---------|
@@ -262,7 +410,7 @@ The `check.decision-coverage-verify` handler could not parse 33-CONTEXT.md (deci
| 33-14 TS tests (10 files) | all | none (`it.skip`/`.todo` absent) | none | value | OK |
| D-57 tests (TestHreflangAlternates, TestAlternates) | yes | no | no | value | INSUFFICIENT for the production path: the precondition (empty URL for an untranslated locale) exists only in the fixture (G3) |
### Human Verification Required
#### Human Verification Required
Listed in frontmatter `human_verification`. In short:
1. Escalation: fix or override G3 and G1 before the tags.
@@ -273,7 +421,7 @@ Listed in frontmatter `human_verification`. In short:
6. Visual checks after the deploys (grzyby, demo on prefix_except_default, admin screens, J2 at 360px).
7. Task 4 items not yet in any patch: -tags nodynamic in the app builds, locale patches, check-git-install.sh on the tag, changelog posts.
### Gaps Summary
#### Gaps Summary
Two must-have truths fail, with different roots:
@@ -302,3 +450,8 @@ Regression check against Phase 32 (32-VERIFICATION.md, human_needed): the Phase
_Verified: 2026-11-04T20:20:34Z_
_Verifier: Claude (msd-verifier)_
---
_Re-verified: 2026-11-04T23:53:40Z_
_Verifier: Claude (msd-verifier)_