11 KiB
Decisions — the distilled engineering decision log
Last verified: 2026-08-02 against commit
51e86a1.
Non-obvious decisions with a reason, extracted from dev/'s ~3MB of build history so they survive
dev/'s move to archive/ in phase 6. product/ wins for business rules — this file is for
engineering decisions, and for business decisions made during the build that never made it back into
product/ (each of those also gets a note filed in product/notes/).
Each entry: what was decided, when, why, where it binds.
Business rules made during the build (candidates for product/ too)
Commission 0.15 / VAT 0.10, VAT on commission only. The canonical fee model as of refinement-phase-3:
platform commission is 15% of the gross booking amount; VAT is 10% of the commission, not of gross. This
became the single source of truth after the pre-refinement code computed VAT two different ways on two
surfaces (carved out of commission at checkout vs. added to it on the invoice, a 341 IRR disagreement on one
seeded booking — see backlog.md BL-060, still open on the invoice screen specifically). Binds:
server/src/Core/Baya.Application/Features/Payments/, every invoice/checkout DTO.
district_id = NULL means whole-city, in both directions. A nurse service area with no district covers
every district in the city; a search with no district filter matches both district-scoped and whole-city
rows. Binds: nurse_service_areas, SqlNurseSearch, the coverage-picker UI. See
backlog.md BL-077 for the one place this invariant currently double-counts a nurse.
Verification status is the source of truth; is_verified is a guarded flip. The nurse-facing status
enum drives all business logic; nurse_profiles.is_verified only flips inside the same transaction that
moves status to approved, never independently. Decided backend-phase-6. Binds: NurseVerificationService,
nurse_search_index.is_searchable.
Booking status is forward-only; the three-amount split has a DB CHECK constraint. No booking status
transition may move backward, enforced in BookingTransitions/BookingSessionTransitions
(CONVENTIONS §6 pattern). The gross/platform_commission/nurse_payout three-way split on a booking is
CHECK-constrained to balance at the DB layer, not just in application code. Decided backend-phase-9.
The two-stage clinical-disclosure gate; EVV is advisory, never a block. Care details are disclosed in two
stages — coarse notes pre-acceptance, full encrypted booking_care_instructions only to the assigned nurse
post-confirmation. A geofence mismatch on check-in/out (EVV) raises a support alert but never blocks the
visit from proceeding — decided explicitly to avoid a GPS false-positive stranding a nurse mid-shift. Binds:
booking_requests vs booking_care_instructions, CheckInVisitCommand/CheckOutVisitCommand.
Webhook idempotency is upsert-first; ledger postings must balance. Every external webhook handler
(payment PSP, BNPL, payout transfer) upserts on the provider's idempotency key before any side effect, so a
replayed webhook is a no-op rather than a double-post. Every LedgerPosting is validated to balance (debits
= credits) before commit — decided backend-phase-10, the same phase that found the payment-webhook confirm
path needs two DB commits (booking creation, then transaction+ledger) because ledger legs need the
DB-generated booking_id; flagged as future hardening once IUnitOfWork grows a transaction scope (see
backlog.md BL-253).
Reviews recompute from source; nurse care records are append-only. A nurse's average_rating/
total_reviews are always recomputed from the live review rows, never incrementally maintained, to avoid
drift. Visit-note/care-record writes are append-only — no record is ever edited or deleted, only superseded
by a newer entry. Decided backend-phase-14.
is_internal ticket messages never appear in user-facing types. The admin-only internal-note boundary on
a support ticket is enforced by keeping isInternal out of every client-facing TypeScript type entirely —
not by a runtime filter that could be bypassed. Decided frontend-phase-14, re-confirmed frontend-phase-15
when the admin console was built on top of the same domain.
One payout per booking (UNIQUE); whole-clawback greedy netting. A nurse_payouts row is
UNIQUE-constrained to one per booking — a booking can never be paid out twice. Clawback recovery uses
greedy whole-amount netting against the next batch rather than partial installments. Decided
backend-phase-13; automated recovery beyond simple netting is deferred (see backlog.md
BL-242).
Escrow releases after a confirmed check-out; weekly payout generation is automatic, processing stays
manual. Funds move from escrow_held to nurse_payable only after a checked-out session passes its
dispute window. Payout batch generation runs on a weekly cron (refinement-phase-7); actually transferring
money in a batch stays a deliberate, explicit admin action by design — decided refinement-phase-7,
re-affirmed in the backend-phase-13 handoff. Not a bug; see backlog.md BL-240.
Config lives in files, not a secret store — deliberate pre-launch trade. dotnet user-secrets was
removed (<UserSecretsId> dropped from the .csproj); all configuration, including live credentials, lives
in appsettings.*.json / .env.* / docker-compose.yml. This is explicitly temporary — root
CLAUDE.md §6 requires rotating every credential and moving the secret half out of git
before real users (see backlog.md BL-003). The one value that must never change once real
data exists: Seams:FieldEncryption:Key/:HashKey.
Error state is never an empty state (the client convention). A failed query must never silently render
the same UI as "no data" — decided in the frontend-phase-1 primitives pass. Still occasionally violated; see
backlog.md BL-078 for the one live regression found this phase (nurse-service-areas's
coverage screen drops isError).
Root / forks by auth via a middleware rewrite, never a redirect. An anonymous visitor to / gets the
public landing page's content rewritten in at the same URL; the canonical URL and address bar never change.
Decided ui-phase-13, chosen specifically so / stays a stable, shareable, indexable URL for both audiences.
Engineering decisions
Phase 2 → Phase 4 handoff, "the rename is filed." The placeholder secret sentinel
SET_VIA_USER_SECRETS_OR_ENV keeps its name — renaming it needs a server-code + test change that is out of
a documentation phase's scope, and the string is load-bearing across several live files. Filed as
backlog.md BL-219, deferred (cleanup-of-convenience, no urgency).
Phase 7 dropped the pre-commit secret-scan hook — MVP stage, no need for it yet. .githooks/pre-commit
(the qw123321/private-key/AWS-key/SQL-host/connection-string scan) and .githooks/README.md were deleted
outright, along with every doc reference to them (root CLAUDE.md's repo-layout table and quick start,
git-and-gates.md, documentation.md,
identity.md, config-matrix.md,
DEPLOY.md). The underlying trade this repo already made — committed live credentials,
config in files not a secret store (root CLAUDE.md §6) — is unchanged; this only removes
the local mechanical backstop against a new leak. Revisit before onboarding real users, alongside the
credential rotation already required by that trade.
Hardening ledger re-verification (C-10) confirms the ledger was right to distrust its own checkboxes.
All 18 hardening items were re-traced against b876490 rather than trusted as-filed: 3 were already fixed
(client-only, landed in the "manual improvement" commits well before this doc chain started), 4 are
partially fixed (real progress with concrete residue), and 11 are unchanged since 2026-07-16 despite 14 UI
phases, 2 manual-testing iterations, and a deploy running on top of them. See
backlog-closed.md and backlog.md for the full disposition of each.
REQ-061 (admin user directory) is filed for real, despite never getting a ledger header (C-15).
ui-phase-11-report.md claimed "REQ-061…064 appended," but the append-only ledger's numbering jumps
060→062 — REQ-061's body survived as an orphaned, headerless block. Ten live client files depend on the
endpoints it describes. Ruling: it is a genuine, currently-undelivered backend gap and is carried into
backlog.md as BL-029, not treated as a documentation artifact to discard.
Five REQs were filed narrower or wrong relative to the live server (C-16). REQ-050, REQ-063, REQ-066,
REQ-067 and the REQ-029/030 admin-mock justification were all checked against the live swagger rather than
against another document, during phase 2. variantLabel already shipped before REQ-050 was filed;
tickets/close+/reopen already ship (only assign is missing); search/nurses and
nurses/{id}/profile are already anonymous (only rate-limiting and a privacy sign-off are missing,
respectively). Ruling: REQ status is derived from code, never copied from the ledger's own prose — this
phase's REQ classification (see backlog.md and backlog-closed.md)
followed the same rule throughout, and found one further instance on its own: REQ-050's "neither field
exists" claim was stale even for the field it explicitly named (variantLabel), and REQ-057's residue was
found duplicated across two unrelated flows independently citing the same missing DTO field.
A true mock flag does not mean the domain is fully fake, and a false flag does not mean it's fully
real. verification has 10 of 14 operations live and probed while mocked; payment is flag-real with only
2 of 6 operations actually working. Decided as the framing for this whole reconciliation (phase 3's finding)
— every BL item in this backlog that touches a USE_*_MOCK flag states the specific operations affected,
never just cites the flag.
Deferred items keep their pull-trigger, not a target date. Following the phase-chain convention set in
dev/shared-working-context/backend/handoff/, every item in backlog.md's Deferred section
carries the condition that should cause it to be picked up (a specific product decision, a scale threshold,
a second integration) rather than a scheduled date. Phase 5 owns turning these into a sequenced roadmap.
BL-245's own trigger ("phase 5 verification pass") was executed during phase 5. ResolveSupportAlert,
AssignSupportAlert, and nurse suspension (AdminSuspendVerificationCommand) were all confirmed real and
code-traced; only FlagConcern is genuinely unbuilt. Recorded here rather than left as an open question in
roadmap/deferred.md — a small, direct illustration of "verify, don't copy" applied
one phase later than the item that requested it.