GitHub
10/22/2026, 2:26 PMPOST /{env}/osctrld-status found in the final review of that change. Paired with the osctrld follow-up that fixes the reporting edge cases on the host side.
Problem
• Lost rollback event on a partial failure: RecordReport records a rollback event for flags and for extensions with two separate compare-and-set updates, and returns the kinds that were new. If the flags update succeeded and the extensions update then hit a database error, the handler discarded the new kinds and returned 500. On osctrld's retry the flags event id was already stored, so that event was never counted in osctrld_rollbacks_total or written to the audit log.
• Database error answered as "bad request": every error from the node lookup was answered with 400, including a database outage. A real server error looked like a client error and wasn't logged.
What changes
• Count and audit before failing. When RecordReport returns an error, the handler first counts (osctrld_rollbacks_total) and audits (RolledBackOsctrld) every kind it returned as newly recorded. This uses the same loop as the success path, not a copy. It then logs the error and answers 500. A retry of the same report finds that event already stored, so it isn't counted or audited twice.
• Only "not found" is a 400. A node that is unknown, or belongs to another environment, still gets the identical empty 400, so nothing leaks between the two cases. Any other lookup error is logged with the environment name (never the secret) and answered 500 with an empty body. The route still never answers 404, because osctrld reads 404 as "route not served".
The changes are in cmd/tls/handlers/osctrld_status.go and its test. pkg/nodeconfig and production code are unchanged: the tests inject failures with gorm callbacks registered on the test database.
Security and operational impact
• No new inputs, routes or labels. Metric labels still only take the environment name, a fixed kind and a validated stage.
• A database fault now shows up as a 500 and a log line instead of a quiet 400, and no longer drops a rollback from the counter and audit log.
• osctrld treats 400 and 500 the same way (it retries on its own schedule), so there is no client-side effect.
Known limits (deferred)
• The partial-failure test checks the flags counter but not that the extensions counter stayed flat.
• The lookup-error test doesn't assert that no audit row was written.
• Rollback audit rows still have no per-environment cap. Only holders of the environment secret can reach them, and the per-address limits from #1097 still apply.
Validation
• `go test ./cmd/tls/... ./pkg/... -count=1`: 57 packages pass, 0 failures
• go vet ./..., gofmt -l pkg cmd (empty), `golangci-lint run ./...`: 0 issues
• make openapi-check was not needed: no Swagger comments or REST contract changed
• New tests, each seen failing first:
• Partial failure: the extensions update fails after the flags update succeeded. The counter and audit log record the flags event once, the response is 500, and a retry of the same report doesn't double count.
• Lookup error: a non-not-found database error answers 500 with an empty body and stores nothing.
• Unchanged not-found cases: an unknown node and a node of another environment get identical empty 400 responses.
jmpsec/osctrlGitHub
10/22/2026, 2:55 PM