Skip to content

feat(dev): Docker Compose local stack + hash-guarded codegen - #123

Open
teetangh wants to merge 4 commits into
devfrom
feat/local-docker-devenv
Open

feat(dev): Docker Compose local stack + hash-guarded codegen#123
teetangh wants to merge 4 commits into
devfrom
feat/local-docker-devenv

Conversation

@teetangh

@teetangh teetangh commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Why

The backend dev loop is slow for two independent reasons, and only one of them is the database.

Codegen dominated. backend/lib/generated/ is ~735k lines across 498 files and is gitignored, and regenerate-build.sh deleted it unconditionally on every run — discarding build_runner's asset graph and forcing a cold rebuild even when nothing had changed. There was also no build.yaml, so json_serializable ran as a build phase over the whole package while producing zero output (the Prisma connector hand-writes fromJson/toJson; the one .g.dart comes from the Prisma generator, not build_runner).

The database was remote. backend/.env points at Supabase's Mumbai pooler. Measured on a warm pooled connection: 57.03 ms per query vs 0.23 ms locally — 248×. appointment_repository.dart alone issues 121 queries.

What

  • docker-compose.yml — Postgres, Dart Frog (hot reload), Flutter web. docker compose up for everything; docker compose up db-init for the DB-only fast path. Every heavy directory (.dart_tool, lib/generated, .dart_frog, build, pub cache) is a named volume so it stays off macOS virtiofs. For .dart_tool that is correctness, not tuning: package_config.json holds absolute host paths that don't exist in the container.
  • docker/db-initprisma db push from the in-repo schema, then the ledger triggers and CHECK constraints vendored from familiarise_web (db push doesn't manage those), then a seed extracted from prompts/testing/unit/*.md. Refuses to provision any non-local host, so a mistyped URL can't run --accept-data-loss against production.
  • backend/build.yaml — disables json_serializable, which also drops source_gen's combining_builder and part_cleanup and freezed's runs_before barrier.
  • backend/scripts/ensure-generated.sh — regenerates only when schema.prisma, the connector/freezed versions, or build.yaml actually change.
  • scripts/use-db.sh, scripts/dev-backend.sh, Makefile, docs.

main.dart now resolves config as .env.env.local → process environment, last winning. DotEnv's own includePlatformEnvironment injects the environment in the constructor and then lets the file overwrite it, which is backwards for containers: a stale DIRECT_URL in a bind-mounted .env would silently hijack a "local" run. Startup also logs the resolved DB host (never credentials) so that's visible rather than silent.

Dockerfile, .dockerignore and the health route are carried over from #111, whose comments document five deployment failures — notably that the base image must be Flutter, not dart:stable. Its main.dart and schema.prisma changes are deliberately not taken; schema.prisma is owned by #122.

Verified end-to-end in Docker

Result
prisma db push 127 tables, 100 enums, ~500 ms
Sidecars ledger_txn_balanced + CHECK constraints present
Seed 19/23 blocks; 4 skipped as stale prompts
Codegen in-container 67 s cold → 0.067 s warm (stamp hit)
/api/health 200 in 3 ms
/api/consultants, /api/domains 200, seeded rows via typed delegates
Startup log host=db:5432, db=familiarise
Sign-up POST row confirmed in the container DB
iOS simulator app builds (49 s) and runs against the containerised API

Running it in Docker caught two bugs that cannot reproduce natively, both fixed here: Platform.environment bound to the Prisma-generated Platform enum re-exported by database_client.dart instead of dart:io's (the gotcha that file's own header warns about), and rm -rf lib/generated failing with "Device or resource busy" because it's a volume mount point.

Notes for review

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added a Docker-based local development stack for PostgreSQL, the API, and Flutter web.
    • Added convenient commands for starting services, viewing logs, running tests, managing databases, and checking setup readiness.
    • Added a health-check endpoint for API availability monitoring.
    • Added stronger database safeguards for booking conflicts, financial records, and ledger consistency.
  • Documentation

    • Added comprehensive local Docker development and troubleshooting guidance.
    • Added configuration templates for local and hosted database environments.
  • Developer Experience

    • Improved automatic code generation, hot reload, database seeding, and repeatable test data setup.

Kaustav Ghosh and others added 4 commits July 25, 2026 00:08
The backend dev loop was slow for two independent reasons, and only one of
them was the database.

Codegen dominated. backend/lib/generated/ is ~735k lines across 498 files and
is gitignored, and regenerate-build.sh deleted it unconditionally on every
run — discarding build_runner's asset graph and forcing a cold rebuild even
when nothing had changed. There was also no build.yaml, so json_serializable
ran as a build phase over the whole package while producing zero output (the
Prisma connector writes fromJson/toJson by hand; the one .g.dart comes from
the Prisma generator, not build_runner).

The database was remote. backend/.env pointed at Supabase's Mumbai pooler at
~40ms RTT; appointment_repository.dart alone issues 121 queries.

Adds:
- docker-compose.yml — Postgres, Dart Frog (hot reload), Flutter web.
  `docker compose up` for everything, `docker compose up db-init` for the
  DB-only fast path. Every heavy directory (.dart_tool, lib/generated,
  .dart_frog, build, pub cache) is a named volume, so it stays off macOS
  virtiofs. For .dart_tool that is correctness, not tuning: package_config.json
  holds absolute host paths that do not exist in the container.
