feat(attendance): add notification delivery and NFC security foundation

This commit is contained in:
zouantchaw
2026-03-16 17:06:17 +01:00
parent 5d8240ed51
commit 73287f42bd
21 changed files with 1734 additions and 36 deletions

View File

@@ -22,16 +22,17 @@ What was validated live against the deployed stack:
- staff auth bootstrap
- client dashboard, billing, coverage, hubs, vendors, managers, team members, orders, and reports
- client coverage incident feed for geofence and override review
- client hub, order, coverage review, and late-worker cancellation flows
- client hub, order, coverage review, device token, and late-worker cancellation flows
- client invoice approve and dispute
- staff dashboard, availability, payments, shifts, profile sections, documents, certificates, attire, bank accounts, benefits, and time card
- staff availability, profile, tax form, bank account, shift apply, shift accept, clock-in, clock-out, location stream upload, and swap request
- staff availability, profile, tax form, bank account, shift apply, shift accept, push token registration, clock-in, clock-out, location stream upload, and swap request
- direct file upload helpers and verification job creation through the unified host
- client and staff sign-out
The live validation command is:
```bash
export FIREBASE_WEB_API_KEY="$(gcloud secrets versions access latest --secret=firebase-web-api-key --project=krow-workforce-dev)"
source ~/.nvm/nvm.sh
nvm use 23.5.0
node backend/unified-api/scripts/live-smoke-v2-unified.mjs
@@ -101,10 +102,12 @@ Important operational rules:
- outside-geofence clock-ins can be accepted only when override is enabled and a written reason is provided
- NFC mismatches are rejected and are not overrideable
- attendance proof logs are durable in SQL and raw object storage
- device push tokens are durable in SQL and can be registered separately for client and staff apps
- background location streams are stored as raw batch payloads in the private v2 bucket and summarized in SQL for query speed
- incident review lives on `GET /client/coverage/incidents`
- confirmed late-worker recovery is exposed on `POST /client/coverage/late-workers/:assignmentId/cancel`
- queued manager alerts are written to `notification_outbox`; this is durable notification orchestration, not a full push delivery worker yet
- queued alerts are written to `notification_outbox`, dispatched by Cloud Run job `krow-notification-dispatcher-v2`, and recorded in `notification_deliveries`
## 5) Route model

View File

@@ -60,6 +60,8 @@ The gateway keeps backend services separate internally, but frontend should trea
### Client writes
- `POST /client/devices/push-tokens`
- `DELETE /client/devices/push-tokens`
- `POST /client/orders/one-time`
- `POST /client/orders/recurring`
- `POST /client/orders/permanent`
@@ -113,6 +115,8 @@ The gateway keeps backend services separate internally, but frontend should trea
### Staff writes
- `POST /staff/profile/setup`
- `POST /staff/devices/push-tokens`
- `DELETE /staff/devices/push-tokens`
- `POST /staff/clock-in`
- `POST /staff/clock-out`
- `POST /staff/location-streams`
@@ -170,11 +174,40 @@ These are exposed as direct unified aliases even though they are backed by `core
- For `POST /staff/clock-in` and `POST /staff/clock-out`:
- send `nfcTagId` when clocking with NFC
- send `latitude`, `longitude`, and `accuracyMeters` when clocking with geolocation
- send `proofNonce` and `proofTimestamp` for attendance-proof logging; these are most important on NFC paths
- send `attestationProvider` and `attestationToken` only when the device has a real attestation result to forward
- send `overrideReason` only when the worker is bypassing a geofence failure and the shift/hub allows overrides
- `POST /staff/location-streams` is for the background tracking loop after a worker is already clocked in.
- `GET /client/coverage/incidents` is the review feed for geofence breaches, missing-location batches, and clock-in overrides.
- `POST /client/coverage/late-workers/:assignmentId/cancel` is the client-side recovery action when lateness is confirmed by incident evidence or elapsed grace time.
- Raw location stream payloads are stored in the private v2 bucket; SQL only stores the summary and incident index.
- Push delivery is backed by:
- SQL token registry in `device_push_tokens`
- durable queue in `notification_outbox`
- per-attempt delivery records in `notification_deliveries`
- Cloud Run job `krow-notification-dispatcher-v2`
### Push token request example
```json
{
"provider": "FCM",
"platform": "IOS",
"pushToken": "expo-or-fcm-device-token",
"deviceId": "iphone-15-pro-max",
"appVersion": "2.0.0",
"appBuild": "2000",
"locale": "en-US",
"timezone": "America/Los_Angeles"
}
```
Push-token delete requests may send `tokenId` or `pushToken` either:
- as JSON in the request body
- or as query params on the `DELETE` URL
Using query params is safer when the client stack or proxy is inconsistent about forwarding `DELETE` bodies.
### Clock-in request example
@@ -186,6 +219,8 @@ These are exposed as direct unified aliases even though they are backed by `core
"latitude": 37.4221,
"longitude": -122.0841,
"accuracyMeters": 12,
"proofNonce": "nonce-generated-on-device",
"proofTimestamp": "2026-03-16T09:00:00.000Z",
"overrideReason": "Parking garage entrance is outside the marked hub geofence",
"capturedAt": "2026-03-16T09:00:00.000Z"
}