feat(claude-usage): POST et GET /claude-usage (#48) #52

Merged
maximus merged 2 commits from issue-48-claude-usage-routes into main 2026-10-04 19:29:24 +00:00

2 commits

Author SHA1 Message Date
le king fu
ef8558e90e test(claude-usage): cover the routes, the sanitizer and the new write surface
Some checks are pending
CI / verification (pull_request) Successful in 26s
CI / verification (push) Waiting to run
- __tests__/claude-usage.test.js (119 cases): sanitizer contract and
  idempotence, dropped values (unknown fields, negative, past 2^53,
  detail outside its regex, status outside the enum, bad dates, model
  keys), list bounds, token isolation both ways, 503 naming each variable,
  413 at 65 537 bytes and /hosts/<id> still 413 at 4 097, atomic mode-600
  write, POST then GET round trip, exact staleness boundary under a frozen
  clock, no_data degradation, rejection logs, COPY line.
- writes.test.js: POST /claude-usage may write only in the directory of
  CLAUDE_USAGE_PATH, its rejections write nothing, GET /claude-usage joins
  the read routes.
- README.md and CLAUDE.md: both routes, the three env vars, the
  /data/claude-usage read-write mount owned by uid 1000, the token table.

Resolves #48

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 09:16:11 -04:00
le king fu
e1224e0df4 feat(claude-usage): add POST and GET /claude-usage behind a third token kind
- checkAuth() reads the expected token from a TOKEN_KINDS table
  (read -> HEALTH_TOKEN, ingest -> HOSTS_INGEST_TOKEN, claude-ingest ->
  CLAUDE_USAGE_INGEST_TOKEN); each ingestion token fails closed to a 503
  naming its own variable. Still one gate, one tokenMatches() call. A route
  naming an unknown kind fails module load.
- readBody() takes its ceiling per route: 4 KiB for /hosts/<id>, 64 KiB for
  /claude-usage.
- claude-usage.js: pure, exported sanitizeClaudeUsage() for contract
  schemaVersion 1 (whitelist rebuild, invalid values dropped, list bounds).
- POST /claude-usage: 400 on invalid JSON or schemaVersion, server-stamped
  receivedAt, atomic write (pid-suffixed temp, mode 600, rename), 204.
- GET /claude-usage: re-sanitized snapshot with ageSeconds, stale and
  staleAfterSeconds (CLAUDE_USAGE_STALE_SECONDS, default 2700), or no_data.
- Dockerfile COPY ships claude-usage.js.
- auth.test.js: the checkAuth() equality allowlist loses the former
  `route.tokenKind === "ingest"` entry, which the table removes. The pin
  only got stricter.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 09:16:05 -04:00