feat/one-api
needs attentionviewing older commit576fbd6 · fullPR #326reviewed 2026-07-21 00:35 UTC1H · 6M · 7L · 8I- Purpose
- Establish the One-API program's architectural foundation: encode the decision, wire grammar, and operation grammar as machine-readable .claude/ rules (not docs/ADRs), and provide the W1 groundwork every later parity wave builds on.
- Goal
- Classification-only groundwork: a machine-readable route-metadata inventory of all three API surfaces, one merged PublicIdPrefix registry, and ADR retirement. No route behavior changes.
- Sub-goals
- SG-1: .claude/rules/one-api.md as SSOT + canonical-form.md SSOT-precedence rewording
- SG-2: scripts/one-api/classify-routes.ts + route-inventory.json (250 routes, dual-capability map)
- SG-3: shared-kernel PublicIdPrefix registry (55 entities), re-export shims, cjb→cfj, publicIdRegex(), integrity test
- SG-4: Retire docs/ADRs/ (23 files, −12k lines); distill 016/018/020 into owning rules
- SG-N: Untrack .branch/ working files
- What
- New rule files (one-api.md, contracts-read-model.md), updated canonical-form.md and domain-patterns.md, merged PublicIdPrefix registry in shared-kernel with re-export shims across utility schema and api schemas, cjb→cfj rename in DB schema/tests/seeds, publicIdRegex() helper + 6-test integrity suite, route classifier script + emitted inventory JSON, and removal of 23 ADR files.
- Why
- The One-API program needs a single authoritative home for API design decisions and a machine-readable snapshot of the current route surface before W2 convergence work begins. ADRs were retired to reduce documentation drift.
- Areas
- .claude/rules/+232−7packages/shared-kernel/src/+45−12packages/api/src/+28−14scripts/one-api/+5845−0domains/utility/src/+4−8packages/database/src/+6−4docs/ADRs/+0−7660
- Blast
- 90 files, +6303/−13652 lines. Bulk is docs/ADRs/ deletion (−12k) and route-inventory.json (+5.4k generated). Substantive changes: ~260 lines of new/updated rules, ~60 lines of shared-kernel/schema/database changes. No runtime behavior changes.
Findings · 23
correctness4
services/ CFE Lambda still hardcodes cjb prefix
services/utility/bills/cfe/src/domain/constants.ts:205
PUBLIC_ID_PREFIXES.CFE_JOB = 'cjb' disagrees with the now-canonical 'cfj' in PublicIdPrefix.CfeJob. This is a second source of truth that wasn't updated. Any future Lambda code using it will produce the wrong prefix without a type error. Should import from @batu/shared-kernel instead.
Seed file inserts rows with stale cjb_ prefix
packages/database/src/seed-utility-demo.ts:1352
Three demo job rows use hardcoded publicIds with the cjb_ prefix. These are unreachable by the now-generated cfj_ regex in PublicJobIdSchema. Update seed values to cfj_.
publicIdRegex i-flag accepts uppercase prefixes
packages/shared-kernel/src/types/ids.ts:94
The 'i' flag is intended to tolerate lowercase ULID body chars, but also makes the prefix match case-insensitive (e.g. 'BIL_...' would pass). No production code generates uppercase-prefix IDs today, but the contract is looser than stated.
classify-routes.ts misclassifies org-scoped creates as 'intent' (inventory data quality)
scripts/one-api/classify-routes.ts:215
Same root cause as the high finding above — the route-inventory.json W1 snapshot shows incorrect op counts as a result. Since this is 'ratify before W2', the inventory should be regenerated after the fix before wave planning.
security4
REVOKE wording: listing 'authenticated, anon, public' is redundant
.claude/rules/rls-checklist.md
Revoking from 'public' already covers all roles. The explicit enumeration is safe but could mislead a developer into thinking omitting 'public' is OK if authenticated+anon are listed.
publicIdRegex uses unescaped prefix in RegExp constructor
packages/shared-kernel/src/types/ids.ts:95
TypeScript type constrains prefix to [a-z]{3} registry values, so no real injection risk. A defensive escapeRegExp call would future-proof against misuse.
route-inventory.json reveals full internal route surface
scripts/one-api/route-inventory.json:1
Contains proposed auth classifications and all route paths. No secrets; paths are already in TypeScript contracts. Acceptable as a dev-tooling artifact.
isValidPublicId accepts any 3-char prefix (pre-existing)
packages/shared-kernel/src/types/ids.ts:124
Generic validator allows any 3-char prefix; entity-specific validation should use publicIdRegex(). Pre-existing behavior, not introduced here.
conventions5
ontology.md and utility/CLAUDE.md still document cfe-job prefix as cjb_
.claude/rules/ontology.md:87
The entity table documents CfeJob prefix as `cjb_` — contradicts the new registry and will cause any developer/agent reading these auto-loaded rules to use the wrong prefix. Update both to `cfj_`.
services/.../event-schemas.ts JSDoc still references cjb_
services/utility/bills/cfe/src/domain/event-schemas.ts:48
JSDoc `/** Job public ID (cjb_...) */` documents the retired prefix. Update to `cfj_`.
Handler/validation files still use hardcoded prefix regex literals
apps/platform/src/api/utils/asset-management-validation.ts:24
Multiple handler and validation files (asset-management-validation.ts, public-v1-validation.ts, bills.handler.ts, files.handler.ts, etc.) still use hardcoded regex literals instead of publicIdRegex(). The new one-api.md rule prohibits this. W1 scope is classification-only; these should be follow-up items.
cfe-job decision test fixtures use stale cjb_ prefix
domains/utility/src/cfe-job/__tests__/cfe-job.decisions.test.ts:56
Test fixtures use publicId: 'cjb_01HXYZ...' — decisions don't validate prefix format so no test failures, but teaches the wrong prefix to readers. Update to cfj_.
one-api.md Wave-1 single-mount statement could be misread as current state
.claude/rules/one-api.md:104
Wave-2 point #2 says 'one mount (/api/v1)' in present tense but this is a W4 target. Adding '(target: W4)' would prevent misreading.
tests3
publicIdRegex: no test for invalid Crockford Base32 characters
packages/api/src/__tests__/schemas/public-id-prefix-registry.test.ts:55
No assertion that characters outside the Crockford alphabet (I, L, O, U) are rejected. Low risk since generated ULIDs are always valid, but a rejection test would pin the character class exclusion.
publicIdRegex: no anchor smoke test
packages/api/src/__tests__/schemas/public-id-prefix-registry.test.ts:55
No test asserting that a padded string like 'prefix_bil_<ULID>suffix' is rejected. Anchors are present in the implementation; a test would guard against a future refactor removing them.
Re-export shims have no dedicated import smoke test — covered by type system
domains/utility/src/schema/base.ts
A wrong re-export would break at compile time. TypeScript coverage is sufficient.
improvement7
classifyOp misclassifies org-scoped POSTs as 'intent'
scripts/one-api/classify-routes.ts:1275
The condition `segments.some((s) => s.startsWith(':'))` treats any POST to a path containing ANY param as 'intent', even when the param is the org context (e.g. POST /api/organizations/:orgId/sites). True intent verbs live at the LAST segment. This misclassifies ~25 plain create routes as intent ops, distorting the W2/W3 wave scoping that depends on these counts. Fix: check only the last segment for intent-verb matching.
route-inventory.json checked in with no staleness guard
scripts/one-api/route-inventory.json:1
Tracked generated file with a 'do not hand-edit' comment but no CI check that fails on drift and no gitignore rule. Will go stale with every contract change. Options: (a) gitignore + document run command in one-api.md, or (b) CI diff check.
one-api.md rule active before W0 ratification checkpoint
.claude/rules/one-api.md:10
The rule encodes 'ratify with tech leads before Wave-2 work' but is already auto-loading for all api/** work. Agents/developers have no signal whether it's enforced or advisory. Add a one-liner: 'Enforcement: advisory until W0 ratification; Don't items are warnings until then'.
one-api.md 'Don't' rule deferred by 'once it exists' qualifier
.claude/rules/one-api.md:109
'Don't add a route outside the metadata registry (once it exists)' — the metadata registry lands in W2. Until then, agents don't know the interim behavior. Rephrase: 'Until W2, add every new route to route-inventory.json manually'.
HANDLER_SEGMENT_CAPABILITY allocated inside inner loop
scripts/one-api/classify-routes.ts:324
Two-key object literal re-allocated on every method iteration. Should be hoisted to module scope alongside APP_KEY_CAPABILITY and PUBLIC_SEGMENT_CAPABILITY for consistency and clarity.
classify-routes.ts has no tests; op classification logic is non-trivial
scripts/one-api/classify-routes.ts:1264
A few unit tests for classifyOp covering the intent-verb vs org-context-param cases would have caught the POST misclassification. Accepted as a script today, but worth adding if the classifier becomes the SSOT for W2+ tooling.
publicIdRegex creates new RegExp on every call — no memoization
packages/shared-kernel/src/types/ids.ts:94
Allocates a new RegExp per call. Called at module eval time across 6 schema files. Not a hot path, but a module-level Map cache would make repeated calls free and signal that these are stable, reusable patterns.
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:47
- 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:35current
- d3465e8needs attentionincremental1H · 7M · 10L2026-07-21 00:23
- dc794a7needs attentionincremental0H · 5M · 5L2026-07-20 23:46
- 9082773needs attentionfull1H · 3M · 3L2026-07-20 23:13