- docker/db-init — prisma db push from the in-repo schema, then the ledger
  triggers and CHECK constraints vendored from familiarise_web (db push does
  not manage those), then a seed harvested from prompts/testing/unit/*.md.
  Refuses to provision any host that is not local, so a mistyped URL cannot
  run --accept-data-loss against production.
- backend/build.yaml — disables json_serializable, which also drops
  source_gen's combining_builder and part_cleanup and freezed's runs_before
  barrier.
- backend/scripts/ensure-generated.sh — regenerates only when schema.prisma,
  the connector/freezed versions or build.yaml actually change.
- scripts/use-db.sh, scripts/dev-backend.sh, Makefile, docs.

backend/main.dart now resolves config as .env -> .env.local -> process
environment, last winning. DotEnv's own includePlatformEnvironment injects the
environment in the constructor and then lets the file overwrite it, which is
backwards for containers: a stale DIRECT_URL in a bind-mounted .env would
silently hijack a "local" run. Startup now also logs the resolved database
host (never credentials) so that is visible rather than silent.

Dockerfile, .dockerignore and the health route are carried over from PR #111,
whose header comments document five deployment failures — notably that the
base image must be Flutter, not dart:stable, because prisma_flutter_connector
declares a Flutter SDK dependency. Its main.dart and schema.prisma changes are
deliberately not taken; schema.prisma is owned by PR #122.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Concatenating the 23 `## Data Seeding` blocks into a single transaction does
not work, for two reasons found by actually running it.

Domain.name is UNIQUE and 17 prompts insert a domain named 'Technology' with
different ids. Their `ON CONFLICT (id) DO NOTHING` does not cover a name
collision, and the rows cannot just be dropped because each block's
ConsultantProfile references its own domainId. The generator now suffixes the
name with the row id, keeping foreign keys intact and names unique.

Four prompts have also drifted from the schema — they reference
`support_tickets` (the model is SupportTicket), `WebinarCollaborator`, and a
`ConsultantProfile.totalRevenue` column that no longer exists. In one
transaction the first of those aborted the entire seed.

So emit one file per prompt into backend/prisma/sql/seed.d/ and apply each in
its own transaction, reporting the ones that fail by name. Drift now costs one
fixture set and is visible in the log, rather than silently taking out
everything after it. Currently 19 of 23 blocks apply.

Verified from a clean volume: db push syncs in ~600ms, both sidecars apply,
127 tables / 100 enums, ledger_txn_balanced and the CHECK constraints present,
28 seeded users, db-init exits 0. A second run is a no-op.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…on recovery

Hit while verifying: the host filled up mid-build, which made writes fail
inside Docker's VM and corrupted its containerd content store. The failure
mode is opaque (input/output error on cache-key computation, then on the
metadata DB) and unfixable by 'docker system prune', because pruning must read
the blobs that are unreadable. Document the symptom, the ~15GB host
requirement, and the factory-reset recovery.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Both only reproduce in Docker, which is why they survived the native run.

1. `Platform` was resolving to the wrong type. lib/generated/index.dart
   exports a Platform enum generated from the Prisma schema, and
   database_client.dart re-exports it, so the unprefixed `Platform` in
   main.dart bound to that enum instead of dart:io's:

     main.dart:33:23: Error: Member not found: 'environment'.

   This is the gotcha already documented in database_client.dart's header.
   Import dart:io's Platform under an `io` prefix.

2. ensure-generated.sh could not wipe lib/generated. Under docker-compose it
   is a named-volume mount point, and unlinking a mount point fails with
   "Device or resource busy". Clear the contents instead, which works both in
   the container and natively.

Verified in Docker end to end: codegen 67s in-container, dart_frog dev serving,
startup logs "host=db:5432, db=familiarise" (not Supabase), /api/health 200 in
3ms, /api/consultants and /api/domains return seeded rows through the typed
delegates, /api/announcements correctly 401s.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@gemini-code-assist

Copy link
Copy Markdown

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@coderabbitai

coderabbitai Bot commented Jul 25, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR adds Docker-first local development for Postgres, the Dart Frog API, and Flutter web, with environment precedence, guarded schema provisioning, database integrity SQL, generated test seeds, code-generation caching, health checks, helper commands, and updated setup documentation.

Changes

Local development stack

Layer / File(s) Summary
Developer commands and environment configuration
Makefile, backend/.env*, backend/.gitignore, backend/.dockerignore, scripts/use-db.sh
Adds local lifecycle, diagnostic, regeneration, database-selection, and environment-template workflows.
Container orchestration and startup
docker-compose.yml, docker/db-init/*, docker/web/*, backend/Dockerfile.dev, backend/docker/*
Defines Postgres, schema initialization, API hot reload, and Flutter web services with health checks, named volumes, and startup entrypoints.
Backend runtime and code generation
backend/Dockerfile, backend/build.yaml, backend/main.dart, backend/routes/api/health.dart, backend/scripts/*, scripts/dev-backend.sh, scripts/regenerate-build.sh
Adds production packaging, environment precedence, a health endpoint, and hash-based or forced generated-code regeneration.
Database safeguards
backend/prisma/sql/check-constraints.sql, backend/prisma/sql/ledger-triggers.sql
Adds PostgreSQL constraints, exclusion and uniqueness rules, financial validations, collaborator-plan XOR enforcement, and deferred ledger-balance checks.
Development seed pipeline
scripts/gen-dev-seed.sh, backend/prisma/sql/seed.d/*, docker/db-init/entrypoint.sh
Generates transactional SQL fixtures from prompt data and applies seed blocks with success and failure reporting.
Development documentation
docs/getting-started/04-local-testing-guide.md, docs/getting-started/05-docker-local-dev.md
Documents Docker-first startup, database routing, schema provisioning, code generation, performance considerations, and troubleshooting.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant DockerCompose
  participant Postgres
  participant DbInit
  participant Api
  participant Web
  DockerCompose->>Postgres: Start and healthcheck database
  DockerCompose->>DbInit: Start after database health
  DbInit->>Postgres: Push schema, apply constraints, and seed fixtures
  DockerCompose->>Api: Start API after initialization
  Api->>Api: Run Dart Frog development server
  DockerCompose->>Web: Start web service after API health
  Web->>Api: Use configured API base URL
Loading

Possibly related issues

Poem

A rabbit hops through Docker bright,
With seeds and schemas tucked in tight.
Ledger paws balance, slots align,
Health checks blink a green design.
“Make up!” I cheer, then spring away—
Fresh code grows faster every day.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly captures the two main changes: a Docker Compose local dev stack and hash-guarded backend code generation.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/local-docker-devenv

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 16

🤖 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 `@backend/Dockerfile`:
- Around line 72-82: Run both API containers as non-root users. In
backend/Dockerfile lines 72-82, create an unprivileged runtime user, copy the
server with ownership assigned to that user, and switch to it before CMD. In
backend/Dockerfile.dev lines 16-43, create and switch to an unprivileged user
while preserving writable named-volume initialization.

In `@backend/prisma/sql/check-constraints.sql`:
- Around line 168-176: Update the tds_record_financial_year_format and
consultant_payout_tds_fy_format CHECK constraints so they validate that the
two-digit suffix represents the year immediately following the four-digit
prefix, rather than only matching the YYYY-YY shape; preserve the nullable
behavior of ConsultantPayout.tdsFinancialYear.

In `@backend/prisma/sql/seed.d/01-auth.sql`:
- Around line 9-11: Replace the shared password hash source in scripts/use-db.sh
with a valid 60-character bcrypt.dart hash, then regenerate
backend/prisma/sql/seed.d/ so the credential account fixtures use it
consistently. Apply this across backend/prisma/sql/seed.d/01-auth.sql (lines
9-11), 02-onboarding.sql (12-15), 03-profile.sql (10-12), 04-verification.sql
(10-12), 05-plans.sql (10-12), 06-slots.sql (10-12), 15-reviews.sql (12-15),
16-support.sql (10-12), and 17-feedback.sql (9-11); do not edit generated SQL
files directly.

In `@backend/prisma/sql/seed.d/02-onboarding.sql`:
- Around line 9-10: Update the source prompt for the test_unit_onb_cnt
consultant fixture to use the CONSULTANT role instead of CONSULTEE, then
regenerate the onboarding seed SQL so the generated insert matches the corrected
role.

In `@backend/prisma/sql/seed.d/07-explore.sql`:
- Around line 12-15: Replace the malformed shared bcrypt hash in credential seed
rows with one valid bcrypt hash for the documented development password,
preserving the existing account data and password semantics. Apply this update
in backend/prisma/sql/seed.d/07-explore.sql lines 12-15, 08-booking.sql lines
12-15, 09-checkout.sql lines 12-15, 10-trials.sql lines 12-15, 11-waitlist.sql
lines 13-16, 12-documents.sql lines 12-15, 13-chat.sql lines 10-12, and
14-referrals.sql lines 10-12.

In `@backend/prisma/sql/seed.d/11-waitlist.sql`:
- Around line 29-35: Update the waitlist seed data following the Webinar insert
for test_unit_waitlist_w1 to add the existing enrollment/attendee row for
test_unit_waitlist_u3, linked to that webinar and its expected user/registration
identifiers. Regenerate the seed so capacity checks observe the webinar as full
before exercising the waitlist path.

In `@backend/scripts/ensure-generated.sh`:
- Around line 44-62: Update the dependency setup and stamp validation around the
WANT calculation in ensure-generated.sh to account for pubspec.yaml and
pubspec.lock changes, including the .dart_tool/package_config.json state. Run
flutter pub get before calculating or accepting the codegen stamp, and ensure
unchanged codegen inputs cannot bypass dependency resolution when the manifest
or lockfile has changed.

In `@docker-compose.yml`:
- Line 67: Update the db-init service environment so its DIRECT_URL always uses
the local database connection and is not overridden by FAM_DIRECT_URL. Move the
FAM_DIRECT_URL override to the api service environment, preserving the local
fallback there.

In `@docker/db-init/entrypoint.sh`:
- Around line 75-82: Update the psql invocation in the seed-file loop to include
the --single-transaction option, ensuring each prisma/sql/seed.d/*.sql file runs
atomically and rolls back completely on failure. Preserve the existing error
capture, success counting, and skipped-file reporting behavior.

In `@docker/web/Dockerfile`:
- Line 13: Update the Dockerfile’s Flutter base image reference from the mutable
stable tag to a tested, explicit Flutter release pinned by its immutable image
digest. Preserve the existing Flutter image source while ensuring future
rebuilds always use the same Flutter/Dart toolchain.

In `@docker/web/entrypoint.sh`:
- Around line 18-36: Update the .env generation guard in the entrypoint script
to construct the complete expected configuration, compare it against the
existing .env contents, and atomically replace .env whenever any generated value
differs. Ensure changes to all settings, not just API_BASE_URL, trigger
regeneration while preserving the existing generated values and file format.

In `@docs/getting-started/05-docker-local-dev.md`:
- Around line 87-93: Remove the Supabase-targeted Docker Compose command from
the getting-started documentation and state that Supabase is supported only with
the native backend workflow using scripts/use-db.sh supabase. Do not document a
remote database Compose path unless the db-init behavior is explicitly made safe
for non-local hosts.
- Around line 135-138: Update the Docker local development seed documentation to
explicitly describe the seed state as incomplete when prompt seed files fail,
including that four of 23 fixtures are currently unavailable and may affect
dependent tests or seeded endpoints. Do not characterize these failures solely
as stale prompts or non-provisioning issues; either identify the missing
fixtures or document that the failures are fatal.
- Around line 110-115: Reconcile the schema count in the provisioning
documentation with the 129-table value reported by docker/db-init/entrypoint.sh,
using a shared source of truth if practical; otherwise remove the volatile table
count while retaining the prisma db push instructions.

In `@scripts/dev-backend.sh`:
- Around line 22-36: Update the load_env calls in the dev-backend
environment-loading flow to load .env.local before .env, allowing local values
to remain effective when the later file only sets absent variables. Adjust the
presence check in load_env so an explicitly inherited empty variable is treated
as already set and is not overwritten.

In `@scripts/gen-dev-seed.sh`:
- Around line 19-21: Update the gen-dev-seed.sh flow around OUT_DIR so
generation occurs in a temporary sibling staging directory rather than deleting
seed.d upfront. Run the complete generation and validation loop against the
staging directory, and only after success atomically replace OUT_DIR with the
validated staging contents; preserve the existing fixture output when any
command fails.
🪄 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: a3b836c0-e5c2-4607-b884-049f370a4854

📥 Commits

Reviewing files that changed from the base of the PR and between 4be3fc7 and f656a9f.

⛔ Files ignored due to path filters (1)
  • backend/pubspec.lock is excluded by !**/*.lock
