feat/one-api
needs attentionviewing older commit3845205 · incrementalpre-PRreviewed 2026-07-29 04:47 UTC3H · 6M · 4L · 2I- Purpose
- Establish a durable, auditable baseline of external API usage before the dashboard converges onto the same surface — making customer traffic and product traffic indistinguishable retrospectively.
- Goal
- One public API surface with RLS-enforced machine-caller isolation, durable per-request telemetry, sealed credentials at rest, and the test infrastructure to rehearse migrations safely.
- Sub-goals
- W1: unified public-API mount, service-key JWT, route registry
- W2: webhook secret sealed at rest, public-API telemetry (log + DB sink)
- W2.5: durable api_request_events table + rehearse-upgrade tooling + machine-caller isolation design
- W3 (pending): RLS machine isolation migration + staging soak
- What
- This commit documents the telemetry persistence work in the access-control narrative (L6 gap partially closed), adds a design proposal for machine-caller RLS isolation via widening get_user_org_ids() additively, and adds migration rehearsal guidance to rules/migrations.md.
- Why
- The access-control narrative now has evidence that per-request records exist; the machine-caller isolation design is the next prerequisite before the dashboard convergence work can begin safely.
- Areas
- domains/core+1365−139apps/platform+1067−39packages/api+447−43docs/security+318−0scripts/db+253−0packages/database+200−10.claude/rules+172−6docs/design+149−0.github/workflows+54−2packages/event-bus+31−1infra/cdk+11−1
- Blast
- 73 files across 11 areas, +4067/-241 total. Core domain and platform dominate; docs and tooling are the majority of this incremental commit.
Findings · 14
correctness2
persist() has no observable signal on persistent write failure
apps/platform/src/api/utils/public-v1-telemetry.ts
All errors are intentionally swallowed — the right call for not blocking requests. But a persistent DB write failure (connection pool exhaustion, schema drift after a migration) is completely invisible: no counter, no log line, no metric. The test proved two silent bugs already existed. Consider emitting a single `console.warn(JSON.stringify({ event: 'api.telemetry.write_failed' }))` in the outer catch — no payload, just a detectable signal.
durationMs stored with floor-truncation — integer column, no Math.round()
apps/platform/src/api/utils/public-v1-telemetry.ts
The schema column is `integer`. If `durationMs` is derived from `performance.now()` subtraction, the float is floor-truncated by Postgres — not rounded. Explicit `Math.round(durationMs)` at the call site would make the intent clear and avoid systematic under-reporting by up to ~1 ms per row.
security3
Proposed get_user_org_ids() cast raises exception on malformed claims
docs/design/machine-caller-isolation.md
The proposed function casts `current_setting('request.jwt.claims', true)::json ->> 'batu_org_id')::uuid` without guarding against non-UUID strings. A valid-JSON but non-UUID claim value (e.g. `{"batu_org_id": "not-a-uuid"}`) raises `invalid_text_representation` rather than silently yielding NULL. Wrap in `BEGIN ... EXCEPTION WHEN others THEN RETURN; END` or use `pg_try_cast` equivalent before the migration is written.
rehearse-upgrade.sh localhost guard is string-based, not IP-resolved
scripts/db/rehearse-upgrade.sh
The guard checks if POSTGRES_URL contains '127.0.0.1' or 'localhost'. A hostname that resolves to loopback via /etc/hosts would bypass it. For a script that drops the database, consider also requiring `--confirm` as a secondary flag.
get_user_admin_org_ids() not-widened boundary should be structurally enforced in future migration
docs/design/machine-caller-isolation.md
The design correctly identifies that the admin helper must not be widened (so machine callers can't read api_keys). When the migration is written, add a regression test that specifically asserts a machine credential cannot SELECT from api_keys — documenting this as a test invariant prevents a future 'cleanup' from silently removing the protection.
tests6
Silent skip via `if (!available) return;` passes with 0 assertions
apps/platform/src/__tests__/integration/api-telemetry-persistence.test.ts:65
When the database is unavailable, each test body returns undefined and Vitest marks it PASSED — not SKIPPED. Zero assertions ran, so a broken schema that happens to pass `LIMIT 1` would make all three tests green. Use `it.skipIf(!available)(...)` or call `ctx.skip()` / `vi.skip()` inside the body so the runner surfaces them as SKIPPED.
afterAll cleanup misses `_refused` principal row on test failure
apps/platform/src/__tests__/integration/api-telemetry-persistence.test.ts:114
The second test deletes the `${PRINCIPAL}_refused` row inline at the end of its body. If `waitForRow` times out or an assertion fails first, that row is orphaned. Move cleanup into `afterAll` using a LIKE prefix match (`WHERE principal LIKE '${PRINCIPAL}%'`) so it runs unconditionally.
No test for `client_error` outcome branch (4xx non-auth statuses)
apps/platform/src/__tests__/integration/api-telemetry-persistence.test.ts
The suite covers `ok` (status 200) and `refused` (status 401) but not `client_error` (e.g. 400, 422, 404) or `server_error` (5xx). `outcomeOf` has four branches; only two are exercised end-to-end.
No assertion that `null` org round-trips as SQL NULL (not string 'null')
apps/platform/src/__tests__/integration/api-telemetry-persistence.test.ts
The second test passes `org: null` but never asserts `row.orgPublicId === null`. Given the history of the column-name spread bug, confirming the nullable column round-trips correctly is a meaningful regression guard.
waitForRow masks DB connection errors with misleading timeout failure
apps/platform/src/__tests__/integration/api-telemetry-persistence.test.ts
If the DB connection drops mid-poll, the `while` loop swallows the query error and spins for the full 15 s before returning null. CI failures slow and the final error message ('no telemetry row was persisted') is misleading. Let unexpected errors propagate so the failure is immediate and diagnostic.
The `after()` production path has no integration coverage — only fallback is tested
apps/platform/src/__tests__/integration/api-telemetry-persistence.test.ts
`next/server` is unavailable in Vitest, so `persist()` always takes the inline fallback path. The `after()` scheduling branch — the production code path — is untested. This is acceptable now but worth an e2e smoke test or a manual confirmation after deploy.
improvement3
Inline comments in persist() describe WHAT, not WHY — violates convention
apps/platform/src/api/utils/public-v1-telemetry.ts
Several inline comments annotate code mechanics (e.g. 'Mapped FIELD BY FIELD, never spread', 'No request context — fall through and write inline', 'Module resolution or connection failure'). The convention is WHY only; WHAT comments are noise to readers who can read the code. The field-mapping risk is better addressed by a compile-time assertion or a narrow type than by a prose warning inside a catch-all sink.
byTime solo index is likely redundant given both composites cover time
packages/database/src/schema/api-request-events.ts
Every real query in api-usage.sql filters by org or route in addition to time, so Postgres will prefer `byOrg` or `byRoute` over the solo `byTime` index. The only pure-time scan is the retention DELETE, which can use either composite index by ignoring the leading column. Dropping `byTime` removes write amplification on every insert with no practical read cost.
Inline cleanup in second test case is asymmetric with afterAll pattern
apps/platform/src/__tests__/integration/api-telemetry-persistence.test.ts:114
First test case relies on afterAll for cleanup; second test cleans up inline. Consolidating into afterAll with a prefix-LIKE match would be consistent and failure-safe.
History · 47 commits
- 82bb5b9blockedincremental5H · 5M · 4L2026-08-12 01:48
- 90aa3d5needs attentionincremental1H · 5M · 3L2026-08-11 19:37
- 29d19a0needs attentionincremental1H · 5M · 9L2026-08-11 17:41
- 9bd8a0cneeds attentionfull0H · 5M · 9L2026-08-11 02:14
- 62ec3f7needs attentionincremental2H · 5M · 6L2026-08-10 22:51
- f93bca9needs attentionincremental2H · 5M · 8L2026-08-10 17:51
- 052db6fneeds attentionincremental1H · 3M · 4L2026-08-09 21:13
- 45699caneeds attentionincremental0H · 7M · 11L2026-08-09 17:44
- b843d8aneeds attentionincremental1H · 7M · 9L2026-08-09 04:05
- e1757b8needs attentionincremental0H · 3M · 6L2026-08-05 02:11
- 7a762faneeds attentionincremental2H · 5M · 5L2026-08-05 01:25
- 3300a60needs attentionincremental2H · 4M · 7L2026-08-04 19:06
- 0c8a7f5needs attentionincremental0H · 4M · 9L2026-08-04 18:15
- 345f42eneeds attentionincremental2H · 6M · 9L2026-08-04 17:28
- 8338a9aneeds attentionincremental5H · 14M · 14L2026-08-04 00:33
- 41be4c3needs attentionincremental0H · 5M · 7L2026-08-03 23:49
- 5ed593dneeds attentionincremental1H · 6M · 6L2026-08-03 21:32
- b333e25needs attentionincremental4H · 9M · 8L2026-08-03 21:00
- 5642cccneeds attentionincremental2H · 3M · 2L2026-08-03 20:17
- 73b0b39needs attentionincremental3H · 10M · 13L2026-07-31 18:29
- b19852eneeds attentionincremental0H · 1M · 5L2026-07-29 05:04
- 3845205needs attentionincremental3H · 6M · 4L2026-07-29 04:47current
- eb8eb50needs attentionincremental0H · 1M · 2L2026-07-29 03:03
- f4720a3needs attentionincremental6H · 8M · 7L2026-07-29 02:54
- f8d341ablockedincremental2H · 2M · 5L2026-07-29 00:00
- a7f1a64needs attentionincremental2H · 8M · 8L2026-07-28 18:41
- 738b60bblockedincremental3H · 6M · 5L2026-07-28 00:46
- 2c248b6needs attentionincremental8H · 12M · 8L2026-07-27 23:23
- 1346cc0needs attentionincremental2H · 8M · 6L2026-07-27 20:15
- 0716018needs attentionincremental2H · 11M · 12L2026-07-27 19:22
- 215cd2dneeds attentionincremental3H · 6M · 5L2026-07-27 17:04
- ec46958needs attentionincremental0H · 3M · 5L2026-07-27 16:51
- de7b337blockedincremental4H · 9M · 14L2026-07-27 06:36
- b1bb9c0needs attentionincremental1H · 2M · 4L2026-07-27 05:09
- 4701d11needs attentionincremental0H · 4M · 3L2026-07-27 04:44
- e1626c4needs attentionincremental3H · 9M · 10L2026-07-27 03:21
- 195f198needs attentionincremental3H · 3M · 3L2026-07-25 01:22
- 42c7358safeincremental0H · 0M · 0L2026-07-22 20:46
- 85b9018needs attentionincremental0H · 1M · 6L2026-07-21 23:51
- a7b2a9aneeds attentionincremental0H · 9M · 12L2026-07-21 18:49
- c2ee0daneeds attentionincremental4H · 7M · 7L2026-07-21 02:17
- e8ffa5eneeds attentionincremental4H · 7M · 5L2026-07-21 01:33
- a2d2a54needs attentionincremental2H · 7M · 3L2026-07-21 00:51
- 576fbd6needs attentionfull1H · 6M · 7L2026-07-21 00:35
- d3465e8needs attentionincremental1H · 7M · 10L2026-07-21 00:23
- dc794a7needs attentionincremental0H · 5M · 5L2026-07-20 23:46
- 9082773needs attentionfull1H · 3M · 3L2026-07-20 23:13