SRD-016 — Quiet startup: suppressible banner and configuration dump¶
| Field | Value |
|---|---|
| Status | Accepted |
| Version | v.1 |
| Date | 2026-06-16 |
| Owner | Ruslan Gabitov |
| Implements | ADR-002 v.2 §4.4.1 Extension Architecture |
This SRD lands ADR-002 v.2 §4.4.1: two
no-argument thresher.New options that each suppress one block of the startup report —
WithoutBanner() drops the ASCII wordmark / version / last-commit block, and
WithoutStartupConfig() drops the per-extension configuration dump. Both default to on
(visible by default — opt-out, never opt-in); the closing separator prints only when a block
was emitted, so suppressing both yields a fully silent startup.
1. Background & motivation¶
1.1 Current state (verified against the code)¶
- The startup report is unconditional.
Thresher.Newcallst.logStartupConfig()after wiring (pkg/thresher/thresher.go:160), with no way to silence it.logStartupConfig(pkg/thresher/thresher.go:168) emits, in order: the banner lines (strings.SplitSeq(banner, "\n")),"GoBPM — BPMN v2 process engine",version:,last commit:,thresher id:,configuration:, onemodule(...)line per extension, thenseparator. - ADR-002 v.1 forbade an opt-out; v.2 §4.4.1 reverses that, splitting the report into a banner block and a configuration dump, each independently opt-out. This SRD implements that decision.
- The option machinery already exists.
Option = func(*thresherConfig) error(pkg/thresher/options.go:42);Newapplies each option and wraps the first error (thresher.go:123-131).thresherConfig(options.go:27) holds the nine extensions;defaultConfig()(options.go:201) wires their defaults. The existingWithXxxoptions are value-setters that reject a nil argument (options.go:45-177). - The banner / separator constants live in
pkg/thresher/buildinfo.go:13(banner) and:21(separator);readBuildInfo()(:36) resolves version + last commit. - The current behaviour is covered by
TestStartupConfigLog(pkg/thresher/options_test.go:124), which captures records via acapHandlerand asserts the human-readable lines are present at INFO.
1.2 Problem¶
The banner is decorative ASCII printed one INFO record per line — pure noise in a structured log aggregator — and the configuration dump is verbose for embedders that construct many short-lived engines or already capture the wiring elsewhere. The Logger-level lever ADR-002 v.1 pointed at cannot drop this noise without also dropping legitimate application INFO. A per-block opt-out removes the noise precisely while keeping the default loud.
2. Decision¶
Add two no-argument options to pkg/thresher:
WithoutBanner()— suppress the banner block: the ASCII wordmark, the"GoBPM — BPMN v2 process engine"tagline,version:, andlast commit:.WithoutStartupConfig()— suppress the configuration block:thresher id:, theconfiguration:header, and the one-per-extensionmodule(...)lines.
Each option sets an unexported bool on thresherConfig. logStartupConfig consults the two
flags and prints each block only when its flag is unset. The separator closes the report iff at
least one block printed; with both suppressed, logStartupConfig emits nothing.
The two flags are engine-construction config, not part of renv.EngineRuntime — they are not
exposed through the runtime interface and do not touch any executor or waiter.
3. Functional requirements¶
| # | Requirement | Acceptance |
|---|---|---|
| FR-1 | WithoutBanner() Option exists; applied, it suppresses the banner block (wordmark, tagline, version:, last commit:). |
With the option, captured records contain none of those lines; the configuration block and separator are still present. |
| FR-2 | WithoutStartupConfig() Option exists; applied, it suppresses the configuration block (thresher id:, configuration:, per-extension lines). |
With the option, captured records contain none of those lines; the banner block and separator are still present. |
| FR-3 | The separator prints iff at least one block was emitted. |
Default → separator present. Both options → zero records emitted (no separator). Either one → separator present. |
| FR-4 | With neither option, startup output is byte-for-byte the pre-change behaviour. | TestStartupConfigLog (unchanged assertions) stays green. |
| FR-5 | The options honour the Option contract and compose with the others; order-independent; idempotent. |
Each returns a non-nil Option; applying twice equals applying once; they do not error (no argument to validate). |
4. Non-functional requirements¶
| # | Requirement |
|---|---|
| NFR-1 | Visible-by-default preserved: both blocks emit unless explicitly suppressed (opt-out only). |
| NFR-2 | No change to renv.EngineRuntime; the flags are unexported thresherConfig fields. TestConfigSatisfiesEngineRuntime stays green. |
| NFR-3 | Diff-coverage ≥ COVER_MIN (95) on every touched function, aiming 100%. |
5. Path analysis (alternatives)¶
- (A) Two no-argument options — chosen.
WithoutBanner()/WithoutStartupConfig(). Matches the requested granularity (suppress each block independently) and the existingWithXxxfamily shape. No argument means nothing to validate — the public-API validation rule is satisfied vacuously. - (B) One
WithQuietStartup()that suppresses everything. Rejected: less granular; cannot drop only the decorative banner while keeping the wiring dump (a common want). The requirement is explicitly two flags. - (C) A configurable
WithStartupLog(level/mask)enum or variadic. Rejected: over-engineered for two booleans; invents a vocabulary the engine has no other use for. - (D) Status quo — silence via the user's Logger level (ADR-002 v.1). Rejected: this was v.1's decision and is the very problem — it cannot separate banner noise from application INFO.
6. API¶
// WithoutBanner suppresses the startup banner block (the ASCII wordmark, the
// product tagline, the version and the last-commit line). The configuration
// dump still prints unless WithoutStartupConfig is also given.
func WithoutBanner() Option
// WithoutStartupConfig suppresses the startup configuration dump (the thresher
// id, the "configuration:" header and the per-extension lines). The banner
// still prints unless WithoutBanner is also given.
func WithoutStartupConfig() Option
// A fully silent startup:
eng, _ := thresher.New("worker-7",
thresher.WithoutBanner(),
thresher.WithoutStartupConfig(),
)
// Keep the build identity, drop the wiring dump:
eng, _ := thresher.New("worker-7", thresher.WithoutStartupConfig())
7. Test plan¶
Captured via the existing capHandler (options_test.go:113).
| # | Test | Asserts |
|---|---|---|
| T-1 | TestStartupConfigLog (existing, unchanged) |
Default: banner + config + separator all present at INFO (FR-4). |
| T-2 | TestWithoutBanner |
WithoutBanner(): no wordmark/version:/last commit: lines; configuration: + per-extension lines + separator present (FR-1, FR-3). |
| T-3 | TestWithoutStartupConfig |
WithoutStartupConfig(): no thresher id:/configuration:/per-extension lines; banner + separator present (FR-2, FR-3). |
| T-4 | TestQuietStartup |
Both options: zero records captured (FR-3). |
| T-5 | TestWithoutBannerIdempotent |
Applying WithoutBanner() twice == once (FR-5). |
8. Cross-document consistency¶
- Implements ADR-002 v.2 §4.4.1 (upward reference, version-pinned). No other doc references the startup-report shape.
README.mdQuick-start area gains a short note on the two options (a reference-doc-style update, landed in this change-set).
9. Definition of Done¶
WithoutBanner()/WithoutStartupConfig()implemented;logStartupConfigbranches on the two flags; separator gated on "a block printed".- T-1…T-5 green; touched functions at ≥95% diff-coverage (aim 100%).
README.mddocuments the two options.make cigreen end to end (tidy → lint → build → race tests → diff-coverage → govulncheck)./check-srdPASS; §10 implementation summary filled; status flipped to Accepted.- RU twin added.
10. Implementation summary¶
Landed on feat/thresher-quiet-startup in four commits; make ci green
end to end. /check-srd audit: PASS — FR-1…FR-5 and NFR-1…NFR-3 all wired
and tested.
10.1 Commits¶
| Commit | Scope |
|---|---|
6077ab8 |
ADR-002 v.2 §4.4.1 — the per-block opt-out decision (EN + RU) |
4341778 |
SRD-016 (this doc) + RU twin |
5cea5cb |
M1 — WithoutBanner() / WithoutStartupConfig() + thresherConfig flags + logStartupConfig two guarded blocks + gated separator + T-2…T-5 |
74d3ba4 |
M2 — README "Startup logging" opt-out note |
10.2 Key files¶
pkg/thresher/options.go—suppressBanner/suppressStartupConfigfields;WithoutBanner/WithoutStartupConfigoptions.pkg/thresher/thresher.go—logStartupConfigrestructured into a banner block and a config block,readBuildInfonow inside the banner branch, separator gated onprinted.pkg/thresher/options_test.go—captureStartuphelper + T-2…T-5.README.md— startup-logging opt-out note.
10.3 V-results¶
make cigreen (CI_EXIT=0): tidy → lint (0 issues, fieldalignment incl.) → build → race tests (50 pass) → diff-coverage → govulncheck.- Touched functions at 100%:
logStartupConfig,WithoutBanner,WithoutStartupConfig(≥95COVER_MIN). examples/basic-processsmoke: default startup renders banner + config + separator, process runs, exit 0 (FR-4 — default path unchanged).
Document History¶
| Version | Date | Author | Change |
|---|---|---|---|
| v.1 | 2026-06-16 | Ruslan Gabitov | Two no-argument thresher.New options — WithoutBanner() / WithoutStartupConfig() — landing the per-block startup-report opt-out decided in ADR-002 v.2 §4.4.1. Separator gated on "a block printed"; both suppressed ⇒ silent startup. Landed on feat/thresher-quiet-startup (M1 5cea5cb + M2 74d3ba4); /check-srd PASS; status Draft → Accepted. |