|
|
|
|
@@ -0,0 +1,247 @@
|
|
|
|
|
---
|
|
|
|
|
phase: quick-261008-kdf
|
|
|
|
|
plan: 01
|
|
|
|
|
type: execute
|
|
|
|
|
wave: 1
|
|
|
|
|
depends_on: []
|
|
|
|
|
files_modified:
|
|
|
|
|
- modules/bonfire/command.go
|
|
|
|
|
- modules/bonfire/root.go
|
|
|
|
|
- modules/bonfire/command_test.go
|
|
|
|
|
- modules/bonfire/README.md
|
|
|
|
|
- docs/console/writing-commands.md
|
|
|
|
|
- cmd/summer/runtime.go
|
|
|
|
|
- cmd/summer/runtime_test.go
|
|
|
|
|
- cmd/summer/main.go
|
|
|
|
|
- cmd/summer/main_test.go
|
|
|
|
|
- docs/console/introduction.md
|
|
|
|
|
- docs/console/setup-and-maintenance.md
|
|
|
|
|
- README.md
|
|
|
|
|
autonomous: true
|
|
|
|
|
requirements: [QUICK-261008-kdf]
|
|
|
|
|
|
|
|
|
|
estimate:
|
|
|
|
|
tokens: 70000
|
|
|
|
|
raw_tokens: 70000
|
|
|
|
|
tasks: 3
|
|
|
|
|
confidence: low
|
|
|
|
|
|
|
|
|
|
must_haves:
|
|
|
|
|
truths:
|
|
|
|
|
- "From an application directory, `summer serve --addr 127.0.0.1:8123` runs `bin/<app> serve --addr 127.0.0.1:8123`; summer no longer fails with `unknown flag: --addr`."
|
|
|
|
|
- "`summer serve --help` and `summer serve -h` print the app binary's own serve help (it lists `--addr`), not summer's cobra help."
|
|
|
|
|
- "The old workaround `summer serve -- --addr :8123` still runs `bin/<app> serve --addr :8123`: one leading `--` is dropped."
|
|
|
|
|
- "`summer migrate:rollback --plugin acme.blog`, `summer queue:work --queue a --queue b` and `summer schedule:run --once` reach the binary as given, and so does any flag the binary adds later."
|
|
|
|
|
- "`summer admin:create --email you@example.com --superuser`, `summer admin:reset-password <identifier>`, `summer route:list` and `summer key:generate` delegate to the app binary, and the hidden password prompt still works because the child inherits summer's stdin."
|
|
|
|
|
- "`summer --help` marks every delegated command with `(flags pass to the app binary)`, and the cobra help command for a delegate prints the long text with its common flags."
|
|
|
|
|
- "When summer gets SIGINT or SIGTERM while a delegated command runs, it sends the same signal to the app binary and waits for it to exit, so stopping `summer serve` frees the port. A context cancel sends SIGTERM and kills the child only after a grace period."
|
|
|
|
|
- "bonfire commands without `Passthrough` parse flags exactly as before (an unknown flag is still rejected). `Passthrough` and `Long` are additive fields."
|
|
|
|
|
artifacts:
|
|
|
|
|
- path: modules/bonfire/command.go
|
|
|
|
|
provides: "Command.Passthrough and Command.Long fields"
|
|
|
|
|
- path: modules/bonfire/root.go
|
|
|
|
|
provides: "wrap() maps Passthrough to cobra DisableFlagParsing + ArbitraryArgs and Long to cobra Long"
|
|
|
|
|
- path: cmd/summer/runtime.go
|
|
|
|
|
provides: "delegateCommands() table, delegateCommand passthrough builder, appRunner seam, newAppCmd (Cancel/WaitDelay), runChild (signal forwarding)"
|
|
|
|
|
- path: cmd/summer/runtime_test.go
|
|
|
|
|
provides: "delegate argv tests via the appRunner seam, help-text tests, helper-process signal-forwarding tests"
|
|
|
|
|
key_links:
|
|
|
|
|
- from: modules/bonfire/root.go wrap()
|
|
|
|
|
to: cobra.Command.DisableFlagParsing
|
|
|
|
|
via: "Command.Passthrough"
|
|
|
|
|
- from: cmd/summer/runtime.go delegateCommand Run
|
|
|
|
|
to: runAppBinary
|
|
|
|
|
via: "appRunner package var (test seam)"
|
|
|
|
|
- from: cmd/summer/runtime.go runAppBinary
|
|
|
|
|
to: runChild
|
|
|
|
|
via: "signal.Notify channel for os.Interrupt and syscall.SIGTERM"
|
|
|
|
|
- from: cmd/summer/main.go toolCommands
|
|
|
|
|
to: delegateCommands()
|
|
|
|
|
via: "append"
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
<objective>
|
|
|
|
|
Make summer's delegated runtime commands pass every flag through to the app binary, add the admin, route and key commands as delegates, and forward SIGINT/SIGTERM to the app binary that summer starts.
|
|
|
|
|
|
|
|
|
|
Purpose: `summer serve --addr :8123` fails with "unknown flag: --addr" today, because `delegateCommand` in cmd/summer/runtime.go builds a bonfire.Command without flags and cobra rejects the flag before it reaches `runAppBinary`. `summer serve --help` shows only `-h`. Also, `kill <summer pid>` leaves the app binary running and holding the port (finding WR-08 of the 14.3 code review).
|
|
|
|
|
|
|
|
|
|
Output: an additive `bonfire.Command.Passthrough` / `bonfire.Command.Long` API, passthrough delegates for serve, migrate, migrate:rollback, migrate:status, queue:work, queue:clear, schedule:run, route:list, key:generate, admin:create and admin:reset-password, signal forwarding in `runAppBinary`, tests, and updated READMEs and console docs.
|
|
|
|
|
</objective>
|
|
|
|
|
|
|
|
|
|
<execution_context>
|
|
|
|
|
@~/.claude/gsd-core/workflows/execute-plan.md
|
|
|
|
|
@~/.claude/gsd-core/templates/summary.md
|
|
|
|
|
</execution_context>
|
|
|
|
|
|
|
|
|
|
<context>
|
|
|
|
|
@.planning/STATE.md
|
|
|
|
|
@CLAUDE.md
|
|
|
|
|
@modules/bonfire/command.go
|
|
|
|
|
@modules/bonfire/root.go
|
|
|
|
|
@modules/bonfire/call.go
|
|
|
|
|
@modules/bonfire/README.md
|
|
|
|
|
@cmd/summer/runtime.go
|
|
|
|
|
@cmd/summer/main.go
|
|
|
|
|
@cmd/summer/main_test.go
|
|
|
|
|
@docs/console/introduction.md
|
|
|
|
|
|
|
|
|
|
Facts gathered during planning (no need to re-derive):
|
|
|
|
|
- cobra is v1.10.2. When `DisableFlagParsing` is true, `ParseFlags` returns early, so the help flag stays false and never returns `flag.ErrHelp`. `execute` then passes the raw args (`argWoFlags = a`) to `ValidateArgs` and `RunE`, and `ValidateRequiredFlags` is skipped. As a result `--help` and `-h` reach Run. cobra's `help <cmd>` command still renders `Long` (or `Short`) plus the usage.
|
|
|
|
|
- The app binary's real commands: `serve` with `--addr` (default `:8080`) in modules/surf/serve.go; `migrate`, `migrate:rollback --plugin`, `migrate:status`, `deferred:purge` and `key:generate` in modules/lagoon/commands.go and keygen.go; `queue:work --queue` (repeatable), `schedule:run --once` and `queue:clear [queue]` in modules/conga/commands.go; `route:list` in modules/surf/routelist_command.go; `admin:create --email --login --role --superuser --password(deprecated)` and `admin:reset-password <identifier>` in modules/cabana/commands.go. The generated app main (internal/build/build.go) always registers all of them. There is no `auth:login` command, so no delegate is added for it.
|
|
|
|
|
- serve, queue:work and the scheduler shut down on `signal.NotifyContext(os.Interrupt, SIGTERM)` with a 10-second timeout. A second SIGINT during shutdown is swallowed, not fatal, because Notify stays registered until `stop()`.
|
|
|
|
|
- summer's `main` calls `root.Execute()` with no context, so `cmd.Context()` is Background and `exec.CommandContext` never cancels today.
|
|
|
|
|
- `runSummer(t, args...)` in cmd/summer/docs_test.go runs the summer root against a buffer. Reuse it.
|
|
|
|
|
- The docs checker (`internal/docsite/check_commands.go`) validates the command word after `summer` / `./bin/<app>` in shell fences AND inline code spans. `help` is not a summer command, so never write `summer help <cmd>` in a code span or shell fence; describe it in prose. New delegate names become valid tool commands automatically, because `docsCommands()` reads `toolCommands()`.
|
|
|
|
|
- No test in cmd/summer or modules/bonfire uses `t.Parallel`, so a package-level test seam var is safe.
|
|
|
|
|
- examples/hello is an app directory (`summer.yaml`, `binary: hello`, `bin/hello` present), so it can serve as an end-to-end target.
|
|
|
|
|
- `summer dev` (internal/dev/watch.go `startBin`) starts `bin/<app>` with NO subcommand and does not use `runAppBinary`. It is out of scope here: report it in the SUMMARY as a follow-up and do not change internal/dev. `docs:serve` runs in-process and already uses `signal.NotifyContext`.
|
|
|
|
|
|
|
|
|
|
Concurrent work (keep out of these files): 261008-k7s edits modules/sunrise/* and docs/setup/installation.md, so do NOT edit docs/setup/installation.md even though its line 186 mentions `summer serve`. 261008-kdh runs AFTER this plan and edits modules/surf/serve.go, modules/cabana and internal/build. This plan edits only the `toolCommands` list region of cmd/summer/main.go, not `newCommand`.
|
|
|
|
|
</context>
|
|
|
|
|
|
|
|
|
|
<tasks>
|
|
|
|
|
|
|
|
|
|
<task type="tracer">
|
|
|
|
|
<name>Task 1: Tracer: bonfire Passthrough/Long, and `summer serve --addr :8123` reaches the app runner</name>
|
|
|
|
|
<files>modules/bonfire/command.go, modules/bonfire/root.go, modules/bonfire/command_test.go, modules/bonfire/README.md, docs/console/writing-commands.md, cmd/summer/runtime.go, cmd/summer/runtime_test.go</files>
|
|
|
|
|
<behavior>
|
|
|
|
|
- bonfire: a `Command{Name: "serve", Passthrough: true}` run through `NewRootIO` with args `serve --addr :8123 -p --help -h -- x` calls Run with `Input.Args()` equal to `--addr :8123 -p --help -h -- x` in order, returns nil, and prints no cobra usage or help.
|
|
|
|
|
- bonfire: the same passthrough command called through `bonfire.Call(ctx, cmds, "serve", []string{"--addr", ":8123", "--help"}, &buf)` receives exactly those three args.
|
|
|
|
|
- bonfire: a passthrough command that declares a required `Arg` accepts zero args (passthrough uses arbitrary args, and the declared Args only shape the usage string).
|
|
|
|
|
- bonfire regression: a command WITHOUT Passthrough (`Name: "migrate"`) given `--addr :8123` still fails with an error containing `unknown flag: --addr`.
|
|
|
|
|
- bonfire: `help serve` on a command with `Long: "Long text"` prints "Long text". Without Long it prints the Description, as before.
|
|
|
|
|
- summer: with `appRunner` replaced by a capturing func, `runSummer(t, "serve", "--addr", ":8123")` captures argv `serve --addr :8123`. `serve --help` gives `serve --help`, `serve -h` gives `serve -h`, `serve --addr=:8123` gives `serve --addr=:8123`, and `serve -- --addr :8123` gives `serve --addr :8123` (leading `--` dropped).
|
|
|
|
|
</behavior>
|
|
|
|
|
<action>
|
|
|
|
|
bonfire (additive only, because many apps use this core module; existing fields, defaults and behaviour do not change):
|
|
|
|
|
- In modules/bonfire/command.go, add two fields to `Command` after `Description`. `Long string`: optional extended help that cobra shows for `help <command>`; when empty, cobra falls back to Description. `Passthrough bool`: when true, cobra does not parse flags, so every argument after the command name, including `--help`, `-h` and `--`, reaches Run unchanged through `Input.Args()`. Its doc comment also says that declared Flags are registered for the usage text only and never parsed (Input.Flag returns only their defaults), and that declared Args shape the usage string but are not count-checked, so `Input.Argument` indexes the raw args. The field is meant for wrapper commands that forward to another program.
|
|
|
|
|
- In modules/bonfire/root.go `wrap()`, set `Long: c.Long` on the cobra command. When `c.Passthrough` is true, set `DisableFlagParsing = true` and use `cobra.ArbitraryArgs` instead of `positionalArgs(c.Args)`. Leave the flag registration loop unchanged.
|
|
|
|
|
- Add the behaviour tests above to modules/bonfire/command_test.go as plain `func Test...(t *testing.T)` with `NewRootIO`, `bytes.Buffer` streams and a closure that captures `in.Args()`.
|
|
|
|
|
- In modules/bonfire/README.md (same commit, per project rule), add a Features bullet saying that passthrough commands (`bonfire.Command.Passthrough`) skip flag parsing so every argument, including `--help`, reaches `bonfire.Input.Args` unchanged, which suits commands that forward to another program, and that `bonfire.Command.Long` adds extended help text. Extend the `bonfire.Command` row of the API reference table to mention `bonfire.Command.Long` and `bonfire.Command.Passthrough`. The Dependencies section does not change (no new imports).
|
|
|
|
|
- In docs/console/writing-commands.md, add to the "Defining a command" bullet list one bullet on `bonfire.Command.Long` and `bonfire.Command.Passthrough`, with the same meaning and no consuming-application names.
|
|
|
|
|
- Commit 1: `feat(bonfire): add Passthrough and Long command fields` (bonfire files + writing-commands.md only).
|
|
|
|
|
|
|
|
|
|
summer (the first consumer):
|
|
|
|
|
- In cmd/summer/runtime.go, add the package-level test seam `var appRunner = runAppBinary` with a one-line comment that tests replace it. Rewrite `delegateCommand(name, description string)` so it returns a Command with `Passthrough: true` and a Run that takes `in.Args()`, drops exactly one leading `--` (this keeps the documented `summer serve -- --addr` workaround working, because otherwise the app binary would receive `--` and treat `--addr` as positional), prepends `name`, and calls `appRunner(ctx, argv, out)`. Leave delegateRollbackCommand, delegateQueueWorkCommand and delegateScheduleRunCommand alone in this task; Task 2 replaces them.
|
|
|
|
|
- Create cmd/summer/runtime_test.go with `TestDelegatedServePassesFlags`, a table over the summer cases in the behavior block. It swaps `appRunner` for a capturing func, restores it with `t.Cleanup`, and calls `runSummer`.
|
|
|
|
|
- Commit 2: `fix(summer): pass delegated command flags to the app binary` (runtime.go + runtime_test.go). Task 2 adds the docs/console/introduction.md update for this behaviour. If the reviewer insists that docs ship in the same commit, move the introduction.md paragraph from Task 2 into this commit.
|
|
|
|
|
- Commit messages carry no Co-Authored-By or other co-author trailer (user rule).
|
|
|
|
|
</action>
|
|
|
|
|
<verify>
|
|
|
|
|
<automated>cd /media/nvme/dev/golem15/summercms.io/summercms/summercms.go && go vet ./modules/bonfire/... ./cmd/summer/... && go test ./modules/bonfire/... -short && go test ./cmd/summer -short -run 'TestDelegatedServePassesFlags|TestToolCommandNames' && cd examples/hello && go run ../../cmd/summer serve --help 2>&1 | grep -e '--addr'</automated>
|
|
|
|
|
</verify>
|
|
|
|
|
<done>bonfire passthrough and Long behaviour is tested and documented. `summer serve --help` run in examples/hello prints the app binary's serve help, including `--addr`. `summer serve --addr :8123` gives the runner argv `serve --addr :8123`. Two commits exist, and non-passthrough commands behave as before.</done>
|
|
|
|
|
</task>
|
|
|
|
|
|
|
|
|
|
<task type="auto" tdd="true">
|
|
|
|
|
<name>Task 2: Every delegate passes flags through, add admin/route/key delegates, help text and docs</name>
|
|
|
|
|
<files>cmd/summer/runtime.go, cmd/summer/runtime_test.go, cmd/summer/main.go, cmd/summer/main_test.go, docs/console/introduction.md, docs/console/setup-and-maintenance.md, README.md</files>
|
|
|
|
|
<behavior>
|
|
|
|
|
- With `appRunner` captured, each delegate passes its args through unchanged: `migrate:rollback --plugin acme.blog`, `queue:work --queue a --queue b`, `schedule:run --once`, `queue:clear emails`, `migrate`, `migrate:status --help`, `route:list`, `key:generate`, `admin:create --email you@example.com --superuser` and `admin:reset-password admin@example.com` each give argv equal to the name followed by the args.
|
|
|
|
|
- For every delegate name, the cobra help command (`runSummer(t, "help", name)`) prints text that contains "passed to the app binary". It also contains the flag hint where one exists: migrate:rollback `--plugin`, queue:work `--queue`, schedule:run `--once`, serve `--addr`, admin:create `--email` and `--superuser`.
|
|
|
|
|
- `runSummer(t, "--help")` lists the delegates with "(flags pass to the app binary)".
|
|
|
|
|
- TestToolCommandNames still passes. It no longer checks `schedule:run --help` locally (that now goes to the app binary), and it expects route:list, key:generate, admin:create and admin:reset-password in the tool command list.
|
|
|
|
|
</behavior>
|
|
|
|
|
<action>
|
|
|
|
|
- In cmd/summer/runtime.go, add `delegateCommands() []bonfire.Command`, built from one table of (name, base description, common-flags hint) rows. It is the single source of every delegate:
|
|
|
|
|
migrate | Run plugin migrations in the app binary | (none);
|
|
|
|
|
migrate:rollback | Roll back the last migration of a plugin in the app binary | --plugin <id>;
|
|
|
|
|
migrate:status | Show per-plugin migration history in the app binary | (none);
|
|
|
|
|
serve | Run the app HTTP server | --addr <host:port> (default :8080);
|
|
|
|
|
queue:work | Run background job workers in the app binary | --queue <name> (repeatable);
|
|
|
|
|
queue:clear | Clear pending queued jobs in the app binary | [queue] argument (default "default");
|
|
|
|
|
schedule:run | Run the scheduler in the app binary (or once for system cron) | --once;
|
|
|
|
|
route:list | List the app's routes from the app binary | (none);
|
|
|
|
|
key:generate | Print a fresh application key from the app binary | (none);
|
|
|
|
|
admin:create | Create a backend administrator in the app binary | --email <email> (required), --login, --role <code>, --superuser; the password is prompted or read from stdin;
|
|
|
|
|
admin:reset-password | Reset a backend administrator's password in the app binary | <identifier> (login or email); the password is prompted or read from stdin.
|
|
|
|
|
Give `delegateCommand` a third parameter for the hint. It sets Description to the base text plus " (flags pass to the app binary)". It sets Long to the base text with a final period, then a blank line and: "summer runs bin/<binary> <name> in the application directory, building the binary first when it is missing. Every flag and argument after <name>, including --help, is passed to the app binary unchanged." Then, only when the hint is non-empty, "Common flags: <hint>." Then "Run summer <name> --help inside an application to list all of them." Delete delegateRollbackCommand, delegateQueueWorkCommand and delegateScheduleRunCommand, since passthrough forwards `--plugin`, `--queue` and `--once` verbatim and the app binary defines them. Remove the `strconv` import if it becomes unused. Per the coordinator's scope addition, do NOT add auth:login (the app binary has none) or deferred:purge (not requested); list both as notes in the SUMMARY.
|
|
|
|
|
- In cmd/summer/main.go `toolCommands`, remove the seven inline delegate entries and append `delegateCommands()...` to the returned slice. cobra sorts the help listing, so order does not matter. Touch only this region of main.go.
|
|
|
|
|
- In cmd/summer/main_test.go `TestToolCommandNames`, add route:list, key:generate, admin:create and admin:reset-password to the expected-names list, and remove the `"schedule:run": {"--once"}` entry from `helpWants`. The new help test in runtime_test.go covers `--once`.
|
|
|
|
|
- In cmd/summer/runtime_test.go, add `TestDelegateCommandsPassArgs` (table over the behavior cases) and `TestDelegateCommandsHelp` (help text plus the root list marker), using the capturing `appRunner` swap and `runSummer`.
|
|
|
|
|
- In docs/console/introduction.md:
|
|
|
|
|
(a) In "Getting help", add that a delegated command answers `--help` with the app binary's own help, so you run it inside an application. Do not write `summer help <cmd>` in a code span or fence; the docs checker rejects `help` as a command.
|
|
|
|
|
(b) In "Commands that summer delegates", add table rows for `summer route:list`, `summer key:generate`, `summer admin:create` and `summer admin:reset-password`. Add a paragraph saying summer parses no flags of these commands: every flag and argument, including `--help`, goes to the binary unchanged. Follow it with a sh fence showing `summer serve --addr 127.0.0.1:8123`, `summer serve --help`, `summer migrate:rollback --plugin acme.blog` and `summer admin:create --email admin@example.com --superuser`. Also mention that one leading `--` is ignored, so `summer serve -- --addr 127.0.0.1:8123` behaves the same.
|
|
|
|
|
(c) Rewrite the final sentence of the paragraph at line 43 so that "every other runtime command, such as `deferred:purge` or the commands your plugins add, is run on the binary directly", and drop route:list, key:generate and admin:create from that list.
|
|
|
|
|
- In docs/console/setup-and-maintenance.md, after the paragraph about `--addr` (around line 66), add one sentence: from the application directory, `summer serve --addr 127.0.0.1:8123` passes the flag through the built binary. After the admin paragraph (around line 80), add one sentence: `summer admin:create --email admin@example.com --superuser` runs the same command, prompt included, from the application directory.
|
|
|
|
|
- In the root README.md (line 76), replace the sentence listing delegates. The new sentence says that `summer migrate`, `summer migrate:status`, `summer migrate:rollback`, `summer serve`, `summer queue:work`, `summer queue:clear`, `summer schedule:run`, `summer route:list`, `summer key:generate`, `summer admin:create` and `summer admin:reset-password`, run from the application directory, delegate to the built binary in `bin/` and pass their flags to it unchanged.
|
|
|
|
|
- Use only neutral names (acme, admin@example.com) in docs and no consuming-application names.
|
|
|
|
|
- Commit: `fix(summer): delegate admin, route and key commands and pass all delegate flags through` (code, tests and docs together, per project rule; no co-author trailer).
|
|
|
|
|
</action>
|
|
|
|
|
<verify>
|
|
|
|
|
<automated>cd /media/nvme/dev/golem15/summercms.io/summercms/summercms.go && go vet ./cmd/summer/... && go test ./cmd/summer -short -run 'TestDelegate|TestToolCommandNames|TestDocsTree|TestDocsCommandNames' && go run ./cmd/summer docs:build --check</automated>
|
|
|
|
|
</verify>
|
|
|
|
|
<done>All eleven delegates are passthrough, generated from one table. The four new delegates exist. The help output and the `summer --help` list say flags pass to the app binary. The console docs, the setup-and-maintenance page and the root README describe the behaviour. The docs checker passes, and docs/setup/installation.md is untouched.</done>
|
|
|
|
|
</task>
|
|
|
|
|
|
|
|
|
|
<task type="auto" tdd="true">
|
|
|
|
|
<name>Task 3: Forward SIGINT/SIGTERM from summer to the app binary (WR-08)</name>
|
|
|
|
|
<files>cmd/summer/runtime.go, cmd/summer/runtime_test.go, docs/console/introduction.md</files>
|
|
|
|
|
<behavior>
|
|
|
|
|
- A helper child (this test binary re-executed with `-test.run=^TestSignalHelperProcess$` and env `SUMMER_SIGNAL_HELPER=1`) registers `signal.NotifyContext(os.Interrupt, syscall.SIGTERM)`, prints `ready`, and on the signal prints `graceful shutdown` and exits 0. It exits 3 after 20s if no signal arrives. When the env var is unset, the helper test just returns.
|
|
|
|
|
- `runChild(cmd, sigs)` with a test-owned `chan os.Signal`: after `ready` appears, sending `os.Interrupt` into the channel makes the child print `graceful shutdown`, and runChild returns nil within 10s. The same holds for `syscall.SIGTERM` (table-driven over both).
|
|
|
|
|
- `newAppCmd(ctx, ...)` pointed at the helper: cancelling ctx after `ready` makes the child print `graceful shutdown` (it got SIGTERM, not SIGKILL), and the returned error satisfies `errors.Is(err, context.Canceled)` (exec reports the context error after a custom Cancel).
|
|
|
|
|
- All signal tests skip when `runtime.GOOS == "windows"`. They are fast (under 1s each) and run under `-short`.
|
|
|
|
|
</behavior>
|
|
|
|
|
<action>
|
|
|
|
|
- In cmd/summer/runtime.go, split process start-up out of `runAppBinary`, which keeps its app-dir, manifest, auto-build and recursion checks unchanged:
|
|
|
|
|
(1) `newAppCmd(ctx context.Context, bin, dir string, argv []string, out io.Writer) *exec.Cmd` builds `exec.CommandContext` with Dir, Stdout and Stderr set to out, `Stdin = os.Stdin` (keep this: admin:create's hidden password prompt needs the inherited terminal), and `Env = os.Environ()`. It sets `cmd.Cancel` to send `syscall.SIGTERM` to `cmd.Process` and `cmd.WaitDelay` to a named const `appShutdownGrace = 15 * time.Second`, longer than the 10-second shutdown timeout of serve and the workers, so a context cancel triggers a graceful shutdown before the SIGKILL fallback.
|
|
|
|
|
(2) `runChild(cmd *exec.Cmd, sigs <-chan os.Signal) error` starts the cmd, waits in a goroutine, and loops. On each received signal it calls `cmd.Process.Signal(sig)`, ignoring `os.ErrProcessDone` and platform "not supported" errors, and keeps waiting. When Wait returns, it returns Wait's error. It forwards every signal and does not escalate to a kill.
|
|
|
|
|
(3) `runAppBinary` creates `sigs := make(chan os.Signal, 2)` and calls `signal.Notify(sigs, os.Interrupt, syscall.SIGTERM)` only after the build and recursion checks, right before `runChild`, with `defer signal.Stop(sigs)`. It wraps errors as today (`summer: <argv>: %w`).
|
|
|
|
|
Add a doc comment on runChild explaining two points. First, Ctrl+C in a terminal delivers SIGINT to both summer and the child (same foreground process group), so the child may see SIGINT twice; the app's NotifyContext-based shutdown tolerates that. Second, the child is deliberately NOT given its own process group (`Setpgid`), because a background process group reading the terminal gets SIGTTIN and would break the password prompts of admin:create and admin:reset-password.
|
|
|
|
|
- In cmd/summer/runtime_test.go, add `TestSignalHelperProcess` (the helper) and `TestRunChildForwardsSignals` and `TestNewAppCmdCancelSendsSIGTERM` per the behavior block. Capture child output with a small mutex-guarded buffer type defined in the test file and poll for `ready` with a 5s deadline. Do not send real signals to the test process.
|
|
|
|
|
- In docs/console/introduction.md "Commands that summer delegates", add one sentence: when `summer` receives SIGINT or SIGTERM while the binary runs, it passes the signal on and waits for the binary to shut down, so stopping `summer serve` (Ctrl+C or `kill`) frees its port.
|
|
|
|
|
- Commit: `fix(summer): forward SIGINT and SIGTERM to the app binary` (code, tests and docs; no co-author trailer). In the SUMMARY, record that this resolves WR-08 of 14.3-REVIEW.md, and that scripts/check-phase14.3.sh needs no change because `kill $server_pid` now reaches the app binary through summer. Do not edit the 14.3 phase files.
|
|
|
|
|
</action>
|
|
|
|
|
<verify>
|
|
|
|
|
<automated>cd /media/nvme/dev/golem15/summercms.io/summercms/summercms.go && go vet ./... && go test ./cmd/summer -short -run 'TestRunChildForwardsSignals|TestNewAppCmdCancelSendsSIGTERM|TestDelegate' -count=3 && go test ./... -short && go test ./cmd/summer -run TestDocsTree && go run ./cmd/summer docs:build --check</automated>
|
|
|
|
|
</verify>
|
|
|
|
|
<done>Signals reaching summer are forwarded to the app binary, and summer waits for it. A context cancel triggers a graceful SIGTERM with a 15s SIGKILL fallback. The helper-process tests pass repeatedly (-count=3). The full `go vet ./...`, `go test ./... -short`, TestDocsTree and `docs:build --check` gate is green.</done>
|
|
|
|
|
</task>
|
|
|
|
|
|
|
|
|
|
</tasks>
|
|
|
|
|
|
|
|
|
|
<threat_model>
|
|
|
|
|
## Trust Boundaries
|
|
|
|
|
|
|
|
|
|
| Boundary | Description |
|
|
|
|
|
|----------|-------------|
|
|
|
|
|
| developer shell -> summer -> app binary | summer forwards the developer's own argv, stdin, environment and signals to a child it starts from the app's bin/ directory |
|
|
|
|
|
|
|
|
|
|
## STRIDE Threat Register
|
|
|
|
|
|
|
|
|
|
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|
|
|
|
|
|-----------|----------|-----------|----------|-------------|-----------------|
|
|
|
|
|
| T-kdf-01 | Tampering | delegateCommand argv passthrough | low | mitigate | argv goes to exec.CommandContext as a slice, never through a shell, so no quoting or injection layer exists; only one leading `--` is stripped |
|
|
|
|
|
| T-kdf-02 | Information disclosure | admin:create / admin:reset-password via summer | low | mitigate | the child inherits os.Stdin directly (no capture or buffering in summer), so the hidden prompt and stdin piping stay the recommended paths; the deprecated `--password` flag's process-list exposure is unchanged and already documented in setup-and-maintenance.md |
|
|
|
|
|
| T-kdf-03 | Denial of service | runChild / newAppCmd | low | mitigate | signals are forwarded, not swallowed; a context cancel escalates to SIGKILL after appShutdownGrace (15s), so summer cannot hang forever on a cancelled context; an orphaned child holding the port (WR-08) is the threat being fixed |
|
|
|
|
|
| T-kdf-04 | Elevation of privilege | runAppBinary recursion | low | accept | the existing sameFile check against os.Executable is kept unchanged |
|
|
|
|
|
| T-kdf-SC | Tampering | dependency installs | low | accept | no new dependency: os/signal, syscall, time and io are standard library, and cobra is already pinned at v1.10.2 |
|
|
|
|
|
</threat_model>
|
|
|
|
|
|
|
|
|
|
<verification>
|
|
|
|
|
- `go vet ./...` and `go test ./... -short` are green after each commit.
|
|
|
|
|
- `go test ./cmd/summer -run TestDocsTree` and `go run ./cmd/summer docs:build --check` pass.
|
|
|
|
|
- End-to-end: in examples/hello, `go run ../../cmd/summer serve --help` prints the app binary's serve help, including `--addr`.
|
|
|
|
|
- Optional manual check (needs Postgres, not required for autonomous completion): in an app directory, run `summer serve --addr 127.0.0.1:8123`, then `kill -TERM <summer pid>`. summer exits after the app logs its shutdown, and `ss -ltn | grep 8123` shows nothing.
|
|
|
|
|
- `git diff --stat` across the three tasks touches only the files in files_modified. In particular, docs/setup/installation.md, modules/sunrise, modules/surf, modules/cabana, internal/build and internal/dev are untouched.
|
|
|
|
|
- Do not tag, push or deploy.
|
|
|
|
|
</verification>
|
|
|
|
|
|
|
|
|
|
<success_criteria>
|
|
|
|
|
- `summer serve --addr :8123`, `summer serve --help` and `summer serve -h` work from an app directory, and `summer serve -- --addr :8123` still works.
|
|
|
|
|
- All eleven delegates (migrate, migrate:rollback, migrate:status, serve, queue:work, queue:clear, schedule:run, route:list, key:generate, admin:create, admin:reset-password) pass flags through and say so in the command list and the help command.
|
|
|
|
|
- SIGINT/SIGTERM to summer reach the app binary, and summer waits for it to exit.
|
|
|
|
|
- The bonfire API change is additive (Passthrough, Long) and is documented in modules/bonfire/README.md and docs/console/writing-commands.md in the same commit.
|
|
|
|
|
- Three or four atomic commits, each with code and docs together, none with co-author trailers.
|
|
|
|
|
</success_criteria>
|
|
|
|
|
|
|
|
|
|
<output>
|
|
|
|
|
Create `.planning/quick/261008-kdf-delegated-summer-commands-pass-flags-thr/261008-kdf-SUMMARY.md` when done. Include:
|
|
|
|
|
- The commits and files changed.
|
|
|
|
|
- WR-08 (14.3-REVIEW.md) is resolved by Task 3; whether to update 14.3-REVIEW-DISPOSITION.md is left to the orchestrator.
|
|
|
|
|
- Handoff for 261008-kdh, which runs after this plan: `summer admin:create --email <email> --superuser`, `summer admin:reset-password <identifier>`, `summer route:list`, `summer key:generate` and `summer serve --addr <host:port>` now work from an application directory, so kdh's startup and admin hints can use the `summer ...` forms instead of `./bin/<app> ...`.
|
|
|
|
|
- Not delegated: `auth:login` (no such command in the app binary) and `deferred:purge` (not requested; it stays binary-only, as documented in docs/console/introduction.md).
|
|
|
|
|
- Follow-up observation (not fixed): `summer dev` starts `bin/<app>` with no subcommand (internal/dev/watch.go startBin), so it never runs `serve`. Its child stop runs only on context cancel, and summer never cancels it.
|
|
|
|
|
- File overlap: cmd/summer/main.go (toolCommands region only), docs/console/introduction.md, docs/console/setup-and-maintenance.md and the root README.md may be touched by later quick tasks. docs/setup/installation.md was deliberately left to 261008-k7s.
|
|
|
|
|
</output>
|