Skip to content

API: Prometheus metrics, DB pool tuning, header/contract docs, health/ready split - #374

Merged
Depo-dev merged 18 commits into
Telocel-Labs:devfrom
Emrys02:dev
Jul 31, 2026
Merged

API: Prometheus metrics, DB pool tuning, header/contract docs, health/ready split#374
Depo-dev merged 18 commits into
Telocel-Labs:devfrom
Emrys02:dev

Conversation

@Emrys02

@Emrys02 Emrys02 commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Summary

Four related Stellar Wave API hardening/observability issues, implemented together since they share the same internal/metrics registry and touch overlapping files (main.go, handlers/, middleware/).

Two real bugs were caught and fixed along the way, both surfaced by the new contract tests:

  • ContractsStats serialized contracts as JSON null instead of [] on empty results.
  • /v1/health's OpenAPI schema documented a response shape (indexer object) that never matched what the handler actually returned.

Test plan

  • go build ./... && go vet ./... && gofmt -l . — clean (pre-existing unformatted files elsewhere untouched).
  • go test -race ./... — full suite green, including new packages (internal/metrics, internal/contracttest) and new test files (pool config, health/ready healthy+unhealthy states, contract validation against api/openapi.yaml).
  • Manually smoke-tested locally: curl :9091/metrics (new metrics), legacy :3000/metrics unchanged, /v1/health vs /v1/ready behavior.
  • No live Postgres/Docker available in the dev sandbox — the pool-config pg_sleep/SHOW statement_timeout end-to-end check from API: connection pool sizing + statement timeouts for Postgres in the Go layer #238's plan should be re-verified against a real DB before/after merge.

Closes #58, closes #238, closes #242, closes #243.

@drips-wave

drips-wave Bot commented Jul 29, 2026

Copy link
Copy Markdown

@Emrys02 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

Depo-dev added 2 commits July 31, 2026 16:46
Seven conflicts, including two add/add where both branches independently
created services/api/middleware/metrics.go.

The two metrics implementations are complementary, not competing, so both are
kept:
- dev's hand-rolled counters render into the public GET /metrics route via
  handlers.MetricsHandler, and handlers/stats.go calls its WriteHTTPMetrics.
