Skip to content

Polish batch: dead BASE_URL config, /records/query auth doc mismatch, fabricated token createdAt, unvalidated token entityId, silent ENTITY_ID mismatch, shutdown timeout #49

Description

@cuibonobo

Server design-review small findings that don't warrant individual issues (same convention as haverstack/core#69).

1. BASE_URL is loaded, documented, and used by nothing

src/config.ts:60 reads it, the README documents it ("auto-detected" — no detection exists either), and no code consumes config.baseUrl. Dead config advertised to operators. Either use it (natural home: a baseUrl field in /.well-known/stack so clients can learn their canonical origin behind proxies) or delete it from config + README.

2. POST /records/query: docs say auth Optional, code says required

docs/api.md lists it as Optional auth; the handler has requireAuth() (src/routes/records.ts:159). One is wrong. GET /records serves anonymous callers their public-record subset, and the spec describes /records/query as a superset of the same query surface — consistency says optional auth (anonymous → asEntity(null)), but deciding the other way just means fixing the doc. Pin the answer in a test either way.

3. POST /tokens fabricates createdAt and trusts entityId blind

  • The response's createdAt: new Date().toISOString() is invented at the route (src/routes/tokens.ts:28) rather than read back from the store — trivially divergent from what GET /tokens later reports. Return the stored row.
  • body.entityId is accepted as any string — no format check, no existence/_entity lookup, no warning. An owner typo mints a token for an entity that can never match a grant. At minimum validate non-empty string shape now; becomes a DID validation under Core sync: DID identity — challenge–response token issuance, DID entityIds in config and discovery (core #49) #42.

4. ENTITY_ID mismatch on an existing DB is silently ignored

Documented behavior ("ignored once the database exists"), but silently is the problem: an operator who changes ENTITY_ID expecting it to take effect gets no signal. Log a warning at startup when the env var is set and differs from the stack's stored ownerEntityId.

5. Discovery falls back to an empty-string entityId

entityId: ctx.stack.ownerEntityId ?? '' (src/routes/wellknown.ts:11) — core's ownerEntityId getter returns string, so the fallback is unreachable; if it ever weren't, "" is the worst possible answer (clients cache it as an identity per spec §Discovery). Drop the fallback; fail loudly if it can be absent.

6. Shutdown has no deadline

shutdown() waits for server.close() (src/index.ts:26-34), which waits for open keep-alive connections — potentially forever; under Docker that means SIGKILL at the grace-period boundary with no flush(). Add a timeout (e.g. 10 s) after which connections are destroyed and flush/close run anyway; also flush() on the fatal-error path.

Work items

  • BASE_URL: implement in discovery or remove
  • /records/query auth: decide, align code+docs, test
  • Token route: stored createdAt; entityId shape validation
  • Startup warning on ENTITY_ID mismatch
  • Discovery fallback removal
  • Bounded shutdown + fatal-path flush

Refs #42 (DID validation supersedes 3b), #33 (any new error responses use the wire body).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions