From d0af79b496323bd136f5baee582aa1294c9c1eca Mon Sep 17 00:00:00 2001 From: Jakub Zych Date: Thu, 8 Oct 2026 12:30:03 +0200 Subject: [PATCH] docs(14.3-04): sign off the phase 14.3 security review and validation map - security review: T-14.3-20..26 added, every row pinned by a named test or gate stage - validation: per-plan map with exact tests and commands, validated and nyquist_compliant --- .../14.3-SECURITY-REVIEW.md | 66 +++++++++------- .../14.3-VALIDATION.md | 76 +++++++++++-------- 2 files changed, 82 insertions(+), 60 deletions(-) diff --git a/.planning/phases/14.3-one-command-app-scaffolding-summer-new-and-summercms-io-new/14.3-SECURITY-REVIEW.md b/.planning/phases/14.3-one-command-app-scaffolding-summer-new-and-summercms-io-new/14.3-SECURITY-REVIEW.md index a358a3e..5f9807e 100644 --- a/.planning/phases/14.3-one-command-app-scaffolding-summer-new-and-summercms-io-new/14.3-SECURITY-REVIEW.md +++ b/.planning/phases/14.3-one-command-app-scaffolding-summer-new-and-summercms-io-new/14.3-SECURITY-REVIEW.md @@ -2,23 +2,26 @@ phase: 14.3 slug: one-command-app-scaffolding status: reviewed -threats_total: 20 -threats_closed: 20 +threats_total: 27 +threats_closed: 27 threats_open: 0 -accepted_risks: 3 +accepted_risks: 4 asvs_level: 1 created: 2026-10-08 -reviewer: gsd-executor (14.3-02 Task 3, self-performed; see Reviewer Note) +reviewer: gsd-executor (14.3-02 Task 3 and 14.3-04 Task 3, self-performed; see Reviewer Note) +updated: 2026-10-08 --- -# Phase 14.3 — Security Review (plans 01 and 02) +# Phase 14.3 — Security Review (plans 01 to 04) > `summer new` (generator), the `curl | bash` bootstrap script in > `modules/sunrise`, the site route `GET /new` and the D-20 acceptance harness. > Every threat registered in 14.3-01-PLAN.md (T-14.3-01..10, SC) and -> 14.3-02-PLAN.md (T-14.3-11..19, SC) is mapped below to its source-level -> control and the evidence available now. Plan 04 adds rows T-14.3-20..26 and -> fills the "Pinned by (plan 04)" column with executed test names. +> 14.3-02-PLAN.md (T-14.3-11..19, SC), 14.3-03-PLAN.md (T-14.3-20..22) and +> 14.3-04-PLAN.md (T-14.3-23..26) is mapped below to its source-level control +> and evidence. The "Pinned by (plan 04)" column names the executed test or +> gate stage of `scripts/check-phase14.3.sh` that fails when the control +> breaks; `--security` checks this table. **Date:** 2026-10-08 **Scope:** `git diff v0.1.12..HEAD -- internal/build cmd/summer modules/lagoon modules/sunrise scripts/check-phase14.3.sh` in `summercms.go` (15 files, +1520/-15), and the site plugin commit `2157bb9` (`script.go`, `plugin.go`, `routes_test.go`, `README.md`) in `sm-summercmsio-plugin`. Read alongside `../sm-summercmsio-app/deploy/nginx/summercms.io.conf`. @@ -36,14 +39,14 @@ The plan names a security-review lane; this session had no separate auditor agen ## Verdict -20 threats in scope (T-14.3-01..19 and T-14.3-SC): 17 mitigated, 3 accepted (all low), **0 open**. No high finding; no code fix was needed. +27 threats in scope (T-14.3-01..26 and T-14.3-SC): 23 mitigated, 4 accepted (all low), **0 open**. No high finding; no code fix was needed. Every high row is pinned by a named test or gate stage (plan 04), and `bash scripts/check-phase14.3.sh --all` passed with these pins on 2026-10-08. ## ASVS L1 | ASVS | Applies | Control | Evidence | |------|---------|---------|----------| | V5 Input Validation | yes | `new.sh` `main`: `^[a-z][a-z0-9]*$` before any path use; `internal/build/new.go` `ValidateAppName`: same regex plus `token.IsKeyword`, `main`, `internal`; `--framework` and siblings accepted only when `go.mod` declares the framework module (`checkFramework`); `SUMMERCMS_REF` starting with `-` rejected (`target_commit`) | `TestNewAppSmoke`; refusal probe (bad name exit 2); plan 01 Task 2 rules check | -| V6 Cryptography | yes | `modules/lagoon/keygen.go` `GenerateAppKey` on `crypto/rand`, 32 bytes; `NewApp` `validateAppKey` rejects anything else | `TestGenerateAppKeyIs32ByteBase64`, `TestEncryptedKeyGeneratePrints32ByteBase64` | +| V6 Cryptography | yes | `modules/lagoon/keygen.go` `GenerateAppKey` on `crypto/rand`, 32 bytes; `NewApp` `validateAppKey` rejects anything else | `TestGenerateAppKey`, `TestEncryptedKeyGeneratePrints32ByteBase64`, `TestNewRequiresValidAppKey` | | V10 Malicious Code | yes | Script embedded from the framework release (`sunrise.Script`); `curl -fsSL`; HTTPS default clone URL; release tags only; tags fetched without `--force`; ancestry check; no `eval`, no `sudo`, no dynamic code strings | `TestScriptTruncationRunsNothing`, `TestBootstrapEndToEnd`, refusal probe, `--acceptance` | | V12 Files and Resources | yes | Script writes only `SUMMERCMS_DIR` (clone), `sm--app` (via `summer new`) and the Go install directory; parent must already exist; `NewApp` refuses an existing target (`os.Lstat`), creates with `os.Mkdir`, removes only what it created; `.env` 0600 | `TestScriptTruncationRunsNothing` (empty work dir), `--generator`, refusal probe | | V14 Configuration | yes | Secrets only in `.env` (gitignored before `git add -A`); `.env.example` without a key; compose publishes `127.0.0.1:54320`; `/new` served `text/plain; charset=utf-8` with `X-Content-Type-Options: nosniff` | `--generator` (`git ls-files .env` empty, mode 600), `TestNewScriptRoute` | @@ -52,29 +55,38 @@ The plan names a security-review lane; this session had no separate auditor agen | Threat ID | Category | Component | Severity | Disposition | Control (file / function) | Evidence now | Status | Pinned by (plan 04) | |-----------|----------|-----------|----------|-------------|---------------------------|--------------|--------|---------------------| -| T-14.3-01 | Tampering | app name, target path | high | mitigate | `internal/build/new.go` `ValidateAppName` before `filepath.Join(parent, appDirName(name))`; target is always `/sm--app` | `TestNewAppSmoke`; plan 01 rules check (RULES-OK) | mitigated | TestNewValidateAppName | -| T-14.3-02 | Information Disclosure | `.env`, first git commit | high | mitigate | `internal/build/new.go` `planNewApp` (`.env` mode 0600, `.gitignore` written with the files) and `initGit` (`git add -A` after `.gitignore` exists) | `scripts/check-phase14.3.sh --generator` asserts `git ls-files .env` empty and mode 600; acceptance run committed the app the same way | mitigated | TestNewEnvFileMode, TestNewGitFirstCommit | -| T-14.3-03 | Information Disclosure | app key generation | high | mitigate | `modules/lagoon/keygen.go` `GenerateAppKey` (`crypto/rand`); `internal/build/new.go` `validateAppKey` | `TestGenerateAppKeyIs32ByteBase64`; `--generator` (44-character key) | mitigated | TestGenerateAppKey, TestNewRequiresValidAppKey | -| T-14.3-04 | Tampering | existing directory, cleanup | high | mitigate | `internal/build/new.go` `NewApp`: `os.Lstat` refusal, `os.Mkdir` (not `MkdirAll`), `os.RemoveAll` only of the directory this run created; `new.sh` `main` also stops early when `sm--app` exists (`-e` or `-L`) | refusal probe: existing `sm-demo-app` stops the script before any clone, exit 1 | mitigated | TestNewRefusesExistingTarget, TestNewCleansUpOnFailure | -| T-14.3-05 | Information Disclosure | compose.yaml Postgres port | medium | mitigate | `internal/build/stubs/new.tmpl` compose: `127.0.0.1:54320:5432`, named volume only | `--generator` and `--acceptance` ran the compose file; template grep | mitigated | TestNewGolden | -| T-14.3-06 | Spoofing | framework auto-detection | medium | mitigate | `internal/build/new.go` `resolveFramework` / `checkFramework`: module path must be `git.golem15.com/golem15/summercms`; an explicit `--framework` never falls through; the script always passes `--framework` | `TestNewAppSmoke`; `--generator` (sibling detection) | mitigated | TestNewFrameworkResolution | -| T-14.3-07 | Information Disclosure | committed go.mod / go.work | low | mitigate | `internal/build/scaffold.go` `relModulePath`, relative `runGoWorkUse` | `--generator` (no absolute `use` line); `TestBootstrapEndToEnd` (relative `../summercms` replace) | mitigated | TestNewGolden | -| T-14.3-08 | Information Disclosure | compose.yaml dev credentials | low | accept | `summer`/`summer` is a documented local default for a loopback-only database; the encryption key stays in `.env` | template review | accepted | TestNewGolden | +| T-14.3-01 | Tampering | app name, target path | high | mitigate | `internal/build/new.go` `ValidateAppName` before `filepath.Join(parent, appDirName(name))`; target is always `/sm--app` | `TestNewAppSmoke`; plan 01 rules check (RULES-OK) | mitigated | TestNewValidateAppName, TestNewCommandViaCLI, TestScriptBehavior (name cases) | +| T-14.3-02 | Information Disclosure | `.env`, first git commit | high | mitigate | `internal/build/new.go` `planNewApp` (`.env` mode 0600, `.gitignore` written with the files) and `initGit` (`git add -A` after `.gitignore` exists) | `scripts/check-phase14.3.sh --generator` asserts `git ls-files .env` empty and mode 600; acceptance run committed the app the same way | mitigated | TestNewEnvFileMode, TestNewGitFirstCommit (.env untracked), TestNewGolden (dot-gitignore) | +| T-14.3-03 | Information Disclosure | app key generation | high | mitigate | `modules/lagoon/keygen.go` `GenerateAppKey` (`crypto/rand`); `internal/build/new.go` `validateAppKey` | `TestGenerateAppKey` (formerly `TestGenerateAppKeyIs32ByteBase64`); `--generator` (44-character key) | mitigated | TestGenerateAppKey, TestNewRequiresValidAppKey | +| T-14.3-04 | Tampering | existing directory, cleanup | high | mitigate | `internal/build/new.go` `NewApp`: `os.Lstat` refusal, `os.Mkdir` (not `MkdirAll`), `os.RemoveAll` only of the directory this run created; `new.sh` `main` also stops early when `sm--app` exists (`-e` or `-L`) | refusal probe: existing `sm-demo-app` stops the script before any clone, exit 1 | mitigated | TestNewRefusesExistingTarget, TestNewCleansUpOnFailure, TestNewRefusesTargetInsideFramework, TestScriptBehavior (existing app) | +| T-14.3-05 | Information Disclosure | compose.yaml Postgres port | medium | mitigate | `internal/build/stubs/new.tmpl` compose: `127.0.0.1:54320:5432`, named volume only | `--generator` and `--acceptance` ran the compose file; template grep | mitigated | TestNewGolden (compose.yaml) | +| T-14.3-06 | Spoofing | framework auto-detection | medium | mitigate | `internal/build/new.go` `resolveFramework` / `checkFramework`: module path must be `git.golem15.com/golem15/summercms`; an explicit `--framework` never falls through; the script always passes `--framework` | `TestNewAppSmoke`; `--generator` (sibling detection) | mitigated | TestNewFrameworkResolution, TestScriptBehavior (fresh clone passes --framework) | +| T-14.3-07 | Information Disclosure | committed go.mod / go.work | low | mitigate | `internal/build/scaffold.go` `relModulePath`, relative `runGoWorkUse` | `--generator` (no absolute `use` line); `TestBootstrapEndToEnd` (relative `../summercms` replace) | mitigated | TestNewGolden (go.mod, go.work), TestMakeAndAddPluginOnHelloCopy, --forbidden | +| T-14.3-08 | Information Disclosure | compose.yaml dev credentials | low | accept | `summer`/`summer` is a documented local default for a loopback-only database; the encryption key stays in `.env` | template review | accepted | TestNewGolden (compose.yaml) | | T-14.3-09 | Information Disclosure | `summer serve` default `:8080` | low | accept | Existing surf default listens on all interfaces; next steps point at `127.0.0.1`; out of scope for this phase | review | accepted | — | -| T-14.3-10 | Repudiation | fallback git identity | low | accept | `internal/build/new.go` `initGit` / `gitIdentityMissing`: fallback only for the missing keys, first commit only, announced with a warning | `--generator` and `--acceptance` printed the warning | accepted | TestNewGitUsesConfiguredIdentity | +| T-14.3-10 | Repudiation | fallback git identity | low | accept | `internal/build/new.go` `initGit` / `gitIdentityMissing`: fallback only for the missing keys, first commit only, announced with a warning | `--generator` and `--acceptance` printed the warning | accepted | TestNewGitFirstCommit, TestNewGitUsesConfiguredIdentity | | T-14.3-11 | Tampering | truncated download | high | mitigate | `modules/sunrise/new.sh`: top level holds only comments and function definitions; the last line is `main "$@"`; `main` parses arguments before any external command | `TestScriptTruncationRunsNothing` (every line-boundary prefix and every byte prefix of the final line, with stub `git`, `go`, `docker`, `curl`, `summer`; positive control proves the stubs record calls); `TestScriptShape` | mitigated | TestScriptTruncationRunsNothing, TestScriptShape | -| T-14.3-12 | Tampering | error body piped to bash | medium | mitigate | Documented one-liner uses `curl -fsSL` (no body on HTTP errors); `GET /new` answers only the embedded bytes (`script.go` `scriptHandler`); a cut-off body is covered by T-14.3-11 | `TestNewScriptRoute`; `TestBootstrapEndToEnd` runs the exact `curl -fsSL … \| bash -s -- demo` form | mitigated | TestScriptBehavior | +| T-14.3-12 | Tampering | error body piped to bash | medium | mitigate | Documented one-liner uses `curl -fsSL` (no body on HTTP errors); `GET /new` answers only the embedded bytes (`script.go` `scriptHandler`); a cut-off body is covered by T-14.3-11 | `TestNewScriptRoute`; `TestBootstrapEndToEnd` runs the exact `curl -fsSL … \| bash -s -- demo` form | mitigated | TestNewScriptRoute, TestBootstrapEndToEnd (--bootstrap) | | T-14.3-13 | Spoofing | `/new` rendered as HTML | medium | mitigate | `sm-summercmsio-plugin` `script.go` `scriptHandler`: `Content-Type: text/plain; charset=utf-8`, `X-Content-Type-Options: nosniff` via `setCommonHeaders`; no request data reflected; nginx allows GET/HEAD only (`limit_except GET HEAD`) and gzips `text/plain` | `TestNewScriptRoute` (headers, strong ETag, 304), `TestRoutesAssemble` (`POST /new` 405) | mitigated | TestNewScriptRoute | -| T-14.3-14 | Tampering | moved upstream tag, non-fast-forward | high | mitigate | `new.sh` `checkout_framework`: `git fetch --quiet --tags origin` without `--force` (git refuses to clobber a local tag and the script stops); checkout only when `git merge-base --is-ancestor HEAD `; otherwise exit 1 with the checkout unchanged | refusal probe: moved `v0.0.2` upstream → fetch refused, local tag kept at its original commit, HEAD unchanged; non-ancestor HEAD → "not a fast-forward", exit 1, HEAD unchanged | mitigated | TestScriptBehavior | -| T-14.3-15 | Tampering | user's existing checkout | high | mitigate | `new.sh` `check_checkout`: refuses a non-directory, a missing or foreign `go.mod` module, a non-git directory or a subdirectory of another repository, tracked changes (`git status --porcelain --untracked-files=no`), a branch (`git symbolic-ref -q HEAD`); the script contains no reset, clean, stash, `--force` or delete | refusal probe: branch checkout, dirty tracked file and non-framework directory each refused with exit 1 and left untouched | mitigated | TestScriptBehavior | -| T-14.3-16 | Elevation of Privilege | shell injection via name or overrides | high | mitigate | `new.sh` `main`: name regex before use; every expansion quoted (`shellcheck` clean); overrides used only as arguments (`git clone -- "$repo" "$dir"`, `-C "$dir"`); `SUMMERCMS_REF` rejected when it starts with `-`; no `eval`, `sudo` or dynamic code strings; `summer new` re-validates the name (`ValidateAppName`) | `grep -Ecw 'sudo\|eval'` = 0; `shellcheck`; refusal probe (bad name exit 2) | mitigated | TestScriptShellcheck, TestScriptBehavior | -| T-14.3-17 | Denial of Service | credential prompt hang with piped stdin | medium | mitigate | `new.sh` `main` exports `GIT_TERMINAL_PROMPT=0`; every `git`, `go`, `docker` and `summer` child runs with ``; otherwise exit 1 with the checkout unchanged | refusal probe: moved `v0.0.2` upstream → fetch refused, local tag kept at its original commit, HEAD unchanged; non-ancestor HEAD → "not a fast-forward", exit 1, HEAD unchanged | mitigated | TestScriptBehavior (moved upstream tag, non-fast-forward, re-run cases) | +| T-14.3-15 | Tampering | user's existing checkout | high | mitigate | `new.sh` `check_checkout`: refuses a non-directory, a missing or foreign `go.mod` module, a non-git directory or a subdirectory of another repository, tracked changes (`git status --porcelain --untracked-files=no`), a branch (`git symbolic-ref -q HEAD`); the script contains no reset, clean, stash, `--force` or delete | refusal probe: branch checkout, dirty tracked file and non-framework directory each refused with exit 1 and left untouched | mitigated | TestScriptBehavior (tracked change, branch, another module, not a git repository) | +| T-14.3-16 | Elevation of Privilege | shell injection via name or overrides | high | mitigate | `new.sh` `main`: name regex before use; every expansion quoted (`shellcheck` clean); overrides used only as arguments (`git clone -- "$repo" "$dir"`, `-C "$dir"`); `SUMMERCMS_REF` rejected when it starts with `-`; no `eval`, `sudo` or dynamic code strings; `summer new` re-validates the name (`ValidateAppName`) | `grep -Ecw 'sudo\|eval'` = 0; `shellcheck`; refusal probe (bad name exit 2) | mitigated | TestScriptShellcheck, TestScriptBehavior (metacharacter name, dash SUMMERCMS_REF) | +| T-14.3-17 | Denial of Service | credential prompt hang with piped stdin | medium | mitigate | `new.sh` `main` exports `GIT_TERMINAL_PROMPT=0`; every `git`, `go`, `docker` and `summer` child runs with ``; script run from a work directory holding `summercms`: | Case | Result | diff --git a/.planning/phases/14.3-one-command-app-scaffolding-summer-new-and-summercms-io-new/14.3-VALIDATION.md b/.planning/phases/14.3-one-command-app-scaffolding-summer-new-and-summercms-io-new/14.3-VALIDATION.md index 9c46f8e..5f23353 100644 --- a/.planning/phases/14.3-one-command-app-scaffolding-summer-new-and-summercms-io-new/14.3-VALIDATION.md +++ b/.planning/phases/14.3-one-command-app-scaffolding-summer-new-and-summercms-io-new/14.3-VALIDATION.md @@ -1,15 +1,16 @@ --- phase: "14.3" slug: "one-command-app-scaffolding-summer-new-and-summercms-io-new" -status: draft -nyquist_compliant: false -wave_0_complete: false +status: validated +nyquist_compliant: true +wave_0_complete: true created: "2026-10-08" +validated: "2026-10-08" --- # Phase 14.3 — Validation Strategy -> Per-phase validation contract for feedback sampling during execution. Source: 14.3-RESEARCH.md § Validation Architecture. +> Per-phase validation contract for feedback sampling during execution. Source: 14.3-RESEARCH.md § Validation Architecture. Signed off by plan 04 after `bash scripts/check-phase14.3.sh --all` ended with `Phase 14.3 gate passed` on 2026-10-08. --- @@ -19,9 +20,11 @@ created: "2026-10-08" |----------|-------| | **Framework** | Go stdlib `testing` (Go 1.27.0); `node:test` in `vue-summercmsio-app` | | **Config file** | none | -| **Quick run command** | `go test ./internal/build/ ./cmd/summer/ ./modules/lagoon/ ./modules// -short` | +| **Quick run command** | `go test ./internal/build/ ./cmd/summer/ ./modules/lagoon/ ./modules/sunrise/ -short` | | **Full suite command** | `go vet ./... && go test ./... && go -C examples/hello test ./...` | -| **Estimated runtime** | ~40 seconds quick (incl. real builds), a few minutes full | +| **Phase gate** | `bash scripts/check-phase14.3.sh --all` (stages `--unit`, `--generator`, `--bootstrap`, `--terminal`, `--acceptance`, `--security`; final line `Phase 14.3 gate passed`) | +| **Golden update** | `go test ./internal/build/ -run '^(TestNewGolden|TestNewGoldenNoPlugin)$' -update`, then review and commit the diff; the gate never passes `-update` and fails when `internal/build/testdata` changes | +| **Estimated runtime** | ~20 seconds quick (incl. real builds), ~90 seconds full suite, ~6 minutes `--all` (container acceptance) | --- @@ -29,26 +32,33 @@ created: "2026-10-08" - **After every task commit:** quick run command - **After every plan wave:** full suite + the plan's sibling-repo command (below) -- **Before `/gsd-verify-work` and before tagging `v0.1.13`:** full suite green, `go run ./cmd/summer docs:build --check` green, D-20 acceptance run recorded -- **Max feedback latency:** ~60 seconds +- **Before `/gsd-verify-work` and before tagging `v0.1.13`:** `bash scripts/check-phase14.3.sh --all` green +- **Max feedback latency:** ~60 seconds for the quick run --- ## Per-Plan Verification Map -| Plan | Behavior | Test Type | Automated Command | -|------|----------|-----------|-------------------| -| 01 | `summer new demo` in a temp parent with a `summercms` symlink to the repo builds | smoke (real build) | `go test ./internal/build/ -run 'TestNewApp' -count=1` | -| 01 | `summer new` wired in CLI with its flags | unit | `go test ./cmd/summer/ -run 'TestToolCommandNames' -count=1` | -| 01 | `GenerateAppKey` returns StdEncoding of 32 random bytes; `key:generate` uses it | unit | `go test ./modules/lagoon/ -run 'Key' -short -count=1` | -| 01 | `plugin:add` writes relative `go.work` paths | unit | `go test ./internal/build/ -run 'TestMakeAndAddPluginOnHelloCopy' -count=1` | -| 01 | `examples/hello` config gap fixed | integration | `go -C examples/hello test ./... -count=1` | -| 02 | script: shebang, `main "$@"` last line, `bash -n`, truncation runs nothing, tag selection on a temp repo | unit + gated e2e | `go test ./modules// -count=1`; gated `SUMMERCMS_BOOTSTRAP_CHECK=1 go test ./modules// -run TestBootstrapEndToEnd -count=1` | -| 02 | `GET /new` 200 `text/plain; charset=utf-8`, body = embedded script, HEAD ok, POST 405, nosniff | unit | `go -C ../sm-summercmsio-app/plugins/golem15/summercms test ./... -count=1` | -| 02 | D-20 container chain ends with starter route 200 | gated/manual | acceptance runner script or documented `docker run` command | -| 03 | docs tree valid, `summer new` known command, llms-full contains one-liner | unit | `go test ./cmd/summer -run 'TestDocsTree|TestDocsBuildRealTree' -count=1` and `go run ./cmd/summer docs:build --check` | -| 03 | landing page terminal data / tests | unit | `go -C ../sm-summercmsio-app test -run 'TestLoadTerminal|TestTerminalScript|TestTerminalEnv' .`; `pnpm -C ../sm-summercmsio-app/vue-summercmsio-app generate && pnpm -C ../sm-summercmsio-app/vue-summercmsio-app test` | -| 04 | golden output for every rendered file; name/denylist/existing-dir/framework-detection errors; relative replace; `--no-plugin`, `--no-git`; no-identity commit; cleanup on failure; real build (`-short` skip) | unit + golden | `go test ./internal/build/ -run 'TestNew' -count=1`; update goldens with `-update` | +| Plan | Behavior | Test Type | Automated Command | Status | +|------|----------|-----------|-------------------|--------| +| 01 | `summer new demo` in a temp parent with a `summercms` symlink builds and lists the starter route; `--no-plugin` builds | smoke (real build) | `go test ./internal/build/ -run '^(TestNewAppSmoke|TestNewNoPluginBuilds)$' -count=1` | ✅ | +| 01 | every file `summer new` writes, default and `--no-plugin` (go.mod relative replace, go.work, config/, .env, compose `127.0.0.1:54320`, .gitignore, CLAUDE.md, starter plugin, next steps) | golden | `go test ./internal/build/ -run '^(TestNewGolden|TestNewGoldenNoPlugin)$' -count=1` | ✅ | +| 01 | name rule and denylist; existing target (dir, file, symlink, dangling symlink); framework resolution order; target inside the checkout; cleanup on failure; key validation | unit | `go test ./internal/build/ -run '^(TestNewValidateAppName|TestNewRefusesExistingTarget|TestNewFrameworkResolution|TestNewRefusesTargetInsideFramework|TestNewCleansUpOnFailure|TestNewRequiresValidAppKey)$' -count=1` | ✅ | +| 01 | `.env` 0600; AGENTS.md symlink; first commit with fallback identity, `.env` untracked; configured identity; git missing is a warning; `--no-git` | unit | `go test ./internal/build/ -run '^(TestNewEnvFileMode|TestNewAgentsSymlink|TestNewGitFirstCommit|TestNewGitUsesConfiguredIdentity|TestNewGitMissingIsWarning|TestNewNoGit)$' -count=1` | ✅ | +| 01 | `plugin:add` writes relative `go.work` paths; plain `make:plugin` keeps the no-route stub | unit | `go test ./internal/build/ -run '^(TestMakeAndAddPluginOnHelloCopy|TestMakePluginRoutesUnchanged)$' -count=1` | ✅ | +| 01 | `summer new` wired in the CLI with its flags | unit | `go test ./cmd/summer/ -run '^(TestToolCommandNames|TestNewCommandViaCLI)$' -count=1` | ✅ | +| 01 | `GenerateAppKey` returns StdEncoding of 32 random bytes; `key:generate` uses it | unit | `go test ./modules/lagoon/ -run '^(TestGenerateAppKey|TestEncryptedKeyGeneratePrints32ByteBase64)$' -count=1` | ✅ | +| 01 | `examples/hello` config gap fixed | integration | `go -C examples/hello test ./... -count=1` | ✅ | +| 01 | D-12: generated app builds, migrates on PostgreSQL 16 and serves the starter route with no edits | e2e | `bash scripts/check-phase14.3.sh --generator` | ✅ | +| 02 | script: embedded bytes, shebang, `main "$@"` last line, `bash -n`, truncation runs nothing | unit | `go test ./modules/sunrise/ -run '^(TestScriptEmbedded|TestScriptShape|TestScriptTruncationRunsNothing)$' -count=1` | ✅ | +| 02 | script behaviour matrix (usage/name errors, existing app, Go missing/old/devel, git required, Docker warning, newest release tag, `SUMMERCMS_REF`, fast-forward re-runs, refusals with HEAD unchanged, stdin closed, cd line, PATH hint) and shellcheck | unit | `go test ./modules/sunrise/ -run '^(TestScriptBehavior|TestScriptShellcheck)$' -count=1` | ✅ | +| 02 | `curl \| bash` from an HTTP server, clone at HEAD, install into a GOBIN off PATH, build the app | gated e2e | `bash scripts/check-phase14.3.sh --bootstrap` (`SUMMERCMS_BOOTSTRAP_CHECK=1 go test ./modules/sunrise/ -run '^TestBootstrapEndToEnd$'`) | ✅ | +| 02 | `GET /new` 200 `text/plain; charset=utf-8`, body = embedded script, HEAD ok, POST 405, nosniff | unit | `go -C ../sm-summercmsio-app/plugins/golem15/summercms test ./... -count=1` (`TestNewScriptRoute`, `TestRoutesAssemble`) | ✅ | +| 02 | D-20 container chain ends with the starter route answering | e2e (Docker) | `bash scripts/check-phase14.3.sh --acceptance` | ✅ | +| 03 | docs tree valid, `summer new` a known command, llms outputs in sync | unit | `go test ./cmd/summer -run '^(TestDocsTree|TestDocsAIOutputsInSync)$' -count=1` and `go run ./cmd/summer docs:build --check` | ✅ | +| 03 | landing page terminal data and tests | unit | `go -C ../sm-summercmsio-app test ./... -count=1` (`TestLoadTerminal`, `TestTerminalScript`, `TestTerminalEnv`, `TestTerminalRunScript`); `pnpm -C ../sm-summercmsio-app/vue-summercmsio-app generate && pnpm -C ../sm-summercmsio-app/vue-summercmsio-app test` | ✅ | +| 03 | the six page commands run verbatim until the starter route answers | gated e2e | `bash scripts/check-phase14.3.sh --terminal` | ✅ | +| 04 | fail-closed gate: named PASS for every required test, forbidden surfaces, threat register | gate | `bash scripts/check-phase14.3.sh --all` | ✅ | *Status: ⬜ pending · ✅ green · ❌ red · ⚠️ flaky* @@ -56,10 +66,10 @@ created: "2026-10-08" ## Wave 0 Requirements -- [ ] `internal/build/testdata/new/` golden files + `-update` flag (no golden infra exists yet) -- [ ] new `modules//` package with README and tests -- [ ] test helper creating `/summercms` → repo-root symlink so the generated replace is deterministic (`../summercms`) -- [ ] optional acceptance runner script +- [x] `internal/build/testdata/new/` and `testdata/new-no-plugin/` golden files + `-update` flag (`internal/build/new_golden_test.go`) +- [x] new `modules/sunrise/` package with README and tests +- [x] test helper creating `/summercms` → repo-root symlink so the generated replace is deterministic (`../summercms`): `newGoldenApp`, `parentWithFramework` +- [x] acceptance runner script: `scripts/check-phase14.3.sh --acceptance` --- @@ -67,17 +77,17 @@ created: "2026-10-08" | Behavior | Why Manual | Test Instructions | |----------|------------|-------------------| -| `curl -fsSL https://summercms.io/new \| bash -s -- demo` against production | needs `v0.1.13` tag pushed and the site redeployed (user actions) | run D-20 chain in a clean `node:24-bookworm` container with Go 1.27 and the host Docker socket | +| `curl -fsSL https://summercms.io/new \| bash -s -- demo` against production | needs `v0.1.13` tagged and pushed, the site's require bumped and the site redeployed (user actions, D-28) | `curl -fsSI https://summercms.io/new` shows `200` and `text/plain; charset=utf-8`; in `sm-summercmsio-app`: `SUMMERCMS_TERMINAL_CHECK=1 go test -run TestTerminalCommands -count=1 -v .` with no overrides; in `summercms.go`: `bash scripts/check-phase14.3.sh --acceptance-production` | --- ## Validation Sign-Off -- [ ] All tasks have `` verify or Wave 0 dependencies -- [ ] Sampling continuity: no 3 consecutive tasks without automated verify -- [ ] Wave 0 covers all MISSING references -- [ ] No watch-mode flags -- [ ] Feedback latency < 60s -- [ ] `nyquist_compliant: true` set in frontmatter +- [x] All tasks have `` verify or Wave 0 dependencies +- [x] Sampling continuity: no 3 consecutive tasks without automated verify +- [x] Wave 0 covers all MISSING references +- [x] No watch-mode flags +- [x] Feedback latency < 60s +- [x] `nyquist_compliant: true` set in frontmatter -**Approval:** pending +**Approval:** approved 2026-10-08 (`bash scripts/check-phase14.3.sh --all` → `Phase 14.3 gate passed`)