← all branches

feat/one-api

needs attentionviewing older commit
3300a60 · incrementalpre-PRreviewed 2026-08-04 19:06 UTC2H · 4M · 7L · 5I
The branch
Purpose
Secure the public API surface: row-level security for all reads, machine-caller isolation, webhook secret sealing at rest, and API telemetry.
Goal
feat/one-api — close the gaps between the initial one-api work and a production-safe state across security, observability, and CI coverage.
Sub-goals
  • SG-1: API telemetry — request records that cannot stall the response
  • SG-2: Machine isolation — database-enforced tenant isolation for service callers
  • SG-3: RLS reads — run public API reads under row-level security
  • SG-4: Webhook secret sealing — seal signing secrets at rest, load key at runtime
  • SG-5: Contract site fix — every contract always gets a site; retire entity_relationships path
  • SG-6: CI gaps — add guards for cross-domain, event-bus, telemetry write path
The changes (whole branch)
What
Stop caching transient Secrets Manager failures in the webhook dispatcher (a thrown SDK error now retries instead of poisoning the container for its lifetime); add a 409 guard to enableMonitoringShell for already-sited contracts; expand the telemetry source guard from two files to the whole src/api tree; add the contract-wizard site-claim integration suite to CI; fix the rehearsal script to handle shipped migrations.
Why
The transient-caching bug would silently drop all webhook deliveries for an entire container lifetime on a throttle or IAM propagation delay. The 409 guard closes an unbounded duplicate-site creation path that became the default case once createFromWizardShell started always creating sites.
Areas
packages/event-bus+14010domains/cross-domain+800.github/workflows+405apps/platform/src/api/utils/__tests__+4030scripts/db+6015
Blast
7 files changed in incremental window (+361/-43). Branch total: 102 files, +73910/-407 across event-bus, cross-domain, platform/api, infra/cdk/events, packages/database, packages/api.
no-open-pr incremental-review
typecheck· not run in this reviewtests· no CI run available for this SHAcoderabbit· no .coderabbit.yaml present

Findings · 17

correctness3

low

enableMonitoringShell 409 uses VALIDATION_ERROR code for a conflict condition

domains/cross-domain/src/contract-wizard.shells.ts:572

The handler maps WizardDecisionError to fail(409, 'VALIDATION_ERROR', msg). HTTP 409 is returned correctly, but the error code VALIDATION_ERROR semantically describes a validation failure, not a conflict. Pre-existing pattern in this handler, not introduced by this diff, but the new case is a semantic mismatch that could confuse callers inspecting the error code.

info

telemetry guard walk skips __tests__ — re-exports from test files undetected

apps/platform/src/api/utils/__tests__/public-v1-telemetry.test.ts:191

The walk skips __tests__ directories. A re-export of next/server placed inside __tests__/ would not be caught. Explicitly accepted by the commit: computed specifier evasions are 'deliberate rather than accidental'. The __tests__ exclusion is correct — test mock files legitimately reference next/server.

info

afterAll cleanup does not remove entity_relationships rows

domains/cross-domain/src/__tests__/contract-wizard-sites.integration.test.ts:96

Cleanup deletes site_utility_contracts, utility contracts, sites, memberships, orgs — but not entity_relationships. Since the fix removes entity_relationships writes, this is not a current correctness issue. The new test asserts rels.length===0 which catches any regression. Low cross-test leak risk given unique RPU generation.

security2

medium

catch block classifies ALL SDK errors as transient — ResourceNotFoundException included

packages/event-bus/src/handlers/webhook-dispatcher.lambda.ts:268

The catch block sets transient=true for any thrown SDK error, including ResourceNotFoundException (a secret that was deleted or renamed — a deterministic condition). This causes one Secrets Manager lookup per invocation indefinitely for a misconfigured secret name, rather than caching null after the first miss. Operationally safe (deliveries are skipped with logging, not sent unsigned), but the comment says 'transient by assumption' without narrowing the scope. Throttling and IAM propagation are genuinely transient; ResourceNotFoundException is not.

low

loadSealingKey exported from Lambda entrypoint — minor surface expansion

packages/event-bus/src/handlers/webhook-dispatcher.lambda.ts:234

loadSealingKey and __resetSealingKeyCacheForTests are now exported from the Lambda entrypoint. No production caller imports them; the Lambda bundle is private. The risk is that a future barrel re-export or package restructure could inadvertently expose the test seam. The established convention (__resetXForTests) is correct; a comment marking it test-only would reduce the surface.

conventions3

medium

Guard returns WizardDecisionError tag instead of a dedicated discriminated variant

domains/cross-domain/src/contract-wizard.shells.ts:572

