diff --git a/.craftkit/designs/engineering-baseline/backend-design.md b/.craftkit/designs/engineering-baseline/backend-design.md new file mode 100644 index 0000000..604591d --- /dev/null +++ b/.craftkit/designs/engineering-baseline/backend-design.md @@ -0,0 +1,479 @@ +--- +reviewStatus: pending +reviewedAt: null +replacedBy: null +--- + +# 第一阶段工程基线后端落地设计 + +## 1. 文档定位 + +本文档定义 JobRadar 第一阶段工程基线的后端落地边界,供后续模型设计、编码、迁移、测试和验收使用。本文档是待审核设计,不代表相关代码已经实现。 + +设计依据: + +- [第一阶段工程基线开发计划](../../../docs/engineering-baseline/dev-plan.md) +- [架构设计](../../../docs/architecture.md) +- [Agent 契约](../../../docs/agent-contract.md) +- [AI 开发约定](../../../docs/development-guide.md) + +当前技术基线为 Python 3.13、Django 6.0.8 和 OpenAI Agents SDK 0.22.0。项目没有已登记的 Django 公共 profile,实现时以当前项目规则和对应版本的官方契约为准。 + +## 2. 目标与范围 + +第一阶段将现有 Django 配置骨架扩展为一个可登录、可隔离、可审计、可记录 Agent 运行,但暂不执行真实岗位研究的模块化单体。 + +阶段完成后应具备: + +- Django 内置用户的登录、退出及基础资料维护能力。 +- 普通用户之间的数据归属隔离。 +- Agent Run、运行事件、工具调用和人工确认的持久化能力。 +- 合法的运行状态转换、事件顺序和幂等控制。 +- OpenAI Agents SDK 的稳定适配入口及无外部费用的测试替身。 +- 包含请求和运行关联标识的脱敏日志。 +- SQLite 完整测试及 PostgreSQL 核心兼容验证能力。 + +第一阶段明确不实现: + +- 招聘网站采集、企业调查、岗位标准化、筛选、评分和推荐报告。 +- 真实岗位研究 Agent 循环或付费模型调用。 +- Celery、Redis、定时调度和失败任务自动重试。 +- 公开注册、复杂 RBAC、前后端分离、REST API 和独立前端框架。 +- 自动投递简历、自动联系招聘人员或其他不可逆外部操作。 + +## 3. 总体调用关系 + +```mermaid +flowchart LR + USER["普通用户"] --> AUTH["Django 登录与 Session"] + AUTH --> PROFILE["用户资料"] + AUTH --> RUN_VIEW["运行列表与详情"] + ADMIN["管理员"] --> ADMIN_SITE["Django Admin"] + ADMIN_SITE --> RUN_SERVICE["Agent Run 服务层"] + RUN_SERVICE --> RUN["AgentRun"] + RUN_SERVICE --> EVENT["AgentRunEvent"] + RUN_SERVICE --> TOOL["ToolCall"] + RUN_SERVICE --> APPROVAL["HumanApproval"] + SDK_ADAPTER["SDK Gateway / 测试替身"] --> RUN_SERVICE +``` + +所有页面、Admin 扩展和 SDK 适配器通过服务层改变运行状态,不直接拼装跨模型写入。 + +## 4. 模块职责 + +### 4.1 `common` + +`common` 只提供跨业务复用的基础能力: + +- 创建时间和更新时间抽象模型。 +- 用户归属抽象模型及用户范围查询入口。 +- 领域异常基类。 +- 请求关联标识中间件。 +- 日志上下文和敏感信息脱敏工具。 + +`common` 不包含账户、Agent 或未来岗位业务逻辑,也不依赖业务应用。 + +### 4.2 `accounts` + +`accounts` 负责: + +- Django 内置 `User` 的一对一扩展资料。 +- 当前用户资料的读取和修改。 +- 登录、退出及资料页面组织。 +- 用户停用约定和资料管理后台。 + +第一阶段不自定义用户模型,不开放公开注册。用户由管理员创建,日常账户退出使用停用而非物理删除。 + +### 4.3 `agent_runtime` + +`agent_runtime` 负责: + +- Agent Run 生命周期和状态转换。 +- 运行事件的有序追加。 +- 工具调用、幂等键及结果摘要记录。 +- 人工确认请求和处理结果。 +- SDK 运行结果到本地持久化模型的适配。 +- 运行列表、运行详情和管理后台。 + +该模块不拥有岗位、企业、筛选和评分数据。 + +### 4.4 依赖方向 + +```text +JobRadar 配置与 URL + ↓ +accounts ──────→ common + ↓ +agent_runtime ─→ common + ↓ +OpenAI Agents SDK 适配层 +``` + +禁止 `common` 反向依赖业务应用,禁止 `accounts` 依赖 `agent_runtime`。 + +## 5. 领域对象与数据所有权 + +### 5.1 用户归属抽象模型 + +建议提供抽象模型 `UserOwnedModel`: + +- `owner`:关联 Django 用户。 +- `created_at`:创建时间。 +- `updated_at`:更新时间。 + +规则: + +- 所有用户私有模型必须继承该模型或提供等价的 `owner` 字段。 +- 普通查询必须从 `owner=request.user` 的数据范围开始,不能先全表查询再在 Python 中校验。 +- 审计数据对用户删除采用保护策略;包含历史运行记录的用户不得直接物理删除。 +- 管理员跨用户查询仅允许出现在 Django Admin 或明确的管理服务中。 + +### 5.2 `UserProfile` + +建议字段: + +- `user`:与 Django `User` 一对一关联。 +- `display_name`:可选显示名。 +- `timezone`:默认 `Asia/Shanghai`。 +- `created_at`、`updated_at`。 + +第一阶段不加入技能、薪资、地区和企业偏好。这些内容属于后续个人画像领域,应在需求明确后独立建模并支持版本化。 + +资料由账户服务显式 `get_or_create`,不依赖隐藏的 Signal 自动创建。 + +### 5.3 `AgentRun` + +建议字段分组: + +- 归属:`owner`。 +- 标识与状态:主键、`title`、`status`、`lock_version`。 +- 输入输出:`input_summary`、`output_summary`。 +- 版本:`agent_name`、`agent_version`、`instruction_version`、`toolset_version`、`model_name`。 +- 追踪:`trace_id`、`started_at`、`finished_at`、`duration_ms`。 +- 用量:`input_tokens`、`output_tokens`、`estimated_cost`。 +- 失败:`error_code`、`error_summary`。 +- 审计:`created_at`、`updated_at`。 + +输入、输出和错误只保存经过脱敏、限制大小的摘要,不保存 API Key、密码、Cookie 或完整敏感工具参数。第一阶段不执行真实模型调用,因此 `model_name`、token 和费用字段允许为空。 + +### 5.4 `AgentRunEvent` + +建议字段: + +- `run`、`sequence`、`event_type`。 +- `summary`、`payload_summary`。 +- `occurred_at`、`created_at`。 + +约束: + +- `(run, sequence)` 唯一。 +- 默认按 Run 和序号升序读取。 +- 事件作为审计记录追加,普通业务不得修改或删除。 + +事件类型至少包括: + +- `run_created`、`run_started`、`run_paused`、`run_resumed`。 +- `run_succeeded`、`run_failed`、`run_cancelled`。 +- `tool_started`、`tool_completed`、`tool_failed`。 +- `approval_requested`、`approval_resolved`。 + +### 5.5 `ToolCall` + +建议字段分组: + +- 归属:`run`、`call_id`。 +- 工具:`tool_name`、`tool_version`、`idempotency_key`。 +- 状态:`status`、`started_at`、`finished_at`、`duration_ms`。 +- 摘要:`arguments_summary`、`result_summary`。 +- 失败:`error_code`、`error_summary`。 +- 审计:`created_at`、`updated_at`。 + +约束: + +- `(run, call_id)` 唯一。 +- 非空幂等键在明确的业务作用域内唯一。 +- 已成功的相同幂等调用不得重复产生副作用。 +- 第一阶段只验证记录能力,不实现真实采集工具。 + +### 5.6 `HumanApproval` + +建议字段: + +- `run`、`request_key`、`approval_type`、`status`。 +- `request_summary`、`decision_summary`。 +- `requested_at`、`resolved_at`、`resolved_by`。 +- `created_at`、`updated_at`。 + +状态包括 `pending`、`approved`、`rejected`、`expired` 和 `cancelled`。 + +约束: + +- `(run, request_key)` 唯一。 +- 只有 `pending` 状态可以处理。 +- `resolved_by` 必须是 Run 所属用户或具备管理权限的管理员。 +- 第一阶段只在 Admin 中处理人工确认,不开发独立用户审批页面。 + +数据库字段类型、索引名和完整删除策略应在编码前通过数据库专项设计进一步确认。 + +## 6. Agent Run 状态机 + +运行状态: + +- `pending` +- `running` +- `waiting_approval` +- `succeeded` +- `failed` +- `cancelled` + +允许的转换: + +| 当前状态 | 可转换状态 | +| --- | --- | +| `pending` | `running`、`cancelled` | +| `running` | `waiting_approval`、`succeeded`、`failed`、`cancelled` | +| `waiting_approval` | `running`、`failed`、`cancelled` | +| `succeeded` | 无 | +| `failed` | 无 | +| `cancelled` | 无 | + +状态规则: + +- 终态不可再次转换。 +- 成功和失败必须记录完成时间;失败还必须记录脱敏错误摘要。 +- 进入 `waiting_approval` 时必须在同一事务中创建待处理的 `HumanApproval` 和对应事件。 +- 确认通过后才能恢复运行;确认拒绝默认将 Run 置为 `cancelled`。 +- 状态转换和对应事件必须在同一事务中提交。 +- 第一阶段不支持失败 Run 原地重试;后续重试应创建新 Run 并关联原 Run。 + +## 7. 服务层设计 + +### 7.1 账户服务 + +建议职责: + +- 获取或显式创建当前用户资料。 +- 修改当前用户自己的资料。 +- 由管理员停用指定用户。 + +普通用户请求其他用户资料时按资源不存在处理,避免泄露目标用户是否存在。 + +### 7.2 Agent Run 服务 + +服务层应提供以下原子能力: + +- 创建 Run。 +- 开始、完成、失败或取消 Run。 +- 追加运行事件。 +- 开始、完成或失败一次工具调用。 +- 创建人工确认请求。 +- 处理人工确认结果。 + +每个状态写方法按统一顺序执行: + +1. 校验操作者身份、用户归属或管理权限。 +2. 锁定需要修改的记录。 +3. 校验当前状态和目标状态。 +4. 修改主记录。 +5. 追加不可变事件或更新关联记录。 +6. 提交事务后再触发任何外部操作。 + +## 8. 事务、并发与一致性 + +关键状态变更使用 `transaction.atomic()`。 + +同一 Run 的并发控制策略: + +- PostgreSQL 使用行锁串行化状态更新。 +- `lock_version` 提供乐观并发检测。 +- 事件序号在锁定 Run 后生成。 +- `(run, sequence)` 唯一约束作为最后一致性防线。 +- SQLite 测试只验证业务约束,不能代替 PostgreSQL 行锁与并发行为验证。 + +外部 SDK 或工具调用不得包裹在数据库长事务中: + +```text +事务 1:登记调用开始 → 提交 +外部调用:不持有数据库事务 +事务 2:登记成功或失败 → 更新 Run → 追加事件 → 提交 +``` + +该边界避免网络或模型调用长期占用数据库连接和行锁。 + +## 9. SDK 适配边界 + +第一阶段只建立 SDK 适配协议和测试替身: + +- `AgentExecutionRequest`:本地运行编号、用户编号、版本和预算摘要。 +- `AgentExecutionResult`:状态、输出摘要、trace 标识、用量和错误摘要。 +- `AgentRunnerGateway`:运行入口协议。 +- `StubAgentRunnerGateway`:自动化测试替身。 +- `OpenAIAgentRunnerGateway`:真实 SDK 适配位置,岗位研究逻辑留到第二阶段。 + +约束: + +- 未配置 OpenAI API Key 时,登录、资料、Admin 和运行查询仍应正常工作。 +- 只有调用真实 SDK Gateway 时才检查 API Key。 +- API Key 不进入运行上下文、数据库或日志。 +- 第一阶段不接入 Celery,也不在 Web 请求内执行长时间 Agent Run。 +- 使用测试替身验证运行开始、事件记录、成功和失败链路。 + +Celery 是目标架构中的后续运行载体;第一阶段仅稳定持久化和适配边界,两者不冲突。 + +## 10. 页面、权限与错误语义 + +建议路由: + +```text +/accounts/login/ +/accounts/logout/ +/accounts/profile/ +/runs/ +/runs// +``` + +访问规则: + +- 未登录用户访问资料和 Run 页面时跳转到登录页。 +- 普通用户查询始终限定 `owner=request.user`。 +- 跨用户访问 Run 统一返回 404,避免暴露资源存在性。 +- 资料修改使用 POST 并启用 CSRF。 +- Run 列表分页并按创建时间倒序。 +- Run 详情按事件序号展示事件和工具调用摘要。 +- 第一阶段不提供公开注册、账户自助删除或 HTTP API。 + +Admin 规则: + +- 管理员可以维护用户资料并查看全部 Agent Run。 +- 运行事件和完成后的工具调用原则上只读。 +- Admin 中展示的参数和结果同样必须脱敏。 + +领域错误分类: + +- `ResourceNotFound`:资源不存在或无权访问,页面响应为 404。 +- `ValidationError`:输入不合法,显示表单错误或返回 400。 +- `InvalidStateTransition`:运行状态冲突,语义为 409。 +- `DuplicateOperation`:重复操作或幂等冲突,语义为 409。 +- `AgentConfigurationError`:SDK 配置缺失,明确提示该功能不可用。 +- `ExternalServiceError`:外部调用失败,保存脱敏错误并将 Run 置为失败。 + +用户响应不得包含堆栈、数据库信息、完整外部响应或敏感配置。 + +## 11. 日志与审计 + +增加请求关联标识中间件: + +- 接受格式合法的入站关联标识,否则生成新标识。 +- 将关联标识加入响应头和日志上下文。 +- Agent 日志同时携带 `run_id`、`trace_id` 和 `tool_call_id`。 +- 用户相关日志只记录内部用户编号。 + +必须记录: + +- 登录失败和账户停用。 +- Run 创建及每次状态转换。 +- 工具调用开始、完成和失败。 +- 人工确认请求及处理结果。 +- 非法状态转换和跨用户访问尝试。 + +禁止记录: + +- API Key、密码和 Session Cookie。 +- 招聘网站完整 Cookie。 +- 未脱敏的工具参数。 +- 模型返回的完整敏感内容。 + +## 12. 实施顺序 + +1. 创建 `common`,实现时间戳、用户归属和领域异常。 +2. 创建 `accounts`,实现 `UserProfile`、Admin、资料服务和迁移。 +3. 增加登录、退出、资料页面和用户隔离测试。 +4. 创建 `agent_runtime` 的枚举、模型、约束和迁移。 +5. 实现 Run 状态、事件序号、工具调用和人工确认服务。 +6. 增加 Agent 运行 Admin,并限制审计记录修改。 +7. 实现 Run 列表、详情页面和访问控制。 +8. 建立 SDK Gateway、测试替身和配置边界。 +9. 增加请求关联标识、日志上下文和日志脱敏。 +10. 执行 SQLite 全量验证。 +11. 在 PostgreSQL 空库执行迁移及核心约束和并发验证。 +12. 同步 README、路线图和第一阶段状态。 + +每一步都应保持可迁移、可测试,避免在单次迁移中同时承载全部模型。 + +## 13. 测试与验收策略 + +自动化测试至少覆盖: + +- 用户资料首次创建和重复获取。 +- 未登录访问拒绝。 +- 用户只能读取和修改自己的资料。 +- 用户 A 查询用户 B 的 Run 返回 404。 +- 管理员能够查看全部运行记录。 +- 所有合法和非法状态转换。 +- 终态不可修改。 +- 并发状态更新只能有一个成功。 +- 事件序号连续且唯一。 +- 工具调用幂等键冲突。 +- 工具调用成功、失败和超时记录。 +- 人工确认只能处理一次且处理人权限正确。 +- 敏感字段不会写入日志和摘要。 +- 未配置 API Key 时非 Agent 页面正常。 +- SDK 测试替身的成功、失败和异常链路。 +- SQLite 迁移和全量测试。 +- PostgreSQL 空库迁移、唯一约束和并发验证。 + +阶段验收命令沿用开发计划: + +```powershell +python manage.py check +python manage.py check --deploy --settings=JobRadar.settings.production +python manage.py makemigrations --check --dry-run +python manage.py migrate +pytest +ruff check . +``` + +生产配置检查需要注入非秘密的验证用配置和隔离的数据库参数,不得连接生产库执行测试。 + +## 14. 兼容、迁移与回滚 + +- 新应用按 `common`、`accounts`、`agent_runtime` 的依赖顺序生成迁移。 +- 模型和约束同时兼容 SQLite 与 PostgreSQL,不使用数据库方言专属 SQL。 +- 每组迁移先在空 SQLite 数据库验证,再在空 PostgreSQL 数据库验证。 +- 第一阶段没有既有业务数据迁移,不提供破坏性数据转换。 +- 单步实现失败时回滚对应代码和未发布迁移;不得通过修改已发布迁移掩盖问题。 +- 外部 SDK 未配置或不可用时关闭真实执行入口,不影响认证、资料和运行查询。 + +## 15. 风险与待确认项 + +以下事项不阻塞第一阶段编码,但必须在对应节点前确认: + +| 事项 | 当前建议 | 最迟确认时间 | +| --- | --- | --- | +| PostgreSQL 实例位置 | 本地开发继续使用 SQLite | PostgreSQL 集成验证前 | +| OpenAI 模型与预算 | 第一阶段不固定付费模型 | 第二阶段启动前 | +| API Key 注入 | 仅使用环境变量或部署平台密钥 | 真实调用前 | +| 用户物理删除 | 默认禁止,使用停用代替 | 开放账户管理能力前 | +| 摘要最大长度 | 编码前给出统一上限 | 模型详细设计时 | +| 日志和审计保留周期 | 暂不自动清理 | 公网部署前 | +| 人工确认超时 | 第一阶段仅支持手动取消 | 第二阶段运行设计时 | + +## 16. 实现交接清单 + +编码前应进一步固化: + +- 使用数据库设计明确字段类型、索引、约束名和删除策略。 +- 如需 HTTP API,再使用 API 设计明确端点和响应契约;第一阶段默认只提供 Django 页面。 +- 确认 `common`、`accounts`、`agent_runtime` 的应用包名和 URL 命名空间。 +- 确认摘要长度、JSON 大小限制和日志脱敏词表。 +- 确认 PostgreSQL 集成验证环境。 + +推荐实现结论: + +- 使用 Django 内置用户和一对一 `UserProfile`。 +- 使用 `common`、`accounts`、`agent_runtime` 三个应用。 +- 由服务层统一权限检查、状态转换和审计写入。 +- 运行事件采用不可变追加模式。 +- 数据库事务与外部调用分离。 +- SDK 使用 Gateway 和测试替身建立稳定边界。 +- 第一阶段不引入 Celery、DRF、Vue 或真实模型调用。 diff --git a/.craftkit/designs/engineering-baseline/database-design.md b/.craftkit/designs/engineering-baseline/database-design.md new file mode 100644 index 0000000..e73cc7c --- /dev/null +++ b/.craftkit/designs/engineering-baseline/database-design.md @@ -0,0 +1,224 @@ +--- +reviewStatus: pending +reviewedAt: null +replacedBy: null +--- + +# 第一阶段工程基线数据库设计 + +## 1. 设计边界 + +本文档定义第一阶段用户资料和 Agent 运行审计数据的逻辑结构、约束、索引、迁移与回滚策略。开发和自动化测试使用 SQLite,集成与生产使用 PostgreSQL。 + +项目尚未确认 PostgreSQL 精确版本,因此本文档不采用版本专属字段、索引方法、表达式索引或方言 SQL。实现以 Django 6.0.8 ORM 和迁移能力表达;版本专属优化须在 PostgreSQL 版本确认后另行评审。 + +关联设计: + +- [后端落地设计](backend-design.md) +- [前端落地设计](frontend-design.md) +- [接口与页面契约设计](interface-design.md) + +## 2. 设计原则 + +- 用户私有数据必须具有明确的用户归属。 +- 状态转换由服务层执行,数据库约束负责守住唯一性和基础合法性。 +- 运行事件采用追加模式,保留审计轨迹。 +- JSON 字段只保存经过脱敏和大小限制的摘要。 +- 时间统一存储为支持时区的 Django 时间值,显示时转换到用户时区。 +- 金额、token 和耗时使用确定类型,避免浮点误差。 +- 表名、约束名和索引名在迁移中显式稳定,避免不同数据库自动命名差异影响排查。 + +## 3. 实体关系 + +```mermaid +erDiagram + AUTH_USER ||--o| USER_PROFILE : has + AUTH_USER ||--o{ AGENT_RUN : owns + AGENT_RUN ||--o{ AGENT_RUN_EVENT : contains + AGENT_RUN ||--o{ TOOL_CALL : contains + AGENT_RUN ||--o{ HUMAN_APPROVAL : requests + AUTH_USER ||--o{ HUMAN_APPROVAL : resolves +``` + +## 4. 表结构草案 + +### 4.1 `accounts_user_profile` + +| 字段 | Django 类型建议 | 空值 | 约束与说明 | +| --- | --- | --- | --- | +| `id` | `BigAutoField` | 否 | 主键 | +| `user_id` | `OneToOneField` | 否 | 唯一,关联 Django 用户 | +| `display_name` | `CharField` | 是 | 空字符串表示未设置,长度上限实现时确认 | +| `timezone` | `CharField` | 否 | 默认 `Asia/Shanghai`,必须来自受控候选 | +| `created_at` | `DateTimeField` | 否 | 创建时写入 | +| `updated_at` | `DateTimeField` | 否 | 修改时更新 | + +删除策略:用户尚无审计数据时资料可随用户删除;用户存在 Agent Run 时由运行记录的保护关系阻止用户物理删除。 + +### 4.2 `agent_runtime_agent_run` + +| 字段组 | 字段 | 类型建议 | 说明 | +| --- | --- | --- | --- | +| 标识 | `id` | `UUIDField` | 主键,避免在页面暴露连续编号 | +| 归属 | `owner_id` | `ForeignKey(PROTECT)` | 用户私有归属 | +| 展示 | `title` | `CharField` | 简短标题 | +| 状态 | `status` | `CharField` | 使用代码枚举和数据库检查约束 | +| 并发 | `lock_version` | `PositiveBigIntegerField` | 每次状态写入递增 | +| 摘要 | `input_summary` | `JSONField` | 脱敏、限长输入摘要 | +| 摘要 | `output_summary` | `JSONField` | 可空,脱敏输出摘要 | +| 版本 | `agent_name` | `CharField` | Agent 稳定标识 | +| 版本 | `agent_version` | `CharField` | 可空 | +| 版本 | `instruction_version` | `CharField` | 可空 | +| 版本 | `toolset_version` | `CharField` | 可空 | +| 模型 | `model_name` | `CharField` | 第一阶段允许为空 | +| 追踪 | `trace_id` | `CharField` | 可空,非秘密标识 | +| 时间 | `started_at` | `DateTimeField` | 可空 | +| 时间 | `finished_at` | `DateTimeField` | 可空 | +| 耗时 | `duration_ms` | `PositiveBigIntegerField` | 可空 | +| 用量 | `input_tokens` | `PositiveBigIntegerField` | 可空 | +| 用量 | `output_tokens` | `PositiveBigIntegerField` | 可空 | +| 费用 | `estimated_cost` | `DecimalField` | 可空,精度实现前确认 | +| 失败 | `error_code` | `CharField` | 可空、稳定机器码 | +| 失败 | `error_summary` | `TextField` | 可空、脱敏且限长 | +| 审计 | `created_at` | `DateTimeField` | 否 | +| 审计 | `updated_at` | `DateTimeField` | 否 | + +检查约束至少保证: + +- `status` 属于已定义枚举。 +- `lock_version >= 0`。 +- token、耗时和费用非负。 + +跨字段状态不变量仍由服务层验证,因为 SQLite 与 PostgreSQL 对复杂检查约束和时间语义的行为需要保持一致。 + +### 4.3 `agent_runtime_agent_run_event` + +| 字段 | 类型建议 | 说明 | +| --- | --- | --- | +| `id` | `BigAutoField` | 主键 | +| `run_id` | `ForeignKey(PROTECT)` | 关联 Run,审计事件不可级联丢失 | +| `sequence` | `PositiveBigIntegerField` | Run 内递增序号 | +| `event_type` | `CharField` | 事件枚举 | +| `summary` | `CharField` | 短摘要 | +| `payload_summary` | `JSONField` | 脱敏事件数据 | +| `occurred_at` | `DateTimeField` | 业务发生时间 | +| `created_at` | `DateTimeField` | 数据库记录时间 | + +唯一约束:`(run_id, sequence)`。 + +索引:`(run_id, sequence)` 唯一索引已覆盖详情页时间线读取,无需重复创建同前缀普通索引。 + +### 4.4 `agent_runtime_tool_call` + +| 字段组 | 字段 | 类型建议 | 说明 | +| --- | --- | --- | --- | +| 标识 | `id` | `BigAutoField` | 主键 | +| 归属 | `run_id` | `ForeignKey(PROTECT)` | 关联 Run | +| 调用 | `call_id` | `CharField` | Run 内调用标识 | +| 工具 | `tool_name` | `CharField` | 工具稳定名称 | +| 工具 | `tool_version` | `CharField` | 可空 | +| 幂等 | `idempotency_key` | `CharField` | 可空,作用域见约束 | +| 状态 | `status` | `CharField` | `started/succeeded/failed` | +| 摘要 | `arguments_summary` | `JSONField` | 脱敏、限长 | +| 摘要 | `result_summary` | `JSONField` | 可空、脱敏、限长 | +| 失败 | `error_code` | `CharField` | 可空 | +| 失败 | `error_summary` | `TextField` | 可空、限长 | +| 时间 | `started_at` | `DateTimeField` | 否 | +| 时间 | `finished_at` | `DateTimeField` | 可空 | +| 耗时 | `duration_ms` | `PositiveBigIntegerField` | 可空 | +| 审计 | `created_at`、`updated_at` | `DateTimeField` | 否 | + +唯一约束: + +- `(run_id, call_id)` 唯一。 +- 非空幂等键的作用域在真实工具模型确定前采用 `(run_id, idempotency_key)`,后续如需跨 Run 幂等必须重新设计业务作用域。 + +### 4.5 `agent_runtime_human_approval` + +| 字段 | 类型建议 | 说明 | +| --- | --- | --- | +| `id` | `UUIDField` | 主键 | +| `run_id` | `ForeignKey(PROTECT)` | 关联 Run | +| `request_key` | `CharField` | Run 内稳定请求键 | +| `approval_type` | `CharField` | 确认类型枚举 | +| `status` | `CharField` | 确认状态枚举 | +| `request_summary` | `JSONField` | 脱敏请求摘要 | +| `decision_summary` | `JSONField` | 可空、脱敏处理摘要 | +| `requested_at` | `DateTimeField` | 请求时间 | +| `resolved_at` | `DateTimeField` | 可空 | +| `resolved_by_id` | `ForeignKey(PROTECT)` | 可空,处理人 | +| `created_at`、`updated_at` | `DateTimeField` | 审计时间 | + +唯一约束:`(run_id, request_key)`。 + +索引:`(status, requested_at)`,支持 Admin 查询待处理确认。 + +## 5. 主要访问模式与索引 + +| 访问模式 | 过滤与排序 | 索引建议 | +| --- | --- | --- | +| 当前用户 Run 列表 | `owner_id`,`created_at DESC` | `(owner_id, created_at)` | +| 当前用户按状态查询 | `owner_id`、`status`、`created_at DESC` | 第一阶段无筛选需求,暂不新增 | +| Run 事件时间线 | `run_id`、`sequence ASC` | 唯一索引 `(run_id, sequence)` | +| Run 工具调用列表 | `run_id`、`started_at ASC` | `(run_id, started_at)` | +| 待处理人工确认 | `status`、`requested_at ASC` | `(status, requested_at)` | +| trace 定位运行 | `trace_id` | 有真实查询需求后再决定唯一性和索引 | + +索引以第一阶段真实页面和 Admin 查询为依据,不为未来假设查询提前增加索引。 + +## 6. 事务与并发 + +- Run 状态变更、状态事件和关联确认记录在同一事务内完成。 +- 更新 Run 前锁定目标记录,并校验 `lock_version`。 +- 事件序号在锁定 Run 后读取最大值并递增。 +- 唯一约束捕获遗漏的并发冲突,并转换为领域冲突错误。 +- 工具外部调用不持有数据库事务;开始和结束分别使用短事务。 +- SQLite 不提供等价的行锁验证,PostgreSQL 集成测试必须覆盖双并发更新。 + +## 7. 数据生命周期 + +- 用户资料可修改,保留最新值即可。 +- Agent Run、事件、工具调用和人工确认属于审计数据,第一阶段不提供删除入口。 +- 用户存在运行记录时不得物理删除,改为停用账户。 +- 第一阶段不自动清理日志或审计记录。 +- 后续建立保留周期时必须区分运行摘要、完整原始证据和应用日志,不能使用同一删除策略。 + +## 8. 迁移顺序 + +1. 创建 `common` 基础抽象代码,不生成抽象表。 +2. 创建 `accounts` 与 `UserProfile` 迁移。 +3. 创建 `agent_runtime` 核心表和基础约束。 +4. 单独迁移补充经验证的组合索引和检查约束。 +5. 在空 SQLite 数据库执行完整迁移。 +6. 确认 PostgreSQL 精确版本和隔离环境。 +7. 在空 PostgreSQL 数据库执行完整迁移和核心约束测试。 + +迁移发布后不得直接修改历史迁移;修正通过新迁移完成。 + +## 9. 回滚策略 + +- 第一阶段没有存量业务数据转换,回滚以撤销未发布迁移为主。 +- 已发布环境回滚前必须确认表内是否已产生审计数据。 +- 存在数据时禁止直接反向删除表,应先停止写入、导出验证数据并制定显式迁移。 +- 应用代码回滚必须与数据库迁移兼容,不能先删除旧代码仍会读取的字段。 + +## 10. 验证清单 + +- Django 迁移无遗漏。 +- 空 SQLite 数据库全量迁移和反向迁移测试。 +- 空 PostgreSQL 数据库全量迁移。 +- 用户一对一资料唯一约束。 +- Run 状态、非负数值和用户保护约束。 +- 事件序号唯一约束。 +- 工具调用编号和幂等键冲突。 +- 人工确认请求键唯一约束。 +- 两个并发状态更新只有一个成功。 +- 主要列表查询使用预期索引;数据量不足时不声称完成性能验收。 + +## 11. 待确认项 + +- PostgreSQL 精确版本、部署方式和连接池策略。 +- 摘要字段的最大序列化字节数。 +- 字符字段长度、费用精度和稳定约束名称。 +- trace 标识是否需要全局唯一。 +- PostgreSQL 版本确认后是否需要部分索引或其他专属优化。 diff --git a/.craftkit/designs/engineering-baseline/frontend-design.md b/.craftkit/designs/engineering-baseline/frontend-design.md new file mode 100644 index 0000000..63b9f2f --- /dev/null +++ b/.craftkit/designs/engineering-baseline/frontend-design.md @@ -0,0 +1,211 @@ +--- +reviewStatus: pending +reviewedAt: null +replacedBy: null +--- + +# 第一阶段工程基线前端落地设计 + +## 1. 文档定位 + +本文档定义第一阶段 Django 服务端页面的页面结构、路由、交互、状态、权限与可访问性边界。当前项目没有独立前端框架或组件库,因此本阶段使用 Django Templates、Django Forms 和少量原生 CSS,不引入 Vue、React、前端状态库或前后端分离架构。 + +关联设计: + +- [后端落地设计](backend-design.md) +- [数据库设计](database-design.md) +- [接口与页面契约设计](interface-design.md) +- [第一阶段工程基线开发计划](../../../docs/engineering-baseline/dev-plan.md) + +## 2. 用户角色与核心场景 + +### 2.1 未登录访问者 + +- 可以进入登录页。 +- 访问个人资料和运行页面时跳转登录页。 +- 不提供公开注册入口。 + +### 2.2 普通用户 + +- 登录和退出系统。 +- 查看、修改自己的基础资料。 +- 查看自己的 Agent Run 列表。 +- 查看自己的 Agent Run 详情、事件和工具调用摘要。 +- 不能查看其他用户的数据,也不能从页面执行真实 Agent 任务。 + +### 2.3 管理员 + +- 使用 Django Admin 创建和停用用户。 +- 查看全部用户资料和 Agent 运行记录。 +- 创建用于验证持久化链路的最小 Run。 +- 在 Admin 中处理待确认记录。 + +## 3. 页面与路由 + +| 页面 | 路由 | 访问角色 | 主要操作 | +| --- | --- | --- | --- | +| 登录 | `/accounts/login/` | 未登录用户 | 提交用户名和密码 | +| 退出 | `/accounts/logout/` | 已登录用户 | POST 确认退出 | +| 个人资料 | `/accounts/profile/` | 普通用户、管理员 | 查看和修改自己的资料 | +| 运行列表 | `/runs/` | 普通用户、管理员 | 查看当前用户的运行记录 | +| 运行详情 | `/runs//` | 记录所属用户、管理员 | 查看状态、事件和工具调用摘要 | +| 管理后台 | `/admin/` | 管理员 | 管理用户和审计记录 | + +登录成功默认进入运行列表;退出成功返回登录页。详情页返回列表时保留原分页参数,非法或越权的运行编号统一展示 404 页面。 + +## 4. 页面结构 + +### 4.1 全局布局 + +建立一个全局基础模板,结构包括: + +- 跳转到主要内容的无障碍链接。 +- 产品名称和主导航。 +- 当前用户标识。 +- 运行列表、个人资料和退出入口。 +- 页面级消息区域。 +- 主内容区域。 + +退出必须使用 POST 表单,不使用可被预加载或误触发的 GET 链接。 + +### 4.2 登录页 + +页面内容: + +- 用户名输入框。 +- 密码输入框。 +- 登录按钮。 +- 表单级错误和字段级错误。 + +登录失败时不区分“用户不存在”和“密码错误”,避免泄露账户信息。密码字段不得回显。 + +### 4.3 个人资料页 + +字段: + +- 用户名:只读。 +- 显示名:可编辑。 +- 时区:第一阶段使用受控选择项,默认 `Asia/Shanghai`。 + +提交成功后采用 POST/Redirect/GET,刷新页面不会重复提交。并发更新暂不提供复杂冲突合并,后端仍必须校验当前用户归属。 + +### 4.4 Agent Run 列表页 + +每条记录展示: + +- 标题或简短运行标识。 +- 当前状态。 +- Agent 名称和版本摘要。 +- 创建、开始和结束时间。 +- 运行耗时。 +- 是否存在待处理人工确认。 + +列表按创建时间倒序分页。第一阶段没有运行记录时展示明确空状态,不显示“创建任务”按钮,避免暗示真实 Agent 功能已开放。 + +### 4.5 Agent Run 详情页 + +页面分为: + +1. 运行概览:状态、标题、版本、开始和结束时间、用量摘要。 +2. 结果摘要:只展示已脱敏的结构化摘要。 +3. 运行时间线:按事件序号升序展示。 +4. 工具调用:名称、状态、耗时、参数摘要、结果或错误摘要。 +5. 人工确认:只读展示当前状态;第一阶段由 Admin 处理。 + +详情页不展示 API Key、Cookie、密码、完整工具参数或原始模型敏感内容。 + +## 5. 模板复用边界 + +建议模板层级: + +```text +templates/ +├── base.html +├── registration/ +│ └── login.html +├── accounts/ +│ └── profile.html +├── agent_runtime/ +│ ├── run_list.html +│ └── run_detail.html +└── errors/ + ├── 403.html + ├── 404.html + └── 500.html +``` + +复用片段仅在存在稳定复用时抽取: + +- 状态徽标。 +- 分页导航。 +- 表单错误摘要。 +- 时间线事件项。 + +第一阶段不建立通用组件系统,也不为了文件数量提前抽象宏或模板标签库。 + +## 6. 状态与数据所有权 + +服务端拥有所有业务和权限状态,页面不保存跨请求客户端状态。 + +| 状态类型 | 所有者 | 页面行为 | +| --- | --- | --- | +| 登录状态 | Django Session | 未登录时跳转登录 | +| 用户资料 | `accounts` 服务 | GET 回显,POST 校验和保存 | +| Run 列表与详情 | `agent_runtime` 查询服务 | 仅返回当前用户范围 | +| 分页状态 | URL 查询参数 | 可复制、可返回 | +| 表单错误 | Django Form | 同页字段级与全局提示 | +| 消息反馈 | Django Messages | 重定向后展示一次 | + +页面只接收格式化后的视图模型,不在模板中执行权限判断、状态转换或复杂数据加工。 + +## 7. 加载、空、错误和权限状态 + +- 服务端页面首屏不引入异步加载骨架。 +- 空列表显示原因和当前阶段说明。 +- 表单校验失败保留非敏感输入并聚焦错误摘要。 +- 资源不存在和跨用户访问统一显示 404。 +- 服务端异常显示通用错误页,并提供返回安全页面的入口。 +- SDK 配置缺失不能阻止登录、资料和历史 Run 查询。 +- 页面不得把后端异常堆栈直接呈现给用户。 + +## 8. 响应式和可访问性 + +- 页面以单列内容为主,在宽屏下限制最大阅读宽度。 +- Run 详情中的宽表格在窄屏改为定义列表或允许局部横向滚动。 +- 所有表单字段必须具有可关联的 `label`、帮助文本和错误描述。 +- 当前导航项提供可感知状态,不只依赖颜色区分。 +- 状态徽标同时包含文本。 +- 焦点样式不可移除,键盘可以访问所有操作。 +- 标题层级连续,每页只有一个主标题。 +- 时间使用语义化 `