Skip to content

feat: scoped hot-reload of client + policy config on SIGHUP#34

Merged
JumpTechCode merged 2 commits into
mainfrom
feat/scoped-hot-reload
Jun 17, 2026
Merged

feat: scoped hot-reload of client + policy config on SIGHUP#34
JumpTechCode merged 2 commits into
mainfrom
feat/scoped-hot-reload

Conversation

@JumpTechCode

Copy link
Copy Markdown
Collaborator

The last functional V1 item: reload the client/auth map and policy rules without dropping connections or restarting (design §5). Downstream topology, redaction, and audit changes remain restart-required.

Atomic-swap primitives

  • policy.Engine.Reload swaps the default + rule set behind the engine's lock and clears the wildcard pins computed for the old rules — fail-closed (a wildcard admits nothing) until the next Sync, never an over-permit. Decide now reads the rule set under the read lock; Sync holds the write lock across its computation. compileRules is shared with New.
  • edge.Guard.Reload swaps the allowed origins and client keys behind an RWMutex; origin checking and authentication read a consistent snapshot, so a request in flight finishes against one view.

Wiring

  • app.Gateway.Reload validates the new config as a whole (a bad edit is rejected and the running config stands), reloads the guard and engine, re-pins wildcards against the live catalog, and logs that a restart is needed when anything outside the hot-reload scope changed (topology/redaction/audit diffed via reflect.DeepEqual against the running snapshot).
  • cmd/portcullis handles SIGHUP (re-read the same config file → reload) alongside SIGINT/SIGTERM (graceful shutdown).

Verification

  • Each swap exercised under -race with concurrent reads (TestEngineReloadIsConcurrencySafe, TestGuardReloadIsConcurrencySafe).
  • App-level: an invalid reload is rejected; a new client key authenticates and the old one stops, observed through the HTTP handler.
  • Repo coverage 95.2% (gate 80%), go test -race ./..., go vet, gofmt, golangci-lint — all clean.

This completes the functional V1 surface (§10): edge hardening, per-session downstream sessions, aggregation/pagination, deny-by-default policy + catalog filtering, connection-level secret injection + redaction, failure-class breaker + timeouts + supervision, async audit, /metrics, declarative config, and now scoped hot-reload — proven end-to-end by #33.

🤖 Generated with Claude Code

Reload the client/auth map and the policy rules without dropping
connections or restarting (design §5). Downstream topology, redaction, and
audit changes remain restart-required.

- policy.Engine: Reload atomically swaps the default + rule set behind the
  engine's lock and clears the wildcard pins computed for the old rules
  (fail-closed until the next Sync, never an over-permit). Decide now reads
  the rule set under the read lock; Sync holds the write lock across its
  computation. compileRules is shared with New.
- edge.Guard: Reload atomically swaps the allowed origins and client keys
  behind an RWMutex; origin checking and authentication read a snapshot, so
  a request in flight finishes against a consistent view.
- app.Gateway.Reload: validates the new config as a whole (a bad edit is
  rejected and the running config stands), reloads the guard and engine,
  re-pins wildcards against the live catalog, and logs that a restart is
  needed when anything outside the hot-reload scope changed.
- cmd/portcullis: SIGHUP re-reads the same config file and applies the
  reload; SIGINT/SIGTERM still drive graceful shutdown.

Each swap is exercised under -race with concurrent reads. Repo coverage
95.2%, -race + lint clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Comment thread cmd/portcullis/main.go Fixed
The SIGHUP reload path logs a rejected reload's error, and that error
could carry a client's api_key_env variable name (CodeQL flags any read of
an *Key*-named field flowing to a log as clear-text logging of sensitive
information). Identify the client by its configured id instead; the env
var name is an internal detail and is not needed to diagnose a missing
key. Applied at both sources that feed the reload error — the app's client
key resolution and the config validator.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@JumpTechCode
JumpTechCode merged commit d1c9ae6 into main Jun 17, 2026
5 checks passed
@JumpTechCode
JumpTechCode deleted the feat/scoped-hot-reload branch June 17, 2026 01:51
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.

2 participants