📒 Files selected for processing (48)
  • Makefile
  • backend/.dockerignore
  • backend/.env.example
  • backend/.env.supabase.example
  • backend/.gitignore
  • backend/Dockerfile
  • backend/Dockerfile.dev
  • backend/build.yaml
  • backend/docker/dev-entrypoint.sh
  • backend/main.dart
  • backend/prisma/sql/check-constraints.sql
  • backend/prisma/sql/ledger-triggers.sql
  • backend/prisma/sql/seed.d/01-auth.sql
  • backend/prisma/sql/seed.d/02-onboarding.sql
  • backend/prisma/sql/seed.d/03-profile.sql
  • backend/prisma/sql/seed.d/04-verification.sql
  • backend/prisma/sql/seed.d/05-plans.sql
  • backend/prisma/sql/seed.d/06-slots.sql
  • backend/prisma/sql/seed.d/07-explore.sql
  • backend/prisma/sql/seed.d/08-booking.sql
  • backend/prisma/sql/seed.d/09-checkout.sql
  • backend/prisma/sql/seed.d/10-trials.sql
  • backend/prisma/sql/seed.d/11-waitlist.sql
  • backend/prisma/sql/seed.d/12-documents.sql
  • backend/prisma/sql/seed.d/13-chat.sql
  • backend/prisma/sql/seed.d/14-referrals.sql
  • backend/prisma/sql/seed.d/15-reviews.sql
  • backend/prisma/sql/seed.d/16-support.sql
  • backend/prisma/sql/seed.d/17-feedback.sql
  • backend/prisma/sql/seed.d/18-payout.sql
  • backend/prisma/sql/seed.d/19-tax.sql
  • backend/prisma/sql/seed.d/20-staff.sql
  • backend/prisma/sql/seed.d/21-announcements.sql
  • backend/prisma/sql/seed.d/22-collaborations.sql
  • backend/prisma/sql/seed.d/23-dashboard.sql
  • backend/routes/api/health.dart
  • backend/scripts/ensure-generated.sh
  • docker-compose.yml
  • docker/db-init/Dockerfile
  • docker/db-init/entrypoint.sh
  • docker/web/Dockerfile
  • docker/web/entrypoint.sh
  • docs/getting-started/04-local-testing-guide.md
  • docs/getting-started/05-docker-local-dev.md
  • scripts/dev-backend.sh
  • scripts/gen-dev-seed.sh
  • scripts/regenerate-build.sh
  • scripts/use-db.sh

