456 lines
16 KiB
Python
456 lines
16 KiB
Python
"""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",
|
||
],
|
||
),
|
||
)
|