26 KiB
phase, verified, status, score, covered_files, covered_digest, behavior_unverified, overrides_applied, behavior_unverified_items, human_verification
| phase | verified | status | score | covered_files | covered_digest | behavior_unverified | overrides_applied | behavior_unverified_items | human_verification | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 11.2-ready-to-share-summercms-io-website-and-newsletter-plugin | 2026-10-01T15:35:00Z | human_needed | 12/14 must-haves verified |
|
v2:sha256:d56e75a7b2e752da25f76d9c353a003ae92abcf52ceb8fb671a49c5f0b7d14bf | 1 | 0 |
|
|
Phase 11.2: summercms.io Alpha 0.1 landing page on SummerCMS Verification Report
Phase goal: summercms.io replaces its "Under construction" page with the Alpha 0.1 landing page from the claude.ai/design handoff. The page is a Nuxt 4 static site, English only and i18n-ready, embedded in and served by a SummerCMS binary that also serves the Phase 11.1 docs at /docs. It deploys behind nginx with supervisor using documented steps.
Verified: 2026-10-01T15:35:00Z
Status: human_needed
Re-verification: No. This is the initial verification.
The phase built everything it can build on the dev machine, and the build works. One gate run covers all four repositories (scripts/check-phase11.2.sh --all), and I ran it myself: it exits 0. I also re-ran two checks outside the gate: the PostgreSQL 15 suites and the v0.1.0 release build from a scratch clone. What is left needs a person: checking the page in a browser, bringing the site up on rome, and the cutover checks. The cutover depends on the user's deferred tag and push.
Goal Achievement
Observable Truths
| # | Truth | Status | Evidence |
|---|---|---|---|
| 1 | SC1 (structure): nuxt generate produces real prerendered HTML in this order: sticky header, hero #top, #why, #features, #winter, #start, footer. The terminal card is fed from terminal.json, and the section links are hidden at 720px and below by CSS, not by markup |
✓ VERIFIED | Gate --site: generate exits 0 and node:test reports 30 pass, 0 fail. In the built public/site/index.html the byte offsets rise through top 11737, why 12671, features 13906, winter 16018, start 17890, <footer 19929. position:sticky is in the CSS, and @media(max-width:720px){.links,.links-wrap{display:none}} is in both index.html and _nuxt/*.css. The six commands each appear once, PostgreSQL 15+ appears, and v-html is used 0 times |
| 2 | SC1 / D-23 (behaviour): the page matches the handoff visually at every width, scroll-spy highlights the section in view, and Copy changes its label and fills the clipboard | ⚠️ PRESENT_BEHAVIOR_UNVERIFIED | SiteHeader.vue calls activeSection() from a passive scroll listener behind landing.scrollSpy. TerminalCard.vue calls copyPayload(), with a textarea fallback and a 1500 ms reset. The pure functions are unit-tested, but nothing drives the browser runtime or checks visual fidelity. This is a backstop truth, so it goes to human UAT |
| 3 | SC2: one summercms-io binary embeds the Nuxt output and the docs build and serves / and /docs. Responses are indexable (no admin noindex or CSP). Hashed assets get an immutable cache header and HTML gets no-cache |
✓ VERIFIED | plugin.go has //go:embed all:public and registers GET /docs (301 to /docs/), GET /docs/{path...} and GET / in GroupRaw. It does not implement HasAdminControllers. In static.go, siteImmutable covers _nuxt/ (except builds/) and _fonts/; every other file gets no-cache with a strong ETag, and no X-Robots-Tag, CSP or frame header is sent. Gate --smoke booted the binary through migrate and serve on postgres:15 and printed smoke: ok (no X-Robots-Tag or CSP, 304 on If-None-Match, immutable /_nuxt/*.js and /_fonts/*.woff2, /backend 404, POST / 405). The 17 named plugin tests pass, and plugin coverage is 98.2% |
| 4 | SC3: every link on the page resolves, including the eight feature tiles and the concept map, checked against the built docs | ✓ VERIFIED | Gate --built: TestLandingLinks sends every href and src (plus og:image) through the real surf.Assemble handler, follows at most one root-relative 301, and requires a final 200. It also requires /docs plus all ten D-44 targets. The external links (Source, golem15.com) are checked at cutover by design. Run informally today, both answer 200 anonymously, the Source link directly and golem15.com after a 301 to www |
| 5 | SC4 (artifacts): a scripted build runs nuxt generate, then docs:build, then go build. DEPLOY.md and the committed configs cover build, upload, the supervisor program and the nginx site (TLS, proxy, gzip) | ✓ VERIFIED (review findings attached) | scripts/build.sh runs pnpm generate, rsyncs into public/site, runs summer docs:build --base-url /docs --site-url / --site-label summercms.io, runs the plugin tests, then summer build and a linux/amd64 go build (dev, and release from the tag through a temporary go.work replace). DEPLOY.md covers the launch checklist, one-time setup (system user, createdb -O summercms, a 0600 .env), build, rsync upload, migrate before restart, cutover, verify and rollback. The nginx config has certbot TLS, HTTP->HTTPS and www->apex redirects, gzip, location ^~ /backend 404, limit_except GET HEAD and the proxy headers. Gate --deploy passes nginx -t and parses the supervisor file. The CR-01, WR-01 and WR-02 review findings apply here; see Advisory |
| 6 | SC4 (outcome): following DEPLOY.md on a clean server brings the site up | ? UNCERTAIN (backstop, human) | This cannot be verified from the build machine; the cutover is a deliberate user step. Nothing I read in DEPLOY.md would stop the site from coming up. CR-01 is a privilege-boundary flaw, not a bring-up failure, and WR-02 could break certificate renewal later. Human item 2 |
| 7 | SC5: the new Go code has unit tests, delivered in the phase's last plan | ✓ VERIFIED | Plan 11.2-03 added static_test.go and routes_test.go (plugin coverage 98.2%, floor 90%), TestCheckSiteURL, TestSiteLabel and TestSiteURLPrecedence (docsite coverage 94.2%, floor 85%), and TestLoadTerminal, TestTerminalScript and TestTerminalEnv (app). The gate refuses a SKIP, a FAIL or zero matched tests |
| 8 | D-01, D-02, D-03, D-43, D-48: sm-summercmsio-app is the root app, with vue-summercmsio-app and plugins/golem15/summercms (module sm-summercmsio-plugin, ID golem15.summercms) as submodules. It is a go.work workspace that replaces the framework locally, and it uses the summercmsio names |
✓ VERIFIED | .gitmodules URLs point to git@git.golem15.com:golem15/{vue-summercmsio-app,sm-summercmsio-plugin}.git. go.mod has module git.golem15.com/golem15/sm-summercmsio-app, requires summercms v0.1.0 and replaces it with ../summercms.go. go.work has use (. ./plugins/golem15/summercms). summer.yaml has binary summercms-io. The generated plugins.gen.go blank-imports sm-summercmsio-plugin. Each repository's origin points to its summercmsio remote, and the gate's --app stage checks that go list ./... prints only the app package |
| 9 | D-25: the framework's database suites pass on postgres:15 before the docs say "PostgreSQL 15 or newer" | ✓ VERIFIED | I re-ran it myself: a HEAD export with postgres:16-alpine replaced by postgres:15 in 9 files (no postgres:16 left). lagoon, lagoon/attach, cabana, beachcomber (and typesense), lighthouse (and centrifugo), bouncer, conga and docs/examples/blog (and 4 subpackages) all returned ok, EXIT=0. README.md lines 15 and 35 and installation.md line 14 say "PostgreSQL 15 or newer", and no "PostgreSQL 16" requirement is left anywhere in the docs |
| 10 | D-41, D-46: optional site_url and site_label keys and the --site-url and --site-label flags add a validated link back to the main site. The framework's own output is unchanged when they are not set |
✓ VERIFIED | header.html adds a conditional site-link anchor on the wordmark line. docs.go registers both flags on docs:build and docs:serve and reads them in docsOptions. docs/site.yaml has no diff since the phase base. Gate --framework passed TestParseSite, TestCheckSiteURL, TestSiteLabel, TestSiteURLPrecedence, TestSiteLink (it checks the exact bytes of the unset header) and TestDocsBuildSiteFlags, along with check-phase11.1 --docs and --forbidden. One accepted deviation: an unset build is byte-identical for every HTML, md, llms and search file, but assets/site.css gains 17 additive lines (11.2-02-SUMMARY deviation 5) |
| 11 | D-39, D-40: the terminal card has one source of commands (terminal.json), which the page and the Go check share. The six commands run to handled=true, and a drift guard ties the page to the file |
✓ VERIFIED (WR-03 advisory) | TerminalCard.vue imports ~/data/terminal.json. TestTerminalCommandsInPage checks every command and comment in the built page. Gate --terminal (with the clone override set to local summercms.go) passed TestTerminalCommands. The check puts GOBIN on PATH, as the plan's must-have specifies; WR-03 explains why that hides a fresh-shell PATH dependency. Human item 4 |
| 12 | D-42: v0.1.0 is created only after the user confirms, and the release build takes the docs and the framework from the tag | ✓ VERIFIED (resolved: defer-tag) | The user chose defer-tag, so no tag exists in summercms.go, as intended. I repeated the release path myself: a fresh clone with a local v0.1.0 tag and SUMMERCMS_FRAMEWORK=<clone> scripts/build.sh printed build: bin/summercms-io (release v0.1.0) ready, and go version -m shows summercms v0.1.0 => /tmp/.../fw. DEPLOY.md launch step 3 covers the real tag |
| 13 | D-33, D-34, D-35, D-36, D-37, D-45: en-only @nuxtjs/i18n with all copy in en.json; self-hosted Roboto fonts; plain-CSS tokens; images derived from the original sun; static SEO; app-config flags | ✓ VERIFIED | nuxt.config.ts has the prefix_except_default strategy with one en locale, @nuxt/fonts with Roboto 300-700 and Roboto Mono 400/500, ogImage.enabled: false, sitemap.autoI18n: false and prerender.ignore ['/docs']. The built site has 3 .woff2 files in _fonts/ and 0 Google font hosts, a canonical link to https://summercms.io/, og:image https://summercms.io/og-image.png, robots index, follow, and a flat sitemap.xml. og-image.png is 1200x630 and assets-src/logo.png is 746x744. sun-crop is not shipped and no Tailwind is used. app.config.ts has landing.showRays and landing.scrollSpy, both true, which the hero and header read |
| 14 | scripts/check-phase11.2.sh --all runs every stage fail-closed and prints phase11.2 all passed |
✓ VERIFIED | I ran it myself and it exited 0. The framework, plugin, app, site, built, smoke, deploy, terminal and full stages each printed phase11.2 <stage> passed, then phase11.2 all passed. The built stage used a dev build because no tag exists |
Score: 12/14 truths verified (1 present but behavior-unverified, 1 backstop routed to a human)
Advisory (code review findings bearing on SC4 and D-40)
These come from 11.2-REVIEW.md. All 15 are still open in 11.2-REVIEW-DISPOSITION.md. They do not fail a must-have truth, but they should be dispositioned before the rome cutover.
| # | Finding | Category | Why advisory, and what resolves it |
|---|---|---|---|
| CR-01 | DEPLOY.md:74 runs adduser --system --home /srv/summercms-io, which makes the install root owned by summercms. The service account could then swap bin/ or config/ for symlinks, and root's later rsync, cp and mv steps would follow them (CWE-59) |
security (SC4 docs) | The site still comes up. Resolve by keeping the install root root-owned (--no-create-home plus install -d -o root, with only storage/ owned by the service) before the first deploy |
| WR-01 | No Strict-Transport-Security header in the HTTPS blocks | security (SC4 nginx) | Add add_header Strict-Transport-Security ... always; and a curl check in "Verify after cutover" |
| WR-02 | The port-80 block also redirects /.well-known/acme-challenge/, and the app returns 404 for dot-segment paths |
operations (SC4 TLS) | Certificate renewal breaks about 60 days later if rome uses the webroot authenticator. Add a challenge location and a certbot renew --dry-run step |
| WR-03 | The terminal check puts GOBIN on PATH, so it cannot catch summer: command not found in a shell that lacks ~/go/bin |
correctness (D-39/D-40) | Changing the copy needs user approval (D-40). Human item 4 |
| WR-04 | pnpm test and the gate's --site stage only work on Node 22.18 to 22.x |
tooling | Pin --test-reporter=tap and set engines >=22.18 |
| WR-05 | smoke.sh can pass against a foreign process already holding port 18095 |
tooling (SC2 evidence) | Not the case in my run: the gate's smoke stage started its own container and binary. Refuse a busy port before the readiness loop |
| WR-06 | In release mode, the plugin tests compile against the working-tree framework, not the tag, and dirty submodules are not refused | release integrity (D-42) | Run the plugin tests under the temporary go.work and refuse a dirty SITE or PLUG |
Required Artifacts
| Artifact | Expected | Status | Details |
|---|---|---|---|
sm-summercmsio-app/vue-summercmsio-app/nuxt.config.ts |
SSG config with fonts, i18n, SEO and prerender ignore | ✓ VERIFIED | Contains ignore: ['/docs'] |
.../vue-summercmsio-app/app/app.config.ts |
D-45 flags | ✓ VERIFIED | Both flags read by the components |
.../vue-summercmsio-app/i18n/locales/en.json |
all copy | ✓ VERIFIED | The en.json leaf check in output.test.ts passes |
.../vue-summercmsio-app/app/data/terminal.json |
single command source | ✓ VERIFIED | 3 groups, 6 commands |
.../vue-summercmsio-app/app/components/TerminalCard.vue |
card with Copy | ✓ VERIFIED | Imports terminal.json and copyPayload |
.../vue-summercmsio-app/app/utils/{scrollSpy,terminal}.ts |
pure utilities | ✓ VERIFIED | Unit-tested |
.../vue-summercmsio-app/scripts/derive-images.sh |
D-36 derivation | ✓ VERIFIED | Outputs committed |
sm-summercmsio-app/plugins/golem15/summercms/plugin.go |
ID, embed, routes | ✓ VERIFIED | Wired through plugins.gen.go |
.../plugins/golem15/summercms/static.go |
static handler | ✓ VERIFIED | http.ServeContent, in-memory map, dot-segment refusal, 301 only to existing .html |
.../plugins/golem15/summercms/{static,routes,links,smoke}_test.go |
tests | ✓ VERIFIED | 98.2% coverage |
sm-summercmsio-app/scripts/{build,smoke,check-deploy}.sh |
build, smoke, deploy check | ✓ VERIFIED | All three run green in the gate |
sm-summercmsio-app/terminal_check_test.go |
D-40 check | ✓ VERIFIED | Reads vue-summercmsio-app/app/data/terminal.json |
sm-summercmsio-app/DEPLOY.md, deploy/nginx/summercms.io.conf, deploy/supervisor/summercms-io.conf, deploy/env.example, deploy/rollback/under-construction/ |
SC4 | ✓ VERIFIED (advisory CR-01, WR-01, WR-02) | Rollback copy is a 9.8 KB index.html plus the logo; .env is not tracked |
internal/docsite/load.go, header.html, cmd/summer/docs.go |
D-41, D-46 | ✓ VERIFIED | Keys, validation, flags and template are in place |
scripts/check-phase11.2.sh |
phase gate | ✓ VERIFIED | --all exits 0 |
Key Link Verification
| From | To | Via | Status | Details |
|---|---|---|---|---|
plugins.gen.go |
site plugin | blank import, then init party.Register |
✓ WIRED | _ "git.golem15.com/golem15/sm-summercmsio-plugin" |
plugin.go |
surf router | r.GroupRaw("", nil, ...) |
✓ WIRED | Exercised by surf.Assemble in TestLandingLinks and TestRoutesAssemble |
build.sh |
plugins/.../public/{site,docs} |
rsync of .output/public, then docs:build --out |
✓ WIRED | Both trees present after the build; the embed is proven by the smoke stage |
build.sh |
framework tag | git -C "$FW" archive "$REF" plus a temporary go.work replace |
✓ WIRED | Release rehearsal: v0.1.0 => /tmp/.../fw |
cmd/summer/docs.go |
docsite.Options |
docsOptions reads site-url and site-label |
✓ WIRED | The built docs carry class="site-link" href="/" |
terminal_check_test.go |
terminal.json |
loadTerminal |
✓ WIRED | TestLoadTerminal reads 3 groups and 6 commands |
TerminalCard.vue |
terminal.json, copyPayload |
static import | ✓ WIRED | |
SiteHeader.vue |
activeSection |
scroll listener | ✓ WIRED (runtime behaviour goes to human UAT) |
Data-Flow Trace (Level 4)
| Artifact | Data | Source | Produces real data | Status |
|---|---|---|---|---|
| TerminalCard | groups |
app/data/terminal.json (bundled) |
yes, rendered into the prerendered HTML | ✓ FLOWING |
| Page copy | t(...) |
i18n/locales/en.json |
yes, every leaf is in index.html | ✓ FLOWING |
| Site and docs handlers | publicFS |
//go:embed all:public, filled by build.sh |
yes, the smoke test served both trees from the binary | ✓ FLOWING |
| Docs header link | SiteURL, SiteLabel |
--site-url / and --site-label summercms.io |
yes | ✓ FLOWING |
Behavioral Spot-Checks
| Behavior | Command | Result | Status |
|---|---|---|---|
| Whole phase gate | scripts/check-phase11.2.sh --all |
all 9 stages passed, EXIT=0 | ✓ PASS |
| D-25 on postgres:15 | HEAD export, sed to postgres:15, go test -count=1 -p 4 over the DB packages |
14 ok lines, EXIT=0 |
✓ PASS |
| Release path from the tag | scratch clone with a local v0.1.0 tag, SUMMERCMS_FRAMEWORK=<clone> scripts/build.sh |
(release v0.1.0) ready; go version -m shows v0.1.0 => /tmp/.../fw |
✓ PASS |
| Built page structure | grep byte offsets and content in public/site/index.html |
sections in order; 6 commands; PG15 chip; no Google font hosts; 720px rule present | ✓ PASS |
| Repositories stay clean after the build | git status --porcelain in all four repositories |
clean (apart from the pre-existing untracked zip in summercms.go) | ✓ PASS |
| External links | curl on git.golem15.com/golem15/summercms and golem15.com |
200, and 301 to www then 200 | ? INFO (official check is at cutover) |
Probe Execution
No scripts/*/tests/probe-*.sh is declared or present for this phase. The phase gate (scripts/check-phase11.2.sh) plays that role, and I ran it myself (above).
Requirements Coverage
The phase has no requirement IDs: ROADMAP says "Requirements: TBD", and every plan sets requirements: []. REQUIREMENTS.md maps no ID to Phase 11.2, so no requirement is orphaned. The acceptance contract is ROADMAP SC1 to SC5 and the D-IDs, all covered above.
Anti-Patterns Found
| File | Line | Pattern | Severity | Impact |
|---|---|---|---|---|
| (all phase files in the 4 repositories) | none | TBD, FIXME, XXX, TODO, HACK, PLACEHOLDER | none | No debt markers |
vue-summercmsio-app/app |
none | v-html |
none | Not used |
sm-summercmsio-app/DEPLOY.md |
74-83 | service-owned install root (CR-01) | ⚠️ Warning | Advisory, see above |
deploy/nginx/summercms.io.conf |
10-16, 33-66 | no HSTS; ACME path redirected (WR-01, WR-02) | ⚠️ Warning | Advisory |
Deferred framework quirks (in deferred-items.md, not part of this phase's goal): summer plugin:add writes an absolute path into go.work, and go mod tidy before the first summer build drops requires.
Human Verification Required
1. Landing page visual and interaction UAT
Test: Run pnpm -C ../sm-summercmsio-app/vue-summercmsio-app run preview and compare it with design/SummerCMS Landing.dc.html at 1280, 721, 720 and 375px. Scroll the page, then press Copy and paste the clipboard.
Expected: The page matches the handoff. Section links show at 721px and are hidden at 720px. The header stays sticky. The active link follows the section being read, and none is active above Why. Copy shows "Copied" for about 1.5 s, and the clipboard holds six lines without $ or comments.
Why human: No automated test checks pixel fidelity or the browser runtime (scroll events, Clipboard API).
2. Rome bring-up by following DEPLOY.md
Test: Before starting, decide on CR-01, WR-01 and WR-02. Then follow DEPLOY.md from the launch checklist through "Verify after cutover". Expected: https://summercms.io shows the landing page, and /docs shows the docs with the "summercms.io" back-link. /backend returns 404 and POST / returns 403. The http:// and www addresses redirect to the https apex. Why human: The server is unreachable from here, and the cutover is the user's step.
3. Cutover checks (deferred by design)
Test: Tag v0.1.0 and push it with summercms.go master. Run scripts/build.sh (release), then SUMMERCMS_CHECK_EXTERNAL=1 go -C plugins/golem15/summercms test -run TestExternalLinks -count=1 -v ./..., then scripts/check-phase11.2.sh --terminal --verbatim.
Expected: The build prints (release v0.1.0) ready, every external link answers 200 anonymously, and the six commands reach handled=true against the public clone.
Why human: The user deferred the tag and the pushes. The public remote's master is 79116a2, which is behind local master.
4. WR-03 decision on the terminal copy
Test: In a shell without $(go env GOPATH)/bin on PATH, paste the six commands.
Expected: Decide whether the card should add a PATH step. D-40 requires your approval for a copy change. Then align TestTerminalCommands.
Why human: This is a copy decision reserved to the user.
Gaps Summary
There are no blocking gaps. Every automatable must-have holds in the code, and I confirmed them with my own gate run and independent re-runs:
- the PostgreSQL 15 suites;
- the release build from a v0.1.0 tag;
- the structure of the built output.
The missing v0.1.0 tag and the unpushed repositories are by design (defer-tag, D-48), so I did not count them. Four items remain for a human: browser UAT, the rome bring-up, the cutover checks, and the WR-03 copy decision.
The 15 open review findings are advisory. CR-01, WR-01 and WR-02 should be dispositioned before following DEPLOY.md on rome.
Note on covered_files: the fingerprint tool refuses paths outside the summercms.go root, so covered_digest covers only the phase plans, the summaries and the summercms.go implementation files. The three sibling repositories (sm-summercmsio-app, sm-summercmsio-plugin, vue-summercmsio-app) were verified at these HEADs: app f34c335, plugin 549adaf, site c3ddd5c.
Verified: 2026-10-01T15:35:00Z Verifier: Claude (gsd-verifier)