Skip to content

feat(api): add observability readiness harness - #30

Merged
mcfarhat merged 2 commits into
developfrom
feat/trello-35-observability-harness
Sep 6, 2026
Merged

mcfarhat merged 2 commits into
developfrom
feat/trello-35-observability-harness

Conversation

@KareemN1936

Copy link
Copy Markdown
Collaborator

Summary

  • Add explicit API request IDs, structured log redaction, and an unhandled-error reporting hook with optional webhook delivery.
  • Add /ready alongside /health, reporting PostgreSQL connectivity and HAF indexer lag against a configurable threshold.
  • Add a reusable migrated PostgreSQL integration-test fixture and wire the readiness route plus existing concurrency test through it.
  • Document the operational env vars and health/readiness endpoints.

Trello

Verification

  • npm run typecheck --workspace=@hivelore/api
  • npm test --workspace=@hivelore/api
  • npm run lint --workspace=@hivelore/api
  • npm run build --workspace=@hivelore/api
  • git diff --check

Note: the two live PostgreSQL integration tests skip locally when TEST_DATABASE_ADMIN_URL is not configured; CI provides that variable via the PostgreSQL service.

@KareemN1936

Copy link
Copy Markdown
Collaborator Author

Implemented Trello #35 in this PR.

What changed:

  • API request IDs are now explicit via x-request-id, with structured Fastify logging and secret redaction kept in place.
  • Added GET /ready for PostgreSQL connectivity and HAF indexer lag, alongside lightweight GET /health.
  • Added optional unhandled-error reporting via ERROR_TRACKING_ENABLED + ERROR_TRACKING_WEBHOOK_URL.
  • Added a reusable migrated PostgreSQL integration-test fixture and wired readiness coverage plus the existing concurrency test through it.
  • Documented the new operational env vars and endpoints.

Verification:

  • npm run typecheck --workspace=@hivelore/api
  • npm test --workspace=@hivelore/api (207 passed, 2 skipped locally because TEST_DATABASE_ADMIN_URL is not configured)
  • npm run lint --workspace=@hivelore/api
  • npm run build --workspace=@hivelore/api
  • git diff --check

@github-actions

github-actions Bot commented Sep 5, 2026 •

Copy link
Copy Markdown

Vercel preview is ready:

https://hivelore-98h4zi3gs-kareemn1936s-projects.vercel.app

Commit: cdecd8a

@KareemN1936

Copy link
Copy Markdown
Collaborator Author

Remote checks are now green:

  • Quality: passed
  • Vercel Preview: passed

… request

Fastify awaits the onError hook, and reportUnhandledError awaited a fetch with
no timeout. A webhook that accepts the connection and never replies therefore
kept every erroring request open until OS-level TCP timeouts — minutes. Proven
against a deliberately unresponsive server: the call was still pending after 8s.

Observability taking the service down with it is the worst version of this, and
it would have bitten on the first day ERROR_TRACKING_ENABLED was turned on.

- bound delivery with AbortSignal.timeout, default 2s, overridable per call
- stop awaiting delivery in the onError hook, so the error response never waits
  on telemetry at all (reportUnhandledError swallows its own errors, so the
  floating promise cannot reject)
- add a regression test using a server that accepts and never replies; it hangs
  against the unbounded version and passes in 150ms against this one

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@mcfarhat mcfarhat left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving, with one fix pushed to your branch (e3585c2).

The observability work is good — request IDs via randomUUID, structured logs, a /ready that returns 503 when degraded, and a sensible 1200-block lag threshold. The integration-db.ts extraction is a genuine improvement, and the webhook payload deliberately excludes headers and bodies, which is the right instinct.

What I fixed: a stalled webhook hung every failing request

app.ts        app.addHook('onError', async (...) => { await reportUnhandledError(...) })
error-tracking  await fetch(webhookUrl, {...})    // no timeout, no AbortSignal

Fastify awaits the onError hook, so the error response waited on that fetch. With no AbortSignal, a webhook that accepts the connection and never replies keeps it pending until OS-level TCP timeouts — minutes, not seconds.

I tested it rather than reasoning about it, pointing it at a server that accepts and never responds:

after 8s: reportUnhandledError resolved?  false
elapsed:  8009ms — still pending

So a bad day at the webhook provider turns every API error into a hung request, and under load that exhausts the connection pool. Observability code taking the service down with it is the worst version of this bug, and it would have surfaced the first day ERROR_TRACKING_ENABLED was switched on — precisely when you'd least want it.

The fix, two parts:

  1. AbortSignal.timeout on delivery, default 2s, overridable per call — so it can never hang unboundedly.
  2. The onError hook no longer awaits delivery. The error response shouldn't wait on telemetry at all. reportUnhandledError swallows its own errors, so the floating promise can't reject.

And a regression test, using a server that accepts and never replies. It hangs against the unbounded version and passes in ~150ms against the fixed one — I verified both directions by mutation, removing the AbortSignal and watching the suite hang.

Why the tests didn't catch it — the useful bit

The two existing tests cover serialization and the disabled path, both correct. But there were zero references to timeout, stall, hang or abort. That's the general lesson for anything doing outbound HTTP: the interesting failure isn't a bad response, it's no response. A mock that returns instantly can't model a peer that never answers.

Two smaller things, not blocking

  • ERROR_TRACKING_WEBHOOK_URL is validated as a well-formed URL but isn't required to be HTTPS, so stack traces could go out in cleartext. Worth tightening when a real endpoint is chosen.
  • url: request.url includes the query string. Harmless today since auth is cookie-based, but it's the kind of field that quietly starts carrying tokens.

Merging.

@mcfarhat
mcfarhat merged commit 221c8bc into develop Sep 6, 2026
2 checks passed
@mcfarhat
mcfarhat deleted the feat/trello-35-observability-harness branch September 6, 2026 10:18
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