feat(config): publish env-sourced feature flags via GET /config/features (+ Repeated Word Check backend)#213
Conversation
📝 WalkthroughWalkthroughThis PR adds a feature-flag config endpoint and docs, a new self-settings API with storage and migration support, chapter-assignment language-code propagation, and several seed and bootstrap updates. ChangesIntegrated API and schema updates
Estimated code review effort: 4 (Complex) | ~60 minutes Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
281bb2f to
756bfe1
Compare
|
Slipped in a small unrelated fix while smoke-testing locally (commit The container seeds the DB via The added step runs after Happy to split this into its own PR if you'd prefer it not ride along with the feature-flags change. |
There was a problem hiding this comment.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/domains/projects/chapter-assignments/project-chapter-assignments.service.ts (1)
36-58: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDuplicate mapping logic across two functions.
Both mapping blocks are near-identical and now had to be updated in two places for one field. Consider extracting a shared mapper (e.g.
toChapterAssignmentProgressResponse(info)) to avoid this kind of duplicated maintenance burden going forward.♻️ Suggested consolidation
+function toChapterAssignmentProgressResponse(info: ChapterAssignmentProgressInfo) { + return { + assignmentId: info.assignmentId, + projectUnitId: info.projectUnitId, + status: info.status, + bookNameEng: info.bookNameEng, + chapterNumber: info.chapterNumber, + bibleId: info.bibleId, + bookId: info.bookId, + bookCode: info.bookCode, + sourceLangCode: info.sourceLangCode ?? '', + targetLanguageCode: info.targetLangCode ?? '', + assignedUser: info.assignedUserId + ? { id: info.assignedUserId, displayName: info.assignedUserDisplayName ?? '' } + : null, + peerChecker: info.peerCheckerId + ? { id: info.peerCheckerId, displayName: info.peerCheckerDisplayName ?? '' } + : null, + totalVerses: info.totalVerses, + completedVerses: info.completedVerses, + createdAt: info.createdAt, + updatedAt: info.updatedAt, + submittedTime: info.submittedTime, + }; +}Then replace both
.map((info) => ({...}))blocks with.map(toChapterAssignmentProgressResponse).Also applies to: 175-197
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/domains/projects/chapter-assignments/project-chapter-assignments.service.ts` around lines 36 - 58, The chapter assignment progress response mapping is duplicated in two places, causing repeated maintenance when fields change. Extract the shared object transformation into a single helper such as toChapterAssignmentProgressResponse(info) in project-chapter-assignments.service.ts, using the same field mapping for assignmentId, assignedUser, peerChecker, and the rest, then replace both inline .map((info) => ({ ... })) blocks with .map(toChapterAssignmentProgressResponse).src/domains/projects/chapter-assignments/project-chapter-assignments.types.ts (1)
16-27: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueInconsistent field naming:
sourceLangCodevstargetLanguageCode.Same response schema abbreviates one language code field but spells out the other. Minor readability nit; not blocking since it's already part of the public contract mirrored in
users-chapter-assignments.types.ts.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/domains/projects/chapter-assignments/project-chapter-assignments.types.ts` around lines 16 - 27, The chapter assignment response schema has inconsistent language-code field naming between sourceLangCode and targetLanguageCode. Update the chapterAssignmentProgressResponseSchema in project-chapter-assignments.types.ts to use a consistent naming convention for both fields, and keep the mirrored type in users-chapter-assignments.types.ts aligned so the public contract remains unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/proposals/feature-flags/feature-flags-suggestion.md`:
- Around line 105-112: The example in the feature-flags proposal uses
z.stringbool(), but that API requires Zod 4 while this repo is pinned to
zod@3.25.76. Update the guidance near EN_FEATURE_REPEATED_WORD_CHECK to
explicitly call out the Zod 4 minimum or switch the snippet/reference to zod/v4;
alternatively, replace it with a Zod 3-compatible boolean parsing approach so
the example matches the supported version.
In `@src/db/schema.editor-state-extension.test.ts`:
- Around line 56-70: The current tests only cover the tolerant
userSettingsSchema, so add a case for userSettingsWriteSchema to verify it
rejects malformed input instead of collapsing to {}. Extend the
userSettingsSchema test block in schema.editor-state-extension.test.ts with a
focused assertion on userSettingsWriteSchema, using its existing export name to
check that invalid shapes like a bad checkIgnoredWordPairs value throw or fail
validation as intended.
In `@src/domains/self/settings/self-settings.service.ts`:
- Around line 34-35: The schema-validation branch in self-settings.service
should not return INVALID_REFERENCE when safeParse fails. Update the handling
around userSettingsWriteSchema.safeParse in the self settings flow to return
VALIDATION_ERROR instead, so request body shape failures map to the correct
422-style error code. Use the existing ErrorCode enum and the parsed.success
check to locate the change.
In `@src/env.ts`:
- Around line 26-36: The envBool parser currently rejects empty strings, so
EN_FEATURE_REPEATED_WORD_CHECK= from dotenv still fails even with
envBool().optional(). Update envBool in src/env.ts to treat a blank or
whitespace-only value as unset/undefined before applying the truthy/falsy
checks, so EnvSchema.safeParse(process.env) succeeds when copying .env.example
verbatim. Keep the change localized to envBool and its optional handling.
---
Outside diff comments:
In
`@src/domains/projects/chapter-assignments/project-chapter-assignments.service.ts`:
- Around line 36-58: The chapter assignment progress response mapping is
duplicated in two places, causing repeated maintenance when fields change.
Extract the shared object transformation into a single helper such as
toChapterAssignmentProgressResponse(info) in
project-chapter-assignments.service.ts, using the same field mapping for
assignmentId, assignedUser, peerChecker, and the rest, then replace both inline
.map((info) => ({ ... })) blocks with .map(toChapterAssignmentProgressResponse).
In
`@src/domains/projects/chapter-assignments/project-chapter-assignments.types.ts`:
- Around line 16-27: The chapter assignment response schema has inconsistent
language-code field naming between sourceLangCode and targetLanguageCode. Update
the chapterAssignmentProgressResponseSchema in
project-chapter-assignments.types.ts to use a consistent naming convention for
both fields, and keep the mirrored type in users-chapter-assignments.types.ts
aligned so the public contract remains unchanged.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 56bbf19e-aa0c-4196-99eb-a8d39fffb71e
📒 Files selected for processing (30)
.env.exampledocker-entrypoint.shdocs/proposals/feature-flags/feature-flags-suggestion.mddocs/proposals/feature-flags/feature-flags-summary.mdsrc/app.tssrc/db/migrations/0013_add_user_settings.sqlsrc/db/migrations/meta/0012_snapshot.jsonsrc/db/migrations/meta/0013_snapshot.jsonsrc/db/migrations/meta/_journal.jsonsrc/db/schema.editor-state-extension.test.tssrc/db/schema.tssrc/db/seeds/data/languages.jsonsrc/db/seeds/dev-users.tssrc/domains/chapter-assignments/chapter-assignments.repository.tssrc/domains/chapter-assignments/chapter-assignments.types.tssrc/domains/projects/chapter-assignments/project-chapter-assignments.service.tssrc/domains/projects/chapter-assignments/project-chapter-assignments.types.tssrc/domains/self/settings/self-settings.repository.tssrc/domains/self/settings/self-settings.route.test.tssrc/domains/self/settings/self-settings.route.tssrc/domains/self/settings/self-settings.service.tssrc/domains/self/settings/self-settings.types.tssrc/domains/users/chapter-assignments/users-chapter-assignments.service.test.tssrc/domains/users/chapter-assignments/users-chapter-assignments.service.tssrc/domains/users/chapter-assignments/users-chapter-assignments.types.tssrc/env.tssrc/lib/features.test.tssrc/lib/features.tssrc/routes/config.route.test.tssrc/routes/config.route.ts
|
Front-end counterpart: eten-tech-foundation/fluent-web#337 — eten-tech-foundation/fluent-web#337. It consumes the |
- rename wire field targetLanguageCode -> targetLangCode (match sourceLangCode) - env: treat blank EN_FEATURE_* as unset so empty .env.example lines don't fail boot - self-settings: return VALIDATION_ERROR (422) on write-schema violation - docs: update feature-flags snippet from z.stringbool() to shipped envBool() - tests: assert userSettingsWriteSchema rejects malformed writes - refactor: extract shared toChapterAssignmentProgressResponse() mapper
|
Pushed 18ca571 addressing the CodeRabbit pass:
Gates green (141 tests). |
Add a docs-only proposal for a lightweight, env-sourced feature-flag mechanism to gate AI-dependent front-end features (starting with the Repeated Word Check) so their PRs can merge and ship hidden while fluent-ai is unhosted. - EN_FEATURE_* env vars, declared in the Zod schema, swept into a map - new unauthenticated meta route GET /config/features (sibling of /health) - fluent-web consumes via a useFeatureFlags hook + FeatureGate (fail-closed) - unlinked, login-gated diagnostics page for the flag view - publishing only; no server-side enforcement (D5) Includes -suggestion.md (design, D1-D8, Q1-Q4) and -summary.md.
The 0012_add_pericope_sets migration snapshot was out of sync with its own schema/SQL: - Removed a phantom `public.recordings` table that was never part of the migration (an accidental leak from experimental mobile DB work; absent from Dev/PROD and from schema.ts). - Fixed `idx_pericope_verses_set_book_chapter_verse` to `isUnique: true` to match the CREATE UNIQUE INDEX in 0012_add_pericope_sets.sql. No SQL or schema changes; snapshot-only correction so drizzle-kit generate diffs cleanly against the actual schema.
Phase 1 of the repeated-word-check persistence backend.
- db/schema.ts: add user_settings table (user_id PK, settings jsonb,
timestamps, FK to users ON DELETE CASCADE) with select/insert schemas;
add userSettingsSchema (read, .catch({})) + strict userSettingsWriteSchema
so malformed PUT bodies 400 instead of being silently coerced; extend
editorStateResourcesSchema with optional activeLeftTab + checkOccurrenceRules.
- migration 0013_add_user_settings.sql (+meta). Renumbered from 0012 during
rebase onto jel-feature-flags-proposal to avoid colliding with the pericope
0012 migration that landed on main via eten-tech-foundation#205.
- domains/self/settings quartet: GET/PUT /self/settings, authenticateUser only,
full-replace upsert, strict-validate -> 400, read-tolerant.
- register self domain in app.ts.
- tests: self-settings.route.test.ts (8) + schema.editor-state-extension.test.ts (8).
Relates to eten-tech-foundation#172, #277, #278
…ts (phase-04 BUG eten-tech-foundation#2) The repeated-words check sends the target language's ISO 639-3 code to greek-room as lang_code (greek-room keys its legitimate-duplicate whitelist on the code, not the display name). The user chapter-assignment response previously exposed only the human display name (targetLanguage), so the web client had no code to send. Plumb targetLangCode (targetLang.langCodeIso6393) through findAssignmentsProgress -> ChapterAssignmentProgressInfo -> toResponse as targetLanguageCode on UserChapterAssignmentResponse (schema + types). Add a toResponse regression test. Found during the Phase 4 manual smoke.
Enables exercising the full project assign -> draft flow out-of-the-box: - dev-users seeder now provisions a second translator (translator2, t2@fluent.local), so a project meets the >=2-translator requirement for chapter assignment (distinct Drafter + Peer Checker). Idempotent; honours SEED_TRANSLATOR2_EMAIL/SEED_TRANSLATOR2_PASSWORD overrides. - languages seed now includes English (eng), so a draftable English target language is available without manual DB insertion. Both seeders remain idempotent (existing rows skipped on re-run).
The repeated-words check sends greek-room a `lang_code` keyed on the ISO
639-3 target language code. greek-room's legitimate-duplicate whitelist
(e.g. "truly truly" for "eng") only matches when the code is correct.
The PM "open chapter" path builds its ProjectItem from
GET /projects/{id}/chapter-assignments/progress, but that endpoint never
serialized the target language code: the repository fetched targetLangCode
yet both service mappers (getChapterAssignmentProgressByProject and
assignSelectedChapters) dropped it, and the response schema didn't declare
it. As a result the PM-path check sent lang_code "<unknown>" and legitimate
duplicates were flagged.
Add `targetLanguageCode` to chapterAssignmentProgressResponseSchema and
populate it in both mappers (falling back to '' when the FK doesn't resolve,
matching the existing sourceLangCode handling).
Includes TEMP [RW-DIAG] logging used to pin down the bug; removed in the
following commit.
Removes the diagnostic logging added in the previous commit (inbound repeated-words request log, fluent-ai response/error log, and the toResponse targetLangCode log) now that the root cause is fixed and verified.
- normalize stored settings JSONB through the tolerant read schema in
toResponse (null-guarded) so a malformed/legacy blob degrades to {}
instead of leaking through GET; add two read-path tests
- declare 403 FORBIDDEN on both GET and PUT route contracts (reachable
via authenticateUser's inactive-account branch); add a 403 test to each
- tighten the OpenAPI response schema settings field to userSettingsSchema
so the spec advertises the real keys/enums (response provably conforms
after the read-path normalization); request schema left generic by design
Implements the fluent-api half of the feature-flags proposal (docs in
docs/proposals/feature-flags): an unauthenticated, DB-less meta route that
publishes which optional, AI-dependent features are enabled per environment,
so fluent-web can ship AI UI hidden while fluent-ai is unhosted.
- env.ts: declare EN_FEATURE_REPEATED_WORD_CHECK in the Zod EnvSchema via a
local envBool() parser (not z.coerce.boolean(), which would read the string
"false" as true and invert the safe-off default). Optional so an unset flag
can derive its default.
- lib/features.ts: typed FLAGS registry tying each env var to its camelCase
wire key + unset default (repeatedWordCheck defaults to whether FLUENT_AI_URL
+ FLUENT_AI_KEY are wired); buildFeatures() derives the published map; named
OpenAPI featuresSchema guarded by a compile-time `satisfies`.
- routes/config.route.ts: GET /config/features -> { features: { ... } },
sibling of /health; publishes state, does not enforce it (no gating of the
AI request path).
- app.ts: register the route. .env.example: document the flag.
- Tests: buildFeatures behavior + a three-way drift guard (env schema / FLAGS /
OpenAPI schema kept in sync, both directions) + route behavior.
Relates to the feature-flags proposal (fluent-api eten-tech-foundation#211).
The container seeds the DB via docker-entrypoint.sh, which inlined its own seed list and stopped after bible-texts, never running the pericope-sets seed that setup.ts includes as its final step. As a result every container-based DB (re)generation left pericope_sets empty, so the Create Project page's Pericope Set dropdown had nothing to select. Add the pericope-sets seed after bible-texts (it depends on books, already seeded earlier). The seed is idempotent (upsert set by name + replace verses) and its data files are baked into the image, matching the other entrypoint seeds.
- rename wire field targetLanguageCode -> targetLangCode (match sourceLangCode) - env: treat blank EN_FEATURE_* as unset so empty .env.example lines don't fail boot - self-settings: return VALIDATION_ERROR (422) on write-schema violation - docs: update feature-flags snippet from z.stringbool() to shipped envBool() - tests: assert userSettingsWriteSchema rejects malformed writes - refactor: extract shared toChapterAssignmentProgressResponse() mapper
… spec
The OpenAPI document walk over every registered schema currently 500s
('Unknown zod object type') and nothing at the unit/type level catches it,
so it only surfaced at runtime. This test renders the real assembled
document via GET /doc and asserts a 200, so that class of break fails CI.
Currently RED (reproduces the live 500); the fix follows in the next commit.
…urface
GET /doc 500'd with 'Unknown zod object type' because the /self/settings
response schema embedded userSettingsSchema (= userSettingsObjectSchema.catch({})).
The .catch wrapper is a ZodCatch node that @asteasolutions/zod-to-openapi
(under @hono/zod-openapi) has no renderer for, so building the OpenAPI
document threw and took the whole /doc endpoint down.
Export the plain userSettingsObjectSchema and embed THAT in
userSettingsResponseSchema. The .catch only affects behaviour on invalid
input, so it adds/removes no fields: the documented shape and the response
bytes are unchanged. The fail-soft W8 read tolerance stays where it belongs
(the service read path, toResponse -> userSettingsSchema.parse), and the
strict write schema is untouched. No request/response contract change.
Turns the doc.route regression test green.
18ca571 to
1b803bd
Compare
| export const FLAGS = { | ||
| // Repeated Word Check — the one AI-dependent feature today. Defaults to | ||
| // whether AI is wired when EN_FEATURE_REPEATED_WORD_CHECK is unset (D2/§4.2). | ||
| repeatedWordCheck: { env: 'EN_FEATURE_REPEATED_WORD_CHECK', default: aiIsWired }, |
There was a problem hiding this comment.
This is an acceptable default for all AI features. In the proposal doc, I suggested that this not be the case. However, After seeing the implementation, I see the merit in this default. Let's keep this and make user it is standard for any feature flag.
There was a problem hiding this comment.
Confirmed: the AI-wiring-derived unset-default stays, and we are treating it as the standard convention for feature flags going forward — an unset flag resolves to a safe-by-context default via its DefaultResolver, rather than a blanket static false. The FLAGS registry in lib/features.ts is built for exactly that (each flag ties its env var to a default resolver), so future AI-dependent flags will follow the same aiIsWired pattern. The proposal + summary docs (folded in f547988) now record Q4 as resolved this way.
…ation#211 Q1) Per kaseywright's review on proposal eten-tech-foundation#211 (Q1), GET /config/features is now login-gated via authenticateUser (no role required), matching the pattern used by self-settings and pericopes-list. The endpoint still only publishes flags (does not enforce them, D5 unchanged); only the read now requires a session. Reversible later if we decide it should be public. - config.route.ts: add authenticateUser middleware + UNAUTHORIZED (401) response, reword description (no longer 'unauthenticated like /health'). - config.route.test.ts: rewrite against the mocked-auth pattern — null session => 401; valid session => 200 with the features map.
|
The Q1 auth-gate from the proposal review (#211) is now in on this branch (0812a39): |
There was a problem hiding this comment.
Actionable comments posted: 7
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/proposals/feature-flags/feature-flags-suggestion.md`:
- Line 3: Update the proposal document so it matches the shipped auth behavior
of the feature-flags route: the current “Draft for review. Not yet implemented.”
status and Q1 open-question text are stale. In the feature-flags suggestion doc,
revise the Status line and the Q1/auth sections (including the mermaid diagram
and endpoint spec references) to state that GET /config/features is
authenticated via authenticateUser and that the auth decision is already
resolved, or point to the canonical decision record if that is where it now
lives.
In `@docs/proposals/feature-flags/feature-flags-summary.md`:
- Line 3: The feature-flags summary is stale and contradicts the shipped auth
behavior. Update the summary and related sections in the proposal so they match
the implemented route guarded by authenticateUser and reflect that auth is no
longer an open question; also revise the reviewer status text and any
“unauthenticated” wording to align with the confirmed decision. Use the
feature-flags summary doc headings and the route/auth references as the places
to sync the narrative with the code.
In `@src/db/schema.ts`:
- Around line 511-514: The comment on userSettingsWriteSchema is stale because
malformed write-body validation failures are not surfaced as 400; they map to
VALIDATION_ERROR and end up as 422. Update the inline comment near
userSettingsWriteSchema to match the behavior described in
self-settings.service.ts and safeParse handling, keeping the note aligned with
the actual status code returned for schema validation failures.
In `@src/domains/self/settings/self-settings.route.ts`:
- Around line 45-51: The GET handler in getSelfSettingsRoute hardcodes
INTERNAL_SERVER_ERROR instead of using the shared status-mapping pattern. Update
the error branch to derive the response status from result.error via
getHttpStatus, matching the PUT handler’s approach in self-settings.route and
keeping status handling consistent if getSettings adds new failure modes.
- Around line 65-95: The OpenAPI contract for saveSelfSettingsRoute is missing
the 422 validation-error response, which is why the return path in
server.openapi uses an unsafe as never cast. Update the responses map in
self-settings.route.ts to declare the validation failure status alongside
BAD_REQUEST, and then remove the cast by letting getHttpStatus(result.error)
flow through with the proper typed response handling. Keep the fix centered on
saveSelfSettingsRoute and the result/error handling branch so the spec matches
the actual selfSettingsService.upsertSettings behavior.
In `@src/lib/features.ts`:
- Around line 50-55: The aiIsWired default resolver in features.ts is
effectively unreachable at runtime because FLUENT_AI_URL and FLUENT_AI_KEY are
already required by the env parsing path. Update the boot/default logic used by
repeatedWordCheck so omitted AI-dependent flags do not resolve to true by
accident: either relax the env requirements in src/env.ts so the “AI not wired”
branch can occur, or change aiIsWired (and any related default resolver wiring)
to default to false when the AI vars are not intended to control the flag.
- Around line 90-99: Tighten the `FlagDefinition.env` typing used by
`buildFeatures` so only boolean-parsed env keys can be referenced. Update the
`FlagDefinition` type (and any related flag definitions in `FLAGS`) to constrain
`env` to keys of `Env` whose values are `boolean | undefined`, then remove the
unsafe cast in `buildFeatures` and let the compiler enforce that `e[def.env]` is
boolean-compatible.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: f0a92cd0-6dbb-4074-8e31-f57683e8d7ce
📒 Files selected for processing (31)
.env.exampledocker-entrypoint.shdocs/proposals/feature-flags/feature-flags-suggestion.mddocs/proposals/feature-flags/feature-flags-summary.mdsrc/app.tssrc/db/migrations/0013_add_user_settings.sqlsrc/db/migrations/meta/0012_snapshot.jsonsrc/db/migrations/meta/0013_snapshot.jsonsrc/db/migrations/meta/_journal.jsonsrc/db/schema.editor-state-extension.test.tssrc/db/schema.tssrc/db/seeds/data/languages.jsonsrc/db/seeds/dev-users.tssrc/domains/chapter-assignments/chapter-assignments.repository.tssrc/domains/chapter-assignments/chapter-assignments.types.tssrc/domains/projects/chapter-assignments/project-chapter-assignments.service.tssrc/domains/projects/chapter-assignments/project-chapter-assignments.types.tssrc/domains/self/settings/self-settings.repository.tssrc/domains/self/settings/self-settings.route.test.tssrc/domains/self/settings/self-settings.route.tssrc/domains/self/settings/self-settings.service.tssrc/domains/self/settings/self-settings.types.tssrc/domains/users/chapter-assignments/users-chapter-assignments.service.test.tssrc/domains/users/chapter-assignments/users-chapter-assignments.service.tssrc/domains/users/chapter-assignments/users-chapter-assignments.types.tssrc/env.tssrc/lib/features.test.tssrc/lib/features.tssrc/routes/config.route.test.tssrc/routes/config.route.tssrc/routes/doc.route.test.ts
…+ harden db bootstrap CodeRabbit re-review (auth-gate follow-up): - self-settings.route: add 422 UNPROCESSABLE_ENTITY to PUT OpenAPI responses so the published contract matches upsertSettings validation behavior (R5) - self-settings.route: GET error branch derives status via getHttpStatus(result.error) instead of hardcoding 500, consistent with PUT (R4) - schema.ts: correct stale userSettingsWriteSchema comment (400 -> 422) (R3) - lib/features: tighten FlagDefinition.env to boolean-valued EN_FEATURE_* keys and drop the boolean cast in buildFeatures (R7) - docs/proposals/feature-flags: fold suggestion + summary to the shipped authenticated contract and Q1-Q4 resolutions (R1/R2) Stack durability (unblocks local smoke testing): - db bootstrap: poll for real query readiness before issuing DDL. The compose healthcheck (pg_isready) reports ready as soon as Postgres accepts connections, but during startup/WAL-recovery it rejects queries with 57P03; the entrypoint's set -e then killed the container (and its dependents) on restart. Retry transient startup/connection errors with bounded backoff (~60s ceiling); real errors still fail fast.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/db/scripts/bootstrap.ts`:
- Around line 91-111: The retry loop in waitForDatabase currently uses a fixed
2s delay, which can cause multiple replicas to synchronize and hammer Postgres
in lockstep. Update the retry delay logic in waitForDatabase to use exponential
backoff with jitter while keeping the existing transient-error handling and
maxAttempts behavior intact, and continue logging the attempt count and delay so
startup diagnostics remain clear.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 32bea658-b8a6-4f3e-8b68-e63ab96566c2
📒 Files selected for processing (6)
docs/proposals/feature-flags/feature-flags-suggestion.mddocs/proposals/feature-flags/feature-flags-summary.mdsrc/db/schema.tssrc/db/scripts/bootstrap.tssrc/domains/self/settings/self-settings.route.tssrc/lib/features.ts
What & why
Enables shipping AI-dependent front-end features hidden while
fluent-aiis unhosted in dev/staging/prod, then flipping them on per environment (via env, no code redeploy). This is the fluent-api side: an env-sourced feature-flag map published at a new meta endpoint. The matching fluent-web consumer (useFeatureFlagshook +<FeatureGate>, diagnostics page) is a separate later PR.This branch bundles the Repeated Word Check backend work (previously PR #203) plus the feature-flags enabler, because #203 was only being held back by the lack of a way to hide its UI. Keeping them together means #203's branch will auto-resolve as merged once this lands, rather than looking like a competing PR.
Endpoint
GET /config/features— unauthenticated, DB-less, sibling of/health. Returns the env-derived map of which optional features are on:It publishes state; it does not enforce it (publishing is decoupled from the AI request path).
Design highlights
EN_FEATURE_*prefix, each declared explicitly in the ZodEnvSchema(a fixedz.objectstrips unknown keys, so the schema is the authoritative flag catalog).EN_FEATURE_REPEATED_WORD_CHECKdefaults OFF unlessFLUENT_AI_URL+FLUENT_AI_KEYare wired — so forgetting the flag in an AI-less environment yields the safe (hidden) answer.envBool()(acceptstrue/false,1/0,yes/no,on/off), notz.coerce.boolean()(which would read the string"false"astrueand invert the safe default). Note:z.stringbool()— floated in the proposal — is only available on thezod/v4subpath, not the classiczbound by@hono/zod-openapi, hence the local parser.FLAGSregistry ties each env var to its camelCase wire key and its unset default; a flag lives in three synced places (env schema / registry / OpenAPI schema), kept honest by a compile-timesatisfiesand a runtime three-way drift test (both directions).Verification
curl localhost:9999/config/features→{"features":{"repeatedWordCheck":true}}with AI wired; explicitEN_FEATURE_REPEATED_WORD_CHECK=false/off→false.fresh→up→db:init) confirmed migrations apply cleanly from scratch: the renumbered0013_add_user_settingsand main's0012_add_pericope_setscoexist; no phantom-table drift.Notes for reviewers
docs/proposals/feature-flags/) also under review in docs(proposals): feature flags to ship AI-dependent UI hidden (config/features) #211.b011cdefix(db): correct 0012 pericope snapshot to match schema— a main-owned snapshot defect (phantomrecordingstable + a wrong index-uniqueness flag) that otherwise produces spuriousdrizzle-kit generatedrift. Confirmed safe to remove with Kasey.Relates to #211 (proposal) and supersedes #203 (checks-api impl).
Summary by CodeRabbit
GET /config/features).GET/PUT /self/settings).