Comment thread backend/Dockerfile
Comment on lines +72 to +82
FROM debian:bookworm-slim

RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates \
&& rm -rf /var/lib/apt/lists/*

COPY --from=build /app/build/bin/server /app/bin/server

EXPOSE 8080

CMD ["/app/bin/server"]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Run both API containers as a non-root user. Both images retain the default root user, so a compromise of the Dart Frog process has root privileges inside its container.

  • backend/Dockerfile#L72-L82: create an unprivileged runtime user, copy the server with appropriate ownership, and switch to it before CMD.
  • backend/Dockerfile.dev#L16-L43: create and switch to an unprivileged user while ensuring named-volume initialization remains writable.
🧰 Tools
🪛 Checkov (3.3.8)

[low] 1-82: Ensure that HEALTHCHECK instructions have been added to container images

(CKV_DOCKER_2)


[low] 1-82: Ensure that a user for the container has been created

(CKV_DOCKER_3)

🪛 Hadolint (2.14.0)

[warning] 74-74: Pin versions in apt get install. Instead of apt-get install <package> use apt-get install <package>=<version>

(DL3008)

📍 Affects 2 files
  • backend/Dockerfile#L72-L82 (this comment)
  • backend/Dockerfile.dev#L16-L43
🤖 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 `@backend/Dockerfile` around lines 72 - 82, Run both API containers as non-root
users. In backend/Dockerfile lines 72-82, create an unprivileged runtime user,
copy the server with ownership assigned to that user, and switch to it before
CMD. In backend/Dockerfile.dev lines 16-43, create and switch to an unprivileged
user while preserving writable named-volume initialization.

Source: Linters/SAST tools

Comment on lines +168 to +176
ALTER TABLE "TDSRecord" DROP CONSTRAINT IF EXISTS "tds_record_financial_year_format";
-- SPLIT
ALTER TABLE "TDSRecord" ADD CONSTRAINT "tds_record_financial_year_format"
CHECK ("financialYear" ~ '^[0-9]{4}-[0-9]{2}$');
-- SPLIT
ALTER TABLE "ConsultantPayout" DROP CONSTRAINT IF EXISTS "consultant_payout_tds_fy_format";
-- SPLIT
ALTER TABLE "ConsultantPayout" ADD CONSTRAINT "consultant_payout_tds_fy_format"
CHECK ("tdsFinancialYear" IS NULL OR "tdsFinancialYear" ~ '^[0-9]{4}-[0-9]{2}$');

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Validate the financial-year relationship, not only its shape.

2026-99 passes both regexes even though the suffix must be the following year (2026-27). Enforce that relationship for both columns.

Proposed fix
-  CHECK ("financialYear" ~ '^[0-9]{4}-[0-9]{2}$');
+  CHECK (
+    "financialYear" ~ '^[0-9]{4}-[0-9]{2}$'
+    AND substring("financialYear" FROM 6 FOR 2)::integer =
+      (substring("financialYear" FROM 1 FOR 4)::integer + 1) % 100
+  );
🤖 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 `@backend/prisma/sql/check-constraints.sql` around lines 168 - 176, Update the
tds_record_financial_year_format and consultant_payout_tds_fy_format CHECK
constraints so they validate that the two-digit suffix represents the year
immediately following the four-digit prefix, rather than only matching the
YYYY-YY shape; preserve the nullable behavior of
ConsultantPayout.tdsFinancialYear.

Comment on lines +9 to +11
INSERT INTO "accounts" (id, "userId", "accountId", "providerId", password, "createdAt", "updatedAt")
VALUES ('test_unit_auth_a1', 'test_unit_auth_u1', 'test_unit_auth_u1', 'credential',
'$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW());

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== repository files containing seed.d =="
git ls-files | grep -E 'backend/prisma/sql/seed\.d/|Seed|seeding|prisma' | head -200

echo
echo "== target SQL snippets =="
for f in \
  backend/prisma/sql/seed.d/01-auth.sql \
  backend/prisma/sql/seed.d/02-onboarding.sql \
  backend/prisma/sql/seed.d/03-profile.sql \
  backend/prisma/sql/seed.d/04-verification.sql \
  backend/prisma/sql/seed.d/05-plans.sql \
  backend/prisma/sql/seed.d/06-slots.sql \
  backend/prisma/sql/seed.d/15-reviews.sql \
  backend/prisma/sql/seed.d/16-support.sql \
  backend/prisma/sql/seed.d/17-feedback.sql
do
  if [ -f "$f" ]; then
    echo "--- $f"
    nl -ba "$f" | sed -n '1,80p'
  else
    echo "MISSING $f"
  fi
done

echo
echo "== source prompt occurrences of hash or Data Seeding =="
rg -n '"Data Seeding"|password|test_unit_auth_a1|test_unit_onb_a1|test_unit_profile_a1|test_unit_verif_a1|test_unit_plans_a1|test_unit_slots_a1|test_unit_reviews|test_unit_support_a1|test_unit_feedback|Data Seeding' -S . --glob '!**/node_modules/**' --glob '!**/dist/**' --glob '!**/build/**' --glob '!**/.git/**' | head -300

Repository: Practitionist/familiarise_mobile

Length of output: 1385


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== target SQL snippets =="
for f in \
  backend/prisma/sql/seed.d/01-auth.sql \
  backend/prisma/sql/seed.d/02-onboarding.sql \
  backend/prisma/sql/seed.d/03-profile.sql \
  backend/prisma/sql/seed.d/04-verification.sql \
  backend/prisma/sql/seed.d/05-plans.sql \
  backend/prisma/sql/seed.d/06-slots.sql \
  backend/prisma/sql/seed.d/15-reviews.sql \
  backend/prisma/sql/seed.d/16-support.sql \
  backend/prisma/sql/seed.d/17-feedback.sql
do
  if [ -f "$f" ]; then
    echo "--- $f"
    awk '{printf "%6d\t%s\n", NR, $0}' "$f" | sed -n '1,80p'
  else
    echo "MISSING $f"
  fi
done

echo
echo "== all seed account inserts and hash occurrences =="
python3 - <<'PY'
import pathlib, re
for p in sorted(pathlib.Path('backend/prisma/sql/seed.d').glob('*.sql')):
    text = p.read_text()
    for m in re.finditer(r'VALUES\s*\([^;]+accounts', text, re.S):
        snippet = text[m.start():m.end()]
        print(f'--- {p} @ line {text[:m.start()].count(chr(10))+1}')
        line = text[:m.start()].count('\n') + 1
        for j, line in enumerate(snippet.splitlines(), start=line):
            if 'test_unit_' in line or 'credential' in line or 'password' in line:
                print(f'{j}: {line}')
PY

echo
echo "== repository text occurrences for source prompt/seed generator hints =="
rg -n 'Data Seeding|seed\.d|seed generator|prisma/sql/seed\.d|credential|passwordHash|verify|bcrypt' -S . \
  --glob '!**/node_modules/**' --glob '!**/dist/**' --glob '!**/build/**' --glob '!**/.git/**' | head -300

Repository: Practitionist/familiarise_mobile

Length of output: 50390


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
import subprocess, re, pathlib
pwd = subprocess.check_output(['pwd'], text=True).strip()
print("PWD:", pwd)
for p in range(95, 66):
    try:
        subprocess.check_output(['python'+str(p % 10 if p>=10 else p), '--version'], stderr=subprocess.PIPE)
    except:
        pass
try:
    import bcrypt
    print("bcrypt module available:", bcrypt.__version__)
except Exception as e:
    print("bcrypt not available:", type(e).__name__, e)
    print("imports of cryptography available:", bool(importlib.util.find_spec("cryptography") if __import__("importlib").util else False))
PY

Repository: Practitionist/familiarise_mobile

Length of output: 584


🌐 Web query:

bcrypt Dart package BCrypt.checkpw hash format expected length checkpw returns false hash too short

💡 Result:

In the Dart bcrypt package, the BCrypt.checkpw function returns false if the provided hash is malformed, truncated, or otherwise does not conform to the standard bcrypt format [1][2]. Because bcrypt is a deterministic hashing algorithm, any change to the stored hash—including truncation—renders it invalid, causing the verification check to fail [3][4][5]. A standard bcrypt hash is expected to be 60 characters long [3][4][6]. If your database column storing the hash is too short (e.g., a VARCHAR(50) field), the hash will be truncated upon insertion, making it impossible to successfully verify against the original password later [3][4][5]. To resolve this issue, ensure your storage schema supports a minimum length of 60 characters (often recommended as TEXT or VARCHAR(60+)) [3][4][5]. Additionally, verify the following: 1. Hash Format: The hash string must follow the structure: $[algorithm]$[cost]$[salt][hash], such as $2a$10$r6huirn1laq6UXBVu6ga9.sHca6sr6tQl3Tiq9LB6/6LMpR37XEGu [2][6]. 2. Argument Order: Ensure you are passing the arguments in the correct order: BCrypt.checkpw(password, hashed) [2][4]. 3. Data Integrity: Check that the hash is not being modified, truncated, or subjected to extra encoding/decoding steps (such as lowercasing) before or after being saved to the database [4][5].

Citations:


Use valid 60-character bcrypt hashes in the seed fixtures.

The generated account rows in these seed files store a 58-character password instead of a valid bcrypt hash; BCrypt.checkpw rejects it, so these credential-authenticated test accounts cannot sign in. Update the shared source value in scripts/use-db.sh to a valid bcrypt.dart hash, then regenerate backend/prisma/sql/seed.d/; avoid editing the generated SQL files directly.

📍 Affects 9 files
  • backend/prisma/sql/seed.d/01-auth.sql#L9-L11 (this comment)
  • backend/prisma/sql/seed.d/02-onboarding.sql#L12-L15
  • backend/prisma/sql/seed.d/03-profile.sql#L10-L12
  • backend/prisma/sql/seed.d/04-verification.sql#L10-L12
  • backend/prisma/sql/seed.d/05-plans.sql#L10-L12
  • backend/prisma/sql/seed.d/06-slots.sql#L10-L12
  • backend/prisma/sql/seed.d/15-reviews.sql#L12-L15
  • backend/prisma/sql/seed.d/16-support.sql#L10-L12
  • backend/prisma/sql/seed.d/17-feedback.sql#L9-L11
🤖 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 `@backend/prisma/sql/seed.d/01-auth.sql` around lines 9 - 11, Replace the
shared password hash source in scripts/use-db.sh with a valid 60-character
bcrypt.dart hash, then regenerate backend/prisma/sql/seed.d/ so the credential
account fixtures use it consistently. Apply this across
backend/prisma/sql/seed.d/01-auth.sql (lines 9-11), 02-onboarding.sql (12-15),
03-profile.sql (10-12), 04-verification.sql (10-12), 05-plans.sql (10-12),
06-slots.sql (10-12), 15-reviews.sql (12-15), 16-support.sql (10-12), and
17-feedback.sql (9-11); do not edit generated SQL files directly.

Comment on lines +9 to +10
('test_unit_onb_cee', 'Onb Consultee', 'test_unit_onb_cee@test.com', true, 'CONSULTEE', false, NOW(), NOW()),
('test_unit_onb_cnt', 'Onb Consultant', 'test_unit_onb_cnt@test.com', true, 'CONSULTEE', false, NOW(), NOW());

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Assign the consultant fixture the CONSULTANT role.

test_unit_onb_cnt is labeled Onb Consultant but is inserted with role CONSULTEE. This causes consultant onboarding tests to run with the wrong role and permissions. Fix the source prompt and regenerate the seed SQL.

🤖 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 `@backend/prisma/sql/seed.d/02-onboarding.sql` around lines 9 - 10, Update the
source prompt for the test_unit_onb_cnt consultant fixture to use the CONSULTANT
role instead of CONSULTEE, then regenerate the onboarding seed SQL so the
generated insert matches the corrected role.

Comment on lines +12 to +15
INSERT INTO "accounts" (id, "userId", "accountId", "providerId", password, "createdAt", "updatedAt")
VALUES
('test_unit_explore_a1', 'test_unit_explore_u1', 'test_unit_explore_u1', 'credential', '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()),
('test_unit_explore_a2', 'test_unit_explore_u2', 'test_unit_explore_u2', 'credential', '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW());

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/sh
python3 - <<'PY'
from pathlib import Path
import re

for path in Path("backend/prisma/sql/seed.d").glob("*.sql"):
    for value in re.findall(r"\$2[aby]\$\d{2}\$[./A-Za-z0-9]+", path.read_text()):
        if len(value) != 60:
            print(f"{path}: invalid bcrypt length {len(value)}: {value}")
PY

Repository: Practitionist/familiarise_mobile

Length of output: 4388


🏁 Script executed:

#!/bin/sh
set -eu

echo "Candidate files:"
git ls-files backend/prisma schema.prisma | sed -n '1,120p'

echo
echo "Search password hashing/comparisons:"
rg -n "bcrypt|comparePassword|password|credentials|credential|passwordHash|setPassword|genSalt|hash" --glob '!node_modules/**' --glob '!dist/**' --glob '!build/**' .

echo
echo "Relevant auth files outline:"
for f in $(git ls-files | rg 'backend/.*/auth|backend/.*/credentials|credentials|auth' | head -50); do
  if [ -f "$f" ]; then
    echo "--- $ f"
    ast-grep outline "$f" 2>/dev/null | sed -n '1,80p' || true
  fi
