docs: lock backend foundation plan and tracking format
This commit is contained in:
228
docs/MILESTONES/M4/planning/m4-api-catalog.md
Normal file
228
docs/MILESTONES/M4/planning/m4-api-catalog.md
Normal file
@@ -0,0 +1,228 @@
|
||||
# M4 API Catalog (Implementation Contract)
|
||||
|
||||
Status: Draft
|
||||
Date: 2026-02-24
|
||||
Owner: Technical Lead
|
||||
Environment: dev
|
||||
|
||||
## 1) Scope and purpose
|
||||
This file defines the backend endpoint contract for the M4 foundation build.
|
||||
|
||||
## 2) Global API rules
|
||||
1. Canonical route groups:
|
||||
- `/core/*` for foundational integration routes
|
||||
- `/commands/*` for business-critical writes
|
||||
2. Foundation phase security model:
|
||||
- authenticated user required
|
||||
- role map enforcement deferred
|
||||
- policy hook required in handler design
|
||||
3. Standard error envelope:
|
||||
```json
|
||||
{
|
||||
"code": "STRING_CODE",
|
||||
"message": "Human readable message",
|
||||
"details": {},
|
||||
"requestId": "optional-request-id"
|
||||
}
|
||||
```
|
||||
4. Required request headers:
|
||||
- `Authorization: Bearer <firebase-token>`
|
||||
- `X-Request-Id: <uuid>` (optional but recommended)
|
||||
5. Required response headers:
|
||||
- `X-Request-Id`
|
||||
6. Validation:
|
||||
- all input validated server-side
|
||||
- reject unknown/invalid fields
|
||||
7. Logging:
|
||||
- route
|
||||
- requestId
|
||||
- actorId
|
||||
- latencyMs
|
||||
- outcome
|
||||
8. Timeouts and retries:
|
||||
- command writes must be retry-safe
|
||||
- use idempotency keys for command write routes
|
||||
9. Idempotency storage:
|
||||
- store in Cloud SQL table
|
||||
- key scope: `userId + route + idempotencyKey`
|
||||
- key retention: 24 hours
|
||||
- repeated key returns original response payload
|
||||
|
||||
## 3) Compatibility aliases (transition)
|
||||
1. `POST /uploadFile` -> `POST /core/upload-file`
|
||||
2. `POST /createSignedUrl` -> `POST /core/create-signed-url`
|
||||
3. `POST /invokeLLM` -> `POST /core/invoke-llm`
|
||||
|
||||
## 4) Rate-limit baseline (initial)
|
||||
1. `/core/invoke-llm`: 60 requests per minute per user
|
||||
2. `/core/upload-file`: 30 requests per minute per user
|
||||
3. `/core/create-signed-url`: 120 requests per minute per user
|
||||
4. `/commands/*`: 60 requests per minute per user
|
||||
|
||||
## 4.1 Timeout baseline (initial)
|
||||
1. `/core/invoke-llm`: 20-second hard timeout
|
||||
2. other `/core/*` routes: 10-second timeout
|
||||
3. `/commands/*` routes: 15-second timeout
|
||||
|
||||
## 5) Core routes
|
||||
|
||||
## 5.1 Upload file
|
||||
1. Method and route: `POST /core/upload-file`
|
||||
2. Auth: required
|
||||
3. Idempotency key: optional
|
||||
4. Request: multipart form data
|
||||
- `file` (required)
|
||||
- `category` (optional)
|
||||
- `visibility` (optional: `public` or `private`)
|
||||
5. Success `200`:
|
||||
```json
|
||||
{
|
||||
"fileUri": "gs://bucket/path/file.ext",
|
||||
"contentType": "application/pdf",
|
||||
"size": 12345,
|
||||
"bucket": "krow-uploads-private",
|
||||
"path": "documents/staff/..."
|
||||
}
|
||||
```
|
||||
6. Errors:
|
||||
- `UNAUTHENTICATED`
|
||||
- `INVALID_FILE_TYPE`
|
||||
- `FILE_TOO_LARGE`
|
||||
- `UPLOAD_FAILED`
|
||||
|
||||
## 5.2 Create signed URL
|
||||
1. Method and route: `POST /core/create-signed-url`
|
||||
2. Auth: required
|
||||
3. Idempotency key: optional
|
||||
4. Request:
|
||||
```json
|
||||
{
|
||||
"fileUri": "gs://bucket/path/file.ext",
|
||||
"expiresInSeconds": 300
|
||||
}
|
||||
```
|
||||
5. Success `200`:
|
||||
```json
|
||||
{
|
||||
"signedUrl": "https://...",
|
||||
"expiresAt": "2026-02-24T15:00:00Z"
|
||||
}
|
||||
```
|
||||
6. Errors:
|
||||
- `UNAUTHENTICATED`
|
||||
- `FORBIDDEN_FILE_ACCESS`
|
||||
- `INVALID_EXPIRES_IN`
|
||||
- `SIGN_URL_FAILED`
|
||||
|
||||
## 5.3 Invoke model
|
||||
1. Method and route: `POST /core/invoke-llm`
|
||||
2. Auth: required
|
||||
3. Idempotency key: optional
|
||||
4. Request:
|
||||
```json
|
||||
{
|
||||
"prompt": "...",
|
||||
"responseJsonSchema": {},
|
||||
"fileUrls": []
|
||||
}
|
||||
```
|
||||
5. Success `200`:
|
||||
```json
|
||||
{
|
||||
"result": {},
|
||||
"model": "provider/model-name",
|
||||
"latencyMs": 980
|
||||
}
|
||||
```
|
||||
6. Errors:
|
||||
- `UNAUTHENTICATED`
|
||||
- `INVALID_SCHEMA`
|
||||
- `MODEL_TIMEOUT`
|
||||
- `MODEL_FAILED`
|
||||
7. Provider default:
|
||||
- Vertex AI Gemini
|
||||
|
||||
## 5.4 Health check
|
||||
1. Method and route: `GET /healthz`
|
||||
2. Auth: optional (internal policy)
|
||||
3. Success `200`:
|
||||
```json
|
||||
{
|
||||
"ok": true,
|
||||
"service": "krow-backend",
|
||||
"version": "commit-or-tag"
|
||||
}
|
||||
```
|
||||
|
||||
## 5.5 Storage bucket policy defaults (dev)
|
||||
1. Public bucket: `krow-workforce-dev-public`
|
||||
2. Private bucket: `krow-workforce-dev-private`
|
||||
3. Private objects are never returned directly; only signed URLs are returned.
|
||||
|
||||
## 6) Command routes (wave 1)
|
||||
|
||||
## 6.1 Create order
|
||||
1. Method and route: `POST /commands/orders/create`
|
||||
2. Auth: required
|
||||
3. Idempotency key: required
|
||||
4. Purpose: create order + shifts + roles atomically
|
||||
5. Replaces:
|
||||
- `apps/web/src/features/operations/orders/components/CreateOrderDialog.tsx`
|
||||
- `apps/mobile/packages/features/client/home/lib/src/presentation/widgets/shift_order_form_sheet.dart`
|
||||
- `apps/mobile/packages/features/client/create_order/lib/src/data/repositories_impl/client_create_order_repository_impl.dart`
|
||||
|
||||
## 6.2 Update order
|
||||
1. Method and route: `POST /commands/orders/{orderId}/update`
|
||||
2. Auth: required
|
||||
3. Idempotency key: required
|
||||
4. Purpose: policy-safe multi-entity order update
|
||||
5. Replaces:
|
||||
- `apps/web/src/features/operations/orders/EditOrder.tsx`
|
||||
- `apps/mobile/packages/features/client/view_orders/lib/src/presentation/widgets/view_order_card.dart`
|
||||
|
||||
## 6.3 Cancel order
|
||||
1. Method and route: `POST /commands/orders/{orderId}/cancel`
|
||||
2. Auth: required
|
||||
3. Idempotency key: required
|
||||
4. Purpose: enforce cancellation policy and return explicit conflict code
|
||||
5. Replaces:
|
||||
- `apps/web/src/features/operations/orders/OrderDetail.tsx`
|
||||
|
||||
## 6.4 Change shift status
|
||||
1. Method and route: `POST /commands/shifts/{shiftId}/change-status`
|
||||
2. Auth: required
|
||||
3. Idempotency key: required
|
||||
4. Purpose: enforce state transitions server-side
|
||||
5. Replaces:
|
||||
- `apps/web/src/features/operations/tasks/TaskBoard.tsx`
|
||||
|
||||
## 6.5 Assign staff
|
||||
1. Method and route: `POST /commands/shifts/{shiftId}/assign-staff`
|
||||
2. Auth: required
|
||||
3. Idempotency key: required
|
||||
4. Purpose: assign + count update + conflict checks atomically
|
||||
5. Replaces:
|
||||
- `apps/web/src/features/operations/orders/components/AssignStaffModal.tsx`
|
||||
|
||||
## 6.6 Accept shift
|
||||
1. Method and route: `POST /commands/shifts/{shiftId}/accept`
|
||||
2. Auth: required
|
||||
3. Idempotency key: required
|
||||
4. Purpose: application + counters + rollback-safe behavior in one command
|
||||
5. Replaces:
|
||||
- `apps/mobile/packages/features/staff/shifts/lib/src/data/repositories_impl/shifts_repository_impl.dart`
|
||||
|
||||
## 7) Locked defaults before coding starts
|
||||
1. Idempotency keys are stored in Cloud SQL with 24-hour retention.
|
||||
2. Request validation library is `zod`.
|
||||
3. Validation schema location is `backend/<service>/src/contracts/`.
|
||||
4. Storage buckets are:
|
||||
- `krow-workforce-dev-public`
|
||||
- `krow-workforce-dev-private`
|
||||
5. Model provider is Vertex AI Gemini with a 20-second timeout for `/core/invoke-llm`.
|
||||
|
||||
## 8) Target response-time objectives (p95)
|
||||
1. `/healthz` under 200ms
|
||||
2. `/core/create-signed-url` under 500ms
|
||||
3. `/commands/*` under 1500ms
|
||||
4. `/core/invoke-llm` under 15000ms
|
||||
Reference in New Issue
Block a user