PMFlow/backend/app/config/operation_catalog.py

456 lines
16 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Operation Catalog(系统写操作目录).
规则:
1. LLM 仅输出结构化意图,真实写库必须走 Tool/Service 层。
2. 每个操作必须声明 operation_code/input_schema/rbac_policy/idempotency_key/audit_fields/side_effect_guard。
3. 执行态统一遵循 commit_then_outbox,便于副作用可追溯与重放。
"""
from __future__ import annotations
from typing import Any, Literal
from pydantic import BaseModel, ConfigDict, Field
class OperationIdempotencySpec(BaseModel):
model_config = ConfigDict(extra="forbid")
fields: list[str] = Field(default_factory=list, min_length=1)
scope: Literal["tenant", "project", "global"] = "tenant"
ttl_seconds: int = Field(default=86400, ge=60, le=604800)
class OperationSideEffectGuard(BaseModel):
model_config = ConfigDict(extra="forbid")
require_tool_executor: bool = True
require_schema_validation: bool = True
deny_free_text_write: bool = True
transaction_mode: Literal["commit_then_outbox", "transactional"] = "commit_then_outbox"
allowed_paths: list[str] = Field(default_factory=list)
class OperationCatalogEntry(BaseModel):
model_config = ConfigDict(extra="forbid")
operation_code: str = Field(min_length=3, max_length=120)
domain: Literal["project", "task", "personnel", "knowledge", "notification", "file"]
description: str = Field(min_length=4, max_length=300)
input_schema: dict[str, Any]
rbac_policy: list[str] = Field(default_factory=list, min_length=1)
idempotency_key: OperationIdempotencySpec
audit_fields: list[str] = Field(default_factory=list, min_length=1)
side_effect_guard: OperationSideEffectGuard
def _entry(
*,
operation_code: str,
domain: Literal["project", "task", "personnel", "knowledge", "notification", "file"],
description: str,
input_schema: dict[str, Any],
rbac_policy: list[str],
idempotency_fields: list[str],
audit_fields: list[str],
allowed_paths: list[str] | None = None,
) -> OperationCatalogEntry:
return OperationCatalogEntry(
operation_code=operation_code,
domain=domain,
description=description,
input_schema=input_schema,
rbac_policy=rbac_policy,
idempotency_key=OperationIdempotencySpec(fields=idempotency_fields),
audit_fields=audit_fields,
side_effect_guard=OperationSideEffectGuard(
allowed_paths=allowed_paths or [],
),
)
_COMMON_AUDIT_FIELDS = [
"tenant_id",
"user_id",
"trace_id",
"operation_code",
"idempotency_key",
"target_type",
"target_id",
"before_snapshot",
"after_snapshot",
]
OPERATION_CATALOG: tuple[OperationCatalogEntry, ...] = (
_entry(
operation_code="project.create",
domain="project",
description="创建项目",
input_schema={
"type": "object",
"required": ["title"],
"properties": {
"title": {"type": "string", "minLength": 1, "maxLength": 80},
"description": {"type": "string", "maxLength": 4000},
"owner_id": {"type": "integer"},
},
"additionalProperties": False,
},
rbac_policy=["BOSS", "ADMIN"],
idempotency_fields=["tenant_id", "title"],
audit_fields=_COMMON_AUDIT_FIELDS,
),
_entry(
operation_code="project.update",
domain="project",
description="更新项目元数据",
input_schema={
"type": "object",
"required": ["project_id"],
"properties": {
"project_id": {"type": "integer"},
"title": {"type": "string", "maxLength": 80},
"description": {"type": "string", "maxLength": 4000},
"status": {"type": "string", "enum": ["active", "archived", "done"]},
},
"additionalProperties": False,
},
rbac_policy=["BOSS", "ADMIN"],
idempotency_fields=["tenant_id", "project_id", "updated_at"],
audit_fields=_COMMON_AUDIT_FIELDS,
),
_entry(
operation_code="project.archive",
domain="project",
description="归档项目",
input_schema={
"type": "object",
"required": ["project_id"],
"properties": {"project_id": {"type": "integer"}},
"additionalProperties": False,
},
rbac_policy=["BOSS", "ADMIN"],
idempotency_fields=["tenant_id", "project_id", "status"],
audit_fields=_COMMON_AUDIT_FIELDS,
),
_entry(
operation_code="project.reopen",
domain="project",
description="重新打开项目",
input_schema={
"type": "object",
"required": ["project_id"],
"properties": {"project_id": {"type": "integer"}},
"additionalProperties": False,
},
rbac_policy=["BOSS", "ADMIN"],
idempotency_fields=["tenant_id", "project_id", "status"],
audit_fields=_COMMON_AUDIT_FIELDS,
),
_entry(
operation_code="task.create",
domain="task",
description="创建任务",
input_schema={
"type": "object",
"required": ["project_id", "title"],
"properties": {
"project_id": {"type": "integer"},
"title": {"type": "string", "minLength": 1, "maxLength": 120},
"description": {"type": "string", "maxLength": 5000},
"assignee_id": {"type": "integer"},
"required_skill_tag_ids": {
"type": "array",
"items": {"type": "integer"},
"maxItems": 20,
},
"priority": {"type": "string", "enum": ["high", "medium", "low", "P0", "P1", "P2", "P3"]},
"estimated_hours": {"type": "number", "minimum": 0, "maximum": 200},
"due_date": {"type": "string", "maxLength": 40},
},
"additionalProperties": False,
},
rbac_policy=["BOSS", "ADMIN", "PM"],
idempotency_fields=["tenant_id", "project_id", "title"],
audit_fields=_COMMON_AUDIT_FIELDS,
),
_entry(
operation_code="task.update",
domain="task",
description="更新任务内容",
input_schema={
"type": "object",
"required": ["task_id"],
"properties": {
"task_id": {"type": "integer"},
"title": {"type": "string", "maxLength": 120},
"description": {"type": "string", "maxLength": 5000},
},
"additionalProperties": False,
},
rbac_policy=["BOSS", "ADMIN", "PM"],
idempotency_fields=["tenant_id", "task_id", "updated_at"],
audit_fields=_COMMON_AUDIT_FIELDS,
),
_entry(
operation_code="task.assign",
domain="task",
description="分配任务负责人",
input_schema={
"type": "object",
"required": ["task_id", "assignee_id"],
"properties": {
"task_id": {"type": "integer"},
"assignee_id": {"type": "integer"},
},
"additionalProperties": False,
},
rbac_policy=["BOSS", "ADMIN", "PM"],
idempotency_fields=["tenant_id", "task_id", "assignee_id"],
audit_fields=_COMMON_AUDIT_FIELDS,
),
_entry(
operation_code="task.prioritize",
domain="task",
description="调整任务优先级",
input_schema={
"type": "object",
"required": ["task_id", "priority"],
"properties": {
"task_id": {"type": "integer"},
"priority": {"type": "string", "enum": ["high", "medium", "low", "P0", "P1", "P2", "P3"]},
},
"additionalProperties": False,
},
rbac_policy=["BOSS", "ADMIN", "PM"],
idempotency_fields=["tenant_id", "task_id", "priority"],
audit_fields=_COMMON_AUDIT_FIELDS,
),
_entry(
operation_code="task.status_transition",
domain="task",
description="任务状态迁移",
input_schema={
"type": "object",
"required": ["task_id", "status"],
"properties": {
"task_id": {"type": "integer"},
"status": {"type": "string", "enum": ["todo", "in_progress", "review", "done", "blocked", "closed"]},
},
"additionalProperties": False,
},
rbac_policy=["BOSS", "ADMIN", "PM", "AI_ENGINEER"],
idempotency_fields=["tenant_id", "task_id", "status"],
audit_fields=_COMMON_AUDIT_FIELDS,
),
_entry(
operation_code="task.delete",
domain="task",
description="删除任务",
input_schema={
"type": "object",
"required": ["task_id"],
"properties": {"task_id": {"type": "integer"}},
"additionalProperties": False,
},
rbac_policy=["BOSS", "ADMIN"],
idempotency_fields=["tenant_id", "task_id"],
audit_fields=_COMMON_AUDIT_FIELDS,
),
_entry(
operation_code="personnel.role_update",
domain="personnel",
description="更新人员角色",
input_schema={
"type": "object",
"required": ["user_id", "role"],
"properties": {
"user_id": {"type": "integer"},
"role": {"type": "string", "maxLength": 50},
},
"additionalProperties": False,
},
rbac_policy=["BOSS", "ADMIN"],
idempotency_fields=["tenant_id", "user_id", "role"],
audit_fields=_COMMON_AUDIT_FIELDS,
),
_entry(
operation_code="personnel.workload_update",
domain="personnel",
description="更新人员每周可用工时",
input_schema={
"type": "object",
"required": ["user_id", "max_weekly_hours"],
"properties": {
"user_id": {"type": "integer"},
"max_weekly_hours": {"type": "number", "minimum": 1, "maximum": 120},
},
"additionalProperties": False,
},
rbac_policy=["BOSS", "ADMIN", "PM"],
idempotency_fields=["tenant_id", "user_id", "max_weekly_hours", "date"],
audit_fields=_COMMON_AUDIT_FIELDS,
),
_entry(
operation_code="personnel.availability_update",
domain="personnel",
description="更新人员可用性",
input_schema={
"type": "object",
"required": ["user_id", "status"],
"properties": {
"user_id": {"type": "integer"},
"status": {"type": "string", "enum": ["active", "away", "offline"]},
},
"additionalProperties": False,
},
rbac_policy=["BOSS", "ADMIN", "PM"],
idempotency_fields=["tenant_id", "user_id", "status", "date"],
audit_fields=_COMMON_AUDIT_FIELDS,
),
_entry(
operation_code="knowledge.doc_upsert",
domain="knowledge",
description="新增或更新知识文档",
input_schema={
"type": "object",
"required": ["title", "content"],
"properties": {
"doc_id": {"type": "integer"},
"title": {"type": "string", "minLength": 1, "maxLength": 255},
"content": {"type": "string", "minLength": 1},
},
"additionalProperties": False,
},
rbac_policy=["BOSS", "ADMIN", "PM", "AI_ENGINEER"],
idempotency_fields=["tenant_id", "doc_id", "content_hash"],
audit_fields=_COMMON_AUDIT_FIELDS,
),
_entry(
operation_code="knowledge.chunk_upsert",
domain="knowledge",
description="新增或更新知识分片",
input_schema={
"type": "object",
"required": ["doc_id", "chunk_text"],
"properties": {
"doc_id": {"type": "integer"},
"chunk_id": {"type": "integer"},
"chunk_text": {"type": "string", "minLength": 1},
},
"additionalProperties": False,
},
rbac_policy=["BOSS", "ADMIN", "PM", "AI_ENGINEER"],
idempotency_fields=["tenant_id", "doc_id", "chunk_hash"],
audit_fields=_COMMON_AUDIT_FIELDS,
),
_entry(
operation_code="knowledge.link_upsert",
domain="knowledge",
description="维护知识链接(文档/实体/项目关系)",
input_schema={
"type": "object",
"required": ["source_type", "source_id", "target_type", "target_id"],
"properties": {
"source_type": {"type": "string", "maxLength": 40},
"source_id": {"type": "integer"},
"target_type": {"type": "string", "maxLength": 40},
"target_id": {"type": "integer"},
},
"additionalProperties": False,
},
rbac_policy=["BOSS", "ADMIN", "PM", "AI_ENGINEER"],
idempotency_fields=["tenant_id", "source_type", "source_id", "target_type", "target_id"],
audit_fields=_COMMON_AUDIT_FIELDS,
),
_entry(
operation_code="knowledge.reindex",
domain="knowledge",
description="触发知识库重建索引",
input_schema={
"type": "object",
"required": ["scope"],
"properties": {
"scope": {"type": "string", "enum": ["tenant", "project", "document"]},
"project_id": {"type": "integer"},
"doc_id": {"type": "integer"},
},
"additionalProperties": False,
},
rbac_policy=["BOSS", "ADMIN", "AI_ENGINEER"],
idempotency_fields=["tenant_id", "scope", "project_id", "doc_id", "date_hour"],
audit_fields=_COMMON_AUDIT_FIELDS,
),
_entry(
operation_code="notification.create",
domain="notification",
description="创建通知",
input_schema={
"type": "object",
"required": ["user_id", "title", "content"],
"properties": {
"user_id": {"type": "integer"},
"title": {"type": "string", "minLength": 1, "maxLength": 150},
"content": {"type": "string", "minLength": 1, "maxLength": 2000},
},
"additionalProperties": False,
},
rbac_policy=["BOSS", "ADMIN", "PM"],
idempotency_fields=["tenant_id", "project_id", "user_id", "title", "content_hash"],
audit_fields=_COMMON_AUDIT_FIELDS,
),
_entry(
operation_code="notification.ack",
domain="notification",
description="确认通知",
input_schema={
"type": "object",
"required": ["notification_id"],
"properties": {"notification_id": {"type": "integer"}},
"additionalProperties": False,
},
rbac_policy=["BOSS", "ADMIN", "PM", "AI_ENGINEER"],
idempotency_fields=["tenant_id", "notification_id", "is_read"],
audit_fields=_COMMON_AUDIT_FIELDS,
),
_entry(
operation_code="notification.close",
domain="notification",
description="关闭通知",
input_schema={
"type": "object",
"required": ["notification_id"],
"properties": {"notification_id": {"type": "integer"}},
"additionalProperties": False,
},
rbac_policy=["BOSS", "ADMIN", "PM"],
idempotency_fields=["tenant_id", "notification_id", "status"],
audit_fields=_COMMON_AUDIT_FIELDS,
),
_entry(
operation_code="file.write_whitelist",
domain="file",
description="写入白名单文件路径",
input_schema={
"type": "object",
"required": ["path", "content"],
"properties": {
"path": {"type": "string", "minLength": 1, "maxLength": 400},
"content": {"type": "string"},
"encoding": {"type": "string", "enum": ["utf-8", "ascii"]},
},
"additionalProperties": False,
},
rbac_policy=["BOSS", "ADMIN", "PM"],
idempotency_fields=["tenant_id", "path", "content_hash"],
audit_fields=_COMMON_AUDIT_FIELDS,
allowed_paths=[
"/app/backend/reports",
"/app/backend/exports",
"/app/backend/tmp",
"/tmp/pmflow",
],
),
)