done

Repository: Practitionist/familiarise_mobile

Length of output: 50389


🏁 Script executed:

#!/bin/sh
set -eu

echo "Backend auth service:"
sed -n '1,180p' backend/lib/services/auth/auth_service.dart

echo
echo "Backend profile password code (current/current-password path):"
sed -n '50,75p' backend/lib/services/profile/profile_service.dart

echo
echo "Seed rows with malformed hash count:"
python3 - <<'PY'
from pathlib import Path
import re
for p in sorted(Path("backend/prisma/sql/seed.d").glob("*.sql")):
    text = p.read_text()
    bad = re.findall(regex := r"\$2[aby]\$\d{2}\$[./A-Za-z0-9]+", text)
    bad = [h for h in bad if len(h) != 60]
    if bad:
        print(f"{p}: {len(bad)} malformed credential hash values")
PY

echo
echo "Markdown seed copy count:"
python3 - <<'PY'
from pathlib import Path, PurePosixPath
import re
patterns = ("backend/testing/unit", "prompts/testing/unit")
count = 0
for root in patterns:
    for p in Path(root).glob("backends/seed.d/*.sql"):
        bad = re.findall(r"\$2[aby]\$\d{2}\$[./A-Za-z0-9]+", p.read_text())
        bad = [h for h in bad if len(h) != 60]
        count += len(bad)
