docs(14.3): create phase plan
This commit is contained in:
@@ -899,10 +899,21 @@ Plans:
|
||||
5. `docs/setup/installation.md` leads with the one-liner, `go-modules-and-workspaces.md` and the console docs cover `summer new`, and `llms.txt`/`llms-full.txt` include the changes; docs checks pass.
|
||||
6. Generator Go tests (golden output plus a real build of the scaffold in a temp dir) are the last plan.
|
||||
|
||||
**Plans:** 0 plans
|
||||
**Plans:** 4 plans
|
||||
|
||||
Plans:
|
||||
- [ ] TBD (run /gsd-plan-phase 14.3 to break down)
|
||||
|
||||
**Wave 1**
|
||||
- [ ] 14.3-01-PLAN.md — `summer new` generator: starter plugin via make:plugin/plugin:add, relative go.work, config, .env + lagoon.GenerateAppKey, compose, CLAUDE.md/AGENTS.md, first commit, D-12 docker gate
|
||||
|
||||
**Wave 2** *(blocked on Wave 1 completion)*
|
||||
- [ ] 14.3-02-PLAN.md — Bootstrap script in `modules/sunrise`, `GET /new` in the site plugin, node:24-bookworm acceptance, security review
|
||||
|
||||
**Wave 3** *(blocked on Wave 2 completion)*
|
||||
- [ ] 14.3-03-PLAN.md — Docs (installation one-liner first, go modules, console, llms) and landing page "Get started" with its tests
|
||||
|
||||
**Wave 4** *(blocked on Wave 3 completion)*
|
||||
- [ ] 14.3-04-PLAN.md — Unit tests last: goldens, real builds, error cases, script matrix, phase gate; post-phase release checklist (tag v0.1.13, site bump, deploy are user actions)
|
||||
|
||||
### Phase 15: Journal plugin
|
||||
|
||||
|
||||
@@ -0,0 +1,303 @@
|
||||
---
|
||||
phase: 14.3-one-command-app-scaffolding-summer-new-and-summercms-io-new
|
||||
plan: 01
|
||||
type: execute
|
||||
wave: 1
|
||||
depends_on: []
|
||||
files_modified:
|
||||
- internal/build/new.go
|
||||
- internal/build/new_test.go
|
||||
- internal/build/stubs/new.tmpl
|
||||
- internal/build/stubs/plugin.tmpl
|
||||
- internal/build/scaffold.go
|
||||
- cmd/summer/main.go
|
||||
- cmd/summer/main_test.go
|
||||
- modules/lagoon/keygen.go
|
||||
- modules/lagoon/README.md
|
||||
- examples/hello/config/http.yaml
|
||||
- README.md
|
||||
- docs/setup/installation.md
|
||||
- scripts/check-phase14.3.sh
|
||||
autonomous: true
|
||||
requirements: [D-01, D-02, D-03, D-04, D-05, D-06, D-07, D-08, D-09, D-10, D-11, D-12, D-22, D-23, D-24, D-25, D-27]
|
||||
estimate:
|
||||
tokens: 150000
|
||||
raw_tokens: 150000
|
||||
tasks: 3
|
||||
confidence: low
|
||||
must_haves:
|
||||
truths:
|
||||
- "D-02/D-22: `summer new <name>` accepts only names matching ^[a-z][a-z0-9]*$ that are not a Go keyword, `main` or `internal`; any other name exits non-zero with a message naming the rule, before anything is written."
|
||||
- "D-02: when `sm-<name>-app` already exists in the current directory as a directory, file or symlink (dangling included), `summer new` exits non-zero and leaves it byte-for-byte untouched."
|
||||
- "D-03/D-04: the generated go.mod declares `module git.golem15.com/golem15/sm-<name>-app`, `go 1.27.0`, `toolchain go1.27.0`, requires `git.golem15.com/golem15/summercms v0.0.0` and replaces it with a relative path (starting ./ or ../) to the framework checkout chosen in the order --framework, ../summercms, ../summercms.go (each accepted only when its go.mod declares the framework module path), then the running binary's own source tree."
|
||||
- "Edge (missing framework): with no --framework, no matching sibling and no usable binary source, `summer new` exits non-zero, the error text contains `--framework`, and no sm-<name>-app directory remains."
|
||||
- "Edge (target inside framework): a target directory inside the resolved framework checkout is refused."
|
||||
- "D-05/D-27: the starter plugin golem15.<name> is written by the make:plugin and plugin:add code paths with a starter route GET /{$}; plain `summer make:plugin` output is unchanged; every `use` line of the generated go.work is relative (./plugins/<name>), and `summer plugin:add` now writes relative go.work paths too."
|
||||
- "D-06: `summer new <name> --no-plugin` writes summer.yaml with no plugin entries and the app still builds."
|
||||
- "D-07/D-27: config/ holds app.yaml, database.yaml, http.yaml (body_limits default_bytes 1048576, upload_bytes 10485760 as YAML numbers) and storage.yaml (uploads.bucket_url file://./storage/app/uploads); examples/hello gains the same http.yaml and `go -C examples/hello test ./...` passes."
|
||||
- "D-08/D-09/D-23: .env (mode 0600) holds SUMMER_DATABASE__DSN=postgres://summer:summer@127.0.0.1:54320/<name>?sslmode=disable and a 44-character SUMMER_APP__KEY from lagoon.GenerateAppKey; compose.yaml runs postgres:16 published only on 127.0.0.1:54320 with matching user, password and database; .env.example lists the DSN, an empty key placeholder and the commented uploads bucket."
|
||||
- "D-10/D-01: CLAUDE.md names the sm-<name>-app / sm-<name>-plugin / sm-<name>-theme convention, https://summercms.io/docs/llms.txt and https://summercms.io/docs/llms-full.txt, the commands make:plugin, plugin:add, build, dev, migrate and serve, and says not to edit the framework; AGENTS.md is a relative symlink to CLAUDE.md."
|
||||
- "D-11: without --no-git the app is a git repository with exactly one commit on branch main whose tree excludes .env; with no git identity configured the commit uses the fallback author SummerCMS <summer@localhost> for that commit only; a git failure prints a warning and the app stays."
|
||||
- "D-24: `summer new` prints next steps listing `docker compose up -d --wait`, `summer build`, `summer migrate` and `summer serve`."
|
||||
- "D-25: the app root has no go.sum; .gitignore lists /bin/, /tmp/, /storage/, .env, go.work.sum and *.exe; go.work, main.go and plugins.gen.go are committed."
|
||||
- "Edge (partial failure): any error after the target directory was created removes that directory, so a re-run with the same name is not blocked."
|
||||
- "D-12: with `docker compose up -d --wait` running, `summer build`, `summer migrate` and `summer serve` succeed in the generated app with no edits and GET http://127.0.0.1:8080/ answers 200 JSON containing \"plugin\":\"golem15.<name>\"."
|
||||
prohibitions:
|
||||
- requirement_id: D-08
|
||||
category: privacy
|
||||
status: resolved
|
||||
verification: judgment
|
||||
resolution: flagged-unverified
|
||||
reason: "Enforced by the generator gate and plan 04 tests; descriptor-less, human review recommended."
|
||||
statement: "summer new must never commit .env or any application key, and .env.example must never carry a real key."
|
||||
- requirement_id: D-03
|
||||
category: privacy
|
||||
status: resolved
|
||||
verification: judgment
|
||||
resolution: flagged-unverified
|
||||
reason: "Golden go.mod/go.work in plan 04 pin relative paths; descriptor-less, human review recommended."
|
||||
statement: "Committed go.mod and go.work files must never contain absolute, machine-specific paths."
|
||||
- requirement_id: D-23
|
||||
category: safety
|
||||
status: resolved
|
||||
verification: judgment
|
||||
resolution: flagged-unverified
|
||||
reason: "compose.yaml golden in plan 04; descriptor-less, human review recommended."
|
||||
statement: "The generated compose.yaml must never publish PostgreSQL on an address other than 127.0.0.1."
|
||||
- requirement_id: D-10
|
||||
category: transparency
|
||||
status: resolved
|
||||
verification: judgment
|
||||
resolution: flagged-unverified
|
||||
reason: "Framework README rule applies to template text; descriptor-less, human review recommended."
|
||||
statement: "Generated template text must never name a consuming application."
|
||||
- requirement_id: D-05
|
||||
category: safety
|
||||
status: resolved
|
||||
verification: judgment
|
||||
resolution: flagged-unverified
|
||||
reason: "Project rules 4 and 5; descriptor-less, human review recommended."
|
||||
statement: "The phase must add no new Go dependency and no runtime plugin loading."
|
||||
artifacts:
|
||||
- path: "internal/build/new.go"
|
||||
provides: "NewOptions, NewApp, ValidateAppName, planNewApp, framework resolution"
|
||||
contains: "func NewApp("
|
||||
- path: "internal/build/stubs/new.tmpl"
|
||||
provides: "templates for go.mod, config/*, .env, .env.example, compose.yaml, .gitignore, CLAUDE.md, next steps"
|
||||
contains: "new/compose.yaml"
|
||||
- path: "internal/build/stubs/plugin.tmpl"
|
||||
provides: "routes.go with opt-in starter route"
|
||||
contains: "StarterRoute"
|
||||
- path: "cmd/summer/main.go"
|
||||
provides: "summer new command with --framework, --no-plugin, --no-git"
|
||||
contains: "\"new\""
|
||||
- path: "modules/lagoon/keygen.go"
|
||||
provides: "exported key generator reused by key:generate and summer new"
|
||||
contains: "func GenerateAppKey("
|
||||
- path: "examples/hello/config/http.yaml"
|
||||
provides: "body_limits for serve and route:list"
|
||||
contains: "default_bytes"
|
||||
- path: "scripts/check-phase14.3.sh"
|
||||
provides: "generator stage: D-12 docker compose build/migrate/serve chain"
|
||||
contains: "--generator"
|
||||
key_links:
|
||||
- from: "cmd/summer/main.go"
|
||||
to: "modules/lagoon/keygen.go"
|
||||
via: "newCommand passes lagoon.GenerateAppKey() into build.NewOptions.AppKey"
|
||||
pattern: "GenerateAppKey"
|
||||
- from: "internal/build/new.go"
|
||||
to: "internal/build/scaffold.go"
|
||||
via: "starter plugin through makePlugin(StarterRoute) and AddPlugin, then generate()"
|
||||
pattern: "makePlugin("
|
||||
- from: "internal/build/scaffold.go"
|
||||
to: "go.work"
|
||||
via: "runGoWorkUse passes a path relative to the go.work directory"
|
||||
pattern: "go\", \"work\", \"use\""
|
||||
---
|
||||
|
||||
<objective>
|
||||
Add `summer new <name>`: one command that writes a ready-to-run `sm-<name>-app/` (go.mod with a relative framework replace, go.work, summer.yaml, a starter plugin with an HTTP route, complete config/, .env with a fresh key, .env.example, compose.yaml, .gitignore, CLAUDE.md/AGENTS.md, first git commit) in the current directory, reusing the make:plugin, plugin:add, generate and key:generate code paths.
|
||||
|
||||
Purpose: remove the hand-written go.mod/summer.yaml/config/ step from starting an app (phase goal, roadmap success criteria 1 and 2) and fix the two shared-path defects research found (absolute go.work paths, no starter route option) plus the examples/hello body_limits gap.
|
||||
Output: generator in internal/build, CLI command, exported lagoon.GenerateAppKey, examples/hello fix, generator gate stage proving D-12 on real PostgreSQL 16.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@~/.claude/gsd-core/workflows/execute-plan.md
|
||||
@~/.claude/gsd-core/templates/summary.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
@.planning/PROJECT.md
|
||||
@.planning/STATE.md
|
||||
@.planning/phases/14.3-one-command-app-scaffolding-summer-new-and-summercms-io-new/14.3-CONTEXT.md
|
||||
@.planning/phases/14.3-one-command-app-scaffolding-summer-new-and-summercms-io-new/14.3-RESEARCH.md
|
||||
@.planning/phases/14.3-one-command-app-scaffolding-summer-new-and-summercms-io-new/14.3-VALIDATION.md
|
||||
@internal/build/scaffold.go
|
||||
@internal/build/build.go
|
||||
@internal/build/manifest.go
|
||||
@internal/build/stubs/plugin.tmpl
|
||||
@cmd/summer/main.go
|
||||
@modules/lagoon/keygen.go
|
||||
</context>
|
||||
|
||||
## Spec-less probe fallback
|
||||
|
||||
Phase 14.3 has no REQUIREMENTS.md IDs and no SPEC.md, so requirement probing is visibly skipped. The acceptance contract is D-01..D-28 from 14.3-CONTEXT.md, the six roadmap success criteria, and the edge predicates and prohibitions authored into `must_haves` above (name validation, existing directory, missing framework, target inside framework, partial failure, no git identity; no committed secrets, no absolute paths, loopback-only Postgres, neutral template text, no new dependencies or runtime plugin loading).
|
||||
|
||||
## Artifacts this phase produces (this plan)
|
||||
|
||||
- `build.NewOptions` (fields `Name`, `Parent`, `Framework`, `AppKey`, `NoPlugin`, `NoGit`), `build.NewApp(ctx, opts, out) (string, error)`, `build.ValidateAppName(name) error`.
|
||||
- Unexported: `planNewApp`, `genFile`, `resolveFramework`, `frameworkFromBinary` (package variable, replaceable in tests), `binarySourceFramework`, `makePlugin` + `pluginOptions{StarterRoute}`, `stubData.StarterRoute`, a relative-path helper shared by `editAppModule`, `runGoWorkUse` and `NewApp`.
|
||||
- Template file `internal/build/stubs/new.tmpl` with defines `new/go.mod`, `new/config/app.yaml`, `new/config/database.yaml`, `new/config/http.yaml`, `new/config/storage.yaml`, `new/env`, `new/env.example`, `new/compose.yaml`, `new/gitignore`, `new/CLAUDE.md`, `new/next-steps`.
|
||||
- CLI: `summer new <name>` with flags `--framework <path>`, `--no-plugin`, `--no-git`.
|
||||
- `lagoon.GenerateAppKey() (string, error)`; `lagoon.KeyGenerateCommand` now calls it.
|
||||
- `examples/hello/config/http.yaml`.
|
||||
- `scripts/check-phase14.3.sh` with the `--generator` stage (final line `Phase 14.3 generator check passed`).
|
||||
- Generated app layout: `go.mod`, `go.work`, `summer.yaml`, `main.go`, `plugins.gen.go`, `config/{app,database,http,storage}.yaml`, `.env` (0600), `.env.example`, `compose.yaml` (postgres:16 on 127.0.0.1:54320, user/password `summer`, database `<name>`), `.gitignore`, `CLAUDE.md`, `AGENTS.md` -> `CLAUDE.md`, `plugins/<name>/` (starter plugin `golem15.<name>`, route `GET /{$}`).
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="tracer">
|
||||
<name>Task 1: `summer new demo --framework <path>` writes an app whose starter route builds and lists</name>
|
||||
<reversibility rating="costly">D-02 name rule (plus the D-22 denylist) and the `sm-<name>-app` directory name are a public CLI contract; loosening the denylist later is non-breaking, changing the regex or directory shape is not.</reversibility>
|
||||
<files>internal/build/new.go, internal/build/stubs/new.tmpl, internal/build/stubs/plugin.tmpl, internal/build/scaffold.go, cmd/summer/main.go, cmd/summer/main_test.go, internal/build/new_test.go</files>
|
||||
<read_first>internal/build/scaffold.go (MakePlugin, AddPlugin, editAppModule lines 318-334, ensureGoWork, writeGoWork, runGoWorkUse, parseGoMod, underRoot, findGoWork, applyGoWork, renderStub), internal/build/build.go (generate, writeIfChanged), internal/build/manifest.go (SaveManifest, LoadManifest, validateBinary), internal/build/stubs/plugin.tmpl (routes.go and go.mod defines), internal/build/build_test.go (copyHelloApp, TestMakeAndAddPluginOnHelloCopy, assertScaffoldFiles), cmd/summer/main.go (makePluginCommand, addPluginCommand, flagTrue), cmd/summer/main_test.go (TestToolCommandNames helpWants), modules/pact/capabilities.go (Router.Get), .planning/phases/14.3-one-command-app-scaffolding-summer-new-and-summercms-io-new/14.3-RESEARCH.md (Q1, Q3, Q6, Pattern 1-4, Pitfalls 1-3)</read_first>
|
||||
<action>Wire the single happy path end to end, production quality, per D-02, D-03, D-05, D-07, D-22, D-25 and D-27.
|
||||
|
||||
Shared code paths (D-05, D-27): add `StarterRoute bool` to `stubData`; move the body of `MakePlugin` into an unexported `makePlugin(ctx, startDir, id string, opts pluginOptions)` with `pluginOptions{StarterRoute bool}`, and make `MakePlugin` call it with zero options so `summer make:plugin` output stays byte-identical. In the existing `routes.go` define of plugin.tmpl add an `{{if .StarterRoute}}` branch that imports `encoding/json` and `net/http` and registers `r.Get("/{$}", ...)` writing `Content-Type: application/json` and the JSON object with keys `message` = `Hello from SummerCMS` and `plugin` = `p.ID()`; the `{{else}}` branch is the current body unchanged. Do not add a second plugin template.
|
||||
|
||||
Relative go.work (D-27): extract the "./"-prefix logic of `editAppModule` into one helper that turns an absolute target into a slash-separated path relative to a base directory, prefixed with `./` unless it is `.`, `..` or starts with `../`. Use it in `editAppModule` (same behaviour as today), and in `runGoWorkUse`: run `go work use <relative path>` with `cmd.Dir` set to the directory that holds the go.work file, the path computed relative to that directory. This changes `summer plugin:add` output from absolute to relative paths.
|
||||
|
||||
Generator (`internal/build/new.go`): exported `NewOptions` (this task uses `Name`, `Parent`, `Framework`), `ValidateAppName(name string) error` and `NewApp(ctx context.Context, opts NewOptions, out io.Writer) (string, error)`. `ValidateAppName` enforces `^[a-z][a-z0-9]*$` and additionally rejects `go/token.IsKeyword(name)`, `main` and `internal`, each with an error that names the rule and says why (D-22). `NewApp`: validate the name first; make `Parent` absolute with `filepath.Abs` (lexical, never EvalSymlinks); target is `Parent/sm-<name>-app`; `os.Lstat` the target and refuse when anything exists there (D-02, no merge); resolve `Framework` relative to `Parent` when not absolute, read its go.mod with `parseGoMod` and require module `frameworkModule`, else error; create the target with `os.Mkdir`. Then write, in this order: go.mod rendered from `new/go.mod` (module `git.golem15.com/golem15/sm-<name>-app`, `goVersionLine`, `toolchainLine`, require framework `v0.0.0`, replace to the relative framework path from the helper, D-03); go.work through `writeGoWork` BEFORE the plugin step so an ancestor go.work is never used (research Pattern 2); summer.yaml through `SaveManifest` with module, binary `<name>` and no plugins; `config/app.yaml` (name, debug false, empty key with a comment naming the env override), `config/database.yaml` (empty dsn with a comment naming the env override), `config/http.yaml` (body_limits default_bytes 1048576, upload_bytes 10485760 as YAML numbers with a comment that env values are strings), `config/storage.yaml` (uploads bucket_url `file://./storage/app/uploads`, public_path_prefix `/storage/uploads`) (D-07). Render these through a pure `planNewApp` step that returns a slice of `genFile{Rel, Body, Mode}` and reads nothing but the framework go.mod, so plan 04 can golden-test it. Then `makePlugin(ctx, target, "golem15."+name, pluginOptions{StarterRoute: true})`, `AddPlugin(ctx, target, "plugins/"+name)`, `LoadManifest`, and `generate(target, m)` so main.go and plugins.gen.go exist. Never run `go mod tidy` at the app root (D-25: no root go.sum). Templates live in the new `internal/build/stubs/new.tmpl` (picked up by the existing `stubs/*.tmpl` embed) with define names prefixed `new/` so they cannot collide with plugin defines.
|
||||
|
||||
CLI: add `newCommand()` to `toolCommands()` in cmd/summer/main.go: name `new`, description `Create a SummerCMS application in sm-<name>-app`, required arg `name` (description states the letters/digits rule and the Go keyword, main and internal exclusions), flag `framework` (path to the framework checkout). Run uses `os.Getwd()` as `Parent`, prints the created directory. Add `new` to the required names in `TestToolCommandNames` and a helpWants entry `"new": {"--framework"}`.
|
||||
|
||||
Smoke test `TestNewAppSmoke` in internal/build/new_test.go (skip under `testing.Short()`, D-21): temp parent with a `summercms` symlink to the repository root (abs of `../..`); `NewApp` with `Framework` = that symlink path; assert go.mod contains `replace git.golem15.com/golem15/summercms => ../summercms`, go.work contains `./plugins/demo` and no line whose first non-blank character is `/`, `App(ctx, dir, &buf)` succeeds, and `bin/demo route:list` run with `cmd.Dir` = app dir prints `/{$}` and `golem15.demo`.</action>
|
||||
<verify>
|
||||
<automated>go vet ./internal/build/ ./cmd/summer/ && go test ./internal/build/ -run '^(TestNewAppSmoke|TestMakeAndAddPluginOnHelloCopy|TestScaffoldPluginSmoke|TestAddPluginRejectsConflictingID)$' -count=1 -v && go test ./cmd/summer/ -run '^TestToolCommandNames$' -count=1 -v</automated>
|
||||
<fails_when>Non-zero exit; any "--- FAIL"; "no tests to run"; or the output lacks "--- PASS: TestNewAppSmoke", "--- PASS: TestMakeAndAddPluginOnHelloCopy" or "--- PASS: TestToolCommandNames".</fails_when>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- `grep -c 'StarterRoute' internal/build/stubs/plugin.tmpl` is at least 1 and plugin.tmpl still has exactly one `{{define "routes.go"}}`.
|
||||
- `grep -c '{{define "new/' internal/build/stubs/new.tmpl` is at least 5 (go.mod and four config files).
|
||||
- `grep -n 'func NewApp(\|func ValidateAppName(\|type NewOptions struct\|func planNewApp(' internal/build/new.go` prints four lines.
|
||||
- `go run ./cmd/summer new --help` output contains `--framework` and `name`.
|
||||
- TestNewAppSmoke passes: relative replace `../summercms`, relative go.work, build succeeds, route:list shows `GET /{$}` for `golem15.demo`.
|
||||
- `TestMakeAndAddPluginOnHelloCopy` still passes (make:plugin output unchanged, plugin:add relative).
|
||||
- `go vet ./...` and `go test ./... -short` pass at the commit.
|
||||
</acceptance_criteria>
|
||||
<done>`summer new demo --framework <path>` writes an app with a relative replace, a relative go.work, config/ and a starter plugin whose `GET /{$}` route compiles and is listed by route:list; committed as one feat commit in summercms.go.</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: Framework auto-detection, refusals, cleanup, --no-plugin, and the examples/hello config gap</name>
|
||||
<files>internal/build/new.go, cmd/summer/main.go, examples/hello/config/http.yaml, README.md, docs/setup/installation.md</files>
|
||||
<read_first>internal/build/new.go (from Task 1), internal/build/scaffold.go (parseGoMod, underRoot), cmd/summer/main.go (newCommand, flagTrue), examples/hello/hello_test.go (TestTypedItemRoute), examples/hello/config/app.yaml, README.md (Quick start step 2 and Known issues, lines 19-68), docs/setup/installation.md ("Configure the application" and the WARNING callout, lines 89-126), .planning/phases/14.3-one-command-app-scaffolding-summer-new-and-summercms-io-new/14.3-RESEARCH.md (Q2, Q3 examples/hello gap, Q6 binary source, Pitfalls 2 and 4)</read_first>
|
||||
<action>Complete the D-04 resolution order, the D-06 flag and the D-07 hello fix.
|
||||
|
||||
Framework resolution (D-04): in new.go add `resolveFramework(parent, flag string) (string, error)`. Order: (1) `--framework` when set: resolved against `parent`, must have a go.mod declaring `frameworkModule`, otherwise error naming the path and the module it found (never fall through); (2) `parent/summercms`, then `parent/summercms.go`, each accepted only when its go.mod declares `frameworkModule` (detection by module path, not folder name; a non-matching candidate is skipped); (3) the running binary's source tree via `binarySourceFramework()`, which uses `runtime.Caller(0)`, ignores a non-absolute file path (a `-trimpath` build), climbs from `internal/build/new.go` to the repository root and accepts it only when its go.mod declares `frameworkModule`; call it through an unexported package variable `frameworkFromBinary` so tests can replace it; (4) otherwise an error that names `--framework` and lists the sibling names it looked for. Keep every candidate path lexical (absolute, not symlink-resolved) so the relative replace for a symlinked sibling is `../summercms`.
|
||||
|
||||
Refusals and cleanup: refuse when the target directory would be inside the resolved framework checkout (use `underRoot`), with a message telling the user to run `summer new` next to the checkout. Refuse when `Parent` is not an existing directory. After `os.Mkdir` of the target succeeds, any later error removes the target with `os.RemoveAll` (it did not exist before this run, D-02) and returns the original error with context; nothing else is ever removed.
|
||||
|
||||
`--no-plugin` (D-06; research Q2 verified an empty plugin list builds and serves): add `NoPlugin` to `NewOptions` and the bare flag `no-plugin` to `newCommand` (read with `flagTrue`); when set skip `makePlugin` and `AddPlugin` and still call `generate`, so summer.yaml lists no plugins and go.work only `use .`.
|
||||
|
||||
examples/hello (D-07, D-27): add `examples/hello/config/http.yaml` with the same `body_limits` block the generator writes (default_bytes 1048576, upload_bytes 10485760). In README.md delete Quick start step 2 (the `config/http.yaml` instruction) and the first Known issues bullet about the missing `http.body_limits`; in docs/setup/installation.md delete the paragraph and yaml fence that tell the reader to create `config/http.yaml` and the matching first bullet of the WARNING callout. Leave the remaining text (the main.go bullet, the one-liner rewrite) to plan 03, which rewrites both pages.</action>
|
||||
<verify>
|
||||
<automated>go vet ./internal/build/ ./cmd/summer/ && go test ./internal/build/ ./cmd/summer/ -count=1 && go -C examples/hello test ./... -count=1 && go run ./cmd/summer docs:build --check && bash -c 'set -e; d=$(mktemp -d); go build -trimpath -o "$d/summer" ./cmd/summer; cd "$d"; for n in go main internal func Demo 1x a-b; do if ./summer new "$n" >out 2>&1; then echo "accepted $n"; exit 1; fi; done; mkdir sm-taken-app; if ./summer new taken >out 2>&1; then exit 1; fi; if ./summer new nofw >out 2>&1; then exit 1; fi; grep -q -- "--framework" out; test ! -e sm-nofw-app; ln -s "$OLDPWD" summercms; ./summer new bare --no-plugin; grep -q "replace git.golem15.com/golem15/summercms => ../summercms" sm-bare-app/go.mod; (cd sm-bare-app && ../summer build); echo RULES-OK'</automated>
|
||||
<fails_when>Non-zero exit; any "FAIL" line; "accepted" printed for a rejected name; `docs:build --check` reports problems; or the output lacks the final "RULES-OK" line.</fails_when>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- `grep -n 'func resolveFramework(\|frameworkFromBinary\|func binarySourceFramework(' internal/build/new.go` prints at least three lines.
|
||||
- A `-trimpath` summer with no sibling checkout fails `summer new nofw` with `--framework` in the error and leaves no `sm-nofw-app`.
|
||||
- `summer new` refuses `go`, `main`, `internal`, `func`, `Demo`, `1x`, `a-b` and an existing `sm-taken-app`.
|
||||
- With a `summercms` symlink sibling and no flag, `summer new bare --no-plugin` writes `replace ... => ../summercms` and `summer build` succeeds.
|
||||
- `examples/hello/config/http.yaml` exists with `default_bytes: 1048576`; `go -C examples/hello test ./...` passes (TestTypedItemRoute green).
|
||||
- README.md and docs/setup/installation.md no longer instruct creating `config/http.yaml`; `docs:build --check` passes.
|
||||
</acceptance_criteria>
|
||||
<done>Framework resolution follows D-04 with verified module paths, refusals and cleanup protect the user's files, `--no-plugin` builds, and examples/hello serves without a manual config step; committed as separate commits for the generator change and the hello fix.</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 3: .env with a fresh key, compose, .gitignore, CLAUDE.md/AGENTS.md, first commit, next steps, and the D-12 docker gate</name>
|
||||
<precondition>The Docker daemon is reachable (`docker info` exits 0), the postgres:16 image can be pulled, and host ports 54320 and 8080 are free.</precondition>
|
||||
<files>modules/lagoon/keygen.go, modules/lagoon/README.md, internal/build/new.go, internal/build/new_test.go, internal/build/stubs/new.tmpl, cmd/summer/main.go, scripts/check-phase14.3.sh</files>
|
||||
<read_first>modules/lagoon/keygen.go, modules/lagoon/keygen_test.go, modules/lagoon/README.md (API reference rows near line 147-150 and the app.key config row), modules/lagoon/encrypted.go (encryptedKeySize, decodeAppKey), modules/compass/env.go (.env parsing, quotes, export prefix), internal/build/new.go, internal/build/stubs/new.tmpl, cmd/summer/main.go, scripts/check-phase14.2.1.sh (gate script conventions: set -euo pipefail, env overrides, final pass line), ../sm-summercmsio-app/.gitignore, ../sm-summercmsio-app/config/app.yaml, ../sm-summercmsio-app/CLAUDE.md (tone reference only; do not copy app-specific text), .planning/phases/14.3-one-command-app-scaffolding-summer-new-and-summercms-io-new/14.3-RESEARCH.md (Q4, Q5, compose.yaml shape, Pitfalls 5, 10, 11)</read_first>
|
||||
<action>Make the generated app run with no edits (D-08 to D-12, D-23 to D-25).
|
||||
|
||||
Key (D-08): export `GenerateAppKey() (string, error)` in modules/lagoon/keygen.go returning `base64.StdEncoding` of `encryptedKeySize` bytes from `crypto/rand`; `KeyGenerateCommand` calls it and still prints through `out.Success` (output unchanged). Add the `lagoon.GenerateAppKey` row to the API reference table of modules/lagoon/README.md in the same commit (project documentation rule). `internal/build` stays free of a lagoon import: add `AppKey` to `NewOptions`; `NewApp` errors when it is empty or does not base64-decode to 32 bytes; `newCommand` calls `lagoon.GenerateAppKey()` and passes the result. Update `TestNewAppSmoke` to pass a fixed valid 44-character test key and `NoGit: true` so it stays hermetic.
|
||||
|
||||
Files, all rendered by `planNewApp` from new.tmpl defines (golden-testable with a fixed key):
|
||||
- `.env` (mode 0600): `SUMMER_DATABASE__DSN=postgres://summer:summer@127.0.0.1:54320/<name>?sslmode=disable` and `SUMMER_APP__KEY=<key>`, unquoted, no `$` characters, with a header comment that the file holds secrets and is gitignored.
|
||||
- `.env.example` (0644): the same DSN, `SUMMER_APP__KEY=` empty with a comment on generating one with `./bin/<name> key:generate` (paste only the key), a commented `SUMMER_STORAGE__UPLOADS__BUCKET_URL=file://./storage/app/uploads`, and a commented `SUMMER_ENV`.
|
||||
- `compose.yaml` (D-09, D-23): one service `postgres` with image `postgres:16`, environment POSTGRES_USER `summer`, POSTGRES_PASSWORD `summer`, POSTGRES_DB `<name>`, ports `127.0.0.1:54320:5432` only, a named volume `pgdata` (no bind mounts), and a `pg_isready -U summer -d <name>` healthcheck (interval 2s, timeout 3s, retries 30) so `docker compose up -d --wait` returns once the database accepts connections.
|
||||
- `.gitignore` (D-08, D-25): `/bin/`, `/tmp/`, `/storage/`, `.env`, `go.work.sum`, `*.exe`.
|
||||
- `CLAUDE.md` (D-10, D-01): states this is a SummerCMS application built from `git.golem15.com/golem15/summercms`, replaced by the local checkout at the relative replace path, which agents must not edit from here; links https://summercms.io/docs/llms.txt and https://summercms.io/docs/llms-full.txt; lists `summer make:plugin`, `summer plugin:add`, `summer build`, `summer dev`, `summer migrate`, `summer serve` and `docker compose up -d --wait`; explains summer.yaml, plugins/ (starter plugin golem15.<name> only when present), config/, `.env` (secrets, never commit), compose.yaml, and that main.go and plugins.gen.go are generated by `summer build`; states plugins are compiled in at build time; states the Golem15 naming convention: `sm-<name>-app` application, `sm-<name>-plugin` standalone plugin module, `sm-<name>-theme` Nuxt/Vue frontend. Use only neutral wording (no consuming application names).
|
||||
- `AGENTS.md`: a relative symlink to `CLAUDE.md` (planner's call under D-10: one source, survives git clone on Linux and macOS); represent it in `genFile` as a link target.
|
||||
|
||||
Git (D-11): add `NoGit` to `NewOptions` and the bare flag `no-git` to `newCommand`. Unless set: look up `git` on PATH (missing means a warning and no repository); run `git init --quiet --initial-branch=main`, `git add -A`, then `git commit --quiet -m "Create sm-<name>-app with summer new"` with stdin closed and `GIT_TERMINAL_PROMPT=0`. When `git config user.name` or `git config user.email` (run inside the new repository) is unset and the matching `GIT_AUTHOR_*`/`GIT_COMMITTER_*` variables are absent, pass `-c user.name=SummerCMS -c user.email=summer@localhost` to that commit only and print a warning saying so. Any git failure (for example signing with an unavailable key) prints a warning with git's output and keeps the app. The commit message carries no trailers.
|
||||
|
||||
Next steps (D-24): after success, print from the `new/next-steps` define: the created directory, then "Next steps, inside sm-<name>-app:" followed by `docker compose up -d --wait`, `summer build`, `summer migrate`, `summer serve`, then `curl http://127.0.0.1:8080/` (with `--no-plugin`, a line pointing at `summer make:plugin` and `summer plugin:add` instead of the curl). Do not print a `cd` line (D-24 assigns it to the bootstrap script).
|
||||
|
||||
Gate (D-12): create executable `scripts/check-phase14.3.sh` (bash, `set -euo pipefail`, `PHASE143_*` overrides, root from the script location) with stage `--generator`: preflight `docker info` and that 127.0.0.1:54320 and :8080 are free (fail with a clear message otherwise); build the CLI with `go build -o "$tmp/bin/summer" ./cmd/summer` and put `$tmp/bin` first on PATH; in a temp parent create a `summercms` symlink to the repo root; run `summer new demo` WITHOUT `--framework` and with HOME set to an empty temp directory and `GIT_CONFIG_NOSYSTEM=1` for that one command (export GOMODCACHE, GOCACHE and GOPATH from `go env` first so the module cache is reused) to exercise sibling detection and the fallback identity; assert `.env` mode is 600, `git -C sm-demo-app rev-list --count HEAD` is 1, `git -C sm-demo-app ls-files .env` prints nothing, `.env.example`, `go.work`, `main.go`, `plugins.gen.go` are tracked, `AGENTS.md` is a symlink to `CLAUDE.md`, there is no root `go.sum`, and go.work has no absolute `use` path; then in the app with a unique `COMPOSE_PROJECT_NAME` run `docker compose up -d --wait`, `summer build`, `summer migrate`, start `summer serve` in the background, poll `http://127.0.0.1:8080/` for up to 60 s and require the body to contain `"plugin":"golem15.demo"`; a trap stops the server and runs `docker compose down -v` on every exit. End with the line `Phase 14.3 generator check passed`. Commit the lagoon change, the generator change and the gate script as separate commits.</action>
|
||||
<verify>
|
||||
<automated>go vet ./... && go test ./modules/lagoon/ -run 'Key' -short -count=1 -v && go test ./internal/build/ -run '^TestNewAppSmoke$' -count=1 -v && bash scripts/check-phase14.3.sh --generator</automated>
|
||||
<fails_when>Non-zero exit; any "--- FAIL"; "--- PASS: TestNewAppSmoke" or "--- PASS: TestEncryptedKeyGeneratePrints32ByteBase64" absent; or the output lacks the final line "Phase 14.3 generator check passed".</fails_when>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- `grep -n 'func GenerateAppKey() (string, error)' modules/lagoon/keygen.go` prints one line and `grep -c 'lagoon.GenerateAppKey' modules/lagoon/README.md` is at least 1.
|
||||
- `grep -c '127.0.0.1:54320:5432' internal/build/stubs/new.tmpl` is 1 and `grep -c 'postgres:16' internal/build/stubs/new.tmpl` is at least 1.
|
||||
- `grep -c 'https://summercms.io/docs/llms-full.txt' internal/build/stubs/new.tmpl` is at least 1 and `grep -c 'sm-<name>-theme\|-theme' internal/build/stubs/new.tmpl` is at least 1.
|
||||
- `grep -Eci 'fonoteka|p[lł]ytarium' internal/build/stubs/new.tmpl` prints 0.
|
||||
- The `--generator` stage passes: sibling detection without `--framework`, fallback identity commit, `.env` 0600 and untracked, AGENTS.md symlink, no root go.sum, relative go.work, and the starter route answers 200 JSON after `docker compose up -d --wait`, `summer build`, `summer migrate`, `summer serve`.
|
||||
- `go run ./cmd/summer new --help` lists `--framework`, `--no-plugin` and `--no-git`.
|
||||
- `go vet ./...` and `go test ./... -short` pass; `git diff v0.1.12 -- go.mod` adds no require line.
|
||||
</acceptance_criteria>
|
||||
<done>A generated app holds a fresh key, a loopback-only PostgreSQL 16 compose file, a secret-free first commit and agent instructions, prints its next steps, and the gate proves `summer build`, `summer migrate` and `summer serve` work with no edits.</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<threat_model>
|
||||
## Trust Boundaries
|
||||
|
||||
| Boundary | Description |
|
||||
|----------|-------------|
|
||||
| CLI argument and flag -> filesystem | `<name>` and `--framework` become paths and module names |
|
||||
| Generator -> git history | Generated files enter the first commit, which may be pushed |
|
||||
| Generated compose.yaml -> host network | Docker publishes the dev database on the host |
|
||||
| crypto/rand -> .env | The application encryption key is created and stored |
|
||||
|
||||
## STRIDE Threat Register
|
||||
|
||||
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|
||||
|-----------|----------|-----------|----------|-------------|-----------------|
|
||||
| T-14.3-01 | Tampering | `ValidateAppName`, target path | high | mitigate | Regex `^[a-z][a-z0-9]*$` plus keyword/main/internal denylist before any path is built; target is always `Parent/sm-<name>-app` |
|
||||
| T-14.3-02 | Information Disclosure | `.env`, first git commit | high | mitigate | `.gitignore` written before `git add -A`; `.env` mode 0600; `.env.example` holds no key; gate asserts `git ls-files .env` is empty |
|
||||
| T-14.3-03 | Information Disclosure | app key generation | high | mitigate | Single implementation `lagoon.GenerateAppKey` on `crypto/rand`; NewApp rejects keys that do not decode to 32 bytes |
|
||||
| T-14.3-04 | Tampering | existing directory, cleanup | high | mitigate | `os.Lstat` refusal of any existing target; `os.Mkdir` (not MkdirAll); `os.RemoveAll` only on the directory this run created |
|
||||
| T-14.3-05 | Information Disclosure | compose.yaml Postgres port | medium | mitigate | Publish only `127.0.0.1:54320`; named volume, no bind mounts |
|
||||
| T-14.3-06 | Spoofing | framework auto-detection | medium | mitigate | Candidates accepted only when go.mod declares `git.golem15.com/golem15/summercms`; explicit `--framework` wins and never falls through |
|
||||
| T-14.3-07 | Information Disclosure | committed go.mod/go.work | low | mitigate | Relative replace and relative `go work use` paths; no absolute home paths in history |
|
||||
| T-14.3-08 | Information Disclosure | compose.yaml dev credentials | low | accept | `summer`/`summer` is a documented local default for a loopback-only dev database, not a production credential; the encryption key stays in `.env` |
|
||||
| T-14.3-09 | Information Disclosure | `summer serve` default `:8080` | low | accept | Existing surf default listens on all interfaces; out of scope for this phase; next steps point at 127.0.0.1 |
|
||||
| T-14.3-10 | Repudiation | fallback git identity | low | accept | Used only when no identity exists, for the first commit only, and announced with a warning |
|
||||
| T-14.3-SC | Tampering | npm/pip/cargo installs | high | mitigate | No package installs in this plan; `git diff v0.1.12 -- go.mod` adds no require line |
|
||||
</threat_model>
|
||||
|
||||
<verification>
|
||||
Run each task's automated command; then `go vet ./... && go test ./... -short && go -C examples/hello test ./... -count=1`. `bash scripts/check-phase14.3.sh --generator` must end with `Phase 14.3 generator check passed`.
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- Roadmap success criterion 1: `summer new <name>` validates, refuses existing directories, resolves the framework and writes the full layout with a first commit.
|
||||
- Roadmap success criterion 2: the generated app builds, migrates and serves with no edits; the starter route answers over HTTP.
|
||||
- `summer make:plugin` output unchanged; `summer plugin:add` writes relative go.work paths; examples/hello tests pass.
|
||||
- No new dependency; lagoon README documents `lagoon.GenerateAppKey`.
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
Create `.planning/phases/14.3-one-command-app-scaffolding-summer-new-and-summercms-io-new/14.3-01-SUMMARY.md` when done.
|
||||
</output>
|
||||
</content>
|
||||
</invoke>
|
||||
@@ -0,0 +1,258 @@
|
||||
---
|
||||
phase: 14.3-one-command-app-scaffolding-summer-new-and-summercms-io-new
|
||||
plan: 02
|
||||
type: execute
|
||||
wave: 2
|
||||
depends_on: ["14.3-01"]
|
||||
files_modified:
|
||||
- modules/sunrise/new.sh
|
||||
- modules/sunrise/sunrise.go
|
||||
- modules/sunrise/sunrise_test.go
|
||||
- modules/sunrise/README.md
|
||||
- README.md
|
||||
- ../sm-summercmsio-app/plugins/golem15/summercms/script.go
|
||||
- ../sm-summercmsio-app/plugins/golem15/summercms/plugin.go
|
||||
- ../sm-summercmsio-app/plugins/golem15/summercms/routes_test.go
|
||||
- ../sm-summercmsio-app/plugins/golem15/summercms/README.md
|
||||
- scripts/check-phase14.3.sh
|
||||
- .planning/phases/14.3-one-command-app-scaffolding-summer-new-and-summercms-io-new/14.3-SECURITY-REVIEW.md
|
||||
autonomous: true
|
||||
requirements: [D-13, D-14, D-15, D-16, D-17, D-18, D-20, D-24, D-26, D-28]
|
||||
estimate:
|
||||
tokens: 140000
|
||||
raw_tokens: 140000
|
||||
tasks: 3
|
||||
confidence: low
|
||||
must_haves:
|
||||
truths:
|
||||
- "D-13/D-26: modules/sunrise embeds new.sh from its own package directory; sunrise.Script() returns exactly those bytes as a fresh copy on every call."
|
||||
- "D-13: the site plugin answers GET /new with 200, Content-Type `text/plain; charset=utf-8`, `X-Content-Type-Options: nosniff`, `Cache-Control: no-cache`, a strong ETag and a body equal to sunrise.Script(); HEAD /new is 200 with no body; If-None-Match with that ETag is 304; POST /new is 405."
|
||||
- "D-14: new.sh consists of comments, function definitions and a final line `main \"$@\"`; running any prefix of the file that ends on a line boundary, or any byte prefix of its final line, invokes no external command and creates no file."
|
||||
- "D-15 step 1: with Go missing or older than 1.27 the script exits non-zero with a hint naming https://go.dev/dl/ before it clones anything; a missing git is an error; a missing Docker or compose plugin is only a warning."
|
||||
- "D-15 step 2/D-16/D-17: with no checkout at SUMMERCMS_DIR (default ./summercms) the script clones SUMMERCMS_REPO (default https://git.golem15.com/golem15/summercms) and detaches at the newest vX.Y.Z tag, or at SUMMERCMS_REF when set."
|
||||
- "Edge (dirty clone): an existing checkout with tracked changes, on a branch, whose go.mod is not the framework module, or that is not a git repository is refused with a message and left untouched."
|
||||
- "Edge (non-fast-forward / moved tag): an existing detached checkout moves only when its HEAD is an ancestor of the target tag; tag fetches never overwrite a local tag; otherwise the script exits non-zero without changing the checkout."
|
||||
- "D-15 steps 3-4: the script installs summer with `go install ./cmd/summer` into GOBIN (or the first GOPATH entry's bin), then runs `<gobin>/summer new <name> --framework <absolute checkout path>` from the checkout's parent directory with stdin closed."
|
||||
- "D-24: the script prints `cd <absolute app dir>` and, only when the install directory is not on PATH, an `export PATH=\"<gobin>:$PATH\"` hint; the generic next steps come from summer new."
|
||||
- "Edge (invalid name / existing app): a name failing ^[a-z][a-z0-9]*$ or an existing sm-<name>-app next to the checkout stops the script before any clone or install."
|
||||
- "D-20: in a clean node:24-bookworm container with Go 1.27.0, the Docker CLI and compose plugin, git and jq, `curl -fsSL <script> | bash -s -- demo` then `cd sm-demo-app && docker compose up -d --wait && summer build && summer migrate && summer serve` ends with GET http://127.0.0.1:8080/ answering 200 JSON containing \"plugin\":\"golem15.demo\"."
|
||||
- "Security: 14.3-SECURITY-REVIEW.md lists T-14.3-01..T-14.3-19 and T-14.3-SC with a disposition; no high threat is open."
|
||||
prohibitions:
|
||||
- requirement_id: D-26
|
||||
category: safety
|
||||
status: resolved
|
||||
verification: judgment
|
||||
resolution: flagged-unverified
|
||||
reason: "compass reads the framework config prefix as config keys; descriptor-less, human review recommended."
|
||||
statement: "The bootstrap script must never read or set variables with the framework's config env prefix (SUMMER_*); overrides are SUMMERCMS_REPO, SUMMERCMS_DIR and SUMMERCMS_REF only."
|
||||
- requirement_id: D-16
|
||||
category: safety
|
||||
status: resolved
|
||||
verification: judgment
|
||||
resolution: flagged-unverified
|
||||
reason: "Refusal paths reviewed in the security review and tested in plan 04; descriptor-less, human review recommended."
|
||||
statement: "The bootstrap script must never discard, overwrite or move past local work in an existing framework checkout."
|
||||
- requirement_id: D-15
|
||||
category: safety
|
||||
status: resolved
|
||||
verification: judgment
|
||||
resolution: flagged-unverified
|
||||
reason: "Script is piped into a shell from the internet; descriptor-less, human review recommended."
|
||||
statement: "The bootstrap script must never escalate privileges or write outside SUMMERCMS_DIR, the new sm-<name>-app directory and the Go install directory."
|
||||
- requirement_id: D-28
|
||||
category: safety
|
||||
status: resolved
|
||||
verification: judgment
|
||||
resolution: flagged-unverified
|
||||
reason: "Release is a user action; descriptor-less, human review recommended."
|
||||
statement: "No task pushes a tag, pushes a branch or deploys the site."
|
||||
artifacts:
|
||||
- path: "modules/sunrise/new.sh"
|
||||
provides: "the curl | bash bootstrap script, single source of truth"
|
||||
contains: "main \"$@\""
|
||||
- path: "modules/sunrise/sunrise.go"
|
||||
provides: "embed and sunrise.Script()"
|
||||
contains: "//go:embed new.sh"
|
||||
- path: "modules/sunrise/README.md"
|
||||
provides: "standard module README"
|
||||
contains: "sunrise.Script"
|
||||
- path: "../sm-summercmsio-app/plugins/golem15/summercms/script.go"
|
||||
provides: "GET /new handler serving the embedded script"
|
||||
contains: "sunrise.Script"
|
||||
- path: "scripts/check-phase14.3.sh"
|
||||
provides: "D-20 container acceptance stages"
|
||||
contains: "--acceptance"
|
||||
- path: ".planning/phases/14.3-one-command-app-scaffolding-summer-new-and-summercms-io-new/14.3-SECURITY-REVIEW.md"
|
||||
provides: "ASVS L1 review of plans 01-02"
|
||||
contains: "T-14.3-11"
|
||||
key_links:
|
||||
- from: "../sm-summercmsio-app/plugins/golem15/summercms/plugin.go"
|
||||
to: "modules/sunrise/sunrise.go"
|
||||
via: "Routes registers GET /new with a handler over sunrise.Script(), built against the local framework through the plugin's replace (D-28)"
|
||||
pattern: "\"/new\""
|
||||
- from: "modules/sunrise/new.sh"
|
||||
to: "cmd/summer/main.go"
|
||||
via: "the script installs ./cmd/summer and calls `summer new <name> --framework <dir>`"
|
||||
pattern: "new \"\\$name\" --framework"
|
||||
- from: "scripts/check-phase14.3.sh"
|
||||
to: "modules/sunrise/new.sh"
|
||||
via: "--acceptance pipes the committed script into bash inside node:24-bookworm"
|
||||
pattern: "node:24-bookworm"
|
||||
---
|
||||
|
||||
<objective>
|
||||
Ship the bootstrap script `curl -fsSL https://summercms.io/new | bash -s -- <name>`: a bash `main()` script versioned in a new framework module `modules/sunrise`, served by the site plugin at `GET /new`, proven end to end in a clean `node:24-bookworm` container, and security-reviewed.
|
||||
|
||||
Purpose: roadmap success criteria 3 and 4 — one command checks prerequisites, fetches the framework at its latest release tag, installs the summer CLI and runs `summer new`.
|
||||
Output: `modules/sunrise` (script, embed, README, smoke tests), the site plugin route, the `--acceptance` gate stages, and 14.3-SECURITY-REVIEW.md.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@~/.claude/gsd-core/workflows/execute-plan.md
|
||||
@~/.claude/gsd-core/templates/summary.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
@.planning/phases/14.3-one-command-app-scaffolding-summer-new-and-summercms-io-new/14.3-CONTEXT.md
|
||||
@.planning/phases/14.3-one-command-app-scaffolding-summer-new-and-summercms-io-new/14.3-RESEARCH.md
|
||||
@.planning/phases/14.3-one-command-app-scaffolding-summer-new-and-summercms-io-new/14.3-01-SUMMARY.md
|
||||
@modules/boardwalk/boardwalk.go
|
||||
@modules/boardwalk/README.md
|
||||
@../sm-summercmsio-app/plugins/golem15/summercms/plugin.go
|
||||
@../sm-summercmsio-app/plugins/golem15/summercms/static.go
|
||||
@../sm-summercmsio-app/plugins/golem15/summercms/routes_test.go
|
||||
</context>
|
||||
|
||||
## Spec-less probe fallback
|
||||
|
||||
No REQUIREMENTS.md IDs or SPEC.md exist for this phase; requirement probing is visibly skipped. The acceptance contract is D-13..D-17, D-20, D-24, D-26, D-28, roadmap success criteria 3 and 4, and the edge predicates (truncated script, invalid name, existing app directory, dirty or branch clone, non-fast-forward, moved tag, missing Go/git/Docker, POST /new) and prohibitions authored into `must_haves` above.
|
||||
|
||||
## Artifacts this phase produces (this plan)
|
||||
|
||||
- New framework module `git.golem15.com/golem15/summercms/modules/sunrise`, package `sunrise`: file `new.sh`, func `sunrise.Script() []byte`, README with the standard structure and a root README.md modules-table row.
|
||||
- Script contract: `bash -s -- <name>`; options `-h`/`--help`; env overrides `SUMMERCMS_REPO` (default `https://git.golem15.com/golem15/summercms`), `SUMMERCMS_DIR` (default `summercms` under the current directory), `SUMMERCMS_REF` (tag or commit; skips latest-tag selection).
|
||||
- Tests `TestScriptEmbedded`, `TestScriptShape`, `TestScriptTruncationRunsNothing`, gated `TestBootstrapEndToEnd` (`SUMMERCMS_BOOTSTRAP_CHECK=1`).
|
||||
- Site plugin: `scriptHandler` in `script.go`, route `GET /new`, test `TestNewScriptRoute` and three new `TestRoutesAssemble` rows; plugin README route row.
|
||||
- Gate stages `scripts/check-phase14.3.sh --acceptance` (local script and repo at HEAD, final line `Phase 14.3 acceptance passed`) and `--acceptance-production` (https://summercms.io/new and the public remote, run by the user after deploy).
|
||||
- `14.3-SECURITY-REVIEW.md`.
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="tracer">
|
||||
<name>Task 1: Script → embed → GET /new → curl | bash → summer new, wired end to end</name>
|
||||
<reversibility rating="costly">The URL `https://summercms.io/new`, the `bash -s -- <name>` argument shape, the SUMMERCMS_REPO/DIR/REF names and the exported `sunrise` package become a published contract copied into docs, the landing page and agent instructions; changing them needs coordinated doc and site updates (old copies can be kept working by redirects), so it is costly, not one-way.</reversibility>
|
||||
<files>modules/sunrise/new.sh, modules/sunrise/sunrise.go, modules/sunrise/sunrise_test.go, modules/sunrise/README.md, README.md, ../sm-summercmsio-app/plugins/golem15/summercms/script.go, ../sm-summercmsio-app/plugins/golem15/summercms/plugin.go, ../sm-summercmsio-app/plugins/golem15/summercms/routes_test.go, ../sm-summercmsio-app/plugins/golem15/summercms/README.md</files>
|
||||
<read_first>modules/boardwalk/boardwalk.go (embed of a file inside the package dir), modules/boardwalk/README.md (module README structure), README.md (Modules table, lines 81-107), cmd/summer/main.go (newCommand flags from plan 01), internal/build/new.go (ValidateAppName rule text), ../sm-summercmsio-app/plugins/golem15/summercms/plugin.go, ../sm-summercmsio-app/plugins/golem15/summercms/static.go (file struct, contentTypes, cacheNoCache, setCommonHeaders, http.ServeContent use), ../sm-summercmsio-app/plugins/golem15/summercms/routes_test.go, ../sm-summercmsio-app/plugins/golem15/summercms/README.md, ../sm-summercmsio-app/plugins/golem15/summercms/go.mod (relative framework replace), .planning/phases/14.3-one-command-app-scaffolding-summer-new-and-summercms-io-new/14.3-RESEARCH.md (Bootstrap script design Q10 table, bash skeleton, Pitfalls 6-9, 13, Q7)</read_first>
|
||||
<action>Build the production script and every layer it passes through, per D-13 to D-17, D-24 and D-26.
|
||||
|
||||
Script `modules/sunrise/new.sh` (single source of truth; mode 0755): shebang `#!/usr/bin/env bash`, a header comment with the one-liner and the override names, helper functions, `main()`, and as the very last line `main "$@"` (D-14). Nothing at top level besides comments and function definitions. Inside `main`: `set -euo pipefail`; export `GIT_TERMINAL_PROMPT=0`; parse arguments first, before any external command: exactly one name, or `-h`/`--help` printing usage (exit 0); otherwise usage on stderr and exit 2. Validate the name with the bash regex `^[a-z][a-z0-9]*$` (summer new enforces the full denylist). Resolve the checkout directory from `SUMMERCMS_DIR` (default `summercms`) to an absolute path against `$PWD`, take its parent as the app parent, and stop with exit 1 when `<parent>/sm-<name>-app` already exists. Prerequisites (D-15 step 1): require `git`; require `go` and read its version with `go env GOVERSION` from `/` (so a local go.mod cannot switch toolchains), parse `^go([0-9]+)\.([0-9]+)`, require 1.27 or newer and print an install hint naming https://go.dev/dl/ when missing or older; an unparseable version (devel builds) is a warning; `docker` and `docker compose version` missing are warnings only. Checkout (D-15 step 2, D-16, D-17), following the research Q10 table: when the directory does not exist, `git clone --quiet "$repo" "$dir"` with `SUMMERCMS_REPO` defaulting to `https://git.golem15.com/golem15/summercms` (full clone, not shallow), then select the target and `git -c advice.detachedHead=false checkout --quiet --detach`. When it exists: it must be a git work tree whose go.mod first `module` line is `git.golem15.com/golem15/summercms` (read with a bash loop, not a pipeline into head), must have no tracked changes (`git status --porcelain --untracked-files=no` empty), must not be on a branch (`git symbolic-ref -q HEAD` fails); each violation stops with a message that suggests a separate `SUMMERCMS_DIR` or running `summer new --framework` by hand. Then `git fetch --quiet --tags origin` with no option that lets a moved upstream tag overwrite a local one (a failed fetch stops the script), select the target, do nothing when HEAD already equals it, check it out when `git merge-base --is-ancestor HEAD <target>` succeeds, otherwise stop (not a fast-forward). Target selection: `SUMMERCMS_REF` when set; otherwise capture `git tag -l 'v*' --sort=-v:refname` into a variable and take the first entry matching `^v[0-9]+\.[0-9]+\.[0-9]+$` in a bash loop (no pipe into head, research Pitfall 6); no release tag is an error. Install (D-15 step 3): run `go install ./cmd/summer` in the checkout with `-C`, `GOWORK` cleared and stdin from /dev/null; the install directory is `go env GOBIN`, else the first entry of `go env GOPATH` plus `/bin`. Create (D-15 step 4): from the parent directory run `"$gobin/summer" new "$name" --framework "$dir"` with stdin from /dev/null. Finish (D-24): print `cd <parent>/sm-<name>-app` as the first thing to run, and when `:$PATH:` lacks `:$gobin:` print `export PATH="$gobin:$PATH"` with a note to add it to the shell profile. Quote every expansion; no dynamic code strings, no privilege escalation, no writes outside the checkout, the new app directory and the install directory. Use only bash builtins plus the external commands `git`, `go` and `docker` (path manipulation through parameter expansion, not dirname or basename), so the script runs on a minimal PATH and plan 04 can test it with only git and stubs available. Environment overrides use only the `SUMMERCMS_` prefix (D-26). Messages go to stderr prefixed `==>` (errors `error:`), no colour codes.
|
||||
|
||||
Package `modules/sunrise/sunrise.go`: package doc comment; `//go:embed new.sh` into an unexported byte slice; `func Script() []byte` returning `bytes.Clone` of it. README.md with the standard structure: H1 `sunrise`, summary sentence `Bootstrap script that installs the summer CLI and creates a new SummerCMS application in one command, embedded so it is versioned with the framework.`, import line, Overview (what the one-liner does, served at https://summercms.io/new), Features (main wrapper, latest release tag, fast-forward-only updates and refusals, PATH hint), Usage (the one-liner, `bash -s -- <name>`, and a short Go handler that writes `sunrise.Script()` as `text/plain; charset=utf-8`), API reference (`sunrise.Script`), Configuration (the three `SUMMERCMS_` variables with defaults), Dependencies (SummerCMS modules none, third-party none, stdlib `bytes`, `embed`), Testing (`go test ./modules/sunrise/...`, the gated `SUMMERCMS_BOOTSTRAP_CHECK=1` run). No consuming application names. Add the row to the root README.md Modules table in alphabetical order with the same summary sentence (D-18, D-26).
|
||||
|
||||
Smoke tests `modules/sunrise/sunrise_test.go`: `TestScriptEmbedded` (Script equals the file bytes; mutating a returned slice does not change the next call); `TestScriptShape` (first line is the shebang, last non-empty line is exactly `main "$@"`, `bash -n` on the file exits 0); `TestScriptTruncationRunsNothing` (for every prefix ending on a line boundary and every byte prefix of the final line, write it to a temp file and run `bash <file> demo` with a clean environment whose PATH starts with a stub directory holding `git`, `go`, `docker`, `curl` and `summer` stubs that append to a marker file; assert the marker never exists and the working directory stays empty); gated `TestBootstrapEndToEnd` (runs only with `SUMMERCMS_BOOTSTRAP_CHECK=1`, 15 minute budget): serve `Script()` from an `httptest.Server`, run `curl -fsSL <url> | bash -s -- demo` in a temp work dir with `SUMMERCMS_REPO` = repository root, `SUMMERCMS_REF` = `git rev-parse HEAD`, `GOBIN` = a temp dir that is not on PATH; assert exit 0, the checkout `summercms` is detached at that commit, `sm-demo-app/go.mod` replaces the framework with `../summercms`, the output contains the `cd` line and the PATH hint, and `<gobin>/summer build` in the app succeeds.
|
||||
|
||||
Site route (D-13, D-28): new `script.go` in the plugin with `scriptHandler(body []byte) http.HandlerFunc` that precomputes a `file` with the same strong ETag scheme as `newTree` and ctype `contentTypes[".txt"]`, and serves with `Content-Type`, `Cache-Control: no-cache` (`cacheNoCache`), `ETag`, `setCommonHeaders` and `http.ServeContent` (HEAD and If-None-Match handled there). In `Routes` register `g.Get("/new", scriptHandler(sunrise.Script()))` inside the existing `GroupRaw` before the catch-all; update the package and Routes doc comments. Tests: add `{GET /new 200 body=Script}`, `{HEAD /new 200}`, `{POST /new 405}` rows to `TestRoutesAssemble` and a `TestNewScriptRoute` asserting the exact headers and a 304 for a matching If-None-Match. Add a `GET /new` row to the plugin README Routes table. The plugin builds against the local framework through its existing replace; its `require` stays `v0.1.12` until the post-phase release bump (D-28). Commit order: summercms.go (sunrise module, one commit), then inside the plugin submodule, then the gitlink bump in `../sm-summercmsio-app` as its own commit. Do not push.</action>
|
||||
<verify>
|
||||
<automated>go vet ./modules/sunrise/ && go test ./modules/sunrise/ -count=1 -v && go test ./cmd/summer -run '^(TestDocsTree|TestEveryModuleInSidebar)$' -count=1 -v && go -C ../sm-summercmsio-app/plugins/golem15/summercms vet ./... && go -C ../sm-summercmsio-app/plugins/golem15/summercms test ./... -count=1 -v -run '^(TestRoutesAssemble|TestNewScriptRoute)$' && SUMMERCMS_BOOTSTRAP_CHECK=1 go test ./modules/sunrise/ -run '^TestBootstrapEndToEnd$' -count=1 -v -timeout 20m</automated>
|
||||
<fails_when>Non-zero exit; any "--- FAIL"; "--- SKIP: TestBootstrapEndToEnd"; "no tests to run"; or any of "--- PASS: TestScriptTruncationRunsNothing", "--- PASS: TestEveryModuleInSidebar", "--- PASS: TestNewScriptRoute", "--- PASS: TestBootstrapEndToEnd" is absent.</fails_when>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- `tail -n 1 modules/sunrise/new.sh` prints exactly `main "$@"` and `head -n 1 modules/sunrise/new.sh` prints `#!/usr/bin/env bash`.
|
||||
- `grep -c 'SUMMER_' modules/sunrise/new.sh` prints 0; `grep -c 'SUMMERCMS_REPO\|SUMMERCMS_DIR\|SUMMERCMS_REF' modules/sunrise/new.sh` is at least 3.
|
||||
- `grep -Ecw 'sudo|eval' modules/sunrise/new.sh` prints 0.
|
||||
- `grep -c 'go:embed new.sh' modules/sunrise/sunrise.go` is 1 and `grep -c 'modules/sunrise/README.md' README.md` is 1.
|
||||
- `grep -c '"/new"' ../sm-summercmsio-app/plugins/golem15/summercms/plugin.go` is 1.
|
||||
- TestBootstrapEndToEnd passes: clone detached at HEAD, relative `../summercms` replace, `cd` line and PATH hint printed, generated app builds.
|
||||
- `go test ./cmd/summer -run TestDocsTree` passes with the new README; `git -C ../sm-summercmsio-app status --porcelain plugins/golem15/summercms` is empty after the gitlink commit.
|
||||
</acceptance_criteria>
|
||||
<done>The embedded script is served at GET /new as text/plain and, piped from an HTTP server into bash, clones the framework at a pinned ref, installs summer and creates a buildable sm-demo-app; committed in summercms.go, the plugin submodule and the site app gitlink.</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: D-20 container acceptance from nothing in node:24-bookworm</name>
|
||||
<precondition>Docker daemon reachable through /var/run/docker.sock, network access to go.dev, download.docker.com, deb.debian.org and proxy.golang.org, and host ports 54320 and 8080 free.</precondition>
|
||||
<files>scripts/check-phase14.3.sh</files>
|
||||
<read_first>scripts/check-phase14.3.sh (the --generator stage from plan 01), modules/sunrise/new.sh, .planning/phases/14.3-one-command-app-scaffolding-summer-new-and-summercms-io-new/14.3-RESEARCH.md (Container acceptance D-20, Pitfalls 9, 10, 11, 12, 13), ../sm-summercmsio-app/DEPLOY.md (step 4 checks)</read_first>
|
||||
<action>Add the D-20 acceptance stages to `scripts/check-phase14.3.sh`.
|
||||
|
||||
`--acceptance` (pre-tag, pre-deploy, D-20 and D-28): refuse to run when `modules/sunrise/new.sh` differs from HEAD (what is tested must be committed); preflight `docker info`, ports 54320 and 8080 free, and pull `node:24-bookworm`. Run `docker run --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -v "$ROOT":/fw:ro -e COMPOSE_PROJECT_NAME=<unique> node:24-bookworm bash -euo pipefail -c '<inner>'`. The inner script, starting from the bare image: `apt-get update` and install `ca-certificates curl gnupg jq`; add Docker's official Debian bookworm apt repository with its signing key under /etc/apt/keyrings and install `docker-ce-cli docker-compose-plugin`; read the sha256 of `go1.27.0.linux-amd64.tar.gz` from `https://go.dev/dl/?mode=json&include=all` with jq, download the tarball from https://go.dev/dl/, verify it with `sha256sum -c`, unpack to /usr/local and put /usr/local/go/bin on PATH; `git config --global --add safe.directory '*'` (throwaway container, research Pitfall 12); in an empty /work run `curl -fsSL file:///fw/modules/sunrise/new.sh | SUMMERCMS_REPO=/fw SUMMERCMS_REF=<HEAD commit> bash -s -- demo` and capture its output; assert the output contains the `cd /work/sm-demo-app` line and the PATH hint, then apply the hint (`export PATH="$(go env GOPATH)/bin:$PATH"`); then exactly the D-20 chain: `cd sm-demo-app && docker compose up -d --wait && summer build && summer migrate`, start `summer serve` in the background, poll `http://127.0.0.1:8080/` for up to 60 s and require the body to contain `"plugin":"golem15.demo"`; an inner trap stops serve and runs `docker compose down -v`. compose.yaml uses a named volume only, so the host daemon needs no container paths (Pitfall 11). On the host a trap removes any container and volume labelled with the unique compose project (`docker ps -aq --filter label=com.docker.compose.project=<name>` and the volume equivalent) in case the inner cleanup did not run. End with `Phase 14.3 acceptance passed`.
|
||||
|
||||
`--acceptance-production` (run by the user after `v0.1.13` is tagged and the site is redeployed): the same container chain but `curl -fsSL https://summercms.io/new | bash -s -- demo` with no SUMMERCMS_ overrides, so the script selects the newest release tag from the public remote; ends with `Phase 14.3 production acceptance passed`. Do not run this stage in this plan.
|
||||
|
||||
Run `--acceptance` once and paste its tail (the chain and the final line) into the plan summary as D-20 evidence. Commit the script change on its own.</action>
|
||||
<verify>
|
||||
<automated>bash scripts/check-phase14.3.sh --acceptance</automated>
|
||||
<fails_when>Non-zero exit; "sha256sum: WARNING" or "FAILED" from the checksum; the output lacks "cd /work/sm-demo-app", lacks "\"plugin\":\"golem15.demo\"", or lacks the final line "Phase 14.3 acceptance passed".</fails_when>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- `grep -c 'node:24-bookworm' scripts/check-phase14.3.sh` is at least 1 and `grep -c -- '--acceptance-production' scripts/check-phase14.3.sh` is at least 1.
|
||||
- `grep -c 'sha256sum -c' scripts/check-phase14.3.sh` is at least 1 and `grep -c 'docker-compose-plugin' scripts/check-phase14.3.sh` is at least 1.
|
||||
- The stage refuses when `git diff --quiet HEAD -- modules/sunrise/new.sh` fails.
|
||||
- A recorded run ends with `Phase 14.3 acceptance passed` and shows `docker compose up -d --wait`, `summer build`, `summer migrate`, `summer serve` and the starter route JSON.
|
||||
- After the run, `docker ps -a --filter label=com.docker.compose.project` shows no container of the acceptance project.
|
||||
</acceptance_criteria>
|
||||
<done>From a bare node:24-bookworm image, the one-liner (script file and repo at HEAD) followed by the D-20 chain serves the starter route, and the production variant is ready for the user to run after deploy.</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 3: Security review of the generator, script and route</name>
|
||||
<files>.planning/phases/14.3-one-command-app-scaffolding-summer-new-and-summercms-io-new/14.3-SECURITY-REVIEW.md, modules/sunrise/new.sh, ../sm-summercmsio-app/plugins/golem15/summercms/script.go, internal/build/new.go</files>
|
||||
<read_first>.planning/phases/14.3-one-command-app-scaffolding-summer-new-and-summercms-io-new/14.3-01-PLAN.md (threat_model T-14.3-01..10), .planning/phases/14.3-one-command-app-scaffolding-summer-new-and-summercms-io-new/14.3-02-PLAN.md (threat_model T-14.3-11..19), .planning/phases/14.3-one-command-app-scaffolding-summer-new-and-summercms-io-new/14.3-RESEARCH.md (Security Domain), .planning/phases/14.2.1-translate-plugin/14.2.1-SECURITY-REVIEW.md (format reference), modules/sunrise/new.sh, internal/build/new.go, ../sm-summercmsio-app/plugins/golem15/summercms/script.go, ../sm-summercmsio-app/deploy/nginx/summercms.io.conf</read_first>
|
||||
<action>Run the security review lane (CONTEXT discretion: the script is piped into a shell and served publicly) over the plan 01 and plan 02 changes: `git diff v0.1.12..HEAD -- internal/build cmd/summer modules/lagoon modules/sunrise scripts/check-phase14.3.sh` in summercms.go and the plugin submodule's diff for `script.go`/`plugin.go`. Review at ASVS L1, blocking on high: input validation of `<name>` in both the script and the generator, quoting of every expansion in new.sh, behaviour under `curl | bash` (stdin, truncation, HTML error bodies with `curl -f`), tag and ancestry handling, refusal paths that protect local work, write locations, `.env` handling and the first commit, compose port binding, MIME type and nosniff on `/new`, nginx passthrough (GET/HEAD only), and the acceptance harness's use of the host Docker socket. Fix any high finding in the affected file within this task (new.sh, script.go or new.go) and re-run the matching Task 1 command.
|
||||
|
||||
Write `14.3-SECURITY-REVIEW.md`: scope, method, an ASVS L1 table (V5, V6, V10, V12, V14 rows per research), and one row per threat T-14.3-01 to T-14.3-19 plus T-14.3-SC with category, severity, disposition, the source-level control (file and function or script function), the evidence available now (Task 1 smoke tests, the generator gate, the acceptance run), status (`mitigated`, `accepted`, or `open`), and a column for the plan 04 test that will pin it. Any `open` high threat blocks the phase. Commit the review as a planning-docs commit, separate from any code fix.</action>
|
||||
<verify>
|
||||
<automated>bash -c 'f=.planning/phases/14.3-one-command-app-scaffolding-summer-new-and-summercms-io-new/14.3-SECURITY-REVIEW.md; test -f "$f"; for i in $(seq -w 1 19); do grep -q "T-14.3-$i" "$f" || { echo "missing T-14.3-$i"; exit 1; }; done; grep -q "T-14.3-SC" "$f"; if grep -E "\| *high *\|.*\| *open *\|" "$f"; then echo "open high threat"; exit 1; fi; echo REVIEW-OK' && go test ./modules/sunrise/ -count=1 && go test ./internal/build/ -run '^TestNewAppSmoke$' -count=1</automated>
|
||||
<fails_when>Non-zero exit; "missing T-14.3-" or "open high threat" printed; any "FAIL"; or the output lacks "REVIEW-OK".</fails_when>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- 14.3-SECURITY-REVIEW.md contains every ID T-14.3-01 through T-14.3-19 and T-14.3-SC exactly once as a table row.
|
||||
- Every high-severity row has status `mitigated` and names the controlling file and function.
|
||||
- Any code fix made by the review is in a separate code commit and the Task 1 tests still pass.
|
||||
</acceptance_criteria>
|
||||
<done>The phase's security-relevant surfaces are reviewed at ASVS L1 with no open high threat, and the review names what plan 04 must pin with tests.</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<threat_model>
|
||||
## Trust Boundaries
|
||||
|
||||
| Boundary | Description |
|
||||
|----------|-------------|
|
||||
| Internet (summercms.io) -> user shell | The script is downloaded and executed with the user's privileges |
|
||||
| Remote git (tags, objects) -> local checkout | Upstream tags select the code that is compiled and installed |
|
||||
| Existing local checkout -> script | The user's own clone may hold uncommitted work |
|
||||
| Acceptance harness -> host Docker daemon | The container drives the host daemon through its socket |
|
||||
|
||||
## STRIDE Threat Register
|
||||
|
||||
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|
||||
|-----------|----------|-----------|----------|-------------|-----------------|
|
||||
| T-14.3-11 | Tampering | truncated download | high | mitigate | Whole script in `main()`, called on the last line; `TestScriptTruncationRunsNothing` covers line and byte prefixes |
|
||||
| T-14.3-12 | Tampering | error body piped to bash | medium | mitigate | Docs and landing page use `curl -fsSL` (fails on HTTP errors); `/new` answers only with the embedded bytes |
|
||||
| T-14.3-13 | Spoofing | `/new` rendered as HTML | medium | mitigate | `text/plain; charset=utf-8` plus `X-Content-Type-Options: nosniff`; no request data reflected; `TestNewScriptRoute` |
|
||||
| T-14.3-14 | Tampering | moved upstream tag, non-fast-forward | high | mitigate | Tags fetched without overwrite permission; checkout only when HEAD is an ancestor of the target; else exit non-zero |
|
||||
| T-14.3-15 | Tampering | user's existing checkout | high | mitigate | Refuse tracked changes, branch checkouts, non-framework go.mod and non-git dirs; no command that discards work |
|
||||
| T-14.3-16 | Elevation of Privilege | shell injection via name or overrides | high | mitigate | Name regex before use; every expansion quoted; overrides passed only as arguments; no dynamic code strings |
|
||||
| T-14.3-17 | Denial of Service | credential prompt hang with piped stdin | medium | mitigate | `GIT_TERMINAL_PROMPT=0`; children run with stdin from /dev/null |
|
||||
| T-14.3-18 | Tampering | supply chain of the served script | high | mitigate | Script embedded from the framework tag the site builds against; HTTPS default clone URL; release tags only; acceptance verifies the Go tarball sha256 from go.dev |
|
||||
| T-14.3-19 | Elevation of Privilege | acceptance harness on host Docker socket | medium | mitigate | Local harness only; framework mounted read-only; named volume only; unique compose project; host-side cleanup trap |
|
||||
| T-14.3-SC | Tampering | npm/pip/cargo installs | high | mitigate | No package-manager installs in the framework; container installs come from Debian and Docker's signed apt repositories and a checksum-verified Go tarball |
|
||||
</threat_model>
|
||||
|
||||
<verification>
|
||||
Task 1 command (smoke, docs tree, plugin route, gated end to end), Task 2 `bash scripts/check-phase14.3.sh --acceptance` ending with `Phase 14.3 acceptance passed`, Task 3 review check. Then `go vet ./... && go test ./... -short` in summercms.go and `go -C ../sm-summercmsio-app/plugins/golem15/summercms test ./... -count=1`.
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- Roadmap success criterion 3: the script checks Go 1.27+ and Docker, clones or fast-forwards the framework at its latest release tag, installs summer with a PATH hint, runs `summer new` and prints the cd line; summercms.io serves it at `/new` as `text/plain` once deployed.
|
||||
- Roadmap success criterion 4: the D-20 chain passes in node:24-bookworm (local variant now, production variant after deploy).
|
||||
- No open high threat in 14.3-SECURITY-REVIEW.md; no tag push, branch push or deploy happened.
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
Create `.planning/phases/14.3-one-command-app-scaffolding-summer-new-and-summercms-io-new/14.3-02-SUMMARY.md` when done.
|
||||
</output>
|
||||
</content>
|
||||
</invoke>
|
||||
@@ -0,0 +1,238 @@
|
||||
---
|
||||
phase: 14.3-one-command-app-scaffolding-summer-new-and-summercms-io-new
|
||||
plan: 03
|
||||
type: execute
|
||||
wave: 3
|
||||
depends_on: ["14.3-02"]
|
||||
files_modified:
|
||||
- docs/setup/installation.md
|
||||
- docs/architecture/go-modules-and-workspaces.md
|
||||
- docs/console/scaffolding.md
|
||||
- docs/console/introduction.md
|
||||
- docs/site.yaml
|
||||
- README.md
|
||||
- ../sm-summercmsio-app/vue-summercmsio-app/app/data/terminal.json
|
||||
- ../sm-summercmsio-app/vue-summercmsio-app/i18n/locales/en.json
|
||||
- ../sm-summercmsio-app/vue-summercmsio-app/tests/terminal.test.ts
|
||||
- ../sm-summercmsio-app/vue-summercmsio-app/tests/output.test.ts
|
||||
- ../sm-summercmsio-app/terminal_check_test.go
|
||||
- ../sm-summercmsio-app/README.md
|
||||
- ../sm-summercmsio-app/CLAUDE.md
|
||||
- ../sm-summercmsio-app/DEPLOY.md
|
||||
autonomous: true
|
||||
requirements: [D-01, D-18, D-19, D-20, D-24, D-28]
|
||||
estimate:
|
||||
tokens: 120000
|
||||
raw_tokens: 120000
|
||||
tasks: 3
|
||||
confidence: low
|
||||
must_haves:
|
||||
truths:
|
||||
- "D-18: docs/setup/installation.md shows `curl -fsSL https://summercms.io/new | bash -s -- demo` in its first section, before any examples/hello step; the manual examples/hello walkthrough follows it; no Known issues callout remains."
|
||||
- "D-18: docs/architecture/go-modules-and-workspaces.md explains what `summer new` writes (relative framework replace, committed go.work with relative paths, no root go.sum, gitignored go.work.sum, the --framework / sibling / binary-source detection order) and says plugin:add adds a relative path; docs/console/scaffolding.md has a Creating an application section documenting `summer new` with `--framework`, `--no-plugin` and `--no-git`."
|
||||
- "D-18: a fresh `summer docs:build` writes llms-full.txt containing the one-liner and `summer new`; TestDocsTree, TestDocsBuildRealTree, TestDocsAIOutputsInSync and `summer docs:build --check` pass."
|
||||
- "D-19: terminal.json has exactly three groups and six commands: [`curl -fsSL https://summercms.io/new | bash -s -- demo`], [`cd sm-demo-app`, `docker compose up -d --wait`], [`summer build`, `summer migrate`, `summer serve`]; every comment key resolves to a non-empty en.json string and no unused terminal comment key remains; `pnpm generate && pnpm test` pass."
|
||||
- "D-19: TestLoadTerminal, TestTerminalScript, TestTerminalEnv and TestTerminalRunScript pass; SUMMERCMS_NEW_URL replaces the script URL only inside the curl command."
|
||||
- "D-19/D-20: with SUMMERCMS_TERMINAL_CHECK=1, SUMMERCMS_NEW_URL pointing at the local script, SUMMERCMS_REPO at the framework checkout and SUMMERCMS_REF at its HEAD, TestTerminalCommands runs the six page commands verbatim, sees JSON containing \"plugin\":\"golem15.demo\" from the backgrounded `summer serve`, and removes its compose project."
|
||||
- "D-28: the site's framework require stays at v0.1.12 in this plan; nothing is deployed and no tag is pushed."
|
||||
prohibitions:
|
||||
- requirement_id: D-19
|
||||
category: safety
|
||||
status: resolved
|
||||
verification: judgment
|
||||
resolution: flagged-unverified
|
||||
reason: "TestTerminalScript pins the exact string; descriptor-less, human review recommended."
|
||||
statement: "The landing page and docs must never show the bootstrap one-liner over plain HTTP or without curl's fail-on-error flag."
|
||||
- requirement_id: D-18
|
||||
category: transparency
|
||||
status: resolved
|
||||
verification: judgment
|
||||
resolution: flagged-unverified
|
||||
reason: "docs:build --check forbidden-name rule covers docs/; descriptor-less, human review recommended."
|
||||
statement: "Framework docs and READMEs must never name a consuming application."
|
||||
- requirement_id: D-28
|
||||
category: safety
|
||||
status: resolved
|
||||
verification: judgment
|
||||
resolution: flagged-unverified
|
||||
reason: "Release is a user action; descriptor-less, human review recommended."
|
||||
statement: "No task pushes, tags, bumps the site's framework require or deploys the site."
|
||||
artifacts:
|
||||
- path: "docs/setup/installation.md"
|
||||
provides: "one-liner first, manual walkthrough second"
|
||||
contains: "curl -fsSL https://summercms.io/new | bash -s -- demo"
|
||||
- path: "docs/console/scaffolding.md"
|
||||
provides: "summer new reference"
|
||||
contains: "summer new"
|
||||
- path: "../sm-summercmsio-app/vue-summercmsio-app/app/data/terminal.json"
|
||||
provides: "landing page Get started commands"
|
||||
contains: "https://summercms.io/new"
|
||||
- path: "../sm-summercmsio-app/terminal_check_test.go"
|
||||
provides: "terminal command checks with SUMMERCMS_NEW_URL and backgrounded serve"
|
||||
contains: "SUMMERCMS_NEW_URL"
|
||||
key_links:
|
||||
- from: "../sm-summercmsio-app/vue-summercmsio-app/app/data/terminal.json"
|
||||
to: "../sm-summercmsio-app/terminal_check_test.go"
|
||||
via: "loadTerminal reads the page's command list and TestTerminalCommands executes it"
|
||||
pattern: "terminal.json"
|
||||
- from: "docs/setup/installation.md"
|
||||
to: "modules/sunrise/new.sh"
|
||||
via: "the documented one-liner is the script served at /new"
|
||||
pattern: "summercms.io/new"
|
||||
- from: "docs/console/scaffolding.md"
|
||||
to: "cmd/summer/main.go"
|
||||
via: "the docs command checker validates `summer new` against toolCommands()"
|
||||
pattern: "summer new"
|
||||
---
|
||||
|
||||
<objective>
|
||||
Document the one-command start and switch the summercms.io landing page "Get started" to the one-liner: installation guide leads with `curl -fsSL https://summercms.io/new | bash -s -- demo`, the architecture and console docs cover `summer new`, llms outputs pick it up, and the landing page plus its four test layers move to the new commands, including a gated run of the page's commands end to end.
|
||||
|
||||
Purpose: roadmap success criterion 5 and D-19; people and agents find the new path first.
|
||||
Output: updated framework docs and root README, new terminal.json/en.json with tests, reworked terminal command check and site docs.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@~/.claude/gsd-core/workflows/execute-plan.md
|
||||
@~/.claude/gsd-core/templates/summary.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
@.planning/phases/14.3-one-command-app-scaffolding-summer-new-and-summercms-io-new/14.3-CONTEXT.md
|
||||
@.planning/phases/14.3-one-command-app-scaffolding-summer-new-and-summercms-io-new/14.3-RESEARCH.md
|
||||
@.planning/phases/14.3-one-command-app-scaffolding-summer-new-and-summercms-io-new/14.3-01-SUMMARY.md
|
||||
@.planning/phases/14.3-one-command-app-scaffolding-summer-new-and-summercms-io-new/14.3-02-SUMMARY.md
|
||||
@docs/setup/installation.md
|
||||
@docs/architecture/go-modules-and-workspaces.md
|
||||
@docs/console/scaffolding.md
|
||||
@../sm-summercmsio-app/terminal_check_test.go
|
||||
@../sm-summercmsio-app/vue-summercmsio-app/app/data/terminal.json
|
||||
</context>
|
||||
|
||||
## Spec-less probe fallback
|
||||
|
||||
No REQUIREMENTS.md IDs or SPEC.md exist for this phase; requirement probing is visibly skipped. The acceptance contract is D-18, D-19, the D-20 terminal run, D-24 (cd line comes from the script, next steps from summer new), D-28, and roadmap success criterion 5.
|
||||
|
||||
## Artifacts this phase produces (this plan)
|
||||
|
||||
- Docs: rewritten `docs/setup/installation.md`; new section in `docs/architecture/go-modules-and-workspaces.md`; section "Creating an application" in `docs/console/scaffolding.md`; updated `docs/console/introduction.md`; one `llms_notes` entry in `docs/site.yaml`; root `README.md` Requirements and Quick start.
|
||||
- Landing page: `terminal.json` groups with comment keys `terminal.createApp`, `terminal.startDatabase`, `terminal.buildAndServe` and their en.json strings (old keys `terminal.getFramework`, `terminal.installCli`, `terminal.buildExample` removed).
|
||||
- Site app tests: const `terminalNewURL`, `terminalScript(groups, newURL)`, helper `terminalRunScript`, test `TestTerminalRunScript`, reworked gated `TestTerminalCommands`; env `SUMMERCMS_NEW_URL` replaces `SUMMERCMS_CLONE_URL`.
|
||||
- Site docs: README test-variable table, CLAUDE.md, DEPLOY.md step 4 wording, `/new` route mention.
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="tracer">
|
||||
<name>Task 1: The one-liner flows from terminal.json to the rendered page, the site checks and the installation guide</name>
|
||||
<files>../sm-summercmsio-app/vue-summercmsio-app/app/data/terminal.json, ../sm-summercmsio-app/vue-summercmsio-app/i18n/locales/en.json, ../sm-summercmsio-app/vue-summercmsio-app/tests/terminal.test.ts, ../sm-summercmsio-app/vue-summercmsio-app/tests/output.test.ts, ../sm-summercmsio-app/terminal_check_test.go, docs/setup/installation.md</files>
|
||||
<read_first>../sm-summercmsio-app/vue-summercmsio-app/app/data/terminal.json, ../sm-summercmsio-app/vue-summercmsio-app/i18n/locales/en.json (start and terminal blocks), ../sm-summercmsio-app/vue-summercmsio-app/tests/terminal.test.ts, ../sm-summercmsio-app/vue-summercmsio-app/tests/output.test.ts (Get started test and assertLeavesRendered), ../sm-summercmsio-app/vue-summercmsio-app/app/components/TerminalCard.vue, ../sm-summercmsio-app/terminal_check_test.go, docs/setup/installation.md, modules/sunrise/README.md, .planning/phases/14.3-one-command-app-scaffolding-summer-new-and-summercms-io-new/14.3-RESEARCH.md (Q9)</read_first>
|
||||
<action>Switch the single command source and everything pinned to it, per D-19 and D-18.
|
||||
|
||||
terminal.json (contract shape unchanged, three groups and six commands): group `terminal.createApp` with `curl -fsSL https://summercms.io/new | bash -s -- demo`; group `terminal.startDatabase` with `cd sm-demo-app` and `docker compose up -d --wait`; group `terminal.buildAndServe` with `summer build`, `summer migrate`, `summer serve`. en.json: remove `terminal.getFramework`, `terminal.installCli`, `terminal.buildExample` (the "every en.json string is rendered" test requires unused keys to go) and add `createApp` "create an app and install the summer CLI", `startDatabase` "start PostgreSQL in Docker", `buildAndServe` "build, migrate and serve". Leave chips, title and links unchanged. tests/terminal.test.ts: expected `copyPayload` string becomes the six new commands joined by newlines, the comment-key list becomes the three new keys; keep the "no prompt, no comment, no trailing newline" test (the one-liner contains no `$` or `#`). tests/output.test.ts: adjust only if a literal of the old commands is pinned there.
|
||||
|
||||
terminal_check_test.go (ungated parts): replace `terminalCloneURL` with `terminalNewURL = "https://summercms.io/new"`; change `terminalScript(groups, newURL string)` so a non-empty `newURL` replaces `terminalNewURL` only inside a command that starts with `curl `; update its doc comment. `TestLoadTerminal` keeps the 3-groups/6-commands assertion and requires the first command to equal `curl -fsSL ` + `terminalNewURL` + ` | bash -s -- demo`. `TestTerminalScript` pins the six new commands verbatim, the override case (`file:///tmp/new.sh` rewrites only the first line) and the "only a curl command is rewritten" case. `TestTerminalCommands` reads the override from `SUMMERCMS_NEW_URL` (its execution flow is reworked in Task 2).
|
||||
|
||||
installation.md: add a first section after the intro, before Requirements content about examples/hello, titled with a plain ASCII heading such as "Create an application with one command", showing the one-liner in an `sh` fence, one sentence on what it does (checks Go 1.27 and Docker, fetches the framework at its latest release tag into `./summercms`, installs `summer`, runs `summer new demo`), and the follow-up commands `cd sm-demo-app`, `docker compose up -d --wait`, `summer build`, `summer migrate`, `summer serve` in a second `sh` fence. Headings stay plain ASCII without code spans (docs policy). Task 3 completes the page.
|
||||
|
||||
Commit the vue submodule change inside `vue-summercmsio-app`, then the test change plus the gitlink bump in `sm-summercmsio-app`, then the docs change in summercms.go. Do not push.</action>
|
||||
<verify>
|
||||
<automated>go -C ../sm-summercmsio-app vet . && go -C ../sm-summercmsio-app test -run '^(TestLoadTerminal|TestTerminalScript|TestTerminalEnv)$' -count=1 -v . && pnpm -C ../sm-summercmsio-app/vue-summercmsio-app generate && pnpm -C ../sm-summercmsio-app/vue-summercmsio-app test && go test ./cmd/summer -run '^TestDocsTree$' -count=1 && go run ./cmd/summer docs:build --check</automated>
|
||||
<fails_when>Non-zero exit; any "--- FAIL" or "not ok" line; the TAP summary shows "# fail" greater than 0; `docs:build --check` prints a problem count; or any of "--- PASS: TestLoadTerminal", "--- PASS: TestTerminalScript" is absent.</fails_when>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- `grep -c 'curl -fsSL https://summercms.io/new | bash -s -- demo' ../sm-summercmsio-app/vue-summercmsio-app/app/data/terminal.json` is 1 and `grep -c 'docker compose up -d --wait' ../sm-summercmsio-app/vue-summercmsio-app/app/data/terminal.json` is 1.
|
||||
- `grep -c 'getFramework\|installCli\|buildExample' ../sm-summercmsio-app/vue-summercmsio-app/i18n/locales/en.json` prints 0.
|
||||
- `grep -c 'SUMMERCMS_NEW_URL' ../sm-summercmsio-app/terminal_check_test.go` is at least 1 and `grep -c 'terminalNewURL' ../sm-summercmsio-app/terminal_check_test.go` is at least 2.
|
||||
- The first `sh` fence in docs/setup/installation.md contains the one-liner and appears before the first mention of `examples/hello`.
|
||||
- `pnpm test` reports 0 failures after `pnpm generate`.
|
||||
</acceptance_criteria>
|
||||
<done>The landing page renders and copies the one-liner flow, the site's ungated checks pin it, and the installation guide opens with the same command.</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: Run the page's commands end to end and update the site docs</name>
|
||||
<precondition>Plan 02 is committed in summercms.go (modules/sunrise/new.sh at HEAD), the Docker daemon is reachable, host ports 54320 and 8080 are free, and pnpm can run `nuxt generate`.</precondition>
|
||||
<files>../sm-summercmsio-app/terminal_check_test.go, ../sm-summercmsio-app/README.md, ../sm-summercmsio-app/CLAUDE.md, ../sm-summercmsio-app/DEPLOY.md</files>
|
||||
<read_first>../sm-summercmsio-app/terminal_check_test.go (terminalEnv, TestTerminalCommands), ../sm-summercmsio-app/README.md (gated test variable table, lines 40-55, and line 3), ../sm-summercmsio-app/CLAUDE.md (lines 7, 39, 55), ../sm-summercmsio-app/DEPLOY.md (launch checklist step 4), ../sm-summercmsio-app/scripts/build.sh (dev mode), ../sm-summercmsio-app/plugins/golem15/summercms/links_test.go (TestTerminalCommandsInPage), modules/sunrise/README.md (override variables)</read_first>
|
||||
<action>Rework the gated execution so the new six commands run verbatim (D-19, D-20 on the developer machine).
|
||||
|
||||
Add `terminalRunScript(script string) string`: returns the script unchanged except that a line equal to `summer serve` becomes: start `summer serve` in the background, poll `http://127.0.0.1:8080/` with curl for at most 60 seconds, print the response body, stop the server and fail when it never answered. Cover it with an ungated `TestTerminalRunScript` (serve line replaced, all other lines verbatim, a script without `summer serve` unchanged). `TestTerminalCommands`: run `bash -euo pipefail -c terminalRunScript(terminalScript(groups, os.Getenv("SUMMERCMS_NEW_URL")))` in a temp work dir with `terminalEnv` plus `COMPOSE_PROJECT_NAME=summercms-terminal-<pid>`; SUMMERCMS_REPO and SUMMERCMS_REF pass through untouched (terminalEnv only drops the framework config prefix, GOWORK and GOBIN); register a `t.Cleanup` that runs `docker compose down -v` with the same project name in the directory named by the page's `cd` command when it exists; require the output to contain `"plugin":"golem15.demo"` (this replaces the old greeter-output assertion). Keep the 10 minute budget, raise it to 20 if the first run needs it.
|
||||
|
||||
Site docs: README.md gated-variable table — `SUMMERCMS_TERMINAL_CHECK=1` runs the six Get started commands (the one-liner, PostgreSQL in Docker, build, migrate, serve) in a fresh shell with a temp GOBIN first on PATH, needs Docker and free ports 54320 and 8080, and requires the starter route JSON; replace the clone-URL override row with `SUMMERCMS_NEW_URL` (replaces only the script URL, for example `file://` + the framework checkout's `modules/sunrise/new.sh` before deploy) and note that `SUMMERCMS_REPO` and `SUMMERCMS_REF` pass through to the script for pre-tag runs; mention in the opening paragraph that the binary also serves the bootstrap script at `/new`. CLAUDE.md: same variable change on the gated-variables line, `/new` in the "What this is" paragraph. DEPLOY.md step 4: the terminal check now runs the six one-liner commands with no overrides and needs Docker; add that after the redeploy the framework's `scripts/check-phase14.3.sh --acceptance-production` repeats the run from a clean container. Leave every `v0.1.12` reference alone (the bump is a post-phase user action, D-28).
|
||||
|
||||
Run the gated check with local overrides (pre-tag, pre-deploy) and the page drift guard after a dev build, then commit in `sm-summercmsio-app`. Do not push.</action>
|
||||
<verify>
|
||||
<automated>FW="$PWD" && go -C ../sm-summercmsio-app test -run '^TestTerminalRunScript$' -count=1 -v . && SUMMERCMS_TERMINAL_CHECK=1 SUMMERCMS_NEW_URL="file://$FW/modules/sunrise/new.sh" SUMMERCMS_REPO="$FW" SUMMERCMS_REF="$(git -C "$FW" rev-parse HEAD)" go -C ../sm-summercmsio-app test -run '^TestTerminalCommands$' -count=1 -v -timeout 25m . && (cd ../sm-summercmsio-app && scripts/build.sh dev) && SUMMERCMS_REQUIRE_BUILD=1 go -C ../sm-summercmsio-app/plugins/golem15/summercms test -run '^TestTerminalCommandsInPage$' -count=1 -v ./...</automated>
|
||||
<fails_when>Non-zero exit; "--- FAIL"; "--- SKIP: TestTerminalCommands" or "--- SKIP: TestTerminalCommandsInPage"; or any of "--- PASS: TestTerminalRunScript", "--- PASS: TestTerminalCommands", "--- PASS: TestTerminalCommandsInPage" is absent.</fails_when>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- `grep -c 'SUMMERCMS_CLONE_URL' ../sm-summercmsio-app/terminal_check_test.go ../sm-summercmsio-app/README.md ../sm-summercmsio-app/CLAUDE.md` prints 0 for each file.
|
||||
- `grep -c 'golem15.demo' ../sm-summercmsio-app/terminal_check_test.go` is at least 1 and `grep -c 'handled=true' ../sm-summercmsio-app/terminal_check_test.go` prints 0.
|
||||
- `grep -c '/new' ../sm-summercmsio-app/README.md` is at least 1 and `grep -c 'acceptance-production' ../sm-summercmsio-app/DEPLOY.md` is at least 1.
|
||||
- `grep -c 'v0.1.12' ../sm-summercmsio-app/go.mod` is unchanged from before this plan.
|
||||
- After the gated run, `docker ps -a --filter label=com.docker.compose.project` lists no `summercms-terminal-` container.
|
||||
</acceptance_criteria>
|
||||
<done>The six landing-page commands run verbatim against the local script and framework and end with the starter route answering, the built page shows exactly those commands, and the site docs describe the new check and route.</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 3: Framework docs for summer new and the regenerated llms outputs</name>
|
||||
<files>docs/setup/installation.md, docs/architecture/go-modules-and-workspaces.md, docs/console/scaffolding.md, docs/console/introduction.md, docs/site.yaml, README.md</files>
|
||||
<read_first>docs/setup/installation.md, docs/architecture/go-modules-and-workspaces.md, docs/console/scaffolding.md, docs/console/introduction.md, docs/site.yaml (llms_notes), README.md (Requirements, Quick start, Using SummerCMS in an application), internal/build/new.go (rules and resolution order as implemented), cmd/summer/main.go (newCommand flags), modules/sunrise/README.md, internal/docsite/check_commands.go (what the command checker reads), internal/docsite/check_policy.go (heading and src= rules), .planning/phases/14.3-one-command-app-scaffolding-summer-new-and-summercms-io-new/14.3-RESEARCH.md (Q8, the obsolete main.go warning)</read_first>
|
||||
<action>Finish D-18 in summercms.go, describing behaviour exactly as implemented in plans 01 and 02.
|
||||
|
||||
installation.md: description frontmatter mentions the one-command start. Order: intro; the one-command section from Task 1; a section on what the script does and its options (prerequisites Go 1.27, git, Docker; framework cloned into `./summercms` and checked out at the newest release tag; re-runs fetch tags and move forward only, refusing a checkout with local changes or on a branch; `SUMMERCMS_REPO`, `SUMMERCMS_DIR`, `SUMMERCMS_REF`; the PATH hint; the `cd` line); a section on running the app (compose PostgreSQL 16 on 127.0.0.1:54320, `.env` with the generated key, `summer build`, `summer migrate`, `summer serve`, `curl http://127.0.0.1:8080/`); a section on `summer new` without the script (clone, `go install ./cmd/summer`, run `summer new demo` next to the checkout or pass `--framework`); then the existing manual examples/hello walkthrough as the second path. Remove the callout that lists the old example defects entirely: the http.yaml gap is fixed and research found the main.go warning obsolete. Keep the existing Go fence with its `src=` reference intact.
|
||||
|
||||
go-modules-and-workspaces.md: add a section (plain ASCII heading, for example "Applications created by summer new") covering module path `git.golem15.com/golem15/sm-<name>-app`, the relative framework `replace`, the detection order (`--framework`, `../summercms`, `../summercms.go` matched by module path, then the summer binary's own source tree), the committed go.work with relative `use` paths, no root go.sum and gitignored go.work.sum; change the plugin:add sentence to say it adds a relative path to the nearest go.work. scaffolding.md: new section "Creating an application" before "Creating a plugin" with `summer new acme`, the name rule (lower-case letters and digits starting with a letter, not a Go keyword, `main` or `internal`), the refusal of an existing directory, a table of `--framework`, `--no-plugin`, `--no-git`, the files written (go.mod, go.work, summer.yaml, config/, .env, .env.example, compose.yaml, .gitignore, CLAUDE.md, AGENTS.md, the starter plugin with `GET /`), the first commit and the printed next steps, and the naming convention `sm-<name>-app`, `sm-<name>-plugin`, `sm-<name>-theme` (D-01). introduction.md: the summer row says it creates, builds and watches applications and is installed by the one-liner or `go install ./cmd/summer`; the chapter list mentions `summer new` under Scaffolding. site.yaml: add one `llms_notes` entry stating that a new application starts with `curl -fsSL https://summercms.io/new | bash -s -- <name>` or `summer new <name>` next to a framework checkout.
|
||||
|
||||
Root README.md: Requirements say Docker runs the generated application's PostgreSQL; Quick start leads with the one-liner and the follow-up commands, then `summer new` for an existing checkout, then the examples/hello walkthrough; remove the subsection that lists the old example defects; "Using SummerCMS in an application" says `summer new` writes the go.mod with a relative replace for you. Use neutral names (`demo`, `acme`) only. Then build the docs into a temp directory and confirm the llms outputs contain the new material (D-18 regenerate; the files are build output and are not committed). Commit as one docs commit.</action>
|
||||
<verify>
|
||||
<automated>go test ./cmd/summer -run '^(TestDocsTree|TestDocsBuildRealTree|TestDocsAIOutputsInSync|TestDocsRequiredPages|TestEveryModuleInSidebar)$' -count=1 -v && go run ./cmd/summer docs:build --check && bash -c 'set -e; o="$(mktemp -d)/site"; go run ./cmd/summer docs:build --out "$o" >/dev/null; grep -qF "curl -fsSL https://summercms.io/new | bash -s -- demo" "$o/llms-full.txt"; grep -qF "summer new" "$o/llms-full.txt"; grep -qF "summercms.io/new" "$o/llms.txt"; echo LLMS-OK'</automated>
|
||||
<fails_when>Non-zero exit; any "--- FAIL"; `docs:build --check` prints problems; any of "--- PASS: TestDocsTree", "--- PASS: TestDocsAIOutputsInSync" absent; or the output lacks "LLMS-OK".</fails_when>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- `grep -c 'git checkout -- examples/hello/main.go' docs/setup/installation.md README.md` prints 0 for each file, `grep -c 'http.body_limits.default_bytes is required' docs/setup/installation.md` prints 0, and `grep -c 'Known issues' README.md` prints 0.
|
||||
- `grep -c 'summer new' docs/console/scaffolding.md docs/architecture/go-modules-and-workspaces.md docs/setup/installation.md` is at least 1 for each file.
|
||||
- `grep -c -- '--no-plugin' docs/console/scaffolding.md` and `grep -c -- '--no-git' docs/console/scaffolding.md` are at least 1.
|
||||
- `grep -c 'summercms.io/new' docs/site.yaml` is 1.
|
||||
- llms-full.txt from a fresh build contains the one-liner and `summer new`; llms.txt contains `summercms.io/new` through the llms note.
|
||||
- `grep -Eci 'fonoteka|p[lł]ytarium' docs/setup/installation.md docs/console/scaffolding.md docs/architecture/go-modules-and-workspaces.md README.md` prints 0 for each file.
|
||||
</acceptance_criteria>
|
||||
<done>Installation, architecture and console docs plus the root README lead with the one-command start and document `summer new` exactly as built; docs checks and llms outputs are green.</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<threat_model>
|
||||
## Trust Boundaries
|
||||
|
||||
| Boundary | Description |
|
||||
|----------|-------------|
|
||||
| Docs and landing page -> reader's shell | Readers copy and run the shown commands verbatim |
|
||||
| Gated terminal check -> local Docker | The check starts real containers and a server |
|
||||
|
||||
## STRIDE Threat Register
|
||||
|
||||
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|
||||
|-----------|----------|-----------|----------|-------------|-----------------|
|
||||
| T-14.3-20 | Tampering | one-liner shown on page and in docs | medium | mitigate | HTTPS URL with `curl -fsSL`, pinned verbatim by TestTerminalScript, terminal.test.ts and TestTerminalCommandsInPage; docs fence checked by the plan 03 Task 3 llms grep |
|
||||
| T-14.3-21 | Denial of Service | gated terminal check leftovers | low | mitigate | Unique COMPOSE_PROJECT_NAME, `t.Cleanup` with `docker compose down -v`, bounded serve poll that stops the server |
|
||||
| T-14.3-22 | Information Disclosure | docs naming a consuming application | low | mitigate | `docs:build --check` forbidden-name rule plus the acceptance grep on README.md |
|
||||
| T-14.3-SC | Tampering | npm/pip/cargo installs | high | mitigate | No new npm or Go packages; `pnpm generate` and `pnpm test` use the existing lockfile only |
|
||||
</threat_model>
|
||||
|
||||
<verification>
|
||||
Task 1 (ungated site tests, pnpm generate and test, docs tree), Task 2 (gated six-command run, dev build, page drift guard), Task 3 (docs tests, docs:build --check, llms grep). Then `go vet ./... && go test ./... -short` in summercms.go and `go -C ../sm-summercmsio-app test ./... -count=1`.
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- Roadmap success criterion 5: installation.md leads with the one-liner, go-modules-and-workspaces.md and the console docs cover `summer new`, llms outputs include the changes, docs checks pass.
|
||||
- D-19: the landing page "Get started" shows and copies the one-liner flow; TestTerminalCommands runs it end to end locally.
|
||||
- Release-gated items (site require bump to v0.1.13, redeploy, production acceptance) are left to the post-phase user steps listed in plan 04.
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
Create `.planning/phases/14.3-one-command-app-scaffolding-summer-new-and-summercms-io-new/14.3-03-SUMMARY.md` when done.
|
||||
</output>
|
||||
</content>
|
||||
</invoke>
|
||||
@@ -0,0 +1,312 @@
|
||||
---
|
||||
phase: 14.3-one-command-app-scaffolding-summer-new-and-summercms-io-new
|
||||
plan: 04
|
||||
type: execute
|
||||
wave: 4
|
||||
depends_on: ["14.3-03"]
|
||||
files_modified:
|
||||
- internal/build/new_golden_test.go
|
||||
- internal/build/new_test.go
|
||||
- internal/build/build_test.go
|
||||
- internal/build/testdata/new/go.mod.golden
|
||||
- internal/build/testdata/new/go.work.golden
|
||||
- internal/build/testdata/new/summer.yaml.golden
|
||||
- internal/build/testdata/new/main.go.golden
|
||||
- internal/build/testdata/new/plugins.gen.go.golden
|
||||
- internal/build/testdata/new/config__app.yaml.golden
|
||||
- internal/build/testdata/new/config__database.yaml.golden
|
||||
- internal/build/testdata/new/config__http.yaml.golden
|
||||
- internal/build/testdata/new/config__storage.yaml.golden
|
||||
- internal/build/testdata/new/dot-env.golden
|
||||
- internal/build/testdata/new/dot-env.example.golden
|
||||
- internal/build/testdata/new/dot-gitignore.golden
|
||||
- internal/build/testdata/new/compose.yaml.golden
|
||||
- internal/build/testdata/new/CLAUDE.md.golden
|
||||
- internal/build/testdata/new/plugins__demo__plugin.go.golden
|
||||
- internal/build/testdata/new/plugins__demo__routes.go.golden
|
||||
- internal/build/testdata/new/next-steps.txt.golden
|
||||
- internal/build/testdata/new-no-plugin/go.mod.golden
|
||||
- internal/build/testdata/new-no-plugin/go.work.golden
|
||||
- internal/build/testdata/new-no-plugin/summer.yaml.golden
|
||||
- internal/build/testdata/new-no-plugin/plugins.gen.go.golden
|
||||
- internal/build/testdata/new-no-plugin/CLAUDE.md.golden
|
||||
- internal/build/testdata/new-no-plugin/next-steps.txt.golden
|
||||
- modules/lagoon/keygen_test.go
|
||||
- cmd/summer/main_test.go
|
||||
- modules/sunrise/script_behavior_test.go
|
||||
- scripts/check-phase14.3.sh
|
||||
- .planning/phases/14.3-one-command-app-scaffolding-summer-new-and-summercms-io-new/14.3-SECURITY-REVIEW.md
|
||||
- .planning/phases/14.3-one-command-app-scaffolding-summer-new-and-summercms-io-new/14.3-VALIDATION.md
|
||||
autonomous: true
|
||||
requirements: [D-01, D-02, D-03, D-04, D-05, D-06, D-07, D-08, D-09, D-10, D-11, D-12, D-13, D-14, D-15, D-16, D-17, D-18, D-19, D-20, D-21, D-22, D-23, D-24, D-25, D-26, D-27, D-28]
|
||||
estimate:
|
||||
tokens: 150000
|
||||
raw_tokens: 150000
|
||||
tasks: 3
|
||||
confidence: low
|
||||
must_haves:
|
||||
truths:
|
||||
- "D-21: TestNewGolden renders `summer new demo` (fixed test key, --no-git, framework through a `summercms` symlink sibling) and every listed file matches its golden under internal/build/testdata/new byte for byte; TestNewGoldenNoPlugin does the same for --no-plugin; goldens change only with `-update`."
|
||||
- "D-21: the real build of a generated app (with and without the starter plugin) runs in TestNewAppSmoke and TestNewNoPluginBuilds and is skipped only under -short."
|
||||
- "D-02/D-22 error cases: TestNewValidateAppName rejects empty, upper-case, leading digit, punctuation, every Go keyword, main and internal, and accepts init, string, vendor, testdata, a1; TestNewRefusesExistingTarget covers a directory, a file, a symlink and a dangling symlink and proves each is untouched."
|
||||
- "D-04 error cases: TestNewFrameworkResolution proves --framework wins (and a mismatched module there is an error, not a fall-through), ../summercms before ../summercms.go, module-path matching skips a wrong sibling, the binary-source candidate is used last, and with none the error contains --framework; TestNewRefusesTargetInsideFramework refuses a target inside the checkout."
|
||||
- "Edge: TestNewCleansUpOnFailure forces a failure after the target was created and asserts the target is gone while a pre-existing sibling file is untouched."
|
||||
- "D-08/D-10/D-11: TestNewEnvFileMode (0600), TestNewAgentsSymlink (AGENTS.md -> CLAUDE.md), TestNewGitFirstCommit (one commit on main, fallback author SummerCMS <summer@localhost> only when no identity, .env untracked, .env.example/go.work/main.go/plugins.gen.go/AGENTS.md tracked), TestNewGitUsesConfiguredIdentity, TestNewGitMissingIsWarning and TestNewNoGit pass."
|
||||
- "D-27: TestMakeAndAddPluginOnHelloCopy asserts every go.work use path is relative; TestMakePluginRoutesUnchanged asserts plain make:plugin still renders the no-route stub; TestGenerateAppKey proves 44 characters, 32 decoded bytes and distinct values."
|
||||
- "D-14..D-17/D-26: TestScriptBehavior drives new.sh with only git and stubs on PATH and proves usage and name errors, existing app refusal, Go missing/old/devel handling, git required, Docker warning only, newest numeric release tag ignoring pre-releases, SUMMERCMS_REF, fast-forward re-runs, no-op when current, refusal of dirty, branch, non-fast-forward, moved-tag, non-framework and non-git checkouts with HEAD unchanged, summer invoked with `new demo --framework <abs>` from the parent with stdin not read, the cd line, and the PATH hint only when needed; TestScriptShellcheck passes."
|
||||
- "Gate: `bash scripts/check-phase14.3.sh --all` runs vet, full tests, examples/hello tests, docs check, site plugin/app/vue tests, the generator stage, the gated bootstrap and terminal runs, the D-20 acceptance stage, forbidden-surface scans and the security check, requires a named PASS for each required test, and ends with `Phase 14.3 gate passed`."
|
||||
- "14.3-SECURITY-REVIEW.md lists T-14.3-01..T-14.3-26 and T-14.3-SC, each high row mitigated with a named test or gate stage; 14.3-VALIDATION.md is validated and nyquist_compliant."
|
||||
prohibitions:
|
||||
- requirement_id: D-21
|
||||
category: transparency
|
||||
status: resolved
|
||||
verification: judgment
|
||||
resolution: flagged-unverified
|
||||
reason: "Gate requires named PASS lines; descriptor-less, human review recommended."
|
||||
statement: "The phase gate must never pass on a skipped, filtered-to-zero or -update test run."
|
||||
- requirement_id: D-28
|
||||
category: safety
|
||||
status: resolved
|
||||
verification: judgment
|
||||
resolution: flagged-unverified
|
||||
reason: "Release steps are listed for the user only; descriptor-less, human review recommended."
|
||||
statement: "No task pushes commits or tags, bumps the site's framework require, or deploys the site."
|
||||
- requirement_id: D-05
|
||||
category: safety
|
||||
status: resolved
|
||||
verification: judgment
|
||||
resolution: flagged-unverified
|
||||
reason: "Gate scans go.mod against v0.1.12 and the dependency graph; descriptor-less, human review recommended."
|
||||
statement: "The phase must add no Go module requirement and no runtime plugin loading."
|
||||
artifacts:
|
||||
- path: "internal/build/new_golden_test.go"
|
||||
provides: "golden harness with -update flag"
|
||||
contains: "TestNewGolden"
|
||||
- path: "internal/build/testdata/new/go.work.golden"
|
||||
provides: "pinned relative workspace"
|
||||
contains: "./plugins/demo"
|
||||
- path: "internal/build/new_test.go"
|
||||
provides: "generator error, git, file-mode and build tests"
|
||||
contains: "TestNewFrameworkResolution"
|
||||
- path: "modules/sunrise/script_behavior_test.go"
|
||||
provides: "bootstrap script behaviour matrix with stubs"
|
||||
contains: "TestScriptBehavior"
|
||||
- path: "scripts/check-phase14.3.sh"
|
||||
provides: "fail-closed phase gate"
|
||||
contains: "Phase 14.3 gate passed"
|
||||
- path: ".planning/phases/14.3-one-command-app-scaffolding-summer-new-and-summercms-io-new/14.3-VALIDATION.md"
|
||||
provides: "validated per-plan map"
|
||||
contains: "nyquist_compliant: true"
|
||||
key_links:
|
||||
- from: "internal/build/new_golden_test.go"
|
||||
to: "internal/build/new.go"
|
||||
via: "NewApp output compared with testdata/new goldens"
|
||||
pattern: "NewApp("
|
||||
- from: "modules/sunrise/script_behavior_test.go"
|
||||
to: "modules/sunrise/new.sh"
|
||||
via: "the test runs the embedded script bytes with bash on a stub PATH"
|
||||
pattern: "Script()"
|
||||
- from: "scripts/check-phase14.3.sh"
|
||||
to: ".planning/phases/14.3-one-command-app-scaffolding-summer-new-and-summercms-io-new/14.3-SECURITY-REVIEW.md"
|
||||
via: "gate fails on a missing threat ID or an open high threat"
|
||||
pattern: "T-14.3-"
|
||||
---
|
||||
|
||||
<objective>
|
||||
Close Phase 14.3 with the dedicated unit-test plan (project rule 3, D-21): golden output for every file `summer new` writes, real builds in temp directories, the generator's error cases, the bootstrap script's behaviour matrix, a fail-closed phase gate, security evidence and validation sign-off, then hand the release steps to the user.
|
||||
|
||||
Purpose: make every locked decision observably fail when broken, before the framework is tagged `v0.1.13`.
|
||||
Output: golden harness and goldens, generator/CLI/lagoon/script tests, `scripts/check-phase14.3.sh --all`, updated security review and validation map, and the post-phase release checklist.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@~/.claude/gsd-core/workflows/execute-plan.md
|
||||
@~/.claude/gsd-core/templates/summary.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
@.planning/phases/14.3-one-command-app-scaffolding-summer-new-and-summercms-io-new/14.3-01-SUMMARY.md
|
||||
@.planning/phases/14.3-one-command-app-scaffolding-summer-new-and-summercms-io-new/14.3-02-SUMMARY.md
|
||||
@.planning/phases/14.3-one-command-app-scaffolding-summer-new-and-summercms-io-new/14.3-03-SUMMARY.md
|
||||
@.planning/phases/14.3-one-command-app-scaffolding-summer-new-and-summercms-io-new/14.3-VALIDATION.md
|
||||
@.planning/phases/14.3-one-command-app-scaffolding-summer-new-and-summercms-io-new/14.3-SECURITY-REVIEW.md
|
||||
@internal/build/new.go
|
||||
@internal/build/build_test.go
|
||||
@modules/sunrise/new.sh
|
||||
@scripts/check-phase14.3.sh
|
||||
</context>
|
||||
|
||||
## Spec-less probe fallback
|
||||
|
||||
No REQUIREMENTS.md IDs or SPEC.md exist; requirement probing is visibly skipped. Tests map directly to D-01..D-28, the six roadmap success criteria, the edge predicates and prohibitions carried in plans 01-03, and the threat register T-14.3-01..T-14.3-26.
|
||||
|
||||
## Artifacts this phase produces (this plan)
|
||||
|
||||
- Golden harness: `var update = flag.Bool("update", false, "rewrite golden files")` in package build tests; goldens under `internal/build/testdata/new/` and `internal/build/testdata/new-no-plugin/`, file names derived from the relative path with `/` replaced by `__` and a leading `.` replaced by `dot-` (keeps them clear of the framework's `.env*` ignore rules).
|
||||
- Tests: `TestNewGolden`, `TestNewGoldenNoPlugin`, `TestNewNoPluginBuilds`, `TestNewValidateAppName`, `TestNewRefusesExistingTarget`, `TestNewFrameworkResolution`, `TestNewRefusesTargetInsideFramework`, `TestNewCleansUpOnFailure`, `TestNewEnvFileMode`, `TestNewAgentsSymlink`, `TestNewGitFirstCommit`, `TestNewGitUsesConfiguredIdentity`, `TestNewGitMissingIsWarning`, `TestNewNoGit`, `TestNewRequiresValidAppKey`, `TestMakePluginRoutesUnchanged`, `TestGenerateAppKey`, `TestNewCommandViaCLI`, `TestScriptBehavior`, `TestScriptShellcheck`.
|
||||
- Gate stages `--unit` and `--all` in `scripts/check-phase14.3.sh` (final line `Phase 14.3 gate passed`).
|
||||
- Updated `14.3-SECURITY-REVIEW.md` (T-14.3-01..26 + SC with test evidence) and `14.3-VALIDATION.md` (validated).
|
||||
- Post-phase release checklist (user actions) below.
|
||||
|
||||
## Multi-source coverage audit
|
||||
|
||||
| SOURCE | ID | Feature/Requirement | Plan | Status | Notes |
|
||||
|---|---|---|---|---|---|
|
||||
| GOAL | — | One command starts an app: `summer new` + `curl … /new \| bash`, shipped as v0.1.13 | 01-04 | COVERED | Tag itself is a post-phase user action (D-28) |
|
||||
| REQ | — | No REQUIREMENTS.md IDs mapped | — | COVERED | Visible spec-less fallback; D-IDs used |
|
||||
| CONTEXT | D-01 | sm-<name>-app/-plugin/-theme convention in CLAUDE.md | 01, 03 | COVERED | Template + scaffolding docs |
|
||||
| CONTEXT | D-02 | Name regex, refuse existing dir | 01, 04 | COVERED | |
|
||||
| CONTEXT | D-03 | go.mod module, go 1.27.0, relative replace | 01, 04 | COVERED | Golden go.mod |
|
||||
| CONTEXT | D-04 | --framework > siblings by module path > binary source > error | 01, 04 | COVERED | |
|
||||
| CONTEXT | D-05 | Starter plugin via make:plugin/plugin:add, go.work, HTTP route | 01, 04 | COVERED | StarterRoute option, one template |
|
||||
| CONTEXT | D-06 | --no-plugin (empty list builds) | 01, 04 | COVERED | |
|
||||
| CONTEXT | D-07 | Complete config/, hello http.yaml | 01, 04 | COVERED | |
|
||||
| CONTEXT | D-08 | .env.example, .env with lagoon key, .gitignore | 01, 04 | COVERED | |
|
||||
| CONTEXT | D-09 | compose.yaml PostgreSQL 16 matching .env | 01, 04 | COVERED | |
|
||||
| CONTEXT | D-10 | CLAUDE.md + AGENTS.md | 01, 04 | COVERED | Symlink |
|
||||
| CONTEXT | D-11 | git init + first commit, --no-git, no-identity fallback | 01, 04 | COVERED | |
|
||||
| CONTEXT | D-12 | build/migrate/serve with no edits | 01 | COVERED | --generator gate stage |
|
||||
| CONTEXT | D-13 | Script in framework package, served at GET /new text/plain | 02 | COVERED | |
|
||||
| CONTEXT | D-14 | main() wrapper | 02, 04 | COVERED | Truncation tests |
|
||||
| CONTEXT | D-15 | Prerequisites, clone/update, install + PATH hint, summer new, next steps | 02, 04 | COVERED | |
|
||||
| CONTEXT | D-16 | Latest release tag, ff-only, refuse dirty/branch | 02, 04 | COVERED | |
|
||||
| CONTEXT | D-17 | Clone location and env overrides | 02, 04 | COVERED | |
|
||||
| CONTEXT | D-18 | Docs, llms, module READMEs | 01, 02, 03 | COVERED | lagoon README in 01, sunrise README in 02 |
|
||||
| CONTEXT | D-19 | Landing page one-liner + TestTerminalCommands | 03 | COVERED | Redeploy is post-phase |
|
||||
| CONTEXT | D-20 | Container acceptance | 02, 04 | COVERED | Production variant post-deploy |
|
||||
| CONTEXT | D-21 | Golden + real build + error cases, tests last | 04 | COVERED | |
|
||||
| CONTEXT | D-22 | Keyword/main/internal denylist | 01, 04 | COVERED | |
|
||||
| CONTEXT | D-23 | Fixed non-5432 loopback port | 01, 04 | COVERED | 127.0.0.1:54320 |
|
||||
| CONTEXT | D-24 | summer new prints next steps; script prints cd + PATH hint | 01, 02, 04 | COVERED | |
|
||||
| CONTEXT | D-25 | No root go.sum; go.work.sum ignored | 01, 04 | COVERED | |
|
||||
| CONTEXT | D-26 | modules/<pkg> embeds script; SUMMERCMS_* overrides | 02, 04 | COVERED | modules/sunrise |
|
||||
| CONTEXT | D-27 | Relative go.work, starter route option, GenerateAppKey, hello http.yaml | 01, 04 | COVERED | |
|
||||
| CONTEXT | D-28 | Release ordering | 02, 03, 04 | COVERED | Post-phase checklist |
|
||||
| RESEARCH | — | Pitfalls 1-14 (absolute go.work, ancestor go.work, keyword names, partial dir, git identity, pipefail, prompts, GOVERSION, GOBIN, Postgres readiness, docker socket, safe.directory, pre-tag ref, release coupling) | 01-04 | COVERED | |
|
||||
| RESEARCH | — | Security Domain (V5, V6, V10, V12, V14) | 01, 02, 04 | COVERED | 14.3-SECURITY-REVIEW.md |
|
||||
|
||||
Excluded by explicit scope: `summer new --theme` / sm-<name>-theme scaffolding, prebuilt release binaries, standalone plugin scaffolding beyond make:plugin, Windows support.
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="tracer">
|
||||
<name>Task 1: Golden harness pins every file summer new writes</name>
|
||||
<files>internal/build/new_golden_test.go, internal/build/testdata/new/go.mod.golden, internal/build/testdata/new/go.work.golden, internal/build/testdata/new/summer.yaml.golden, internal/build/testdata/new/main.go.golden, internal/build/testdata/new/plugins.gen.go.golden, internal/build/testdata/new/config__app.yaml.golden, internal/build/testdata/new/config__database.yaml.golden, internal/build/testdata/new/config__http.yaml.golden, internal/build/testdata/new/config__storage.yaml.golden, internal/build/testdata/new/dot-env.golden, internal/build/testdata/new/dot-env.example.golden, internal/build/testdata/new/dot-gitignore.golden, internal/build/testdata/new/compose.yaml.golden, internal/build/testdata/new/CLAUDE.md.golden, internal/build/testdata/new/plugins__demo__plugin.go.golden, internal/build/testdata/new/plugins__demo__routes.go.golden, internal/build/testdata/new/next-steps.txt.golden, internal/build/testdata/new-no-plugin/go.mod.golden, internal/build/testdata/new-no-plugin/go.work.golden, internal/build/testdata/new-no-plugin/summer.yaml.golden, internal/build/testdata/new-no-plugin/plugins.gen.go.golden, internal/build/testdata/new-no-plugin/CLAUDE.md.golden, internal/build/testdata/new-no-plugin/next-steps.txt.golden</files>
|
||||
<read_first>internal/build/new.go, internal/build/new_test.go (TestNewAppSmoke helper for the symlinked framework), internal/build/stubs/new.tmpl, internal/build/build_test.go (copyHelloApp, execGo), .gitignore (framework ignore rules: .env, .env.*, *.out, coverage.*), .planning/phases/14.3-one-command-app-scaffolding-summer-new-and-summercms-io-new/14.3-VALIDATION.md (Wave 0 requirements)</read_first>
|
||||
<action>Introduce the golden infrastructure (none exists yet) and pin the D-03, D-05, D-07..D-10, D-23..D-25 output.
|
||||
|
||||
In `internal/build/new_golden_test.go` declare `var update = flag.Bool("update", false, "rewrite golden files")` and a shared helper that creates a temp parent with a `summercms` symlink to the repository root (so the replace is deterministically `../summercms`) and runs `NewApp` with name `demo`, a fixed test key (base64 of the 32 bytes 0x00..0x1f), `NoGit: true`, capturing `out`. `TestNewGolden` compares these files of the result with `testdata/new/<golden name>`: go.mod, go.work, summer.yaml, main.go, plugins.gen.go, config/app.yaml, config/database.yaml, config/http.yaml, config/storage.yaml, .env, .env.example, .gitignore, compose.yaml, CLAUDE.md, plugins/demo/plugin.go, plugins/demo/routes.go, plus the captured output with the temp parent path replaced by `<parent>` as `next-steps.txt`. Golden names: relative path with `/` replaced by `__` and a leading `.` replaced by `dot-`. Excluded and asserted instead: AGENTS.md (symlink, Task 2), plugins/demo/go.mod and go.sum (dependency versions drift; assert the plugin go.mod contains `replace git.golem15.com/golem15/summercms => ../../../summercms`). `TestNewGoldenNoPlugin` does the same with `NoPlugin: true` against `testdata/new-no-plugin/` for go.mod, go.work, summer.yaml, plugins.gen.go, CLAUDE.md and next-steps.txt. With `-update` the tests rewrite the goldens and still fail-safe compare on the next run; without it a mismatch prints a unified-style diff of the first differing lines and the golden path. Both tests run `go mod tidy` through makePlugin (module cache) and are not -short gated; they never build. Generate the goldens with `-update` once, review every golden by eye against D-03 (relative replace, go 1.27.0, toolchain), D-23 (`127.0.0.1:54320:5432`), D-25 (.gitignore entries, no go.sum) and D-10 (llms URLs, naming convention, neutral text), and commit tests and goldens together.</action>
|
||||
<verify>
|
||||
<automated>go test ./internal/build/ -run '^(TestNewGolden|TestNewGoldenNoPlugin)$' -count=1 -v && bash -c 'set -e; ! grep -nE "^\s*/|=> /" internal/build/testdata/new/go.mod.golden internal/build/testdata/new/go.work.golden internal/build/testdata/new-no-plugin/go.mod.golden internal/build/testdata/new-no-plugin/go.work.golden; git check-ignore -q internal/build/testdata/new/dot-env.golden && exit 1; echo GOLDEN-OK'</automated>
|
||||
<fails_when>Non-zero exit; any "--- FAIL"; "no tests to run"; "--- PASS: TestNewGolden" or "--- PASS: TestNewGoldenNoPlugin" absent; an absolute path printed from a golden go.mod/go.work; or the output lacks "GOLDEN-OK".</fails_when>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- Every file in this task's file list exists and is tracked by git: `git ls-files internal/build/testdata` prints exactly the 23 golden paths listed above.
|
||||
- `grep -c './plugins/demo' internal/build/testdata/new/go.work.golden` is 1 and `grep -c 'replace git.golem15.com/golem15/summercms => ../summercms' internal/build/testdata/new/go.mod.golden` is 1.
|
||||
- `grep -c '127.0.0.1:54320:5432' internal/build/testdata/new/compose.yaml.golden` is 1 and `grep -c 'go.work.sum' internal/build/testdata/new/dot-gitignore.golden` is 1.
|
||||
- `grep -c 'https://summercms.io/docs/llms.txt' internal/build/testdata/new/CLAUDE.md.golden` is 1.
|
||||
- Changing one byte of a golden makes `TestNewGolden` fail (checked once by hand, then restored).
|
||||
</acceptance_criteria>
|
||||
<done>Every file `summer new` writes for the default and `--no-plugin` variants is pinned byte for byte, with an explicit `-update` path.</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: Generator, CLI and key error-case and behaviour matrix</name>
|
||||
<files>internal/build/new_test.go, internal/build/build_test.go, modules/lagoon/keygen_test.go, cmd/summer/main_test.go</files>
|
||||
<read_first>internal/build/new.go (ValidateAppName, resolveFramework, frameworkFromBinary, cleanup, git step), internal/build/new_test.go, internal/build/build_test.go (TestMakeAndAddPluginOnHelloCopy, TestScaffoldPluginSmoke), modules/lagoon/keygen.go, modules/lagoon/keygen_test.go, cmd/summer/main.go (newCommand), cmd/summer/main_test.go (TestToolCommandNames, copyHelloApp, t.Chdir use), .planning/phases/14.3-one-command-app-scaffolding-summer-new-and-summercms-io-new/14.3-RESEARCH.md (Pitfalls 3, 4, 5)</read_first>
|
||||
<action>Cover every generator branch from plan 01 with named tests (D-21: stdlib testing, -short gating only for real builds).
|
||||
|
||||
internal/build/new_test.go: `TestNewValidateAppName` table (reject "", "Demo", "1x", "a-b", "a.b", "a/b", every keyword from the Go spec list via `go/token`, "main", "internal"; accept "init", "string", "vendor", "testdata", "a1"; rejected keywords' errors mention keyword). `TestNewRefusesExistingTarget` (existing directory with a file inside, regular file, symlink to a directory, dangling symlink: each errors and the entry is unchanged afterwards). `TestNewFrameworkResolution` (replace `frameworkFromBinary` for the test: flag wins over siblings; a flag pointing at a go.mod with another module errors and does not fall through; `summercms` is preferred over `summercms.go`; a sibling whose go.mod declares another module is skipped; with no siblings the stubbed binary source is used; with nothing the error contains `--framework` and no target remains). `TestNewRefusesTargetInsideFramework`. `TestNewCleansUpOnFailure` (framework flag at a temp dir whose go.mod only declares the framework module, so the starter plugin's go mod tidy fails; assert an error, no `sm-demo-app`, and a sibling file created before the run untouched). `TestNewRequiresValidAppKey` (empty and short keys rejected before anything is written). `TestNewEnvFileMode` (0600). `TestNewAgentsSymlink` (`os.Readlink` gives `CLAUDE.md`). `TestNewGitFirstCommit` (HOME and XDG_CONFIG_HOME at temp dirs, `GIT_CONFIG_NOSYSTEM=1`, `GIT_CONFIG_GLOBAL=/dev/null`: one commit, branch main, author `SummerCMS <summer@localhost>`, `git config user.name` inside the repo still unset afterwards, `git ls-files` lists .env.example, go.work, main.go, plugins.gen.go, AGENTS.md and not .env). `TestNewGitUsesConfiguredIdentity` (GIT_AUTHOR_NAME/EMAIL and GIT_COMMITTER_NAME/EMAIL set: author taken from them). `TestNewGitMissingIsWarning` (PATH holding only a symlink to the real go binary: warning in output, app present, no .git). `TestNewNoGit`. `TestNewNoPluginBuilds` (skip under -short: `App` succeeds, plugins.gen.go has an empty PluginIDs). Keep `TestNewAppSmoke` as the real build with the starter plugin.
|
||||
|
||||
internal/build/build_test.go: in `TestMakeAndAddPluginOnHelloCopy` assert every `use` path in go.work is relative (no line whose first non-blank character is `/`) and `./plugins/demo` is present; add `TestMakePluginRoutesUnchanged` asserting a plain make:plugin routes.go contains `func (p *Plugin) Routes(pact.Router) error { return nil }` and no `net/http` import.
|
||||
|
||||
modules/lagoon/keygen_test.go: `TestGenerateAppKey` (44 characters, StdEncoding decodes to 32 bytes, two calls differ); keep the existing key:generate test.
|
||||
|
||||
cmd/summer/main_test.go: extend helpWants with `"new": {"--framework", "--no-plugin", "--no-git", "name"}`; add `TestNewCommandViaCLI` (t.Chdir into a temp parent holding a `summercms` symlink, run `new demo --no-git` through `bonfire.NewRoot("summer", toolCommands(), &buf)`, assert the app exists, `.env` carries a 44-character `SUMMER_APP__KEY`, output lists the next steps; `new` with no argument and `new Demo` both error).
|
||||
|
||||
Commit as one test commit.</action>
|
||||
<verify>
|
||||
<automated>go vet ./internal/build/ ./cmd/summer/ ./modules/lagoon/ && go test ./internal/build/ -run '^(TestNew|TestMakeAndAddPluginOnHelloCopy$|TestMakePluginRoutesUnchanged$)' -count=1 -v && go test ./modules/lagoon/ -run '^(TestGenerateAppKey|TestEncryptedKeyGeneratePrints32ByteBase64)$' -count=1 -v && go test ./cmd/summer/ -run '^(TestToolCommandNames|TestNewCommandViaCLI)$' -count=1 -v</automated>
|
||||
<fails_when>Non-zero exit; any "--- FAIL"; "--- SKIP" for any TestNew test (this run is not -short); "no tests to run"; or any of "--- PASS: TestNewFrameworkResolution", "--- PASS: TestNewCleansUpOnFailure", "--- PASS: TestNewGitFirstCommit", "--- PASS: TestNewNoPluginBuilds", "--- PASS: TestMakePluginRoutesUnchanged", "--- PASS: TestGenerateAppKey", "--- PASS: TestNewCommandViaCLI" is absent.</fails_when>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- Each test named in this task's action exists (`grep -c 'func TestNew' internal/build/new_test.go` is at least 13).
|
||||
- `go test ./internal/build/ -short -run TestNew -v` skips only TestNewAppSmoke and TestNewNoPluginBuilds.
|
||||
- TestNewGitFirstCommit proves the fallback identity is used for the first commit only and that .env is untracked.
|
||||
- TestNewCleansUpOnFailure proves only the directory created by the run is removed.
|
||||
- `go vet ./...` and `go test ./... -short` pass.
|
||||
</acceptance_criteria>
|
||||
<done>Every generator branch (validation, refusal, resolution order, cleanup, key, file modes, symlink, git paths, no-plugin) and the shared-path fixes have named, passing tests.</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 3: Bootstrap script behaviour matrix, fail-closed phase gate, security evidence and validation sign-off</name>
|
||||
<files>modules/sunrise/script_behavior_test.go, scripts/check-phase14.3.sh, .planning/phases/14.3-one-command-app-scaffolding-summer-new-and-summercms-io-new/14.3-SECURITY-REVIEW.md, .planning/phases/14.3-one-command-app-scaffolding-summer-new-and-summercms-io-new/14.3-VALIDATION.md</files>
|
||||
<read_first>modules/sunrise/new.sh, modules/sunrise/sunrise_test.go (stub PATH harness from plan 02), scripts/check-phase14.3.sh (--generator, --acceptance stages), scripts/check-phase14.2.1.sh (named-PASS detection, --all pattern), .planning/phases/14.3-one-command-app-scaffolding-summer-new-and-summercms-io-new/14.3-SECURITY-REVIEW.md, .planning/phases/14.3-one-command-app-scaffolding-summer-new-and-summercms-io-new/14.3-VALIDATION.md, .planning/phases/14.3-one-command-app-scaffolding-summer-new-and-summercms-io-new/14.3-03-PLAN.md (threats T-14.3-20..22), ../sm-summercmsio-app/terminal_check_test.go</read_first>
|
||||
<action>Finish phase coverage and the gate.
|
||||
|
||||
`modules/sunrise/script_behavior_test.go`, `TestScriptBehavior` table: write `Script()` to a temp file and run it with `/bin/bash` (absolute), a fresh temp working directory, and PATH set to a temp tools directory that contains only a symlink to the real `git` plus stub `go` and `docker` scripts (the plan 02 script uses no other external commands). The `go` stub answers `env GOVERSION` from a test variable, `env GOBIN` and `env GOPATH` from temp paths, and for `-C <dir> install ./cmd/summer` writes a stub `summer` into GOBIN that records its working directory, arguments and whatever it reads from stdin. A helper builds a bare "remote" framework repository with go.mod `module git.golem15.com/golem15/summercms` and tags v0.1.0, v0.2.0, v0.10.0 on successive commits and v1.0.0-rc1 on a later one. Cases: no argument (exit 2, usage, no stub call); `Demo` (exit 2); existing `sm-demo-app` (exit 1, nothing cloned); Go missing (exit 1, stderr names https://go.dev/dl/, nothing cloned); `go1.26.3` (exit 1, hint); `devel` version (warning, success); git missing (exit 1); Docker missing (warning, success); fresh clone (detached at v0.10.0, summer called with `new demo --framework <abs>/summercms` from `<abs>`, stdin not read even though the test pipes data into the script, output contains `cd <abs>/sm-demo-app`); `SUMMERCMS_REF=v0.2.0`; re-run after the remote gains v0.11.0 (moves forward); re-run at the newest tag (no change, success); tracked change in the checkout (exit 1, file content unchanged); checkout on a branch (exit 1); detached on a commit that is not an ancestor of the newest tag (exit 1, HEAD unchanged); remote tag v0.10.0 deleted and recreated on another commit while the local tag exists (exit non-zero, HEAD unchanged); existing directory with another module's go.mod (exit 1); existing directory that is not a git repository (exit 1); GOBIN not on PATH (hint printed) versus on PATH (no hint). Add `TestScriptShellcheck` running `shellcheck -s bash` on new.sh, skipped only when shellcheck is not installed.
|
||||
|
||||
Gate: add to `scripts/check-phase14.3.sh` the `--unit` stage (go vet ./...; go test ./... -count=1 -v saved to a log; `go -C examples/hello test ./... -count=1`; `go run ./cmd/summer docs:build --check`; `go -C ../sm-summercmsio-app/plugins/golem15/summercms test ./... -count=1`; `go -C ../sm-summercmsio-app test ./... -count=1`; `pnpm -C ../sm-summercmsio-app/vue-summercmsio-app generate` and `test`; a required list of named tests that must each print `--- PASS:` in the log: TestNewGolden, TestNewGoldenNoPlugin, TestNewAppSmoke, TestNewNoPluginBuilds, TestNewValidateAppName, TestNewRefusesExistingTarget, TestNewFrameworkResolution, TestNewRefusesTargetInsideFramework, TestNewCleansUpOnFailure, TestNewRequiresValidAppKey, TestNewEnvFileMode, TestNewAgentsSymlink, TestNewGitFirstCommit, TestNewGitUsesConfiguredIdentity, TestNewGitMissingIsWarning, TestNewNoGit, TestMakeAndAddPluginOnHelloCopy, TestMakePluginRoutesUnchanged, TestGenerateAppKey, TestNewCommandViaCLI, TestToolCommandNames, TestScriptEmbedded, TestScriptShape, TestScriptTruncationRunsNothing, TestScriptBehavior, TestScriptShellcheck, TestDocsTree, TestDocsAIOutputsInSync; any `--- SKIP` of a required test or a `no tests to run` line fails the stage) and the forbidden-surface scan (new.sh contains no name with the framework config prefix; `git diff v0.1.12 -- go.mod` adds no require line; `go list -deps ./...` contains neither the stdlib `plugin` package nor yaegi; golden go.mod/go.work contain no absolute path; the framework .gitignore does not ignore any testdata golden). Add `--all`: `--unit`, `--generator`, the gated runs `SUMMERCMS_BOOTSTRAP_CHECK=1 go test ./modules/sunrise/ -run '^TestBootstrapEndToEnd$'` and the site's `TestTerminalCommands` with the local SUMMERCMS_NEW_URL/REPO/REF overrides (each requiring its named PASS), `--acceptance`, and the security check (every T-14.3-01..26 and T-14.3-SC row present, no high row with status open, every high row names a test or gate stage); end with `Phase 14.3 gate passed`.
|
||||
|
||||
Security review: add rows T-14.3-20..22 (plan 03) and T-14.3-23..26 (this plan's register) and fill the test column of every row with the named test or gate stage that now pins it; statuses stay `mitigated` or `accepted`. Validation: replace `<pkg>` with `sunrise`, map each per-plan row to the exact test names and commands above, set frontmatter `status: validated`, `nyquist_compliant: true`, `wave_0_complete: true` only after `--all` passes, and keep the manual-only row for the production run with the commands from the release checklist. Commit the test and gate as code commits and the two planning documents as a separate docs commit. Do not push, tag or deploy.</action>
|
||||
<verify>
|
||||
<automated>go test ./modules/sunrise/ -run '^(TestScriptBehavior|TestScriptShellcheck)$' -count=1 -v && bash scripts/check-phase14.3.sh --all</automated>
|
||||
<fails_when>Non-zero exit; any "--- FAIL"; "--- SKIP: TestScriptShellcheck" while shellcheck is installed; "no tests to run"; a required test without its "--- PASS:" line; an open high threat; or the output lacks the final line "Phase 14.3 gate passed".</fails_when>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- `grep -c 'func TestScriptBehavior' modules/sunrise/script_behavior_test.go` is 1 and the table has at least 18 cases.
|
||||
- `grep -c -- '--all' scripts/check-phase14.3.sh` is at least 1 and `grep -c 'Phase 14.3 gate passed' scripts/check-phase14.3.sh` is 1.
|
||||
- 14.3-SECURITY-REVIEW.md contains T-14.3-01 through T-14.3-26 and T-14.3-SC; every high row is `mitigated` with a named test or stage.
|
||||
- 14.3-VALIDATION.md frontmatter has `status: validated`, `nyquist_compliant: true`, `wave_0_complete: true`, and no `<pkg>` placeholder remains.
|
||||
- `bash scripts/check-phase14.3.sh --all` ends with `Phase 14.3 gate passed`.
|
||||
</acceptance_criteria>
|
||||
<done>The bootstrap script's refusal and selection paths are tested, one fail-closed gate proves the whole phase across summercms.go and the site, and the security review and validation map are signed off.</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
## Post-phase release checklist (user actions, not executed by this plan)
|
||||
|
||||
Per D-19 and D-28 these run after the phase is verified; they are listed here so they are not lost:
|
||||
|
||||
1. Review and push summercms.go `master`.
|
||||
2. Tag the framework: `git tag -a v0.1.13 -m "v0.1.13"` on the commit that holds all Phase 14.3 framework changes, then `git push origin v0.1.13`. One-way: module proxies cache the version, a defect afterwards ships as v0.1.14.
|
||||
3. In `sm-summercmsio-app`: bump `require git.golem15.com/golem15/summercms` to `v0.1.13` in `go.mod` and `plugins/golem15/summercms/go.mod`, update the `v0.1.12` references in DEPLOY.md and CLAUDE.md, commit in the plugin submodule and the app, push the submodules then the app (`ssu`).
|
||||
4. `scripts/build.sh` (release) and deploy per `sm-summercmsio-app/DEPLOY.md`.
|
||||
5. Verify in production: `curl -fsSI https://summercms.io/new` shows `200` and `text/plain; charset=utf-8`; `SUMMERCMS_TERMINAL_CHECK=1 go test -run TestTerminalCommands -count=1 -v .` in `sm-summercmsio-app` with no overrides; `bash scripts/check-phase14.3.sh --acceptance-production` in summercms.go (the final D-20 run against https://summercms.io/new).
|
||||
|
||||
<threat_model>
|
||||
## Trust Boundaries
|
||||
|
||||
| Boundary | Description |
|
||||
|----------|-------------|
|
||||
| Tests and gate -> phase completion claim | A skipped, filtered or self-updating test must not count as evidence |
|
||||
| Tests -> host Docker and filesystem | Gate stages start containers and write temp files |
|
||||
|
||||
## STRIDE Threat Register
|
||||
|
||||
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|
||||
|-----------|----------|-----------|----------|-------------|-----------------|
|
||||
| T-14.3-23 | Repudiation | phase gate | high | mitigate | Required named `--- PASS:` lines; any required SKIP or "no tests to run" fails; final marker line |
|
||||
| T-14.3-24 | Tampering | golden files | medium | mitigate | Goldens rewritten only with explicit `-update`, never by the gate; goldens reviewed and committed with the tests |
|
||||
| T-14.3-25 | Information Disclosure | fixed test key in goldens | low | accept | Deterministic non-secret test value (bytes 0x00..0x1f), never used outside tests |
|
||||
| T-14.3-26 | Denial of Service | Docker resources from gate stages | low | mitigate | Unique compose project names, traps with `docker compose down -v`, host-side label cleanup |
|
||||
| T-14.3-SC | Tampering | npm/pip/cargo installs | high | mitigate | No package installs; gate fails when go.mod gains a require line versus v0.1.12 |
|
||||
</threat_model>
|
||||
|
||||
<verification>
|
||||
`bash scripts/check-phase14.3.sh --all` is the authoritative command and must end with `Phase 14.3 gate passed`. Then `go vet ./... && go test ./... -short` once more at the final commit.
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- Roadmap success criterion 6: generator golden tests and real builds of the scaffold in temp directories exist and pass, as the last plan.
|
||||
- Every D-01..D-28 decision maps to a named test, gate stage or the post-phase checklist.
|
||||
- No open high threat; validation signed off; nothing pushed, tagged or deployed.
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
Create `.planning/phases/14.3-one-command-app-scaffolding-summer-new-and-summercms-io-new/14.3-04-SUMMARY.md` when done.
|
||||
</output>
|
||||
</content>
|
||||
</invoke>
|
||||
Reference in New Issue
Block a user