seed data
This commit is contained in:
365
docs/BACKEND/API_GUIDES/00-initial-api-contracts.md
Normal file
365
docs/BACKEND/API_GUIDES/00-initial-api-contracts.md
Normal file
@@ -0,0 +1,365 @@
|
||||
# KROW Workforce API Contracts
|
||||
|
||||
This document captures all API contracts used by the Staff and Client mobile applications. The application backend is powered by **Firebase Data Connect (GraphQL)**, so traditional REST endpoints do not exist natively. For clarity and ease of reading for all engineering team members, the tables below formulate these GraphQL Data Connect queries and mutations into their **Conceptual REST Endpoints** alongside the actual **Data Connect Operation Name**.
|
||||
|
||||
---
|
||||
|
||||
## Staff Application
|
||||
|
||||
### Authentication / Onboarding Pages
|
||||
*(Pages: get_started_page.dart, intro_page.dart, phone_verification_page.dart, profile_setup_page.dart)*
|
||||
|
||||
#### Setup / User Validation API
|
||||
| Field | Description |
|
||||
|---|---|
|
||||
| **Conceptual Endpoint** | `GET /users/{id}` |
|
||||
| **Data Connect OP** | `getUserById` |
|
||||
| **Purpose** | Retrieves the base user profile to determine authentication status and role access (e.g., if the user is STAFF). |
|
||||
| **Operation** | Query |
|
||||
| **Inputs** | `id: UUID!` (Firebase UID) |
|
||||
| **Outputs** | `User { id, email, phone, role }` |
|
||||
| **Notes** | Required after OTP verification to route users appropriately. |
|
||||
|
||||
#### Create Default User API
|
||||
| Field | Description |
|
||||
|---|---|
|
||||
| **Conceptual Endpoint** | `POST /users` |
|
||||
| **Data Connect OP** | `createUser` |
|
||||
| **Purpose** | Inserts a base user record into the system during initial signup. |
|
||||
| **Operation** | Mutation |
|
||||
| **Inputs** | `id: UUID!`, `role: UserBaseRole` |
|
||||
| **Outputs** | `id` of newly created User |
|
||||
| **Notes** | Used explicitly during the "Sign Up" flow if the user doesn't physically exist in the database. |
|
||||
|
||||
#### Get Staff Profile API
|
||||
| Field | Description |
|
||||
|---|---|
|
||||
| **Conceptual Endpoint** | `GET /staff/user/{userId}` |
|
||||
| **Data Connect OP** | `getStaffByUserId` |
|
||||
| **Purpose** | Finds the specific Staff record associated with the base user ID. |
|
||||
| **Operation** | Query |
|
||||
| **Inputs** | `userId: UUID!` |
|
||||
| **Outputs** | `Staffs { id, userId, fullName, email, phone, photoUrl, status }` |
|
||||
| **Notes** | Needed to verify if a complete staff profile exists before allowing navigation to the main app dashboard. |
|
||||
|
||||
#### Update Staff Profile API
|
||||
| Field | Description |
|
||||
|---|---|
|
||||
| **Conceptual Endpoint** | `PUT /staff/{id}` |
|
||||
| **Data Connect OP** | `updateStaff` |
|
||||
| **Purpose** | Saves onboarding data across Personal Info, Experience, and Preferred Locations pages. |
|
||||
| **Operation** | Mutation |
|
||||
| **Inputs** | `id: UUID!`, `fullName`, `email`, `phone`, `address`, etc. |
|
||||
| **Outputs** | `id` |
|
||||
| **Notes** | Called incrementally during the profile setup wizard as the user fills out step-by-step information. |
|
||||
|
||||
### Home Page & Benefits Overview
|
||||
*(Pages: worker_home_page.dart, benefits_overview_page.dart)*
|
||||
|
||||
#### Load Today/Tomorrow Shifts
|
||||
| Field | Description |
|
||||
|---|---|
|
||||
| **Conceptual Endpoint** | `GET /staff/{staffId}/applications` |
|
||||
| **Data Connect OP** | `getApplicationsByStaffId` |
|
||||
| **Purpose** | Retrieves applications (shifts) assigned to the current staff member within a specific date range. |
|
||||
| **Operation** | Query |
|
||||
| **Inputs** | `staffId: UUID!`, `dayStart: Timestamp`, `dayEnd: Timestamp` |
|
||||
| **Outputs** | `Applications { shift, shiftRole, status, createdAt }` |
|
||||
| **Notes** | The frontend filters the query response for `CONFIRMED` applications to successfully display "Today's" and "Tomorrow's" shifts. |
|
||||
|
||||
#### List Recommended Shifts
|
||||
| Field | Description |
|
||||
|---|---|
|
||||
| **Conceptual Endpoint** | `GET /shifts/recommended` |
|
||||
| **Data Connect OP** | `listShifts` |
|
||||
| **Purpose** | Fetches open shifts that are available for the staff to apply to. |
|
||||
| **Operation** | Query |
|
||||
| **Inputs** | None directly mapped on load, but fetches available items logically. |
|
||||
| **Outputs** | `Shifts { id, title, orderId, cost, location, startTime, endTime, status }` |
|
||||
| **Notes** | Limits output to 10 on the frontend. Should ideally rely on an active backend `$status: OPEN` parameter. |
|
||||
|
||||
#### Benefits Summary API
|
||||
| Field | Description |
|
||||
|---|---|
|
||||
| **Conceptual Endpoint** | `GET /staff/{staffId}/benefits` |
|
||||
| **Data Connect OP** | `listBenefitsDataByStaffId` |
|
||||
| **Purpose** | Retrieves accrued benefits (e.g., Sick time, Vacation) to display gracefully on the home screen. |
|
||||
| **Operation** | Query |
|
||||
| **Inputs** | `staffId: UUID!` |
|
||||
| **Outputs** | `BenefitsDatas { vendorBenefitPlan { title, total }, current }` |
|
||||
| **Notes** | Used by `benefits_overview_page.dart`. Derives available metrics via `usedHours = total - current`. |
|
||||
|
||||
### Find Shifts / Shift Details Pages
|
||||
*(Pages: shifts_page.dart, shift_details_page.dart)*
|
||||
|
||||
#### List Available Shifts Filtered
|
||||
| Field | Description |
|
||||
|---|---|
|
||||
| **Conceptual Endpoint** | `GET /shifts` |
|
||||
| **Data Connect OP** | `filterShifts` |
|
||||
| **Purpose** | Used to fetch Open Shifts in specific regions when the worker searches in the "Find Shifts" tab. |
|
||||
| **Operation** | Query |
|
||||
| **Inputs** | `$status: ShiftStatus`, `$dateFrom: Timestamp`, `$dateTo: Timestamp` |
|
||||
| **Outputs** | `Shifts { id, title, location, cost, durationDays, order { business, vendor } }` |
|
||||
| **Notes** | Main driver for discovering available work. |
|
||||
|
||||
#### Get Shift Details
|
||||
| Field | Description |
|
||||
|---|---|
|
||||
| **Conceptual Endpoint** | `GET /shifts/{id}` |
|
||||
| **Data Connect OP** | `getShiftById` |
|
||||
| **Purpose** | Gets deeper details for a single shift including exact uniform requirements and managers. |
|
||||
| **Operation** | Query |
|
||||
| **Inputs** | `id: UUID!` |
|
||||
| **Outputs** | `Shift { id, title, hours, cost, locationAddress, workersNeeded ... }` |
|
||||
| **Notes** | Invoked when users click into a full `shift_details_page.dart`. |
|
||||
|
||||
#### Apply To Shift
|
||||
| Field | Description |
|
||||
|---|---|
|
||||
| **Conceptual Endpoint** | `POST /applications` |
|
||||
| **Data Connect OP** | `createApplication` |
|
||||
| **Purpose** | Worker submits an intent to take an open shift (creates an application record). |
|
||||
| **Operation** | Mutation |
|
||||
| **Inputs** | `shiftId: UUID!`, `staffId: UUID!`, `roleId: UUID!`, `status: ApplicationStatus!` (e.g. `PENDING` or `CONFIRMED`), `origin: ApplicationOrigin!` (e.g. `STAFF`); optional: `checkInTime`, `checkOutTime` |
|
||||
| **Outputs** | `application_insert.id` (Application ID) |
|
||||
| **Notes** | The app uses `status: CONFIRMED` and `origin: STAFF` when claiming; backend also supports `PENDING` for admin review flows. After creation, shift-role assigned count and shift filled count are updated. |
|
||||
|
||||
### Availability Page
|
||||
*(Pages: availability_page.dart)*
|
||||
|
||||
#### Get Default Availability
|
||||
| Field | Description |
|
||||
|---|---|
|
||||
| **Conceptual Endpoint** | `GET /staff/{staffId}/availabilities` |
|
||||
| **Data Connect OP** | `listStaffAvailabilitiesByStaffId` |
|
||||
| **Purpose** | Fetches the standard Mon-Sun recurring availability for a staff member. |
|
||||
| **Operation** | Query |
|
||||
| **Inputs** | `staffId: UUID!` |
|
||||
| **Outputs** | `StaffAvailabilities { dayOfWeek, isAvailable, startTime, endTime }` |
|
||||
| **Notes** | Bound to Monday through Sunday configuration. |
|
||||
|
||||
#### Update Availability
|
||||
| Field | Description |
|
||||
|---|---|
|
||||
| **Conceptual Endpoint** | `PUT /staff/availabilities/{id}` |
|
||||
| **Data Connect OP** | `updateStaffAvailability` (or `createStaffAvailability` for new entries) |
|
||||
| **Purpose** | Upserts availability preferences. |
|
||||
| **Operation** | Mutation |
|
||||
| **Inputs** | `staffId`, `dayOfWeek`, `isAvailable`, `startTime`, `endTime` |
|
||||
| **Outputs** | `id` |
|
||||
| **Notes** | Called individually per day edited. |
|
||||
|
||||
### Payments Page
|
||||
*(Pages: payments_page.dart, early_pay_page.dart)*
|
||||
|
||||
#### Get Recent Payments
|
||||
| Field | Description |
|
||||
|---|---|
|
||||
| **Conceptual Endpoint** | `GET /staff/{staffId}/payments` |
|
||||
| **Data Connect OP** | `listRecentPaymentsByStaffId` |
|
||||
| **Purpose** | Loads the history of earnings and timesheets completed by the staff. |
|
||||
| **Operation** | Query |
|
||||
| **Inputs** | `staffId: UUID!` |
|
||||
| **Outputs** | `Payments { amount, processDate, shiftId, status }` |
|
||||
| **Notes** | Displays historical metrics under the comprehensive Earnings tab. |
|
||||
|
||||
### Compliance / Profiles
|
||||
*(Pages: certificates_page.dart, documents_page.dart, tax_forms_page.dart, form_i9_page.dart, form_w4_page.dart)*
|
||||
|
||||
#### Get Tax Forms
|
||||
| Field | Description |
|
||||
|---|---|
|
||||
| **Conceptual Endpoint** | `GET /staff/{staffId}/tax-forms` |
|
||||
| **Data Connect OP** | `getTaxFormsByStaffId` |
|
||||
| **Purpose** | Check the filing status and detailed inputs of I9 and W4 forms. |
|
||||
| **Operation** | Query |
|
||||
| **Inputs** | `staffId: UUID!` |
|
||||
| **Outputs** | `TaxForms { formType, isCompleted, updatedDate }` |
|
||||
| **Notes** | Crucial requirement for staff to be eligible to apply for highly regulated shifts. |
|
||||
|
||||
#### Update Tax Forms
|
||||
| Field | Description |
|
||||
|---|---|
|
||||
| **Conceptual Endpoint** | `PUT /tax-forms/{id}` |
|
||||
| **Data Connect OP** | `updateTaxForm` |
|
||||
| **Purpose** | Submits state and filing for the given tax form type (W4/I9). |
|
||||
| **Operation** | Mutation |
|
||||
| **Inputs** | `id`, `dataPoints...` |
|
||||
| **Outputs** | `id` |
|
||||
| **Notes** | Modifies the core compliance state variables directly. |
|
||||
|
||||
---
|
||||
|
||||
## Client Application
|
||||
|
||||
### Authentication / Intro
|
||||
*(Pages: client_sign_in_page.dart, client_get_started_page.dart)*
|
||||
|
||||
#### Client User Validation API
|
||||
| Field | Description |
|
||||
|---|---|
|
||||
| **Conceptual Endpoint** | `GET /users/{id}` |
|
||||
| **Data Connect OP** | `getUserById` |
|
||||
| **Purpose** | Retrieves the base user profile to determine authentication status and role access (ensuring user is BUSINESS). |
|
||||
| **Operation** | Query |
|
||||
| **Inputs** | `id: UUID!` (Firebase UID) |
|
||||
| **Outputs** | `User { id, email, phone, userRole }` |
|
||||
| **Notes** | Validates against conditional statements checking `userRole == BUSINESS` or `BOTH`. |
|
||||
|
||||
#### Get Businesses By User API
|
||||
| Field | Description |
|
||||
|---|---|
|
||||
| **Conceptual Endpoint** | `GET /business/user/{userId}` |
|
||||
| **Data Connect OP** | `getBusinessesByUserId` |
|
||||
| **Purpose** | Maps the authenticated user to their client business context. |
|
||||
| **Operation** | Query |
|
||||
| **Inputs** | `userId: String!` |
|
||||
| **Outputs** | `Businesses { id, businessName, email, contactName }` |
|
||||
| **Notes** | Dictates the working scopes (Business ID) across the entire application lifecycle and binds the user. |
|
||||
|
||||
### Hubs Page
|
||||
*(Pages: client_hubs_page.dart, edit_hub_page.dart, hub_details_page.dart)*
|
||||
|
||||
#### List Hubs by Team
|
||||
| Field | Description |
|
||||
|---|---|
|
||||
| **Conceptual Endpoint** | `GET /teams/{teamId}/hubs` |
|
||||
| **Data Connect OP** | `getTeamHubsByTeamId` |
|
||||
| **Purpose** | Fetches the primary working sites (Hubs) for a client context by using Team mapping. |
|
||||
| **Operation** | Query |
|
||||
| **Inputs** | `teamId: UUID!` |
|
||||
| **Outputs** | `TeamHubs { id, hubName, address, managerName, isActive }` |
|
||||
| **Notes** | `teamId` is derived first from `getTeamsByOwnerId(ownerId: businessId)`. |
|
||||
|
||||
#### Create / Update / Delete Hub
|
||||
| Field | Description |
|
||||
|---|---|
|
||||
| **Conceptual Endpoint** | `POST /team-hubs` / `PUT /team-hubs/{id}` / `DELETE /team-hubs/{id}` |
|
||||
| **Data Connect OP** | `createTeamHub` / `updateTeamHub` / `deleteTeamHub` |
|
||||
| **Purpose** | Provisions, Edits details directly, or Removes a Team Hub location. |
|
||||
| **Operation** | Mutation |
|
||||
| **Inputs** | `id: UUID!`, optionally `hubName`, `address`, etc. |
|
||||
| **Outputs** | `id` |
|
||||
| **Notes** | Fired from `edit_hub_page.dart` mutations. |
|
||||
|
||||
### Orders Page
|
||||
*(Pages: create_order_page.dart, view_orders_page.dart, recurring_order_page.dart)*
|
||||
|
||||
#### Create Order
|
||||
| Field | Description |
|
||||
|---|---|
|
||||
| **Conceptual Endpoint** | `POST /orders` |
|
||||
| **Data Connect OP** | `createOrder` |
|
||||
| **Purpose** | Submits a new request for temporary staff requirements. |
|
||||
| **Operation** | Mutation |
|
||||
| **Inputs** | `businessId`, `eventName`, `orderType`, `status` |
|
||||
| **Outputs** | `id` (Order ID) |
|
||||
| **Notes** | This explicitly invokes an order pipeline, meaning Shift instances are subsequently created through secondary mutations triggered after order instantiation. |
|
||||
|
||||
#### List Orders
|
||||
| Field | Description |
|
||||
|---|---|
|
||||
| **Conceptual Endpoint** | `GET /business/{businessId}/orders` |
|
||||
| **Data Connect OP** | `listOrdersByBusinessId` |
|
||||
| **Purpose** | Retrieves all ongoing and past staff requests from the client. |
|
||||
| **Operation** | Query |
|
||||
| **Inputs** | `businessId: UUID!` |
|
||||
| **Outputs** | `Orders { id, eventName }` |
|
||||
| **Notes** | Populates the `view_orders_page.dart`. |
|
||||
|
||||
### Billing Pages
|
||||
*(Pages: billing_page.dart, pending_invoices_page.dart, completion_review_page.dart)*
|
||||
|
||||
#### List Invoices
|
||||
| Field | Description |
|
||||
|---|---|
|
||||
| **Conceptual Endpoint** | `GET /business/{businessId}/invoices` |
|
||||
| **Data Connect OP** | `listInvoicesByBusinessId` |
|
||||
| **Purpose** | Fetches all invoices bound directly to the active business context (mapped directly in Firebase Schema). |
|
||||
| **Operation** | Query |
|
||||
| **Inputs** | `businessId: UUID!` |
|
||||
| **Outputs** | `Invoices { id, amount, issueDate, status }` |
|
||||
| **Notes** | Used massively across all Billing view tabs. |
|
||||
|
||||
#### Mark / Dispute Invoice
|
||||
| Field | Description |
|
||||
|---|---|
|
||||
| **Conceptual Endpoint** | `PUT /invoices/{id}` |
|
||||
| **Data Connect OP** | `updateInvoice` |
|
||||
| **Purpose** | Actively marks an invoice as disputed or pays it directly (altering status). |
|
||||
| **Operation** | Mutation |
|
||||
| **Inputs** | `id: UUID!`, `status: InvoiceStatus` |
|
||||
| **Outputs** | `id` |
|
||||
| **Notes** | Disputing usually involves setting a `disputeReason` flag state dynamically via builder pattern. |
|
||||
|
||||
### Reports Page
|
||||
*(Pages: reports_page.dart, coverage_report_page.dart, performance_report_page.dart)*
|
||||
|
||||
#### Get Coverage Stats
|
||||
| Field | Description |
|
||||
|---|---|
|
||||
| **Conceptual Endpoint** | `GET /business/{businessId}/coverage` |
|
||||
| **Data Connect OP** | `listShiftsForCoverage` |
|
||||
| **Purpose** | Provides data on Shifts grouped by Date for fulfillment calculations. |
|
||||
| **Operation** | Query |
|
||||
| **Inputs** | `businessId: UUID!`, `startDate: Timestamp!`, `endDate: Timestamp!` |
|
||||
| **Outputs** | `Shifts { id, date, workersNeeded, filled, status }` |
|
||||
| **Notes** | The frontend aggregates the raw backend rows to compose Coverage percentage natively. |
|
||||
|
||||
#### Get Daily Ops Stats
|
||||
| Field | Description |
|
||||
|---|---|
|
||||
| **Conceptual Endpoint** | `GET /business/{businessId}/dailyops` |
|
||||
| **Data Connect OP** | `listShiftsForDailyOpsByBusiness` |
|
||||
| **Purpose** | Supplies current day operations and shift tracking progress. |
|
||||
| **Operation** | Query |
|
||||
| **Inputs** | `businessId: UUID!`, `date: Timestamp!` |
|
||||
| **Outputs** | `Shifts { id, title, location, workersNeeded, filled }` |
|
||||
| **Notes** | - |
|
||||
|
||||
#### Get Forecast Stats
|
||||
| Field | Description |
|
||||
|---|---|
|
||||
| **Conceptual Endpoint** | `GET /business/{businessId}/forecast` |
|
||||
| **Data Connect OP** | `listShiftsForForecastByBusiness` |
|
||||
| **Purpose** | Retrieves scheduled future shifts to calculate financial run-rates. |
|
||||
| **Operation** | Query |
|
||||
| **Inputs** | `businessId: UUID!`, `startDate: Timestamp!`, `endDate: Timestamp!` |
|
||||
| **Outputs** | `Shifts { id, date, workersNeeded, hours, cost }` |
|
||||
| **Notes** | The App maps hours `x` cost to deliver Financial Dashboards. |
|
||||
|
||||
#### Get Performance KPIs
|
||||
| Field | Description |
|
||||
|---|---|
|
||||
| **Conceptual Endpoint** | `GET /business/{businessId}/performance` |
|
||||
| **Data Connect OP** | `listShiftsForPerformanceByBusiness` |
|
||||
| **Purpose** | Fetches historical data allowing time-to-fill and completion-rate calculations. |
|
||||
| **Operation** | Query |
|
||||
| **Inputs** | `businessId: UUID!`, `startDate: Timestamp!`, `endDate: Timestamp!` |
|
||||
| **Outputs** | `Shifts { id, workersNeeded, filled, createdAt, filledAt }` |
|
||||
| **Notes** | Data Connect exposes timestamps so the App calculates `avgFillTimeHours`. |
|
||||
|
||||
#### Get No-Show Metrics
|
||||
| Field | Description |
|
||||
|---|---|
|
||||
| **Conceptual Endpoint** | `GET /business/{businessId}/noshows` |
|
||||
| **Data Connect OP** | `listShiftsForNoShowRangeByBusiness` |
|
||||
| **Purpose** | Retrieves shifts where workers historically ghosted the platform. |
|
||||
| **Operation** | Query |
|
||||
| **Inputs** | `businessId: UUID!`, `startDate: Timestamp!`, `endDate: Timestamp!` |
|
||||
| **Outputs** | `Shifts { id, date }` |
|
||||
| **Notes** | Accompanies `listApplicationsForNoShowRange` cascading querying to generate full report. |
|
||||
|
||||
#### Get Spend Analytics
|
||||
| Field | Description |
|
||||
|---|---|
|
||||
| **Conceptual Endpoint** | `GET /business/{businessId}/spend` |
|
||||
| **Data Connect OP** | `listInvoicesForSpendByBusiness` |
|
||||
| **Purpose** | Detailed invoice aggregates for Spend metrics filtering. |
|
||||
| **Operation** | Query |
|
||||
| **Inputs** | `businessId: UUID!`, `startDate: Timestamp!`, `endDate: Timestamp!` |
|
||||
| **Outputs** | `Invoices { id, issueDate, dueDate, amount, status }` |
|
||||
| **Notes** | Used explicitly under the "Spend Report" graphings. |
|
||||
|
||||
---
|
||||
232
docs/MILESTONES/M4/planning/m4-api-catalog.md
Normal file
232
docs/MILESTONES/M4/planning/m4-api-catalog.md
Normal file
@@ -0,0 +1,232 @@
|
||||
# M4 API Catalog (Core Only)
|
||||
|
||||
Status: Active
|
||||
Date: 2026-02-24
|
||||
Owner: Technical Lead
|
||||
Environment: dev
|
||||
|
||||
## Frontend source of truth
|
||||
Use this file and `docs/MILESTONES/M4/planning/m4-core-api-frontend-guide.md` for core endpoint consumption.
|
||||
|
||||
## Related next-slice contract
|
||||
Verification pipeline design (attire, government ID, certification):
|
||||
- `docs/MILESTONES/M4/planning/m4-verification-architecture-contract.md`
|
||||
|
||||
## 1) Scope and purpose
|
||||
This catalog defines the currently implemented core backend contract for M4.
|
||||
|
||||
## 2) Global API rules
|
||||
1. Route group in scope: `/core/*`.
|
||||
2. Compatibility aliases in scope:
|
||||
- `POST /uploadFile` -> `POST /core/upload-file`
|
||||
- `POST /createSignedUrl` -> `POST /core/create-signed-url`
|
||||
- `POST /invokeLLM` -> `POST /core/invoke-llm`
|
||||
3. Auth model:
|
||||
- `GET /health` is public in dev
|
||||
- all other routes require `Authorization: Bearer <firebase-id-token>`
|
||||
4. Standard error envelope:
|
||||
```json
|
||||
{
|
||||
"code": "STRING_CODE",
|
||||
"message": "Human readable message",
|
||||
"details": {},
|
||||
"requestId": "optional-request-id"
|
||||
}
|
||||
```
|
||||
5. Response header:
|
||||
- `X-Request-Id`
|
||||
|
||||
## 3) Core routes
|
||||
|
||||
## 3.1 Upload file
|
||||
1. Method and route: `POST /core/upload-file`
|
||||
2. Request format: `multipart/form-data`
|
||||
3. Fields:
|
||||
- `file` (required)
|
||||
- `visibility` (`public` or `private`, optional)
|
||||
- `category` (optional)
|
||||
4. Accepted types:
|
||||
- `application/pdf`
|
||||
- `image/jpeg`
|
||||
- `image/jpg`
|
||||
- `image/png`
|
||||
5. Max size: `10 MB` (default)
|
||||
6. Behavior: real upload to Cloud Storage.
|
||||
7. Success `200`:
|
||||
```json
|
||||
{
|
||||
"fileUri": "gs://krow-workforce-dev-private/uploads/<uid>/...",
|
||||
"contentType": "application/pdf",
|
||||
"size": 12345,
|
||||
"bucket": "krow-workforce-dev-private",
|
||||
"path": "uploads/<uid>/...",
|
||||
"requestId": "uuid"
|
||||
}
|
||||
```
|
||||
8. Errors:
|
||||
- `UNAUTHENTICATED`
|
||||
- `INVALID_FILE_TYPE`
|
||||
- `FILE_TOO_LARGE`
|
||||
|
||||
## 3.2 Create signed URL
|
||||
1. Method and route: `POST /core/create-signed-url`
|
||||
2. Request:
|
||||
```json
|
||||
{
|
||||
"fileUri": "gs://krow-workforce-dev-private/uploads/<uid>/file.pdf",
|
||||
"expiresInSeconds": 300
|
||||
}
|
||||
```
|
||||
3. Security checks:
|
||||
- bucket must be allowed
|
||||
- path must be owned by caller (`uploads/<caller_uid>/...`)
|
||||
- object must exist
|
||||
- `expiresInSeconds <= 900`
|
||||
4. Success `200`:
|
||||
```json
|
||||
{
|
||||
"signedUrl": "https://storage.googleapis.com/...",
|
||||
"expiresAt": "2026-02-24T15:22:28.105Z",
|
||||
"requestId": "uuid"
|
||||
}
|
||||
```
|
||||
5. Errors:
|
||||
- `VALIDATION_ERROR`
|
||||
- `FORBIDDEN`
|
||||
- `NOT_FOUND`
|
||||
|
||||
## 3.3 Invoke model
|
||||
1. Method and route: `POST /core/invoke-llm`
|
||||
2. Request:
|
||||
```json
|
||||
{
|
||||
"prompt": "...",
|
||||
"responseJsonSchema": {},
|
||||
"fileUrls": []
|
||||
}
|
||||
```
|
||||
3. Behavior:
|
||||
- real Vertex AI call
|
||||
- model default: `gemini-2.0-flash-001`
|
||||
- timeout default: `20 seconds`
|
||||
4. Rate limit:
|
||||
- `20 requests/minute` per user (default)
|
||||
- when exceeded: `429 RATE_LIMITED` and `Retry-After` header
|
||||
5. Success `200`:
|
||||
```json
|
||||
{
|
||||
"result": {},
|
||||
"model": "gemini-2.0-flash-001",
|
||||
"latencyMs": 367,
|
||||
"requestId": "uuid"
|
||||
}
|
||||
```
|
||||
6. Errors:
|
||||
- `UNAUTHENTICATED`
|
||||
- `VALIDATION_ERROR`
|
||||
- `MODEL_TIMEOUT`
|
||||
- `MODEL_FAILED`
|
||||
- `RATE_LIMITED`
|
||||
|
||||
## 3.4 Create verification job
|
||||
1. Method and route: `POST /core/verifications`
|
||||
2. Auth: required
|
||||
3. Request:
|
||||
```json
|
||||
{
|
||||
"type": "attire",
|
||||
"subjectType": "worker",
|
||||
"subjectId": "worker_123",
|
||||
"fileUri": "gs://krow-workforce-dev-private/uploads/<uid>/file.pdf",
|
||||
"rules": {}
|
||||
}
|
||||
```
|
||||
4. Behavior:
|
||||
- validates `fileUri` ownership
|
||||
- requires file existence when `UPLOAD_MOCK=false` and `VERIFICATION_REQUIRE_FILE_EXISTS=true`
|
||||
- enqueues async verification
|
||||
5. Success `202`:
|
||||
```json
|
||||
{
|
||||
"verificationId": "ver_123",
|
||||
"status": "PENDING",
|
||||
"type": "attire",
|
||||
"requestId": "uuid"
|
||||
}
|
||||
```
|
||||
6. Errors:
|
||||
- `UNAUTHENTICATED`
|
||||
- `VALIDATION_ERROR`
|
||||
- `FORBIDDEN`
|
||||
- `NOT_FOUND`
|
||||
|
||||
## 3.5 Get verification status
|
||||
1. Method and route: `GET /core/verifications/{verificationId}`
|
||||
2. Auth: required
|
||||
3. Success `200`:
|
||||
```json
|
||||
{
|
||||
"verificationId": "ver_123",
|
||||
"status": "NEEDS_REVIEW",
|
||||
"type": "attire",
|
||||
"requestId": "uuid"
|
||||
}
|
||||
```
|
||||
4. Errors:
|
||||
- `UNAUTHENTICATED`
|
||||
- `FORBIDDEN`
|
||||
- `NOT_FOUND`
|
||||
|
||||
## 3.6 Review verification
|
||||
1. Method and route: `POST /core/verifications/{verificationId}/review`
|
||||
2. Auth: required
|
||||
3. Request:
|
||||
```json
|
||||
{
|
||||
"decision": "APPROVED",
|
||||
"note": "Manual review passed",
|
||||
"reasonCode": "MANUAL_REVIEW"
|
||||
}
|
||||
```
|
||||
4. Success `200`: status becomes `APPROVED` or `REJECTED`.
|
||||
5. Errors:
|
||||
- `UNAUTHENTICATED`
|
||||
- `VALIDATION_ERROR`
|
||||
- `FORBIDDEN`
|
||||
- `NOT_FOUND`
|
||||
|
||||
## 3.7 Retry verification
|
||||
1. Method and route: `POST /core/verifications/{verificationId}/retry`
|
||||
2. Auth: required
|
||||
3. Success `202`: status resets to `PENDING`.
|
||||
4. Errors:
|
||||
- `UNAUTHENTICATED`
|
||||
- `FORBIDDEN`
|
||||
- `NOT_FOUND`
|
||||
|
||||
## 3.8 Health
|
||||
1. Method and route: `GET /health`
|
||||
2. Success `200`:
|
||||
```json
|
||||
{
|
||||
"ok": true,
|
||||
"service": "krow-core-api",
|
||||
"version": "dev",
|
||||
"requestId": "uuid"
|
||||
}
|
||||
```
|
||||
|
||||
## 4) Locked defaults
|
||||
1. Validation library: `zod`.
|
||||
2. Validation schema location: `backend/core-api/src/contracts/`.
|
||||
3. Buckets:
|
||||
- `krow-workforce-dev-public`
|
||||
- `krow-workforce-dev-private`
|
||||
4. Model provider: Vertex AI Gemini.
|
||||
5. Max signed URL expiry: `900` seconds.
|
||||
6. LLM timeout: `20000` ms.
|
||||
7. LLM rate limit: `20` requests/minute/user.
|
||||
8. Verification access mode default: `authenticated`.
|
||||
9. Verification file existence check default: enabled (`VERIFICATION_REQUIRE_FILE_EXISTS=true`).
|
||||
10. Verification attire provider default in dev: `vertex` with model `gemini-2.0-flash-lite-001`.
|
||||
11. Verification government/certification providers: external adapters via configured provider URL/token.
|
||||
@@ -0,0 +1,269 @@
|
||||
# M4 Backend Foundation Implementation Plan (Dev First)
|
||||
|
||||
Date: 2026-02-24
|
||||
Owner: Wilfred (Technical Lead)
|
||||
Primary environment: `krow-workforce-dev`
|
||||
|
||||
## 1) Objective
|
||||
Build a secure, modular, and scalable backend foundation in `dev` without breaking the current frontend while we migrate high-risk writes from direct Data Connect mutations to backend command endpoints.
|
||||
|
||||
## 2) First-principles architecture rules
|
||||
1. Client apps are untrusted for business-critical writes.
|
||||
2. Backend is the enforcement layer for validation, permissions, and write orchestration.
|
||||
3. Multi-entity writes must be atomic, idempotent, and observable.
|
||||
4. Configuration and deployment must be reproducible by automation.
|
||||
5. Migration must be backward-compatible until each frontend flow is cut over.
|
||||
|
||||
## 3) Pre-coding gates (must be true before implementation starts)
|
||||
|
||||
## Gate A: Security boundary
|
||||
1. Frontend sends Firebase token only. No database credentials in client code.
|
||||
2. Every new backend endpoint validates Firebase token.
|
||||
3. Data Connect write access strategy is defined:
|
||||
- keep simple reads available to client
|
||||
- route high-risk writes through backend command endpoints
|
||||
4. Upload and signed URL paths are server-controlled.
|
||||
|
||||
## Gate B: Contract standards
|
||||
1. Standard error envelope is frozen:
|
||||
```json
|
||||
{
|
||||
"code": "STRING_CODE",
|
||||
"message": "Human readable message",
|
||||
"details": {},
|
||||
"requestId": "optional-request-id"
|
||||
}
|
||||
```
|
||||
2. Request validation layer is chosen and centralized.
|
||||
3. Route naming strategy is frozen:
|
||||
- canonical routes under `/core` and `/commands`
|
||||
- compatibility aliases preserved during migration (`/uploadFile`, `/createSignedUrl`, `/invokeLLM`)
|
||||
4. Validation standard is locked:
|
||||
- library: `zod`
|
||||
- schema location: `backend/<service>/src/contracts/` with `core/` and `commands/` subfolders
|
||||
|
||||
## Gate C: Atomicity and reliability
|
||||
1. Command endpoints support idempotency keys for retry-safe writes.
|
||||
2. Multi-step write flows are wrapped in single backend transaction boundaries.
|
||||
3. Domain conflict codes are defined for expected business failures.
|
||||
4. Idempotency storage is locked:
|
||||
- store in Cloud SQL table
|
||||
- key scope: `userId + route + idempotencyKey`
|
||||
- retain records for 24 hours
|
||||
- repeated key returns original response
|
||||
|
||||
## Gate D: Automation and operability
|
||||
1. Makefile is source of truth for backend setup and deploy in dev.
|
||||
2. Core deploy and smoke test commands exist before feature migration.
|
||||
3. Logging format and request tracing fields are standardized.
|
||||
|
||||
## 4) Security baseline for foundation phase
|
||||
|
||||
## 4.1 Authentication and authorization
|
||||
1. Foundation phase is authentication-first.
|
||||
2. Role-based access control is intentionally deferred.
|
||||
3. All handlers include a policy hook for future role checks (`can(action, resource, actor)`).
|
||||
|
||||
## 4.2 Data access control model
|
||||
1. Client retains Data Connect reads required for existing screens.
|
||||
2. High-risk writes move behind `/commands/*` endpoints.
|
||||
3. Backend mediates write interactions with Data Connect and Cloud SQL.
|
||||
|
||||
## 4.3 File and URL security
|
||||
1. Validate file type and size server-side.
|
||||
2. Separate public and private storage behavior.
|
||||
3. Signed URL creation checks ownership/prefix scope and expiry limits.
|
||||
4. Bucket policy split is locked:
|
||||
- `krow-workforce-dev-public`
|
||||
- `krow-workforce-dev-private`
|
||||
- private bucket access only through signed URL
|
||||
|
||||
## 4.4 Model invocation safety
|
||||
1. Enforce schema-constrained output.
|
||||
2. Apply per-user rate limits and request timeout.
|
||||
3. Log model failures with safe redaction (no sensitive prompt leakage in logs).
|
||||
4. Model provider and timeout defaults are locked:
|
||||
- provider: Vertex AI Gemini
|
||||
- max route timeout: 20 seconds
|
||||
- timeout error code: `MODEL_TIMEOUT`
|
||||
|
||||
## 4.5 Secrets and credentials
|
||||
1. Runtime secrets come from Secret Manager only.
|
||||
2. Service accounts use least-privilege roles.
|
||||
3. No secrets committed in repository files.
|
||||
|
||||
## 5) Modularity baseline
|
||||
|
||||
## 5.1 Backend module boundaries
|
||||
1. `core` module: upload, signed URL, model invocation, health.
|
||||
2. `commands` module: business writes and state transitions.
|
||||
3. `policy` module: validation and future role checks.
|
||||
4. `data` module: Data Connect adapters and transaction wrappers.
|
||||
5. `infra` module: logging, tracing, auth middleware, error mapping.
|
||||
|
||||
## 5.2 Contract separation
|
||||
1. Keep API request/response schemas in one location.
|
||||
2. Keep domain errors in one registry file.
|
||||
3. Keep route declarations thin; business logic in services.
|
||||
|
||||
## 5.3 Cloud runtime roles
|
||||
1. Cloud Run is the primary command and core API execution layer.
|
||||
2. Cloud Functions v2 is worker-only in this phase:
|
||||
- upload-related async handlers
|
||||
- notification jobs
|
||||
- model-related async helpers when needed
|
||||
|
||||
## 6) Automation baseline
|
||||
|
||||
## 6.1 Makefile requirements
|
||||
Add `makefiles/backend.mk` and wire it into root `Makefile` with at least:
|
||||
1. `make backend-enable-apis`
|
||||
2. `make backend-bootstrap-dev`
|
||||
3. `make backend-deploy-core`
|
||||
4. `make backend-deploy-commands`
|
||||
5. `make backend-deploy-workers`
|
||||
6. `make backend-smoke-core`
|
||||
7. `make backend-smoke-commands`
|
||||
8. `make backend-logs-core`
|
||||
|
||||
## 6.2 CI requirements
|
||||
1. Backend lint
|
||||
2. Backend tests
|
||||
3. Build/package
|
||||
4. Smoke test against deployed dev route(s)
|
||||
5. Block merge on failed checks
|
||||
|
||||
## 6.3 Session hygiene
|
||||
1. Update `TASKS.md` and `CHANGELOG.md` each working session.
|
||||
2. If a new service/API is added, Makefile target must be added in same change.
|
||||
|
||||
## 7) Migration safety contract (no frontend breakage)
|
||||
1. Backend routes ship first.
|
||||
2. Frontend migration is per-feature wave, not big bang.
|
||||
3. Keep compatibility aliases until clients migrate.
|
||||
4. Keep existing Data Connect reads during foundation.
|
||||
5. For each migrated write flow:
|
||||
- before/after behavior checklist
|
||||
- rollback path
|
||||
- smoke verification
|
||||
|
||||
## 8) Scope for foundation build
|
||||
1. Backend runtime/deploy foundation in dev.
|
||||
2. Core endpoints:
|
||||
- `POST /core/upload-file`
|
||||
- `POST /core/create-signed-url`
|
||||
- `POST /core/invoke-llm`
|
||||
- `GET /healthz`
|
||||
3. Compatibility aliases:
|
||||
- `POST /uploadFile`
|
||||
- `POST /createSignedUrl`
|
||||
- `POST /invokeLLM`
|
||||
4. Command layer scaffold for first migration routes.
|
||||
5. Initial migration of highest-risk write paths.
|
||||
|
||||
## 9) Implementation phases
|
||||
|
||||
## Phase 0: Baseline and contracts
|
||||
Deliverables:
|
||||
1. Freeze endpoint naming and compatibility aliases.
|
||||
2. Freeze error envelope and error code registry.
|
||||
3. Freeze auth middleware interface and policy hook interface.
|
||||
4. Publish route inventory from web/mobile direct writes.
|
||||
|
||||
Exit criteria:
|
||||
1. No unresolved contract ambiguity.
|
||||
2. Team agrees on auth-first now and role-map-later approach.
|
||||
|
||||
## Phase 1: Backend infra and automation
|
||||
Deliverables:
|
||||
1. `makefiles/backend.mk` with bootstrap, deploy, smoke, logs targets.
|
||||
2. Environment templates for backend runtime config.
|
||||
3. Secret Manager and service account setup automation.
|
||||
|
||||
Exit criteria:
|
||||
1. A fresh machine can deploy core backend to dev via Make commands.
|
||||
|
||||
## Phase 2: Core endpoint implementation
|
||||
Deliverables:
|
||||
1. `/core/upload-file`
|
||||
2. `/core/create-signed-url`
|
||||
3. `/core/invoke-llm`
|
||||
4. `/healthz`
|
||||
5. Compatibility aliases (`/uploadFile`, `/createSignedUrl`, `/invokeLLM`)
|
||||
|
||||
Exit criteria:
|
||||
1. API harness passes for core routes.
|
||||
2. Error, logging, and auth standards are enforced.
|
||||
|
||||
## Phase 3: Command layer scaffold
|
||||
Deliverables:
|
||||
1. `/commands/orders/create`
|
||||
2. `/commands/orders/{orderId}/cancel`
|
||||
3. `/commands/orders/{orderId}/update`
|
||||
4. `/commands/shifts/{shiftId}/change-status`
|
||||
5. `/commands/shifts/{shiftId}/assign-staff`
|
||||
6. `/commands/shifts/{shiftId}/accept`
|
||||
|
||||
Exit criteria:
|
||||
1. High-risk writes have backend command alternatives ready.
|
||||
|
||||
## Phase 4: Wave 1 frontend migration
|
||||
Deliverables:
|
||||
1. Replace direct writes in selected web/mobile flows.
|
||||
2. Keep reads stable.
|
||||
3. Verify no regressions in non-migrated screens.
|
||||
|
||||
Exit criteria:
|
||||
1. Migrated flows run through backend commands only.
|
||||
2. Rollback instructions validated.
|
||||
|
||||
## Phase 5: Hardening and handoff
|
||||
Deliverables:
|
||||
1. Runbook for deploy, rollback, and smoke.
|
||||
2. Backend CI pipeline active.
|
||||
3. Wave 2 and wave 3 migration task list defined.
|
||||
|
||||
Exit criteria:
|
||||
1. Foundation is reusable for staging/prod with environment changes only.
|
||||
|
||||
## 10) Wave 1 migration inventory (real call sites)
|
||||
|
||||
Web:
|
||||
1. `apps/web/src/features/operations/tasks/TaskBoard.tsx:100`
|
||||
2. `apps/web/src/features/operations/orders/OrderDetail.tsx:145`
|
||||
3. `apps/web/src/features/operations/orders/EditOrder.tsx:84`
|
||||
4. `apps/web/src/features/operations/orders/components/CreateOrderDialog.tsx:31`
|
||||
5. `apps/web/src/features/operations/orders/components/AssignStaffModal.tsx:60`
|
||||
6. `apps/web/src/features/workforce/documents/DocumentVault.tsx:99`
|
||||
|
||||
Mobile:
|
||||
1. `apps/mobile/packages/features/client/home/lib/src/presentation/widgets/shift_order_form_sheet.dart:232`
|
||||
2. `apps/mobile/packages/features/client/view_orders/lib/src/presentation/widgets/view_order_card.dart:1195`
|
||||
3. `apps/mobile/packages/features/client/create_order/lib/src/data/repositories_impl/client_create_order_repository_impl.dart:68`
|
||||
4. `apps/mobile/packages/features/staff/shifts/lib/src/data/repositories_impl/shifts_repository_impl.dart:446`
|
||||
5. `apps/mobile/packages/features/client/authentication/lib/src/data/repositories_impl/auth_repository_impl.dart:257`
|
||||
6. `apps/mobile/packages/features/staff/profile_sections/onboarding/profile_info/lib/src/data/repositories/personal_info_repository_impl.dart:51`
|
||||
|
||||
## 11) Definition of done for foundation
|
||||
1. Core endpoints deployed in dev and validated.
|
||||
2. Command scaffolding in place for wave 1 writes.
|
||||
3. Auth-first protection active on all new routes.
|
||||
4. Idempotency + transaction model defined for command writes.
|
||||
5. Makefile and CI automation cover bootstrap/deploy/smoke paths.
|
||||
6. Frontend remains stable during migration.
|
||||
7. Role-map integration points are documented for next phase.
|
||||
|
||||
## 12) Locked defaults (approved)
|
||||
1. Idempotency key storage strategy:
|
||||
- Cloud SQL table, 24-hour retention, keyed by `userId + route + idempotencyKey`.
|
||||
2. Validation library and schema location:
|
||||
- `zod` in `backend/<service>/src/contracts/` (`core/`, `commands/`).
|
||||
3. Storage bucket naming and split:
|
||||
- `krow-workforce-dev-public` and `krow-workforce-dev-private`.
|
||||
4. Model provider and timeout:
|
||||
- Vertex AI Gemini, 20-second max timeout.
|
||||
5. Target response-time objectives (p95):
|
||||
- `/healthz` under 200ms
|
||||
- `/core/create-signed-url` under 500ms
|
||||
- `/commands/*` under 1500ms
|
||||
- `/core/invoke-llm` under 15000ms
|
||||
245
docs/MILESTONES/M4/planning/m4-core-api-frontend-guide.md
Normal file
245
docs/MILESTONES/M4/planning/m4-core-api-frontend-guide.md
Normal file
@@ -0,0 +1,245 @@
|
||||
# M4 Core API Frontend Guide (Dev)
|
||||
|
||||
Status: Active
|
||||
Last updated: 2026-02-24
|
||||
Audience: Web and mobile frontend developers
|
||||
|
||||
## 1) Base URLs (dev)
|
||||
1. Core API: `https://krow-core-api-e3g6witsvq-uc.a.run.app`
|
||||
|
||||
## 2) Auth requirements
|
||||
1. Send Firebase ID token on protected routes:
|
||||
```http
|
||||
Authorization: Bearer <firebase-id-token>
|
||||
```
|
||||
2. Health route is public:
|
||||
- `GET /health`
|
||||
3. All other routes require Firebase token.
|
||||
|
||||
## 3) Standard error envelope
|
||||
```json
|
||||
{
|
||||
"code": "STRING_CODE",
|
||||
"message": "Human readable message",
|
||||
"details": {},
|
||||
"requestId": "uuid"
|
||||
}
|
||||
```
|
||||
|
||||
## 4) Core API endpoints
|
||||
|
||||
## 4.1 Upload file
|
||||
1. Route: `POST /core/upload-file`
|
||||
2. Alias: `POST /uploadFile`
|
||||
3. Content type: `multipart/form-data`
|
||||
4. Form fields:
|
||||
- `file` (required)
|
||||
- `visibility` (optional: `public` or `private`, default `private`)
|
||||
- `category` (optional)
|
||||
5. Accepted file types:
|
||||
- `application/pdf`
|
||||
- `image/jpeg`
|
||||
- `image/jpg`
|
||||
- `image/png`
|
||||
6. Max upload size: `10 MB` (default)
|
||||
7. Current behavior: real upload to Cloud Storage (not mock)
|
||||
8. Success `200` example:
|
||||
```json
|
||||
{
|
||||
"fileUri": "gs://krow-workforce-dev-private/uploads/<uid>/173...",
|
||||
"contentType": "application/pdf",
|
||||
"size": 12345,
|
||||
"bucket": "krow-workforce-dev-private",
|
||||
"path": "uploads/<uid>/173..._file.pdf",
|
||||
"requestId": "uuid"
|
||||
}
|
||||
```
|
||||
|
||||
## 4.2 Create signed URL
|
||||
1. Route: `POST /core/create-signed-url`
|
||||
2. Alias: `POST /createSignedUrl`
|
||||
3. Request body:
|
||||
```json
|
||||
{
|
||||
"fileUri": "gs://krow-workforce-dev-private/uploads/<uid>/file.pdf",
|
||||
"expiresInSeconds": 300
|
||||
}
|
||||
```
|
||||
4. Security checks:
|
||||
- bucket must be allowed (`krow-workforce-dev-public` or `krow-workforce-dev-private`)
|
||||
- path must be owned by caller (`uploads/<caller_uid>/...`)
|
||||
- object must exist
|
||||
- `expiresInSeconds` must be `<= 900`
|
||||
5. Success `200` example:
|
||||
```json
|
||||
{
|
||||
"signedUrl": "https://storage.googleapis.com/...",
|
||||
"expiresAt": "2026-02-24T15:22:28.105Z",
|
||||
"requestId": "uuid"
|
||||
}
|
||||
```
|
||||
6. Typical errors:
|
||||
- `400 VALIDATION_ERROR` (bad payload or expiry too high)
|
||||
- `403 FORBIDDEN` (path not owned by caller)
|
||||
- `404 NOT_FOUND` (object does not exist)
|
||||
|
||||
## 4.3 Invoke model
|
||||
1. Route: `POST /core/invoke-llm`
|
||||
2. Alias: `POST /invokeLLM`
|
||||
3. Request body:
|
||||
```json
|
||||
{
|
||||
"prompt": "Return JSON with keys summary and risk.",
|
||||
"responseJsonSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"summary": { "type": "string" },
|
||||
"risk": { "type": "string" }
|
||||
},
|
||||
"required": ["summary", "risk"]
|
||||
},
|
||||
"fileUrls": []
|
||||
}
|
||||
```
|
||||
4. Current behavior: real Vertex model call (not mock)
|
||||
- model: `gemini-2.0-flash-001`
|
||||
- timeout: `20 seconds`
|
||||
5. Rate limit:
|
||||
- per-user `20 requests/minute` (default)
|
||||
- on limit: `429 RATE_LIMITED`
|
||||
- includes `Retry-After` header
|
||||
6. Success `200` example:
|
||||
```json
|
||||
{
|
||||
"result": { "summary": "text", "risk": "Low" },
|
||||
"model": "gemini-2.0-flash-001",
|
||||
"latencyMs": 367,
|
||||
"requestId": "uuid"
|
||||
}
|
||||
```
|
||||
|
||||
## 4.4 Create verification job
|
||||
1. Route: `POST /core/verifications`
|
||||
2. Auth: required
|
||||
3. Purpose: enqueue an async verification job for an uploaded file.
|
||||
4. Request body:
|
||||
```json
|
||||
{
|
||||
"type": "attire",
|
||||
"subjectType": "worker",
|
||||
"subjectId": "<worker-id>",
|
||||
"fileUri": "gs://krow-workforce-dev-private/uploads/<uid>/file.pdf",
|
||||
"rules": {
|
||||
"dressCode": "black shoes"
|
||||
}
|
||||
}
|
||||
```
|
||||
5. Success `202` example:
|
||||
```json
|
||||
{
|
||||
"verificationId": "ver_123",
|
||||
"status": "PENDING",
|
||||
"type": "attire",
|
||||
"requestId": "uuid"
|
||||
}
|
||||
```
|
||||
6. Current machine processing behavior in dev:
|
||||
- `attire`: live vision check using Vertex Gemini Flash Lite model.
|
||||
- `government_id`: third-party adapter path (falls back to `NEEDS_REVIEW` if provider is not configured).
|
||||
- `certification`: third-party adapter path (falls back to `NEEDS_REVIEW` if provider is not configured).
|
||||
|
||||
## 4.5 Get verification status
|
||||
1. Route: `GET /core/verifications/{verificationId}`
|
||||
2. Auth: required
|
||||
3. Purpose: polling status from frontend.
|
||||
4. Success `200` example:
|
||||
```json
|
||||
{
|
||||
"verificationId": "ver_123",
|
||||
"status": "NEEDS_REVIEW",
|
||||
"type": "attire",
|
||||
"review": null,
|
||||
"requestId": "uuid"
|
||||
}
|
||||
```
|
||||
|
||||
## 4.6 Review verification
|
||||
1. Route: `POST /core/verifications/{verificationId}/review`
|
||||
2. Auth: required
|
||||
3. Purpose: final human decision for the verification.
|
||||
4. Request body:
|
||||
```json
|
||||
{
|
||||
"decision": "APPROVED",
|
||||
"note": "Manual review passed",
|
||||
"reasonCode": "MANUAL_REVIEW"
|
||||
}
|
||||
```
|
||||
5. Success `200` example:
|
||||
```json
|
||||
{
|
||||
"verificationId": "ver_123",
|
||||
"status": "APPROVED",
|
||||
"review": {
|
||||
"decision": "APPROVED",
|
||||
"reviewedBy": "<uid>"
|
||||
},
|
||||
"requestId": "uuid"
|
||||
}
|
||||
```
|
||||
|
||||
## 4.7 Retry verification
|
||||
1. Route: `POST /core/verifications/{verificationId}/retry`
|
||||
2. Auth: required
|
||||
3. Purpose: requeue verification to run again.
|
||||
4. Success `202` example: status resets to `PENDING`.
|
||||
|
||||
## 5) Frontend fetch examples (web)
|
||||
|
||||
## 5.1 Signed URL request
|
||||
```ts
|
||||
const token = await firebaseAuth.currentUser?.getIdToken();
|
||||
const res = await fetch('https://krow-core-api-e3g6witsvq-uc.a.run.app/core/create-signed-url', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
fileUri: 'gs://krow-workforce-dev-private/uploads/<uid>/file.pdf',
|
||||
expiresInSeconds: 300,
|
||||
}),
|
||||
});
|
||||
const data = await res.json();
|
||||
```
|
||||
|
||||
## 5.2 Model request
|
||||
```ts
|
||||
const token = await firebaseAuth.currentUser?.getIdToken();
|
||||
const res = await fetch('https://krow-core-api-e3g6witsvq-uc.a.run.app/core/invoke-llm', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
prompt: 'Return JSON with status.',
|
||||
responseJsonSchema: {
|
||||
type: 'object',
|
||||
properties: { status: { type: 'string' } },
|
||||
required: ['status'],
|
||||
},
|
||||
}),
|
||||
});
|
||||
const data = await res.json();
|
||||
```
|
||||
|
||||
## 6) Notes for frontend team
|
||||
1. Use canonical `/core/*` routes for new work.
|
||||
2. Aliases exist only for migration compatibility.
|
||||
3. `requestId` in responses should be logged client-side for debugging.
|
||||
4. For 429 on model route, retry with exponential backoff and respect `Retry-After`.
|
||||
5. Verification routes are now available in dev under `/core/verifications*`.
|
||||
6. Current verification processing is async and returns machine statuses first (`PENDING`, `PROCESSING`, `NEEDS_REVIEW`, etc.).
|
||||
7. Full verification design and policy details:
|
||||
`docs/MILESTONES/M4/planning/m4-verification-architecture-contract.md`.
|
||||
166
docs/MILESTONES/M4/planning/m4-core-data-actors-scenarios.md
Normal file
166
docs/MILESTONES/M4/planning/m4-core-data-actors-scenarios.md
Normal file
@@ -0,0 +1,166 @@
|
||||
# M4 Core Data Actors and Example Scenarios
|
||||
|
||||
Status: Working draft
|
||||
Date: 2026-02-25
|
||||
Owner: Technical Lead
|
||||
|
||||
## 1) Core data actors
|
||||
1. `Tenant`: staffing company boundary and data isolation root.
|
||||
2. `User`: human identity that signs in.
|
||||
3. `TenantMembership`: user role/context inside one tenant.
|
||||
4. `Business`: client account served by the tenant.
|
||||
5. `BusinessMembership`: maps users to a business with role/status (`owner`, `manager`, `approver`, `viewer`).
|
||||
6. `Vendor`: supplier account that can fulfill staffing demand.
|
||||
7. `VendorMembership`: maps users to a vendor with role/status (`owner`, `manager`, `scheduler`, `viewer`).
|
||||
8. `Workforce/Staff`: worker profile used for assignment and attendance.
|
||||
9. `StakeholderType`: typed category (`buyer`, `operator`, `vendor_partner`, `workforce`, `partner`, `procurement_partner`).
|
||||
10. `StakeholderProfile`: typed actor record inside a tenant.
|
||||
11. `StakeholderLink`: relationship between stakeholder profiles.
|
||||
|
||||
## 1.1 Current schema coverage (today)
|
||||
Current Data Connect handles this only partially:
|
||||
1. `Business.userId` supports one primary business user.
|
||||
2. `Vendor.userId` supports one primary vendor user.
|
||||
3. `TeamMember` can represent multiple users by team as a workaround.
|
||||
|
||||
This is why we need first-class membership tables:
|
||||
1. `business_memberships`
|
||||
2. `vendor_memberships`
|
||||
|
||||
Without those, client/vendor user partitioning is indirect and harder to enforce safely at scale.
|
||||
|
||||
## 2) Minimal actor map
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
T["Tenant"] --> TM["TenantMembership (global tenant access)"]
|
||||
U["User"] --> TM
|
||||
T --> B["Business"]
|
||||
T --> V["Vendor"]
|
||||
U --> BM["BusinessMembership"]
|
||||
BM --> B
|
||||
U --> VM["VendorMembership"]
|
||||
VM --> V
|
||||
U --> S["Workforce/Staff"]
|
||||
T --> SP["StakeholderProfile"]
|
||||
ST["StakeholderType"] --> SP
|
||||
SP --> SL["StakeholderLink"]
|
||||
B --> O["Orders/Shifts"]
|
||||
V --> O
|
||||
S --> O
|
||||
```
|
||||
|
||||
## 3) Scenario A: Legendary Event Staffing
|
||||
Context:
|
||||
1. Tenant is `Legendary Event Staffing and Entertainment`.
|
||||
2. Business is `Google Mountain View Cafes`.
|
||||
3. Legendary uses its own workforce, and can still route overflow to approved vendors.
|
||||
|
||||
Actor mapping (text):
|
||||
1. Tenant: `Legendary Event Staffing and Entertainment` (the company using Krow).
|
||||
2. User: `Wil` (ops lead), `Maria` (Google client manager), `Omar` (Google procurement approver), `Jose` (vendor scheduler), `Ana` (worker).
|
||||
3. TenantMembership:
|
||||
4. `Wil` is `admin` in Legendary tenant.
|
||||
5. `Maria` is `member` in Legendary tenant.
|
||||
6. `Omar` is `member` in Legendary tenant.
|
||||
7. `Jose` is `member` in Legendary tenant.
|
||||
8. `Ana` is `member` in Legendary tenant.
|
||||
9. BusinessMembership:
|
||||
10. `Maria` is `manager` in `Google Mountain View Cafes`.
|
||||
11. `Omar` is `approver` in `Google Mountain View Cafes`.
|
||||
12. VendorMembership:
|
||||
13. `Jose` is `scheduler` in `Legendary Staffing Pool A`.
|
||||
14. `Wil` is `owner` in `Legendary Staffing Pool A`.
|
||||
15. Business: `Google Mountain View Cafes` (client account under the tenant).
|
||||
16. Vendor: `Legendary Staffing Pool A` (or an external approved vendor).
|
||||
17. Workforce/Staff: `Ana` is a staff profile in workforce, linked to certifications and assignments.
|
||||
18. StakeholderType: `buyer`, `operator`, `vendor_partner`, `workforce`, `procurement_partner`.
|
||||
19. StakeholderProfile:
|
||||
20. `Google Procurement` = `buyer`.
|
||||
21. `Legendary Ops` = `operator`.
|
||||
22. `FoodBuy` = `procurement_partner`.
|
||||
23. StakeholderLink:
|
||||
24. `Google Procurement` `contracts_with` `Legendary Ops`.
|
||||
25. `Legendary Ops` `sources_from` `Legendary Staffing Pool A`.
|
||||
26. `Google Procurement` `reports_through` `FoodBuy`.
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Tenant as "Tenant (Legendary)"
|
||||
participant BizUser as "Business user (Maria/Omar)"
|
||||
participant Ops as "Ops user (Wil)"
|
||||
participant VendorUser as "Vendor user (Jose)"
|
||||
participant Staff as "Workforce/Staff (Barista Ana)"
|
||||
participant StakeRel as "StakeholderLink"
|
||||
|
||||
BizUser->>Ops: "Create staffing request"
|
||||
Ops->>Tenant: "Create order under tenant scope"
|
||||
Ops->>StakeRel: "Resolve business-to-vendor/workforce relationships"
|
||||
Ops->>VendorUser: "Dispatch role demand"
|
||||
VendorUser->>Staff: "Confirm worker assignment"
|
||||
Staff-->>Ops: "Clock in/out and complete shift"
|
||||
Ops-->>BizUser: "Invoice/report generated with audit trail"
|
||||
```
|
||||
|
||||
## 4) Scenario B: Another tenant (external vendor-heavy)
|
||||
Context:
|
||||
1. Tenant is `Peakline Events`.
|
||||
2. Business is `NVIDIA Campus Dining`.
|
||||
3. Peakline primarily fulfills demand through external approved vendors.
|
||||
|
||||
Actor mapping (text):
|
||||
1. Tenant: `Peakline Events` (another staffing company using Krow).
|
||||
2. User: `Chris` (operations coordinator), `Nina` (client manager), `Sam` (vendor manager), `Leo` (worker).
|
||||
3. TenantMembership:
|
||||
4. `Chris` is `admin` in Peakline tenant.
|
||||
5. `Nina` is `member` in Peakline tenant.
|
||||
6. `Sam` is `member` in Peakline tenant.
|
||||
7. `Leo` is `member` in Peakline tenant.
|
||||
8. BusinessMembership:
|
||||
9. `Nina` is `manager` in `NVIDIA Campus Dining`.
|
||||
10. VendorMembership:
|
||||
11. `Sam` is `manager` in `Metro Staffing LLC`.
|
||||
12. Business: `NVIDIA Campus Dining` (client account under the tenant).
|
||||
13. Vendor: `Metro Staffing LLC` (approved external vendor for this tenant).
|
||||
14. Workforce/Staff: `Leo` is a workforce profile fulfilled through Metro Staffing for assignment and attendance.
|
||||
15. StakeholderType: `buyer`, `operator`, `vendor_partner`, `workforce`, `procurement_partner`.
|
||||
16. StakeholderProfile:
|
||||
17. `NVIDIA Procurement` = `buyer`.
|
||||
18. `Peakline Ops` = `operator`.
|
||||
19. `FoodBuy Regional` = `procurement_partner`.
|
||||
20. StakeholderLink:
|
||||
21. `NVIDIA Procurement` `contracts_with` `Peakline Ops`.
|
||||
22. `Peakline Ops` `sources_from` `Metro Staffing LLC`.
|
||||
23. `NVIDIA Procurement` `reports_through` `FoodBuy Regional`.
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Tenant as "Tenant (Peakline Events)"
|
||||
participant BizUser as "Business user (Nina)"
|
||||
participant Ops as "Ops user (Chris)"
|
||||
participant VendorUser as "Vendor user (Sam)"
|
||||
participant Staff as "Workforce/Staff (Vendor worker)"
|
||||
|
||||
BizUser->>Ops: "Request recurring event staffing"
|
||||
Ops->>Tenant: "Create recurring order"
|
||||
Ops->>VendorUser: "Dispatch required roles"
|
||||
VendorUser->>Staff: "Provide available workers"
|
||||
Staff-->>Ops: "Attendance and completion events"
|
||||
Ops-->>BizUser: "Settlement + performance reports"
|
||||
```
|
||||
|
||||
## 5) Why this model scales
|
||||
1. New stakeholders can be added through `StakeholderType` and `StakeholderProfile` without changing core order/shift tables.
|
||||
2. Business and vendor user partitioning is explicit through membership tables, not hidden in one owner field.
|
||||
3. Multi-tenant isolation stays strict because all critical records resolve through `Tenant`.
|
||||
4. Legendary and non-Legendary operating models both fit the same structure.
|
||||
|
||||
## 6) Industry alignment (primary references)
|
||||
1. Google Cloud Identity Platform multi-tenancy:
|
||||
- https://docs.cloud.google.com/identity-platform/docs/multi-tenancy
|
||||
2. AWS SaaS tenant isolation fundamentals:
|
||||
- https://docs.aws.amazon.com/whitepapers/latest/saas-architecture-fundamentals/tenant-isolation.html
|
||||
3. B2B organization-aware login flows (Auth0 Organizations):
|
||||
- https://auth0.com/docs/manage-users/organizations/login-flows-for-organizations
|
||||
4. Supplier-side multi-user management patterns (Coupa Supplier Portal):
|
||||
- https://compass.coupa.com/en-us/products/product-documentation/supplier-resources/for-suppliers/coupa-supplier-portal/set-up-the-csp/users/manage-users
|
||||
@@ -1,47 +0,0 @@
|
||||
# M4 Planning Phase: Identified Discrepancies and Enhancements
|
||||
|
||||
## Feedback and Discrepancies (Based on M3 Review done by Iliana)
|
||||
|
||||
### Mobile Application (Client & Staff)
|
||||
- **Flexible Shift Locations:** Feedback from the M3 review indicated a need for the ability to specify different locations for individual positions within an order on the mobile app. Currently, the web application handles this by requiring a separate shift for each location.
|
||||
|
||||
- **Order Visibility Management:** Currently, when an order is created with multiple positions, the "View Order" page displays them as separate orders. This is due to UI (prototype) cannot support multi-position views in the order. We should consider adopting the "legacy" app's approach to group these positions under a single order for better clarity.
|
||||
|
||||
- **Cost Center Clarification:** The purpose and functionality of the "Cost Center" field in the Hub creation process is not clear.
|
||||
|
||||
- **Tax Form Data Completeness:** Feedback noted that while tax forms are visible in the Staff mobile application, they appear to be missing critical information. This not clear.
|
||||
|
||||
### Web Dashboard
|
||||
- **Role-Based Content Logic:** The current web dashboard prototype contains some logical inconsistencies regarding user roles:
|
||||
- **Client Dashboard:** Currently includes Staff Availability, Staff Directory, and Staff Onboarding. Since workers (Staff) are managed by Vendors, these pages should be moved to the Vendor dashboard.
|
||||
|
||||
- **Vendor Dashboard:** Currently includes "Teams and Hubs." Since Hubs are client-specific locations where staff clock in/out, these management pages should be moved to the Client dashboard.
|
||||
|
||||
- **Admin Dashboard Filtering:** The Admin dashboard requires improved filtering capabilities. Admins should be able to select specific Clients or Vendors to filter related data, such as viewing only the orders associated with a chosen partner.
|
||||
|
||||
## Proposed Features and Enhancements (Post-M3 Identification)
|
||||
|
||||
- **Feature: Navigation to Hub Details from Coverage Screen (#321)**
|
||||
- **Description:** Allow users to navigate directly to the Hub Details page by clicking on a hub within the Coverage Screen.
|
||||
|
||||
- **Feature: Dedicated Hub Details Screen with Order History (#320)**
|
||||
- **Description:** Develop a comprehensive Hub Details view that aggregates all hub-specific data, including historical order records.
|
||||
- **Benefit:** Centralizes information for better decision-making and easier access to historical data.
|
||||
|
||||
- **Feature: Dedicated Order Details Screen**
|
||||
- **Description:** Transition from displaying all order information on the primary "View Order" page to a dedicated "Order Details" screen. This screen will support viewing multiple positions within a single order.
|
||||
- **Benefit:**
|
||||
- **Improved UX:** Reduces complexity by grouping associated positions together and presenting them in a structured way.
|
||||
- **Performance:** Optimizes data loading by fetching detailed position information only when requested.
|
||||
|
||||
- **Feature: Optimized Clock-In Page (#350)**
|
||||
- **Description:** Remove the calendar component from the Clock-In page. Since workers only clock in for current-day assignments, the calendar is unnecessary.
|
||||
- **Benefit:** Simplifies the interface and reduces user confusion.
|
||||
|
||||
- **Feature: Contextual Shift Actions**
|
||||
- **Description:** Restrict the Clock-In page to show only active or upcoming shifts (starting within 30 minutes). Shift-specific actions (Clock-In/Clock-Out) should be performed within the specific Shift Details page.
|
||||
- **Reasoning:** This solves issues where staff cannot clock out of overnight shifts (shifts starting one day and ending the next) due to the current day-based UI.
|
||||
|
||||
- **Feature: Dedicated Emergency Contact Management (#356)**
|
||||
- **Description:** Replace the inline form in the "View Emergency Contact" page with a dedicated "Create Emergency Contact" screen.
|
||||
- **Benefit:** Standardizes the data entry process and improves UI organization within the Staff app.
|
||||
@@ -0,0 +1,168 @@
|
||||
# M4 Roadmap CSV Schema Reconciliation
|
||||
|
||||
Status: Draft for implementation alignment
|
||||
Date: 2026-02-25
|
||||
Owner: Technical Lead
|
||||
|
||||
## 1) Why this exists
|
||||
We reviewed the original product-roadmap exports to confirm that the target schema can support all stakeholder lanes as the platform grows.
|
||||
|
||||
This avoids two failure modes:
|
||||
1. Building command APIs on top of a schema that cannot represent required workflows.
|
||||
2. Hard-coding today's customer setup in a way that blocks future staffing companies.
|
||||
|
||||
## 2) Inputs reviewed
|
||||
All 13 roadmap exports from `/Users/wiel/Downloads`:
|
||||
1. `Krow App – Roadmap - Business App_ Google, Nvidia.csv`
|
||||
2. `Krow App – Roadmap - Client_ Google, Nvidia.csv`
|
||||
3. `Krow App – Roadmap - Compass- The Operator.csv`
|
||||
4. `Krow App – Roadmap - Employee App.csv`
|
||||
5. `Krow App – Roadmap - Features.csv`
|
||||
6. `Krow App – Roadmap - FoodBuy- Procurement.csv`
|
||||
7. `Krow App – Roadmap - KROW Dashboard.csv`
|
||||
8. `Krow App – Roadmap - Offenses.csv`
|
||||
9. `Krow App – Roadmap - Partner.csv`
|
||||
10. `Krow App – Roadmap - Roadmap.csv`
|
||||
11. `Krow App – Roadmap - Sectors_ BA, Flik ( The executors).csv`
|
||||
12. `Krow App – Roadmap - The Workforce_ Employees.csv`
|
||||
13. `Krow App – Roadmap - Vendor_ Legendary (Staffing).csv`
|
||||
|
||||
Parsed signal:
|
||||
1. 983 non-empty task lines.
|
||||
2. 1,263 planning rows with task/status/priority/reference signals.
|
||||
|
||||
## 3) What the roadmap is clearly asking for
|
||||
Cross-file recurring capabilities:
|
||||
1. Multi-party org model: client, operator, vendor, procurement, workforce, partner, sector execution.
|
||||
2. Orders and shift operations: recurring events, assignment, coverage, schedule management.
|
||||
3. Attendance and policy enforcement: clock-in/out, no-show, tardiness, cancellation, offense ladders.
|
||||
4. Compliance and document verification: certifications, insurance, legal docs, renewals, risk alerts.
|
||||
5. Finance and settlement: invoice lifecycle, disputes, remittance, payment history, aging, payroll/earnings.
|
||||
6. Reporting and prediction: dashboards, KPI, forecasting, scenario planning.
|
||||
|
||||
Repeated examples across many sheets:
|
||||
1. `Vendor Onboarding`, `Service Locations`, `Compliance`, `Certifications`.
|
||||
2. `All Invoices (Open/Pending/Overdue/Paid...)`, `Payment Summary`, `Remittance Advice Download`.
|
||||
3. Offense progression rules in `Offenses.csv` and `Employee App.csv` (warning -> suspension -> disable/block).
|
||||
|
||||
## 4) Stakeholder capability matrix (from roadmap exports)
|
||||
|
||||
| Stakeholder lane | Org network | Orders and shifts | Attendance and offense | Compliance docs | Finance | Reporting |
|
||||
|---|---|---|---|---|---|---|
|
||||
| Client (Google, Nvidia) | Yes | Yes | Partial (visibility) | Yes | Yes | Yes |
|
||||
| Vendor (Legendary) | Yes | Yes | Yes | Yes | Yes | Yes |
|
||||
| Workforce (Employee app) | Limited | Yes | Yes | Yes | Earnings focus | Limited |
|
||||
| Operator (Compass) | Yes | Yes | KPI visibility | Yes | Yes | Yes |
|
||||
| Procurement (FoodBuy) | Yes | KPI/SLA focus | KPI/SLA focus | Yes | Yes | Yes |
|
||||
| KROW Dashboard | Cross-entity | Cross-entity | Cross-entity risk | Cross-entity | Cross-entity | Heavy |
|
||||
| Partner | Basic | Basic | Minimal | Yes | Basic | Basic |
|
||||
|
||||
Implication:
|
||||
1. This is a multi-tenant, multi-party workflow platform, not a single-role CRUD app.
|
||||
2. Schema must separate party identity, party relationships, and role-based permissions from workflow records.
|
||||
|
||||
## 5) Reconciliation against current Data Connect schema
|
||||
|
||||
What already exists and is useful:
|
||||
1. Core scheduling entities: `orders`, `shifts`, `shift_roles`, `applications`, `assignments`.
|
||||
2. Workforce entities: `staffs`, `workforce`, `staff_roles`.
|
||||
3. Financial entities: `invoices`, `recent_payments`, `vendor_rates`.
|
||||
4. Compliance entities: `documents`, `staff_documents`, `certificates`.
|
||||
|
||||
Current structural gaps for roadmap scale:
|
||||
1. No tenant boundary key on core tables (`tenant_id` missing).
|
||||
2. No first-class business user partitioning table (`business_memberships` missing).
|
||||
3. No first-class vendor user partitioning table (`vendor_memberships` missing).
|
||||
4. No first-class stakeholder profile/link model for buyer/operator/partner/sector relationships.
|
||||
5. Attendance history is not first-class (check in/out only inside `applications`).
|
||||
6. No offense policy, offense event, or enforcement action tables.
|
||||
7. Finance is coarse (invoice + recent payment), missing line items, payment runs, remittance artifact model.
|
||||
8. Sensitive bank fields are currently modeled directly in `accounts` (`accountNumber`, `routeNumber`).
|
||||
9. Many core workflow fields are JSON (`orders.assignedStaff`, `orders.shifts`, `shift.managers`, `assignment.managers`).
|
||||
10. Money still uses float in critical tables.
|
||||
|
||||
Connector boundary gap:
|
||||
1. 147 Data Connect mutation operations exist.
|
||||
2. 36 of those are high-risk core workflow mutations (`order`, `shift`, `shiftRole`, `application`, `assignment`, `invoice`, `vendor`, `business`, `workForce`, `teamMember`, `account`).
|
||||
|
||||
## 6) Target schema additions required before full command rollout
|
||||
|
||||
### 6.1 Tenant and stakeholder graph
|
||||
1. `tenants`
|
||||
2. `tenant_memberships`
|
||||
3. `business_memberships`
|
||||
4. `vendor_memberships`
|
||||
5. `stakeholder_types`
|
||||
6. `stakeholder_profiles`
|
||||
7. `stakeholder_links`
|
||||
8. `role_bindings` (scoped to tenant/team/hub/business/vendor/resource)
|
||||
|
||||
### 6.2 Attendance and timesheet reliability
|
||||
1. `attendance_events` (append-only clock-in/out/NFC/manual-corrected)
|
||||
2. `attendance_sessions` (derived per shift role assignment)
|
||||
3. `timesheets` (approval state and pay-eligible snapshot)
|
||||
4. `timesheet_adjustments` (manual corrections with audit reason)
|
||||
|
||||
### 6.3 Offense and policy enforcement
|
||||
1. `offense_policies` (per tenant or per business)
|
||||
2. `offense_rules` (threshold and consequence ladder)
|
||||
3. `offense_events` (who, what, when, evidence)
|
||||
4. `enforcement_actions` (warning, suspension, disable, block-from-client)
|
||||
|
||||
### 6.4 Compliance and verification
|
||||
1. `verification_jobs`
|
||||
2. `verification_reviews`
|
||||
3. `verification_events`
|
||||
4. `compliance_requirements` (per role, tenant, business, or client contract)
|
||||
|
||||
### 6.5 Finance completeness
|
||||
1. `invoice_line_items`
|
||||
2. `invoice_status_history`
|
||||
3. `payment_runs`
|
||||
4. `payment_allocations`
|
||||
5. `remittance_documents`
|
||||
6. `account_token_refs` (tokenized provider refs, no raw account/routing)
|
||||
|
||||
## 7) Minimal target relationship view
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
TENANT ||--o{ TENANT_MEMBERSHIP : has
|
||||
BUSINESS ||--o{ BUSINESS_MEMBERSHIP : has
|
||||
VENDOR ||--o{ VENDOR_MEMBERSHIP : has
|
||||
USER ||--o{ BUSINESS_MEMBERSHIP : belongs_to
|
||||
USER ||--o{ VENDOR_MEMBERSHIP : belongs_to
|
||||
TENANT ||--o{ STAKEHOLDER_PROFILE : has
|
||||
STAKEHOLDER_PROFILE ||--o{ STAKEHOLDER_LINK : links_to
|
||||
|
||||
TENANT ||--o{ BUSINESS : owns
|
||||
TENANT ||--o{ VENDOR : owns
|
||||
BUSINESS ||--o{ ORDER : requests
|
||||
VENDOR ||--o{ ORDER : fulfills
|
||||
ORDER ||--o{ SHIFT : expands_to
|
||||
SHIFT ||--o{ SHIFT_ROLE : requires
|
||||
SHIFT_ROLE ||--o{ APPLICATION : receives
|
||||
APPLICATION ||--o{ ASSIGNMENT : becomes
|
||||
|
||||
ASSIGNMENT ||--o{ ATTENDANCE_EVENT : emits
|
||||
ASSIGNMENT ||--o{ TIMESHEET : settles
|
||||
OFFENSE_POLICY ||--o{ OFFENSE_RULE : defines
|
||||
ASSIGNMENT ||--o{ OFFENSE_EVENT : may_trigger
|
||||
OFFENSE_EVENT ||--o{ ENFORCEMENT_ACTION : causes
|
||||
|
||||
ORDER ||--o{ INVOICE : bills
|
||||
INVOICE ||--o{ INVOICE_LINE_ITEM : contains
|
||||
PAYMENT_RUN ||--o{ PAYMENT_ALLOCATION : allocates
|
||||
INVOICE ||--o{ PAYMENT_ALLOCATION : receives
|
||||
PAYMENT_RUN ||--o{ REMITTANCE_DOCUMENT : publishes
|
||||
```
|
||||
|
||||
## 8) First-principles rules we should lock now
|
||||
1. Every command-critical table includes `tenant_id`.
|
||||
2. High-risk writes go through command APIs only.
|
||||
3. Money uses exact numeric type (or integer cents), never float.
|
||||
4. Core workflow state is relational and constrained, not JSON blobs.
|
||||
5. Every irreversible state change has append-only audit event rows.
|
||||
|
||||
## 9) Decision
|
||||
This roadmap evidence supports continuing with the target architecture direction (command boundary + multi-tenant schema), but we should add attendance/offense/settlement/stakeholder-graph tables before full command rollout on mission-critical flows.
|
||||
490
docs/MILESTONES/M4/planning/m4-target-schema-blueprint.md
Normal file
490
docs/MILESTONES/M4/planning/m4-target-schema-blueprint.md
Normal file
@@ -0,0 +1,490 @@
|
||||
# M4 Target Schema Blueprint (Command-Ready)
|
||||
|
||||
Status: Draft for team alignment
|
||||
Date: 2026-02-25
|
||||
Owner: Technical Lead
|
||||
|
||||
## 1) Goal
|
||||
Define the target database shape we want **before** command-backend implementation, so critical flows are atomic, secure, and scalable.
|
||||
|
||||
## 1.1 Stakeholder and tenancy model
|
||||
This product should be designed as a **multi-tenant platform**.
|
||||
|
||||
1. Tenant:
|
||||
- One staffing company account (example: Legendary Event Staffing and Entertainment).
|
||||
2. Business:
|
||||
- A customer/client account owned by a tenant.
|
||||
3. User:
|
||||
- A human identity (auth account) that can belong to one or more tenants.
|
||||
4. Staff:
|
||||
- A workforce profile linked to a user identity and tenant-scoped operations.
|
||||
|
||||
Practical meaning:
|
||||
1. The same platform can serve multiple staffing companies safely.
|
||||
2. Data isolation is by `tenant_id`, not only by business/vendor IDs.
|
||||
3. Not every record starts as a full active user:
|
||||
- invite-first or pending onboarding records are valid,
|
||||
- then bound to `user_id` when activation is completed.
|
||||
4. Business-side users and vendor-side users are partitioned with dedicated membership tables, not only one `userId` owner field.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
U["User identity"] --> M["Tenant membership"]
|
||||
M --> T["Tenant staffing company"]
|
||||
T --> B["Business client"]
|
||||
T --> V["Vendor partner"]
|
||||
B --> O["Orders and shifts"]
|
||||
V --> O
|
||||
```
|
||||
|
||||
## 1.2 Stakeholder wheel mapping (current baseline)
|
||||
The stakeholder labels from the customer workshop map to schema as follows:
|
||||
|
||||
1. Buyer (Procurements):
|
||||
- Buyer users inside a business/client account.
|
||||
- Schema anchor: `users` + `tenant_memberships` + `business_memberships`.
|
||||
2. Enterprises (Operator):
|
||||
- Tenant operator/admin users running staffing operations.
|
||||
- Schema anchor: `tenants`, `tenant_memberships`, `role_bindings`.
|
||||
3. Sectors (Execution):
|
||||
- Operational segments or business units executing events.
|
||||
- Schema anchor: `teams`, `team_hubs`, `team_hud_departments`, `roles`.
|
||||
4. Approved Vendor:
|
||||
- Supplier companies approved to fulfill staffing demand.
|
||||
- Schema anchor: `vendors`, `vendor_memberships`, `workforce`, `vendor_rates`, `vendor_benefit_plans`.
|
||||
5. Workforce:
|
||||
- Individual workers/staff and their assignments.
|
||||
- Schema anchor: `staffs`, `staff_roles`, `applications`, `assignments`, `certificates`, `staff_documents`.
|
||||
6. Partner:
|
||||
- External integration or service partner (future).
|
||||
- Schema anchor: `stakeholder_profiles` extension path + scoped role bindings.
|
||||
|
||||
Rule:
|
||||
1. Start with baseline stakeholders above.
|
||||
2. Add new stakeholders via extension tables and role bindings, not by changing core scheduling and finance tables.
|
||||
|
||||
## 1.3 Future stakeholder expansion model
|
||||
To add stakeholders later without breaking core schema:
|
||||
1. Add `stakeholder_types` (registry).
|
||||
2. Add `stakeholder_profiles` (`tenant_id`, `type`, `status`, `metadata`).
|
||||
3. Add `stakeholder_links` (relationship graph across stakeholders).
|
||||
4. Bind permissions through `role_bindings` with scope (`tenant`, `team`, `hub`, `business`, or specific resource).
|
||||
|
||||
## 1.4 Roadmap CSV evidence snapshot
|
||||
Evidence source:
|
||||
1. `docs/MILESTONES/M4/planning/m4-roadmap-csv-schema-reconciliation.md`
|
||||
|
||||
What the exports confirmed:
|
||||
1. The product is multi-party and multi-tenant by design (client, operator, vendor, workforce, procurement, partner, dashboard).
|
||||
2. Attendance and offense enforcement are core business workflows, not side features.
|
||||
3. Finance requires more than invoices (payment runs, remittance, status history, dispute/audit trace).
|
||||
4. Compliance requires asynchronous verification and requirement templates by tenant/business/role.
|
||||
|
||||
## 2) First-principles rules
|
||||
1. Every critical write must be server-mediated and transactional.
|
||||
2. Tenant boundaries must be explicit in data and queries.
|
||||
3. Money and rates must use exact numeric types, not floating point.
|
||||
4. Data needed for constraints should be relational, not hidden in JSON blobs.
|
||||
5. Every high-risk state transition must be auditable and replayable.
|
||||
|
||||
## 3) Current anti-patterns we are removing
|
||||
1. Direct client mutation of core entities.
|
||||
2. Broad `USER`-auth CRUD without strict tenant scoping.
|
||||
3. Financial values as `Float`.
|
||||
4. Core workflow state embedded in generic `Any/jsonb` fields.
|
||||
5. Missing uniqueness/index constraints on high-traffic paths.
|
||||
|
||||
## 4) Target modular schema
|
||||
|
||||
## 4.1 Identity and Access
|
||||
Tables:
|
||||
1. `users` (source identity, profile, auth linkage)
|
||||
2. `tenant_memberships` (new; membership + base access per tenant)
|
||||
3. `business_memberships` (new; user access to business account scope)
|
||||
4. `vendor_memberships` (new; user access to vendor account scope)
|
||||
5. `team_members` (membership + scope per team)
|
||||
6. `roles` (new)
|
||||
7. `permissions` (new)
|
||||
8. `role_bindings` (new; who has which role in which scope)
|
||||
|
||||
Rules:
|
||||
1. Unique tenant membership: `(tenant_id, user_id)`.
|
||||
2. Unique business membership: `(business_id, user_id)`.
|
||||
3. Unique vendor membership: `(vendor_id, user_id)`.
|
||||
4. Unique team membership: `(team_id, user_id)`.
|
||||
5. Access checks resolve through tenant membership first, then business/vendor/team scope.
|
||||
|
||||
## 4.2 Organization and Tenant
|
||||
Tables:
|
||||
1. `tenants` (new canonical boundary: business/vendor ownership root)
|
||||
2. `businesses`
|
||||
3. `vendors`
|
||||
4. `teams`
|
||||
5. `team_hubs`
|
||||
6. `hubs`
|
||||
|
||||
Rules:
|
||||
1. Every command-critical row references `tenant_id`.
|
||||
2. All list queries must include tenant predicate.
|
||||
3. Business and vendor routes must enforce membership scope before data access.
|
||||
|
||||
## 4.8 RBAC rollout strategy (deferred enforcement)
|
||||
RBAC should be introduced in phases and **not enforced everywhere immediately**.
|
||||
|
||||
Phase A: Auth-first (now)
|
||||
1. Require valid auth token.
|
||||
2. Resolve tenant context.
|
||||
3. Allow current work to continue while logging actor + tenant + action.
|
||||
|
||||
Phase B: Shadow RBAC
|
||||
1. Evaluate permissions (`allow`/`deny`) in backend.
|
||||
2. Log decisions but do not block most requests yet.
|
||||
3. Start with warnings and dashboards for denied actions.
|
||||
|
||||
Phase C: Enforced RBAC on command writes
|
||||
1. Enforce RBAC on `/commands/*` only.
|
||||
2. Keep low-risk read flows in transition mode.
|
||||
|
||||
Phase D: Enforced RBAC on high-risk reads
|
||||
1. Enforce tenant and role checks on sensitive read connectors.
|
||||
2. Remove remaining broad user-level access.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A["Auth only"] --> B["Shadow RBAC logging"]
|
||||
B --> C["Enforce RBAC on command writes"]
|
||||
C --> D["Enforce RBAC on sensitive reads"]
|
||||
```
|
||||
|
||||
## 4.3 Scheduling and Orders
|
||||
Tables:
|
||||
1. `orders`
|
||||
2. `order_schedule_rules` (new; replaces schedule JSON fields)
|
||||
3. `shifts`
|
||||
4. `shift_roles`
|
||||
5. `shift_role_requirements` (optional extension for policy rules)
|
||||
6. `shift_managers` (new; replaces `managers: [Any!]`)
|
||||
|
||||
Rules:
|
||||
1. No denormalized `assignedStaff` or `shifts` JSON in `orders`.
|
||||
2. Time constraints: `start_time < end_time`.
|
||||
3. Capacity constraints: `assigned <= count`, `filled <= workers_needed`.
|
||||
4. Canonical status names (single spelling across schema).
|
||||
|
||||
## 4.4 Staffing and Matching
|
||||
Tables:
|
||||
1. `staffs`
|
||||
2. `staff_roles`
|
||||
3. `workforce`
|
||||
4. `applications`
|
||||
5. `assignments`
|
||||
|
||||
Rules:
|
||||
1. One active workforce relation per `(vendor_id, staff_id)`.
|
||||
2. One application per `(shift_id, role_id, staff_id)` unless versioned intentionally.
|
||||
3. Assignment state transitions only through command APIs.
|
||||
|
||||
## 4.5 Compliance and Verification
|
||||
Tables:
|
||||
1. `documents`
|
||||
2. `staff_documents`
|
||||
3. `certificates`
|
||||
4. `verification_jobs`
|
||||
5. `verification_reviews`
|
||||
6. `verification_events`
|
||||
|
||||
Rules:
|
||||
1. Verification is asynchronous and append-only for events.
|
||||
2. Manual review is explicit and tracked.
|
||||
3. Government ID and certification provider references are persisted.
|
||||
|
||||
## 4.6 Financial and Payout
|
||||
Tables:
|
||||
1. `invoices`
|
||||
2. `invoice_templates`
|
||||
3. `recent_payments`
|
||||
4. `accounts` (refactor to tokenized provider references)
|
||||
|
||||
Rules:
|
||||
1. Replace monetary `Float` with exact numeric (`DECIMAL(12,2)` or integer cents).
|
||||
2. Do not expose raw account/routing values in query connectors.
|
||||
3. Add one-primary-account constraint per owner.
|
||||
|
||||
## 4.7 Audit and Reliability
|
||||
Tables:
|
||||
1. `domain_events` (new)
|
||||
2. `idempotency_keys` (already started in command API SQL)
|
||||
3. `activity_logs`
|
||||
|
||||
Rules:
|
||||
1. Every command write emits a domain event.
|
||||
2. Idempotency scope: `(actor_uid, route, idempotency_key)`.
|
||||
|
||||
## 4.9 Attendance, Timesheets, and Offense Governance
|
||||
Tables:
|
||||
1. `attendance_events` (append-only: clock-in/out, source, correction metadata)
|
||||
2. `attendance_sessions` (derived work session per assignment)
|
||||
3. `timesheets` (approval-ready payroll snapshot)
|
||||
4. `timesheet_adjustments` (manual edits with reason and actor)
|
||||
5. `offense_policies` (tenant/business scoped policy set)
|
||||
6. `offense_rules` (threshold ladder and consequence)
|
||||
7. `offense_events` (actual violation events)
|
||||
8. `enforcement_actions` (warning, suspension, disable, block)
|
||||
|
||||
Rules:
|
||||
1. Attendance corrections are additive events, not destructive overwrites.
|
||||
2. Offense consequences are computed from policy + history and persisted as explicit actions.
|
||||
3. Manual overrides require actor, reason, and timestamp in audit trail.
|
||||
|
||||
## 4.10 Stakeholder Network Extensibility
|
||||
Tables:
|
||||
1. `stakeholder_types` (buyer, operator, vendor, workforce, partner, future types)
|
||||
2. `stakeholder_profiles` (tenant-scoped typed profile)
|
||||
3. `stakeholder_links` (explicit relationship graph between profiles)
|
||||
|
||||
Rules:
|
||||
1. New stakeholder categories are added by data, not by schema rewrites to core workflow tables.
|
||||
2. Permission scope resolves through role bindings plus stakeholder links where needed.
|
||||
3. Scheduling and finance records remain stable while stakeholder topology evolves.
|
||||
|
||||
## 5) Target core model (conceptual)
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
TENANT ||--o{ BUSINESS : owns
|
||||
TENANT ||--o{ VENDOR : owns
|
||||
TENANT ||--o{ TEAM : owns
|
||||
TEAM ||--o{ TEAM_MEMBER : has
|
||||
USER ||--o{ TEAM_MEMBER : belongs_to
|
||||
USER ||--o{ BUSINESS_MEMBERSHIP : belongs_to
|
||||
USER ||--o{ VENDOR_MEMBERSHIP : belongs_to
|
||||
BUSINESS ||--o{ BUSINESS_MEMBERSHIP : has
|
||||
VENDOR ||--o{ VENDOR_MEMBERSHIP : has
|
||||
|
||||
BUSINESS ||--o{ ORDER : requests
|
||||
VENDOR ||--o{ ORDER : fulfills
|
||||
ORDER ||--o{ ORDER_SCHEDULE_RULE : has
|
||||
ORDER ||--o{ SHIFT : expands_to
|
||||
SHIFT ||--o{ SHIFT_ROLE : requires
|
||||
SHIFT ||--o{ SHIFT_MANAGER : has
|
||||
|
||||
USER ||--o{ STAFF : identity
|
||||
STAFF ||--o{ STAFF_ROLE : skills
|
||||
VENDOR ||--o{ WORKFORCE : contracts
|
||||
STAFF ||--o{ WORKFORCE : linked
|
||||
SHIFT_ROLE ||--o{ APPLICATION : receives
|
||||
STAFF ||--o{ APPLICATION : applies
|
||||
SHIFT_ROLE ||--o{ ASSIGNMENT : allocates
|
||||
WORKFORCE ||--o{ ASSIGNMENT : executes
|
||||
ASSIGNMENT ||--o{ ATTENDANCE_EVENT : emits
|
||||
ASSIGNMENT ||--o{ TIMESHEET : settles
|
||||
OFFENSE_POLICY ||--o{ OFFENSE_RULE : defines
|
||||
ASSIGNMENT ||--o{ OFFENSE_EVENT : may_trigger
|
||||
OFFENSE_EVENT ||--o{ ENFORCEMENT_ACTION : causes
|
||||
|
||||
STAFF ||--o{ CERTIFICATE : has
|
||||
STAFF ||--o{ STAFF_DOCUMENT : uploads
|
||||
DOCUMENT ||--o{ STAFF_DOCUMENT : references
|
||||
STAFF ||--o{ VERIFICATION_JOB : subject
|
||||
VERIFICATION_JOB ||--o{ VERIFICATION_REVIEW : reviewed_by
|
||||
VERIFICATION_JOB ||--o{ VERIFICATION_EVENT : logs
|
||||
|
||||
ORDER ||--o{ INVOICE : billed_by
|
||||
INVOICE ||--o{ RECENT_PAYMENT : settles
|
||||
TENANT ||--o{ ACCOUNT_TOKEN_REF : payout_method
|
||||
INVOICE ||--o{ INVOICE_LINE_ITEM : details
|
||||
PAYMENT_RUN ||--o{ PAYMENT_ALLOCATION : allocates
|
||||
INVOICE ||--o{ PAYMENT_ALLOCATION : receives
|
||||
PAYMENT_RUN ||--o{ REMITTANCE_DOCUMENT : publishes
|
||||
|
||||
ORDER ||--o{ DOMAIN_EVENT : emits
|
||||
SHIFT ||--o{ DOMAIN_EVENT : emits
|
||||
ASSIGNMENT ||--o{ DOMAIN_EVENT : emits
|
||||
STAKEHOLDER_TYPE ||--o{ STAKEHOLDER_PROFILE : classifies
|
||||
STAKEHOLDER_PROFILE ||--o{ STAKEHOLDER_LINK : relates
|
||||
```
|
||||
|
||||
## 6) Command write boundary on this schema
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A["Frontend app"] --> B["Command API"]
|
||||
B --> C["Policy + validation"]
|
||||
C --> D["Single database transaction"]
|
||||
D --> E["orders, shifts, shift_roles, applications, assignments"]
|
||||
D --> F["domain_events + idempotency_keys"]
|
||||
E --> G["Read models and reports"]
|
||||
```
|
||||
|
||||
## 7) Minimum constraints and indexes to add before command build
|
||||
|
||||
## 7.1 Constraints
|
||||
1. `shift_roles`: check `assigned >= 0 AND assigned <= count`.
|
||||
2. `shifts`: check `start_time < end_time`.
|
||||
3. `applications`: unique `(shift_id, role_id, staff_id)`.
|
||||
4. `workforce`: unique active `(vendor_id, staff_id)`.
|
||||
5. `team_members`: unique `(team_id, user_id)`.
|
||||
6. `accounts` (or token ref table): unique primary per owner.
|
||||
7. `attendance_events`: unique idempotency tuple (for example `(assignment_id, source_event_id)`).
|
||||
8. `offense_rules`: unique `(policy_id, trigger_type, threshold_count)`.
|
||||
|
||||
## 7.2 Indexes
|
||||
1. `orders (tenant_id, status, date)`.
|
||||
2. `shifts (order_id, date, status)`.
|
||||
3. `shift_roles (shift_id, role_id, start_time)`.
|
||||
4. `applications (shift_id, role_id, status, created_at)`.
|
||||
5. `assignments (workforce_id, shift_id, role_id, status)`.
|
||||
6. `verification_jobs (subject_id, type, status, created_at)`.
|
||||
7. `invoices (business_id, vendor_id, status, due_date)`.
|
||||
8. `attendance_events (assignment_id, event_time, event_type)`.
|
||||
9. `offense_events (staff_id, occurred_at, offense_type, status)`.
|
||||
10. `invoice_line_items (invoice_id, line_type, created_at)`.
|
||||
|
||||
## 8) Data type normalization
|
||||
1. Monetary: `Float -> DECIMAL(12,2)` (or integer cents).
|
||||
2. Generic JSON fields in core scheduling: split into relational tables.
|
||||
3. Timestamps: store UTC and enforce server-generated creation/update fields.
|
||||
|
||||
## 9) Security boundary in schema/connectors
|
||||
1. Remove broad list queries for sensitive entities unless tenant-scoped.
|
||||
2. Strip sensitive fields from connector query payloads (bank/routing).
|
||||
3. Keep high-risk mutations behind command API; Data Connect remains read-first for client.
|
||||
|
||||
## 10) Migration phases (schema-first)
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
P0["Phase 0: Safety patch
|
||||
- lock sensitive fields
|
||||
- enforce tenant-scoped queries
|
||||
- freeze new direct write connectors"] --> P1["Phase 1: Core constraints
|
||||
- add unique/check constraints
|
||||
- add indexes
|
||||
- normalize money types"]
|
||||
P1 --> P2["Phase 2: Tenant and RBAC base tables
|
||||
- add tenants and tenant_memberships
|
||||
- add roles permissions role_bindings
|
||||
- run RBAC in shadow mode"]
|
||||
P2 --> P3["Phase 3: Scheduling normalization
|
||||
- remove order JSON workflow fields
|
||||
- add order_schedule_rules and shift_managers
|
||||
- add attendance and offense base tables"]
|
||||
P3 --> P4["Phase 4: Command rollout
|
||||
- command writes on hardened schema
|
||||
- emit domain events + idempotency
|
||||
- enforce RBAC for command routes
|
||||
- add finance settlement tables for payment runs and remittance"]
|
||||
P4 --> P5["Phase 5: Read migration + cleanup
|
||||
- migrate frontend reads as needed
|
||||
- enforce RBAC for sensitive reads
|
||||
- retire deprecated connectors"]
|
||||
```
|
||||
|
||||
## 11) Definition of ready for command backend
|
||||
1. P0 and P1 complete in `dev`.
|
||||
2. Tenant scoping verified in connector tests.
|
||||
3. Sensitive field exposure removed.
|
||||
4. Core transaction invariants enforced by schema constraints.
|
||||
5. Command API contracts mapped to new normalized tables.
|
||||
6. RBAC is in shadow mode with decision logs in place (not hard-blocking yet).
|
||||
7. Attendance and offense tables are ready for policy-driven command routes.
|
||||
8. Finance settlement tables (`invoice_line_items`, `payment_runs`, `payment_allocations`) are available.
|
||||
|
||||
## 12) Full current model relationship map (all models)
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Account["Account"]
|
||||
ActivityLog["ActivityLog"]
|
||||
Application["Application"]
|
||||
Assignment["Assignment"]
|
||||
AttireOption["AttireOption"]
|
||||
BenefitsData["BenefitsData"]
|
||||
Business["Business"]
|
||||
Category["Category"]
|
||||
Certificate["Certificate"]
|
||||
ClientFeedback["ClientFeedback"]
|
||||
Conversation["Conversation"]
|
||||
Course["Course"]
|
||||
CustomRateCard["CustomRateCard"]
|
||||
Document["Document"]
|
||||
EmergencyContact["EmergencyContact"]
|
||||
FaqData["FaqData"]
|
||||
Hub["Hub"]
|
||||
Invoice["Invoice"]
|
||||
InvoiceTemplate["InvoiceTemplate"]
|
||||
Level["Level"]
|
||||
MemberTask["MemberTask"]
|
||||
Message["Message"]
|
||||
Order["Order"]
|
||||
RecentPayment["RecentPayment"]
|
||||
Role["Role"]
|
||||
RoleCategory["RoleCategory"]
|
||||
Shift["Shift"]
|
||||
ShiftRole["ShiftRole"]
|
||||
Staff["Staff"]
|
||||
StaffAvailability["StaffAvailability"]
|
||||
StaffAvailabilityStats["StaffAvailabilityStats"]
|
||||
StaffCourse["StaffCourse"]
|
||||
StaffDocument["StaffDocument"]
|
||||
StaffRole["StaffRole"]
|
||||
Task["Task"]
|
||||
TaskComment["TaskComment"]
|
||||
TaxForm["TaxForm"]
|
||||
Team["Team"]
|
||||
TeamHub["TeamHub"]
|
||||
TeamHudDepartment["TeamHudDepartment"]
|
||||
TeamMember["TeamMember"]
|
||||
User["User"]
|
||||
UserConversation["UserConversation"]
|
||||
Vendor["Vendor"]
|
||||
VendorBenefitPlan["VendorBenefitPlan"]
|
||||
VendorRate["VendorRate"]
|
||||
Workforce["Workforce"]
|
||||
|
||||
Application --> Shift
|
||||
Application --> ShiftRole
|
||||
Application --> Staff
|
||||
Assignment --> ShiftRole
|
||||
Assignment --> Workforce
|
||||
BenefitsData --> Staff
|
||||
BenefitsData --> VendorBenefitPlan
|
||||
Certificate --> Staff
|
||||
ClientFeedback --> Business
|
||||
ClientFeedback --> Vendor
|
||||
Course --> Category
|
||||
Invoice --> Business
|
||||
Invoice --> Order
|
||||
Invoice --> Vendor
|
||||
InvoiceTemplate --> Business
|
||||
InvoiceTemplate --> Order
|
||||
InvoiceTemplate --> Vendor
|
||||
MemberTask --> Task
|
||||
MemberTask --> TeamMember
|
||||
Message --> User
|
||||
Order --> Business
|
||||
Order --> TeamHub
|
||||
Order --> Vendor
|
||||
RecentPayment --> Application
|
||||
RecentPayment --> Invoice
|
||||
Shift --> Order
|
||||
ShiftRole --> Role
|
||||
ShiftRole --> Shift
|
||||
StaffAvailability --> Staff
|
||||
StaffAvailabilityStats --> Staff
|
||||
StaffDocument --> Document
|
||||
StaffRole --> Role
|
||||
StaffRole --> Staff
|
||||
TaskComment --> TeamMember
|
||||
TeamHub --> Team
|
||||
TeamHudDepartment --> TeamHub
|
||||
TeamMember --> Team
|
||||
TeamMember --> TeamHub
|
||||
TeamMember --> User
|
||||
UserConversation --> Conversation
|
||||
UserConversation --> User
|
||||
VendorBenefitPlan --> Vendor
|
||||
VendorRate --> Vendor
|
||||
Workforce --> Staff
|
||||
Workforce --> Vendor
|
||||
```
|
||||
264
docs/MILESTONES/M4/planning/m4-target-schema-models-and-keys.md
Normal file
264
docs/MILESTONES/M4/planning/m4-target-schema-models-and-keys.md
Normal file
@@ -0,0 +1,264 @@
|
||||
# M4 Target Schema Models, Keys, and Relationships
|
||||
|
||||
Status: Draft for architecture workshop
|
||||
Date: 2026-02-25
|
||||
Owner: Technical Lead
|
||||
|
||||
## 1) Purpose
|
||||
This document is the model-level view for slide creation:
|
||||
1. Key fields per model (`PK`, `FK`, unique keys).
|
||||
2. How models relate to each other.
|
||||
3. A first-principles structure for scale across tenant, business, vendor, and workforce flows.
|
||||
|
||||
## 2) Identity and access models
|
||||
|
||||
### 2.1 Model keys
|
||||
|
||||
| Model | Primary key | Foreign keys | Important unique keys |
|
||||
|---|---|---|---|
|
||||
| `users` | `id` | - | `email` (optional unique) |
|
||||
| `tenants` | `id` | - | `slug` |
|
||||
| `tenant_memberships` | `id` | `tenant_id -> tenants.id`, `user_id -> users.id` | `(tenant_id, user_id)` |
|
||||
| `business_memberships` | `id` | `tenant_id -> tenants.id`, `business_id -> businesses.id`, `user_id -> users.id` | `(business_id, user_id)` |
|
||||
| `vendor_memberships` | `id` | `tenant_id -> tenants.id`, `vendor_id -> vendors.id`, `user_id -> users.id` | `(vendor_id, user_id)` |
|
||||
| `roles` | `id` | `tenant_id -> tenants.id` | `(tenant_id, name)` |
|
||||
| `permissions` | `id` | - | `code` |
|
||||
| `role_bindings` | `id` | `tenant_id -> tenants.id`, `role_id -> roles.id`, `user_id -> users.id` | `(tenant_id, role_id, user_id, scope_type, scope_id)` |
|
||||
|
||||
### 2.2 Diagram
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
USERS ||--o{ TENANT_MEMBERSHIPS : has
|
||||
TENANTS ||--o{ TENANT_MEMBERSHIPS : has
|
||||
|
||||
USERS ||--o{ BUSINESS_MEMBERSHIPS : has
|
||||
BUSINESSES ||--o{ BUSINESS_MEMBERSHIPS : has
|
||||
TENANTS ||--o{ BUSINESS_MEMBERSHIPS : scopes
|
||||
|
||||
USERS ||--o{ VENDOR_MEMBERSHIPS : has
|
||||
VENDORS ||--o{ VENDOR_MEMBERSHIPS : has
|
||||
TENANTS ||--o{ VENDOR_MEMBERSHIPS : scopes
|
||||
|
||||
TENANTS ||--o{ ROLES : defines
|
||||
ROLES ||--o{ ROLE_BINDINGS : used_by
|
||||
USERS ||--o{ ROLE_BINDINGS : receives
|
||||
TENANTS ||--o{ ROLE_BINDINGS : scopes
|
||||
```
|
||||
```
|
||||
|
||||
## 3) Organization and stakeholder models
|
||||
|
||||
### 3.1 Model keys
|
||||
|
||||
| Model | Primary key | Foreign keys | Important unique keys |
|
||||
|---|---|---|---|
|
||||
| `businesses` | `id` | `tenant_id -> tenants.id` | `(tenant_id, business_name)` |
|
||||
| `vendors` | `id` | `tenant_id -> tenants.id` | `(tenant_id, company_name)` |
|
||||
| `teams` | `id` | `tenant_id -> tenants.id` | `(tenant_id, team_name)` |
|
||||
| `team_hubs` | `id` | `team_id -> teams.id` | `(team_id, hub_name)` |
|
||||
| `team_hud_departments` | `id` | `team_hub_id -> team_hubs.id` | `(team_hub_id, name)` |
|
||||
| `stakeholder_types` | `id` | - | `code` |
|
||||
| `stakeholder_profiles` | `id` | `tenant_id -> tenants.id`, `stakeholder_type_id -> stakeholder_types.id` | `(tenant_id, stakeholder_type_id, name)` |
|
||||
| `stakeholder_links` | `id` | `tenant_id -> tenants.id`, `from_profile_id -> stakeholder_profiles.id`, `to_profile_id -> stakeholder_profiles.id` | `(tenant_id, from_profile_id, to_profile_id, relation_type)` |
|
||||
|
||||
### 3.2 Diagram
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
TENANTS ||--o{ BUSINESSES : owns
|
||||
TENANTS ||--o{ VENDORS : owns
|
||||
TENANTS ||--o{ TEAMS : owns
|
||||
TEAMS ||--o{ TEAM_HUBS : has
|
||||
TEAM_HUBS ||--o{ TEAM_HUD_DEPARTMENTS : has
|
||||
|
||||
STAKEHOLDER_TYPES ||--o{ STAKEHOLDER_PROFILES : classifies
|
||||
TENANTS ||--o{ STAKEHOLDER_PROFILES : owns
|
||||
STAKEHOLDER_PROFILES ||--o{ STAKEHOLDER_LINKS : from
|
||||
STAKEHOLDER_PROFILES ||--o{ STAKEHOLDER_LINKS : to
|
||||
TENANTS ||--o{ STAKEHOLDER_LINKS : scopes
|
||||
```
|
||||
```
|
||||
|
||||
## 4) Workforce, orders, and assignments
|
||||
|
||||
### 4.1 Model keys
|
||||
|
||||
| Model | Primary key | Foreign keys | Important unique keys |
|
||||
|---|---|---|---|
|
||||
| `staffs` | `id` | `tenant_id -> tenants.id`, `user_id -> users.id` | `(tenant_id, user_id)` (nullable until activation if needed) |
|
||||
| `staff_roles` | `id` | `staff_id -> staffs.id`, `role_id -> roles.id` | `(staff_id, role_id)` |
|
||||
| `workforce` | `id` | `tenant_id -> tenants.id`, `vendor_id -> vendors.id`, `staff_id -> staffs.id` | active `(vendor_id, staff_id)` |
|
||||
| `orders` | `id` | `tenant_id -> tenants.id`, `business_id -> businesses.id`, `vendor_id -> vendors.id` | `(tenant_id, external_ref)` optional |
|
||||
| `order_schedule_rules` | `id` | `order_id -> orders.id` | `(order_id, rule_type, effective_from)` |
|
||||
| `shifts` | `id` | `tenant_id -> tenants.id`, `order_id -> orders.id` | `(order_id, start_time, end_time)` |
|
||||
| `shift_roles` | `id` | `shift_id -> shifts.id`, `role_id -> roles.id` | `(shift_id, role_id)` |
|
||||
| `shift_managers` | `id` | `shift_id -> shifts.id`, `team_member_id -> team_members.id` | `(shift_id, team_member_id)` |
|
||||
| `applications` | `id` | `tenant_id -> tenants.id`, `shift_id -> shifts.id`, `role_id -> roles.id`, `staff_id -> staffs.id` | `(shift_id, role_id, staff_id)` |
|
||||
| `assignments` | `id` | `tenant_id -> tenants.id`, `shift_role_id -> shift_roles.id`, `workforce_id -> workforce.id` | `(shift_role_id, workforce_id)` active |
|
||||
|
||||
### 4.2 Diagram
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
TENANTS ||--o{ STAFFS : owns
|
||||
USERS ||--o| STAFFS : identity
|
||||
STAFFS ||--o{ STAFF_ROLES : has
|
||||
ROLES ||--o{ STAFF_ROLES : classifies
|
||||
|
||||
TENANTS ||--o{ ORDERS : scopes
|
||||
BUSINESSES ||--o{ ORDERS : requests
|
||||
VENDORS ||--o{ ORDERS : fulfills
|
||||
ORDERS ||--o{ ORDER_SCHEDULE_RULES : configures
|
||||
ORDERS ||--o{ SHIFTS : expands_to
|
||||
SHIFTS ||--o{ SHIFT_ROLES : requires
|
||||
ROLES ||--o{ SHIFT_ROLES : typed_as
|
||||
SHIFTS ||--o{ SHIFT_MANAGERS : has
|
||||
|
||||
VENDORS ||--o{ WORKFORCE : contracts
|
||||
STAFFS ||--o{ WORKFORCE : linked
|
||||
|
||||
SHIFT_ROLES ||--o{ APPLICATIONS : receives
|
||||
STAFFS ||--o{ APPLICATIONS : applies
|
||||
SHIFT_ROLES ||--o{ ASSIGNMENTS : allocates
|
||||
WORKFORCE ||--o{ ASSIGNMENTS : executes
|
||||
```
|
||||
```
|
||||
|
||||
## 5) Attendance and offense governance
|
||||
|
||||
### 5.1 Model keys
|
||||
|
||||
| Model | Primary key | Foreign keys | Important unique keys |
|
||||
|---|---|---|---|
|
||||
| `attendance_events` | `id` | `tenant_id -> tenants.id`, `assignment_id -> assignments.id` | `(assignment_id, source_event_id)` |
|
||||
| `attendance_sessions` | `id` | `tenant_id -> tenants.id`, `assignment_id -> assignments.id` | one open session per assignment |
|
||||
| `timesheets` | `id` | `tenant_id -> tenants.id`, `assignment_id -> assignments.id`, `staff_id -> staffs.id` | `(assignment_id)` |
|
||||
| `timesheet_adjustments` | `id` | `timesheet_id -> timesheets.id`, `actor_user_id -> users.id` | - |
|
||||
| `offense_policies` | `id` | `tenant_id -> tenants.id`, `business_id -> businesses.id` nullable | `(tenant_id, name, business_id)` |
|
||||
| `offense_rules` | `id` | `policy_id -> offense_policies.id` | `(policy_id, trigger_type, threshold_count)` |
|
||||
| `offense_events` | `id` | `tenant_id -> tenants.id`, `staff_id -> staffs.id`, `assignment_id -> assignments.id` nullable, `rule_id -> offense_rules.id` nullable | `(staff_id, occurred_at, offense_type)` |
|
||||
| `enforcement_actions` | `id` | `offense_event_id -> offense_events.id`, `actor_user_id -> users.id` | - |
|
||||
|
||||
### 5.2 Diagram
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
ASSIGNMENTS ||--o{ ATTENDANCE_EVENTS : emits
|
||||
ASSIGNMENTS ||--o{ ATTENDANCE_SESSIONS : opens
|
||||
ASSIGNMENTS ||--o{ TIMESHEETS : settles
|
||||
TIMESHEETS ||--o{ TIMESHEET_ADJUSTMENTS : adjusts
|
||||
USERS ||--o{ TIMESHEET_ADJUSTMENTS : made_by
|
||||
|
||||
TENANTS ||--o{ OFFENSE_POLICIES : defines
|
||||
BUSINESSES ||--o{ OFFENSE_POLICIES : overrides
|
||||
OFFENSE_POLICIES ||--o{ OFFENSE_RULES : contains
|
||||
STAFFS ||--o{ OFFENSE_EVENTS : incurs
|
||||
OFFENSE_RULES ||--o{ OFFENSE_EVENTS : triggered_by
|
||||
OFFENSE_EVENTS ||--o{ ENFORCEMENT_ACTIONS : causes
|
||||
USERS ||--o{ ENFORCEMENT_ACTIONS : applied_by
|
||||
```
|
||||
```
|
||||
|
||||
## 6) Compliance and verification
|
||||
|
||||
### 6.1 Model keys
|
||||
|
||||
| Model | Primary key | Foreign keys | Important unique keys |
|
||||
|---|---|---|---|
|
||||
| `documents` | `id` | `tenant_id -> tenants.id` | `(tenant_id, document_type, name)` |
|
||||
| `staff_documents` | `id` | `staff_id -> staffs.id`, `document_id -> documents.id` | `(staff_id, document_id)` |
|
||||
| `certificates` | `id` | `staff_id -> staffs.id` | `(staff_id, certificate_number)` optional |
|
||||
| `compliance_requirements` | `id` | `tenant_id -> tenants.id`, `business_id -> businesses.id` nullable, `role_id -> roles.id` nullable | `(tenant_id, requirement_type, business_id, role_id)` |
|
||||
| `verification_jobs` | `id` | `tenant_id -> tenants.id`, `staff_id -> staffs.id`, `document_id -> documents.id` nullable | `(tenant_id, idempotency_key)` |
|
||||
| `verification_reviews` | `id` | `verification_job_id -> verification_jobs.id`, `reviewer_user_id -> users.id` | - |
|
||||
| `verification_events` | `id` | `verification_job_id -> verification_jobs.id` | - |
|
||||
|
||||
### 6.2 Diagram
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
STAFFS ||--o{ STAFF_DOCUMENTS : uploads
|
||||
DOCUMENTS ||--o{ STAFF_DOCUMENTS : references
|
||||
STAFFS ||--o{ CERTIFICATES : has
|
||||
|
||||
TENANTS ||--o{ COMPLIANCE_REQUIREMENTS : defines
|
||||
BUSINESSES ||--o{ COMPLIANCE_REQUIREMENTS : overrides
|
||||
ROLES ||--o{ COMPLIANCE_REQUIREMENTS : role_scope
|
||||
|
||||
STAFFS ||--o{ VERIFICATION_JOBS : subject
|
||||
VERIFICATION_JOBS ||--o{ VERIFICATION_REVIEWS : reviewed
|
||||
VERIFICATION_JOBS ||--o{ VERIFICATION_EVENTS : logs
|
||||
USERS ||--o{ VERIFICATION_REVIEWS : reviewer
|
||||
```
|
||||
```
|
||||
|
||||
## 7) Finance and settlement
|
||||
|
||||
### 7.1 Model keys
|
||||
|
||||
| Model | Primary key | Foreign keys | Important unique keys |
|
||||
|---|---|---|---|
|
||||
| `invoices` | `id` | `tenant_id -> tenants.id`, `order_id -> orders.id`, `business_id -> businesses.id`, `vendor_id -> vendors.id` | `(tenant_id, invoice_number)` |
|
||||
| `invoice_line_items` | `id` | `invoice_id -> invoices.id`, `assignment_id -> assignments.id` nullable | `(invoice_id, line_number)` |
|
||||
| `invoice_status_history` | `id` | `invoice_id -> invoices.id`, `actor_user_id -> users.id` | - |
|
||||
| `payment_runs` | `id` | `tenant_id -> tenants.id` | `(tenant_id, run_reference)` |
|
||||
| `payment_allocations` | `id` | `payment_run_id -> payment_runs.id`, `invoice_id -> invoices.id` | `(payment_run_id, invoice_id)` |
|
||||
| `remittance_documents` | `id` | `payment_run_id -> payment_runs.id` | `(payment_run_id, document_url)` |
|
||||
| `account_token_refs` | `id` | `tenant_id -> tenants.id`, `owner_business_id -> businesses.id` nullable, `owner_vendor_id -> vendors.id` nullable | one primary per owner |
|
||||
|
||||
### 7.2 Diagram
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
ORDERS ||--o{ INVOICES : billed
|
||||
BUSINESSES ||--o{ INVOICES : billed_to
|
||||
VENDORS ||--o{ INVOICES : billed_from
|
||||
INVOICES ||--o{ INVOICE_LINE_ITEMS : contains
|
||||
INVOICES ||--o{ INVOICE_STATUS_HISTORY : transitions
|
||||
USERS ||--o{ INVOICE_STATUS_HISTORY : changed_by
|
||||
|
||||
PAYMENT_RUNS ||--o{ PAYMENT_ALLOCATIONS : allocates
|
||||
INVOICES ||--o{ PAYMENT_ALLOCATIONS : receives
|
||||
PAYMENT_RUNS ||--o{ REMITTANCE_DOCUMENTS : publishes
|
||||
|
||||
TENANTS ||--o{ ACCOUNT_TOKEN_REFS : scopes
|
||||
BUSINESSES ||--o{ ACCOUNT_TOKEN_REFS : business_owner
|
||||
VENDORS ||--o{ ACCOUNT_TOKEN_REFS : vendor_owner
|
||||
```
|
||||
```
|
||||
|
||||
## 8) Audit and reliability
|
||||
|
||||
### 8.1 Model keys
|
||||
|
||||
| Model | Primary key | Foreign keys | Important unique keys |
|
||||
|---|---|---|---|
|
||||
| `domain_events` | `id` | `tenant_id -> tenants.id`, `actor_user_id -> users.id` | `(tenant_id, aggregate_type, aggregate_id, sequence)` |
|
||||
| `idempotency_keys` | `id` | `tenant_id -> tenants.id`, `actor_user_id -> users.id` | `(tenant_id, actor_user_id, route, key)` |
|
||||
| `activity_logs` | `id` | `tenant_id -> tenants.id`, `user_id -> users.id` | `(tenant_id, created_at, id)` |
|
||||
|
||||
### 8.2 Diagram
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
TENANTS ||--o{ DOMAIN_EVENTS : scopes
|
||||
USERS ||--o{ DOMAIN_EVENTS : actor
|
||||
TENANTS ||--o{ IDEMPOTENCY_KEYS : scopes
|
||||
USERS ||--o{ IDEMPOTENCY_KEYS : actor
|
||||
TENANTS ||--o{ ACTIVITY_LOGS : scopes
|
||||
USERS ||--o{ ACTIVITY_LOGS : actor
|
||||
```
|
||||
```
|
||||
|
||||
## 9) Practical note for current system
|
||||
Current schema already has:
|
||||
1. `businesses.userId` (single business owner user).
|
||||
2. `vendors.userId` (single vendor owner user).
|
||||
3. `team_members` (multi-user workaround).
|
||||
|
||||
Target schema improves this by adding explicit:
|
||||
1. `business_memberships`
|
||||
2. `vendor_memberships`
|
||||
|
||||
This is the key upgrade for clean client/vendor user partitioning.
|
||||
@@ -0,0 +1,233 @@
|
||||
# M4 Verification Architecture Contract (Attire, Government ID, Certification)
|
||||
|
||||
Status: Partially implemented in dev (core endpoints + async in-memory processor)
|
||||
Date: 2026-02-24
|
||||
Owner: Technical Lead
|
||||
|
||||
## Implementation status today (dev)
|
||||
1. Implemented routes:
|
||||
- `POST /core/verifications`
|
||||
- `GET /core/verifications/{verificationId}`
|
||||
- `POST /core/verifications/{verificationId}/review`
|
||||
- `POST /core/verifications/{verificationId}/retry`
|
||||
2. Current processor is in-memory and non-persistent (for fast frontend integration in dev).
|
||||
3. Next hardening step is persistent job storage and worker execution before staging.
|
||||
4. Attire uses a live Vertex vision model path with `gemini-2.0-flash-lite-001` by default.
|
||||
5. Government ID and certification use third-party adapter contracts (provider URL/token envs) and fall back to `NEEDS_REVIEW` when providers are not configured.
|
||||
|
||||
## 1) Goal
|
||||
Define a single backend verification pipeline for:
|
||||
1. `attire`
|
||||
2. `government_id`
|
||||
3. `certification`
|
||||
|
||||
This contract gives the team exact endpoint behavior, state flow, and ownership before coding.
|
||||
|
||||
## 2) Principles
|
||||
1. Upload is evidence intake, not final verification.
|
||||
2. Verification runs asynchronously in backend workers.
|
||||
3. Model output is a signal, not legal truth.
|
||||
4. High-risk identity decisions require stronger validation and human audit trail.
|
||||
5. Every decision is traceable (`who`, `what`, `when`, `why`).
|
||||
|
||||
## 3) Verification types and policy
|
||||
|
||||
## 3.1 Attire
|
||||
1. Primary check: vision model + rule checks.
|
||||
2. Typical output: `AUTO_PASS`, `AUTO_FAIL`, or `NEEDS_REVIEW`.
|
||||
3. Manual override is always allowed.
|
||||
|
||||
## 3.2 Government ID
|
||||
1. Required path for mission-critical use: third-party identity verification provider.
|
||||
2. Model/OCR can pre-parse fields but does not replace identity verification.
|
||||
3. Final status should require either provider success or manual approval by authorized reviewer.
|
||||
|
||||
## 3.3 Certification
|
||||
1. Preferred path: verify against issuer API/registry when available.
|
||||
2. If no issuer API: OCR extraction + manual review.
|
||||
3. Keep evidence of the source used for validation.
|
||||
|
||||
## 4) State model
|
||||
1. `PENDING`
|
||||
2. `PROCESSING`
|
||||
3. `AUTO_PASS`
|
||||
4. `AUTO_FAIL`
|
||||
5. `NEEDS_REVIEW`
|
||||
6. `APPROVED`
|
||||
7. `REJECTED`
|
||||
8. `ERROR`
|
||||
|
||||
Rules:
|
||||
1. `AUTO_*` and `NEEDS_REVIEW` are machine outcomes.
|
||||
2. `APPROVED` and `REJECTED` are human outcomes.
|
||||
3. All transitions are append-only in audit events.
|
||||
|
||||
## 5) API contract
|
||||
|
||||
## 5.1 Create verification job
|
||||
1. Route: `POST /core/verifications`
|
||||
2. Auth: required
|
||||
3. Purpose: enqueue verification job for previously uploaded file.
|
||||
4. Request:
|
||||
```json
|
||||
{
|
||||
"type": "attire",
|
||||
"subjectType": "staff",
|
||||
"subjectId": "staff_123",
|
||||
"fileUri": "gs://krow-workforce-dev-private/uploads/<uid>/item.jpg",
|
||||
"rules": {
|
||||
"attireType": "shoes",
|
||||
"expectedColor": "black"
|
||||
},
|
||||
"metadata": {
|
||||
"shiftId": "shift_123"
|
||||
}
|
||||
}
|
||||
```
|
||||
5. Success `202`:
|
||||
```json
|
||||
{
|
||||
"verificationId": "ver_123",
|
||||
"status": "PENDING",
|
||||
"type": "attire",
|
||||
"requestId": "uuid"
|
||||
}
|
||||
```
|
||||
|
||||
## 5.2 Get verification status
|
||||
1. Route: `GET /core/verifications/{verificationId}`
|
||||
2. Auth: required
|
||||
3. Purpose: polling from frontend.
|
||||
4. Success `200`:
|
||||
```json
|
||||
{
|
||||
"verificationId": "ver_123",
|
||||
"type": "attire",
|
||||
"status": "NEEDS_REVIEW",
|
||||
"confidence": 0.62,
|
||||
"reasons": ["Color uncertain"],
|
||||
"extracted": {
|
||||
"detectedType": "shoe",
|
||||
"detectedColor": "dark"
|
||||
},
|
||||
"provider": {
|
||||
"name": "vertex-attire",
|
||||
"reference": "gemini-2.0-flash-lite-001"
|
||||
},
|
||||
"review": null,
|
||||
"createdAt": "2026-02-24T15:00:00Z",
|
||||
"updatedAt": "2026-02-24T15:00:04Z",
|
||||
"requestId": "uuid"
|
||||
}
|
||||
```
|
||||
|
||||
## 5.3 Review override
|
||||
1. Route: `POST /core/verifications/{verificationId}/review`
|
||||
2. Auth: required (reviewer role later; auth-first now + explicit reviewer id logging)
|
||||
3. Purpose: final human decision and audit reason.
|
||||
4. Request:
|
||||
```json
|
||||
{
|
||||
"decision": "APPROVED",
|
||||
"note": "Document matches required certification",
|
||||
"reasonCode": "MANUAL_REVIEW"
|
||||
}
|
||||
```
|
||||
5. Success `200`:
|
||||
```json
|
||||
{
|
||||
"verificationId": "ver_123",
|
||||
"status": "APPROVED",
|
||||
"review": {
|
||||
"decision": "APPROVED",
|
||||
"reviewedBy": "user_456",
|
||||
"reviewedAt": "2026-02-24T15:02:00Z",
|
||||
"note": "Document matches required certification",
|
||||
"reasonCode": "MANUAL_REVIEW"
|
||||
},
|
||||
"requestId": "uuid"
|
||||
}
|
||||
```
|
||||
|
||||
## 5.4 Retry verification job
|
||||
1. Route: `POST /core/verifications/{verificationId}/retry`
|
||||
2. Auth: required
|
||||
3. Purpose: rerun failed or updated checks.
|
||||
4. Success `202`: status resets to `PENDING`.
|
||||
|
||||
## 6) Worker execution flow
|
||||
1. API validates payload and ownership of `fileUri`.
|
||||
2. API writes `verification_jobs` row with `PENDING`.
|
||||
3. Worker consumes job, marks `PROCESSING`.
|
||||
4. Worker selects processor by type:
|
||||
- `attire` -> model + rule scorer
|
||||
- `government_id` -> provider adapter (+ optional OCR pre-check)
|
||||
- `certification` -> issuer API adapter or OCR adapter
|
||||
5. Worker writes machine outcome (`AUTO_PASS`, `AUTO_FAIL`, `NEEDS_REVIEW`, or `ERROR`).
|
||||
6. Frontend polls status route.
|
||||
7. Reviewer finalizes with `APPROVED` or `REJECTED` where needed.
|
||||
|
||||
## 7) Data model (minimal)
|
||||
|
||||
## 7.1 Table: `verification_jobs`
|
||||
1. `id` (pk)
|
||||
2. `type` (`attire|government_id|certification`)
|
||||
3. `subject_type`, `subject_id`
|
||||
4. `owner_uid`
|
||||
5. `file_uri`
|
||||
6. `status`
|
||||
7. `confidence` (nullable)
|
||||
8. `reasons_json`
|
||||
9. `extracted_json`
|
||||
10. `provider_name`, `provider_ref`
|
||||
11. `created_at`, `updated_at`
|
||||
|
||||
## 7.2 Table: `verification_reviews`
|
||||
1. `id` (pk)
|
||||
2. `verification_id` (fk)
|
||||
3. `decision` (`APPROVED|REJECTED`)
|
||||
4. `reviewed_by`
|
||||
5. `note`
|
||||
6. `reason_code`
|
||||
7. `reviewed_at`
|
||||
|
||||
## 7.3 Table: `verification_events`
|
||||
1. `id` (pk)
|
||||
2. `verification_id` (fk)
|
||||
3. `from_status`, `to_status`
|
||||
4. `actor_type` (`system|reviewer`)
|
||||
5. `actor_id`
|
||||
6. `details_json`
|
||||
7. `created_at`
|
||||
|
||||
## 8) Security and compliance notes
|
||||
1. Restrict verification file paths to owner-owned upload prefixes.
|
||||
2. Never expose raw private bucket URLs directly.
|
||||
3. Keep third-party provider secrets in Secret Manager.
|
||||
4. Log request and decision IDs for every transition.
|
||||
5. For government ID, keep provider response reference and verification timestamp.
|
||||
|
||||
## 9) Provider configuration (environment variables)
|
||||
1. Attire model:
|
||||
- `VERIFICATION_ATTIRE_PROVIDER=vertex`
|
||||
- `VERIFICATION_ATTIRE_MODEL=gemini-2.0-flash-lite-001`
|
||||
2. Government ID provider:
|
||||
- `VERIFICATION_GOV_ID_PROVIDER_URL`
|
||||
- `VERIFICATION_GOV_ID_PROVIDER_TOKEN` (Secret Manager recommended)
|
||||
3. Certification provider:
|
||||
- `VERIFICATION_CERT_PROVIDER_URL`
|
||||
- `VERIFICATION_CERT_PROVIDER_TOKEN` (Secret Manager recommended)
|
||||
4. Provider timeout:
|
||||
- `VERIFICATION_PROVIDER_TIMEOUT_MS` (default `8000`)
|
||||
|
||||
## 10) Frontend integration pattern
|
||||
1. Upload file via existing `POST /core/upload-file`.
|
||||
2. Create verification job with returned `fileUri`.
|
||||
3. Poll `GET /core/verifications/{id}` until terminal state.
|
||||
4. Show machine status and confidence.
|
||||
5. For `NEEDS_REVIEW`, show pending-review UI state.
|
||||
|
||||
## 11) Delivery split (recommended)
|
||||
1. Wave A (fast): attire verification pipeline end-to-end.
|
||||
2. Wave B: certification verification with issuer adapter + review.
|
||||
3. Wave C: government ID provider integration + reviewer flow hardening.
|
||||
BIN
docs/available_gql.txt
Normal file
BIN
docs/available_gql.txt
Normal file
Binary file not shown.
85
docs/research/flutter-testing-tools.md
Normal file
85
docs/research/flutter-testing-tools.md
Normal file
@@ -0,0 +1,85 @@
|
||||
# 📱 Research: Flutter Integration Testing Evaluation
|
||||
**Issue:** #533
|
||||
**Focus:** Maestro vs. Marionette MCP (LeanCode)
|
||||
**Status:** ✅ Completed
|
||||
**Target Apps:** `KROW Client App` & `KROW Staff App`
|
||||
|
||||
---
|
||||
|
||||
## 1. Executive Summary & Recommendation
|
||||
|
||||
Following a technical spike implementing full authentication flows (Login/Signup) for both KROW platforms, **Maestro is the recommended integration testing framework.**
|
||||
|
||||
While **Marionette MCP** offers an innovative LLM-driven approach for exploratory debugging, it lacks the determinism required for a production-grade CI/CD pipeline. Maestro provides the stability, speed, and native OS interaction necessary to gate our releases effectively.
|
||||
|
||||
### Why Maestro Wins for KROW:
|
||||
* **Zero-Flake Execution:** Built-in wait logic handles Firebase Auth latency without hard-coded `sleep()` calls.
|
||||
* **Platform Parity:** Single `.yaml` definitions drive both iOS and Android build variants.
|
||||
* **Non-Invasive:** Maestro tests the compiled `.apk` or `.app` (Black-box), ensuring we test exactly what the user sees.
|
||||
* **System Level Access:** Handles native OS permission dialogs (Camera/Location/Notifications) which Marionette cannot "see."
|
||||
|
||||
---
|
||||
|
||||
## 2. Technical Evaluation Matrix
|
||||
|
||||
| Criteria | Maestro | Marionette MCP | Winner |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| **Test Authoring** | **High Speed:** Declarative YAML; Maestro Studio recorder. | **Variable:** Requires precise Prompt Engineering. | **Maestro** |
|
||||
| **Execution Latency** | **Low:** Instantaneous interaction (~5s flows). | **High:** LLM API roundtrips (~45s+ flows). | **Maestro** |
|
||||
| **Environment** | Works on Release/Production builds. | Restricted to Debug/Profile modes. | **Maestro** |
|
||||
| **CI/CD Readiness** | Native CLI; easy GitHub Actions integration. | High overhead; depends on external AI APIs. | **Maestro** |
|
||||
| **Context Awareness** | Interacts with Native OS & Bottom Sheets. | Limited to the Flutter Widget Tree. | **Maestro** |
|
||||
|
||||
---
|
||||
|
||||
## 3. Spike Analysis & Findings
|
||||
|
||||
### Tool A: Maestro (The Standard)
|
||||
We verified the `login.yaml` and `signup.yaml` flows across both apps. Maestro successfully abstracted the asynchronous nature of our **Data Connect** and **Firebase** backends.
|
||||
|
||||
* **Pros:** * **Semantics Driven:** By targeting `Semantics(identifier: '...')` in our `/design_system/`, tests remain stable even if the UI text changes for localization.
|
||||
* **Automatic Tolerance:** It detects spinning loaders and waits for destination widgets automatically.
|
||||
* **Cons:** * Requires strict adherence to adding `Semantics` wrappers on all interactive components.
|
||||
|
||||
### Tool B: Marionette MCP (The Experiment)
|
||||
We spiked this using the `marionette_flutter` binding and executing via **Cursor/Claude**.
|
||||
|
||||
* **Pros:** * Phenomenal for visual "smoke testing" and live-debugging UI issues via natural language.
|
||||
* **Cons:** * **Non-Deterministic:** Prone to "hallucinations" during heavy network traffic.
|
||||
* **Architecture Blocker:** Requires the Dart VM Service to be active, making it impossible to test against hardened production builds.
|
||||
|
||||
---
|
||||
|
||||
## 4. Implementation & Migration Blueprint
|
||||
|
||||
|
||||
|
||||
### Phase 1: Semantics Enforcement
|
||||
We must enforce a linting rule or PR checklist: All interactive widgets in `@krow/design_system` must include a unique `identifier`.
|
||||
|
||||
```dart
|
||||
// Standardized Implementation
|
||||
Semantics(
|
||||
identifier: 'login_submit_button',
|
||||
child: KrowPrimaryButton(
|
||||
onPressed: _handleLogin,
|
||||
label: 'Sign In',
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
### Phase 2: Repository Structure (Implemented)
|
||||
Maestro flows are co-located with each app under `auth/`:
|
||||
|
||||
* `apps/mobile/apps/client/maestro/auth/sign_in.yaml` — Client sign-in
|
||||
* `apps/mobile/apps/client/maestro/auth/sign_up.yaml` — Client sign-up
|
||||
* `apps/mobile/apps/staff/maestro/auth/sign_in.yaml` — Staff sign-in (phone + OTP)
|
||||
* `apps/mobile/apps/staff/maestro/auth/sign_up.yaml` — Staff sign-up (phone + OTP)
|
||||
|
||||
Credentials are injected via env variables (never hardcoded). Use `make test-e2e` to run the suite.
|
||||
|
||||
### Phase 3: CI/CD Integration
|
||||
The Maestro CLI will be added to our **GitHub Actions** workflow to automate quality gates.
|
||||
|
||||
* **Trigger:** Every PR targeting `main` or `develop`.
|
||||
* **Action:** Generate a build, execute `maestro test`, and block merge on failure.
|
||||
100
docs/research/maestro-test-run-instructions.md
Normal file
100
docs/research/maestro-test-run-instructions.md
Normal file
@@ -0,0 +1,100 @@
|
||||
# How to Run Maestro Integration Tests
|
||||
|
||||
Credentials are injected via env variables — **never hardcoded** in YAML.
|
||||
|
||||
## Env variables
|
||||
|
||||
| Flow | Env variables |
|
||||
|------|---------------|
|
||||
| **Client sign-in** | `TEST_CLIENT_EMAIL`, `TEST_CLIENT_PASSWORD` |
|
||||
| **Client sign-up** | `TEST_CLIENT_EMAIL`, `TEST_CLIENT_PASSWORD`, `TEST_CLIENT_COMPANY` |
|
||||
| **Staff sign-in** | `TEST_STAFF_PHONE`, `TEST_STAFF_OTP` |
|
||||
| **Staff sign-up** | `TEST_STAFF_SIGNUP_PHONE`, `TEST_STAFF_OTP` |
|
||||
|
||||
**Example values (login):** legendary@krowd.com / Demo2026! (client), 5557654321 / 123456 (staff)
|
||||
|
||||
---
|
||||
|
||||
## Step-by-step
|
||||
|
||||
### 1. Install Maestro CLI
|
||||
|
||||
**Windows:** Download from [Maestro releases](https://github.com/mobile-dev-inc/maestro/releases), extract, add to PATH.
|
||||
|
||||
**macOS/Linux:**
|
||||
```bash
|
||||
curl -Ls "https://get.maestro.mobile.dev" | bash
|
||||
```
|
||||
|
||||
### 2. Add Firebase test phone (Staff app)
|
||||
|
||||
Firebase Console → **Authentication** → **Sign-in method** → **Phone** → **Phone numbers for testing**:
|
||||
- Add **+1 5557654321** with verification code **123456**
|
||||
|
||||
### 3. Build and install apps
|
||||
|
||||
```bash
|
||||
make mobile-client-build PLATFORM=apk MODE=debug
|
||||
adb install apps/mobile/apps/client/build/app/outputs/flutter-apk/app-debug.apk
|
||||
|
||||
make mobile-staff-build PLATFORM=apk MODE=debug
|
||||
adb install apps/mobile/apps/staff/build/app/outputs/flutter-apk/app-debug.apk
|
||||
```
|
||||
|
||||
### 4. Run E2E tests via Makefile
|
||||
|
||||
**Export credentials, then run:**
|
||||
|
||||
```bash
|
||||
# Client login credentials
|
||||
export TEST_CLIENT_EMAIL=legendary@krowd.com
|
||||
export TEST_CLIENT_PASSWORD=Demo2026!
|
||||
export TEST_CLIENT_COMPANY="Krow Demo"
|
||||
|
||||
# Staff login credentials
|
||||
export TEST_STAFF_PHONE=5557654321
|
||||
export TEST_STAFF_OTP=123456
|
||||
export TEST_STAFF_SIGNUP_PHONE=5555550000 # use a new number for signup
|
||||
|
||||
# Run full suite
|
||||
make test-e2e
|
||||
|
||||
# Or run per app
|
||||
make test-e2e-client
|
||||
make test-e2e-staff
|
||||
```
|
||||
|
||||
### 5. Run flows directly (without Make)
|
||||
|
||||
```bash
|
||||
maestro test apps/mobile/apps/client/maestro/auth/sign_in.yaml \
|
||||
-e TEST_CLIENT_EMAIL=legendary@krowd.com \
|
||||
-e TEST_CLIENT_PASSWORD=Demo2026!
|
||||
|
||||
maestro test apps/mobile/apps/staff/maestro/auth/sign_in.yaml \
|
||||
-e TEST_STAFF_PHONE=5557654321 \
|
||||
-e TEST_STAFF_OTP=123456
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Folder structure
|
||||
|
||||
```
|
||||
apps/mobile/apps/client/maestro/auth/
|
||||
sign_in.yaml
|
||||
sign_up.yaml
|
||||
apps/mobile/apps/staff/maestro/auth/
|
||||
sign_in.yaml
|
||||
sign_up.yaml
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Checklist
|
||||
|
||||
- [ ] Maestro CLI installed
|
||||
- [ ] Firebase test phone +1 5557654321 / 123456 added
|
||||
- [ ] Client & Staff apps built and installed
|
||||
- [ ] Env vars exported
|
||||
- [ ] `make test-e2e` run from project root
|
||||
Reference in New Issue
Block a user