print("markdown seed copies:", count)
PY

Repository: Practitionist/familiarise_mobile

Length of output: 8382


Replace the malformed shared bcrypt hashes in credential seed fixtures.

$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z is 58 characters, but the credential sign-in path uses BCrypt.checkpw, so seeded accounts that use these hashes sign in as invalid credentials. Generate a valid bcrypt hash for the documented dev password and update the affected backend/prisma/sql/seed.d/*.sql credential rows.

📍 Affects 8 files
  • backend/prisma/sql/seed.d/07-explore.sql#L12-L15 (this comment)
  • backend/prisma/sql/seed.d/08-booking.sql#L12-L15
  • backend/prisma/sql/seed.d/09-checkout.sql#L12-L15
  • backend/prisma/sql/seed.d/10-trials.sql#L12-L15
  • backend/prisma/sql/seed.d/11-waitlist.sql#L13-L16
  • backend/prisma/sql/seed.d/12-documents.sql#L12-L15
  • backend/prisma/sql/seed.d/13-chat.sql#L10-L12
  • backend/prisma/sql/seed.d/14-referrals.sql#L10-L12
🤖 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 `@backend/prisma/sql/seed.d/07-explore.sql` around lines 12 - 15, Replace the
malformed shared bcrypt hash in credential seed rows with one valid bcrypt hash
for the documented development password, preserving the existing account data
and password semantics. Apply this update in
backend/prisma/sql/seed.d/07-explore.sql lines 12-15, 08-booking.sql lines
12-15, 09-checkout.sql lines 12-15, 10-trials.sql lines 12-15, 11-waitlist.sql
lines 13-16, 12-documents.sql lines 12-15, 13-chat.sql lines 10-12, and
14-referrals.sql lines 10-12.

Comment on lines +87 to +93
# Containers, pointed at Supabase instead:
FAM_DIRECT_URL='postgresql://…@aws-0-….pooler.supabase.com:5432/postgres' docker compose up

# Native backend:
source scripts/use-db.sh local
source scripts/use-db.sh supabase # needs backend/.env.supabase
```

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Remove or correct the unsupported Supabase Docker workflow.

FAM_DIRECT_URL=... docker compose up sends the remote URL to db-init, but docker/db-init/entrypoint.sh intentionally exits for every host except db, postgres, localhost, and 127.0.0.1. This documented command therefore cannot start the full stack as written. Document Supabase as native-backend-only, or add a safe Compose path that bypasses destructive provisioning for remote databases.

🤖 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 `@docs/getting-started/05-docker-local-dev.md` around lines 87 - 93, Remove the
Supabase-targeted Docker Compose command from the getting-started documentation
and state that Supabase is supported only with the native backend workflow using
scripts/use-db.sh supabase. Do not document a remote database Compose path
unless the db-init behavior is explicitly made safe for non-local hosts.

Comment on lines +110 to +115
There is no `migrations/` directory in this repo — `familiarise_web` owns
migrations. Provisioning therefore runs, in order:

1. `prisma db push` from the in-repo `backend/prisma/schema.prisma`, using
Prisma 7.7.0 to match `familiarise_web`. Takes well under a second and
produces 127 tables and 100 enums.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Reconcile the documented table count.

docker/db-init/entrypoint.sh states that provisioning creates 129 tables, while this guide says 127. Use one source-of-truth count, or omit the volatile count to avoid misleading schema-drift diagnosis.

🤖 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 `@docs/getting-started/05-docker-local-dev.md` around lines 110 - 115,
Reconcile the schema count in the provisioning documentation with the 129-table
value reported by docker/db-init/entrypoint.sh, using a shared source of truth
if practical; otherwise remove the volatile table count while retaining the
prisma db push instructions.

Comment on lines +135 to +138
Those are **stale prompts, not provisioning failures** — the model is
`SupportTicket`, not `support_tickets`. Fixing the prompts is worthwhile but out
of scope here; running them per-block means the drift is visible and costs one
fixture set instead of the entire seed.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Describe the seed state as incomplete.

Four of 23 prompt seed files currently fail, so the database is only partially seeded. Calling these “not provisioning failures” may mislead developers whose tests or seeded endpoints depend on those fixtures; identify the unavailable fixtures or make the failures fatal.

🤖 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 `@docs/getting-started/05-docker-local-dev.md` around lines 135 - 138, Update
the Docker local development seed documentation to explicitly describe the seed
state as incomplete when prompt seed files fail, including that four of 23
fixtures are currently unavailable and may affect dependent tests or seeded
endpoints. Do not characterize these failures solely as stale prompts or
non-provisioning issues; either identify the missing fixtures or document that
the failures are fatal.

Comment thread scripts/dev-backend.sh
Comment on lines +22 to +36
# Export .env, then .env.local, without clobbering anything already exported —
# an explicit `source scripts/use-db.sh` must still win.
load_env() {
[ -f "$1" ] || return 0
while IFS= read -r line; do
case "$line" in ''|'#'*) continue ;; esac
key=${line%%=*}
key=${key// /}
[ -n "${!key:-}" ] && continue # already exported: leave it alone
export "${key?}=${line#*=}"
done < "$1"
}

load_env .env
load_env .env.local

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Restore .env.local override precedence.

Loading .env first exports its keys, so line 30 skips the corresponding .env.local values. A developer’s local DIRECT_URL therefore cannot override a shared hosted URL. Reverse the load order under this “only set if absent” implementation, and preserve explicitly empty inherited environment variables as overrides too.

🤖 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 `@scripts/dev-backend.sh` around lines 22 - 36, Update the load_env calls in
the dev-backend environment-loading flow to load .env.local before .env,
allowing local values to remain effective when the later file only sets absent
variables. Adjust the presence check in load_env so an explicitly inherited
empty variable is treated as already set and is not overwritten.

Comment thread scripts/gen-dev-seed.sh
Comment on lines +19 to +21
OUT_DIR=backend/prisma/sql/seed.d
rm -rf "$OUT_DIR"
mkdir -p "$OUT_DIR"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Generate into a staging directory before replacing fixtures.

An awk, grep, or Python failure after Line 20 exits under set -e and leaves backend/prisma/sql/seed.d empty or partial. Build and validate in a temporary sibling directory, then atomically replace OUT_DIR only after the full loop succeeds.

🤖 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 `@scripts/gen-dev-seed.sh` around lines 19 - 21, Update the gen-dev-seed.sh
flow around OUT_DIR so generation occurs in a temporary sibling staging
directory rather than deleting seed.d upfront. Run the complete generation and
validation loop against the staging directory, and only after success atomically
replace OUT_DIR with the validated staging contents; preserve the existing
fixture output when any command fails.

@teetangh teetangh self-assigned this Jul 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant