← all branches

feat/one-api

needs attentionviewing older commit
195f198 · incrementalPR #326reviewed 2026-07-25 01:22 UTC3H · 3M · 3L · 2I
The branch
Purpose
Establish the One-API program: one canonical contract tree where audience (public/internal/admin) is metadata on a route, not a separate mount. Ratified in-session 2026-07-20 by Alex/CTO.
Goal
Land the One-API SSOT rule, W1 groundwork (route inventory + merged id registry), RouteMeta machinery, and three P0 security gates (withMetaGate, catalog-admin gate, two-org IDOR harness) that make the unified public surface enforceable.
Sub-goals
  • SG-1: one-api.md SSOT rule — decision, wire grammar, operation grammar, canonical contract form, wave plan
  • SG-2: classify-routes.ts → route-inventory.json: 250 routes classified, dual map = 14 audit routes
  • SG-3: shared-kernel sole 55-entity PublicIdPrefix registry; cjb→cfj; publicIdRegex; integrity test
  • SG-4: ADRs 016/018/020 distilled into .claude/rules/; docs/ADRs/ retired
  • SG-5: @batu/api/meta/route-meta — RouteMeta type/builder/schema/scope-helpers; all 48 public-v1 routes stamped; guard test
  • SG-6 (BAT-274): withMetaGate = one enforcement point at public mount — credential class + machine scopes from RouteMeta; fail-closed
  • SG-7 (BAT-273): 12 catalog write exports gated isPlatformAdmin before validation; regression suite
  • SG-8 (BAT-275): two-org IDOR harness over all 48 routes via real gated dispatch map; local ES256 token mint; construction-enforced coverage
The changes (whole branch)
What
Incremental: reconcile audit log table + nightly pg_cron wrapper (runOverviewReconcile), two new display columns on utility_contract_overview (tariffCode/utilityServiceCode for rate-tag chip), findOverviewByContractPublicId org-access gate query, doc updates to contracts-read-model.md and CLAUDE.md branch naming (16 chars), branch-provisioning rm fix.
Why
Parallel utility domain work landing alongside One-API: the overview read-model gains its scheduled reconcile-and-repair (deferred from the original trigger-maintenance PR), two columns needed for the agreement-regime rate-tag chip, and the new findOverviewByContractPublicId query gates contract detail reads without a raw UUID lookup.
Areas
apps/platform+1103144packages/api+26566packages/shared-kernel+7722.claude/rules+69110packages/database/src/schema+42packages/database/drizzle+00domains/utility/src/utility-contract-overview+31domains/core/src/api-key+246scripts/one-api+60360
Blast
~40 files, +3000/−250 lines; touches all 48 public-v1 routes (RouteMeta stamps), security middleware (withMetaGate), shared-kernel ID types (PublicIdPrefix), utility-contract-overview read-model (schema + queries + new reconcile audit table).
public-v1 all 48 routes re-stamped with RouteMeta — any meta-gate misconfiguration is a security regression runOverviewReconcile uses pg_advisory_xact_lock + CREATE TEMP TABLE — must never be called inside an outer transaction bill_count RPU union is cross-org by design per PR description — but may be a UX mismatch vs org-scoped Historial
ci· GraphQL check rollup unavailable via personal tokencoderabbit· no .coderabbit.yaml in repo

Findings · 11

correctness3

high

Missing composite index on (org_id, contract_public_id) — full org-scan on a hot gate path

packages/database/src/schema/utility-contract-overview.ts

findOverviewByContractPublicId filters WHERE org_id = $1 AND contract_public_id = $2. The only available index is idx_utility_contract_overview_org (org_id only), so Postgres index-seeks on org_id then heap-scans every row for that org to evaluate contract_public_id. For large orgs (hundreds/thousands of contracts) this is O(contracts_per_org) on every contract detail open. contract_public_id is effectively unique per org (it mirrors utility_contracts.public_id, a ULID UNIQUE key). Add a composite unique index on (org_id, contract_public_id) — serves as both a constraint guarantee and an index-only lookup.

medium

bill_count RPU union counts bills from all orgs sharing contract_number

packages/database/drizzle/0063_youthful_martin_li.sql

The bc LATERAL in the updated derive() counts bills across ALL utility_contracts sharing contract_number with no org-scoping: FROM utility_contracts sib JOIN bills b ON b.utility_contract_id = sib.id WHERE sib.contract_number = uc.contract_number AND sib.deleted_at IS NULL. Since the same RPU can be held by multiple orgs (ownership transfers, multiple integradores), Org A's displayed bill_count includes bills collected by Org B. The comment says it should match 'Historial filtered by RPU', but Historial IS org-scoped via RBAC — a user cannot reconcile the displayed count with what they can actually see.

info

