PMFlow/docs/architecture/API_CONTRACTS.md

670 lines
66 KiB
Markdown

# API CONTRACTS
## PMFlow OS
### POST `/pmflow-os/projects/{project_id}/extractions`
- Purpose: turn a project thread message into an open ChangeSet with pending_review ChangeItems.
- Auth: project member, `OWNER`, or `REVIEWER`; tenant comes from JWT.
- Request body:
- `thread_id: string`
- `message_id: string`
- `scope?: string`, default `latest_message`
- `summary?: string`, `headline?: string`
- `routing_meta?: object`, audit/routing metadata only
- `llm_call_id?: string`
- `proposed_items?: PMFlowProposedChangeItem[]`
- Behavior:
- If `proposed_items` is present and non-empty, the endpoint preserves the existing explicit ChangeSet draft path.
- If `proposed_items` is omitted or empty, the backend runs real structured AI extraction through `llm_service.generate_structured_object` using the dedicated `change_review` protected scene, `pmflow_change_review_extraction_v1` Function Runtime Profile, and `PROMPT_PMFLOW_CHANGE_REVIEW_EXTRACTION` prompt asset, then normalizes requirements/tasks/risks/decisions into ChangeItems.
- Success creates one `pmflow_change_sets` row with `status=open`, pending_review `pmflow_change_items`, and a `pmflow_evidence_links` row pointing to the source thread message.
- Failure is fail-closed: no ChangeSet or ChangeItem is created. The API returns product-semantic errors such as `ai_extraction_failed` or `extraction_evidence_insufficient`.
- This endpoint never commits Project Memory. ChangeReview remains responsible for accept/edit/defer and ChangeSet commit.
- Response data: `PMFlowChangeSetDetailResponse`
- `changeSet`: serialized open ChangeSet with `routingMeta.aiExtraction` when AI extraction was used.
- `items[]`: pending_review ChangeItems.
- `evidence[]`: source thread message evidence.
- `approvals[]`: initially empty unless later review actions were recorded.
- Failure semantics:
- `404 message_not_found`
- `409 extraction_evidence_insufficient`
- `502 ai_extraction_failed`
### POST `/pmflow-os/change-sets/{change_set_id}/commit`
- Purpose: commit reviewed ChangeSet items into the official project truth layer.
- Auth: project member, `OWNER`, or `REVIEWER`; tenant comes from JWT.
- Request body:
- `item_ids?: string[]` optional; default commits all `accepted / edited` items.
- `mode: "atomic"` required for C2 V1.
- `note?: string` audit note only.
- Behavior:
- Processes only `accepted / edited` ChangeItems.
- C2 V1 is atomic: any eligible item write failure rolls back the whole commit.
- Required C2 objectType support: `Requirement / Decision / Task / Risk`.
- Optional C2 objectType support: `Release`.
- Unsupported for C2 V1: `ModelDecision / MetricSignal`, which must fail with `422 unsupported_object_type`.
- `Requirement / Decision / Risk / Release` write to `truth_nodes`.
- `Task` writes `truth_nodes(node_type='task')` plus `tasks.truth_node_id`.
- High-risk items require item-level approval with `decision in ('approved','edited')` and `after_status in ('accepted','edited')`.
- Response data:
- `changeSet`: `{ id, status, rawStatus, committedAt, writebackAt, committedById, committedBy, committedItemIds, skippedItemIds, failedItemIds }`
- `committedItems[]`: `{ changeItemId, objectType, targetType, targetId, status, evidenceIds, semanticFactStatus, committedAt, committedById, committedBy }`
- `skippedItems[]`: `{ changeItemId, objectType, status, reason }`
- `failedItems[]`: `{ changeItemId, objectType, code, message }`
- `projectTruthState?`: optional backend projection after commit.
- Failure semantics:
- `404 changeset_not_found`
- `409 changeset_not_reviewed`
- `409 changeset_already_committed`
- `409 no_accepted_items`
- `409 high_risk_approval_required`
- `422 invalid_commit_item_ids`
- `422 unsupported_object_type`
- `422 invalid_change_payload`
- `500 commit_failed`
## Personal Space
### Saved Ideas
- Purpose: keep pre-project idea seeds visible in `/space` without polluting Project Memory or PMFlow OS truth objects.
- Boundary:
- `saved_ideas` stores raw input, source, summary, readiness, missing questions, status, enrichment metadata, and optional Project Launch draft linkage.
- Enrichment metadata is explicit: `enrichment_status="ai_enriched"` means the AI structured completeness pass succeeded; `ai_failed` means AI did not complete and the row is showing deterministic baseline fields; `baseline` means no AI enrichment is present.
- Saving an idea never creates a project, task, ChangeSet, Project Memory entry, execution event, release signal, or capacity item.
- Promoting an idea only generates a Project Launch draft and returns the user to human review. Project creation still requires `POST /pmflow-launch/drafts/{draft_id}/confirm`.
### GET `/space/saved-ideas`
- Auth: logged-in user; tenant comes from JWT.
- Query:
- `limit?: number`, default `20`, max `100`
- `include_archived?: boolean`, default `false`
- Response data: `{ items: SavedIdeaRead[] }`. `SavedIdeaRead` includes `id/title/raw_text/summary/source_type/source_ref/input_mode/attachments/status/status_label/ai_summary/missing_questions/readiness_score/enrichment_status/enrichment_payload/enrichment_error/enriched_at/launch_draft_id/promoted_project_id/created_at/updated_at/href`.
### POST `/space/saved-ideas`
- Auth: logged-in user; tenant comes from JWT.
- Request body:
- `raw_text: string`
- `title?: string`
- `summary?: string`
- `source_type?: "project_launch" | "ai_concierge" | "logout_guard" | "feishu" | "upload" | "manual"`
- `source_ref?: string`
- `input_mode?: "idea" | "import" | "template"`
- `attachments?: SavedIdeaAttachment[]`
- Behavior: writes one `saved_ideas` row for the current user and workspace. The service first tries `space_saved_idea_enrichment_v1` structured AI enrichment for `summary / target_user / problem / value_hypothesis / validation_signal / constraints / missing_questions / readiness_score / status / confidence`; if AI is unavailable or parsing fails, it stores `enrichment_status="ai_failed"` with a deterministic `system_baseline` payload instead of pretending AI succeeded.
- Response data: `SavedIdeaRead`.
### PATCH `/space/saved-ideas/{idea_id}`
- Purpose: update title/raw text/summary/status for the current user's own idea. When `raw_text` changes, the same structured enrichment attempt runs again and refreshes readiness, missing questions, status and enrichment metadata.
- Response data: `SavedIdeaRead`.
### POST `/space/saved-ideas/{idea_id}/archive`
- Purpose: remove a saved idea from active Space lists while preserving audit/history.
- Response data: `SavedIdeaRead` with `status="archived"`.
### POST `/space/saved-ideas/{idea_id}/promote`
- Purpose: convert a saved idea into a reviewable Project Launch draft.
- Behavior:
- Calls the same structured draft generation path as `POST /pmflow-launch/drafts`.
- Updates `saved_ideas.status="promoted"` and stores `launch_draft_id`.
- Does not confirm the draft or create a project.
- Response data: `{ idea: SavedIdeaRead, draft: PMFlowLaunchDraftResponse, redirect_url: "/project-launch?state=human_review" }`.
- `POST /pmflow-launch/drafts` rejects low-information launch input before calling the LLM with `422` and message prefix `input_needs_more_info`, so pure greetings or inputs without enough product/user/problem evidence stay in the supplement-input path instead of producing an empty draft.
- Project Launch taskized generation:
- `POST /pmflow-launch/draft-jobs` creates a `pmflow_launch_draft_jobs` row with `job_id / revision_id / status=queued`, supersedes active jobs for the same user, and starts the structured generation worker. Response returns job status only; clients must poll.
- `GET /pmflow-launch/draft-jobs/{job_id}` returns `queued | running | superseded | completed | failed | cancelled`; `completed` includes the generated `PMFlowLaunchDraftResponse` and `changed_fields`.
- `POST /pmflow-launch/draft-jobs/{job_id}/cancel` marks a queued/running job as cancelled. If the provider call already completed, workers must ignore non-running jobs before persisting a draft.
- `POST /pmflow-launch/drafts/{draft_id}/revise` creates a new `merge_supplement` job using the parent draft plus the user's supplement; it does not confirm or write a project.
- Project Launch draft generation uses the managed `PROMPT_PMFLOW_LAUNCH_DRAFT` active release when available. The expected structured draft is a monetizable MVP launch plan, targeting 7-10 requirements, 10-16 tasks, 4-7 risks and 3-6 decisions; count or business-loop gaps are surfaced in draft warnings instead of silently accepting a thin feature-demo plan.
## AI Readiness
### GET `/ai-readiness/project-launch`
- Purpose: derive the customer-facing Project Launch AI readiness state from backend runtime facts, not from frontend local state.
- Auth: logged-in user; tenant comes from JWT.
- Behavior:
- Reads platform runtime configuration from the platform runtime tenant, including `integrations(type='llm_engine')` and published `integrations(type='ai_capability_slots').active.slots`.
- Verifies the Project Launch scene contract, the published `structured` slot, the bound runtime connection state, structured output / JSON Schema capability evidence, and the latest tenant-scoped `ai_call_logs(function_name='pmflow_launch_draft_v1')`.
- Returns customer-safe copy and actions by default. Supplier, slot, probe, recent call, raw status and platform repair actions are exposed only inside `platform / platform_diagnostic` for platform operations views.
- `can_generate_project_draft=true` only when the required runtime, structured slot, capability evidence and latest draft smoke/call evidence are all healthy. Missing active structured slot, unavailable runtime or missing capability proof keeps Project Launch draft generation blocked.
- Response data: `AIReadiness`
- `scene: "project_launch"`
- `status: "ready" | "blocked" | "recovering" | "unknown"`
- `customer_state`, `title`, `summary`, `short_label`, `updated_at_label`
- `can_generate_project_draft: boolean`
- `blockers[]`, `customer_actions[]`, `workspace_owner_actions[]`
- `platform.checks`, `platform.blockers`, `platform.latest_call`, `platform.last_checked_at`
- `platform_diagnostic`
### POST `/ai-readiness/project-launch/check`
- Purpose: run a protected Project Launch smoke check without creating a draft or project.
- Auth: platform AI operations capability, via `platform.ai_routing.manage`.
- Behavior:
- Calls the same `pmflow_launch_draft_v1` structured generation path used by real Project Launch, with `routing_meta.tier_alias="structured"` and `source="system_smoke"`.
- Normalizes and validates the generated draft payload, but does not write `pmflow_launch_drafts`, `projects`, Project Memory, tasks, ChangeSet or semantic facts.
- Updates the returned readiness payload from the fresh smoke result and latest `ai_call_logs` evidence.
- Response data: same as `GET /ai-readiness/project-launch`, with `platform.last_smoke_error` when the smoke check fails.
## Platform AI Operations
### Provider code boundary
- Runtime provider identity must come from configured `integrations(type='llm_engine').config.engines[*].provider`; UI, API, audit and routing code may display product labels, but must preserve the raw configured provider code separately.
- DashScope/Qwen runtime provider code is `dashscope`. The old internal provider code `glm` has been removed and must not be reintroduced as an alias, fallback, display value or audit value. `glm-5` may still appear only as a model name when a configured provider points to that model.
- Historical `integrations.config` and `ai_call_logs` rows are migrated by `backend/alembic/versions/20260429_rename_glm_provider_to_dashscope.py`; new writes must persist `dashscope` for DashScope/Qwen suppliers and keep `provider_label / runtime_label / engine_name` as display-only fields.
### Prompt operations center
- Scope: `/prompt-assets*` is platform-only and requires `platform.ai_routing.manage`. It is not a customer workspace Prompt strategy surface and must not be exposed in Space, Project Launch or project-level AI copilot.
- Main read endpoints:
- `GET /prompt-assets`
- `GET /prompt-assets/{prompt_key}`
- `GET /prompt-assets/{prompt_key}/quality-summary`
- `GET /prompt-assets/{prompt_key}/golden-cases`
- `GET /prompt-assets/{prompt_key}/releases/{release_id}/checks`
- `GET /prompt-assets/{prompt_key}/releases/{release_id}/replay-runs`
- `GET /prompt-assets/{prompt_key}/replay-runs/{run_id}`
- `GET /prompt-assets/{prompt_key}/releases/{release_id}/watch-summary`
- Main write endpoints:
- `POST /prompt-assets/{prompt_key}/releases`
- `POST /prompt-assets/{prompt_key}/releases/{release_id}/activate`
- `POST /prompt-assets/{prompt_key}/releases/{release_id}/rollback`
- `POST /prompt-assets/{prompt_key}/releases/{release_id}/checks/run`
- `POST /prompt-assets/{prompt_key}/golden-cases`
- `POST /prompt-assets/{prompt_key}/golden-cases/from-call/{ai_call_log_id}`
- `POST /prompt-assets/{prompt_key}/releases/{release_id}/replay-runs`
- Golden case request supports `case_type`, `priority`, `scenario_key` and `tags`. `priority=blocking` failures block guarded release activation; `important/observation` failures are warning-only and require human review.
- Replay request supports `mode=contract_smoke|compare|llm_compare`, `include_llm`, `judge_enabled`, `case_status` and `max_cases`. `include_llm=true` forces `llm_compare` and caps max cases. LLM replay must call `llm_service` with explicit `routing_meta` (`prompt_key`, `prompt_release_id`, `prompt_source`, `tier_alias`, `scene_key`, `run_id`) and must not hardcode provider/model.
- Replay case results persist deterministic contract status plus optional `baseline_ai_call_log_id`, `candidate_ai_call_log_id`, `judge_score`, `judge_label`, `judge_reason`, `risk_flags_json`, latency and cost estimate. Judge rubric comes from `backend/app/config/ai_prompt_eval_rubrics.py`.
- Release watch summary returns 24h/7d quality windows, `structured_invalid_24h`, `fallback_rate_24h`, recent failures and `recommendation=healthy|watch|needs_review|rollback_suggested`. It is advisory only and must not auto-rollback.
- `inline_discovered` and `needs_migration` assets are read-only inventory until their runtime call sites are migrated to `PromptRuntimeService` / `PromptReleaseService`; UI and API copy must not imply these prompts are already configurable in production runtime.
## Signal & SSTC
All `/projects/{project_id}/...` SSTC and model-capability context read endpoints must enforce project visibility before returning business data: project member, `OWNER`, or `REVIEWER`. This includes overview, stages, risks, governance, chain, charters, signals, tasks, releases, reviews, truth nodes, and experiments.
## Workspace / Membership
Product language uses workspace/space; the compatibility table name remains `tenants`.
### POST `/auth/phone/send-code`
- Purpose: send an SMS-style verification code for phone login, self-registration, or invitation binding.
- Request body:
- `phone_country_code: string`, default `+86`
- `phone_number: string`
- `purpose?: "login" | "bind_invitation" | "reset"`
- `invite_token?: string`
- Behavior:
- Phone is normalized to global `users.phone_e164`; duplicate accounts for the same verified phone are not allowed.
- Development environments may return `dev_code` to keep local verification testable. Production must not expose the code to users.
### POST `/auth/phone/verify`
- Purpose: verify the phone code, log in the account, and create the account if it does not exist.
- Request body:
- `phone_country_code: string`, default `+86`
- `phone_number: string`
- `code: string`
- `purpose?: "login" | "bind_invitation" | "reset"`
- `name?: string`
- `invite_token?: string`
- Behavior:
- If the phone has no existing account, the backend creates a global user account plus a personal workspace.
- If `invite_token` is present and bound to the same verified phone, the backend accepts the team invitation and switches the active workspace to that team.
- Existing users keep their personal workspace; joining or leaving a team never transfers ownership of the account itself.
- Response data: regular login payload with `access_token`, `token_type`, `user.workspace_context`, and `user.platform_context`.
- `workspace_context` describes the customer's active personal/team workspace membership.
- `platform_context` is PMFlow-internal access only: `{ has_platform_access, roles, capabilities }`. A customer workspace `OWNER` does not imply any platform capability.
### POST `/auth/register`
- Purpose: create a global account plus that user's personal workspace.
- Behavior: compatibility username/password registration path. The preferred user-facing path is phone verification via `/auth/phone/verify`. The new user becomes `OWNER` of the personal workspace; this workspace is not removed when the user later leaves team workspaces.
### Platform membership
- Table/model: `platform_memberships`.
- Purpose: grant PMFlow-internal operators access to the platform AI operations control plane.
- Roles: `PLATFORM_OWNER`, `PLATFORM_ADMIN`, `AI_OPS`, `SUPPORT`, `BILLING_OPS`, `SECURITY_AUDITOR`.
- Shape: one user may hold multiple platform roles; uniqueness is `user_id + role`, and `platform_context.capabilities` is the union of active roles.
- Boundary: platform roles are not workspace roles. They never come from `tenant_memberships.role` or legacy `users.role`.
- Bootstrap: `PMFLOW_PLATFORM_ADMIN_USERS` and `PMFLOW_AI_OPS_USERS` may grant temporary env-based platform access before DB grants exist.
### GET `/workspaces/me`
- Purpose: list all active workspaces for the current account.
- Response data: `{ active_workspace, workspaces[] }`; each workspace includes `workspace_type` (`personal` or `team`) and membership `role`.
### POST `/workspaces/teams`
- Purpose: create a small-team workspace from a solo account.
- Behavior: current user becomes `OWNER` and the new team workspace becomes active.
### POST `/workspaces/invitations`
- Purpose: invite a member by phone into the current team workspace.
- Auth: active workspace role must be `OWNER` or `REVIEWER`; only `OWNER` may invite `REVIEWER`.
- Request body:
- `phone_country_code: string`, default `+86`
- `phone_number: string`
- `role: "OWNER" | "BUILDER" | "OPERATOR" | "REVIEWER"`
- `email?: string`, optional compatibility/contact field
- `project_ids?: number[]`
- Behavior: invite may include initial `project_ids` to create project membership after acceptance. Re-inviting the same pending `tenant_id + phone_e164` refreshes the token and role instead of creating duplicate pending invitations.
### POST `/workspaces/invitations/accept`
- Purpose: accept a team invitation.
- Behavior: legacy token acceptance remains supported. Phone-bound invitations require the accepting account's verified phone to match the invitation phone. Existing users keep their personal workspace; new users created from an invitation also receive a personal workspace before joining the team.
### POST `/workspaces/{tenant_id}/leave`
- Purpose: leave a team workspace.
- Boundary: personal workspaces cannot be left; the last `OWNER` of a team must transfer ownership before leaving.
### GET `/projects/`
- Purpose: list project rows or summary rows for project index and personal Space aggregation.
- Auth: logged-in user; tenant comes from JWT.
- Query:
- `summary?: 0 | 1`
- `status?: string`
- `assigned_role?: string`
- Visibility:
- `OWNER` and `REVIEWER` receive all non-filtered projects in the current tenant.
- Other roles only receive projects they own (`projects.owner_id`) or projects linked through `user_projects`.
- The same visibility rule applies to `GET /projects/{project_id}`; inaccessible projects return `404` rather than leaking existence.
- Response data:
- `summary=1`: `{ projects: ProjectSummary[] }`
- otherwise: `Project[]`
### POST `/projects/`
- Purpose: create a project shell/title and membership record. This endpoint does not generate requirements, tasks, risks, or Project Memory.
- Auth: `OWNER` or `REVIEWER`; tenant comes from the active workspace membership.
- Request body:
- `title: string`
- `description?: string`
- `status?: string`, default `active`
- `domain_id?: number`
- `assigned_role?: string`
- `member_user_ids?: number[]`
- Behavior:
- Project titles are compacted and must be unique per `tenant_id` across non-`inactive` projects.
- A duplicate title returns `409 project title already exists`.
- The endpoint never calls AI and never writes `truth_nodes`, `tasks`, ChangeSets, or semantic facts.
- Response data: serialized project row `{ id, title, description, status, domain_id, assigned_role, signal_count, created_at, updated_at }`
### GET `/ai-pmo/work-items`
- Purpose: compatibility read model for Space legacy manual gates and draft confirmations.
- Auth: logged-in user; tenant comes from JWT.
- Visibility:
- `OWNER` and `REVIEWER` may inspect tenant-level legacy work items.
- Other roles only receive work items owned by themselves (`work_items.owner_user_id`) or bound to a project they can access through `user_projects`.
- Unbound legacy work items without `owner_user_id` are hidden from non-manager users.
- Boundary: this endpoint is not a team inbox product surface; Space consumes it only as a compatibility adapter.
### GET `/projects/{project_id}/signals`
- Response data:
- `items[]` includes planner fields:
- `planner_status: "pending" | "completed" | "failed"`
- `planner_analyzed_at: string | null`
- `planner_assignee_user_id: string | null`
- `strategic_analysis: object`
## Model Routing
### GET `/model-routing/functions/{function_name}`
- Purpose: query function-level routing diagnostics for hidden Route Lab. This is not shown in member, team-lead, or regular AI Ops navigation.
- Auth: PMFlow platform capability `platform.ai_routing.manage`.
- Response data:
- `function_name`
- `tier`
- `source: "default" | "override"`
### PUT `/model-routing/functions/{function_name}`
- Auth: PMFlow platform capability `platform.ai_routing.manage`.
- UI exposure: hidden from the current Route Lab UI. Future exposure must add reason, TTL, dry-run, audit review, and auto-expiry controls before operators can edit function-level overrides.
- Request body:
- `tier_alias: "low" | "high"`
- Side effects:
- persist override into `integrations(type='model_routing')`
- append audit to `operation_logs`
- Boundary:
- The API only accepts slot aliases, not provider/model/base URL strings; arbitrary model-name editing remains owned by provider connection setup.
- Protected high-requirement functions such as requirement planning and assignee matching cannot be downgraded to `low`; the service returns a validation error instead of saving an override that would violate scene-slot capability requirements.
## AI Routing & Providers
- UI placement: split by control plane.
- Customer workspace UI under `/ai-config` only shows AI usage settings: readiness, team policy, input sources, knowledge, MCP template use, and quotas.
- PMFlow platform AI operations UI under `/platform/*` owns provider health, global routing, route/context governance, semantic facts, prompt/eval diagnostics, and cross-tenant operations metrics.
- Project pages consume AI state only through AI 副驾 status and read-only inherited policy summaries.
- Platform AI Ops UI is now menu-split under the AI runtime layer:
- `/platform`: platform runtime overview, affected scenes, diagnostics, and next actions; old `/platform/ai-routing-console` redirects here for compatibility.
- `/platform/ai-routing-providers`: provider/runtime connection, independent enable/disable state, masked credential state, connection test state, normalized capability matrix, executable capability probe evidence, and one global default fallback runtime.
- `/platform/ai-routing-slots`: PMFlow capability slots; fast/deep are configurable now, structured is consumed by `llm_service.generate_structured_object` once published active, long-context is consumed only by explicit long-document/long-thread/cross-project-context calls, and tool is consumed only by explicit tool-calling or MCP execution-planning calls. External-query/private/multimodal remain reserved candidate slots. A slot consumes enabled supplier runtimes through primary runtime + fallback chain, publish validation checks provider capabilities and probe evidence, and business/runtime code consumes published active slots instead of hardcoded model names.
- `/platform/ai-routing-scenes`: business-scene to capability-slot matrix; advanced function details are hidden by default.
- `/platform/ai-routing-costs`: call cost, upgrade cost, quota/cache gaps, and provider usage integration gaps.
- `/platform/ai-routing-failures`: sanitized risk diagnostics and recent failure samples.
- `/platform/ai-call-traces`: AI call trace ledger for provider/model invocations, redacted prompt/response previews, routing metadata, slot evidence, latency/token/cost estimates, and trace detail drill-down.
- `/platform/prompt-assets`: Prompt asset inventory and operation view; lists registry prompts, default seed prompt availability, active/draft prompt releases, call sites, risk level, release policy, and inline prompt migration gaps.
- These platform menus consume `GET /ai-routing/admin-console`, `GET/PUT/POST /ai-routing/capability-slots*`, `POST /ai-routing/diagnostics/run`, `GET /ai-call-traces*`, and `GET/POST /prompt-assets*`; provider writes are owned by `/ai-steward/llm-engines`. Runtime route mutation remains restricted to platform AI Ops. Provider rows expose `available_actions` so the UI only renders operations that are valid for the saved connection state.
### LLM Engine Provider Connections
- Auth: PMFlow platform capability `platform.ai_routing.manage`.
- Storage: `integrations(type='llm_engine')` under the platform AI Ops runtime scope. The database table remains tenant-scoped for compatibility, but platform supplier/runtime config is pinned to the internal platform runtime scope (`PMFLOW_PLATFORM_RUNTIME_TENANT_ID`, default `1`) and is not a customer workspace setting. Existing `active_engine_id` is treated as legacy compatibility for `default_fallback_engine_id`.
- Runtime semantics:
- Multiple supplier connections may be enabled at the same time.
- `enabled=false` connections are retained for editing/testing history but skipped by runtime routing, low/high tier selection, and low-to-high escalation.
- `default_fallback_engine_id` is the global fallback when no function rule, capability slot, or tier route resolves. It is not a supplier enable switch.
- Connection tests update `connection_status`, `last_test_status`, `last_test_at`, and `last_error_summary`; they do not by themselves make a connection the default fallback.
- Each connection stores a normalized `capabilities` object inside `integrations(type='llm_engine').config.engines[*]`; legacy rows without capabilities are normalized at read time using provider/model defaults so UI and validation do not crash.
- Capability fields currently include `structured_output`, `json_schema`, `tool_calling`, `long_context`, `max_context_tokens`, `vision`, `audio`, `local_only`, `private_deployment`, `external_search_ready`, `avg_latency_ms`, `cost_tier`, and `reliability_tier`.
- Capability audit fields include the normalized nested `capability_probe` object plus backward-compatible flat fields `capability_source`, `capability_probe_status`, `capability_last_verified_at`, `capability_blocking_reasons`, and `capability_evidence`. `capability_probe` is stored inside the engine config and has `{status, last_verified_at, verified_capabilities, evidence, source}` where `source` is `inferred | manual | probed`; each evidence row records `status/method/evidence_strength/request_summary/response_summary/failure_code/latency_ms` and may carry operator evidence metadata (`source_type/source_url/source_note/verified_by/verified_at/expires_at`). Read responses also expose `declared_capabilities` and backend-derived `effective_capabilities`; legacy rows without `capability_probe` normalize to `status=untested` and do not crash old runtime reads.
- Endpoints:
- `GET /ai-steward/llm-engines`: list masked connections, settings, normalized `capabilities`, `available_actions`, and `default_fallback_engine_id`.
- `POST /ai-steward/llm-engines`: create an enabled supplier connection; accepts optional `capabilities`; optional `set_active` remains accepted for backward compatibility and now means “set as default fallback”.
- `PUT /ai-steward/llm-engines/{engine_id}`: update name/provider/model/base URL/key/capabilities and reset connection test state when runtime fields change.
- `PUT /ai-steward/llm-engines/{engine_id}/enabled`: enable or disable a saved connection.
- `POST /ai-steward/llm-engines/{engine_id}/default-fallback`: set the global fallback runtime; only enabled connections with `connection_status=available` can be selected.
- `POST /ai-steward/llm-engines/{engine_id}/test`: test one enabled saved connection using its saved provider/model/base URL/key.
- `POST /ai-steward/llm-engines/{engine_id}/capability-probe`: only runs for enabled connections that already passed connection test; accepts `mode=quick|full|advanced`, optional `targets[]`, and `allow_high_cost`. The default full plan runs basic chat, a low-cost model self-report reference call, plus the declared capability scope: structured JSON parse, independent JSON Schema parameter, tool-calling, streaming observability, and long-context declared-window gates only when long-context is declared or explicitly targeted. Model self-report is stored as `status=declared_only/evidence_strength=model_self_report`; it is visible in provider evidence and AI call audit because it is a real upstream call, but it never counts as protected slot verified evidence or aggregate probe pass/partial/fail status. Provider UI must render model self-report only as a separate reference card, not as a probe-detail capability pill; the long-context probe-detail row is the single user-facing capability item for context window status. Streaming is a transport/runtime trait for `stream=true` incremental responses, not a declared model capability matrix item; provider UI may show it in probe detail and a separate runtime traits area, but must not mix it into the verified model capability list. Declared capabilities must be displayed without arbitrary truncation; if any declared capability lacks live/provider-contract/operator evidence, aggregate status cannot be `passed` and must stay `partial` or `untested`. Undeclared optional capabilities do not produce blocking failures or downgrade the aggregate probe status. Long-context gates read the configured `max_context_tokens`; provider/model specs that PMFlow knows from official contracts, such as DashScope `qwen-plus`, are stored as `status=verified/evidence_strength=provider_contract` without sending high-cost long text to the endpoint. Unknown provider declared windows remain `declared_only` and do not count as verified slot evidence. A verified higher long-context window covers lower threshold rows such as 32K/128K for aggregate status and UI display. Results write back into `capabilities.capability_probe`, `capability_evidence`, and derived `effective_capabilities` without adding a migration.
- `POST /ai-steward/llm-engines/{engine_id}/capability-attest`: platform operator manually confirms a high-cost capability evidence item, currently scoped to `capability_key=long_context`. The request records `tested_tokens`, evidence source type, optional source URL/note, and optional expiry. It writes `evidence_strength=operator_verified/method=operator_attestation` into `capability_probe.evidence` and compatibility `capability_evidence`, updates derived `effective_capabilities`, and can satisfy long-context protected slot publishing until the evidence expires. This endpoint does not call the upstream provider endpoint and therefore does not create an `ai_call_logs` row; AI call audit continues to show only real upstream endpoint interactions.
- `DELETE /ai-steward/llm-engines/{engine_id}`: delete a saved connection only after it is disabled and is not the default fallback.
### Capability Slots
- Auth: PMFlow platform capability `platform.ai_routing.manage`.
- Storage: `integrations(type='ai_capability_slots')` under the same platform runtime scope as provider connections (`PMFLOW_PLATFORM_RUNTIME_TENANT_ID`, default `1`).
- Runtime semantics:
- Provider connections are the runtime inventory; capability slots bind enabled provider connections into PMFlow ability abstractions.
- Slot config is release-controlled: edits write `draft.slots`; only `publish` promotes the draft into `active.slots`. Runtime code reads active slots only and never consumes unpublished draft.
- `low/high` tier resolution, control-plane rule resolution, function override resolution, and low-to-high escalation first consume active slot binding when present.
- `backend/.control_plane/pmflow-routing-rules.json` remains the baseline/default rule pack and cold-start fallback, not the only live binding source once a slot is configured.
- Disabled provider connections cannot be saved as primary or fallback slot runtimes.
- Draft validation applies `SLOT_REQUIREMENTS` from `backend/app/services/ai_routing_governance_service.py`: high forbids local-only/Ollama, structured requires `structured_output + json_schema`, long-context requires `long_context` and `max_context_tokens >= 32768`, and tool requires `tool_calling`. Structured/tool/long-context are configurable draft slots; external/private/multimodal remain non-configurable candidate slots and cannot be saved as runtime bindings.
- Provider capability probing treats `long_context` as the provider's configured `max_context_tokens` window, not as a hard-coded 32K test. A provider configured with 16K verifies that declared 16K window when the provider/model has a known official contract covering it, but it still cannot satisfy the long-context slot publish gate unless `max_context_tokens >= 32768`; `long_context_32k/128k` are explicit threshold probes and slot-publish gates only. Editing provider/model/base URL/API key or declared capabilities invalidates previous capability probe evidence and resets the engine to `capability_probe_status=untested`; name-only edits preserve evidence.
- For structured/tool/long-context slots, validation distinguishes missing capability, missing probe evidence, and stale probe evidence: `*_capability_missing` means the normalized declared capability is false or below threshold; `*_capability_evidence_missing` means labels claim support but no verified `capability_probe.evidence`/compatibility `capability_evidence` with live, provider-contract, or operator-verified evidence strength exists; `*_capability_evidence_stale` means verified evidence is older than `AI_CAPABILITY_PROBE_EVIDENCE_TTL_DAYS` (default 90) or its explicit `expires_at` has passed. `declared_only/manual/inferred/model_self_report` evidence never satisfies protected slot publishing. `low` can publish without probe evidence but returns a warning.
- Runtime effect is release-controlled: `generate_structured_object` only consumes the published active `structured` slot. Draft structured bindings are visible for validation/review but do not affect runtime until publish succeeds. When an active structured slot exists, runtime tries the structured primary first and then its fallback chain, skipping disabled/deleted runtimes and runtimes that do not satisfy `structured_output + json_schema`. If all configured structured slot runtimes fail after invocation, the call fails explicitly instead of falling back to templates or an unrelated low/high route. If the published active structured slot is configured but has no usable runtime before invocation, protected structured functions fail explicitly with a structured-slot runtime error; non-protected structured calls may fall back to the existing high/low/default path only with degraded routing metadata.
- Long-context and tool runtime effects are narrower than structured: runtime consumes active `long_context` only when `routing_meta` explicitly requests `slot_key/planned_tier/tier_alias=long_context`, and consumes active `tool` only when `routing_meta` explicitly requests `tool` or uses MCP execution-planning modes `execution_mode=local_planner|local_worker`. These slots skip disabled/deleted runtimes and runtimes missing `long_context + max_context_tokens >= 32768` or `tool_calling`. If the requested active slot is configured but has no usable runtime, the call fails with `{slot}_slot_runtime_unavailable`; if no active slot is published, the existing low/high/control-plane path remains in force. External-query/private/multimodal slots are not runtime consumers in this phase even if an active candidate binding exists.
- Capability slot routing metadata includes `slot_key`, `slot_version`, `slot_runtime_source=active_slot`, `slot_fallback_index`, and `slot_degraded=false`; degraded non-protected structured fallback uses `slot_runtime_source=active_slot_degraded` and `slot_degraded=true`.
- Slot detail payloads expose `candidate_engines[]` for runtimes satisfying the slot requirements and `blocked_candidate_engines[]` with product-level `gap_reasons[]` for enabled runtimes that are missing capability, missing probe evidence, or stale evidence.
- Deep/high slot is protected and cannot enable customer-space downgrade; high slot cannot publish a local Ollama runtime.
- Existing legacy `{slots: ...}` configs are read as both active and draft during normalization so upgrades do not drop live routing.
- Endpoints:
- `GET /ai-routing/capability-slots`: returns `active_version`, `draft_version`, `dirty`, `validation`, `history`, `active_slots`, `draft_slots`, configurable runtime slots, external-query provider-chain slots, and non-configurable candidate slots with masked runtime metadata.
- `PUT /ai-routing/capability-slots/{slot_key}/draft`: update `primary_engine_id`, `fallback_engine_ids`, `failure_policy`, and `allow_scene_downgrade` for configurable `low/high/structured/tool/long_context` draft slots; for `external_query`, the same request fields carry `primary_provider_id` and `fallback_provider_ids` and are normalized into the slot binding as `primary_provider_id / fallback_provider_ids / verifier_required=true / source_filtering_required=true / failure_policy=explicit_fail`. The endpoint appends `operation_logs(entity_type='ai_capability_slot', action='draft_update')`. `private/multimodal` return a product error because they are candidate slots and do not consume runtime in this phase.
- `PUT /ai-routing/capability-slots/{slot_key}`: backward-compatible alias for draft update; it no longer makes runtime routing immediately consume the change.
- `POST /ai-routing/capability-slots/validate`: validate draft slots for missing/disabled/test-failed runtimes, duplicate fallback, high-slot Ollama, protected downgrade, capability requirement misses, missing capability evidence, and stale capability evidence.
- `POST /ai-routing/capability-slots/publish`: promote draft slots to active when validation has no critical item, generate a release version, and append `operation_logs(entity_type='ai_capability_slot_release', action='publish')`.
- `POST /ai-routing/capability-slots/rollback`: restore a prior published slot snapshot as a new active version and append `operation_logs(entity_type='ai_capability_slot_release', action='rollback')`.
### GET `/ai-routing/overview`
- Purpose: platform AI routing overview for PMFlow AI operators.
- Auth: PMFlow platform capability `platform.ai_routing.manage`.
- Response data:
- `control_plane`: `{ version, source, updated_at, baseline_note }`
- `slots[]`: low/high slot summaries with Chinese labels, provider/model display summary, connection state, capability coverage, and masked key status.
- `capability_slots[]`: configurable `low/high/structured/tool/long_context/external_query` slots and candidate `private/multimodal` slots. LLM slots include `primary_engine_id`, fallback chain, failure policy, protected state, requirements, capability coverage, candidate count, candidate runtime previews, and blocked candidate reasons. `external_query` includes `primary_provider_id`, `fallback_provider_ids`, `verifier_required`, `source_filtering_required`, and `external_provider_registry` status for MCP adapter, bounded live search fallback, verifier, and fail-closed source filtering.
- `metrics`: recent 24h/7d calls, failures, failure rate, escalation rate, structured failure rate, average latency, and cost estimate.
- `diagnostics[]`: risk cards such as high slot unavailable, same runtime for low/high, protected downgrade, structured failure spike, or local Ollama scope risk.
- Boundary: no cleartext key, `api_env`, prompt, raw provider secret, raw error, or raw routing meta.
### GET `/ai-routing/team-policy`
- Purpose: team-lead-facing business policy view for project launch, requirement structuring, task breakdown, assignee matching, internal QA, external query, Feishu understanding, and Feishu reply.
- Auth: current workspace `OWNER` or `REVIEWER`.
- Response data:
- `strategy`: `quality_first | balanced | speed_first`
- `scenes[]`: Chinese business scene policies, preferred mode, escalation rule, fallback behavior, and editable business note.
- `automation_boundary`, `budget_guard`, `quality_targets`, `updated_at`, `updated_by`
- Boundary: team policy is saved product governance state only; it does not rewrite `llm_service` runtime routing in v1.
### PUT `/ai-routing/team-policy`
- Purpose: save team AI policy display/governance state.
- Auth: current workspace `OWNER` only via `can_approve_ai_routing_change`.
- Request body:
- `strategy?: "quality_first" | "balanced" | "speed_first"`
- `scenes?: [{ key, policy, mode?, human_confirmation?, protected? }]`
- `automation_boundary?: string`
- `budget_guard?: { enabled?: boolean, monthly_budget_cny?: number | null, alert_threshold_ratio?: number }`
- `quality_targets?: string[]`
- Side effects:
- persist into `integrations(type='ai_team_policy')`
- append `operation_logs(entity_type='ai_routing_policy')`
- Boundary: never writes `backend/.control_plane/pmflow-routing-rules.json`, provider secrets, or model-routing overrides.
### GET `/ai-routing/admin-console`
- Purpose: AI admin/operator console aggregating provider connection summaries, capability slots, post-publish slot observability, business-scene routes, recent failure samples, protected scenes, and supplier/runtime diagnostics.
- Auth: PMFlow platform capability `platform.ai_routing.manage`.
- Response data:
- `providers[]`: display-only provider health, enable state, fallback marker, connection test state, masked key state, normalized capability tags, capability source, probe status, last verified time, probe error summaries, probe blocking reasons, and state-derived `available_actions`.
- `capability_slots[]`: configurable `low/high/structured/tool/long_context/external_query` slot binding summaries and candidate `private/multimodal` placeholders with requirements, coverage, missing capability labels, missing evidence labels, stale evidence labels, candidate runtimes, and external provider registry state for the external-query slot.
- `capability_slot_release`: release-control payload from `GET /ai-routing/capability-slots`; the UI uses draft slots for editing and active slots for current runtime display.
- `slot_observability[]`: post-publish read model for active `low/high/structured/long_context/tool/external_query` slots. Each item includes `slot_key`, `slot_label`, `active_version`, `active_runtime`, and `windows.last_24h / windows.last_7d` with `status=sampled|empty`, `total_calls`, `failed_calls`, `failure_rate`, `fallback_hits`, `fallback_rate`, `degraded_calls`, `degraded_rate`, `active_slot_calls`, `active_slot_degraded_calls`, `average_latency_ms`, `estimated_cost`, source labels, and sanitized `recent_failure_samples[]`. Aggregation is based on `ai_call_logs` routing metadata when present (`slot_key`, `slot_runtime_source`, `slot_fallback_index`, `slot_degraded`) and compatible slot columns (`planned_tier/executed_tier/routing_decision`) for old logs. Empty windows return zero counts and `sample_state_label=暂无运行样本`; cost and latency are operational estimates from logs, not provider bills or SLA measurements.
- `external_query` publish validation fails closed when no external provider is available, neither MCP adapter nor bounded live search fallback is available, verifier is unavailable, source filtering is not fail-closed, or failure policy is not `explicit_fail`. Runtime only consumes the active external-query slot after `route_mode=external`; internal query and unpublished drafts do not consume it.
- `scene_routes[]`: scene label, mode, backend `scene_contract`, protection state, lifecycle, and diagnostic function mapping; raw `function_name` may only be present in admin data and must not be shown in the business-scene route UI.
- `scene_slot_matrix[]`: explainability matrix for operators. Each row includes `scene_key`, `scene_label`, `scene_contract`, `lifecycle`, `scene_family`, `current_slot`, `required_slot`, `current_slot_label`, `required_capabilities[]`, `protected`, `active_runtime`, `active_slot_runtime`, `draft_runtime`, `draft_slot_runtime`, `active_version`, `draft_dirty`, `capability_gap`, `recent_failure_rate`, `recent_failure_count`, `recent_failed_calls`, `recent_total_calls`, `scene_health`, `release_gate`, `impact_summary`, aggregate `risk_level/risk_label`, `slots[]`, and advanced `functions[]`. `scene_contract` includes business goal, input contract, output contract, success criteria, failure policy, confirmation policy, evidence policy, protected reason, expected slot, scene family, owner, and lifecycle. `scene_health` includes `status=healthy|warning|blocked|unknown`, score, blockers, warnings, and evidence summary. `release_gate` includes whether publish is blocked, blocking reason, draft impact, protected-scene regression, and suggested action. `active_runtime` / `active_slot_runtime` and `draft_runtime` / `draft_slot_runtime` summarize slot key/label, release version, configured state, runtime label/source, and primary runtime id. `capability_gap` distinguishes missing capability, missing probe evidence, stale evidence, and human-readable blocking reasons for release review. Each slot row includes `slot_key`, slot label, whether it is required by the scene, active runtime/source, release version, capability coverage, and risk reasons such as missing active binding, missing capability, missing probe evidence, stale probe evidence, or protected-scene downgrade. `functions[]` is diagnostic payload only; business-scene route UI must stay at scene/slot level, consume backend scene contracts, and route function-level inspection to hidden Route Lab.
- `recent_failures[]`: sanitized failure samples without raw error or prompt; includes `scene_key / scene_label` when the function belongs to a registered business scene so `/platform/ai-routing-failures?scene=...` can filter deterministically.
- `protected_scenes[]`, `diagnostics[]`
### GET `/ai-call-traces`
- Purpose: platform AI call trace ledger for PMFlow AI operators to inspect provider/model call history, sanitized request/response evidence, runtime routing metadata, slot usage, latency, tokens, cost estimates, and recent failure context.
- Auth: PMFlow platform capability `platform.ai_routing.manage`.
- Storage: reads from `ai_call_logs`; new runtime writes should populate `trace_id`, `user_id` when the request has an authenticated operator, raw configured `provider` code, `model`, `latency_ms`, `routing_meta`, prompt attribution fields `prompt_key / prompt_release_id / prompt_source`, and true upstream provider payload fields `provider_request_payload / provider_response_payload / provider_attempts` in addition to existing prompt/response/token/cost/status fields. Operator-facing responses must distinguish raw provider code from product/runtime display labels; DashScope/Qwen calls must persist provider code `dashscope`, never the removed historical code `glm`.
- Query params:
- `page`, `page_size`
- `status`
- `provider`, `model`
- `function_name`, `scene_key`, `slot_key`
- `prompt_key`, `prompt_source`, `prompt_release_id`
- `trace_id`
- `tenant_id`
- `keyword`
- `since_hours`
- Response data:
- `summary`: total calls, failed calls, total tokens, estimated cost, average latency, and top provider codes/display labels for the current filter set.
- `items[]`: log id, trace id, tenant/user, created time, raw `function_name` plus Chinese `function_label`, scene display metadata, status, raw `provider_code`, `provider_label`, `runtime_label`, optional `engine_id/engine_name`, model, slot key/label, prompt key/release/source, routing decision/source/rule, token/cost/latency metrics, upstream usage-derived `prompt_tokens / completion_tokens / total_tokens`, request `max_tokens`, `finish_reason`, Function Runtime Profile version/max token metadata, redacted trigger preview, and error summary.
- `page`, `page_size`, `total`, `has_next`.
- Capability probe display: AI call audit follows the real upstream endpoint interaction boundary. New provider capability probes do not write an extra `probe_engine_capabilities` aggregate log. Rows whose `function_name` starts with `probe_capability_` are visible by default only when they correspond to an upstream provider call; the model self-report probe is shown because it is a short upstream call, while non-upstream declared/contract gates are stored in provider capability evidence but are not written as AI call logs. Historical `probe_engine_capabilities` aggregate rows are hidden from default list/summary results and remain queryable only through an explicit `function_name` filter.
- Boundary: prompt, response, and provider payload content are always redacted before returning to the UI. Provider secrets, cleartext bearer tokens, API keys, phone numbers, emails, and raw transport headers must not be returned. The database stores compatibility `prompt/response` for legacy search and previews, but operator detail UI should prioritize true upstream `provider_request_payload / provider_response_payload / provider_attempts` and routing/probe metadata. This view is platform-only and must not appear in customer workspace AI settings, Project Launch, Space, or project-level AI copilot surfaces.
### GET `/ai-call-traces/{trace_id_or_log_id}`
- Purpose: open one AI call trace detail by `trace_id` or numeric `ai_call_logs.id`.
- Auth: PMFlow platform capability `platform.ai_routing.manage`.
- Response data:
- `trace`: list-row fields plus routing metadata, probe metadata, redacted upstream payload evidence, and audit policy notes.
- `trace.raw_payload_available=true` when `ai_call_logs.provider_request_payload`, `provider_response_payload`, or `provider_attempts` exists; detail response includes `request.provider_request_payload_redacted`, `response.provider_response_payload_redacted`, and `response.provider_attempts_redacted`. Raw encrypted provider payload replay/export is not part of this API.
- Boundary: 404 returns the standard response envelope with `data=null`; details follow the same redaction and platform-only visibility rules as the list endpoint.
### GET `/prompt-assets`
- Purpose: platform Prompt asset center for PMFlow AI operators to inspect all registered and discovered runtime prompts, release state, and migration gaps before editing or optimizing prompt behavior.
- Auth: PMFlow platform capability `platform.ai_routing.manage`.
- Storage/read model: combines `backend/app/config/ai_prompt_registry.py`, default templates exported by `backend/app/config/ai_prompts.py`, tenant-scoped `prompt_releases`, prompt release gate checks in `prompt_release_checks`, golden cases in `prompt_golden_cases`, replay runs/results in `prompt_replay_runs / prompt_replay_case_results`, and prompt-attributed quality metrics from `ai_call_logs`. It does not expose secrets or raw provider payloads.
- Query params:
- `family`: `classification | extraction | planning | response | governance`
- `runtime_source`: `catalog | ai_prompts | inline_discovered`
- `editable_policy`: `managed_release | guarded_release | needs_migration`
- `keyword`: matches prompt key, business scenario, module, or call site.
- Response data:
- `summary`: total assets, active release count, needs-migration count, and guarded count for the current tenant.
- `items[]`: prompt key, module, business scenario, call sites, input/output contract, family, responsibility scope, priority/audience, runtime source, editable policy, risk level, template variables, default template hash/preview, active release summary, latest release summary, release gate summary, latest replay run summary, draft count, release count, and migration status.
- Boundary: this is a platform-only operational inventory. `inline_discovered` means the prompt has been found in code but the runtime call site still needs migration to read active releases; the UI must not present such entries as already configurable or active. Customer workspace settings, Space, Project Launch, and project-level AI copilot surfaces must not expose prompt bodies.
### GET `/prompt-assets/{prompt_key}`
- Purpose: open one prompt asset detail, including the default template body when available, active/draft release state, call sites, contracts, and migration status.
- Auth: PMFlow platform capability `platform.ai_routing.manage`.
- Response data:
- Same fields as the list item, plus `default_template_body`, release `body_snapshot` values for platform-side review and diff, and a 7-day `quality_summary` based on `ai_call_logs.prompt_key`.
- Boundary: 404 returns the standard response envelope with `data=null`. Guarded prompts are visible for audit, but activation policy still belongs to the prompt release / replay / evaluation workflow and must not bypass release review.
### POST `/prompt-assets/{prompt_key}/releases`
- Purpose: create a tenant-scoped draft prompt release for a managed or guarded prompt asset.
- Auth: PMFlow platform capability `platform.ai_routing.manage`.
- Request data:
- `body_snapshot`: required prompt template body.
- `notes`: optional release note / change reason.
- Validation:
- `prompt_key` must exist in `ai_prompt_registry`.
- Only `editable_policy=managed_release | guarded_release` and non-inline assets with a default template can create releases.
- Template placeholders must not introduce variables outside the default template contract.
- Boundary: `needs_migration` and `inline_discovered` return the standard error envelope and remain read-only until runtime migration is implemented. `guarded_release` can create candidate releases, but cannot activate until release gate checks pass.
### POST `/prompt-assets/{prompt_key}/releases/{release_id}/activate`
- Purpose: activate a draft prompt release for the current tenant; any existing active release for the same prompt becomes archived.
- Auth: PMFlow platform capability `platform.ai_routing.manage`.
- Boundary: managed releases can activate directly. Guarded releases require passing gate checks before activation; `golden_replay` remains blocking when no active golden cases exist, and with active cases it requires a completed release replay run with zero failed cases. Current replay mode is deterministic contract smoke and explicitly records that no real LLM replay was executed.
### POST `/prompt-assets/{prompt_key}/releases/{release_id}/rollback`
- Purpose: reactivate an archived managed prompt release as a rollback target.
- Auth: PMFlow platform capability `platform.ai_routing.manage`.
- Boundary: only archived releases can be rolled back. Draft releases must use activate, and guarded prompts remain blocked until release gates exist.
### GET `/prompt-assets/{prompt_key}/quality-summary`
- Purpose: aggregate prompt-attributed AI call quality for one prompt asset.
- Auth: PMFlow platform capability `platform.ai_routing.manage`.
- Query params:
- `window`: `24h | 7d | 30d`, default `7d`.
- `status`: optional status filter.
- `release_id`: optional prompt release id filter.
- Response data: `total_calls`, `failed_calls`, `failure_rate`, `fallback_calls`, `active_release_calls`, `default_fallback_calls`, `average_latency_ms`, `estimated_cost`, `recent_failures`, and `release_breakdown`.
- Boundary: no attributed call rows returns zero metrics and empty samples; the API must not infer quality from unrelated function names or raw prompt text.
### GET `/prompt-assets/{prompt_key}/golden-cases`
- Purpose: list deterministic golden cases for a prompt asset.
- Auth: PMFlow platform capability `platform.ai_routing.manage`.
- Query params: `status`, default can be omitted; UI usually reads `active`.
- Response data: `items[]`, `total`, `active_count`.
### POST `/prompt-assets/{prompt_key}/golden-cases`
- Purpose: create a manual golden case for future replay and current guarded release gate.
- Auth: PMFlow platform capability `platform.ai_routing.manage`.
- Request data: `case_name`, `input_payload`, optional `expected_contract`, `expected_contains`, `notes`.
- Boundary: stores JSON contracts only; it does not execute an LLM and does not publish a prompt.
### POST `/prompt-assets/{prompt_key}/golden-cases/from-call/{ai_call_log_id}`
- Purpose: convert a prompt-attributed AI call log into a golden case seed.
- Auth: PMFlow platform capability `platform.ai_routing.manage`.
- Boundary: the source call must have the same `prompt_key`. The generated case stores compact prompt/response previews and metadata, not raw provider payloads or secrets.
### GET `/prompt-assets/{prompt_key}/releases/{release_id}/checks`
- Purpose: list release gate checks for one prompt release and return the derived gate summary.
- Auth: PMFlow platform capability `platform.ai_routing.manage`.
- Response data:
- `items[]`: check id, prompt key, release id, check type, status, severity, summary, details, creator, created/completed time.
- `gate`: derived status `not_run | passed | blocked`, blocking reasons, required checks.
### POST `/prompt-assets/{prompt_key}/releases/{release_id}/checks/run`
- Purpose: run MVP prompt release gate checks for a candidate release.
- Auth: PMFlow platform capability `platform.ai_routing.manage`.
- Current checks:
- `variable_contract`: verifies the candidate template does not introduce placeholders outside the default template contract.
- `schema_smoke`: checks whether structured/JSON prompt contracts declare JSON/schema constraints.
- `golden_replay`: records blocking `skipped/no_cases` when no active golden cases exist; with active cases it remains blocking until a release replay run exists, and passes only when the latest completed replay run has zero failed cases. Warnings are recorded for human review and do not pretend to be real LLM replay evidence.
- Boundary: this endpoint does not call an LLM, does not mutate runtime routing, and does not automatically activate a release. Guarded releases stay blocked unless all required checks pass.
### POST `/prompt-assets/{prompt_key}/releases/{release_id}/replay-runs`
- Purpose: run deterministic replay review for one prompt release against active golden cases.
- Auth: PMFlow platform capability `platform.ai_routing.manage`.
- Request data:
- `mode`: default `contract_smoke`.
- `case_status`: default `active`.
- `max_cases`: default `20`, max `100`.
- `include_llm`: must be `false` in this phase; true returns a standard error.
- `reason`: optional audit note.
- Behavior:
- Renders candidate release templates with golden case `input_payload`.
- Optionally renders the current active release as baseline when present.
- Evaluates deterministic `expected_contract` fields such as `required_input_keys`, `required_prompt_contains`, `expected_contains`, and `forbidden_terms`.
- Persists one `prompt_replay_runs` row and per-case `prompt_replay_case_results` rows.
- Response data: replay run summary with `run_status`, counts, risk flags, summary, baseline release id, and case results.
- Boundary: does not call an LLM, does not write runtime routing, and does not activate a release. Missing active golden cases, unsupported replay mode, or `include_llm=true` fail closed.
### GET `/prompt-assets/{prompt_key}/releases/{release_id}/replay-runs`
- Purpose: list replay runs for one prompt release.
- Auth: PMFlow platform capability `platform.ai_routing.manage`.
- Response data: `{ prompt_key, release_id, items[], total }`; item rows include run status, counts, summary and risk flags without per-case results.
### GET `/prompt-assets/{prompt_key}/replay-runs/{run_id}`
- Purpose: read one replay run with per-case results.
- Auth: PMFlow platform capability `platform.ai_routing.manage`.
- Boundary: 404 returns the standard response envelope with `data=null`; replay result payloads contain rendered prompt snapshots and must remain platform-only.
### POST `/ai-routing/diagnostics/run`
- Purpose: run read-only diagnostics for routing/provider health.
- Auth: PMFlow platform capability `platform.ai_routing.manage`.
- Side effects: none; does not modify provider, slot, team policy, or runtime routing.
## PMFlow OS Project Memory Read Models
All endpoints return `{ code, message, data }` and require project visibility through membership or manager access. These endpoints are read-only: they must not commit ChangeSets, mutate truth nodes, execute AI actions, or write Project Memory.
### GET `/pmflow-os/projects/{project_id}/project-memory/overview`
- Purpose: return the Project Knowledge Hub overview for the memory homepage.
- Response data: `{ projectId, projectName, generatedAt, metrics[], facts[], requirements[], decisions[], constraints[], evidence[], contextPacks[], writebacks[], aiSuggestions[], warnings[] }`.
- Boundary: `overview` is a backend read model over confirmed truth, indexed assets, context pack projections, and writeback audit rows; frontend must not reconstruct the authoritative memory overview from raw `truth-state` except as a degraded fallback.
### GET `/pmflow-os/projects/{project_id}/project-memory/items`
- Purpose: list confirmed project memory objects across facts, requirements, decisions, risks, constraints, tasks, tests, releases, and metrics.
- Query params: `category`, `stage`, `source_type`, `status`, `query`, `limit`, `offset`.
- Response data: `{ projectId, total, limit, offset, items[] }`; each item includes `evidenceRefs`, `impactRefs`, timestamps, status, and metadata.
### GET `/pmflow-os/projects/{project_id}/project-memory/items/{item_id}`
- Purpose: read one memory item for detail drawers or deep inspection.
- Response data: `{ projectId, item }`.
### GET `/pmflow-os/projects/{project_id}/project-memory/assets`
- Purpose: list project assets such as conversation evidence, documents, code/PR evidence, tests, release records, feedback, metrics, and external research assets.
- Query params: `asset_type`, `source_type`, `status`, `query`, `limit`, `offset`.
- Boundary: assets may be indexed automatically, but they do not become Project Truth until a Change Review or trusted rule confirms them.
### GET `/pmflow-os/projects/{project_id}/project-memory/context-packs`
- Purpose: list AI-readable context packs grouped by execution scenario.
- Response data: `{ projectId, total, contextPacks[] }`; packs expose included item/asset ids, usage, warnings, and freshness.
### GET `/pmflow-os/projects/{project_id}/project-memory/writebacks`
- Purpose: list ChangeSet writeback/version audit rows.
- Response data: `{ projectId, total, limit, offset, writebacks[] }`; rows include item counts, committed/skipped/pending status, source ChangeSet id, committer display fields (`committedBy / committedById`), and item summaries.
## PMFlow OS Adaptive Capacity
All endpoints return `{ code, message, data }`. Project members may read. Override simulate/apply requires `OWNER` or `REVIEWER`; write endpoints only persist capacity override audit records and must not assign tasks, request review, write ChangeSet, or mutate Project Memory.
### GET `/pmflow-os/projects/{project_id}/capacity-plan?date=YYYY-MM-DD`
- Purpose: compute the daily adaptive human capacity plan from current truth-state, reviewer routing, execution summary, workspace bindings, user projects, legacy workload reference, and active overrides.
- Response data:
- `projectId`, `date`, `generatedAt`
- `teamDemandMix`
- `teamEffectiveMinutes`, `teamPlannedTouchMinutes`
- `memberPlans[]`: base/effective minutes, dynamic `suggestedBaseline`, current mix, planned touch minutes, status, remaining minutes, bottleneck, fatigue risk, risk density, `canTakeMore`, manual override, explanations
- `humanTouches[]`: source, target href, work type, estimated minutes, risk, urgency, binding, assigned user, context readiness, AI reduction potential
- `warnings`, `recommendations`
- Boundary: daily plan is computed at read time. Do not persist fixed baselines or daily snapshots.
### POST `/pmflow-os/projects/{project_id}/capacity-overrides/simulate`
- Purpose: simulate a manager capacity override before applying it.
- Request body: `PMFlowCapacityOverridePayload`
- Response data: `{ beforePlan, afterPlan, impactSummary, warnings }`
- Boundary: no database write and no assignment side effect.
### POST `/pmflow-os/projects/{project_id}/capacity-overrides`
- Purpose: apply a manager override and persist audit in `human_capacity_overrides`.
- Request body: `PMFlowCapacityOverridePayload`
- Response data: `PMFlowCapacityOverrideRecord`
- Side effects: insert one override audit row only.
- Boundary: `reason` is required; default expiry is same day 23:59 if omitted. Overtime increases time budget but must not clear fatigue or quality risk.
### GET `/pmflow-os/projects/{project_id}/capacity-overrides?date=YYYY-MM-DD`
- Purpose: list override audit records for the given day.
- Response data: `PMFlowCapacityOverrideRecord[]`
### GET `/pmflow-os/projects/{project_id}/execution-capabilities`
- Addition: response data may include `adaptiveCapacitySummary: DailyCapacityPlan`.
## Task Briefs
### GET `/task-briefs/center`
- Purpose: list generated brief cards
### GET `/task-briefs/templates`
- Purpose: list templates
### POST `/task-briefs/templates`
- Purpose: create template
### PUT `/task-briefs/templates/{template_id}`
- Purpose: update template
### GET `/task-briefs/runs`
- Purpose: list generation runs
### POST `/task-briefs/generate`
- Purpose: generate one task brief run
### GET `/task-briefs/contract-sync`
- Purpose: expose docs/contract sync status snapshot