"""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", ], ), )