Skip to content

Hardening: durable dedup + stuck-incident recovery, signed alerts, drop dead fields - #13

Merged
Lushenwar merged 3 commits into
mainfrom
feat/hardening-2
Oct 11, 2026
Merged

Lushenwar merged 3 commits into
mainfrom
feat/hardening-2

Conversation

@Lushenwar

Copy link
Copy Markdown
Owner

Commits

  1. fix(core): no more incidents stuck in triage; dedup survives restarts
    • Open-incident dedup moves from an in-memory dict to a partial unique index on incidents.signature (status IN ('triggered','triaging')), so it holds across restarts and workers.
    • A failed triage now ends degraded with the reason recorded. It used to revert to triggered and stay open with nothing in it.
    • At startup, orphaned triages (open, no diagnostics, no update for 5 min) are re-run.
    • The unused app_logs table is dropped from the schema.
    • CI runs a Postgres service, and the SQL is tested against it (test_db_integration.py, opt-in via SENTINEL_DB_TESTS=1 so local runs never touch Neon).
  2. feat(core): HMAC-signed alerts; CORS origins from env: with SENTINEL_ALERT_SECRET set, /alert requires X-Sentinel-Signature: sha256=<hmac> (constant-time compare); without it, unsigned alerts are allowed with a startup warning. The sandbox senders sign. CORS origins come from SENTINEL_CORS_ORIGINS.
  3. refactor: drop estimated_affected_users: it was always null, and no data source exists to fill it honestly.

Tests

  • 42 core tests pass locally. The 3 DB tests skip locally and run in CI: 98 passed, 0 skipped on the first commit.
  • Dashboard lint passes.

Notes

  • The first core start against Neon adds the signature column and index. Existing rows stay NULL and never dedup, so no backfill is needed.
  • Dedup now persists, so an incident left open (triaged, never resolved) absorbs its signature's alerts until it is resolved. Before, a restart cleared that.

🤖 Generated with Claude Code

Lushenwar and others added 3 commits October 11, 2026 16:39
- Open-incident dedup moves from an in-memory dict to a partial unique index
  on incidents.signature (status triggered/triaging), so it holds across
  restarts and workers. Rows from before the column existed stay NULL and
  never dedup, so no backfill is needed.
- A triage that throws now ends `degraded` with the reason recorded. It used
  to revert to `triggered` and stay open with nothing in it.
- At startup, open incidents with no diagnostics and no update for
  SENTINEL_STUCK_AFTER_S (default 300s) are re-triaged in a background
  thread.
- Drops the unused app_logs table from the schema. Existing tables are left
  alone.
- CI runs a Postgres service so the new SQL is tested against a real
  database (test_db_integration, opt-in via SENTINEL_DB_TESTS=1).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With SENTINEL_ALERT_SECRET set, /alert requires
X-Sentinel-Signature: sha256=<HMAC-SHA256 of the raw body>, compared in
constant time; anything else gets a 401. Unset, unsigned alerts are still
accepted, with a startup warning, so the local demo works without setup.
chaos_cli and the load generator sign through sandbox/alert_signing.py.

/alert now reads the raw body (the signature covers exact bytes) and
rejects non-object JSON with a 400. The handler runs in the threadpool so
its DB call doesn't block the event loop.

CORS origins come from SENTINEL_CORS_ORIGINS. An empty value falls back to
http://localhost:3000 rather than allowing no origins.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Nothing upstream reports user ids, so the field could never be filled
honestly. It is removed from the orchestrator, the contract schema, the
dashboard type and the CLAUDE.md contract example. Old rows that still
carry it validate, because the schema allows extra properties.
CLAUDE.md's pipeline step 2 now says Sentinel reads the alert's stack
trace; the Postgres log table it described never existed in practice.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Lushenwar
Lushenwar merged commit 5a96903 into main Oct 11, 2026
4 checks passed
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