The already-sited guard uses WizardErrors.decisionError({message, statusCode:409}), which produces _tag:'WizardDecisionError' — the catch-all for forwarded decision failures. The handler cannot distinguish 'contract already sited' from a genuine decideCreateSite error. ADR-016 requires unique, named _tag values per business rule. A dedicated WizardContractAlreadySitedError with _tag:'WizardContractAlreadySited' and fixed statusCode:409 should be added to the EnableMonitoringError union.

low

Guard belongs in a decision function by FCIS, not inlined in the shell

domains/cross-domain/src/contract-wizard.shells.ts:572

The `if (hasPath1Access)` precondition is a business rule — 'enableMonitoringShell is for requires_site contracts only'. FCIS places business rules in pure decision functions testable at ~5000/sec. The condition could live in a decideEnableMonitoring function receiving hasPath1Access. Not blocking since the surrounding shell already mixes access-check logic, but the untestability at unit speed is the cost.

low

Block comment over-explains WHAT the guard does

domains/cross-domain/src/contract-wizard.shells.ts:557

The 14-line block comment before the guard describes the hazard at length (duplicate site creation, unbounded). The non-obvious WHY — routing semantics from contract-access.ts (requires_site vs ok) — is in the last two lines. The code comment convention asks only for the non-obvious WHY. The hazard description is derivable from `hasPath1Access` and the surrounding context.

tests6

high

New no-site-info test placed in wrong describe block

domains/cross-domain/src/__tests__/contract-wizard-sites.integration.test.ts:161

The test 'creates a site named after the RPU when NO site information is given' was added inside 'createFromWizardShell — explicit existing site', which is the opposite of what this test covers. A reader scanning describe-block headings will miss this coverage, defeating the discoverability goal. Should be its own block, e.g. 'createFromWizardShell — no site information (RPU-named fallback)'.

high

enableMonitoringShell integration tests likely broken — invisible to CI

apps/platform/src/__tests__/integration/utility-contracts-api.test.ts:375

The beforeAll at line 375 creates a contract with enableMonitoring:false and no site info. After the earlier branch commit (345f42e2) made createFromWizardShell always create a site, that contract now has a site. The new 409 guard added in this commit fires when hasPath1Access=true — so every subsequent enableMonitoringHandler call in this describe block gets 409 instead of 200. The file is not in CI's explicit run list (only public-v1-idor, api-telemetry-persistence, rls-machine-claim), so the breakage is invisible. No test exists for the 409 path of enableMonitoringShell.

medium

skippedUnsigned counter has no dispatch-level assertion

packages/event-bus/__tests__/unit/webhook-dispatcher.sealing-key.test.ts:1

The new skippedUnsigned field in DispatchSummary is incremented when open() returns null inside the handler's endpoint loop. The sealing-key test file only exercises loadSealingKey in isolation — it never instantiates the handler or asserts that skippedUnsigned propagates to the summary. The counter could silently revert to 0 without breaking any test — defeating the 'no longer looks like an idle bus' purpose of the field.

medium

SecretBinary test asserts null via the wrong mechanism

packages/event-bus/__tests__/unit/webhook-dispatcher.sealing-key.test.ts:118

The test mocks {SecretBinary: new Uint8Array(32)} — so SecretString is undefined. The code computes raw = res.SecretString ?? '' = '', and Buffer.from(''.trim(), 'base64') produces a 0-byte buffer failing the 32-byte length check. The test returns null for the wrong reason: it would return null even if the code were changed to never check SecretBinary at all. A stronger test would pass a non-empty SecretString AND a SecretBinary and assert only the SecretString path is used.

low

CI db-changes filter misses handler-only changes to enableMonitoringShell's callers

.github/workflows/pr-checks.yml:176

The db-changes regex includes domains/cross-domain/ so contract-wizard changes trigger the cross-domain suite. But apps/platform/src/api/handlers/ is not in the filter — a change to utility-contracts.handler.ts routing enableMonitoringShell would not trigger the suite. Minor coverage gap for handler-layer-only refactors.

info

No enableMonitoringShell 409 integration test exists anywhere

domains/cross-domain/src/__tests__/contract-wizard-sites.integration.test.ts:1

The new guard (409 when contract already has a site) is not covered by any integration test. Per testing patterns, shell error flows should have integration tests. The guard is simple and a mutation would be caught eventually at runtime, but a targeted integration test would give CI-level confidence.

improvement3

low

Stale JSDoc on cachedSealingKey contradicts new transient semantics

packages/event-bus/src/handlers/webhook-dispatcher.lambda.ts:210

The comment still says 'null is cached too, so a misconfigured environment costs one lookup'. After this fix, null is only cached for deterministic failures — transient errors return null without caching. The declaration-site comment should be updated to reflect the new two-case distinction.

low

transient flag can be replaced with an early return in the catch block

packages/event-bus/src/handlers/webhook-dispatcher.lambda.ts:249

