← all branches

feat/discovery

needs attention
c1fe7b7 · incrementalPR #305reviewed 2026-07-27 06:02 UTC3H · 4M · 3L · 2I
The branch
Purpose
Contextual feature discovery for existing Batu platform users: ambient spotlights, a Descubre hub, and a first-run linear product tour. Flag-gated (feature-discovery-spotlights, OFF by default).
Goal
Ship Feature Discovery v1 — compute discovery gaps on read (no materialized tracker), pace spotlights ≤1/rolling-day with retire-on-dismiss, provide a self-serve Descubre hub, and a fixed 5-step first-run tour.
Sub-goals
  • discovery_state table (migration 0058, user-scoped RLS) — FCIS throughout
  • Ambient spotlight engine: DiscoveryFCIS, pure decisions, read-coordinator shell
  • Descubre hub component in header with badge + gap listing
  • First-run linear tour: 5 steps, dimmed scrim + spotlight cutout, 1/N progress
  • JSend API surface + es/en i18n + PostHog analytics
  • Adversarial review applied: prod-env bypass fix, spotlight self-destruct fix, atomic upsert, gate-key retirement exemption
The changes (whole branch)
What
This incremental window is a merge of main into feat/discovery, bringing in One-API groundwork: consolidated PublicIdPrefix registry (shared-kernel/ids.ts), new route-meta.ts export in packages/api, and a comment fix in database/schema/index.ts.
Why
Merge main to pick up the id-prefix SSOT (one-api.md) before the feature branch ships — ensures discovery_state uses 'dst' from the canonical registry rather than a local constant.
Areas
apps/platform+254252domains/cross-domain+16040domains/core+4980packages/secrets+15212packages/database+1381packages/api+1030packages/analytics+980e2e/platform+610scripts+300packages/shared-kernel+10
Blast
64 files, +5227/-65 (branch vs main). Core areas: apps/platform (discovery UI + API handlers), domains/cross-domain (coordinator shells), domains/core (discovery entity + decisions). One new migration (0058). No infra changes.
flag-gated: feature-discovery-spotlights (OFF by default — deploy preconditions required before flip)
CI checks· No check run data available for this SHACodeRabbit· No .coderabbit.yaml in repo

Findings · 13

correctness2

high

'i' flag in publicIdRegex makes prefix case-insensitive — 'CFJ_xxx' matches

packages/shared-kernel/src/types/ids.ts:78

The 'i' flag is applied to the full regex `^${prefix}_${ULID_BODY_PATTERN}$`. Since the prefix is lowercase (e.g. 'cfj'), 'i' causes 'CFJ_...', 'Cfj_...' etc. to pass wire validation. The docstring says this flag is for accepting lowercase ULID bodies (backward compat), but it silently widens the prefix contract too. Fix: include lowercase chars explicitly in ULID_BODY_PATTERN (`[0-9A-HJKMNPa-hjkmnptv-zTV-Z]{26}`) and drop the 'i' flag, or document that uppercase-prefix tolerance is intentional.

low

isValidPublicId() accepts non-registry prefixes — diverges from publicIdRegex

packages/shared-kernel/src/types/ids.ts:125

isValidPublicId() uses /^[a-z]{3}_[0-9A-HJKMNP-TV-Z]{26}$/ — accepts any 3-char lowercase prefix, including 'aaa_...'. This is intentional for a format-only check, but it diverges from publicIdRegex which enforces a specific registry prefix. The function's JSDoc should explicitly state it's a format check only (not a registry membership check) to prevent misuse as an authorization guard.

security1

info

No injection risk — publicIdRegex inputs are compile-time constants

packages/shared-kernel/src/types/ids.ts:78

prefix parameter is typed as PublicIdPrefix (union of literal strings). No user-controlled input flows into the RegExp constructor. Safe.

conventions4

high

Stale 'cjb_' JSDoc in event-schemas.ts — contradicts canonical registry

services/utility/bills/cfe/src/domain/event-schemas.ts:48

JSDoc on JobCreatedEventDataSchema.publicId still says 'cjb_...' as the CfeJob prefix. The canonical value is now 'cfj' in both the registry and the DB schema. Misleads any reader auditing the id registry and will cause confusion when event-schemas are eventually tightened to use publicIdRegex.

medium

JSDoc claims 'retired copies re-export this constant' — unverified by diff

packages/shared-kernel/src/types/ids.ts:14

The new JSDoc states domains/utility/src/schema/base.ts enum and packages/api/src/schemas/common.schemas.ts map 're-export this constant'. If those files still carry independent values (even if identical), the SSOT claim is aspirational rather than structural. The diff does not show those files being updated to delegate to PublicIdPrefix.

low

one-api.md reference in JSDoc lacks explicit path

packages/shared-kernel/src/types/ids.ts:10

The JSDoc references 'one-api.md' without a path. The same PR's change to database/schema/index.ts shows the pattern: '.claude/rules/contracts-read-model.md'. Should be '.claude/rules/one-api.md' for consistency.

info

Comment update in database/schema/index.ts is correct

packages/database/src/schema/index.ts:119

Updating the ADR path to .claude/rules/contracts-read-model.md aligns with the Tier 5 retirement rule in CLAUDE.md. No issue.

tests4

high

publicIdRegex() has no unit tests

packages/shared-kernel/src/types/ids.ts:72

New exported wire-validation utility with non-trivial behavior (flag handling, ULID body composition, case-sensitivity toggle). Already consumed by job.public-schemas.ts for production wire validation. Missing tests: correct prefix match, wrong prefix rejection, 'i' flag lowercase acceptance, invalid format rejection. A bug here silently breaks schema enforcement across all public-API schemas.

