refinement phase 7

This commit is contained in:
hamid
2026-07-13 17:48:50 +03:30
parent 70268ecc06
commit 7edadadea1
30 changed files with 6971 additions and 154 deletions
@@ -12,6 +12,24 @@ One block per completed backend phase. Newest at the top. Backend lane writes he
- **Notes for frontend:** <anything load-bearing>
-->
## refinement-phase-7 — Unattended ops: scheduler, locking & multi-instance readiness — 2026-07-13
- **Shipped:** one in-process **`RecurringJobSchedulerHostedService`** + the **`IRecurringJob`** seam
(`Persistence/Services/Scheduling/`) replacing the two `PeriodicTimer` hosted services and scheduling the
previously admin-manual crons — `booking_request_expiry`, `notification_retention`, `verification_expiry_scan`,
`no_show_sweep`, `weekly_payout_generation` — each reading its seeded cadence key; admin triggers stay overrides.
Payout **generation** is scheduled (system-initiated `draft`); **processing** stays admin-only
(`NursePayoutBatch.InitiatedByAdminId` now nullable = system; `SystemInitiated` flag is scheduler-only,
controller-neutralized). **Migrations split from boot:** `dotnet run -- migrate` one-shot + deployed-boot schema
*check* (`EnsureSchemaUpToDateAsync`); Dev keeps migrate-on-boot. **No Redis/Hangfire added** — documented as the
>1-instance scale-out gate; the scheduler's per-tick `IDistributedLock` is that swap point.
- **Contracts:** `PayoutBatchDto.initiatedByAdminId` nullable — `dev/contracts/domains/payouts.md` + openapi
snapshot refreshed (yes).
- **Mocked:** none new. Redis = scale gate; Moadian/refund-settlement poll = Phase 8 jobs (see mocks-registry).
- **Gate:** build clean (0 new warnings) / **402 tests pass** (396 prior + 6 new scheduling tests).
- **Handoff:** backend/handoff/after-refinement-phase-7.md
- **Notes for frontend:** admin payout batch `initiatedByAdminId` can be `null` (system/scheduled batch) — render
a "system"/"scheduled" label rather than assuming an admin id.
## refinement-phase-1 — Database: local-dev story, demo seed & migration hygiene — 2026-07-13
- **Shipped (no migration, no endpoint, no contract change):** Development-gated **demo-world seeder**
`Persistence/Services/Seeding/DemoWorldSeeder.cs` + `DemoWorldDefinitions.cs`, scoped-registered in
@@ -0,0 +1,38 @@
# After refinement-phase-7 — Unattended operation (scheduler, locking, migrations-from-boot)
**For the frontend / next backend phase. Backend-owned; frontend reads.**
## What changed for a client
Almost nothing user-facing — this is infrastructure. One wire delta:
- **`PayoutBatchDto.initiatedByAdminId` is now nullable.** `null` = a **system-initiated / scheduled** payout
batch (the weekly cron generated it, no human initiator). Admin payout UIs should render a "system"/"scheduled"
label instead of assuming an admin id. Contract + `swagger.v1.json` updated.
## What the platform now does on its own
The four previously admin-click-only sweeps run on schedule (each reading its `platform_configs` cadence key):
credential-expiry scan, EVV no-show sweep, and **weekly payout-batch generation** — plus the two re-homed sweeps
(booking-request expiry, notification retention). **Admin manual triggers are unchanged and remain overrides.**
- **Payout generation only.** The cron opens a `draft` batch; **processing (money movement) is still an explicit
admin action** (`POST admin_payouts/batches/{id}/process`). Do not build a client flow that auto-processes.
## For the next backend phase (Phase 8 — external rails)
- **Register new crons via the seam, not a new host.** Implement `IRecurringJob`
(`Persistence/Services/Scheduling/`) + one `services.AddSingleton<IRecurringJob, YourJob>()` in
`AddPersistenceServices`. Phase 8 owns the **Moadian reconciliation poll** and the **refund-settlement
reconciliation** this way (each reads/adds its own cadence key). The scheduler already provides the per-tick
scope, the `scheduler:{name}` lock, and error isolation.
- **Jobs must stay idempotent** — a retry (or a second instance once the lock is Redis-backed) must never
double-pay/double-post; the DB uniques/state-machines are the backstop.
## Ops / deployment
- **DDL is a deploy step now:** run `dotnet run -- migrate` (applies migrations + idempotent seeders, then exits)
before starting the API in a deployed environment. A normal deployed boot only *checks* the schema and **fails
fast** if a migration is pending. Development still migrates + seeds on boot.
- **Redis is the >1-instance gate** (shared cache + the cross-instance scheduler/money lock). Single-instance MVP
does not need it; the in-proc seams are correct for one instance. Elasticsearch is never MVP.