← all branches

feat/one-api

needs attentionviewing older commit
8338a9a · incrementalpre-PRreviewed 2026-08-04 00:33 UTC5H · 14M · 14L · 4I
The branch
Purpose
feat/one-api establishes a unified public-v1 API surface with machine caller isolation (service-key/API-key auth unified under one table, kind discriminator), per-request telemetry, and row-level security for the full read path — enabling multi-tenant safe machine-to-machine access to the Batu platform API.
Goal
Bring the public API and internal mounts to a state where machine callers (service tokens and API keys) get database-enforced tenant isolation via RLS, with reads migrated one capability at a time after removing the blockers. This commit removes the entity_relationships access path — the blocker that made bill/contract reads impossible to run under RLS without silently returning fewer rows.
Sub-goals
  • SG-1a: platform-scoped service actor — JWT, AuthContext, gate
  • SG-1b: ServiceKey entity + service_keys table
  • SG-2: mint service tokens from /v1/auth/token
  • SG-3: internal mount dual-accepts service tokens (W2 expand)
  • Seal webhook signing secret at rest
  • Measure public API — one telemetry record per request
  • Persist request telemetry so the baseline survives
  • Machine callers get database-enforced tenant isolation (RLS)
  • Run public reads under row-level security
  • Run asset-management reads under row-level security
  • Delete entity_relationships access path (this commit) — enables bill/contract reads under RLS
The changes (whole branch)
What
Removes the entity_relationships secondary access path from 4 call sites: accessible-contracts.ts (public-v1 SSOT), bills.handler.ts (getAccessibleContracts), utility-contracts.handler.ts (contractFacetsHandler), and csv/route.ts (bill export). Updates docs (.claude/rules/rls-checklist.md, domains/utility/CLAUDE.md, auth-implementation/SKILL.md, cfe-portfolio-diagnostic/SKILL.md) to mark the SG-19 gap as CLOSED and document that the entity_relationships path reached zero live contracts in production.
Why
Migration 0036_rescope_stage_a.sql rescoped the RLS policies for bills/bill_files/utility_contracts to resolve tenancy through site_utility_contracts → sites.org_id only — the application filter was still UNIONing a second entity_relationships path the policies never covered. This made the app filter BROADER than the database policy, which is what caused the RLS wrapping attempts to silently return fewer rows. Production audit: 10,847 of 10,908 contracts resolve via sites; the 2 live contracts only reachable via entity_relationships were failed onboarding attempts and were soft-deleted. PATH 2 then reached zero live contracts and was removed.
Areas
apps/platform+167265domains/core+1365139packages/database+6833411packages/api+65184.claude/rules+3159docs/security+3180docs/design+1910domains/utility+4610.github/workflows+905
Blast
94 files across apps/platform, domains/core, packages/api, packages/database (migrations + RLS), .claude/rules (updated), docs/security. The incremental commit touches 9 files: 4 handlers/utils for the PATH 2 removal and 5 doc/rule files. +361 lines of meaningful code (excluding 68k migration adds) and -161.
contract-access.ts and checkContractExistsHandler still consult entity_relationships for point-lookup mutations — not updated in this commit RLS migration for bills/contracts reads not yet done (explicitly deferred as next step) Dead ContextFCIS imports remain in bills.handler.ts and csv/route.ts
github-ci· No PR open — CI signal not availablecoderabbit· No .coderabbit.yaml found

Findings · 37

correctness5

medium

contract-access.ts still consults entity_relationships for per-contract mutations

apps/platform/src/api/utils/contract-access.ts:70

resolveContractAccess() still calls hasContractRelationshipForOrgs. A user cannot list a PATH-2-only contract but CAN reach it via a direct publicId mutation probe. Zero live contracts today, but the path is live.

medium

checkContractExistsHandler wizard probe still uses entity_relationships

apps/platform/src/api/handlers/utility-contracts.handler.ts:620

A user would be told 'Este RPU ya está registrado en tu organización' for an RPU they cannot read via list/bill endpoints, causing a confusing dead-end in the onboarding wizard.

low

Stale comment at bills.handler.ts:264 says showAll=true fetches PATH 2 contracts

apps/platform/src/api/handlers/bills.handler.ts:264

Downstream comment not updated with the PATH 2 removal 12 lines earlier.

