From 07dd6609d9efaa2a12a76cc44781badaefc9f08a Mon Sep 17 00:00:00 2001 From: zouantchaw <44246692+zouantchaw@users.noreply.github.com> Date: Tue, 24 Feb 2026 10:28:44 -0500 Subject: [PATCH] docs(m4): scope api docs to core endpoints only --- CHANGELOG.md | 1 + docs/MILESTONES/M4/planning/m4-api-catalog.md | 240 ++++++------------ .../M4/planning/m4-core-api-frontend-guide.md | 31 +-- 3 files changed, 81 insertions(+), 191 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d977913..28b961de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,3 +14,4 @@ | 2026-02-24 | 0.1.9 | Switched core API from mock behavior to real GCS upload/signed URLs and real Vertex model calls in dev deployment. | | 2026-02-24 | 0.1.10 | Hardened core APIs with signed URL ownership/expiry checks, object existence checks, and per-user LLM rate limiting. | | 2026-02-24 | 0.1.11 | Added frontend-ready core API guide and linked M4 API catalog to it as source of truth for consumption. | +| 2026-02-24 | 0.1.12 | Reduced M4 API docs to core-only scope and removed command-route references until command implementation is complete. | diff --git a/docs/MILESTONES/M4/planning/m4-api-catalog.md b/docs/MILESTONES/M4/planning/m4-api-catalog.md index 8a4141c9..25c5293c 100644 --- a/docs/MILESTONES/M4/planning/m4-api-catalog.md +++ b/docs/MILESTONES/M4/planning/m4-api-catalog.md @@ -1,30 +1,26 @@ -# M4 API Catalog (Implementation Contract) +# M4 API Catalog (Core Only) -Status: Active (Planning + Route Inventory) +Status: Active Date: 2026-02-24 Owner: Technical Lead Environment: dev ## Frontend source of truth -For frontend implementation, use: -- `docs/MILESTONES/M4/planning/m4-core-api-frontend-guide.md` - -Reason: -- this catalog is the broader M4 planning contract -- the frontend guide is the exact deployed request/response contract +Use this file and `docs/MILESTONES/M4/planning/m4-core-api-frontend-guide.md` for core endpoint consumption. ## 1) Scope and purpose -This file defines the backend endpoint contract for the M4 foundation build. +This catalog defines the currently implemented core backend contract for M4. ## 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: +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 ` +4. Standard error envelope: ```json { "code": "STRING_CODE", @@ -33,100 +29,71 @@ This file defines the backend endpoint contract for the M4 foundation build. "requestId": "optional-request-id" } ``` -4. Required request headers: -- `Authorization: Bearer ` -- `X-Request-Id: ` (optional but recommended) -5. Required response headers: +5. Response header: - `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` +## 3) Core routes -## 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 +## 3.1 Upload file 1. Method and route: `POST /core/upload-file` -2. Auth: required -3. Idempotency key: optional -4. Request: multipart form data +2. Request format: `multipart/form-data` +3. Fields: - `file` (required) +- `visibility` (`public` or `private`, optional) - `category` (optional) -- `visibility` (optional: `public` or `private`) -5. Success `200`: +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://bucket/path/file.ext", + "fileUri": "gs://krow-workforce-dev-private/uploads//...", "contentType": "application/pdf", "size": 12345, - "bucket": "krow-uploads-private", - "path": "documents/staff/..." + "bucket": "krow-workforce-dev-private", + "path": "uploads//...", + "requestId": "uuid" } ``` -6. Errors: +8. Errors: - `UNAUTHENTICATED` - `INVALID_FILE_TYPE` - `FILE_TOO_LARGE` -- `UPLOAD_FAILED` -## 5.2 Create signed URL +## 3.2 Create signed URL 1. Method and route: `POST /core/create-signed-url` -2. Auth: required -3. Idempotency key: optional -4. Request: +2. Request: ```json { - "fileUri": "gs://bucket/path/file.ext", + "fileUri": "gs://krow-workforce-dev-private/uploads//file.pdf", "expiresInSeconds": 300 } ``` -5. Success `200`: +3. Security checks: +- bucket must be allowed +- path must be owned by caller (`uploads//...`) +- object must exist +- `expiresInSeconds <= 900` +4. Success `200`: ```json { - "signedUrl": "https://...", - "expiresAt": "2026-02-24T15:00:00Z" + "signedUrl": "https://storage.googleapis.com/...", + "expiresAt": "2026-02-24T15:22:28.105Z", + "requestId": "uuid" } ``` -6. Errors: -- `UNAUTHENTICATED` -- `FORBIDDEN_FILE_ACCESS` -- `INVALID_EXPIRES_IN` -- `SIGN_URL_FAILED` +5. Errors: +- `VALIDATION_ERROR` +- `FORBIDDEN` +- `NOT_FOUND` -## 5.3 Invoke model +## 3.3 Invoke model 1. Method and route: `POST /core/invoke-llm` -2. Auth: required -3. Idempotency key: optional -4. Request: +2. Request: ```json { "prompt": "...", @@ -134,103 +101,48 @@ This file defines the backend endpoint contract for the M4 foundation build. "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": "provider/model-name", - "latencyMs": 980 + "model": "gemini-2.0-flash-001", + "latencyMs": 367, + "requestId": "uuid" } ``` 6. Errors: - `UNAUTHENTICATED` -- `INVALID_SCHEMA` +- `VALIDATION_ERROR` - `MODEL_TIMEOUT` - `MODEL_FAILED` -7. Provider default: -- Vertex AI Gemini +- `RATE_LIMITED` -## 5.4 Health check -1. Method and route: `GET /healthz` -2. Auth: optional (internal policy) -3. Success `200`: +## 3.4 Health +1. Method and route: `GET /health` +2. Success `200`: ```json { "ok": true, - "service": "krow-backend", - "version": "commit-or-tag" + "service": "krow-core-api", + "version": "dev", + "requestId": "uuid" } ``` -## 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//src/contracts/`. -4. Storage buckets are: +## 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` -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 +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. diff --git a/docs/MILESTONES/M4/planning/m4-core-api-frontend-guide.md b/docs/MILESTONES/M4/planning/m4-core-api-frontend-guide.md index 7525dfdb..98e41492 100644 --- a/docs/MILESTONES/M4/planning/m4-core-api-frontend-guide.md +++ b/docs/MILESTONES/M4/planning/m4-core-api-frontend-guide.md @@ -6,7 +6,6 @@ Audience: Web and mobile frontend developers ## 1) Base URLs (dev) 1. Core API: `https://krow-core-api-e3g6witsvq-uc.a.run.app` -2. Command API: `https://krow-command-api-e3g6witsvq-uc.a.run.app` ## 2) Auth requirements 1. Send Firebase ID token on protected routes: @@ -119,31 +118,9 @@ Authorization: Bearer } ``` -## 5) Command API endpoint currently ready for consumption +## 5) Frontend fetch examples (web) -## 5.1 Create order command (scaffold) -1. Route: `POST /commands/orders/create` -2. Required headers: -- `Authorization: Bearer ` -- `Idempotency-Key: ` -3. Current behavior: -- validates auth + idempotency -- returns accepted scaffold response -- duplicate key returns the original response payload -4. Success `200` example: -```json -{ - "accepted": true, - "route": "/commands/orders/create", - "commandId": "/commands/orders/create:173...", - "idempotencyKey": "client-key-123", - "requestId": "uuid" -} -``` - -## 6) Frontend fetch examples (web) - -## 6.1 Signed URL request +## 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', { @@ -160,7 +137,7 @@ const res = await fetch('https://krow-core-api-e3g6witsvq-uc.a.run.app/core/crea const data = await res.json(); ``` -## 6.2 Model request +## 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', { @@ -181,7 +158,7 @@ const res = await fetch('https://krow-core-api-e3g6witsvq-uc.a.run.app/core/invo const data = await res.json(); ``` -## 7) Notes for frontend team +## 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.