Files
summercms/.planning/phases/04-cli-scaffolding-i18n-and-mail/04-VERIFICATION.md
2026-09-18 14:23:56 +02:00

205 lines
22 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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 `<strong>Ada</strong>`, 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 <plugin ID>` 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/<locale>/<group>.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/<locale>/<group>.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 `<strong>Ada</strong>` | ✓ 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 `<script>` (`TestMailRenderSmoke` / `TestMailRecipientsAndSafety`). Typed HTML bypass is a later hardening item, not SC3. |
| `postcard/drivers.go` | 163–165 | SMTP errors wrapped without extra redaction | ℹ️ Info | IN-01. Dial-to-port-1 tests assert `secretpass` is absent. |
| `phrasebook/loader.go` | 80–107 | Extra files in `LangFS` fail boot | ℹ️ Info | IN-02. Fail-closed against traversal; editor backups in `lang/` cannot boot. |
| `postcard/templates.go` | 25, 406–408 | `data:` banned in all rendered HTML | ℹ️ Info | IN-03. Conservative for v1; blocks inline `data:image/*`. |
| `internal/build/artifact.go` | 177 | Generated command `Description: "TODO: describe " + cmdName` | ℹ️ Info | Scaffold placeholder in compiling stubs (CLI-02), not unfinished production code. |
**Anti-patterns:** 8 found (0 blockers, 4 warnings, 4 info)
Code-review warnings WR-01…WR-04 do not fail the scaffolding/i18n/mail goal. No security-review agent was required for this phase (no auth/crypto surface in the ROADMAP goal).
## Test Quality Audit
| Test File | Linked Req | Active | Skipped | Circular | Assertion Level | Verdict |
|-----------|-----------|--------|---------|----------|----------------|---------|
| `internal/build/build_test.go` | CLI-02 | yes | no | no | Behavioral (`go build`/`go vet` of generated plugin; leaf-import error text) | ✓ |
| `cmd/summer/main_test.go` | CLI-02 | yes | no | no | Behavioral (cobra help + `make:*` writes files) | ✓ |
| `phrasebook/translator_test.go` | I18N-01 | yes | no | no | Value (pl/en strings, CLDR counts, pipe `{0}`, params) | ✓ |
| `party/registry_test.go` | I18N-01, I18N-03 | yes | no | no | Behavioral (translator/mailer published before Boot; named boot errors) | ✓ |
| `examples/hello/hello_test.go` | I18N-01, I18N-03 | yes | no | no | Value (Cześć/Hello/pl-PL/raw key) + Behavioral (Send hello and hello-en) | ✓ |
| `postcard/mailer_test.go` | I18N-03 | yes | no | no | Value (subject/HTML/text) + Behavioral (memory/log/smtp/env, no retry) | ✓ |
| `postcard/templates_test.go` | I18N-03 | yes | no | no | Value (recipients, escaped script, registration ownership) | ✓ |
| `postcard/smtp_test.go` | I18N-03 | yes | no | no | Behavioral (TLS policy; errors omit credentials) | ✓ |
| `postcard/mailpit_test.go` | I18N-03 | yes | `-short` only | no | Value (Mailpit To/subject/HTML/text after real SMTP) | ✓ |
**Disabled tests on requirements:** 0 blockers (`-short` skip is the documented fast loop; sign-off is `check-phase4.sh` without `-short`).
**Circular patterns detected:** 0. Expected mail subject/HTML/text are authored in the test (`Witaj Ada`, `<strong>Ada</strong>`), not captured from the SUT. CLDR expected strings are hand-written Polish/English forms, not dumped from go-i18n.
**Insufficient assertions:** 0. Scaffold proof is compile/vet, not file-exists. Translation proof is exact strings. Mail proof is Mailpit HTTP body, not `Send` returning nil alone.
**Provenance:** VALID — expected translation strings come from the YAML fixtures; expected mail parts come from the Winter-shaped templates the test registers. Mailpit is an independent SMTP observer.
## Human Verification Required
N/A — Infrastructure/tooling phase with no product UI. Acceptance is compiling stubs, catalog lookup, and a Mailpit SMTP receipt. Production SMTP credentials stay in `mail.*` / `SUMMER_MAIL__` (deployment config). VALIDATION.md: all phase behaviors have automated verification.
## Gaps Summary
**No gaps found.** Phase goal achieved. All three ROADMAP success criteria hold in the codebase and were exercised this session (forced `-count=1` scaffold/i18n/mail tests, live Mailpit receipt, `scripts/check-phase4.sh`). Advisory code-review warnings do not break must-haves.
I18N-02 (user `preferred_locale` per request) remains Phase 7 by design. `pact.Job` → River remains Phase 11. Admin `fields.yaml` pipeline remains Phase 9. Not Phase 4 gaps.
## Recommended Fix Plans
None — status is `passed`.
## Verification Metadata
**Verification approach:** Goal-backward from ROADMAP success criteria
**Must-haves source:** ROADMAP.md success criteria (override PLAN.md frontmatter)
**Automated checks:** 19 artifacts passed, 12 key links passed (manual; gsd-sdk filename-grep false negatives excluded), 3/3 truths, 3/3 requirements, 21/21 decisions
**Human checks required:** 0
**Total verification time:** ~12 min
---
*Verified: 2026-09-18T12:22:19Z*
*Verifier: the agent (subagent)*