low

Multiple SG-19 comments in bills.handler.ts describe closed gap as still open

apps/platform/src/api/handlers/bills.handler.ts:177

Lines 177-182, 204-208, 340, 447 reference the old entity_relationships rationale.

info

getAccessibleContractPublicIds @returns JSDoc still mentions PATH 2

packages/api/src/auth/accessible-contracts.ts:46

@returns tag says 'across PATH 1 + PATH 2' — PATH 2 is gone.

security6

medium

Stale showAll contract comment: still documented as enabling PATH 2

apps/platform/src/api/contracts/bills.contract.ts:199

showAll documented as 'include PATH 2 (entity_relationship) contracts'. A developer could re-introduce entity_relationships access under showAll to 'restore the documented behavior'.

medium

contract-access.ts entity_relationships fallback open for per-contract mutations

apps/platform/src/api/utils/contract-access.ts:70

User cannot SEE a PATH-2-only contract in any list, but CAN reach it via a direct publicId probe through mutation surface. Moot with zero live contracts today.

low

Stale doc comment in utility-contract.queries.ts references PATH 2 as active

domains/utility/src/utility-contract/utility-contract.queries.ts:563

findPublicDetailsByPublicIds comment mentions 'PATH 2 entity_relationships contracts' as expected input.

low

Service-role bypass documented and intentional — no exploit path

packages/api/src/auth/accessible-contracts.ts

orgId derived from JWT membership in all callers. No exploit path given current derivation.

low

2 soft-deleted contracts: accessible via direct publicId if bills exist

apps/platform/src/api/utils/public-v1-bill-read.ts:342

findByPublicId applies isNull(deletedAt). Both contracts had zero bills so moot in practice.

info

Tenant isolation chain correctly scoped in all 4 changed call sites

packages/api/src/auth/accessible-contracts.ts

PATH 1 chain: findByOrgIds scopes by org_id; listContractPublicIdsBySiteIds narrows to site IDs. No cross-org leakage found.

conventions12

high

Stale @returns JSDoc in accessible-contracts.ts still mentions entity_relationships and PATH 2

packages/api/src/auth/accessible-contracts.ts:46

@returns says 'across PATH 1 + PATH 2. Returns [] when the org owns no sites and has no entity_relationships rows'. Both are wrong after this commit.

high

Stale SG-19 comments in bills.handler.ts describe the closed gap as still open

apps/platform/src/api/handlers/bills.handler.ts:177

Lines 177-182 say 'wrapping in createRLSDb returns empty results until the pipeline is fixed'. That rationale is now CLOSED per rls-checklist.md. Same stale text at lines 204, 208, 340-343, 446-449.

high

Orphaned + stale JSDoc blocks before contractFacetsHandler in utility-contracts.handler.ts

apps/platform/src/api/handlers/utility-contracts.handler.ts:130

Two consecutive JSDoc blocks before contractFacetsHandler: one orphaned from listContractsHandler, one for facets that still says 'Merges PATH 1 and PATH 2'. Both need cleanup.

medium

Multiple stale SG-19 comments in utility-contracts.handler.ts describe the closed gap as open

apps/platform/src/api/handlers/utility-contracts.handler.ts:171

Lines 171-174, 590-591, 926-928 describe the entity_relationships gap as open. The chapters handler comment especially misleads — explaining why it cannot wrap in rlsDb with the now-closed rationale.

medium

Stale showAll comment in listBillsHandler says 'also fetch PATH 2 contracts'

apps/platform/src/api/handlers/bills.handler.ts:264

Comment directly contradicts the code — showAll no longer triggers PATH 2.

medium

enableMonitoringHandler JSDoc still describes PATH 2 contracts as the handler's purpose

apps/platform/src/api/handlers/utility-contracts.handler.ts:753

Says 'Enables monitoring for a contract that doesn't have a site yet (PATH 2 contracts)' and 'Removes the direct org → contract entity_relationship'. Both stale.

medium

Dead parameter includeAllPaths in csv/route.ts getAccessibleContracts

apps/platform/src/app/api/bill-export/csv/route.ts:159

includeAllPaths is accepted but silently ignored. Caller passes body.showAll ?? false into a dead param.

medium

Dead ContextFCIS import in bills.handler.ts and csv/route.ts