uco_on_bills_delete trigger sibling-chapter fanout — soft-deleted bill edge case noted

packages/database/drizzle/0063_youthful_martin_li.sql

In uco_on_bills_delete, the chapters CTE unions affected contracts with sib.deleted_at IS NULL siblings. A bill on a soft-deleted/terminated chapter will correctly trigger refresh of the active chapter via uco_orgs_reaching (no deleted_at filter on initial old_rows select). No bug found — noted for completeness during the bill_count RPU union analysis.

conventions3

high

runOverviewReconcile throws instead of returning Result<T,E>

domains/utility/src/utility-contract-overview/utility-contract-overview.queries.ts

FCIS (ADR-016) requires all fallible operations to return Result<T,E> from @batu/result — never throw. runOverviewReconcile throws a raw Error when the SQL function returns no row, and returns OverviewReconcileSummary bare on success. The comment justifies 'surface loudly', but callers (EventBridge Lambda, manual CLI) must now wrap in try/catch instead of pattern-matching on a discriminated error. Wrap in Result<OverviewReconcileSummary, InfraError> with a '_tag' so the FCIS contract is preserved and the error is attributable.

medium

runOverviewReconcile belongs in shells.ts, not queries.ts

domains/utility/src/utility-contract-overview/utility-contract-overview.queries.ts

Canonical form (ADR-018): query functions take DbOrTx and return Entity|null; shells take Database when they own their transaction boundary (advisory locks + temp tables). runOverviewReconcile is typed Database precisely because it owns its transaction — that is the hallmark of a shell. It should live in utility-contract-overview.shells.ts, keeping queries.ts as pure thin DB wrappers.

low

OverviewReconcileSummary exported as loose identifier, not grouped with FCIS namespace

domains/utility/src/utility-contract-overview/index.ts

The canonical barrel exports an {Entity}FCIS namespace (UtilityContractOverviewFCIS). OverviewReconcileSummary is the return type of runOverviewReconcile — it should be grouped alongside or inside that namespace for discoverability. Currently exported as a standalone top-level re-export alongside the namespace, which is consistent with other summary types but diverges slightly from the 'sole import surface is the FCIS namespace' intent.

tests2

high

findOverviewByContractPublicId has zero test coverage

domains/utility/src/utility-contract-overview/utility-contract-overview.queries.ts

This function is an org-access gate for the contract detail drawer — a bug in either WHERE condition (orgId OR contractPublicId) is a direct IDOR. No test file exercises it. Three cases are missing: (1) matching row returned when both conditions hold; (2) null returned when contractPublicId belongs to a different orgId (the IDOR case); (3) null returned when contractPublicId does not exist. Should be integration-tested with two orgs and shared RPU.

low

No isolated mapper test for new tariffCode / utilityServiceCode fields

domains/utility/src/utility-contract-overview/utility-contract-overview.mapper.ts

The two new columns are structurally guarded by AssertEqual<DrizzleRow, DomainType> and exercised end-to-end in the rpu-history integration suite, but there is no targeted unit assertion that toUtilityContractOverview correctly passes tariffCode and utilityServiceCode through (including null values). Low priority given the compile-time guard, but a mapper round-trip test would catch a future copy-paste error without requiring a full DB setup.

improvement3

medium

utilityServiceCode filter has no supporting index

packages/database/src/schema/utility-contract-overview.ts

buildFilterCondition maps the 'utilityService' filter axis to inArray(t.utilityServiceCode, values), but there is no index on (org_id, utility_service_code). Every other filterable column with meaningful cardinality (lifecyclePhase, isMonitored, lastCheckedAt) has a dedicated (org_id, col) composite index. Without one, filtering by utility service code degrades to a post-orgIdx heap scan for large orgs. Add a composite index on (org_id, utility_service_code).

low

branch-provisioning rm list must be kept in sync manually

.claude/skills/branch-provisioning/SKILL.md

Replacing rm -rf .branch with rm -f .branch/scope.md .branch/intent.md .branch/stashes.log is the right fix, but the list is now a manual registry. If a future provisioning step writes a new tracked file under .branch/ (e.g. .branch/decisions.md), it won't be cleared and will silently carry stale content into the next branch. Add a comment: 'keep in sync with every file written by this skill's Initialize branch step'.

info

EventBridge-Lambda fallback documented but unwired

.claude/rules/contracts-read-model.md

contracts-read-model.md states 'EventBridge-Lambda remains the documented fallback if pg_cron is ever unavailable', and the queries.ts comment echoes this. The Lambda does not exist. If pg_cron fails silently (Supabase plan change, extension disabled), the CI watchdog is the only safety net — not a real fallback invocation path. Acceptable as a deferred work item but should be tracked in Linear rather than presented as an existing fallback.

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: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:22current
  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