medium

~6 test files and seed data carry stale 'cjb_' prefix strings

domains/utility/src/cfe-job/__tests__/cfe-job.decisions.test.ts:56

Affected files: cfe-job.decisions.test.ts (L56,78,162,171), credit-metering.decisions.test.ts (L97,105), event-schemas.test.ts (multiple), seed-utility-demo.ts (L1352,1363,1396). Tests pass today because decision functions treat IDs as opaque strings (no regex validation). But the test data is inconsistent with the enforced wire schema (cfj_*), and any future validation tightening will cause silent failures.

medium

Seed data 'cjb_*' IDs may break integration tests validating response schemas

packages/database/src/seed-utility-demo.ts:1352

seed-utility-demo.ts seeds cfe_jobs with publicId = 'cjb_001_test_queued' etc. PublicJobIdSchema now enforces cfj_* via publicIdRegex. Integration tests that seed from this file and inspect job IDs in API responses risk failing schema validation or asserting on wrong prefix values.

medium

No regression test pinning PublicIdPrefix.CfeJob === 'cfj'

packages/shared-kernel/src/types/ids.ts:56

The CfeJob prefix was corrected from 'cjb' to 'cfj' with a comment citing production data. No snapshot or assertion test pins this value. An accidental revert would propagate to wire validation (PublicJobIdSchema) without a test-layer catch.

improvement2

medium

'i' flag footgun — callers must know to pass it for wire schemas

packages/shared-kernel/src/types/ids.ts:64

The docstring says 'public-v1 wire schemas pass i' but there's no named constant or wrapper to enforce this. A caller that forgets the flag silently gets a stricter schema. Consider exporting a PUBLIC_V1_REGEX_FLAGS = 'i' constant or two wrappers (publicIdRegexStrict / publicIdRegexWire) to make the pit of success explicit.

low

MonitoringSubscription prefix 'sub' is ambiguous across domains

packages/shared-kernel/src/types/ids.ts:54

'sub' for MonitoringSubscription could collide conceptually with Stripe subscriptions or other subscription entities. 'mns' or 'mons' would be more domain-specific. No current collision in the registry.

History · 36 commits

  1. c1fe7b7needs attentionincremental3H · 4M · 3L2026-07-27 06:02current
  2. 414db56safeincremental0H · 0M · 2L2026-07-23 00:52
  3. ea68968needs attentionincremental0H · 4M · 7L2026-07-22 23:01
  4. 4bdb2c9needs attentionincremental1H · 3M · 5L2026-07-21 23:22
  5. 6e3f255safeincremental0H · 0M · 0L2026-07-20 16:46
  6. cc41079blockedincremental5H · 5M · 3L2026-07-20 16:25
  7. 2451ee0needs attentionincremental0H · 5M · 6L2026-07-20 16:09
  8. ab7c103needs attentionincremental0H · 1M · 6L2026-07-20 15:52
  9. 7eff611needs attentionincremental0H · 1M · 2L2026-07-18 00:41
  10. 246c67csafeincremental0H · 0M · 0L2026-07-17 23:58
  11. 7521b17safeincremental0H · 0M · 0L2026-07-17 23:35
  12. 1847ec8needs attentionincremental0H · 2M · 4L2026-07-17 23:28
  13. 5d3175cneeds attentionincremental3H · 6M · 4L2026-07-17 19:31
  14. d3f875dneeds attentionincremental1H · 2M · 4L2026-07-17 18:01
  15. 47d25faneeds attentionincremental1H · 1M · 2L2026-07-17 00:46
  16. 440d838needs attentionincremental4H · 9M · 9L2026-07-17 00:27
  17. 7466f97needs attentionincremental0H · 1M · 5L2026-07-16 23:20
  18. b686d58needs attentionincremental0H · 2M · 2L2026-07-16 14:24
  19. 9ea1446blockedincremental2H · 7M · 10L2026-07-16 13:44
  20. 6f39bb9needs attentionincremental0H · 2M · 7L2026-07-14 21:53
  21. 2eadf2aneeds attentionincremental0H · 1M · 2L2026-07-14 20:12
  22. 97bd08fneeds attentionincremental0H · 2M · 5L2026-07-14 19:40
  23. a48e3ffneeds attentionincremental5H · 8M · 6L2026-07-14 18:22
  24. 69473b7needs attentionincremental2H · 5M · 3L2026-07-14 01:34
  25. 92e4235needs attentionincremental2H · 1M · 3L2026-07-14 01:04
  26. 6be8018safeincremental0H · 0M · 3L2026-07-14 00:15
  27. 090b4daneeds attentionincremental1H · 3M · 5L2026-07-13 23:50
  28. fa7683bneeds attentionincremental1H · 4M · 3L2026-07-13 18:55
  29. d64cd72needs attentionincremental2H · 3M · 2L2026-07-13 16:27
  30. c1f337bneeds attentionincremental3H · 7M · 14L2026-07-13 13:30
  31. 46e32b2safeincremental0H · 0M · 3L2026-07-11 02:54
  32. 3b30949needs attentionincremental0H · 2M · 3L2026-07-11 02:39
  33. 9f8dbdfneeds attentionincremental3H · 5M · 5L2026-07-11 02:32
  34. 23191eeneeds attentionincremental5H · 12M · 7L2026-07-11 02:18
  35. fa9b88fneeds attentionincremental0H · 1M · 2L2026-07-11 01:30
  36. 6a8bf42blockedfull8H · 11M · 8L2026-07-11 01:08