A `return null` inside the catch block is exactly equivalent and removes the need for the boolean flag. Current: set transient=true in catch, then `if (transient) return null` after the block closes. Simpler: `catch (err) { console.error(...); return null; }`. No behavioural change.

info

REFUSING to purge grep anchor is robust — confirmed

scripts/db/rehearse-upgrade.sh:139

The migration RAISE format produces 'REFUSING to purge N webhook endpoint(s)...' — grep -q 'REFUSING to purge' matches correctly. Not fragile.

History · 47 commits

  1. 82bb5b9blockedincremental5H · 5M · 4L2026-08-12 01:48
  2. 90aa3d5needs attentionincremental1H · 5M · 3L2026-08-11 19:37
  3. 29d19a0needs attentionincremental1H · 5M · 9L2026-08-11 17:41
  4. 9bd8a0cneeds attentionfull0H · 5M · 9L2026-08-11 02:14
  5. 62ec3f7needs attentionincremental2H · 5M · 6L2026-08-10 22:51
  6. f93bca9needs attentionincremental2H · 5M · 8L2026-08-10 17:51
  7. 052db6fneeds attentionincremental1H · 3M · 4L2026-08-09 21:13
  8. 45699caneeds attentionincremental0H · 7M · 11L2026-08-09 17:44
  9. b843d8aneeds attentionincremental1H · 7M · 9L2026-08-09 04:05
  10. e1757b8needs attentionincremental0H · 3M · 6L2026-08-05 02:11
  11. 7a762faneeds attentionincremental2H · 5M · 5L2026-08-05 01:25
  12. 3300a60needs attentionincremental2H · 4M · 7L2026-08-04 19:06current
  13. 0c8a7f5needs attentionincremental0H · 4M · 9L2026-08-04 18:15
  14. 345f42eneeds attentionincremental2H · 6M · 9L2026-08-04 17:28
  15. 8338a9aneeds attentionincremental5H · 14M · 14L2026-08-04 00:33
  16. 41be4c3needs attentionincremental0H · 5M · 7L2026-08-03 23:49
  17. 5ed593dneeds attentionincremental1H · 6M · 6L2026-08-03 21:32
  18. b333e25needs attentionincremental4H · 9M · 8L2026-08-03 21:00
  19. 5642cccneeds attentionincremental2H · 3M · 2L2026-08-03 20:17
  20. 73b0b39needs attentionincremental3H · 10M · 13L2026-07-31 18:29
  21. b19852eneeds attentionincremental0H · 1M · 5L2026-07-29 05:04
  22. 3845205needs attentionincremental3H · 6M · 4L2026-07-29 04:47
  23. eb8eb50needs attentionincremental0H · 1M · 2L2026-07-29 03:03
  24. f4720a3needs attentionincremental6H · 8M · 7L2026-07-29 02:54
  25. f8d341ablockedincremental2H · 2M · 5L2026-07-29 00:00
  26. a7f1a64needs attentionincremental2H · 8M · 8L2026-07-28 18:41
  27. 738b60bblockedincremental3H · 6M · 5L2026-07-28 00:46
  28. 2c248b6needs attentionincremental8H · 12M · 8L2026-07-27 23:23
  29. 1346cc0needs attentionincremental2H · 8M · 6L2026-07-27 20:15
  30. 0716018needs attentionincremental2H · 11M · 12L2026-07-27 19:22
  31. 215cd2dneeds attentionincremental3H · 6M · 5L2026-07-27 17:04
  32. ec46958needs attentionincremental0H · 3M · 5L2026-07-27 16:51
  33. de7b337blockedincremental4H · 9M · 14L2026-07-27 06:36
  34. b1bb9c0needs attentionincremental1H · 2M · 4L2026-07-27 05:09
  35. 4701d11needs attentionincremental0H · 4M · 3L2026-07-27 04:44
  36. e1626c4needs attentionincremental3H · 9M · 10L2026-07-27 03:21
  37. 195f198needs attentionincremental3H · 3M · 3L2026-07-25 01:22
  38. 42c7358safeincremental0H · 0M · 0L2026-07-22 20:46
  39. 85b9018needs attentionincremental0H · 1M · 6L2026-07-21 23:51
  40. a7b2a9aneeds attentionincremental0H · 9M · 12L2026-07-21 18:49
  41. c2ee0daneeds attentionincremental4H · 7M · 7L2026-07-21 02:17
  42. e8ffa5eneeds attentionincremental4H · 7M · 5L2026-07-21 01:33
  43. a2d2a54needs attentionincremental2H · 7M · 3L2026-07-21 00:51
  44. 576fbd6needs attentionfull1H · 6M · 7L2026-07-21 00:35
  45. d3465e8needs attentionincremental1H · 7M · 10L2026-07-21 00:23
  46. dc794a7needs attentionincremental0H · 5M · 5L2026-07-20 23:46
  47. 9082773needs attentionfull1H · 3M · 3L2026-07-20 23:13