Skip to content

refactor(api): extract Run-scoped background goroutine lifecycle into BackgroundComponents - #407

Merged
krisarmstrong merged 1 commit into
mainfrom
harden/stem-composition-root
Jun 8, 2026
Merged

refactor(api): extract Run-scoped background goroutine lifecycle into BackgroundComponents#407
krisarmstrong merged 1 commit into
mainfrom
harden/stem-composition-root

Conversation

@krisarmstrong

Copy link
Copy Markdown
Collaborator

Summary

Introduces internal/api/background.go — a BackgroundComponents holder that is the single ordered Start(ctx)/Stop() seam for Run-scoped long-lived goroutines, beginning with the reflector-stats SSE publisher. Mirrors seed's internal/api/background.go shape (harmonization, not a code copy).

This is the composition-root + DEK item from the stem/niac remediation plan (Phase 4).

What changed

  • background.go: BackgroundComponents owns the publisher goroutine, a derived cancellable context, and a WaitGroup. Stop() cancels and waits for exit (nil-safe + idempotent).
  • sse_publishers.go: startReflectorStatsPublisher → blocking runReflectorStatsPublisher(ctx); the holder owns the goroutine.
  • server.go: Run constructs + starts the holder; Shutdown stops it first — before tearing down the reflector executor the publisher reads, closing a latent use-during-teardown gap (Shutdown previously never waited for the publisher).
  • Construction-scoped cleanup goroutines (rate limiters, CSRF/auth managers) intentionally stay in Shutdown — they outlive Run and must stop even when Run was never called (tests construct servers and call Shutdown directly).

Decisions (ADRs)

  • ADR-0005 — background-component lifecycle holder.
  • ADR-0006 — stem's only at-rest encryption (license activation) is device-fingerprint-keyed and fully decoupled from the JWT signing secret, so seed ADR-0015 DEK separation is N/A by construction. No credential.key is added.

Verification

  • go build ./..., go vet ./... clean
  • golangci-lint run ./internal/api/ — 0 issues
  • full internal/api -race suite — ok (320s)
  • non-api packages — all pass
  • new background_internal_test.go: start/stop, stop-before-start, idempotent stop, ctx-cancel-stops (all -race)

… BackgroundComponents

Introduce internal/api/background.go (BackgroundComponents) as the single
ordered Start/Stop seam for Run-scoped long-lived goroutines, beginning with
the reflector-stats SSE publisher (runReflectorStatsPublisher). Shutdown now
stops the publisher and waits for it to exit before tearing down the reflector
executor it reads, closing a latent use-during-teardown race.

Construction-scoped cleanup goroutines (rate limiters, CSRF/auth managers)
deliberately stay in Shutdown: they outlive Run and must stop even when Run was
never called (the test suite constructs servers and calls Shutdown directly).

ADR-0005 records the lifecycle-holder decision. ADR-0006 records that stem's
only at-rest encryption (license activation) is device-fingerprint-keyed and
fully decoupled from the JWT signing secret, so seed ADR-0015 DEK separation is
N/A by construction.

Verified: go build ./..., go vet ./..., golangci-lint (0 issues), full
internal/api -race suite (ok 320s), non-api tests.
@krisarmstrong
krisarmstrong enabled auto-merge (squash) June 8, 2026 04:11
@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

License Compliance Report

All dependencies pass license compliance checks

Go Dependencies

  • Unknown: 31 package(s)
  • MIT: 26 package(s)
  • BSD-3-Clause: 16 package(s)
  • Apache-2.0: 11 package(s)
  • BSD-2-Clause: 1 package(s)

npm Dependencies

See full report in workflow artifacts

Allowed Licenses: MIT, Apache-2.0, BSD-*, ISC, CC0-1.0, MPL-2.0
Forbidden: GPL, AGPL, SSPL (strong copyleft)

@krisarmstrong
krisarmstrong merged commit da57391 into main Jun 8, 2026
25 checks passed
@krisarmstrong
krisarmstrong deleted the harden/stem-composition-root branch June 8, 2026 04:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant