Imported from eshu-hq/eshu (
go/cmd/api/AGENTS.md). Install upstream withnpx skills add eshu-hq/eshu --skill api. Copyright stays with the author.
AGENTS.md — cmd/api guidance for LLM assistants
Read first
go/cmd/api/wiring.go— the wiring sequence: how Postgres and the graph driver are opened, how the query graph is opened, and how the admin surface is mounted viamountRuntimeSurfaceinwireAPI. The router assembly (newRouter,newRouterWithSemanticEmbedding) and the version/ deprecation middleware live in the siblinggo/cmd/api/wiring_router.go.go/cmd/api/main.go—main, telemetry bootstrap, signal handling, andhttp.Serverconfiguration.go/internal/query/handler.go—APIRouterandAPIRouter.Mount; understand this before adding handler families or changing route registration.go/internal/runtime/—NewStatusAdminMux,OpenNeo4jDriver,ResolveAPIKey; the shared admin seams this binary delegates to.go/cmd/api/README.md— lifecycle summary, env vars, and operational notes.
Invariants this package enforces
- Read-mostly runtime with one bounded policy mutation — normal data routes
use read adapters (
query.Neo4jReader,query.ContentReader) and the binary never calls graph write methods. The authenticated, all-scopes-only vulnerability-suppression route is the deliberate exception: it writes one immutable operator fact generation and projector intent transactionally throughVulnerabilitySuppressionMutationStore. Do not add another fact or queue write here without an owner-approved runtime-boundary review. - Required Postgres DSN —
wireAPIreturns an error afterResolveAPIKeysucceeds if bothESHU_POSTGRES_DSNandESHU_CONTENT_STORE_DSNare empty; the binary exits at startup (wiring.go:42). - Profile and backend validated at startup —
loadQueryProfileandloadGraphBackendcallParseQueryProfileandParseGraphBackendrespectively; unrecognized values return errors that causeos.Exit(1)(wiring.go:147). - Auth wraps the full mux —
AuthMiddlewareis applied aftermountRuntimeSurface, so data routes cannot be reached without auth when a token is configured (wiring.go:105). - Authenticating is not being admitted —
wrapAPIAuthderives aquery.BrowserSessionRoutePolicyfromESHU_GOVERNANCE_MODEviaquery.ScopedRoutePolicyForGovernanceModeand hands it to the middleware that serves both bearer tokens and dashboard browser sessions. Underhosted_multi_tenant, or a mode the binary does not recognize, an all-scope credential is refused with 403 on grant-bound, deployment-scoped, and transitive routes. Thread that policy through any new auth constructor here;query.BrowserSessionRoutePolicy's zero value is fail-closed, so a constructor that drops it refuses all-scope callers on a laptop too. The env var is documented for operators ingo/cmd/api/README.md. - Graceful shutdown timeout — the shutdown goroutine calls
Shutdownon the server with a configurable timeout read fromESHU_API_SHUTDOWN_TIMEOUT(default 30 s). Requests not completed within that window are interrupted. Operators setting an explicit 5 s value retain the prior hard-coded behavior. The deadline is read at startup; runtime changes require a restart (main.go:95). - Recovery response budget — the HTTP write timeout is derived from
rebuildreset.DefaultRefinalizeDrainTimeoutplus a one-minute margin. A hard-killed reducer can retain its lease for the drain window; shortening the server timeout below that bound drops the eventual recovery response. - Compile-time port conformance —
wiring.go:23asserts thatNeo4jReadersatisfiesGraphQueryandContentReadersatisfiesContentStore; removing these assertions will silently break port conformance.
Common changes and how to scope them
-
Add a new handler family → add the handler struct to
internal/query, add aMountcall inAPIRouter.Mount, wire the struct innewRouter(wiring_router.go), add a path fragment underinternal/query/openapi/paths/<family>/and reference it in the OpenAPI assembly function, updatedocs/public/reference/http-api.md, rungo test ./cmd/api ./internal/query -count=1. Why: all handler families follow the same struct-and-Mountpattern; missing a step leaves routes unreachable or undocumented. -
Change the listen address or timeouts → edit
main.goforESHU_API_ADDRandnewAPIServer. The read-header and idle timeouts are hard-coded; the write timeout follows the recovery drain bound; only the graceful shutdown timeout is configurable viaESHU_API_SHUTDOWN_TIMEOUT. Why: server timeouts are deployment-level contracts that should be changed deliberately, not silently picked up from environment. -
Swap the graph backend → set
ESHU_GRAPH_BACKEND=nornicdborESHU_GRAPH_BACKEND=neo4j; the binary delegates toParseGraphBackendandopenQueryGraph. Do not add backend-conditional branches in this package; those belong ininternal/storage/cypheradapters. -
Add a new environment variable → read it in
wireAPIvia thegetenvfunction parameter (notos.Getenvdirectly), updatedoc.goandREADME.md, and add it todocs/public/reference/cli-reference.md. Why:wireAPItakesgetenv func(string) stringso tests can inject values withoutt.Setenv.
Failure modes and how to debug
-
Symptom: binary exits immediately after
eshu-apistarts → likely cause: missingESHU_POSTGRES_DSN, badESHU_QUERY_PROFILE, or graph driver unreachable → check the structured logruntime.startup.failedevent; it carries the specific error. -
Symptom:
/healthzreturns 503 → likely cause:NewStatusAdminMuxor the status reader returned an error → check/admin/statusfor the reported stage and failure fields. -
Symptom: high latency on data routes → likely cause: slow graph or Postgres queries → check
eshu_dp_neo4j_query_duration_secondsandeshu_dp_postgres_query_duration_secondsat/metrics; trace individual requests via theotelhttpspan namedeshu-api. -
Symptom: 401 on all data routes → likely cause: bearer token mismatch → the
AuthMiddlewareininternal/queryskips auth only when the resolved token is empty (dev mode); verify the token resolves viaResolveAPIKeyininternal/runtime. -
Symptom: requests interrupted mid-stream during redeploy → likely cause: the graceful
Shutdownwindow (default 30 s, configurable viaESHU_API_SHUTDOWN_TIMEOUT) was exceeded → consider a query-side timeout on long graph traversals before extending the server shutdown window (main.go:95).
Anti-patterns specific to this package
-
Calling
os.Getenvdirectly insidewireAPI—wireAPItakesgetenv func(string) stringfor test injection. Directos.Getenvcalls insidewireAPImake the wiring untestable withoutt.Setenv. -
Adding backend-conditional branches to
wiring.go— backend brand differences belong ininternal/storage/cypheradapters behind theGraphQueryport. Addingif graphBackend == "nornicdb"in this package leaks dialect logic into the transport layer. -
Adding data routes directly to
apiMuxinwireAPI— new routes belong in handler structs underinternal/querywith aMountmethod. Bypassing theAPIRoutermeans the route misses OpenAPI registration, capability-matrix gating, and the standard response-envelope contract. -
Mounting the admin surface after
AuthMiddleware— admin endpoints (/healthz,/readyz,/admin/status,/metrics) must remain public.mountRuntimeSurfaceis called beforeAuthMiddlewareis applied; reversing that order blocks health probes.
What NOT to change without an ADR
ESHU_QUERY_PROFILEaccepted values — part of the public truth-label contract; seedocs/public/reference/http-api.mdandgo/internal/query/envelope_aliases.go.ESHU_GRAPH_BACKENDaccepted values — governed by the backend promotion gate; seedocs/public/reference/backend-conformance.md.- The
AuthMiddlewareplacement relative tomountRuntimeSurface— moving this changes which routes require auth; that is a security-boundary change. - Any API fact or queue write beyond the bounded, all-scopes vulnerability suppression mutation — data-plane write ownership belongs to intake and resolution runtimes unless an owner-approved boundary change says otherwise.
Evidence (W-1: ESHU_API_SHUTDOWN_TIMEOUT)
- No-Regression Evidence: The change replaces a hardcoded
5*time.Secondwith an env-read-and-parse that lands on the same 5 s whenESHU_API_SHUTDOWN_TIMEOUT=5sis set explicitly, and defaults to 30 s otherwise. No runtime, storage, Cypher, concurrency, queue, or performance path is affected. The shutdown goroutine remains a single closure with identical scheduling behavior. - No-Observability-Change: The existing
eshu-apiOTEL span,eshu_dp_*metrics, and structured log keys already diagnose the API binary lifecycle. The shutdown timeout value change does not alter the signal contract. W-2 adds a dedicatedeshu_dp_shutdown_duration_secondshistogram for the shutdown path.