apps/platform/src/api/handlers/bills.handler.ts:15

ContextFCIS imported but no longer used in bills.handler.ts and csv/route.ts after PATH 2 removal. Will produce lint/build warnings.

medium

Double blank lines in 3 files after PATH 2 deletion

packages/api/src/auth/accessible-contracts.ts:77

Whitespace scar from removing PATH 2 block — 3 occurrences in accessible-contracts.ts, bills.handler.ts, csv/route.ts.

low

accessType: 'bart' comment in createBillHandler is stale terminology

apps/platform/src/api/handlers/bills.handler.ts:137

'BART' was the legacy name for the entity_relationships path.

low

RLS checklist 'Contract-derived' predicate description is accurate

.claude/rules/rls-checklist.md:63

Informational — no change needed.

low

Large comment block in accessible-contracts.ts appropriate per the WHY rule

packages/api/src/auth/accessible-contracts.ts:54

Security-relevant audit trail. Length is justified.

tests7

high

CSV export: includeAllPaths accepted but silently ignored — no test

apps/platform/src/app/api/bill-export/csv/route.ts:159

showAll=true is passed to a dead param that no longer does anything. A caller expecting wider contract access gets silently narrower results. No test covers this route at all.

high

contractFacetsHandler: no test verifying entity_relationships-only contracts are excluded

apps/platform/src/api/handlers/utility-contracts.handler.ts:148

No integration test seeds a PATH-2-only contract and asserts absence from the facets response.

medium

showAll=true behavioral change in bills.handler.ts is untested

apps/platform/src/api/handlers/bills.handler.ts:261

showAll previously triggered PATH 2; now only controls isMonitored default filter. No test verifies it no longer expands the contract set.

medium

listContractPublicIdsBySiteIds does not filter soft-deleted contracts — no test

domains/utility/src/site-utility-contract/site-utility-contract.queries.ts:218

Query joins siteUtilityContracts → utilityContracts with no isNull(deletedAt) predicate. No test seeds a soft-deleted site-linked contract and asserts exclusion.

medium

No test asserts bills/utility_contracts reads work correctly under RLS

apps/platform/src/__tests__/integration/rls-machine-claim.test.ts

Commit message says 'reads can now run under RLS' but no test validates the positive path. Prior attempt 976c2a90 failed silently by returning empty rows.

low

Positive read-isolation test correctly present in IDOR harness

apps/platform/src/__tests__/integration/public-v1-idor.test.ts:537

Test at lines 537-543 directly calls getAccessibleContractPublicIds for both orgs, asserting cross-org isolation. Good coverage for the primary regression risk.

info

Stale inline comment at bills.handler.ts:264 describes removed PATH 2

apps/platform/src/api/handlers/bills.handler.ts:264

Correctness risk for future maintainers, not a test gap.

improvement7

medium

Dead parameter includeAllPaths in csv/route.ts getAccessibleContracts

apps/platform/src/app/api/bill-export/csv/route.ts:159

Should be removed from signature and call site.

low

Stale JSDoc @returns on getAccessibleContractPublicIds still mentions PATH 2

packages/api/src/auth/accessible-contracts.ts:46

Module-level comment was updated but function-level JSDoc was not.

low

Multiple admin-required: SG-19 comments give wrong rationale in bills.handler.ts

apps/platform/src/api/handlers/bills.handler.ts:177

Lines 177-182, 204-208, 338-342 reference old entity_relationships rationale.

low

Dead import: ContextFCIS unused in bills.handler.ts and csv/route.ts

apps/platform/src/api/handlers/bills.handler.ts:15

Will produce lint/build warnings.

low

Leftover double blank line in utility-contracts.handler.ts

apps/platform/src/api/handlers/utility-contracts.handler.ts:179

After removing entityContracts PATH 2 block.

low

Three divergent getAccessibleContracts implementations — consolidation opportunity

packages/api/src/auth/accessible-contracts.ts:50

All three now do the same PATH 1 core. Module comment correctly notes they're not unified. A shared helper would reduce duplication.

info

Comment verbosity in accessible-contracts.ts function body appropriate given stakes

packages/api/src/auth/accessible-contracts.ts:54

10-line production-stats comment explaining WHY PATH 2 was removed. Security-relevant audit trail. Length justified.

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:06
  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:33current
  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