- this branch's NewMetrics(mux) records to a Prometheus registry served on the
  separate METRICS_PORT listener (issue Telocel-Labs#58), and is wired as the outermost
  middleware so requests shed by the global concurrency limiter are still
  counted.
They share no state and use different exposition paths. This branch's version
moved to metrics_registry.go so the boundary between the two stays visible,
rather than interleaving them in one file.

Other resolutions:
- main.go — kept both sides: this branch's pgxpool saturation poller (Telocel-Labs#238)
  and dev's usage-rollup loops, which are unrelated background jobs.
- api/openapi.yaml — kept dev's clearer batch-endpoint description together
  with this branch's X-RateLimit-* response headers.
- docs/deployment.md — this branch is right that the Fly check now probes
  /v1/ready, and fly/api.toml on this branch does exactly that (Telocel-Labs#243), so the
  endpoint is this branch's; dev's gRPC TCP-check caveat is kept alongside it.
- go.mod / go.sum — took this branch's newer dependency set (it adds the
  Prometheus client) and reconciled with `go mod tidy`.

Two stale references fixed, both pre-existing here and only reachable once
the branch compiled against dev:
- contract_test.go called handlers.Health(nil, nil, nil) after this branch
  refactored Health() to take no arguments; main.go and health_test.go were
  updated at the time, this call site was not.
- TestContract_RouteParity failed on "GET /v1/ready documented but not
  registered". It was registered — the test's expectedRoutes map is
  hand-maintained and had not been updated when the route was added.

Verified: go vet and go test across all twelve services/api packages,
cargo fmt --check, cargo clippy --workspace --all-targets -D warnings, and
api/openapi.yaml parses.
…el-Labs#331/Telocel-Labs#353/Telocel-Labs#359/Telocel-Labs#378/Telocel-Labs#380)

Six PRs landed on dev since the first merge, so this branch went dirty again.
One conflict: services/api/handlers/health_test.go, add/add — both sides
created it.

The two files are not alternatives. dev's is `package handlers_test` with a
single table-driven TestHealthHandler_TableDriven; this branch's is
`package handlers` with six tests covering the /v1/health and /v1/ready split
it introduces (Telocel-Labs#243).

Kept this branch's file and dropped dev's, having checked what that costs.
dev's four cases — all dependencies reachable, db down, grpc down, redis down
— map one-to-one onto TestReady_AllHealthy_Returns200,
TestReady_PostgresDown_Returns503, TestReady_GRPCDown_Returns503, and
TestReady_RedisDown_Returns503. This branch adds a fifth
(TestReady_NilDependencies_Returns503) and TestHealth_AlwaysReturns200.

Keeping dev's file was not an option regardless: it asserts against
handlers.HealthResponse, a type this branch deliberately split into
LivenessResponse and ReadyResponse, and it expects /v1/health to return 503
when a dependency is down — the exact behaviour Telocel-Labs#243 changes, since a
liveness probe must not fail because Postgres is unreachable.

Verified: go vet and go test across all twelve services/api packages,
cargo fmt --check, cargo clippy --workspace --all-targets -D warnings, and
the REST handler coverage gate added by Telocel-Labs#380 still passes (ListEvents 100.0%,
GetEvent 90.5%, Health 100.0%).
@Depo-dev
Depo-dev merged commit d6f4dc6 into Telocel-Labs:dev Jul 31, 2026
15 of 20 checks passed
Depo-dev added a commit that referenced this pull request Jul 31, 2026
Correcting my previous commit, which fixed a quarter of the problem.

The OpenAPI Spec job does not run openapi-typescript directly any more — #353
replaced it with `python3 scripts/generate_sdk_models.py`, which regenerates
four files and diffs all of them:

    sdk/typescript/src/api-types.gen.ts
    sdk/go/openapi/models_gen.go
    sdk/python/src/trident_indexer/openapi_models_gen.py
    sdk/rust/src/openapi_models_gen.rs

I regenerated only the TypeScript one, so the job stayed red. Running the
real generator produces 958 lines across all four.

The trigger is #374's liveness/readiness split (#243): `HealthResponse` in the
spec became `LivenessResponse` plus `ReadyResponse`/`ReadyChecks`, and
`/v1/ready` was documented as a new path.

That rename also broke the Python package: `src/trident_indexer/__init__.py`
re-exported `HealthResponse`, which the generator no longer emits, so
`mypy --strict` failed with attr-defined and every pytest collection errored.
Updated the re-export and `__all__` to the three new names.

Verified per SDK: cargo clippy -p trident-sdk -D warnings; go build and go vet
in sdk/go; tsc --noEmit and 49 tests in sdk/typescript; mypy --strict and 38
tests in sdk/python.
Depo-dev added a commit that referenced this pull request Jul 31, 2026
…#384)

The `OpenAPI Spec` job was failing on `dev` at **Check for uncommitted changes**.

#374's liveness/readiness split (#243) renamed `HealthResponse` in the spec to `LivenessResponse` plus `ReadyResponse`/`ReadyChecks` and documented `GET /v1/ready`, but did not regenerate the SDK models. That check exists to catch exactly this.

## What was stale

All four files the generator owns, not just TypeScript — 958 lines total:

- `sdk/typescript/src/api-types.gen.ts`
- `sdk/go/openapi/models_gen.go`
- `sdk/python/src/trident_indexer/openapi_models_gen.py`
- `sdk/rust/src/openapi_models_gen.rs`

The rename also broke the Python package: `__init__.py` re-exported `HealthResponse`, which the generator no longer emits, so `mypy --strict` failed and every pytest collection errored. Updated the re-export and `__all__`.

## A real conflict between two CI jobs

Regenerating alone was not enough. The job kept failing on one line:

```
-use serde::{Deserialize, Serialize};
+use serde::{Serialize, Deserialize};
```

quicktype emits `{Serialize, Deserialize}`; `cargo fmt --all` reorders it alphabetically, and `openapi_models_gen.rs` is reachable from `lib.rs` so it is not skipped. The Rust job and this job were asserting opposite content for the same file — whichever ran last left the other red.

Fixed in the generator rather than around it: `generate_rust` now runs `rustfmt` over its output, so the committed file is simultaneously what quicktype produces and what `cargo fmt` wants. Verified idempotent — regenerating twice gives byte-identical output.

(`ignore` in `rustfmt.toml` was the first attempt; that option is nightly-only and CI runs stable, so it silently did nothing.)

## Still red, out of scope

Two checks fail for reasons that predate this PR and need dependency work, tracked with the backlog in #373:

- **Dependency audit** — `govulncheck` reports a `google.golang.org/grpc` advisory reachable from `gen/trident_grpc.pb.go`.
- **SBOM + scan (go-api)** — trivy CVEs in the go-api image.

Verified per SDK: `cargo clippy -p trident-sdk -D warnings`; `go build`/`go vet` in sdk/go; `tsc --noEmit` and 49 tests in sdk/typescript; `mypy --strict` and 38 tests in sdk/python.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants