From d08675aaff339eb42a27fa55a643f8144b888f3f Mon Sep 17 00:00:00 2001 From: Jakub Zych Date: Fri, 18 Sep 2026 14:23:56 +0200 Subject: [PATCH] docs(phase-4): complete phase execution --- .planning/STATE.md | 17 +- .../04-VERIFICATION.md | 204 ++++++++++++++++++ 2 files changed, 213 insertions(+), 8 deletions(-) create mode 100644 .planning/phases/04-cli-scaffolding-i18n-and-mail/04-VERIFICATION.md diff --git a/.planning/STATE.md b/.planning/STATE.md index ed8fd35..51877da 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -2,9 +2,9 @@ gsd_state_version: 1.0 milestone: v1.0 milestone_name: milestone -status: verifying -stopped_at: Completed 04-04-PLAN.md -last_updated: "2026-09-18T12:14:08.793Z" +status: ready_to_plan +stopped_at: Phase 04 complete (4/4) — ready to discuss Phase 5 +last_updated: 2026-09-18T12:23:27.307Z last_activity: 2026-09-18 progress: total_phases: 15 @@ -21,13 +21,13 @@ progress: See: .planning/PROJECT.md (updated 2026-09-16) **Core value:** An existing WinterCMS-shaped app can be ported plugin by plugin to a single Go binary without its frontend noticing: the PHP version's API contract is the acceptance test. -**Current focus:** Phase 04 — cli-scaffolding-i18n-and-mail +**Current focus:** Phase 5 — data layer full fidelity ## Current Position -Phase: 04 (cli-scaffolding-i18n-and-mail) — EXECUTING -Plan: 4 of 4 -Status: Phase complete — ready for verification +Phase: 5 +Plan: Not started +Status: Ready to plan Last activity: 2026-09-18 Progress: [██████████] 100% @@ -36,7 +36,7 @@ Progress: [██████████] 100% **Velocity:** -- Total plans completed: 18 +- Total plans completed: 22 - Average duration: 21 min - Total execution time: 104 min @@ -47,6 +47,7 @@ Progress: [██████████] 100% | 01 | 4 | - | - | | 02 | 5 | - | - | | 03 | 4 | - | - | +| 04 | 4 | - | - | **Recent Trend:** diff --git a/.planning/phases/04-cli-scaffolding-i18n-and-mail/04-VERIFICATION.md b/.planning/phases/04-cli-scaffolding-i18n-and-mail/04-VERIFICATION.md new file mode 100644 index 0000000..632b2db --- /dev/null +++ b/.planning/phases/04-cli-scaffolding-i18n-and-mail/04-VERIFICATION.md @@ -0,0 +1,204 @@ +--- +phase: 04-cli-scaffolding-i18n-and-mail +verified: 2026-09-18T12:22:19Z +status: passed +score: 3/3 must-haves verified +overrides_applied: 0 +human_verification: [] +decision_coverage: + honored: 21 + total: 21 + not_honored: [] +--- + +# Phase 4: CLI scaffolding, i18n and mail Verification Report + +**Phase Goal:** Scaffolding commands generate compiling stubs for every plugin artifact type, and plugins can register translated, CLDR-pluralized, namespaced strings and mail templates rendered through a pluggable driver interface. +**Verified:** 2026-09-18T12:22:19Z +**Status:** passed +**Re-verification:** No — initial verification + +Must-haves are the three ROADMAP success criteria (they override plan-level truths). Plan `must_haves` (D-01…D-21) were checked as supporting evidence. The ROADMAP goal is an infrastructure/tooling outcome, not a product UI; MVP developer-flow coverage is mapped from Plan 01's objective below. + +`04-REVIEW.md` status is `issues` (4 warnings, 3 info, 0 critical). Those findings do not break must-haves or the phase goal and are recorded as non-blocking notes. + +## User Flow Coverage + +User story (Plan 01 objective): «As a plugin developer, I want to generate compiling plugin artifacts, resolve translated strings, and send registered mail, so that I can port WinterCMS plugins into one SummerCMS binary.» + +| Step | Expected | Evidence | Status | +|------|----------|----------|--------| +| Run `summer make:*` for plugin, model, migration, command, job, admin-controller | Stubs compile and pass `go vet` | `TestScaffoldAllArtifacts` generates all six into a copied hello workspace then `go build`/`go vet`; `TestMakeCommandsViaCLI` drives public cobra commands | ✓ | +| Resolve `vendor.plugin::group.key` for pl and en | Nested keys, CLDR plurals, `:name` substitution, fallback | Hello `LangFS` YAML + `TestHelloPluginResolvesLang`; `TestPluralSmoke` CLDR `one/few/many` and Laravel pipes | ✓ | +| Register mail template/layout and send | Dotted names, `-en` sibling, html/template + Goldmark, driver Send | Hello `HasMailTemplates`; memory render asserts subject/HTML/text; `TestSMTPMailpit` go-mail receipt in Mailpit | ✓ | +| Outcome | Phase gate green with Docker required | This session: `./scripts/check-phase4.sh` → `phase4 check passed`; forced `-count=1` on scaffold/i18n/mail including Mailpit PASS 1.88s | ✓ | + +## Goal Achievement + +### Observable Truths + +| # | Truth | Status | Evidence | +|---|-------|--------|----------| +| 1 | `summer make:plugin`, `make:model`, `make:migration`, `make:command`, `make:job` and `make:admin-controller` each generate stubs that compile and pass `go vet` | ✓ VERIFIED | `cmd/summer/main.go` registers all six; `make:plugin` → `build.MakePlugin`; the others → `build.MakeModel`/`MakeMigration`/`MakeCommand`/`MakeJob`/`MakeAdminController`. Stubs render from embedded `internal/build/stubs/*.tmpl` through `go/format`. `TestScaffoldAllArtifacts` (this session 6.196s) writes Album+Widget models (`golem15_demo_albums`, timestamps, `--no-migration`), a separate `AddAlbumIndex` migration, `Purge` command, `Reindex` job (`Kind()`/`Work`, no River), `Albums` admin controller + `fields.yaml`/`columns.yaml`, then `go build ./...` and `go vet` on the copied workspace and plugin. Registry bytes are stable; handwritten `plugin.go` is untouched. `TestMakeCommandsViaCLI` executes `make:plugin`/`make:model --no-migration`/`make:job`/`make:command` through cobra. `TestModelsLeaf` fails `summer build` naming plugin ID, file, and sibling import. | +| 2 | A translation key `vendor.plugin::group.key` resolves for pl and en, including a CLDR plural form, loaded from per-plugin per-locale YAML files with parameter substitution | ✓ VERIFIED | Hello embeds `lang/en/lang.yaml` and `lang/pl/lang.yaml`. Keys are `golem15.hello::lang.greeting` / `lang.labels.pluginName`. `phrasebook.Activate` walks `HasLang` after Register and publishes `*phrasebook.Translator` before Boot. `Get`/`Choice` use `towel.Locale`; go-i18n v2.6.1 selects only the CLDR category via `IdentityParser`. This session: `go test ./phrasebook ./party -count=1` ok; `go -C examples/hello test ./... -count=1` ok. `TestHelloPluginResolvesLang`: pl=`Cześć`, en=`Hello`, nested labels, `pl-PL`→`pl`, `de`→configured `en`, missing key returns the raw key. `TestPluralSmoke`: pl albums 1/2/5/22 → one/few/many/few with `:count`; en `{0}`/`{1}`/`[2,*]` pipes; `:name`/`:Name`/`:NAME` → `hi alice Alice ALICE`; `{{.Count}}` stays literal. | +| 3 | A plugin registers a mail template and layout by dotted name with the per-locale suffix convention, and it renders via `html/template` through a driver interface (SMTP via go-mail) in a test send | ✓ VERIFIED | Hello registers `golem15.hello::mail.hello`, `golem15.hello::mail.hello-en`, layout alias `hello` → `golem15.hello::mail.layouts.hello`. `Send` takes the full dotted name and does no locale lookup. Render: `html/template` on Markdown → Goldmark HTML (`goldmark.New()`, no `html.WithUnsafe`) → substituted Markdown as text. Drivers: `memory`, `log`, `smtp` (`github.com/wneessen/go-mail` `DialAndSendWithContext`) behind `postcard.Driver`. `mail.driver` + `SUMMER_MAIL__DRIVER`. This session: `TestMailRenderSmoke` subject `Witaj Ada` / `Hello Ada`, HTML `Ada`, text `Witaj **Ada**.`; `TestSMTPMailpit` started `axllent/mailpit:v1.31.1`, sent via go-mail TLS `none`, Mailpit HTTP API asserted To/subject/HTML/text — PASS 1.88s. Missing template/layout fails `party: boot ` with the name. | + +**Score:** 3/3 truths verified + +Supporting plan truths (D-01…D-21: Winter directory shape, models-leaf check, registry.gen.go, gormigrate create-table, admin/job contracts, embedded templates, argument inference, HasLang load/flatten, both plural syntaxes, parameter case variants, fallback chain, en/en framework defaults, Winter mail headers, Goldmark safety, `-en` siblings, layout aliases, three drivers, Mailpit vs `-short`, backpack `Mailer.Send`, no-retry driver errors) all hold in code; they are not extra score rows. + +### Required Artifacts + +`gsd-sdk query verify.artifacts`: **19/19 passed** across the four plans. + +| Artifact | Expected | Status | Details | +|----------|----------|--------|---------| +| `internal/build/registry.go` | Deterministic per-plugin `registry.gen.go` | ✓ VERIFIED | Scan `Code generated by summer make`; atomic temp+rename; sorted models/migrations/commands/jobs/admin slices | +| `internal/build/leaf.go` | `models/` sibling-import enforcement | ✓ VERIFIED | `checkModelsLeaf` before `go build`; reports plugin ID, file, import | +| `internal/build/stubs/plugin.tmpl` | Winter-shaped plugin root, routes, leaves, go.mod | ✓ VERIFIED | `plugin.go`/`routes.go`/leaf `doc.go`; embeds `config`/`lang`/`views/mail`; generated accessors | +| `internal/build/stubs/artifacts.tmpl` | Model, migration, command, job, admin, YAML stubs | ✓ VERIFIED | GORM timestamps + `TableName`; gormigrate CREATE/DROP; `pact.Job` without River; `fields.yaml`/`columns.yaml` | +| `internal/build/stubs/registry.tmpl` | Generated-registry template | ✓ VERIFIED | `generatedModels`/`Migrations`/`Commands`/`Jobs`/`AdminControllers` | +| `cmd/summer/main.go` | Six make commands | ✓ VERIFIED | `make:plugin` plus five artifact commands; `--no-migration` bare flag | +| `pact/capabilities.go` | Job, admin, lang, mail contracts | ✓ VERIFIED | `Job`/`JobArgs`, `AdminController`, `HasLang`, `HasMailTemplates` | +| `phrasebook/loader.go` | Embedded YAML catalog load | ✓ VERIFIED | `lang//.yaml`; flatten; CLDR map vs nested map; goccy/go-yaml | +| `phrasebook/translator.go` | Get/Choice, CLDR, pipes, fallback, params | ✓ VERIFIED | `towel.Locale`; per-locale i18n.Bundle; `:name`/`:Name`/`:NAME`; missing-key log once | +| `party/registry.go` | HasLang + HasMailTemplates at Activate | ✓ VERIFIED | `phrasebook.Activate` then `postcard.Activate` after Register; `BootPlugin` before `Boot` | +| `postcard/templates.go` | Winter template/layout parse and render | ✓ VERIFIED | INI + `==`; `html/template` then Goldmark; layout `.Content` | +| `postcard/mailer.go` | App-scoped `Send(ctx, Message)` | ✓ VERIFIED | Full dotted name; optional subject override; one wrap, no retry | +| `postcard/drivers.go` | memory, log, SMTP behind `Driver` | ✓ VERIFIED | go-mail client; TLS mandatory/starttls/none; `FailDriver` | +| `postcard/assets/default.htm` | Neutral framework layout | ✓ VERIFIED | Header + text wrapper + HTML `content-body` | +| `internal/build/build_test.go` | Six-artifact compile/vet + leaf check | ✓ VERIFIED | `TestScaffoldAllArtifacts`, `TestModelsLeaf` | +| `phrasebook/translator_test.go` | Catalog, CLDR, pipe, substitution, fallback | ✓ VERIFIED | `TestTranslationSmoke`, `TestPluralSmoke`, `TestLocaleFallbackSmoke`, `TestCatalogBoundaries` | +| `postcard/mailpit_test.go` | Real SMTP receipt via Mailpit HTTP API | ✓ VERIFIED | This session PASS; `-short` skips only this test | +| `scripts/check-phase4.sh` | Repeatable root/hello/Mailpit/race gate | ✓ VERIFIED | Refuses missing Docker; no `-short`; this session `phase4 check passed` | +| `examples/hello/plugins/base/plugin.go` | HasLang + HasMailTemplates on the testbed plugin | ✓ VERIFIED | Embedded `lang/` and `views/mail/`; hello + hello-en + hello layout | + +**Artifacts:** 19/19 verified + +`internal/build/artifact.go` (Make* generators) and `internal/build/scaffold.go` (MakePlugin + embedded templates) are the shared scaffold entry the CLI calls; they are substantive and wired even though only `registry.go`/`leaf.go`/`scaffold` templates appear in plan 01 artifact paths. + +### Key Link Verification + +`gsd-sdk query verify.key-links` reported 2/12 verified. Same false negative as Phases 1–3: the checker greps target *filenames* inside source; Go imports package paths. Manual wiring: + +| From | To | Via | Status | Details | +|------|----|-----|--------|---------| +| `cmd/summer/main.go` | `internal/build/scaffold.go` / `artifact.go` | make commands call `build.Make*` | ✓ WIRED | `MakePlugin` in `scaffold.go`; `MakeModel`/`Migration`/`Command`/`Job`/`AdminController` in `artifact.go` (same package). `TestMakeCommandsViaCLI` executes cobra → those funcs. | +| `internal/build/artifact.go` | `internal/build/registry.go` | `finishArtifact` → `refreshRegistry` | ✓ WIRED | Every successful artifact write refreshes `registry.gen.go` then `go mod tidy`. Plan listed `scaffold.go`; refresh lives next to the Make* writers. `MakePlugin` seeds an empty registry from `registry.tmpl`. | +| `internal/build/build.go` | `internal/build/leaf.go` | `checkModelsLeaf` before `go build` | ✓ WIRED | `App` line 40 calls `checkModelsLeaf` then `exec.CommandContext(..., "go", "build", ...)`. | +| `examples/hello/plugins/base/plugin.go` | `pact.HasLang` | `LangFS` embedded assets | ✓ WIRED | Compile-time assert `_ pact.HasLang`; `//go:embed lang`. gsd-sdk also verified this link. | +| `party/registry.go` | `phrasebook` (`loader.go` via `Activate`) | ordered HasLang scan before Boot | ✓ WIRED | `phrasebook.Activate(app, ordered)` after all `Register`; `cat.Load(p.ID(), hl.LangFS())`. | +| `phrasebook/translator.go` | `towel/context.go` | `towel.Locale` request-context seam | ✓ WIRED | `localeFrom` → `towel.Locale(ctx)`; hello test uses `towel.WithLocale`. | +| `examples/hello/plugins/base/plugin.go` | `pact.HasMailTemplates` | embedded mail FS + names | ✓ WIRED | `MailTemplatesFS`/`MailTemplates`/`MailLayouts`; gsd-sdk verified this link. | +| `party/registry.go` | `postcard` (`mailer.go`) | `postcard.Activate` + `BootPlugin` then `Publish[Mailer]` | ✓ WIRED | Activate publishes Mailer; `BootPlugin` registers declared templates/layouts and fails named missing files. | +| `postcard/mailer.go` | `postcard/drivers.go` | `driver.Send` after render | ✓ WIRED | `m.catalog.render` then `validateRendered` then `m.driver.Send`. | +| `scripts/check-phase4.sh` | `postcard/mailpit_test.go` | full `go test ./...` without `-short` | ✓ WIRED | Script runs `go test ./...` (includes `TestSMTPMailpit`) after refusing missing Docker. | +| `internal/build/build_test.go` | `internal/build/scaffold.go` | copied hello app drives public Make/Build API | ✓ WIRED | `copyHelloApp` → `MakePlugin`/`MakeModel`/… → `AddPlugin` → `App`. | +| `examples/hello/hello_test.go` | `party/registry.go` | Activate publishes translator and mailer | ✓ WIRED | `party.Activate` then `Lookup[*phrasebook.Translator]` and `Lookup[postcard.Mailer]`. | + +**Wiring:** 12/12 connections verified (manual) + +### Data-Flow Trace (Level 4) + +| Artifact | Data | Source | Produces real data | Status | +|----------|------|--------|--------------------|--------| +| `Make*` stubs | Go sources + YAML | Embedded `text/template` + `go/format` | Yes — this session `TestScaffoldAllArtifacts` `go build`/`go vet` on generated plugin | ✓ FLOWING | +| `phrasebook.Translator` | `vendor.plugin::group.key` strings | Per-plugin `lang//.yaml` | Yes — hello embedded en/pl; CLDR few/many from go-i18n category selection + YAML maps | ✓ FLOWING | +| `postcard.Mailer.Send` | subject, HTML, text | `html/template` + Goldmark + layout wrappers | Yes — memory driver stores rendered parts; Mailpit HTTP API observed the SMTP payload | ✓ FLOWING | +| SMTP driver | RFC 5322 message | go-mail `DialAndSendWithContext` | Yes — this session Mailpit `To=ada@example.test`, subject `Witaj Ada`, HTML `Ada` | ✓ FLOWING | + +## Requirements Coverage + +PLAN frontmatter IDs: CLI-02 (01, 04), I18N-01 (02, 04), I18N-03 (03, 04). REQUIREMENTS.md maps the same three to Phase 4. No Phase 4 orphans. Every ID is accounted for. + +I18N-02 (per-request `preferred_locale`) is Phase 7. CLI-03 (migrate rollback) is Phase 5. Admin schema depth is Phase 9. River adaptation of `pact.Job` is Phase 11. Those are later-phase requirements, not Phase 4 gaps. + +| Requirement | Source Plan | Description | Status | Evidence | +|-------------|-------------|-------------|--------|----------| +| CLI-02 | 01, 04 | Scaffolding commands generate a plugin, model, migration, command, job and admin controller with stubs that compile | ✓ SATISFIED | All six commands exist; generated plugin with every artifact type `go build`/`go vet` this session; CLI path proven for plugin/model/job/command | +| I18N-01 | 02, 04 | Translation keys use `vendor.plugin::group.key`, load from per-plugin per-locale YAML, parameters and CLDR plurals (go-i18n) for pl and en | ✓ SATISFIED | Hello YAML + phrasebook Get/Choice; Polish CLDR map and English pipes; `:name` case variants; go-i18n used only for category labels | +| I18N-03 | 03, 04 | Plugins register mail templates and layouts by dotted name with per-locale suffix, rendered with html/template, sent through a driver interface (SMTP via go-mail) | ✓ SATISFIED | `hello` / `hello-en` registration; memory + SMTP drivers; Mailpit receipt this session | + +**Coverage:** 3/3 requirements satisfied + +### Decision Coverage + +All trackable CONTEXT.md decisions are honored by shipped artifacts. `gsd-sdk query check.decision-coverage-verify`: **21/21 honored**, `not_honored: []`. Lang D-01–D-05, mail D-06–D-09 and D-18–D-21, scaffolding D-10–D-17. + +## Behavioral Verification + +| Check | Result | Detail | +|-------|--------|--------| +| `./scripts/check-phase4.sh` | ✓ | This session: docker available; root `go vet`/`go test ./...`; hello vet/test; `-race` `./internal/build ./phrasebook ./postcard`; printed `phase4 check passed` (~14s; some packages cached from the implementation session) | +| `go test ./internal/build ./cmd/summer -run 'TestScaffold…\|TestModelsLeaf\|TestMakeCommandsViaCLI\|TestToolCommandNames' -count=1` | ✓ | internal/build 6.196s; cmd/summer 0.262s | +| `go test ./phrasebook ./party -count=1` | ✓ | phrasebook 0.007s; party 0.009s | +| `go -C examples/hello test ./... -count=1` | ✓ | 0.595s including `TestHelloPluginResolvesLang` and `TestHelloPluginSendsMail` | +| `go test ./postcard -count=1` | ✓ | 1.913s including Mailpit | +| `go test ./postcard -run TestSMTPMailpit -count=1 -v` | ✓ | Container `axllent/mailpit:v1.31.1` created, SMTP send, HTTP API receipt, PASS 1.88s | +| `go vet ./...` and `go -C examples/hello vet ./...` | ✓ | Clean this session | +| `go test ./postcard -run TestSMTPMailpit -short` skip-only-Mailpit | ℹ not re-run | Code: `if testing.Short() { t.Skip(...) }` then `t.Fatalf` on testcontainers error — Docker absence fails the full test, matching D-19. Gate does not pass `-short`. | + +## Anti-Patterns Found + +No `FIXME` / `XXX` / `TBD` / `HACK` / `coming soon` / `not implemented` in `internal/build`, `phrasebook`, `postcard`, `cmd/summer`, `pact`, or hello plugin sources. + +| File | Line | Pattern | Severity | Impact | +|------|------|---------|----------|--------| +| `internal/build/leaf.go` | 22–80 | Models leaf check is prefix-only, non-recursive, skips plugins outside the app root | ⚠️ Warning | Advisory WR-01 from `04-REVIEW.md`. Happy-path sibling import in `models/*.go` is rejected (`TestModelsLeaf`). Nested packages / aliased replace paths are bypassable. Does not break SC1: generated stubs still compile and the documented check fires on the intended shape. | +| `internal/build/registry.go` | 80–111 | Concurrent `make:*` can lose registry entries (no flock) | ⚠️ Warning | Advisory WR-02. Single-writer `TestScaffoldAllArtifacts` is stable. Agent-parallel `make:*` is a real race, not a missing feature. | +| `phrasebook/loader.go` | 243–263 | Catalog keys keep directory spelling (`pt_BR` vs `pt-BR`) | ⚠️ Warning | Advisory WR-03. D-04 `pl-PL`→`pl` works because files live under `pl` (hello + tests). Winter `pt_BR` trees are not a Phase 4 success criterion. | +| `postcard/templates.go` | 336–409 | `Vars` can carry `template.HTML`; regex is not a sanitizer | ⚠️ Warning | Advisory WR-04. String Vars cannot drop raw `