From 53a42a4525019a063ceeb5692cf4d8ca81b94153 Mon Sep 17 00:00:00 2001 From: zzg Date: Sat, 22 Aug 2026 22:45:23 +0800 Subject: [PATCH 1/4] feat: add governed metal ductile damage execution pilot --- .ai-team/TASK.md | 76 +- .project-to-act/PROJECT_ACCEPTANCE.md | 15 +- .project-to-act/PROJECT_FEATURES.md | 2 + .project-to-act/PROJECT_OVERVIEW.md | 8 +- .project-to-act/PROJECT_PROGRESS.md | 14 +- .project-to-act/PROJECT_VERSIONS.md | 8 +- README.md | 4 + docs/API.md | 7 +- ...41\346\213\206\350\247\243\344\271\246.md" | 2 +- ...37\350\203\275\350\256\276\350\256\241.md" | 714 ++++++++++++++++++ docs/FUNCTION_MATRIX.md | 3 +- docs/M08_METAL_DUCTILE_DAMAGE.md | 46 ++ docs/TESTING.md | 12 +- evidence/AR005_M08_EVIDENCE.md | 69 ++ evidence/journeys/ar005-execution-report.json | 161 ++++ evidence/journeys/ar005-health-report.json | 121 +++ .../ar005-llm-scope-observed-report.json | 171 +++++ evidence/journeys/ar005-llm-scope-report.json | 66 ++ evidence/journeys/ar005-m08-ledger.sqlite3 | Bin 0 -> 90112 bytes .../ar005-manifest-create-report.json | 121 +++ .../journeys/ar005-manuscript-gap-report.json | 139 ++++ .../journeys/ar005-workflow-block-report.json | 295 ++++++++ .../.project-to-act/PROJECT_ACCEPTANCE.md | 43 ++ .../.project-to-act/PROJECT_CONFIG.json | 4 + .../.project-to-act/PROJECT_FEATURES.md | 31 + .../.project-to-act/PROJECT_OVERVIEW.md | 68 ++ .../.project-to-act/PROJECT_PROGRESS.md | 45 ++ .../.project-to-act/PROJECT_VERSIONS.md | 28 + .../00_intake/IDEA.md | 3 + .../01_scope/RESEARCH_CHARTER.md | 35 + .../02_literature/READING_QUEUE.yaml | 13 + .../02_literature/SEARCH_PROTOCOL.yaml | 22 + .../03_innovation/INNOVATION_MAP.md | 7 + .../04_execution/SYNTHETIC_FIXTURE.json | 19 + .../04_execution/WORK_PACKAGES.yaml | 55 ++ .../manifest_16b49559507942be/RESULT.json | 30 + .../RUN_MANIFEST.json | 39 + .../manifest_50ef1020804544d8/RESULT.json | 30 + .../RUN_MANIFEST.json | 39 + .../manifest_5d651346dd864227/RESULT.json | 30 + .../RUN_MANIFEST.json | 39 + .../05_writing/MANUSCRIPT_DRAFT.md | 49 ++ .../05_writing/OUTLINE.md | 25 + .../06_review/REVIEW_LOG.md | 5 + .../07_delivery/DELIVERY_CHECKLIST.md | 11 + .../PROJECT_MANIFEST.yaml | 23 + .../md-metal-ductile-damage-pilot/README.md | 12 + .../evidence/README.md | 5 + .../handoffs/README.md | 3 + .../knowledge-links/README.md | 3 + .../state/README.md | 3 + .../state/RUN_INDEX.jsonl | 4 + pyproject.toml | 2 +- src/autoresearch/__init__.py | 2 +- src/autoresearch/agents/base.py | 2 + src/autoresearch/agents/orchestrator.py | 34 +- src/autoresearch/api.py | 41 +- src/autoresearch/application.py | 36 +- src/autoresearch/cli.py | 76 ++ src/autoresearch/contracts.py | 102 +++ src/autoresearch/domain_profiles.py | 141 ++++ src/autoresearch/execution_service.py | 558 +++++++++++++- src/autoresearch/graph.py | 117 ++- src/autoresearch/storage.py | 8 + src/autoresearch/writing_service.py | 15 + tests/test_execution_service.py | 271 +++++++ 66 files changed, 4116 insertions(+), 66 deletions(-) create mode 100644 "docs/AutoResearch_\345\217\202\350\200\203\346\236\266\346\236\204\344\270\216\345\212\237\350\203\275\350\256\276\350\256\241.md" create mode 100644 docs/M08_METAL_DUCTILE_DAMAGE.md create mode 100644 evidence/AR005_M08_EVIDENCE.md create mode 100644 evidence/journeys/ar005-execution-report.json create mode 100644 evidence/journeys/ar005-health-report.json create mode 100644 evidence/journeys/ar005-llm-scope-observed-report.json create mode 100644 evidence/journeys/ar005-llm-scope-report.json create mode 100644 evidence/journeys/ar005-m08-ledger.sqlite3 create mode 100644 evidence/journeys/ar005-manifest-create-report.json create mode 100644 evidence/journeys/ar005-manuscript-gap-report.json create mode 100644 evidence/journeys/ar005-workflow-block-report.json create mode 100644 paper-projects/md-metal-ductile-damage-pilot/.project-to-act/PROJECT_ACCEPTANCE.md create mode 100644 paper-projects/md-metal-ductile-damage-pilot/.project-to-act/PROJECT_CONFIG.json create mode 100644 paper-projects/md-metal-ductile-damage-pilot/.project-to-act/PROJECT_FEATURES.md create mode 100644 paper-projects/md-metal-ductile-damage-pilot/.project-to-act/PROJECT_OVERVIEW.md create mode 100644 paper-projects/md-metal-ductile-damage-pilot/.project-to-act/PROJECT_PROGRESS.md create mode 100644 paper-projects/md-metal-ductile-damage-pilot/.project-to-act/PROJECT_VERSIONS.md create mode 100644 paper-projects/md-metal-ductile-damage-pilot/00_intake/IDEA.md create mode 100644 paper-projects/md-metal-ductile-damage-pilot/01_scope/RESEARCH_CHARTER.md create mode 100644 paper-projects/md-metal-ductile-damage-pilot/02_literature/READING_QUEUE.yaml create mode 100644 paper-projects/md-metal-ductile-damage-pilot/02_literature/SEARCH_PROTOCOL.yaml create mode 100644 paper-projects/md-metal-ductile-damage-pilot/03_innovation/INNOVATION_MAP.md create mode 100644 paper-projects/md-metal-ductile-damage-pilot/04_execution/SYNTHETIC_FIXTURE.json create mode 100644 paper-projects/md-metal-ductile-damage-pilot/04_execution/WORK_PACKAGES.yaml create mode 100644 paper-projects/md-metal-ductile-damage-pilot/04_execution/runs/manifest_16b49559507942be/RESULT.json create mode 100644 paper-projects/md-metal-ductile-damage-pilot/04_execution/runs/manifest_16b49559507942be/RUN_MANIFEST.json create mode 100644 paper-projects/md-metal-ductile-damage-pilot/04_execution/runs/manifest_50ef1020804544d8/RESULT.json create mode 100644 paper-projects/md-metal-ductile-damage-pilot/04_execution/runs/manifest_50ef1020804544d8/RUN_MANIFEST.json create mode 100644 paper-projects/md-metal-ductile-damage-pilot/04_execution/runs/manifest_5d651346dd864227/RESULT.json create mode 100644 paper-projects/md-metal-ductile-damage-pilot/04_execution/runs/manifest_5d651346dd864227/RUN_MANIFEST.json create mode 100644 paper-projects/md-metal-ductile-damage-pilot/05_writing/MANUSCRIPT_DRAFT.md create mode 100644 paper-projects/md-metal-ductile-damage-pilot/05_writing/OUTLINE.md create mode 100644 paper-projects/md-metal-ductile-damage-pilot/06_review/REVIEW_LOG.md create mode 100644 paper-projects/md-metal-ductile-damage-pilot/07_delivery/DELIVERY_CHECKLIST.md create mode 100644 paper-projects/md-metal-ductile-damage-pilot/PROJECT_MANIFEST.yaml create mode 100644 paper-projects/md-metal-ductile-damage-pilot/README.md create mode 100644 paper-projects/md-metal-ductile-damage-pilot/evidence/README.md create mode 100644 paper-projects/md-metal-ductile-damage-pilot/handoffs/README.md create mode 100644 paper-projects/md-metal-ductile-damage-pilot/knowledge-links/README.md create mode 100644 paper-projects/md-metal-ductile-damage-pilot/state/README.md create mode 100644 paper-projects/md-metal-ductile-damage-pilot/state/RUN_INDEX.jsonl create mode 100644 src/autoresearch/domain_profiles.py create mode 100644 tests/test_execution_service.py diff --git a/.ai-team/TASK.md b/.ai-team/TASK.md index 8982ec2..3fa67a3 100644 --- a/.ai-team/TASK.md +++ b/.ai-team/TASK.md @@ -1,61 +1,77 @@ # Current Task -- ID: `AR-003` -- Title: `Simplify the assignment plan to module level` -- Status: `done` -- Owner: `zzg` +- ID: `AR-005` +- Title: `Implement the M08 metal ductile-damage research pilot vertical slice` +- Status: `handoff` +- Owner: `Codex` - Next owner: `user/team` ## Goal -根据用户反馈,把任务拆解入口从 122 个子任务收敛为模块级分配。保留此前详细版作为附录,但当前团队先按模块认领负责人、边界、主要产出、依赖和验收目标。 +在完整保留 AR-004 参考架构文档的基础上,优先补齐 M08 工作执行与结果闭环,并用“分子动力学中的金属韧性损伤机理分析”建立可运行的领域纵向切片。该纵切必须贯通受管 LLM 研究范围深化、论文证据、领域工作包、RunManifest、受控分析执行、artifact lineage、结果门禁、缺口稿件、审核打回和项目文件投影。 ## Acceptance scenarios -- [x] `docs/AutoResearch_任务拆解书.md` 只作为模块级分配入口,覆盖 M00–M17。 -- [x] 每个模块说明目标、边界、主要交付物、前置模块、负责人建议和当前状态。 -- [x] 文档提供模块负责人验收模板和第一批模块分配建议。 -- [x] 原 122 子任务版本保留为 `docs/AutoResearch_任务拆解书_详细版.md`,不丢失后续拆解素材。 -- [x] README、Project-to-Act 和 VibeCollab 任务状态保持一致,不改变五业务 Agent 和证据门禁边界。 +- [x] 当前分支完整包含 AR-004 的参考架构文档、README 入口和本任务代码/台账更新。 +- [x] 业务 Agent registry 仍恰好包含 `orchestrator`、`paper_search`、`paper_reader`、`writer`、`reviewer`;执行器是确定性服务节点,不新增业务 Agent。 +- [x] 金属韧性损伤领域 Profile 能生成可证伪问题、变量、停止条件和领域工作包,且未知项不会被写成事实。 +- [x] `RunManifest` 记录输入校验、数据分类、分析 Profile、参数、环境、seed、状态、输出 hash 和 lineage;相同幂等键不会重复执行。 +- [x] 内置金属损伤后处理器能对受控应力-应变/缺陷序列生成可复核指标和 JSON artifact;无效数据、伪造完成状态和 synthetic fixture 的稿件放行均 fail closed。 +- [x] API 与 CLI 使用同一应用服务,可创建/读取 manifest、执行/读取 run,并可从主 workflow 消费合格结果证据。 +- [x] 用真实本地 HTTP listener 跑通正常计算、无输入阻断和 synthetic 不可作为论文真实结果的客户旅程,报告进入 append-only evidence ledger。 +- [x] 使用 `llm-api-config` 的受管配置实际调用 LLM,对指定选题返回结构化研究范围;失败时明确记录 blocked/inconclusive,不用离线结果冒充外部成功。 +- [x] Ruff、pytest warnings-as-errors、doctor、Project-to-Act validate、repo-task-sync check 全部通过并记录新鲜证据。 ## Invariants -- 当前入口不要求团队一次性拆到函数、接口或工单;模块负责人后续再自行细化。 -- 详细版是参考材料,不代表所有子任务都已实现或已验收。 -- 真实论文试点、生产数据库、Web UI、灾备和性能仍保持原有“外部阻塞/待开发”状态。 -- 不提交 token、密钥、私有来源、完整论文、原始提示或虚构外部结果。 +- 保持恰好五个业务 Agent;证据、门禁、执行、状态、知识、画像和项目管理均为确定性服务或节点。 +- Gate 决定、状态转换、证据有效性、数据分类和人工批准不能由 LLM 覆盖。 +- `synthetic_fixture` 只证明软件链路和计算逻辑,不得写成真实 MD 模拟或真实金属损伤机理结论。 +- 只有带输入 checksum、运行状态、输出 checksum 和验证记录的执行结果才能成为实验证据;计划或失败输出永远不是结果。 +- 不运行任意用户 shell 命令,不自动修改/合并代码,不启动无限优化循环,不外发或发布稿件。 +- 不提交密钥、`.env.local`、原始提示、模型原始响应、完整论文或未经许可数据。 ## Decisions -- 使用 M00–M17 作为稳定模块 ID,先按模块分配负责人。 -- 保留详细拆解版,避免未来重新整理时丢失依赖和验收素材。 -- `.project-to-act` 继续作为长期项目事实源;`.ai-team/TASK.md` 只记录本次短周期交付。 +- AR-005 从当前 AR-004 未提交工作区切出,作为包含参考架构、实现、项目账本和测试证据的完整分支。 +- 当前实现聚焦 M08 与领域 Pilot 纵切,不把 PostgreSQL/pgvector/Neo4j、多租户 Web UI 或真实 LAMMPS 大规模模拟虚报为本分支完成。 +- 领域执行采用固定注册表中的确定性后处理器;输入为轨迹衍生的结构化观测,不开放任意命令执行。 +- 真实 LLM 用于研究范围和检索计划深化,科研结果与 Gate 仍由证据和确定性服务控制。 ## Completed -- 将原 `docs/AutoResearch_任务拆解书.md` 移为 `docs/AutoResearch_任务拆解书_详细版.md`。 -- 新建模块级 `docs/AutoResearch_任务拆解书.md`,包含 18 个模块、执行阶段、负责人模板和首批分配建议。 -- 保留 README 任务书入口,并将 AR-003 的状态、范围和下一步写入协作账本。 +- 已恢复 0.1.0 Foundation、M00–M17 计划和 Project-to-Act 事实源。 +- 已核对 AR-004 参考架构、当前未提交文件和 `origin/main` 一致基线。 +- 已创建 `codex/ar-005-md-ductile-damage-pilot`,完整携带当前工作区。 +- 已实现 `metal_ductile_damage_v1`、领域工作包、RunManifest、deterministic 后处理、artifact lineage、E1/E2 分类、幂等和失败不产证据。 +- 已把 M08 服务节点接入 LangGraph,并把大输入保存在 execution store、state 只携带稳定 ID。 +- 已扩展 execution/manuscript API 与 register/execute/status CLI,版本升级为 `0.2.0.dev0`。 +- 已实例化 `paper-projects/md-metal-ductile-damage-pilot`,完整维护独立 Project-to-Act、fixture、run artifact、缺口稿件和运行索引。 +- 已用受管 GLM/DeepSeek 和真实 loopback listener 测试;GLM/provider timeout 与 AAWO 15 秒 timeout 保留为非通过证据,DeepSeek observed run 与 6 条 HTTP 旅程通过。 +- 已完成 25 tests、84% coverage、Ruff、doctor、CLI、YAML、双账本和协作校验。 ## Pending -- 用户/团队需要为模块负责人模板填写实际 owner、分支、目标日期和 evidence ID。 -- 模块负责人确认后,再选择需要展开的模块,不默认展开全部模块。 -- 真实论文试点仍等待 idea、领域、目标 venue、授权全文和实验资源边界。 +- AR-005 软件分支无剩余实现项。 +- 项目级后续仍需用户/科研负责人提供许可全文、目标材料/晶向/势函数、真实 MD 数据和独立复验;这些外部输入不属于本分支可伪造完成的范围。 +- PostgreSQL/队列/容器 executor/Web UI/生产灾备仍按 M17 推进,不属于 AR-005。 ## Next step -先认领 M01–M04、M05–M07、M08、M09–M10、M11–M12、M13–M15/M17、M16 这 7 个模块包;完成模块边界确认后,再只对当前瓶颈模块做子任务拆解。 +由用户/团队审阅并合并本完整分支;随后以 Pilot 账本的下一步导入许可全文和带 checksum 的真实 `md_trajectory_summary`。 ## Verification -- [x] Project-to-Act `--check` — managed 配置有效,唯一账本保持不变。 -- [x] 模块文档结构检查 — M00–M17 全部存在,详细版文件可访问。 -- [x] `node .ai-team/check.mjs --base main` — AR-003 acceptance 5/5,任务范围和变更同步有效。 -- [x] Project-to-Act `--validate` — managed 配置 valid,issues 为空。 +- [x] `.venv\Scripts\python.exe -m ruff check src tests` +- [x] `.venv\Scripts\python.exe -m pytest -W error -q` +- [x] `.venv\Scripts\autoresearch.exe doctor` +- [x] Project-to-Act 根与 Pilot `--validate` +- [x] 真实 HTTP 客户旅程报告状态核验与 evidence ledger 检查 +- [x] 受管 LLM 真实结构化调用(只记录脱敏元数据、状态和 artifact hash) +- [x] `node .ai-team/check.mjs --base main` ## Handoff note -- From: `zzg` +- From: `Codex` - To: `user/team` -- Summary: 当前分配入口已经降为模块级;详细拆解保留在附录。先分模块负责人,后续只对真正需要的模块继续细化。 +- Summary: AR-005 完整分支已收口;M08 软件纵切、真实 LLM/HTTP 证据和实例化 Pilot 均在分支内。synthetic fixture 与题录/摘要只验证软件路径,真实论文/MD/生产 Gate 仍保持未通过。 diff --git a/.project-to-act/PROJECT_ACCEPTANCE.md b/.project-to-act/PROJECT_ACCEPTANCE.md index fb74271..3c3bf6b 100644 --- a/.project-to-act/PROJECT_ACCEPTANCE.md +++ b/.project-to-act/PROJECT_ACCEPTANCE.md @@ -5,10 +5,10 @@ ## 当前验收结论 -- 结论:0.1.0 Foundation Preview 基础验收通过;生产级 P1–P6 与真实论文项目仍未验收 -- 验收范围:严格五 Agent 本地架构、基础纵向工作流、CLI/API、证据门禁、项目文件夹投影和隔离客户旅程 -- 最后检查:2026-08-22(foundation 收口检查) -- 遗留问题:真实论文 idea/gold corpus/实验、生产数据库、重启幂等、备份恢复、Web/认证和真实投稿均未验收 +- 结论:0.1.0 Foundation 与 AR-005 M08 软件纵切验收通过;生产级 P1–P6 与真实论文/MD 项目仍未验收 +- 验收范围:严格五 Agent 本地架构、M08 RunManifest/执行 lineage、CLI/API、证据门禁、实例化项目和隔离客户旅程 +- 最后检查:2026-08-22(AR-005 收口检查) +- 遗留问题:许可全文/gold corpus/真实 MD/独立复验、生产数据库、备份恢复、Web/认证和真实投稿均未验收 ## 验收标准 @@ -57,7 +57,7 @@ | A-RUN-007 | 论文搜索多源、去重、版本、撤稿/更正和检索日志完整 | 待检查 | gold queries + 手工标注集 | 无 | | A-RUN-008 | 论文读取支持全文 locator、总结、陪读、claim map 和创新候选 | 待检查 | 固定论文集人工盲审 | 无 | | A-RUN-009 | 创新候选完成最近似工作和反向检索,不把 UNKNOWN 写成创新 | 待检查 | 已知重复 idea 和反例回归 | 无 | -| A-RUN-010 | 工作包与 RunManifest 可复核,未执行计划不能变成结果 | 待检查 | fake-run/adversarial + 实际运行测试 | 无 | +| A-RUN-010 | 工作包与 RunManifest 可复核,未执行计划不能变成结果 | 已通过 | fake-run/adversarial + 实际确定性运行测试 | E-M08-001、E-M08-LIVE-001 | | A-RUN-011 | 写作只消费已放行 claim,关键事实/数字/结果证据绑定 100% | 待检查 | manuscript claim-citation audit | 无 | | A-RUN-012 | 审核可定向打回并在修订后复验,无自批或改证据放行 | 待检查 | reviewer/gate 权限测试 | 无 | | A-RUN-013 | paper/experience/knowledge/user/project/evidence 分区与 ACL 生效 | 待检查 | 跨分区污染和权限负例 | 无 | @@ -82,6 +82,8 @@ | E-LIVE-001 | 2026-08-22 | OpenAlex/Crossref/S2 limit=1;受管 DeepSeek JSON connectivity | 部分外部源受限 | OpenAlex/Crossref 成功;S2 HTTPStatusError;LLM call 成功 | 只证明当时边界;不证明检索/生成质量 | `evidence/FOUNDATION_EVIDENCE.md` | 7 天或外部配置变化前 | | E-PROJECT-001 | 2026-08-22 | 项目实例化/防覆盖/终态投影测试 | 0 | 同 E-RUN-001 | 子项目 manifest、RUN_INDEX、进度 runtime 区块与稿件路径一致 | `evidence/FOUNDATION_EVIDENCE.md` | 模板或投影代码变化前 | | E-COLLAB-001 | 2026-08-22 | VibeCollab check;根/模板 Project-to-Act validate | 0 | AR-001 9/9;两账本 zero issues | 当前 Git 任务、代码、验收和长期账本一致;private sessions disabled | `.ai-team/TASK.md`、`evidence/FOUNDATION_EVIDENCE.md` | TASK 或账本变化前 | +| E-M08-001 | 2026-08-22 | Ruff;25 tests warnings-as-errors;84% coverage;doctor;CLI register/execute/status;双账本/YAML 校验 | 0 | `0.2.0.dev0`;收口提交后以 Git commit 固化 | 金属韧性损伤 Profile、RunManifest、幂等、hash、失败不产证据、E1/E2 和 workflow Gate 通过 | `evidence/AR005_M08_EVIDENCE.md`、`tests/test_execution_service.py` | 代码/fixture 变化前 | +| E-M08-LIVE-001 | 2026-08-22 | 受管 GLM/DeepSeek;127.0.0.1:8137;AAWO HTTP journeys | GLM/provider timeout 和 AAWO 15s timeout 如实保留;6 pass | run `run_c3357978832d4b77`、`run_b33c1d0b69564e6f`;报告 hash 见证据文件 | DeepSeek scope 成功;synthetic 执行 completed 但稿件 blocked | `evidence/journeys/ar005-*`、`evidence/AR005_M08_EVIDENCE.md` | 7 天或模型/HTTP 合同变化前 | ## Gate 记录 @@ -89,10 +91,13 @@ |---|---|---|---|---|---|---| | G-P0-001 | 2026-08-21 | P0 规划交付 | 计划书、功能表、根账本、论文项目模板 | PASS | E-PLAN-001、E-TPL-001 | 无豁免 | | G-FND-001 | 2026-08-22 | Foundation Preview 本地交付 | 源码、文档、测试、CLI/API、本地客户旅程 | PASS | E-RUN-001、E-JOURNEY-001、E-LIVE-001、E-PROJECT-001 | 无豁免;真实论文与生产 Gate 保持未通过 | +| G-M08-SW-001 | 2026-08-22 | M08 软件纵切 | 领域 Profile、RunManifest、deterministic execution、artifact lineage、synthetic Gate | PASS | E-M08-001、E-M08-LIVE-001 | 无豁免;真实 MD/论文/生产 Gate 保持未通过 | ## 验收记录 按时间倒序追加:日期、检查范围、证据 ID、结果、遗留问题和结论。失败、跳过与过期证据也必须如实记录。 +- 2026-08-22|AR-005 M08 软件纵切:25 tests、84% coverage、doctor、CLI、双账本、真实 DeepSeek scope、AAWO 6 pass/1 timeout inconclusive、真实 OpenAlex 题录+synthetic 完整 run 和稿件缺口|E-M08-001、E-M08-LIVE-001|G-M08-SW-001 PASS|遗留:无许可全文/真实 MD/独立复验/生产 executor|结论:A-RUN-010 软件合同通过;A-RUN-018 真实论文试点仍未通过。 + - 2026-08-22|0.1.0 foundation 收口:18 tests、84% coverage、warnings-as-errors、doctor、四个真实 HTTP 客户旅程、OpenAlex/Crossref 最小 live、S2 受限诊断、受管 LLM 最小 connectivity、子项目运行投影与完整文档|E-RUN-001、E-JOURNEY-001、E-LIVE-001、E-PROJECT-001|G-FND-001 PASS|遗留:严格产品 A-RUN-002–020 多数仍需生产级/真实论文证据|结论:基础架构和功能纵向切片可运行,不可声明真实论文或生产交付完成。 - 2026-08-21|P0 收口:计划书、86 项功能表、根/模板 Project-to-Act、模板结构、相对链接、字段、密钥样式和现有底座核验|E-BASE-001、E-WEB-001、E-PLAN-001、E-TPL-001|P0 PASS|遗留:产品运行时全部阶段尚未实现;真实论文试点未开始|结论:规划交付完成,项目产品不可声明完成。 diff --git a/.project-to-act/PROJECT_FEATURES.md b/.project-to-act/PROJECT_FEATURES.md index 549123e..336a47b 100644 --- a/.project-to-act/PROJECT_FEATURES.md +++ b/.project-to-act/PROJECT_FEATURES.md @@ -144,5 +144,7 @@ 按时间倒序追加:日期、功能 ID、变化、原因、影响、证据 ID 和确认来源。 +- 2026-08-22|M08 金属韧性损伤纵切|F-EX-002/003/004 继续保持“进行中”,但已增加可运行的 RunManifest、固定 Profile、输入/来源/输出 hash、幂等、失败记录、E1/E2 分类和 synthetic 稿件阻断;未因软件 fixture 将真实实验或通用生产能力标完成|证据:E-M08-001、E-M08-LIVE-001|确认来源:用户本轮指令。 + - 2026-08-22|Foundation 纵向切片|将已落地但尚未满足生产级完成条件的功能更新为“进行中”;F-WF-002(严格五 Agent 父图)和 F-EG-009(证据不足/未闭环/打回 fail-closed)有新鲜证据后标为“已完成”|原因:代码、测试、真实本地 HTTP 旅程和文档已经交付,但真实论文/gold corpus/生产数据库/灾备仍未验收|影响:下一阶段从真实文献与执行证据继续,而非重做基础骨架|证据:E-RUN-001、E-JOURNEY-001、E-LIVE-001、E-PROJECT-001|确认来源:用户本轮构建指令。 - 2026-08-21|F-GOV-001–F-QA-010|建立 AutoResearch 首版功能基线;F-GOV-001/002 已完成规划治理交付,其余产品功能为已规划、未实现|原因:严格落实用户给定八个研究环节、五 Agent、证据/门禁/状态/交接/工作流/自进化/知识库/画像/Project-to-Act|影响:后续范围与状态以本文件为准|证据:E-PLAN-001、E-TPL-001|确认来源:用户本轮指令。 diff --git a/.project-to-act/PROJECT_OVERVIEW.md b/.project-to-act/PROJECT_OVERVIEW.md index 112871d..0076d69 100644 --- a/.project-to-act/PROJECT_OVERVIEW.md +++ b/.project-to-act/PROJECT_OVERVIEW.md @@ -10,7 +10,7 @@ - 项目负责人:用户/项目负责人(具体姓名不在账本保存) - 风险等级:T4 / 高(长周期多 Agent、外部论文源、实验/写作与可选外部动作) - 当前阶段:0.1.0 Foundation Preview 已验收,P1–P5 生产级条件进行中 -- 当前状态:严格五 Agent 本地运行时、CLI/API、基础纵向科研链和论文项目进度投影已交付;真实论文试点与生产部署未完成 +- 当前状态:严格五 Agent 本地运行时与 M08 金属韧性损伤软件纵切已交付;真实 LLM/HTTP 边界有证据,真实论文/MD 试点与生产部署未完成 - 最后更新:2026-08-22 ## 项目目标 @@ -65,9 +65,9 @@ ## 当前焦点 -- 下一里程碑:0.2.0 Literature Pilot,使用一个真实论文 idea 和许可明确 corpus 验收检索/阅读/执行证据 -- 当前工作重点:补真实 gold set、RunManifest、进程重启幂等和生产持久化差距;不重做 foundation 骨架 -- 主要阻塞:尚无具体论文 idea、目标 venue、许可全文和实验资源边界;不阻塞基础代码维护,阻塞真实 P2/P3/P4 验收 +- 下一里程碑:0.2.0 Literature Pilot,以“分子动力学中的金属韧性损伤机理分析”为首个领域纵切 +- 当前工作重点:AR-005 优先补 M08 RunManifest、确定性损伤后处理、artifact lineage、幂等和结果门禁;不重做 foundation 骨架 +- 主要阻塞:选题已明确,但尚无许可全文、真实 LAMMPS/MD 输出、目标 venue 和实验资源边界;不阻塞软件纵切,阻塞真实科研结论验收 ## 按需读取索引 diff --git a/.project-to-act/PROJECT_PROGRESS.md b/.project-to-act/PROJECT_PROGRESS.md index a640d26..d390510 100644 --- a/.project-to-act/PROJECT_PROGRESS.md +++ b/.project-to-act/PROJECT_PROGRESS.md @@ -13,7 +13,7 @@ | P1-01 状态/交接/证据/Gate 合同 | 进行中 | Codex | 基础 schema、单元测试和 fail-closed 已通过;历史迁移待补 | E-RUN-001 | 2026-08-22 | | P1-02 最小 LangGraph 父图与五子图 | 进行中 | Codex | SQLite checkpoint/interrupt/resume 已通过;进程重启、分布式幂等待补 | E-RUN-001、E-JOURNEY-001 | 2026-08-22 | | P2 论文搜索与读取纵向切片 | 进行中 | Codex | 三连接器、题录、阅读卡、locator、陪读已落地;gold corpus 和人工质量验收待补 | E-RUN-001、E-LIVE-001 | 2026-08-22 | -| P3 创新挖掘与工作执行 | 进行中 | Codex | hypothesis、反证条件和证据化 WorkPackage 已落地;RunManifest/真实实验待补 | E-RUN-001 | 2026-08-22 | +| P3 创新挖掘与工作执行 | 进行中 | Codex | AR-005 已补 RunManifest、确定性金属损伤后处理、artifact lineage、幂等和 synthetic 结果门禁;真实 MD 待补 | E-RUN-001、E-M08-001、E-M08-LIVE-001 | 2026-08-22 | | P4 写作与审核 | 进行中 | Codex | 缺口稿、版本化修改/润色、L3 审核打回已通过;真实 claim audit 待补 | E-RUN-001、E-JOURNEY-001 | 2026-08-22 | | P5 Wiki+Graph、画像与自进化 | 进行中 | Codex | 分区 Wiki/一跳图、画像、经验晋级和 proposal-only 已落地;向量/灰度/回滚待补 | E-RUN-001 | 2026-08-22 | | P6 真实端到端试点与 v1.0 | 已规划 | 待指定 | `PROJECT_ACCEPTANCE.md` 全部运行时标准通过 | 无 | 未开始 | @@ -22,21 +22,25 @@ | 阻塞 | 影响 | 解除条件 | 状态 | |---|---|---|---| -| 尚未提供首个真实论文 idea | 无法建立 gold query、真实阅读集与端到端论文试点 | 用户在 P2 前提供 idea、领域、目标和资源边界 | 不阻塞 P0/P1,阻塞 P2 验收 | +| 首个 idea 已确定但真实 corpus/MD 输出未提供 | 可建立领域 Profile 和软件纵切,但不能完成真实文献盲审或声明真实金属损伤机理结论 | 为该 idea 提供许可全文和带来源校验的 LAMMPS/MD 轨迹衍生数据 | 不阻塞 AR-005 软件实现;阻塞真实 M16 科研结论验收 | | embedding 配置与检索模型未选定 | 无法进行真实向量召回质量评测 | 在取得真实论文 corpus 后按安全配置流程选择并锁定版本 | 不阻塞 foundation;阻塞向量验收 | | 目标部署规模未确认 | Neo4j/对象存储/并发容量只能按默认单团队方案规划 | P1 明确本地单机、内网团队或云端部署 | 待决策 | ## 下一步 -1. 由用户提供首个真实论文 idea、领域、目标 venue、可用全文与实验资源边界。 -2. 用真实语料建立 gold query、ReadingCard/locator 人工基准和创新重复反例。 -3. 补 RunManifest、artifact lineage、checkpoint 进程重启和分布式幂等测试。 +1. 完成 AR-005 金属韧性损伤领域 Profile、RunManifest、artifact lineage 和确定性后处理纵切。 +2. 用受管 LLM 和真实本地 HTTP listener 验证该选题的范围深化、执行阻断与 synthetic 边界。 +3. 取得许可全文和真实 MD 输出后建立 gold query、ReadingCard/locator 人工基准与结果复验。 4. 在生产化前引入 PostgreSQL/pgvector 与可重建图投影,不先扩建无证据 UI。 ## 进度历史 按时间倒序追加:日期、完成事项、证据 ID、遗留问题、下一步和确认来源。不要覆盖旧记录。 +- 2026-08-22|完成 AR-005 M08 金属韧性损伤软件纵切|新增领域 Profile、RunManifest、确定性后处理、artifact lineage、执行 API/CLI、25 tests、真实受管 DeepSeek scope、6 条 pass/1 条 timeout inconclusive HTTP 旅程和实例化论文 Project-to-Act|证据:E-M08-001、E-M08-LIVE-001|遗留:许可全文、真实 MD 数据、独立复验、生产 executor 未完成|下一步:真实 `md_trajectory_summary` 与人工科研验收|确认来源:用户本轮指令。 + +- 2026-08-22|启动 AR-005 M08 金属韧性损伤研究纵切|已创建 `codex/ar-005-md-ductile-damage-pilot` 并完整携带 AR-004 参考架构工作区;确定性执行、受管 LLM 和真实 HTTP 旅程进入实现|证据:待生成 E-M08-001/E-M08-LIVE-001|遗留:真实许可 corpus 和真实 MD 输出未提供,synthetic fixture 只能验证软件链路|下一步:实现 RunManifest、后处理器和 lineage 门禁|确认来源:用户本轮指令。 + - 2026-08-22|按用户反馈将任务拆解入口收敛到模块级|模块版覆盖 M00–M17,原 122 子任务版本保留为详细附录|遗留:模块负责人和实际 evidence 尚未填写|下一步:先分配模块负责人,再只细化当前瓶颈模块|确认来源:用户本轮反馈。 - 2026-08-22|发布 public GitHub 仓库并补充 M00–M17 任务拆解书|仓库可见性、main 推送和文档结构已核验|遗留:分支保护/CI、真实论文试点和生产化仍按任务书待分配|下一步:由团队填写 owner 并启动 M00-05、M01-02、M02-07、M04-04、M16-01|确认来源:用户本轮发布与任务拆解指令。 diff --git a/.project-to-act/PROJECT_VERSIONS.md b/.project-to-act/PROJECT_VERSIONS.md index 1e2de7a..441d155 100644 --- a/.project-to-act/PROJECT_VERSIONS.md +++ b/.project-to-act/PROJECT_VERSIONS.md @@ -4,9 +4,9 @@ ## 当前版本 -- 版本号:`0.1.0` -- 版本代号:Foundation Preview -- 发布状态:本地基础架构可运行;不是生产发布,也未完成真实论文验收 +- 版本号:`0.2.0.dev0` +- 版本代号:M08 Metal Damage Software Pilot +- 发布状态:本地开发分支包含 M08 金属韧性损伤纵切;不是正式发布,也未完成真实论文验收 - 兼容性说明:已验证 Python 3.12.10、LangGraph 1.2.11、langgraph-checkpoint-sqlite 3.1.1、Pydantic 2.13.4、FastAPI 0.141.1;SQLite profile 仅承诺本地单进程基础验证。 - 最后更新:2026-08-22 @@ -33,5 +33,7 @@ 按时间倒序追加:版本号、日期、状态、主要变更、原因、兼容性、证据 ID 和 Gate 结果。 +- `0.2.0.dev0`|2026-08-22|AR-005 开发分支|金属韧性损伤领域 Profile、RunManifest、确定性后处理、artifact lineage、execution API/CLI、真实 LLM/HTTP Pilot 和实例化 Project-to-Act|原因:用户要求参考新架构继续完整功能开发并以该领域真实测试|兼容性:保留 0.1.0 五 Agent/SQLite 合同;新增可选 execution_input 与执行接口|证据:E-M08-001、E-M08-LIVE-001|Gate:软件纵切待最终收口,真实论文/MD/生产 Gate 未通过。 + - `0.1.0`|2026-08-22|Foundation Preview|严格五 Agent、LangGraph/SQLite、证据门禁、检索/阅读/创新/工作包/稿件、Wiki+Graph、画像/经验/提案、CLI/API 和项目运行投影|原因:用户要求架构和基础功能完整落地|兼容性:仅经验证的 Python 3.12 本地 profile;旧规划文档无运行时兼容承诺|证据:E-RUN-001、E-JOURNEY-001、E-LIVE-001、E-PROJECT-001|Gate:G-FND-001 PASS,P1–P6 严格 Gate 未全部通过。 - `0.1.0-plan`|2026-08-21|规划交付完成|建立规划、功能、项目模板与验收基线|原因:先固化架构和不可变约束|兼容性:仅文档,不代表运行时实现|证据:E-PLAN-001、E-TPL-001|Gate:G-P0-001 PASS。 diff --git a/README.md b/README.md index 8c38e3e..b3eceff 100644 --- a/README.md +++ b/README.md @@ -28,8 +28,10 @@ AutoResearch 是一个以证据为边界、以项目文件夹为账本的 LangGr 文档入口: +- docs/AutoResearch_参考架构与功能设计.md:整合开源优秀设计的目标架构、完整功能、来源边界与 M00–M17 映射。 - docs/ARCHITECTURE.md:总体架构与五 Agent。 - docs/FUNCTION_MATRIX.md:基础版功能表与生产边界。 +- docs/M08_METAL_DUCTILE_DAMAGE.md:RunManifest、确定性金属韧性损伤后处理、lineage 与 synthetic Gate。 - docs/WORKFLOW_AND_STATE.md:状态机、handoff、checkpoint/resume。 - docs/EVIDENCE_AND_GATES.md:证据等级与 L0–L4 门禁。 - docs/KNOWLEDGE_AND_EVOLUTION.md:Wiki+Graph、画像、经验与自进化。 @@ -39,3 +41,5 @@ AutoResearch 是一个以证据为边界、以项目文件夹为账本的 LangGr - docs/AutoResearch_任务拆解书_详细版.md:需要继续细化时使用的历史详细拆解附录。 项目治理见 .project-to-act,当前协作任务见 .ai-team/TASK.md。 + +AR-005 实例化 Pilot 位于 `paper-projects/md-metal-ductile-damage-pilot/`。它包含真实受管 LLM/HTTP 测试证据和明确标注的 synthetic fixture;当前稿件是被 Reviewer 阻断的缺口稿,不是可发布科研结论。 diff --git a/docs/API.md b/docs/API.md index 31ac246..b00c7eb 100644 --- a/docs/API.md +++ b/docs/API.md @@ -51,8 +51,12 @@ POST /runs/{id}/resume: | POST | /evidence/{evidence_id}/invalidate | 追加失效状态 | | GET | /projects/{project_id}/work-packages | 列出工作包 | | PATCH | /work-packages/{work_package_id} | 更新 planned/in_progress/blocked/completed | +| POST | /execution/manifests | 注册确定性执行 manifest | +| GET | /projects/{project_id}/execution/manifests | 列出项目 manifest | +| POST | /execution/manifests/{manifest_id}/execute | 执行已注册 Profile | +| GET | /execution/runs/{execution_id} | 读取执行记录与 artifact lineage | -工作包标记 completed 时必须提供至少一条同项目、有效的 evidence_id,否则返回 409。 +工作包标记 completed 时必须提供至少一条同项目、有效的 evidence_id,否则返回 409。M08 金属韧性损伤合同、数据分类和 checksum 规则见 `M08_METAL_DUCTILE_DAMAGE.md`。 ## 论文阅读与写作 @@ -60,6 +64,7 @@ POST /runs/{id}/resume: |---|---|---| | POST | /papers/{paper_id}/questions | 证据限定的陪读问答 | | POST | /manuscripts/{manuscript_id}/revisions | 生成修改或润色版本 | +| GET | /manuscripts/{manuscript_id} | 读取稿件、证据、缺口和 release 状态 | 阅读材料不足时返回 unresolved=true,不生成猜测答案。修订不会覆盖原稿,继承 evidence IDs 和 unresolved gaps,并强制 release_ready=false。 diff --git "a/docs/AutoResearch_\344\273\273\345\212\241\346\213\206\350\247\243\344\271\246.md" "b/docs/AutoResearch_\344\273\273\345\212\241\346\213\206\350\247\243\344\271\246.md" index 6b5f4db..645dbf8 100644 --- "a/docs/AutoResearch_\344\273\273\345\212\241\346\213\206\350\247\243\344\271\246.md" +++ "b/docs/AutoResearch_\344\273\273\345\212\241\346\213\206\350\247\243\344\271\246.md" @@ -35,7 +35,7 @@ | M05 | 论文搜索 Agent | 从研究问题得到合规、可追溯的论文候选集 | 检索式、多源连接器、去重、筛选、许可诊断 | M02,M03 | 检索工程师 | 部分落地 | | M06 | 论文读取 Agent | 把授权全文变成可定位、可追问的阅读证据 | 全文解析、ReadingCard、locator、陪读、claim map | M05 | 论文/科研工程师 | 部分落地 | | M07 | 创新与相关工作 | 从论文证据中形成可证伪的创新候选 | 近邻分析、差异特征、缺口、反证条件、创新 Gate | M06,M02 | 论文/科研工程师 | 部分落地 | -| M08 | 工作执行与结果 | 把创新候选落实为可审计的实验或分析工作 | WorkPackage、RunManifest、执行器、artifact lineage、结果验证 | M07,M04,M02 | 实验工程师 | 待开发 | +| M08 | 工作执行与结果 | 把创新候选落实为可审计的实验或分析工作 | WorkPackage、RunManifest、执行器、artifact lineage、结果验证 | M07,M04,M02 | 实验工程师 | 部分落地(金属损伤纵切) | | M09 | 写作 Agent | 根据证据和结果生成、修改、润色论文 | 提纲、缺口稿、结果稿、引用、版本 diff、格式导出 | M06,M07,M08 | 写作工程师 | 部分落地 | | M10 | 审核 Agent | 对证据、方法、创新、结果和文稿进行独立审核 | 审核报告、问题等级、定向打回、复验记录 | M02,M07,M08,M09 | 审核/质量工程师 | 部分落地 | | M11 | Wiki+Graph 知识库 | 将论文、证据、知识、经验和项目事实分区存储并检索 | 六分区 Wiki、属性图、分级召回、provenance | M02,M04,M06 | KB/检索工程师 | 部分落地 | diff --git "a/docs/AutoResearch_\345\217\202\350\200\203\346\236\266\346\236\204\344\270\216\345\212\237\350\203\275\350\256\276\350\256\241.md" "b/docs/AutoResearch_\345\217\202\350\200\203\346\236\266\346\236\204\344\270\216\345\212\237\350\203\275\350\256\276\350\256\241.md" new file mode 100644 index 0000000..356f417 --- /dev/null +++ "b/docs/AutoResearch_\345\217\202\350\200\203\346\236\266\346\236\204\344\270\216\345\212\237\350\203\275\350\256\276\350\256\241.md" @@ -0,0 +1,714 @@ +# AutoResearch 参考架构与功能设计 + +> 文档性质:开源方案优秀设计整合基线 +> 适用项目:AutoResearch +> 核验快照:2026-08-22 +> 功能状态事实源:[`../.project-to-act/PROJECT_FEATURES.md`](../.project-to-act/PROJECT_FEATURES.md) 与 [`FUNCTION_MATRIX.md`](FUNCTION_MATRIX.md) +> 实施分配事实源:[`AutoResearch_任务拆解书.md`](AutoResearch_任务拆解书.md) + +## 1. 文档目的 + +本文档把多个开源 Research Agent 的优秀设计整合为一套统一的 AutoResearch 目标架构和功能规划。它回答四个问题: + +1. AutoResearch 应由哪些层、Agent、确定性服务和数据对象组成; +2. 从研究 idea 到本地可复核稿件,信息、证据、实验结果和人工决定如何流动; +3. 哪些开源设计应吸收、如何适配现有五 Agent 和证据门禁边界; +4. 当前 0.1.0 Foundation、后续目标能力、真实试点和生产化之间如何区分。 + +本文档是架构与功能参考,不替代实际功能账本,也不表示文中所有目标能力已经实现。任何“已实现”结论均以当前代码、测试和 `FUNCTION_MATRIX.md` 为准。 + +## 2. 来源范围与排除边界 + +### 2.1 主体吸收的完整科研方案 + +| 方案 | 主要吸收设计 | 在 AutoResearch 中的落点 | +|---|---|---| +| AutoResearchClaw | 显式科研阶段、阶段输入输出合同、回退、人工协作、质量与引用检查、领域执行 Profile | M01、M03、M05–M10、M13 | +| Open Deep Research | 父图/研究子图、动态并行检索、研究 brief、网页压缩、不同阶段模型配置、MCP 工具扩展 | M03、M05、M13–M15 | +| PaperQA2 | 科学文献索引、元数据连接器、全文解析、定位证据、引文 ID、撤稿和期刊质量信息 | M05、M06、M11、M14 | +| GPT Researcher | 多检索器、Web/本地/hybrid 来源、流式进度、报告导出、API/UI、模型和搜索配置 | M05、M09、M13–M15 | +| AI Scientist-v2 | 假设风险、实验阶段、基线/消融、图表与论文复核、多轮稿件生成 | M07–M10;只吸收设计,不直接复制受限许可代码 | +| Agent Laboratory | 阶段协作、checkpoint、逐阶段人类反馈、评审后返回实验、累计研究资产 | M03、M08–M10、M12–M13 | +| Tongyi DeepResearch / WebWeaver | 长程信息搜索、动态提纲、证据 Memory Bank、按章节定向召回、文件/视觉工具 | M05、M06、M09、M11、M13 | + +### 2.2 只吸收通用执行基础设施的方案 + +| 方案 | 保留 | 排除 | +|---|---|---| +| Arbor | Idea Tree 表达研究分支;隔离 worktree;开发信号与 held-out 复验分离;运行恢复和经验摘要 | 自主无限搜索、单指标最优分支、超时自动继续、Coordinator/Executor 替换现有五 Agent | +| CORAL | public/private 数据隔离;detached workspace 独立评测;Attempt ledger;Rubric;恢复和运行诊断 | Agent 群体进化、排行榜驱动、自动迁移、heartbeat 获得控制权、自动合并最佳代码 | + +这些执行设计用于保证普通科研工作的隔离、复验和审计,不构成“自动优化产品”。 + +### 2.3 明确排除的纯自动优化方案 + +AIDE 与 Karpathy autoresearch 不进入本产品功能规划。下列能力明确排除: + +- 自动修改训练代码并无限循环运行; +- 只按一个标量指标 keep/discard; +- 自动寻找超参数、模型结构或竞赛最优解; +- 以排行榜代替科学证据审核; +- 自动回退、重置或合并代码分支; +- 无人工目标确认、证据审查和停止条件的“整夜自治优化”。 + +## 3. 设计原则 + +### 3.1 认知与授权分离 + +Agent 负责理解、规划、综合、写作和审核建议;确定性服务负责身份、状态转换、数据校验、证据有效性、门禁、审批、幂等和审计。LLM 的文本、自评置信度或工具选择不能替代 GateDecision。 + +### 3.2 恰好五个业务 Agent + +系统只保留: + +1. `orchestrator`; +2. `paper_search`; +3. `paper_reader`; +4. `writer`; +5. `reviewer`。 + +Supervisor、Planner、Fact Checker、Citation Verifier、Experiment Manager、Knowledge Curator 等概念均实现为五 Agent 的阶段职责或确定性服务,不新增隐藏业务 Agent。 + +### 3.3 Evidence first,fail closed + +关键结论必须形成 `Claim -> Evidence` 关系。题录、摘要、全文定位、实验运行、人工批准和经验记录具有不同证据强度。证据不足、来源不独立、工作未闭环、审核打回或人工未批准时,系统必须停止或请求修订。 + +### 3.4 有界上下文而非聊天堆积 + +LangGraph state 和 Agent handoff 只保存稳定 ID、短摘要、约束和当前目标。全文、网页原文、运行日志、完整聊天和大文件存入受控 artifact store,按需检索。压缩摘要不能取代原始证据 locator。 + +### 3.5 产物契约驱动阶段完成 + +每个阶段都要声明输入、输出、Definition of Done、失败分类、重试边界、回退目标和允许的下一状态。不能仅凭“Agent 说完成了”推进工作流。 + +### 3.6 可恢复、可复验、不可静默改史 + +暂停、失败、补证据、稿件修订和重新执行都保留旧记录。修订使用新版本和 `SUPERSEDES` 关系;失效使用 `INVALIDATES`;普通证据阻断后创建新 run,不静默改写旧结论。 + +### 3.7 人类可控制,但不能用交互模式降低门禁 + +用户可以提问、编辑、补材料、暂停、恢复、否决和要求回退。任何交互模式都不能跳过 L3/L4 证据要求,也不能因超时自动批准。 + +### 3.8 版权、安全和科研诚信内建 + +系统只处理开放获取或用户有权提供的全文;不绕过付费墙和访问控制;不伪造论文、实验、指标、引用或人工身份;计划、估计、虚拟结果和真实执行结果必须显式区分。 + +## 4. 总体分层架构 + +```mermaid +flowchart TB + U[用户 / 研究负责人] --> I[CLI / HTTP API / Web UI] + I --> APP[AutoResearch Application Service] + + subgraph RUNTIME[工作流与认知层] + G[LangGraph Parent Graph] + O[Orchestrator] + S[Paper Search] + R[Paper Reader] + W[Writer] + V[Reviewer] + G --> O + G --> S + G --> R + G --> W + G --> V + end + + subgraph RESEARCH[研究服务层] + SRCH[Search Connector Registry] + PARSE[Document Acquisition & Parsing] + SYN[Synthesis & Novelty Service] + EXEC[Execution Service] + WRITE[Writing & Citation Service] + end + + subgraph GOVERNANCE[治理与一致性层] + HAND[Handoff & Context Assembler] + EVID[Claim / Evidence / Bundle] + GATE[Deterministic Gate Engine] + STATE[State Machine / Checkpoint] + POLICY[Security / License / Cost Policy] + AUDIT[Append-only Audit] + end + + subgraph KNOWLEDGE[长期知识层] + WIKI[Partitioned Wiki] + GRAPH[Evidence-linked Graph] + PROFILE[User Profile] + EXP[Experience & Evolution Proposals] + end + + subgraph DATA[数据与基础设施层] + DB[Canonical Store] + OBJ[Artifact / Object Store] + IDX[FTS / Vector Index] + QUEUE[Task Queue / Workers] + OBS[Telemetry / Cost / Health] + end + + APP --> G + O & S & R & W & V --> HAND + S --> SRCH + R --> PARSE + O & R & V --> SYN + O --> EXEC + W & V --> WRITE + SRCH & PARSE & SYN & EXEC & WRITE --> EVID + HAND --> STATE + EVID --> GATE + POLICY --> GATE + GATE --> STATE + EVID --> AUDIT + STATE --> AUDIT + EVID --> WIKI + WIKI <--> GRAPH + WIKI --> PROFILE + WIKI --> EXP + GOVERNANCE --> DB + KNOWLEDGE --> DB + PARSE & EXEC & WRITE --> OBJ + WIKI --> IDX + APP --> QUEUE + APP --> OBS +``` + +架构的核心不是让五个 Agent 互相自由聊天,而是让它们围绕规范数据对象协作。所有向下写入和向上恢复都经过应用服务与治理层。 + +## 5. 五 Agent 职责 + +| Agent | 核心职责 | 允许使用的能力 | 必须输出 | 明确禁止 | +|---|---|---|---|---| +| Orchestrator | 澄清 idea、Research Charter、问题拆解、阶段规划、任务派发、回退重规划 | 读取项目事实和 Gate 结果;请求检索、执行和审核服务 | charter、ResearchBrief、SearchPlan、WorkPackage、HandoffEnvelope | 自批创新、结果或外发;直接修改证据和 Gate | +| Paper Search | 生成/修订检索式、执行多源检索、候选筛选建议和检索诊断 | Connector Registry、并行检索任务、去重/版本关系服务 | SearchPlan、QueryTrace、PaperRecord、SearchCoverageReport | 无结果时编造论文;把搜索摘要当全文证据 | +| Paper Reader | 全文结构解析后的理解、ReadingCard、问答、跨论文比较、冲突和创新候选 | 授权文档、定位片段、表格/图像/公式解析结果、证据检索 | ReadingCard、ClaimMap、ComparisonMatrix、InnovationCandidate、ReadingAnswer | 用摘要冒充全文结论;确认未经反向检索的 novelty | +| Writer | 动态提纲、按章节证据召回、草稿、修订、润色、格式与引用装配 | 已放行 claim、结果 artifact、引用索引、目标模板和审稿意见 | Outline、ManuscriptRevision、CitationIndex、GapList、ChangeSummary | 新增无证据数字;补造实验;删除不确定性;自行标记可发布 | +| Reviewer | 范围、方法、证据、创新、结果、引用、复现和文稿质量审核 | 待审 artifact、EvidenceBundle、验收标准和旧审核记录 | ReviewReport、findings、required changes、Gate 建议、rollback target | 修改证据后自批;把写作风格判断当事实审核;越权批准 | + +同一 Agent 可在不同阶段复用,但每次调用必须有独立任务 ID、checkpoint namespace 和 HandoffEnvelope。并行检索 worker 是同一 Paper Search Agent 的隔离调用或确定性连接器任务,不算新增业务 Agent。 + +## 6. 确定性服务目录 + +### 6.1 Project Service + +- 创建论文项目文件夹和项目级 `.project-to-act`; +- 保存 idea、目标、非目标、目标 venue、资源和许可边界; +- 防止路径穿越、已有项目覆盖和跨项目数据混用; +- 管理本地交付包,但不自动投稿。 + +### 6.2 Workflow and State Service + +- 维护父图、五 Agent 子图、条件边和合法状态迁移; +- 保存 checkpoint、interrupt、恢复游标和 workflow/schema version; +- 区分 `BLOCKED`、`INTERRUPTED`、`FAILED`、`REJECTED` 和 `COMPLETED`; +- 对旧 checkpoint 提供迁移或兼容策略。 + +### 6.3 Handoff and Context Assembler + +- 校验 sender、receiver、project、run、task 和 schema version; +- 只装配当前任务需要的 artifact/evidence ID 和 bounded summary; +- 按风险、来源、证据等级、时效和许可过滤上下文; +- 对过长任务返回拆分请求,不截断成可能失真的上下文。 + +### 6.4 Search Connector Registry + +- 统一 OpenAlex、Crossref、Semantic Scholar、arXiv、PubMed、OpenReview、临床试验等来源; +- 支持网络、用户 seed、本地文档和 hybrid 模式; +- 并行执行独立 query,记录 query、连接器、时间、分页、过滤条件和失败诊断; +- 统一 DOI、标题、作者、年份、预印本/期刊版本关系和去重; +- 不将“连接器成功”误报为检索覆盖充分。 + +### 6.5 Document Acquisition and Parsing + +- 检查访问许可、开放获取状态和用户授权; +- 解析 PDF、HTML、DOCX、TXT、表格、图像和可选视频/音频; +- 保存页码、章节、段落、表格、图号、公式和字符 offset locator; +- 识别扫描件和 OCR 质量,低质量材料不得升级为强证据; +- 保存原始文件 checksum、解析器版本和派生 artifact lineage。 + +### 6.6 Claim and Evidence Service + +- 登记题录、全文摘录、实验、人工、知识和经验证据; +- 构建支持、反驳、限制和未解决 Claim 关系; +- 进行独立来源归并、版本替代、撤稿/更正和证据失效传播; +- 为 Gate 和 Writer 生成 EvidenceBundle; +- 保留原始定位和来源,不允许摘要覆盖原始证据。 + +### 6.7 Synthesis and Novelty Service + +- 生成相关工作矩阵、方法/数据/指标/限制对比; +- 识别共识、冲突、证据空白和适用边界; +- 对创新候选执行最近邻、反向检索和已知方案检索; +- 要求每个候选声明必要工作量、关键风险、最小验证和反证条件; +- 只输出 hypothesis/candidate,不自动确认 novelty。 + +### 6.8 Execution Service + +Execution Service 负责执行研究计划,不负责自动寻找最优模型或超参数。 + +- 将放行的 WorkPackage 固化为不可歧义的 RunManifest; +- 记录代码/分析脚本版本、数据 checksum、环境、依赖、随机种子、资源和预算; +- 在隔离 worktree、容器、远程受控执行器或人工执行入口中运行; +- 执行器只能访问 manifest 明确授权的数据、网络和密钥代理; +- 保存 stdout/stderr、退出码、指标、图表、模型、表格和失败日志; +- 使用独立 validation workspace 复验 artifact; +- 结果只有在 lineage、验收和证据 Gate 通过后才能进入正式稿件。 + +明确不提供:无限运行、自动 keep/discard、排行榜、自动合并最佳代码、自动扩大预算和超时自动继续。 + +### 6.9 Writing and Citation Service + +- 把提纲作为版本化“活文档”,允许随新证据修订; +- Writer 按章节检索相关 EvidenceBundle,而不是一次加载全部来源; +- 关键事实、数字、方法和结果 claim 必须绑定 citation/evidence ID; +- 引用装配只允许选择真实存在的 PaperRecord 和 locator; +- 支持 Markdown、DOCX、LaTeX/PDF 等导出适配; +- 修订保留 parent revision、diff、审稿意见和未解决缺口; +- Citation Verify 检查引用存在性、支持关系、重复、撤稿和 URL/DOI 一致性。 + +### 6.10 Reviewer and Gate Service + +Reviewer 提供认知审核,Gate Service 做最终确定性计算。审核维度包括: + +- scope 和 Research Charter 一致性; +- 文献覆盖与来源许可; +- claim/evidence 绑定; +- 方法、实验、统计和结果 lineage; +- novelty 表述强度与反向检索; +- 引用准确性、图表一致性和稿件缺口; +- 复现包、成本、隐私和交付完整性。 + +### 6.11 Knowledge, Profile and Evolution Services + +- 论文、证据、经验、知识、画像和项目事实分区存储; +- Wiki revision 和图边必须绑定来源和 evidence ID; +- 检索先做确定性过滤,再做词法/向量召回、图扩展、融合和 rerank; +- 用户推断偏好有置信度和有效期,不能充当人工批准; +- 经验需记录适用条件、反例、复现次数和等级; +- 自进化只产生 proposal、回归要求和回滚点,不能直接改代码、Gate 或工作流。 + +### 6.12 Policy, Audit and Observability + +- 管理模型、连接器、执行器、网络、许可、成本和外发策略; +- 记录 run/node/tool/gate/evidence/handoff/artifact 事件; +- 采集耗时、token、外部 API、执行资源、失败分类和重试次数; +- 日志不得包含密钥、Authorization header、完整论文或敏感原文; +- 提供健康检查、队列状态、阻断诊断、备份水位和恢复验证。 + +## 7. 端到端工作流 + +### 7.1 父生命周期 + +父图继续使用当前兼容状态: + +```text +INTAKE + -> SCOPED + -> LITERATURE_SEARCHED + -> LITERATURE_READ + -> INNOVATION_REVIEWED + -> EXECUTION_PLANNED + -> DRAFT_WRITTEN + -> DRAFT_REVIEWED + -> RELEASE_PENDING + -> RELEASED +``` + +M08 执行、文献筛选和稿件修改使用阶段内部子状态,不在未提供 checkpoint 迁移前破坏父状态枚举。 + +### 7.2 Idea 与 Research Charter + +1. 用户提交 idea、目标、非目标、目标读者/venue、资源、时间、隐私和全文许可边界; +2. Orchestrator 提出澄清问题并生成 ResearchBrief; +3. 用户可编辑目标和约束; +4. Research Charter 固化研究问题、成功标准、风险和停止条件; +5. L0/L1 Gate 允许进入文献规划。 + +### 7.3 文献检索与筛选 + +```text +ResearchBrief +→ SearchPlan +→ 并行 Query Tasks +→ 多源候选 +→ 去重/版本关联/许可诊断 +→ Screening Set +→ Coverage Report +``` + +检索停止条件同时考虑目标覆盖、来源多样性、时间范围、反向检索和预算。连接器失败、零结果和受限全文必须显式记录。 + +### 7.4 阅读与证据提取 + +```text +PaperRecord +→ 授权与完整性检查 +→ 结构解析 +→ 定位片段 +→ ReadingCard / ClaimMap +→ EvidenceItem +→ 跨论文综合 +``` + +阅读问答只装配相关片段和必要定义。找不到证据时返回 `unresolved=true`,不使用模型常识填补论文内容。 + +### 7.5 综合与创新候选 + +1. 构建相关工作、方法、数据、指标、限制和冲突矩阵; +2. 识别研究空白和可能解释; +3. 生成可证伪 InnovationCandidate; +4. 执行最近邻与反向检索; +5. Reviewer 检查证据、区别特征、价值和反证条件; +6. Gate 决定进入工作计划、退回检索/阅读或拒绝候选。 + +### 7.6 工作计划与执行 + +```mermaid +flowchart LR + C[InnovationCandidate] --> WP[WorkPackage] + WP --> PG{Planning Gate} + PG -->|REVISE| C + PG -->|PASS| RM[RunManifest] + RM --> EX[Isolated Execution] + EX --> AR[Artifacts & Raw Results] + AR --> IV[Independent Validation] + IV --> RG{Result Gate} + RG -->|REVISE| WP + RG -->|PASS| ER[Result EvidenceBundle] +``` + +工作包可包含代码实验、统计分析、数据整理、模拟、人工实验交接或混合任务。系统只执行明确批准的任务,不自行改变科学目标或扩大资源范围。 + +### 7.7 动态提纲与稿件生成 + +1. Writer 根据 Research Charter、放行 claim 和目标模板生成提纲; +2. 提纲随证据和结果更新,但每次修改都版本化; +3. 每写一节,Context Assembler 只召回本节相关证据和前后文; +4. 缺实验时保留明确占位符,不能生成虚构 Results; +5. Writing Service 检查 citation ID、证据支持关系和引用格式; +6. 输出独立 ManuscriptRevision 和 GapList。 + +### 7.8 审核、返工和本地交付 + +Reviewer 对稿件执行证据、方法、结果、创新、引用和复现审核。每条 finding 必须包含严重度、对象、原因、证据、要求修改、回退阶段和复验条件。 + +返工采用定向路由: + +- 文献不足:回 `paper_search`; +- 全文理解或 locator 问题:回 `paper_reader`; +- 创新证据不足:回综合与反向检索; +- 结果不闭环:回 WorkPackage/Execution; +- 表达和结构问题:回 `writer`; +- 系统/许可/人工问题:进入阻断或 interrupt。 + +本地交付包含稿件、引用索引、证据清单、运行 manifest、结果 artifact、审核记录、已知缺口和许可证说明。外部发送仍进入 L4 Gate,不自动执行。 + +## 8. 人类协作设计 + +### 8.1 交互模式 + +| 模式 | 用途 | 自动化边界 | +|---|---|---| +| Offline deterministic | 无网络、无模型的测试和受控材料处理 | 只运行确定性路径 | +| Assisted | 模型协助检索式、阅读、综合和写作 | 不能覆盖任何 Gate | +| Gate-driven | 平时自动推进,在明确 checkpoint 暂停 | 所有高风险 Gate 等待人类 | +| Co-pilot workshop | Idea、文献筛选、实验设计或稿件共创 | 用户可编辑产物,修改后重新审核 | +| Review-only | 系统准备完整材料,人类只做阶段审核 | 未响应时保持暂停,不自动继续 | + +### 8.2 用户动作 + +允许动作包括 `view`、`ask`、`edit`、`inject evidence`、`approve`、`reject`、`pause`、`resume`、`request revision` 和 `rollback request`。`skip` 只可用于策略明确允许的低风险可选步骤,不能跳过证据、结果、引用或外发 Gate。 + +### 8.3 持久审批请求 + +审批请求必须包含 gate ID、风险等级、待审对象、EvidenceBundle、Reviewer findings、影响、截止/成本信息和允许动作。请求持久化后可跨进程恢复;超时只产生提醒或保持阻断,不产生批准。 + +## 9. 核心数据对象 + +| 对象 | 作用 | 核心不变量 | +|---|---|---| +| ResearchState | 父图状态 | 保存 ID 和短诊断,不保存全文 | +| ResearchBrief / Charter | 研究目标与约束 | 用户可审阅;变更需版本化 | +| SearchPlan / QueryTrace | 检索计划和实际执行 | query、来源、过滤和失败可追溯 | +| PaperRecord | 论文题录和来源 | DOI/source ID/版本关系去重;题录不代表全文许可 | +| ReadingCard | 结构化阅读结论 | 每个重要结论带 locator/evidence ID | +| ClaimRecord | 待论证主张 | 状态、范围、支持/反驳证据和适用边界明确;Foundation 可先由 EvidenceItem.claim 承载 | +| EvidenceItem | 不可覆盖证据 | project、source、locator、checksum、grade、valid、supersedes | +| EvidenceBundle | Gate/写作消费的证据包 | 独立来源、冲突、时效、许可和闭环可计算 | +| InnovationCandidate | 可证伪创新候选 | 最近邻、区别点、风险、最小验证和反证条件 | +| WorkPackage | 研究工作计划 | 目标、依赖、产物、验收、风险和审批点明确 | +| RunManifest | 一次真实执行定义 | 输入、代码、环境、种子、资源、预算和执行器不可歧义 | +| ResultRecord | 运行结果摘要 | 指向原始 artifact;区分成功、失败、部分和未知 | +| ArtifactRef | 大产物稳定引用 | URI、kind、checksum、size、producer、lineage | +| ManuscriptRevision | 稿件版本 | parent、evidence、gap、diff、release_ready 分离 | +| ReviewReport | 独立审核 | findings、严重度、回退目标和复验条件完整 | +| GateDecision | 最终策略结果 | PASS/REVISE/INTERRUPT/DENY;LLM 不可覆盖 | +| ExperienceRecord | 可复用经验候选 | 适用条件、反例、复现和晋级状态明确 | +| EvolutionProposal | 自进化建议 | proposal-only,带风险、回归和回滚要求 | + +## 10. 证据、引用和门禁 + +### 10.1 证据链 + +```text +Source / Artifact +→ checksum + locator + acquisition metadata +→ EvidenceItem +→ supports / refutes / limits Claim +→ EvidenceBundle +→ Reviewer audit +→ Deterministic GateDecision +→ lifecycle transition + audit event +``` + +### 10.2 证据质量维度 + +Gate 不只检查数量或模型置信度,还检查: + +- 类型:human、paper、experiment、experience、knowledge、system; +- 强度:题录/摘要、全文定位、原始结果、独立复验、具名人工批准; +- 直接性:证据是否直接支持当前 claim; +- 独立性:重复摘要和同一来源派生物不能重复计分; +- 时效与状态:更正、撤稿、过期和失效传播; +- 完整性:工作包、运行和 artifact lineage 是否闭环; +- 冲突:反驳证据是否被纳入并处理; +- 许可和范围:来源能否用于当前项目和交付物。 + +### 10.3 风险等级 + +当前 Foundation 继续使用 L0–L4 和 E0–E3/H3。生产化可在兼容迁移后扩展论文、实验、人工和复现证据家族,但不能把计划中的细粒度等级写成当前已实现。 + +### 10.4 永久硬阻断 + +- 伪造论文、作者、DOI、数据、实验、指标、工具结果或人工批准; +- 用摘要冒充全文证据; +- 删除失败或冲突证据以获得 PASS; +- 未经授权抓取受限全文; +- 无真实结果时生成正式 Results; +- 未经具名人工确认对外发送、发布、删除或执行高成本任务。 + +## 11. 上下文、记忆与知识架构 + +### 11.1 四种时间尺度 + +| 时间尺度 | 内容 | 规范载体 | +|---|---|---| +| 单次调用 | 当前目标、输入、约束和短上下文 | HandoffEnvelope / ContextPack | +| 单次 run | lifecycle、checkpoint、阻断和事件 | ResearchState / checkpoint / audit | +| 单项目 | 论文、证据、工作包、稿件和决定 | canonical store / paper-project folder | +| 跨项目 | 经复验的经验、知识和用户偏好 | partitioned Wiki+Graph / profile / experience | + +### 11.2 分区与召回 + +论文、经验、知识、画像、项目和证据不能混成一个无类型向量库。推荐召回顺序: + +1. 项目已确认事实和人工决定; +2. 当前 claim 的有效证据; +3. 论文库; +4. 经审核经验; +5. 概念知识; +6. 受控外部检索。 + +召回管线采用 ACL/项目/许可/有效性过滤,再执行词法、向量、图扩展、RRF/rerank 和证据包重排。所有命中保留原分区、record ID 和 evidence ID。 + +### 11.3 动态提纲与 Memory Bank + +WebWeaver 式 Memory Bank 在本系统中不是自由文本缓存,而是由 `source ID + summary + evidence IDs + locator + validity` 组成的受控索引。Writer 每写一节按提纲节点召回,不允许引用只存在于摘要但无法回到原始来源的内容。 + +### 11.4 经验与自进化 + +运行日志、Agent note 和用户修正先形成经验候选。只有经过复验、反例检查、Reviewer 和人工批准后才能晋级。所谓“自进化”只提交变更提案,不自动更新提示词、Agent 权限、Gate、模型路由或工作流。 + +## 12. 完整功能目录 + +| 功能域 | 核心功能 | 当前 Foundation | 目标补充 | +|---|---|---|---| +| 项目与 Idea | 建项目、Research Charter、问题拆解、非目标、资源边界 | 已有基础实现 | 多领域 charter 模板、可视化编辑和变更审计 | +| 文献策略 | SearchPlan、seed、引用追踪、反向检索、停止条件 | 三连接器基础实现 | arXiv/PubMed/OpenReview/临床试验、时间过滤、覆盖诊断 | +| 文献获取 | OA/许可诊断、用户授权文件、hybrid corpus | seed 和网络题录基础 | Unpaywall、版本图、下载队列、缓存和许可策略包 | +| 全文解析 | 章节、页码、表格、图像、公式、OCR | 基础文本/locator | 多解析器 Profile、多模态、质量分和结构化表图 | +| 阅读陪跑 | ReadingCard、ClaimMap、问答、跨论文比较 | 已有基础实现 | 章节 checkpoint、公式/图表问答、冲突综合 | +| 创新分析 | 缺口、最近邻、反向检索、候选、反证条件 | 已有候选基础 | 近邻矩阵、领域 novelty policy、重复反例集 | +| 研究计划 | WorkPackage、依赖、风险、验收和成本 | 已有基础对象 | 资源规划、模板 Profile、人工实验交接 | +| 研究执行 | RunManifest、隔离执行、artifact lineage、复验 | 完成状态证据门槛基础 | worktree/container/remote worker、独立 validation、结果 Gate | +| 数据与统计 | 数据版本、质量检查、统计计划、失败分析 | 未完整实现 | 数据卡、统计检查、置信区间、敏感性和消融支持 | +| 动态提纲 | 提纲版本、证据覆盖、章节依赖 | 基础稿件流程 | living outline、章节级 evidence retrieval 和 coverage map | +| 论文写作 | 草稿、结果占位、修改、润色、模板导出 | 已有基础实现 | LaTeX/DOCX、目标 venue 模板、图表和引用装配 | +| 审核闭环 | Reviewer、findings、定向打回、复验 | 已有 L3 打回 | Rubric 包、图表/VLM 辅助、citation/claim audit | +| 本地交付 | 稿件、证据、运行、审计和许可清单 | 项目文件夹基础 | 可复现 bundle、checksum manifest、交付预检 | +| 人类协作 | ask/edit/approve/reject/pause/resume | L4 interrupt/resume 基础 | Workshop UI、持久审批队列、差异审阅 | +| 证据与 Gate | EvidenceItem、Bundle、L0–L4、失效 | 已有基础实现 | 冲突传播、证据家族、策略版本和领域包 | +| Wiki+Graph | 分区页面、图关系、检索 | 词法+一跳图基础 | FTS/vector/RRF/rerank、Neo4j 可重建投影 | +| 画像与经验 | 显式/推断偏好、经验晋级 | 已有基础实现 | 自助修改/删除、跨项目复验、过期降级 | +| 自进化 | proposal、审核、人工应用标记 | proposal-only 已实现 | 沙箱回归、canary、人工应用和回滚编排 | +| API/CLI/UI | 项目、run、证据、阅读、写作、审核 | CLI/FastAPI 基础 | Web UI、流式事件、认证、队列和跨项目面板 | +| 安全与隐私 | 密钥、许可、ACL、外发/删除 Gate | 本地安全边界基础 | 多租户 ACL、RLS、审计签名、隐私生命周期 | +| 可观测与运维 | 健康、事件、成本、备份恢复 | doctor/本地手册基础 | OpenTelemetry、告警、队列诊断、恢复演练和 SIEM | + +## 13. API 与界面规划 + +### 13.1 统一应用服务 + +CLI、HTTP 和 Web UI 必须调用同一 AutoResearchApplication,不能各自实现状态转换或 Gate。所有写请求支持 idempotency key;长任务返回 run/task ID,通过事件流读取进度。 + +### 13.2 用户界面信息架构 + +建议至少包含: + +- 项目总览:目标、阶段、阻断、预算和最近决定; +- Research Charter 编辑器; +- 文献检索式、来源、筛选和许可面板; +- 阅读卡、原文 locator 和 Claim/Evidence Map; +- InnovationCandidate 对比和反证条件; +- WorkPackage、RunManifest、执行状态和结果 lineage; +- 动态提纲、章节证据覆盖和稿件 diff; +- Reviewer findings 和定向返工; +- Gate/审批中心; +- Wiki+Graph、经验和自进化提案; +- 审计、成本、健康和恢复面板。 + +### 13.3 流式事件 + +对外事件只暴露结构化状态和安全摘要,例如 `run.started`、`node.completed`、`search.partial_failure`、`evidence.created`、`gate.revise`、`human.required`、`artifact.verified`。不流式暴露隐藏提示、密钥、完整原文或内部推理。 + +## 14. 部署与数据拓扑 + +### 14.1 Foundation Profile + +- 单机 Python 3.12; +- SQLite canonical store; +- SQLite LangGraph checkpointer; +- 本地 paper-project 文件夹; +- 默认离线,API 只绑定 loopback; +- 可选受管模型与受控网络检索。 + +### 14.2 Production Target Profile + +```mermaid +flowchart LR + API[API / UI] --> APP[Application Service] + APP --> PG[(PostgreSQL + RLS)] + APP --> Q[Task Queue] + Q --> SW[Search/Parse Workers] + Q --> EW[Execution Workers] + Q --> WW[Writing/Export Workers] + SW & EW & WW --> OBJ[(Object Store)] + APP --> VEC[(pgvector / FTS)] + APP --> NEO[(Rebuildable Graph Projection)] + APP --> OTEL[OpenTelemetry] + APP --> KMS[Secrets / KMS Proxy] +``` + +PostgreSQL 是项目、run、claim、evidence、Gate、Wiki revision、profile 和 outbox 的规范源;对象存储保存大文件;图数据库是可从规范记录重建的投影,不能成为唯一事实源。 + +### 14.3 隔离与执行器 + +- 文献 parser 与研究执行 worker 分离; +- 高风险执行器采用容器/用户隔离、只读基线和最小网络权限; +- grader/validator 使用独立环境和隐藏测试数据; +- Agent 无权访问其他项目、私有 grader 数据或宿主机凭证; +- 远程执行只接受签名/校验后的 RunManifest,并回传 checksum artifact。 + +## 15. 非功能要求 + +| 属性 | 目标要求 | +|---|---| +| 可审计 | 每个关键状态、证据、Gate、人工动作和产物版本可追溯 | +| 可复现 | 真实结果有 RunManifest、环境、数据/代码 checksum、种子和原始 artifact | +| 一致性 | 状态转换 allowlist;跨存储写入通过事务/outbox/幂等保护 | +| 可恢复 | checkpoint、任务重试、人工 interrupt、备份与恢复水位明确 | +| 上下文安全 | bounded context、按需召回、分区/ACL/许可过滤 | +| 科研诚信 | 关键 claim 100% 证据绑定;虚拟/计划/估计/真实结果显式区分 | +| 安全 | 密钥不落 artifact/log;执行器最小权限;高风险动作 fail closed | +| 成本 | 模型、搜索、解析、存储和执行资源按项目/run 可计量和限额 | +| 可观测 | run/node/tool/gate/evidence/handoff 全链路事件和失败分类 | +| 可扩展 | Connector、Parser、Executor、Exporter、Policy Profile 可插拔且版本化 | + +## 16. M00–M17 映射 + +| 模块 | 本文档对应架构 | 主要来源设计 | +|---|---|---| +| M00 项目治理与发布 | 文档事实源、版本、交付边界 | 当前 AutoResearch | +| M01 合同与交接 | 阶段合同、Handoff、ContextPack、核心对象 | AutoResearchClaw、Open Deep Research | +| M02 证据与门禁 | Claim/Evidence/Bundle、引用、失效和 Gate | PaperQA2、当前 AutoResearch | +| M03 LangGraph 工作流 | 父图、五子图、并行调用、回退、checkpoint/HITL | Open Deep Research、AutoResearchClaw、Agent Laboratory | +| M04 规范存储与幂等 | canonical store、audit、artifact、outbox | 当前 AutoResearch、CORAL 受限设计 | +| M05 论文搜索 | Connector Registry、并行 query、去重和覆盖诊断 | PaperQA2、GPT Researcher、Tongyi | +| M06 论文读取 | 多解析器、locator、ReadingCard、陪读和 ClaimMap | PaperQA2、Tongyi/WebWeaver | +| M07 创新与相关工作 | 综合矩阵、最近邻、反向检索、反证条件 | AutoResearchClaw、AI Scientist-v2 | +| M08 工作执行与结果 | WorkPackage、RunManifest、隔离执行、复验和 lineage | AutoResearchClaw、AI Scientist-v2、Arbor/CORAL 受限设计 | +| M09 写作 | 动态提纲、章节召回、稿件版本和引用装配 | WebWeaver、AutoResearchClaw、GPT Researcher | +| M10 审核 | Reviewer、Rubric、定向打回、图表和引用检查 | AI Scientist-v2、Agent Laboratory、CORAL 受限设计 | +| M11 Wiki+Graph | 分区 Memory Bank、图关系和分级召回 | WebWeaver、PaperQA2、当前 AutoResearch | +| M12 画像、经验与自进化 | 累计研究资产、经验提案和人工晋级 | Agent Laboratory、Arbor 受限设计、当前 AutoResearch | +| M13 API、CLI 与界面 | 统一应用服务、流式事件、Workshop 和导出 | GPT Researcher、Open Deep Research、AutoResearchClaw | +| M14 安全、隐私与模型配置 | 许可、受管模型、执行隔离、ACL 和外发 Gate | PaperQA2、GPT Researcher 安全教训、当前 AutoResearch | +| M15 可观测、运维与灾备 | 事件、健康、成本、恢复、队列和备份 | GPT Researcher、CORAL 受限设计 | +| M16 真实论文试点 | gold query、人工 ReadingCard、真实执行和稿件审查 | 必须由真实项目验证,不能由仓库设计代替 | +| M17 生产化与规模验证 | Postgres/pgvector、队列、对象存储、RLS、并发和 DR | 综合目标拓扑 | + +## 17. 实施顺序 + +### 阶段 A:稳定治理内核 + +完成 M01–M04 的 schema、阶段合同、Gate policy、checkpoint 兼容、canonical store、artifact 和幂等。任何外部能力都必须先通过这一层。 + +### 阶段 B:建立真实文献证据链 + +扩展 M05–M07 的连接器、全文许可、解析 Profile、locator、ClaimMap、综合和反向检索。使用真实领域 gold query 和人工阅读卡验收。 + +### 阶段 C:补齐非自动优化的执行闭环 + +优先实现 M08 的 RunManifest、隔离执行、独立 validation、artifact lineage 和 Result Gate。每次执行由明确 WorkPackage 触发,不运行自动寻优循环。 + +### 阶段 D:稿件和审核闭环 + +引入动态提纲、章节级证据召回、稿件 revision、citation verify、Reviewer Rubric 和定向返工。内部缺口稿与正式交付稿严格分开。 + +### 阶段 E:知识、界面和运维 + +完成分区检索、画像/经验、自进化 proposal、Web UI、审批中心、流式事件、认证、观测和恢复工具。 + +### 阶段 F:真实 Pilot 后生产化 + +先用一个真实 idea、授权 corpus、真实工作和人工审查完成 M16;再根据实际单机、内网团队或云端规模推进 M17。不能用 demo 或作者 benchmark 替代真实项目验收。 + +## 18. 验收基线 + +1. 业务 Agent registry 始终恰好五个; +2. 非法状态转换和 Gate 绕过率为零; +3. 关键事实、数字、方法和结果 claim 全部绑定有效 evidence ID; +4. 文献问答缺材料时返回 unresolved,不生成猜测; +5. 撤稿、更正和证据失效可阻止旧结论继续放行; +6. 每个真实结果都可追到 WorkPackage、RunManifest、代码/数据/环境和原始 artifact; +7. Writer 无法消费未放行结果,Reviewer 无法修改证据后自批; +8. checkpoint、interrupt 和进程恢复不重复不可逆动作; +9. 不同项目、知识分区和用户数据的隔离测试通过; +10. 外发、删除、覆盖和高成本执行没有具名人工批准时保持阻断; +11. 本地交付包包含稿件、引用、证据、运行、审核、缺口和许可证说明; +12. 一个真实论文试点保留完整 run/evidence/gate/handoff/artifact 记录。 + +## 19. 不直接照搬的设计 + +- AutoResearchClaw 的 skip、超时自动继续和 Agent 代批; +- Open Deep Research 将压缩 notes 直接当最终依据; +- PaperQA2 在超时后强制生成答案; +- GPT Researcher 的未认证演示后端和用户可控 MCP 命令执行; +- AI Scientist-v2 的受限许可代码、开放式成功率假设和无人工科研 Gate; +- Agent Laboratory 的额外角色 Agent、未完成并行实验室路径和 pickle 作为生产存储; +- Tongyi Heavy Mode 未完整开源部分、未公开训练数据和高资源参考部署假设; +- Arbor/CORAL 的自动进化、标量排行榜、自动迁移/合并和 heartbeat 控制语义; +- AIDE、Karpathy autoresearch 的纯自动优化循环。 + +## 20. 官方参考来源 + +- AutoResearchClaw: +- Open Deep Research: +- PaperQA2: +- GPT Researcher: +- AI Scientist-v2: +- Agent Laboratory: +- Tongyi DeepResearch / WebWeaver: +- Arbor(仅通用执行设计): +- CORAL(仅通用执行设计): + +许可证提示:顶层仓库许可证不自动覆盖模型权重、数据集、内嵌外部 Agent、解析器和商业 API。AI Scientist-v2 使用自定义许可证;所有代码级复用必须再次检查具体文件、依赖和模型卡。本文档目前只吸收设计思想,不引入上述仓库源码。 diff --git a/docs/FUNCTION_MATRIX.md b/docs/FUNCTION_MATRIX.md index 157e953..73abf04 100644 --- a/docs/FUNCTION_MATRIX.md +++ b/docs/FUNCTION_MATRIX.md @@ -13,7 +13,7 @@ | 论文总结 | 已实现 | PaperReaderService.read | ReadingCard、locator、置信度 | 结构化抽取为基础算法;图表/公式解析待增强 | | 阅读陪跑 | 已实现 | POST /papers/{id}/questions、ask-paper | ReadingAnswer、locator、evidence IDs | 只回答已供应文本;无材料时 unresolved=true | | 创新点挖掘 | 已实现 | PaperReaderService.mine_innovations | InnovationCandidate、反证测试 | 永远先标记 hypothesis,不自动宣称 novelty | -| 工作量辅助 | 已实现 | WorkPackage、ExecutionService | 四类工作包、依赖、验收、状态 | 完成状态必须绑定有效证据;资源估算器待增强 | +| 工作量辅助 | 已实现 | WorkPackage、ExecutionService | 领域工作包、RunManifest、确定性后处理、artifact lineage | 金属韧性损伤 Profile 已落地;任意命令/真实 LAMMPS 执行未开放 | | 论文写作 | 已实现 | WriterAgent、WritingService.draft | MANUSCRIPT_DRAFT.md | 缺实验时 Results 固定为待真实实验 | | 修改与润色 | 已实现 | POST /manuscripts/{id}/revisions、revise-manuscript | 独立 revision 文件 | 保留证据和缺口;新版本默认 release_ready=false | | 审核与打回 | 已实现 | ReviewerAgent | ReviewReport、GateDecision | L3 证据/闭环不足转 WAITING_EVIDENCE | @@ -35,6 +35,7 @@ | 项目管理 | ProjectService | 从模板实例化独立 Project-to-Act 文件夹 | 跨项目仪表盘和归档/迁移工具 | | 接口 | CLI + FastAPI | doctor/project/run/resume/evidence/knowledge/profile/evolution | Web UI、认证、多租户限流 | | 审计 | RecordStore | append-only event timeline | WORM 存储、签名审计和外部 SIEM | +| M08 执行 | metal_ductile_damage_v1 | 输入/来源 hash、幂等、失败记录、结果 artifact、E1/E2 分类 | 容器执行器、远程 worker、独立 grader 和真实 MD Pilot | ## 3. 非完成声明 diff --git a/docs/M08_METAL_DUCTILE_DAMAGE.md b/docs/M08_METAL_DUCTILE_DAMAGE.md new file mode 100644 index 0000000..4832ccb --- /dev/null +++ b/docs/M08_METAL_DUCTILE_DAMAGE.md @@ -0,0 +1,46 @@ +# M08 金属韧性损伤执行纵切 + +本纵切把 `WorkPackage -> RunManifest -> deterministic executor -> artifact -> EvidenceItem -> manuscript/reviewer Gate` 落到可运行代码,领域选题为“分子动力学中的金属韧性损伤机理分析”。 + +## 能力边界 + +- 执行 Profile 固定为 `metal_ductile_damage_v1`,只接收结构化应变、应力、位错密度、空洞体积分数和 centrosymmetry 序列。 +- 不接受任意 shell、LAMMPS 命令或自动优化循环。 +- `synthetic_fixture` 用于软件测试,执行证据为 E1,永远不能关闭论文 Results Gate。 +- `md_trajectory_summary` 必须提供来源 URI,并使 `source_checksum` 等于规范化观测序列的 SHA-256;执行证据为 E2,仍需论文、环境和独立复验证据共同通过 L3。 +- 输出的机制指标只表示提交序列中的时序相关,不自动构成因果机理证明。 + +## 数据合同 + +`RunManifest` 记录:项目/研究 run/工作包、Profile、数据分类、输入 checksum、来源、参数、seed、Python/执行器版本、状态、execution ID、输出 artifact、验证项和时间。 + +后处理器验证应变严格递增、数值有限、应变跨度充分且至少存在一种缺陷观测,并计算: + +- 初始应力-应变斜率; +- 峰值应力和峰值应变; +- 峰后 5% 应力下降起始应变; +- 半峰值承载应变; +- 位错、空洞和晶格无序的变化指标。 + +同一项目内相同 idempotency key 和相同输入只返回原 manifest/执行记录;同 key 改变输入会被拒绝。失败执行保存 `failed` 记录,但不创建结果证据。 + +## API + +| 方法 | 路径 | 作用 | +|---|---|---| +| POST | `/execution/manifests` | 注册有界执行与输入 lineage | +| GET | `/projects/{project_id}/execution/manifests` | 列出 manifest | +| POST | `/execution/manifests/{manifest_id}/execute` | 运行注册的确定性 Profile | +| GET | `/execution/runs/{execution_id}` | 读取执行状态、指标和 artifact | +| GET | `/manuscripts/{manuscript_id}` | 检查稿件 evidence/gap/release 状态 | + +主 `/runs` 请求可选提供 `execution_input` 和 `execution_idempotency_key`。应用会在进入 LangGraph 前把大输入写入受控 execution store,Graph state 只携带 manifest ID;到 M08 服务节点执行并用 artifact/evidence enrich Writer handoff。 + +## CLI + + autoresearch register-execution --input-file INPUT.json --idempotency-key KEY + autoresearch execute-manifest + autoresearch execution-status + autoresearch run --idea "..." --execution-file INPUT.json --execution-idempotency-key KEY + +真实样例项目位于 `paper-projects/md-metal-ductile-damage-pilot/`。其中 fixture、run artifact 和缺口稿件均保留 synthetic 标签,不代表真实 MD 结果。 diff --git a/docs/TESTING.md b/docs/TESTING.md index 7926da5..9a68003 100644 --- a/docs/TESTING.md +++ b/docs/TESTING.md @@ -2,7 +2,7 @@ ## 1. 当前自动化结果 -基础测试覆盖 18 个场景,当前语句覆盖率 84%: +自动化覆盖 25 个场景;收口时以实际 coverage 命令为准: - Agent registry 恰好五个。 - 自交接、超长上下文被拒绝。 @@ -22,6 +22,12 @@ - 全文 + E3 实验 lineage 可到 DRAFT_REVIEWED。 - L4 interrupt 可用同 thread 恢复。 - FastAPI、CLI doctor 与秘密摘要边界。 +- 金属韧性损伤领域 Profile 和四个领域工作包。 +- RunManifest 输入/来源 checksum、幂等和同 key 改输入负例。 +- 严格递增应变、有限数值、缺陷观测和失败不产出结果证据。 +- deterministic 后处理的峰值、损伤起始、artifact 文件 hash 和 E1/E2 分类。 +- 主 workflow 消费 `md_trajectory_summary`,同时拒绝 synthetic fixture 关闭 Results Gate。 +- execution/manuscript HTTP 合同。 执行: @@ -37,11 +43,11 @@ - 任一真实实验或论文结果成立。 - 外部投稿成功。 - 生产并发、灾备、安全合规或成本达标。 -- LLM 提供商在当前凭据下可用。 +- 任一特定 LLM 提供商持续可用;AR-005 只证明当时 DeepSeek 真实调用成功,GLM 调用发生 ReadTimeout。 ## 3. 下一阶段验收 -1. 提供一个真实 idea、研究领域、目标 venue 和资源边界。 +1. 为当前金属韧性损伤 idea 锁定材料、晶向、势函数、目标 venue 和资源边界。 2. 建立人工标注 gold queries 和 20–50 篇许可明确的 paper corpus。 3. 人工盲审 ReadingCard、locator、冲突综合和 InnovationCandidate。 4. 运行真实 baseline/ablation,登记 RunManifest、hash 和失败结果。 diff --git a/evidence/AR005_M08_EVIDENCE.md b/evidence/AR005_M08_EVIDENCE.md new file mode 100644 index 0000000..a9d4f74 --- /dev/null +++ b/evidence/AR005_M08_EVIDENCE.md @@ -0,0 +1,69 @@ +# AR-005 M08 金属韧性损伤软件纵切证据 + +检查日期:2026-08-22(Asia/Shanghai) +分支:`codex/ar-005-md-ductile-damage-pilot` +目标:验证领域 Profile、RunManifest、确定性后处理、artifact lineage、受管 LLM、真实 HTTP 边界和 synthetic fail-closed;不声明真实 MD 科研结果或生产就绪。 + +## 实现证据 + +- 业务 Agent registry 仍恰好五个;M08 是 `execution_service` 确定性节点。 +- `metal_ductile_damage_v1` 只处理结构化观测,不接受 shell 命令。 +- `RunManifest` 保存数据分类、输入/来源 checksum、参数、seed、Python/执行器版本、状态、execution ID、输出 artifact 和验证项。 +- 同项目相同 idempotency key/输入返回原 manifest;同 key 改输入被拒绝;失败执行不创建结果证据。 +- synthetic 结果登记为 E1;匹配来源 checksum 的 `md_trajectory_summary` 登记为 E2。 +- 主 Graph state 只携带 manifest/execution ID,大输入保存在 execution store;Writer handoff 只增加引用型 artifact/evidence。 + +## 自动化验证 + +| 检查 | 结果 | +|---|---| +| `python -m ruff check src tests` | PASS | +| `python -m pytest -W error --cov=autoresearch --cov-report=term-missing -q` | PASS,25 tests,84% statements | +| `autoresearch doctor` | PASS,0.2.0.dev0,agent_count=5,DeepSeek 配置存在,network=false | +| CLI register/execute/status | PASS,manifest `manifest_16b49559507942be`,execution `execution_b10aefcbfc3a4876`,completed,1 evidence,1 artifact | +| 根 Project-to-Act validate | PASS,0 issues | +| Pilot Project-to-Act validate | PASS,0 issues | +| YAML 与 `git diff --check` | PASS;仅工作区换行提示 | + +自动化覆盖领域 Profile、manifest 幂等、同 key 改输入、source checksum、非法应变序列、失败不产出证据、真实文件 hash、E1/E2 分类、主 workflow synthetic 阻断、checksum-bound MD summary 消费和 HTTP 合同。 + +## 真实 LLM 证据 + +- 受管 GLM `glm-5.2`:真实调用在 60 秒内发生 `ReadTimeout`;run `run_30b03b3cff5049a8` 使用确定性 fallback,并因无论文 blocked。该调用不记为成功。 +- 受管 DeepSeek `deepseek-v4-flash`:真实调用成功;run `run_c3357978832d4b77` 保存 `Managed LLM refined the structured research scope.`,生成 5 个检索式,业务仍因无论文在 `waiting_evidence` 阻断。 +- AAWO 直接 POST LLM 旅程受固定 15 秒 adapter timeout 限制,报告为 `inconclusive`;随后对已完成的真实 DeepSeek run 做 GET 复核,报告为 `pass`。这只证明当时的连接与结构化范围路径,不证明科研质量或持续可用性。 + +## 真实 HTTP 客户旅程 + +目标 listener:`http://127.0.0.1:8137`,loopback,本项目进程。最终 ledger 共 36 条 append-only records。 + +| 场景 | 结果 | 业务解释 | +|---|---|---| +| health/five-agent/model | pass | 正确 listener,五 Agent,受管模型已配置 | +| LLM direct POST | inconclusive | AAWO adapter 15 秒超时,不放行 | +| observed DeepSeek run | pass | 真实模型 scope 已完成;无论文 blocked | +| synthetic manifest register | pass | planned、分类与输入 hash 存在 | +| deterministic execution | pass | actual_execution=true、artifact/evidence 存在 | +| full workflow synthetic result | pass | 2 条真实 OpenAlex 题录/摘要、执行 completed、稿件 waiting_evidence | +| manuscript synthetic gap | pass | `release_ready=false` 且 unresolved gap 显式包含 `synthetic_fixture` | + +完整 HTTP run `run_b33c1d0b69564e6f` 使用两条真实 OpenAlex 题录/摘要与 synthetic fixture:2 篇论文、2 张阅读卡、1 个 manifest、1 个 execution 和 1 个缺口稿件;Reviewer 因 evidence score 65<75 且结果闭环不足阻断。题录/摘要不冒充授权全文。 + +## 报告与 hash + +| 文件 | 状态 | SHA-256 | +|---|---|---| +| `journeys/ar005-health-report.json` | pass | `5aceb14ad4b07f9a4055461217777460f29fe61f13b96f60cf5fcced6fe424ec` | +| `journeys/ar005-llm-scope-report.json` | inconclusive | `649fa933b35d509d7e653fdf511d66c4fb65efd052ddb35c55c3fc4af10fe468` | +| `journeys/ar005-llm-scope-observed-report.json` | pass | `341a9d8514d2da9d61f6faa757abea0d1233fba5950f51b088f7bc9b7bc8bfba` | +| `journeys/ar005-manifest-create-report.json` | pass | `c79dacd3c7aa0b78e4c7549a84ddefe171188e169e094726db8fe202b0e3a930` | +| `journeys/ar005-execution-report.json` | pass | `d331c0db31b6d6fde581b029be32a4fe8b46561be3321880797fb8a444aaa525` | +| `journeys/ar005-workflow-block-report.json` | pass | `17e491e94fdaf49c3236c0d62cadd9e425a024fec808ccad2047912413c5699c` | +| `journeys/ar005-manuscript-gap-report.json` | pass | `cd7176e955bdde515f0ef9a1840ae01058d5221d1b72a2cc40db830e8ca85501` | +| `journeys/ar005-m08-ledger.sqlite3` | append-only ledger | `2e5062930eac973cbb2c0dbe0eb39649a8f5bb0481e0a51fe1157a11f84cbbce` | + +## 未完成边界 + +- 没有许可全文人工盲审、真实 LAMMPS 输入/轨迹、独立 seed/环境复验或真实因果机理结论。 +- 没有生产数据库、队列、容器/远程 executor、Web UI、多租户、灾备或外部投稿。 +- synthetic fixture 的数值只用于验证软件计算与 Gate,不应在科研引用或对外材料中使用。 diff --git a/evidence/journeys/ar005-execution-report.json b/evidence/journeys/ar005-execution-report.json new file mode 100644 index 0000000..1dcf87b --- /dev/null +++ b/evidence/journeys/ar005-execution-report.json @@ -0,0 +1,161 @@ +{ + "target": { + "adapter_id": "http:autoresearch-ar005", + "url": "http://127.0.0.1:8137/execution/manifests/manifest_50ef1020804544d8/execute", + "method": "POST" + }, + "profile": { + "agent_id": "autoresearch-ar005", + "adapter_id": "http:autoresearch-ar005", + "revision": 1 + }, + "journey": { + "scenario_id": "ar005.execution.synthetic-compute", + "goal": "执行确定性金属损伤后处理并验证 artifact lineage,同时不外推科研结论", + "actor": { + "role": "customer_simulation" + } + }, + "status": "pass", + "run": { + "run_id": "run_c9cbfb05c2104ad98321e41ddb32955b", + "scenario_id": "ar005.execution.synthetic-compute", + "agent_id": "autoresearch-ar005", + "profile_revision": 1, + "status": "pass", + "step_results": [ + { + "step_id": "request", + "status": "observed", + "observation": { + "output": { + "execution_id": "execution_b320d036557c4f1c", + "manifest_id": "manifest_50ef1020804544d8", + "project_id": "md-metal-ductile-damage-pilot", + "status": "completed", + "actual_execution": true, + "started_at": "2026-08-22T14:32:34.309483Z", + "completed_at": "2026-08-22T14:32:34.318993Z", + "metrics": { + "material": "fcc-Ni", + "temperature_k": 300.0, + "strain_rate_s": 100000000.0, + "loading_mode": "uniaxial_tension", + "observation_count": 8, + "elastic_slope_gpa": 100.0, + "peak_stress_gpa": 6.2, + "peak_stress_strain": 0.08, + "damage_onset_strain": 0.1, + "half_peak_stress_strain": 0.14, + "mechanism_indicators": [ + "dislocation_activity_increased", + "void_fraction_increased", + "lattice_disorder_increased" + ], + "interpretation_boundary": "Indicators are temporal correlations in submitted summaries; they do not by themselves establish a causal damage mechanism." + }, + "artifact_refs": [ + { + "artifact_id": "artifact_execution_b320d036557c4f1c", + "kind": "metal_ductile_damage_result", + "uri": "paper-projects/md-metal-ductile-damage-pilot/04_execution/runs/manifest_50ef1020804544d8/RESULT.json", + "checksum": "sha256:acadcc76056f10cf639b3a32c7e9b2f1fd723b8e60f2c22578fa262c999e9e48", + "summary": "profile=metal_ductile_damage_v1; classification=synthetic_fixture; observations=8" + } + ], + "evidence_ids": [ + "ev_0d0dc10f3f4943c3" + ], + "validation": [ + "strain is strictly increasing", + "all submitted values are finite", + "at least one defect observable is present", + "post-peak stress-loss onset was located" + ], + "error_type": null, + "error_message": null + }, + "status": "ok", + "error": null, + "metadata": { + "status_code": 200 + }, + "observed_at": "2026-08-22T14:32:34.328502+00:00" + }, + "assertion_errors": [], + "evidence_ids": [] + }, + { + "step_id": "outcome", + "status": "validated", + "observation": { + "output": { + "execution_id": "execution_b320d036557c4f1c", + "manifest_id": "manifest_50ef1020804544d8", + "project_id": "md-metal-ductile-damage-pilot", + "status": "completed", + "actual_execution": true, + "started_at": "2026-08-22T14:32:34.309483Z", + "completed_at": "2026-08-22T14:32:34.318993Z", + "metrics": { + "material": "fcc-Ni", + "temperature_k": 300.0, + "strain_rate_s": 100000000.0, + "loading_mode": "uniaxial_tension", + "observation_count": 8, + "elastic_slope_gpa": 100.0, + "peak_stress_gpa": 6.2, + "peak_stress_strain": 0.08, + "damage_onset_strain": 0.1, + "half_peak_stress_strain": 0.14, + "mechanism_indicators": [ + "dislocation_activity_increased", + "void_fraction_increased", + "lattice_disorder_increased" + ], + "interpretation_boundary": "Indicators are temporal correlations in submitted summaries; they do not by themselves establish a causal damage mechanism." + }, + "artifact_refs": [ + { + "artifact_id": "artifact_execution_b320d036557c4f1c", + "kind": "metal_ductile_damage_result", + "uri": "paper-projects/md-metal-ductile-damage-pilot/04_execution/runs/manifest_50ef1020804544d8/RESULT.json", + "checksum": "sha256:acadcc76056f10cf639b3a32c7e9b2f1fd723b8e60f2c22578fa262c999e9e48", + "summary": "profile=metal_ductile_damage_v1; classification=synthetic_fixture; observations=8" + } + ], + "evidence_ids": [ + "ev_0d0dc10f3f4943c3" + ], + "validation": [ + "strain is strictly increasing", + "all submitted values are finite", + "at least one defect observable is present", + "post-peak stress-loss onset was located" + ], + "error_type": null, + "error_message": null + }, + "status": "ok", + "error": null, + "metadata": { + "status_code": 200 + }, + "observed_at": "2026-08-22T14:32:34.328502+00:00" + }, + "assertion_errors": [], + "evidence_ids": [] + } + ], + "finding_ids": [], + "started_at": "2026-08-22T14:32:34.332508+00:00", + "finished_at": "2026-08-22T14:32:34.332508+00:00" + }, + "findings": [], + "ledger_records": 21, + "ledger_path": "F:\\Codex\\AutoResearch\\evidence\\journeys\\ar005-m08-ledger.sqlite3", + "limitations": [ + "This run covers only the configured HTTP journey.", + "No external LLM quality or production write behavior is inferred." + ] +} \ No newline at end of file diff --git a/evidence/journeys/ar005-health-report.json b/evidence/journeys/ar005-health-report.json new file mode 100644 index 0000000..dd9b931 --- /dev/null +++ b/evidence/journeys/ar005-health-report.json @@ -0,0 +1,121 @@ +{ + "target": { + "adapter_id": "http:autoresearch-ar005", + "url": "http://127.0.0.1:8137/health", + "method": "GET" + }, + "profile": { + "agent_id": "autoresearch-ar005", + "adapter_id": "http:autoresearch-ar005", + "revision": 1 + }, + "journey": { + "scenario_id": "ar005.health.five-agent-llm", + "goal": "确认当前 listener 是五 Agent AutoResearch 且受管模型已配置", + "actor": { + "role": "customer_simulation" + } + }, + "status": "pass", + "run": { + "run_id": "run_3629b97419ee4f1c98f3808b0d4bea2a", + "scenario_id": "ar005.health.five-agent-llm", + "agent_id": "autoresearch-ar005", + "profile_revision": 1, + "status": "pass", + "step_results": [ + { + "step_id": "request", + "status": "observed", + "observation": { + "output": { + "ok": true, + "version": "0.1.0", + "python_profile": "3.12+", + "agents": [ + "orchestrator", + "paper_search", + "paper_reader", + "writer", + "reviewer" + ], + "agent_count": 5, + "settings": { + "llm_provider": "openai-compatible", + "llm_model": "deepseek-v4-flash", + "llm_base_url_configured": true, + "llm_api_key_configured": true, + "network_enabled": false, + "db_path": "var\\autoresearch.sqlite3", + "checkpoint_path": "var\\checkpoints.sqlite3" + }, + "projects_dir_exists": true, + "template_dir_exists": true, + "database_ready": true, + "checkpoint_database_ready": true, + "diagnostic_id": "doctor_72527537ce734804" + }, + "status": "ok", + "error": null, + "metadata": { + "status_code": 200 + }, + "observed_at": "2026-08-22T14:32:18.503865+00:00" + }, + "assertion_errors": [], + "evidence_ids": [] + }, + { + "step_id": "outcome", + "status": "validated", + "observation": { + "output": { + "ok": true, + "version": "0.1.0", + "python_profile": "3.12+", + "agents": [ + "orchestrator", + "paper_search", + "paper_reader", + "writer", + "reviewer" + ], + "agent_count": 5, + "settings": { + "llm_provider": "openai-compatible", + "llm_model": "deepseek-v4-flash", + "llm_base_url_configured": true, + "llm_api_key_configured": true, + "network_enabled": false, + "db_path": "var\\autoresearch.sqlite3", + "checkpoint_path": "var\\checkpoints.sqlite3" + }, + "projects_dir_exists": true, + "template_dir_exists": true, + "database_ready": true, + "checkpoint_database_ready": true, + "diagnostic_id": "doctor_72527537ce734804" + }, + "status": "ok", + "error": null, + "metadata": { + "status_code": 200 + }, + "observed_at": "2026-08-22T14:32:18.503865+00:00" + }, + "assertion_errors": [], + "evidence_ids": [] + } + ], + "finding_ids": [], + "started_at": "2026-08-22T14:32:18.508373+00:00", + "finished_at": "2026-08-22T14:32:18.508373+00:00" + }, + "findings": [], + "ledger_records": 5, + "ledger_path": "F:\\Codex\\AutoResearch\\evidence\\journeys\\ar005-m08-ledger.sqlite3", + "limitations": [ + "This run covers only the configured HTTP journey.", + "No external LLM quality or production write behavior is inferred." + ] +} \ No newline at end of file diff --git a/evidence/journeys/ar005-llm-scope-observed-report.json b/evidence/journeys/ar005-llm-scope-observed-report.json new file mode 100644 index 0000000..e532bae --- /dev/null +++ b/evidence/journeys/ar005-llm-scope-observed-report.json @@ -0,0 +1,171 @@ +{ + "target": { + "adapter_id": "http:autoresearch-ar005", + "url": "http://127.0.0.1:8137/runs/run_c3357978832d4b77", + "method": "GET" + }, + "profile": { + "agent_id": "autoresearch-ar005", + "adapter_id": "http:autoresearch-ar005", + "revision": 1 + }, + "journey": { + "scenario_id": "ar005.llm-scope.observed-run", + "goal": "复核已由真实受管 LLM 完成范围深化的 run,并确认无论文时仍 fail closed", + "actor": { + "role": "customer_simulation" + } + }, + "status": "pass", + "run": { + "run_id": "run_5fa283f584f4449b866058d25cd7e5d1", + "scenario_id": "ar005.llm-scope.observed-run", + "agent_id": "autoresearch-ar005", + "profile_revision": 1, + "status": "pass", + "step_results": [ + { + "step_id": "request", + "status": "observed", + "observation": { + "output": { + "run_id": "run_c3357978832d4b77", + "project_id": "md-metal-ductile-damage-pilot", + "status": "blocked", + "state": { + "schema_version": 1, + "project_id": "md-metal-ductile-damage-pilot", + "run_id": "run_c3357978832d4b77", + "idea": "基于分子动力学,分析金属在拉伸载荷下位错演化、空洞形核与长大、晶格无序和应力下降之间的时序关系,识别韧性损伤起始与失稳机理,并明确温度、应变率、晶体取向、尺寸和势函数的适用边界。", + "lifecycle_state": "waiting_evidence", + "run_status": "blocked", + "request_release": false, + "search_queries": [ + "molecular dynamics metal tensile deformation dislocation evolution void nucleation void growth stress drop temporal sequence", + "MD simulation ductile damage initiation mechanism lattice disorder stress-strain curve", + "temperature effect strain rate effect crystal orientation effect tensile failure molecular dynamics", + "sample size effect MD simulation mechanical properties ductile damage", + "embedded atom method potential applicability limits FCC metals tensile deformation" + ], + "seed_papers": [], + "paper_ids": [], + "reading_card_ids": [], + "innovation_ids": [], + "work_package_ids": [ + "wp_3ec82f9c2324446f", + "wp_7926e73f0cb2473c", + "wp_d5feefb5a1bc4a46", + "wp_f72e83c66fde4fa0" + ], + "execution_manifest_ids": [], + "execution_run_ids": [], + "manuscript_id": null, + "review_ids": [], + "evidence_ids": [ + "ev_cdfceeb187cf4956", + "ev_f31740d9fb4f48eb" + ], + "gate_decision_ids": [], + "handoff": null, + "diagnostics": [ + "Managed LLM refined the structured research scope.", + "Network search is disabled; only user-supplied seed papers were processed.", + "No papers were found; downstream reading is blocked instead of inventing records." + ], + "blockers": [ + "No real paper records are available for reading." + ], + "last_agent": "paper_search", + "updated_at": "2026-08-22T14:30:53.163413Z" + }, + "interrupts": [] + }, + "status": "ok", + "error": null, + "metadata": { + "status_code": 200 + }, + "observed_at": "2026-08-22T14:33:09.390679+00:00" + }, + "assertion_errors": [], + "evidence_ids": [] + }, + { + "step_id": "outcome", + "status": "validated", + "observation": { + "output": { + "run_id": "run_c3357978832d4b77", + "project_id": "md-metal-ductile-damage-pilot", + "status": "blocked", + "state": { + "schema_version": 1, + "project_id": "md-metal-ductile-damage-pilot", + "run_id": "run_c3357978832d4b77", + "idea": "基于分子动力学,分析金属在拉伸载荷下位错演化、空洞形核与长大、晶格无序和应力下降之间的时序关系,识别韧性损伤起始与失稳机理,并明确温度、应变率、晶体取向、尺寸和势函数的适用边界。", + "lifecycle_state": "waiting_evidence", + "run_status": "blocked", + "request_release": false, + "search_queries": [ + "molecular dynamics metal tensile deformation dislocation evolution void nucleation void growth stress drop temporal sequence", + "MD simulation ductile damage initiation mechanism lattice disorder stress-strain curve", + "temperature effect strain rate effect crystal orientation effect tensile failure molecular dynamics", + "sample size effect MD simulation mechanical properties ductile damage", + "embedded atom method potential applicability limits FCC metals tensile deformation" + ], + "seed_papers": [], + "paper_ids": [], + "reading_card_ids": [], + "innovation_ids": [], + "work_package_ids": [ + "wp_3ec82f9c2324446f", + "wp_7926e73f0cb2473c", + "wp_d5feefb5a1bc4a46", + "wp_f72e83c66fde4fa0" + ], + "execution_manifest_ids": [], + "execution_run_ids": [], + "manuscript_id": null, + "review_ids": [], + "evidence_ids": [ + "ev_cdfceeb187cf4956", + "ev_f31740d9fb4f48eb" + ], + "gate_decision_ids": [], + "handoff": null, + "diagnostics": [ + "Managed LLM refined the structured research scope.", + "Network search is disabled; only user-supplied seed papers were processed.", + "No papers were found; downstream reading is blocked instead of inventing records." + ], + "blockers": [ + "No real paper records are available for reading." + ], + "last_agent": "paper_search", + "updated_at": "2026-08-22T14:30:53.163413Z" + }, + "interrupts": [] + }, + "status": "ok", + "error": null, + "metadata": { + "status_code": 200 + }, + "observed_at": "2026-08-22T14:33:09.390679+00:00" + }, + "assertion_errors": [], + "evidence_ids": [] + } + ], + "finding_ids": [], + "started_at": "2026-08-22T14:33:09.395081+00:00", + "finished_at": "2026-08-22T14:33:09.395081+00:00" + }, + "findings": [], + "ledger_records": 26, + "ledger_path": "F:\\Codex\\AutoResearch\\evidence\\journeys\\ar005-m08-ledger.sqlite3", + "limitations": [ + "This run covers only the configured HTTP journey.", + "No external LLM quality or production write behavior is inferred." + ] +} \ No newline at end of file diff --git a/evidence/journeys/ar005-llm-scope-report.json b/evidence/journeys/ar005-llm-scope-report.json new file mode 100644 index 0000000..5ac34bc --- /dev/null +++ b/evidence/journeys/ar005-llm-scope-report.json @@ -0,0 +1,66 @@ +{ + "target": { + "adapter_id": "http:autoresearch-ar005", + "url": "http://127.0.0.1:8137/runs", + "method": "POST" + }, + "profile": { + "agent_id": "autoresearch-ar005", + "adapter_id": "http:autoresearch-ar005", + "revision": 1 + }, + "journey": { + "scenario_id": "ar005.llm-scope.no-paper-block", + "goal": "使用真实受管 LLM 深化金属韧性损伤研究范围且无论文时保持阻断", + "actor": { + "role": "customer_simulation" + } + }, + "status": "inconclusive", + "run": { + "run_id": "run_dc17200386e54372a115e36fb5d5009d", + "scenario_id": "ar005.llm-scope.no-paper-block", + "agent_id": "autoresearch-ar005", + "profile_revision": 1, + "status": "inconclusive", + "step_results": [ + { + "step_id": "request", + "status": "unknown", + "observation": { + "output": null, + "status": "unknown", + "error": "transport: timed out", + "metadata": {}, + "observed_at": "2026-08-22T14:32:33.794001+00:00" + }, + "assertion_errors": [], + "evidence_ids": [] + } + ], + "finding_ids": [ + "finding_run_dc17200386e54372a115e36fb5d5009d_request_0124a13a" + ], + "started_at": "2026-08-22T14:32:33.801005+00:00", + "finished_at": "2026-08-22T14:32:33.801005+00:00" + }, + "findings": [ + { + "created_at": "2026-08-22T14:32:33.799005+00:00", + "detail": "transport: timed out", + "evidence_ids": [], + "finding_id": "finding_run_dc17200386e54372a115e36fb5d5009d_request_0124a13a", + "kind": "adapter_error", + "run_id": "run_dc17200386e54372a115e36fb5d5009d", + "severity": "high", + "step_id": "request", + "title": "结果未知,禁止继续放行" + } + ], + "ledger_records": 11, + "ledger_path": "F:\\Codex\\AutoResearch\\evidence\\journeys\\ar005-m08-ledger.sqlite3", + "limitations": [ + "This run covers only the configured HTTP journey.", + "No external LLM quality or production write behavior is inferred." + ] +} \ No newline at end of file diff --git a/evidence/journeys/ar005-m08-ledger.sqlite3 b/evidence/journeys/ar005-m08-ledger.sqlite3 new file mode 100644 index 0000000000000000000000000000000000000000..7fa3a617b78bd514f1b2c2a248b3c2aa21101a44 GIT binary patch literal 90112 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 literal 0 HcmV?d00001 diff --git a/evidence/journeys/ar005-manifest-create-report.json b/evidence/journeys/ar005-manifest-create-report.json new file mode 100644 index 0000000..b106c46 --- /dev/null +++ b/evidence/journeys/ar005-manifest-create-report.json @@ -0,0 +1,121 @@ +{ + "target": { + "adapter_id": "http:autoresearch-ar005", + "url": "http://127.0.0.1:8137/execution/manifests", + "method": "POST" + }, + "profile": { + "agent_id": "autoresearch-ar005", + "adapter_id": "http:autoresearch-ar005", + "revision": 1 + }, + "journey": { + "scenario_id": "ar005.execution.synthetic-register", + "goal": "注册有界 synthetic 金属损伤后处理 manifest 并保留数据分类和输入 hash", + "actor": { + "role": "customer_simulation" + } + }, + "status": "pass", + "run": { + "run_id": "run_1be43e44dbb240389b67fb7c01be26d7", + "scenario_id": "ar005.execution.synthetic-register", + "agent_id": "autoresearch-ar005", + "profile_revision": 1, + "status": "pass", + "step_results": [ + { + "step_id": "request", + "status": "observed", + "observation": { + "output": { + "manifest_id": "manifest_50ef1020804544d8", + "project_id": "md-metal-ductile-damage-pilot", + "work_package_id": null, + "research_run_id": null, + "profile": "metal_ductile_damage_v1", + "data_classification": "synthetic_fixture", + "input_checksum": "sha256:a356d812098484494ac517b66fbfb175344fa89fd4cbba79c858ad640af5a6be", + "source_uri": null, + "source_checksum": null, + "random_seed": 0, + "parameters": {}, + "environment": { + "python": "3.12.10", + "executor": "metal_ductile_damage_v1:1" + }, + "idempotency_key": "ar005:journey:synthetic:001", + "status": "planned", + "execution_id": null, + "output_artifacts": [], + "validation": [ + "registered profile", + "bounded structured input", + "no shell execution" + ], + "created_at": "2026-08-22T14:32:34.045471Z" + }, + "status": "ok", + "error": null, + "metadata": { + "status_code": 201 + }, + "observed_at": "2026-08-22T14:32:34.059477+00:00" + }, + "assertion_errors": [], + "evidence_ids": [] + }, + { + "step_id": "outcome", + "status": "validated", + "observation": { + "output": { + "manifest_id": "manifest_50ef1020804544d8", + "project_id": "md-metal-ductile-damage-pilot", + "work_package_id": null, + "research_run_id": null, + "profile": "metal_ductile_damage_v1", + "data_classification": "synthetic_fixture", + "input_checksum": "sha256:a356d812098484494ac517b66fbfb175344fa89fd4cbba79c858ad640af5a6be", + "source_uri": null, + "source_checksum": null, + "random_seed": 0, + "parameters": {}, + "environment": { + "python": "3.12.10", + "executor": "metal_ductile_damage_v1:1" + }, + "idempotency_key": "ar005:journey:synthetic:001", + "status": "planned", + "execution_id": null, + "output_artifacts": [], + "validation": [ + "registered profile", + "bounded structured input", + "no shell execution" + ], + "created_at": "2026-08-22T14:32:34.045471Z" + }, + "status": "ok", + "error": null, + "metadata": { + "status_code": 201 + }, + "observed_at": "2026-08-22T14:32:34.059477+00:00" + }, + "assertion_errors": [], + "evidence_ids": [] + } + ], + "finding_ids": [], + "started_at": "2026-08-22T14:32:34.062985+00:00", + "finished_at": "2026-08-22T14:32:34.062985+00:00" + }, + "findings": [], + "ledger_records": 16, + "ledger_path": "F:\\Codex\\AutoResearch\\evidence\\journeys\\ar005-m08-ledger.sqlite3", + "limitations": [ + "This run covers only the configured HTTP journey.", + "No external LLM quality or production write behavior is inferred." + ] +} \ No newline at end of file diff --git a/evidence/journeys/ar005-manuscript-gap-report.json b/evidence/journeys/ar005-manuscript-gap-report.json new file mode 100644 index 0000000..7e78126 --- /dev/null +++ b/evidence/journeys/ar005-manuscript-gap-report.json @@ -0,0 +1,139 @@ +{ + "target": { + "adapter_id": "http:autoresearch-ar005", + "url": "http://127.0.0.1:8137/manuscripts/manuscript_095a925913474da4", + "method": "GET" + }, + "profile": { + "agent_id": "autoresearch-ar005", + "adapter_id": "http:autoresearch-ar005", + "revision": 1 + }, + "journey": { + "scenario_id": "ar005.manuscript.synthetic-gap-visible", + "goal": "确认稿件显式保留 synthetic_fixture 缺口且不可发布", + "actor": { + "role": "customer_simulation" + } + }, + "status": "pass", + "run": { + "run_id": "run_578f1b11b27e4aa48f89e99979020680", + "scenario_id": "ar005.manuscript.synthetic-gap-visible", + "agent_id": "autoresearch-ar005", + "profile_revision": 1, + "status": "pass", + "step_results": [ + { + "step_id": "request", + "status": "observed", + "observation": { + "output": { + "manuscript_id": "manuscript_095a925913474da4", + "project_id": "md-metal-ductile-damage-pilot", + "title": "Research draft: 基于分子动力学分析金属韧性损伤中位错演化、空洞形核长大和应力下降的时序关系", + "sections": { + "Abstract": "本稿是证据约束的研究草稿。研究目标、方法和结果须在对应 Gate 通过后才能升级为可发布表述。", + "Introduction": "基于分子动力学分析金属韧性损伤中位错演化、空洞形核长大和应力下降的时序关系", + "Related Work": "- [paper_f46ecbdcf61b4249] The process of fracture in ductile metals involves the nucleation, growth, and linking of voids. (locator: abstract)\n- [paper_1ee7d9d9c77b4cc0] Void coalescence and interaction in dynamic fracture of ductile metals have been investigated using three-dimensional strain-controlled multimillion atom molecular dynamics simulations of copper. (locator: abstract)", + "Proposed Contribution": "- Test whether the proposed idea (基于分子动力学分析金属韧性损伤中位错演化、空洞形核长大和应力下降的时序关系) closes a documented research gap.(状态:hypothesis;验证:Run the planned baseline and ablation work packages; reject the candidate if the claimed benefit is not reproduced or prior work already covers it.)", + "Method": "方法由工作包驱动;每项实现、数据、基线与消融均需产出带校验信息的证据。", + "Experiments": "执行计划应记录环境、输入、版本、指标、随机性与失败结果。", + "Results": "【待真实实验】此处禁止填入推测数值。", + "Discussion": "缺少可追溯实验结果;Results 只能保留待填占位,不得声称性能提升。\n当前执行结果来自 synthetic_fixture,只能验证软件链路,不能作为真实 MD 机理结论。", + "Conclusion": "仅总结已由证据支持的内容;创新性与外部发布仍由审核和人工门禁控制。", + "References": "- [paper_f46ecbdcf61b4249] Void growth in bcc metals simulated with molecular dynamics using the Finnis–Sinclair potential. 2009. 10.1080/14786430903222529\n- [paper_1ee7d9d9c77b4cc0] Three-dimensional molecular dynamics simulations of void coalescence during dynamic fracture of ductile metals. 2005. 10.1103/physrevb.71.064112" + }, + "evidence_ids": [ + "ev_cdfceeb187cf4956", + "ev_f31740d9fb4f48eb", + "ev_0d0dc10f3f4943c3", + "ev_1b9d472ddc034a05", + "ev_071bba1811de45e1", + "ev_6edcc7c6eadf4d26", + "ev_57e2f67458814d1d", + "ev_c3104d9f27a84a0b", + "ev_e29363304c1845e6", + "ev_4c7325795f9246d9" + ], + "unresolved_gaps": [ + "缺少可追溯实验结果;Results 只能保留待填占位,不得声称性能提升。", + "当前执行结果来自 synthetic_fixture,只能验证软件链路,不能作为真实 MD 机理结论。" + ], + "release_ready": false, + "parent_manuscript_id": null, + "revision_note": null + }, + "status": "ok", + "error": null, + "metadata": { + "status_code": 200 + }, + "observed_at": "2026-08-22T14:35:38.739672+00:00" + }, + "assertion_errors": [], + "evidence_ids": [] + }, + { + "step_id": "outcome", + "status": "validated", + "observation": { + "output": { + "manuscript_id": "manuscript_095a925913474da4", + "project_id": "md-metal-ductile-damage-pilot", + "title": "Research draft: 基于分子动力学分析金属韧性损伤中位错演化、空洞形核长大和应力下降的时序关系", + "sections": { + "Abstract": "本稿是证据约束的研究草稿。研究目标、方法和结果须在对应 Gate 通过后才能升级为可发布表述。", + "Introduction": "基于分子动力学分析金属韧性损伤中位错演化、空洞形核长大和应力下降的时序关系", + "Related Work": "- [paper_f46ecbdcf61b4249] The process of fracture in ductile metals involves the nucleation, growth, and linking of voids. (locator: abstract)\n- [paper_1ee7d9d9c77b4cc0] Void coalescence and interaction in dynamic fracture of ductile metals have been investigated using three-dimensional strain-controlled multimillion atom molecular dynamics simulations of copper. (locator: abstract)", + "Proposed Contribution": "- Test whether the proposed idea (基于分子动力学分析金属韧性损伤中位错演化、空洞形核长大和应力下降的时序关系) closes a documented research gap.(状态:hypothesis;验证:Run the planned baseline and ablation work packages; reject the candidate if the claimed benefit is not reproduced or prior work already covers it.)", + "Method": "方法由工作包驱动;每项实现、数据、基线与消融均需产出带校验信息的证据。", + "Experiments": "执行计划应记录环境、输入、版本、指标、随机性与失败结果。", + "Results": "【待真实实验】此处禁止填入推测数值。", + "Discussion": "缺少可追溯实验结果;Results 只能保留待填占位,不得声称性能提升。\n当前执行结果来自 synthetic_fixture,只能验证软件链路,不能作为真实 MD 机理结论。", + "Conclusion": "仅总结已由证据支持的内容;创新性与外部发布仍由审核和人工门禁控制。", + "References": "- [paper_f46ecbdcf61b4249] Void growth in bcc metals simulated with molecular dynamics using the Finnis–Sinclair potential. 2009. 10.1080/14786430903222529\n- [paper_1ee7d9d9c77b4cc0] Three-dimensional molecular dynamics simulations of void coalescence during dynamic fracture of ductile metals. 2005. 10.1103/physrevb.71.064112" + }, + "evidence_ids": [ + "ev_cdfceeb187cf4956", + "ev_f31740d9fb4f48eb", + "ev_0d0dc10f3f4943c3", + "ev_1b9d472ddc034a05", + "ev_071bba1811de45e1", + "ev_6edcc7c6eadf4d26", + "ev_57e2f67458814d1d", + "ev_c3104d9f27a84a0b", + "ev_e29363304c1845e6", + "ev_4c7325795f9246d9" + ], + "unresolved_gaps": [ + "缺少可追溯实验结果;Results 只能保留待填占位,不得声称性能提升。", + "当前执行结果来自 synthetic_fixture,只能验证软件链路,不能作为真实 MD 机理结论。" + ], + "release_ready": false, + "parent_manuscript_id": null, + "revision_note": null + }, + "status": "ok", + "error": null, + "metadata": { + "status_code": 200 + }, + "observed_at": "2026-08-22T14:35:38.739672+00:00" + }, + "assertion_errors": [], + "evidence_ids": [] + } + ], + "finding_ids": [], + "started_at": "2026-08-22T14:35:38.743672+00:00", + "finished_at": "2026-08-22T14:35:38.743672+00:00" + }, + "findings": [], + "ledger_records": 36, + "ledger_path": "F:\\Codex\\AutoResearch\\evidence\\journeys\\ar005-m08-ledger.sqlite3", + "limitations": [ + "This run covers only the configured HTTP journey.", + "No external LLM quality or production write behavior is inferred." + ] +} \ No newline at end of file diff --git a/evidence/journeys/ar005-workflow-block-report.json b/evidence/journeys/ar005-workflow-block-report.json new file mode 100644 index 0000000..d26e6d2 --- /dev/null +++ b/evidence/journeys/ar005-workflow-block-report.json @@ -0,0 +1,295 @@ +{ + "target": { + "adapter_id": "http:autoresearch-ar005", + "url": "http://127.0.0.1:8137/runs/run_b33c1d0b69564e6f", + "method": "GET" + }, + "profile": { + "agent_id": "autoresearch-ar005", + "adapter_id": "http:autoresearch-ar005", + "revision": 1 + }, + "journey": { + "scenario_id": "ar005.workflow.synthetic-result-block", + "goal": "确认真实 HTTP 全流程完成 synthetic 计算但在论文结果门禁保持阻断", + "actor": { + "role": "customer_simulation" + } + }, + "status": "pass", + "run": { + "run_id": "run_408715649fae446da2f7deb3693ad156", + "scenario_id": "ar005.workflow.synthetic-result-block", + "agent_id": "autoresearch-ar005", + "profile_revision": 1, + "status": "pass", + "step_results": [ + { + "step_id": "request", + "status": "observed", + "observation": { + "output": { + "run_id": "run_b33c1d0b69564e6f", + "project_id": "md-metal-ductile-damage-pilot", + "status": "blocked", + "state": { + "schema_version": 1, + "project_id": "md-metal-ductile-damage-pilot", + "run_id": "run_b33c1d0b69564e6f", + "idea": "基于分子动力学分析金属韧性损伤中位错演化、空洞形核长大和应力下降的时序关系", + "lifecycle_state": "waiting_evidence", + "run_status": "blocked", + "request_release": false, + "search_queries": [ + "molecular dynamics metal ductile damage dislocation evolution void nucleation stress drop temporal sequence", + "MD simulation void nucleation dislocation emission stress-strain curve", + "critical dislocation density void nucleation molecular dynamics", + "stress drop signal void growth atomistic simulation", + "strain rate temperature effect dislocation-void interaction MD" + ], + "seed_papers": [ + { + "paper_id": "paper_f46ecbdcf61b4249", + "project_id": "md-metal-ductile-damage-pilot", + "title": "Void growth in bcc metals simulated with molecular dynamics using the Finnis–Sinclair potential", + "abstract": "The process of fracture in ductile metals involves the nucleation, growth, and linking of voids. This process takes place both at the low rates involved in typical engineering applications and at the high rates associated with dynamic fracture processes such as spallation. Here we study the growth of a void in a single crystal at high rates using molecular dynamics (MD) based on Finnis–Sinclair interatomic potentials for the body-centred cubic (bcc) metals V, Nb, Mo, Ta, and W. The use of the Finnis–Sinclair potential enables the study of plasticity associated with void growth at the atomic level at room temperature and strain rates from 109/s down to 106/s and systems as large as 128 million atoms. The atomistic systems are observed to undergo a transition from twinning at the higher end of this range to dislocation flow at the lower end. We analyse the simulations for the specific mechanisms of plasticity associated with void growth as dislocation loops are punched out to accommodate the growing void. We also analyse the process of nucleation and growth of voids in simulations of nanocrystalline Ta expanding at different strain rates. We comment on differences in the plasticity associated with void growth in the bcc metals compared to earlier studies in face-centred cubic (fcc) metals.", + "authors": [ + "Robert E. Rudd" + ], + "year": 2009, + "doi": "10.1080/14786430903222529", + "url": "https://doi.org/10.1080/14786430903222529", + "source": "openalex", + "source_record_id": "https://openalex.org/W2014313073", + "full_text_path": null, + "retrieved_at": "2026-08-22T14:34:14.367093Z" + }, + { + "paper_id": "paper_1ee7d9d9c77b4cc0", + "project_id": "md-metal-ductile-damage-pilot", + "title": "Three-dimensional molecular dynamics simulations of void coalescence during dynamic fracture of ductile metals", + "abstract": "Void coalescence and interaction in dynamic fracture of ductile metals have been investigated using three-dimensional strain-controlled multimillion atom molecular dynamics simulations of copper. The correlated growth of two voids during the coalescence process leading to fracture is investigated, both in terms of its onset and the ensuing dynamical interactions. Void interactions are quantified through the rate of reduction of the distance between the voids, through the correlated directional growth of the voids, and through correlated shape evolution of the voids. The critical intervoid ligament distance marking the onset of coalescence is shown to be approximately one void radius based on the quantification measurements used, independent of the initial separation distance between the voids and the strain rate of the expansion of the system. The interaction of the voids is not reflected in the volumetric asymptotic growth rate of the voids, as demonstrated here. Finally, the practice of using a single void and periodic boundary conditions to study coalescence is examined critically and shown to produce results markedly different than the coalescence of a pair of isolated voids.", + "authors": [ + "E. Seppälä", + "James Belak", + "Robert E. Rudd" + ], + "year": 2005, + "doi": "10.1103/physrevb.71.064112", + "url": "https://doi.org/10.1103/physrevb.71.064112", + "source": "openalex", + "source_record_id": "https://openalex.org/W2051327578", + "full_text_path": null, + "retrieved_at": "2026-08-22T14:34:14.367093Z" + } + ], + "paper_ids": [ + "paper_f46ecbdcf61b4249", + "paper_1ee7d9d9c77b4cc0" + ], + "reading_card_ids": [ + "card_6f9f78819b3d4bd6", + "card_0641cd8cedcf480d" + ], + "innovation_ids": [ + "innovation_5da405c5c16747bd" + ], + "work_package_ids": [ + "wp_7b60ac076cda4ff1", + "wp_11b572fe7d3f466f", + "wp_4b831f25186146aa", + "wp_de27779ac1454ad9" + ], + "execution_manifest_ids": [ + "manifest_5d651346dd864227" + ], + "execution_run_ids": [ + "execution_0edd188fa0284d80" + ], + "manuscript_id": "manuscript_095a925913474da4", + "review_ids": [ + "review_776f402152a44262", + "review_961b0c19aa95449b" + ], + "evidence_ids": [ + "ev_cdfceeb187cf4956", + "ev_f31740d9fb4f48eb", + "ev_0d0dc10f3f4943c3", + "ev_1b9d472ddc034a05", + "ev_071bba1811de45e1", + "ev_6edcc7c6eadf4d26", + "ev_57e2f67458814d1d", + "ev_c3104d9f27a84a0b", + "ev_e29363304c1845e6", + "ev_4c7325795f9246d9" + ], + "gate_decision_ids": [ + "gate_873fc56fde094591", + "gate_c2754f01a668429e" + ], + "handoff": null, + "diagnostics": [ + "Managed LLM refined the structured research scope.", + "Network search is disabled; only user-supplied seed papers were processed.", + "Registered execution execution_0edd188fa0284d80 finished with status=completed." + ], + "blockers": [ + "evidence score 65 is below required 75", + "required work and result lineage are not closed loop" + ], + "last_agent": "reviewer", + "updated_at": "2026-08-22T14:34:33.050323Z" + }, + "interrupts": [] + }, + "status": "ok", + "error": null, + "metadata": { + "status_code": 200 + }, + "observed_at": "2026-08-22T14:35:38.518873+00:00" + }, + "assertion_errors": [], + "evidence_ids": [] + }, + { + "step_id": "outcome", + "status": "validated", + "observation": { + "output": { + "run_id": "run_b33c1d0b69564e6f", + "project_id": "md-metal-ductile-damage-pilot", + "status": "blocked", + "state": { + "schema_version": 1, + "project_id": "md-metal-ductile-damage-pilot", + "run_id": "run_b33c1d0b69564e6f", + "idea": "基于分子动力学分析金属韧性损伤中位错演化、空洞形核长大和应力下降的时序关系", + "lifecycle_state": "waiting_evidence", + "run_status": "blocked", + "request_release": false, + "search_queries": [ + "molecular dynamics metal ductile damage dislocation evolution void nucleation stress drop temporal sequence", + "MD simulation void nucleation dislocation emission stress-strain curve", + "critical dislocation density void nucleation molecular dynamics", + "stress drop signal void growth atomistic simulation", + "strain rate temperature effect dislocation-void interaction MD" + ], + "seed_papers": [ + { + "paper_id": "paper_f46ecbdcf61b4249", + "project_id": "md-metal-ductile-damage-pilot", + "title": "Void growth in bcc metals simulated with molecular dynamics using the Finnis–Sinclair potential", + "abstract": "The process of fracture in ductile metals involves the nucleation, growth, and linking of voids. This process takes place both at the low rates involved in typical engineering applications and at the high rates associated with dynamic fracture processes such as spallation. Here we study the growth of a void in a single crystal at high rates using molecular dynamics (MD) based on Finnis–Sinclair interatomic potentials for the body-centred cubic (bcc) metals V, Nb, Mo, Ta, and W. The use of the Finnis–Sinclair potential enables the study of plasticity associated with void growth at the atomic level at room temperature and strain rates from 109/s down to 106/s and systems as large as 128 million atoms. The atomistic systems are observed to undergo a transition from twinning at the higher end of this range to dislocation flow at the lower end. We analyse the simulations for the specific mechanisms of plasticity associated with void growth as dislocation loops are punched out to accommodate the growing void. We also analyse the process of nucleation and growth of voids in simulations of nanocrystalline Ta expanding at different strain rates. We comment on differences in the plasticity associated with void growth in the bcc metals compared to earlier studies in face-centred cubic (fcc) metals.", + "authors": [ + "Robert E. Rudd" + ], + "year": 2009, + "doi": "10.1080/14786430903222529", + "url": "https://doi.org/10.1080/14786430903222529", + "source": "openalex", + "source_record_id": "https://openalex.org/W2014313073", + "full_text_path": null, + "retrieved_at": "2026-08-22T14:34:14.367093Z" + }, + { + "paper_id": "paper_1ee7d9d9c77b4cc0", + "project_id": "md-metal-ductile-damage-pilot", + "title": "Three-dimensional molecular dynamics simulations of void coalescence during dynamic fracture of ductile metals", + "abstract": "Void coalescence and interaction in dynamic fracture of ductile metals have been investigated using three-dimensional strain-controlled multimillion atom molecular dynamics simulations of copper. The correlated growth of two voids during the coalescence process leading to fracture is investigated, both in terms of its onset and the ensuing dynamical interactions. Void interactions are quantified through the rate of reduction of the distance between the voids, through the correlated directional growth of the voids, and through correlated shape evolution of the voids. The critical intervoid ligament distance marking the onset of coalescence is shown to be approximately one void radius based on the quantification measurements used, independent of the initial separation distance between the voids and the strain rate of the expansion of the system. The interaction of the voids is not reflected in the volumetric asymptotic growth rate of the voids, as demonstrated here. Finally, the practice of using a single void and periodic boundary conditions to study coalescence is examined critically and shown to produce results markedly different than the coalescence of a pair of isolated voids.", + "authors": [ + "E. Seppälä", + "James Belak", + "Robert E. Rudd" + ], + "year": 2005, + "doi": "10.1103/physrevb.71.064112", + "url": "https://doi.org/10.1103/physrevb.71.064112", + "source": "openalex", + "source_record_id": "https://openalex.org/W2051327578", + "full_text_path": null, + "retrieved_at": "2026-08-22T14:34:14.367093Z" + } + ], + "paper_ids": [ + "paper_f46ecbdcf61b4249", + "paper_1ee7d9d9c77b4cc0" + ], + "reading_card_ids": [ + "card_6f9f78819b3d4bd6", + "card_0641cd8cedcf480d" + ], + "innovation_ids": [ + "innovation_5da405c5c16747bd" + ], + "work_package_ids": [ + "wp_7b60ac076cda4ff1", + "wp_11b572fe7d3f466f", + "wp_4b831f25186146aa", + "wp_de27779ac1454ad9" + ], + "execution_manifest_ids": [ + "manifest_5d651346dd864227" + ], + "execution_run_ids": [ + "execution_0edd188fa0284d80" + ], + "manuscript_id": "manuscript_095a925913474da4", + "review_ids": [ + "review_776f402152a44262", + "review_961b0c19aa95449b" + ], + "evidence_ids": [ + "ev_cdfceeb187cf4956", + "ev_f31740d9fb4f48eb", + "ev_0d0dc10f3f4943c3", + "ev_1b9d472ddc034a05", + "ev_071bba1811de45e1", + "ev_6edcc7c6eadf4d26", + "ev_57e2f67458814d1d", + "ev_c3104d9f27a84a0b", + "ev_e29363304c1845e6", + "ev_4c7325795f9246d9" + ], + "gate_decision_ids": [ + "gate_873fc56fde094591", + "gate_c2754f01a668429e" + ], + "handoff": null, + "diagnostics": [ + "Managed LLM refined the structured research scope.", + "Network search is disabled; only user-supplied seed papers were processed.", + "Registered execution execution_0edd188fa0284d80 finished with status=completed." + ], + "blockers": [ + "evidence score 65 is below required 75", + "required work and result lineage are not closed loop" + ], + "last_agent": "reviewer", + "updated_at": "2026-08-22T14:34:33.050323Z" + }, + "interrupts": [] + }, + "status": "ok", + "error": null, + "metadata": { + "status_code": 200 + }, + "observed_at": "2026-08-22T14:35:38.518873+00:00" + }, + "assertion_errors": [], + "evidence_ids": [] + } + ], + "finding_ids": [], + "started_at": "2026-08-22T14:35:38.523387+00:00", + "finished_at": "2026-08-22T14:35:38.523387+00:00" + }, + "findings": [], + "ledger_records": 31, + "ledger_path": "F:\\Codex\\AutoResearch\\evidence\\journeys\\ar005-m08-ledger.sqlite3", + "limitations": [ + "This run covers only the configured HTTP journey.", + "No external LLM quality or production write behavior is inferred." + ] +} \ No newline at end of file diff --git a/paper-projects/md-metal-ductile-damage-pilot/.project-to-act/PROJECT_ACCEPTANCE.md b/paper-projects/md-metal-ductile-damage-pilot/.project-to-act/PROJECT_ACCEPTANCE.md new file mode 100644 index 0000000..238b8f5 --- /dev/null +++ b/paper-projects/md-metal-ductile-damage-pilot/.project-to-act/PROJECT_ACCEPTANCE.md @@ -0,0 +1,43 @@ +# 论文项目验收 + +## 当前验收结论 + +- 结论:AR-005 软件 Pilot 通过;真实金属韧性损伤科研项目未验收 +- 验收范围:领域 Profile、受管 LLM、真实 HTTP 边界、RunManifest、deterministic 后处理、artifact lineage、synthetic Gate +- 最后检查:2026-08-22 +- 遗留问题:许可全文、真实 MD 数据、独立复验、人工 ReadingCard、真实结果稿和本地复现包 + +## 验收标准 + +| 标准 ID | 标准 | 状态 | 验证方法 | 证据 ID | +|---|---|---|---|---| +| PA-001 | project_id、idea、负责人、范围和软件验收已确认 | 已通过 | manifest/overview/intake + validate | PE-M08-001 | +| PA-002 | 领域 Profile 含变量、边界和否证条件 | 已通过 | 合同/领域测试 + charter | PE-M08-001 | +| PA-003 | 受管 LLM 真实调用并保留失败/成功边界 | 已通过 | GLM timeout + DeepSeek observed run | PE-LLM-001 | +| PA-004 | RunManifest 记录输入/环境/seed/status/hash/lineage | 已通过 | 单元、集成、HTTP 旅程和 artifact 检查 | PE-M08-001、PE-HTTP-001 | +| PA-005 | 无效序列、重复 key 变更和失败执行不能产生结果证据 | 已通过 | 负例回归 | PE-M08-001 | +| PA-006 | synthetic fixture 不能关闭论文结果 Gate | 已通过 | 完整 HTTP run + manuscript GET | PE-HTTP-001 | +| PA-007 | 检索协议与真实论文质量人工验收 | 已阻塞 | gold query、许可全文、人工样本 | 无 | +| PA-008 | 真实 MD 工作包、运行和结果独立复验 | 已阻塞 | LAMMPS/环境/checksum/复验审计 | 无 | +| PA-009 | 稿件 claim、数字、图表和引用全绑定真实证据 | 已阻塞 | manuscript claim audit | 无 | +| PA-010 | 本地交付与复现包完整 | 已阻塞 | delivery checklist | 无 | +| PA-011 | 外部投稿有 H3 人工批准 | 未检查 | G9 与外部结果审计 | 无 | + +## 证据索引 + +| 证据 ID | 时间 | 方法或命令 | 退出状态 | 版本或文件哈希 | 结果摘要 | 证据位置 | 有效期 | +|---|---|---|---|---|---|---|---| +| PE-M08-001 | 2026-08-22 | Ruff、25 tests、manifest/idempotency/hash/失败/完整 workflow 回归 | 0 | 收口提交前补 Git commit;artifact 自带 sha256 | M08 软件纵切通过,synthetic E1 | `tests/test_execution_service.py`、`04_execution/runs/` | 代码或 fixture 变化前 | +| PE-LLM-001 | 2026-08-22 | 受管 GLM 调用 + 受管 DeepSeek 真实 run + AAWO 读取复核 | GLM timeout;DeepSeek pass | run `run_c3357978832d4b77` | DeepSeek 完成结构化范围;无论文时 blocked | `evidence/journeys/ar005-llm-scope*.json` | 7 天或模型配置变化前 | +| PE-HTTP-001 | 2026-08-22 | 127.0.0.1:8137 客户旅程,真实题录+synthetic 执行+稿件缺口 | 0 | run `run_b33c1d0b69564e6f` | 执行 completed;稿件 waiting_evidence;6 条 pass、1 条 timeout inconclusive | `evidence/journeys/ar005-*.json`、ledger | HTTP 合同变化前 | + +## Gate 记录 + +| Gate ID | 日期 | Gate | 对象 | 结果 | 证据 ID | 豁免与确认人 | +|---|---|---|---|---|---|---| +| PG-SW-001 | 2026-08-22 | 软件 Pilot | 领域 Profile、M08、HTTP/LLM 测试 | PASS | PE-M08-001、PE-LLM-001、PE-HTTP-001 | 无豁免 | +| PG-G6-001 | 2026-08-22 | 真实结果 | synthetic fixture | REVISE/BLOCKED | PE-HTTP-001 | 无豁免;不得升级 | + +## 验收记录 + +- 2026-08-22|AR-005 软件 Pilot|25 tests、真实 DeepSeek scope、AAWO HTTP 旅程、synthetic manifest/执行/稿件阻断均符合合同|结论:软件纵切通过;真实科研项目仍阻塞于全文与 MD 数据。 diff --git a/paper-projects/md-metal-ductile-damage-pilot/.project-to-act/PROJECT_CONFIG.json b/paper-projects/md-metal-ductile-damage-pilot/.project-to-act/PROJECT_CONFIG.json new file mode 100644 index 0000000..61ca5f3 --- /dev/null +++ b/paper-projects/md-metal-ductile-damage-pilot/.project-to-act/PROJECT_CONFIG.json @@ -0,0 +1,4 @@ +{ + "schema_version": 1, + "mode": "managed" +} diff --git a/paper-projects/md-metal-ductile-damage-pilot/.project-to-act/PROJECT_FEATURES.md b/paper-projects/md-metal-ductile-damage-pilot/.project-to-act/PROJECT_FEATURES.md new file mode 100644 index 0000000..5b1f394 --- /dev/null +++ b/paper-projects/md-metal-ductile-damage-pilot/.project-to-act/PROJECT_FEATURES.md @@ -0,0 +1,31 @@ +# 论文项目功能 + +> 本表只记录本论文项目的实际状态;synthetic 软件证据不等于真实科研完成。 + +## 状态定义 + +- 已规划:已纳入范围但未开始 +- 进行中:已有实现或证据,但完成条件未全部满足 +- 已阻塞:等待真实外部输入或人工决定 +- 已完成:完成条件满足且有新鲜证据 +- 候选:默认关闭或尚未批准 + +## 功能清单 + +| 功能 ID | 功能 | 优先级 | 状态 | 依赖 | 完成条件 | 证据 ID | +|---|---|---|---|---|---|---| +| PF-001 | 项目实例化与 G0 软件范围 | P0 | 已完成 | idea/负责人/边界 | manifest、idea、账本有效且外发关闭 | PE-M08-001 | +| PF-002 | Idea 拆解与深化 | P0 | 进行中 | PF-001 | Profile/LLM 输出经科研负责人确认 | PE-LLM-001 | +| PF-003 | 论文检索、去重与筛选 | P0 | 进行中 | PF-002 | gold query、真实题录、排除理由与召回缺口人工验收 | PE-HTTP-001 | +| PF-004 | 全文总结与阅读陪跑 | P0 | 已阻塞 | PF-003/许可全文 | 阅读卡与问答绑定全文 locator | 无 | +| PF-005 | 综合与创新候选 | P0 | 已阻塞 | PF-004 | 最近似工作、区别与反证检索通过审核 | 无 | +| PF-006 | RunManifest 与确定性执行 | P0 | 进行中 | PF-002 | synthetic 软件纵切已通过;真实 MD 输入和复验未完成 | PE-M08-001、PE-HTTP-001 | +| PF-007 | artifact hash 与 lineage | P0 | 进行中 | PF-006 | 输入/输出/环境/运行/证据/稿件数字可追溯 | PE-M08-001 | +| PF-008 | 稿件生成与缺口保护 | P0 | 进行中 | PF-003,PF-006 | synthetic 缺口可见;真实 claim audit 待补 | PE-HTTP-001 | +| PF-009 | Reviewer 打回与结果 Gate | P0 | 进行中 | PF-008 | synthetic 无法放行已验证;真实复验待补 | PE-HTTP-001 | +| PF-010 | 本地复现交付 | P0 | 已阻塞 | PF-004–PF-009 | 真实数据、许可、复验和交付清单闭环 | 无 | +| PF-011 | 外部投稿/发布 | P2 | 候选 | PF-010 + H3 | 默认关闭;具名人工批准与外部结果审计 | 无 | + +## 功能变更历史 + +- 2026-08-22|PF-001–PF-011|把模板状态替换为 AR-005 实际软件 Pilot 状态;RunManifest/lineage/synthetic Gate 有证据,真实全文/MD/投稿保持未完成|证据:PE-M08-001、PE-LLM-001、PE-HTTP-001|确认来源:用户本轮指令。 diff --git a/paper-projects/md-metal-ductile-damage-pilot/.project-to-act/PROJECT_OVERVIEW.md b/paper-projects/md-metal-ductile-damage-pilot/.project-to-act/PROJECT_OVERVIEW.md new file mode 100644 index 0000000..83fe65e --- /dev/null +++ b/paper-projects/md-metal-ductile-damage-pilot/.project-to-act/PROJECT_OVERVIEW.md @@ -0,0 +1,68 @@ +# 论文项目总览:金属韧性损伤分子动力学机理分析 + +> 本目录是 AR-005 的真实软件 Pilot;研究结论仍必须由真实文献、MD 数据、复验和人工门禁确认。 +> 本目录不得记录密钥、令牌、完整个人信息或未脱敏工具输出。 + +## 基本信息 + +- 项目名称:金属韧性损伤分子动力学机理分析 +- 项目 ID:md-metal-ductile-damage-pilot +- 项目负责人:Codex(软件纵切);真实科研结论负责人待指定 +- 风险等级:T3 / 中高(外部 LLM 与论文元数据、科研结果误表述风险;外部发布关闭) +- 当前阶段:G0/G1 软件纵切已建立,G3–G8 真实科研验收未完成 +- 当前状态:真实受管 LLM 已深化范围;两条 OpenAlex 题录参与 HTTP 纵切;synthetic 后处理已完成并被 Reviewer 正确阻断 +- 最后更新:2026-08-22 + +## 项目目标 + +- 研究金属在拉伸载荷下位错演化、空洞形核/长大、晶格无序与应力下降的时序关系。 +- 明确温度、应变率、晶体取向、尺寸、初始缺陷和势函数对损伤起始与失稳路径的适用边界。 +- 先验证 AutoResearch 的证据治理、RunManifest、artifact lineage、结果门禁和稿件缺口机制;取得真实 MD 数据后再进行科研结论验收。 + +## 范围 + +### 包含 + +- 受管 LLM 的研究问题、变量、否证条件和检索计划深化。 +- 合规论文元数据、授权全文导入、阅读卡、claim-evidence map 和最近似工作对比。 +- 有界的应力-应变、位错密度、空洞体积分数和晶格无序后处理。 +- RunManifest、输入/输出 checksum、幂等、失败记录、artifact lineage、稿件与 Reviewer Gate。 + +### 非目标 + +- 当前 synthetic fixture 不是 LAMMPS/MD 模拟,不证明任何真实材料机理或数值。 +- 不绕过论文访问控制,不把题录/摘要冒充全文,不自主运行高成本模拟。 +- 不自动调参、无限搜索、投稿、公开发布或覆盖原始研究资产。 + +## 技术路线与关键约束 + +- 业务 Agent 固定为总控、论文搜索、论文读取、写作、审核;执行器为确定性服务节点。 +- 领域 Profile 为 `metal_ductile_damage_v1`,输入只接受结构化观测,不开放任意 shell。 +- `synthetic_fixture` 结果最高只登记 E1 软件执行证据;带来源和 checksum 的 `md_trajectory_summary` 才能登记 E2。 +- 机制指标仅表示提交序列中的时序相关,不自动升级为因果结论。 +- 外部发布保持 `external_publication_enabled: false`。 + +## 数据与安全边界 + +- 当前数据:公开 OpenAlex 题录/摘要、受管 LLM 的结构化范围摘要、明确标注的 synthetic fixture、应用运行与审计记录。 +- 待提供数据:许可全文、LAMMPS 输入、势函数版本、轨迹或可信衍生观测、环境与 seed、独立复验结果。 +- 密钥只存在本机受管 `.env.local`,已被 Git 忽略;项目文件只记录 provider/model 和成功/失败状态。 + +## 当前焦点 + +- 下一里程碑:0.2.0-md-evidence,导入带 checksum 的真实 MD 轨迹衍生观测并完成独立复验。 +- 当前工作重点:收口 AR-005 软件纵切、Project-to-Act、HTTP 旅程和证据包。 +- 主要阻塞:尚无授权全文、真实 MD 输出、目标材料/晶向/势函数和科研负责人确认,不能通过 G3–G8。 + +## 按需读取索引 + +| 当前任务 | 追加读取 | +|---|---| +| 规划、实施、阻塞处理 | `PROJECT_PROGRESS.md` | +| 新增、修改、删除功能 | `PROJECT_FEATURES.md` | +| 版本、兼容性 | `PROJECT_VERSIONS.md` | +| 测试、交付、完成声明 | `PROJECT_ACCEPTANCE.md` | + +## 路线变更记录 + +- 2026-08-22|将该项目定位为金属韧性损伤 M08 软件 Pilot,而非已完成真实论文试点|原因:用户指定选题并要求真实 LLM/Agent 测试,但未提供许可全文和真实 MD 数据|影响:synthetic 结果只验证软件链路,G3–G8 保持阻断|证据:PE-M08-001、PE-LLM-001|确认来源:用户本轮指令。 diff --git a/paper-projects/md-metal-ductile-damage-pilot/.project-to-act/PROJECT_PROGRESS.md b/paper-projects/md-metal-ductile-damage-pilot/.project-to-act/PROJECT_PROGRESS.md new file mode 100644 index 0000000..d17579a --- /dev/null +++ b/paper-projects/md-metal-ductile-damage-pilot/.project-to-act/PROJECT_PROGRESS.md @@ -0,0 +1,45 @@ +# 论文项目进度 + +## 当前任务 + +| 任务 | 状态 | 负责人 | 完成条件 | 证据 ID | 最后更新 | +|---|---|---|---|---|---| +| T-000 实例化论文项目 | 已完成 | Codex | manifest、idea、唯一账本和 G0 软件范围有效 | PE-M08-001 | 2026-08-22 | +| T-010 Idea 拆解与深化 | 进行中 | 总控 Agent | 领域 Profile 与真实 LLM 范围已生成;待科研负责人确认 | PE-LLM-001 | 2026-08-22 | +| T-020 文献检索与筛选 | 进行中 | 论文搜索 Agent | HTTP Pilot 使用两条真实 OpenAlex 题录;gold query/许可全文待补 | PE-HTTP-001 | 2026-08-22 | +| T-030 论文读取与创新候选 | 已阻塞 | 论文读取 Agent | 需要许可全文和人工 ReadingCard 基准 | 无 | 2026-08-22 | +| T-040 工作包与研究执行 | 进行中 | Codex/总控 Agent | synthetic 纵切已闭环;真实 MD RunManifest 与复验待补 | PE-M08-001、PE-HTTP-001 | 2026-08-22 | +| T-050 稿件写作与修改 | 进行中 | 写作 Agent | 缺口稿已生成;真实 Results 和 claim audit 待补 | PE-HTTP-001 | 2026-08-22 | +| T-060 审核与本地交付 | 已阻塞 | 审核 Agent | synthetic 缺口已正确打回;真实 G7/G8 未通过 | PE-HTTP-001 | 2026-08-22 | + +## 阻塞项 + +| 阻塞 | 影响 | 解除条件 | 状态 | +|---|---|---|---| +| 无授权全文 | 不能完成定位阅读、方法/限制核验和相关工作覆盖 | 提供开放获取或用户有权使用的全文 | 硬阻塞 G3/G4 | +| 无真实 MD 数据与环境清单 | synthetic 数值不能写入真实 Results | 提供输入/轨迹衍生观测、checksum、势函数、环境和 seed | 硬阻塞 G6–G8 | +| 材料体系与目标 venue 未锁定 | 无法确定对照、指标和稿件格式 | 科研负责人确认材料/晶向/尺寸/势函数与目标 venue | 阻塞真实 Pilot 收口 | + +## 下一步 + +1. 科研负责人确认材料体系、晶向、缺陷、势函数、温度/应变率矩阵和停止条件。 +2. 导入许可全文,建立 gold query、人工 ReadingCard 和最近似工作反例。 +3. 用带 checksum 的真实 `md_trajectory_summary` 重跑 manifest,并由独立环境复验。 +4. 只有 Reviewer 和 deterministic Gate 均通过后,才把真实结果写入交付稿。 + +## 进度历史 + +- 2026-08-22|AR-005 软件纵切|真实 DeepSeek 受管模型完成结构化范围深化;首次 GLM 调用 ReadTimeout 被保留为失败证据;五 Agent HTTP 流程使用两条 OpenAlex 题录和 synthetic fixture,执行完成但稿件被正确阻断|证据:PE-LLM-001、PE-HTTP-001|遗留:无许可全文和真实 MD 结果|下一步:真实数据与人工科研确认。 +- 2026-08-22|项目实例化|创建独立 Project-to-Act、idea、manifest 和本地项目目录|证据:PE-M08-001|下一步:范围与执行纵切。 + + +## AutoResearch 运行投影 + +- 当前 run:run_b33c1d0b69564e6f +- 运行状态:blocked +- 生命周期:waiting_evidence +- 论文/阅读卡:2/2 +- 稿件:manuscript_095a925913474da4 +- 阻塞:evidence score 65 is below required 75;required work and result lineage are not closed loop +- 详细索引:state/RUN_INDEX.jsonl + diff --git a/paper-projects/md-metal-ductile-damage-pilot/.project-to-act/PROJECT_VERSIONS.md b/paper-projects/md-metal-ductile-damage-pilot/.project-to-act/PROJECT_VERSIONS.md new file mode 100644 index 0000000..a1f153d --- /dev/null +++ b/paper-projects/md-metal-ductile-damage-pilot/.project-to-act/PROJECT_VERSIONS.md @@ -0,0 +1,28 @@ +# 论文项目版本 + +## 当前版本 + +- 版本号:`0.1.0-software-pilot` +- 发布状态:本地软件纵切可复核;不是科研结果版本,也不可投稿 +- 兼容性说明:Python 3.12、SQLite、五 Agent AutoResearch;执行 Profile 为 `metal_ductile_damage_v1` +- 最后更新:2026-08-22 + +## 下一版本计划 + +- 目标版本:`0.2.0-md-evidence` +- 计划内容:许可全文、真实 MD 轨迹衍生观测、环境/seed/势函数版本、独立复验和人工 ReadingCard。 +- 发布条件:G3–G6 通过;真实结果与 synthetic fixture 在数据、证据和稿件中完全分离。 + +## 版本路线 + +| 版本 | 内容 | Gate | +|---|---|---| +| `0.1.0-software-pilot` | 领域范围、真实 LLM、RunManifest、synthetic 计算与阻断稿件 | 软件 Gate | +| `0.2.0-md-evidence` | 许可全文、真实 MD 输入与独立复验 | G3–G6 | +| `0.3.0-draft` | 真实结果稿、claim audit、引用一致性 | G7 | +| `1.0.0-local` | 人工审核通过的本地复现包 | G8 | +| `1.1.0-submitted` | 可选外部投稿 | G9 + H3 | + +## 版本历史 + +- `0.1.0-software-pilot`|2026-08-22|本地可复核|受管 LLM、真实题录、M08 deterministic execution、synthetic 边界和 Reviewer 打回已验证|证据:PE-M08-001、PE-LLM-001、PE-HTTP-001|Gate:软件 Pilot PASS,科研 G3–G8 未通过。 diff --git a/paper-projects/md-metal-ductile-damage-pilot/00_intake/IDEA.md b/paper-projects/md-metal-ductile-damage-pilot/00_intake/IDEA.md new file mode 100644 index 0000000..27b52d9 --- /dev/null +++ b/paper-projects/md-metal-ductile-damage-pilot/00_intake/IDEA.md @@ -0,0 +1,3 @@ +# 原始 Idea + +基于分子动力学,分析金属在拉伸载荷下位错演化、空洞形核与长大、晶格无序和应力下降之间的时序关系,识别韧性损伤起始与失稳机理,并明确温度、应变率、晶体取向、尺寸和势函数的适用边界。 diff --git a/paper-projects/md-metal-ductile-damage-pilot/01_scope/RESEARCH_CHARTER.md b/paper-projects/md-metal-ductile-damage-pilot/01_scope/RESEARCH_CHARTER.md new file mode 100644 index 0000000..87c3f82 --- /dev/null +++ b/paper-projects/md-metal-ductile-damage-pilot/01_scope/RESEARCH_CHARTER.md @@ -0,0 +1,35 @@ +# 研究章程 + +## 研究问题 + +在给定材料、晶体取向、势函数、温度、应变率、尺寸和边界条件下,金属拉伸从弹性、屈服、塑性局域化到失稳的过程中,位错活动、空洞形核/长大、晶格无序和应力下降呈现怎样的时序关系?这些关系在何种条件变化后仍可重复? + +## 候选假设与可证伪条件 + +| 假设 ID | 假设 | 支持条件 | 否证条件 | 状态 | +|---|---|---|---|---| +| HYP-001 | 位错活动增加先于明显应力下降并参与塑性耗散 | 位错密度变化在多次独立运行中稳定领先损伤起始 | 位错变化不领先应力下降,或在对照中与失稳无关 | 候选 | +| HYP-002 | 空洞体积分数持续增加与峰后承载下降相关 | 空洞指标在峰后持续增长且对缺陷/三轴度对照敏感 | 去除缺陷或改变三轴度后仍无稳定差异 | 候选 | +| HYP-003 | 晶格无序可作为局部损伤演化指标之一 | 无序指标与局域塑性/空洞演化在空间和时间上共同定位 | 指标只受温控、边界或势函数伪影驱动 | 候选 | + +## 变量与边界 + +- 自变量:材料/晶向、势函数版本、温度 K、应变率 s^-1、尺寸、边界条件、初始缺陷、随机 seed。 +- 因变量:应力-应变响应 GPa、峰值应力/应变、损伤起始应变、半峰值承载应变、位错密度 m^-2、空洞体积分数、centrosymmetry/晶格无序。 +- 控制变量:时间步长、最小化/平衡流程、加载方式、thermostat/barostat、dump cadence、后处理版本。 +- 适用范围:原子尺度模拟与已登记的材料/势函数/尺度/加载条件;不得无依据外推到宏观构件。 +- 非目标:自动寻优、无限参数搜索、用单一指标宣称因果机理、用 synthetic fixture 形成科研结论。 + +## 评价标准 + +1. 每次真实分析有 RunManifest、输入/输出 checksum、环境、seed 和 artifact lineage。 +2. 损伤起始、峰值和失稳指标在独立 seed/环境中复现,并报告失败或不一致。 +3. 位错、空洞和无序指标的时序只先形成相关性结论;因果表述需要对照/消融。 +4. 论文关键 claim、数字、图表和限制全部绑定有效 evidence ID。 + +## 证据策略 + +- 公开题录/摘要只支持检索与候选筛选,不替代全文证据。 +- synthetic fixture 登记为 E1 软件证据,不能关闭 Results Gate。 +- 带来源 URI 与匹配 checksum 的 MD 轨迹衍生观测可登记为 E2;独立复验与原始 artifact 齐全后再评估更高等级。 +- Reviewer 与确定性 Gate 共同控制稿件升级;LLM 不得覆盖证据等级或人工批准。 diff --git a/paper-projects/md-metal-ductile-damage-pilot/02_literature/READING_QUEUE.yaml b/paper-projects/md-metal-ductile-damage-pilot/02_literature/READING_QUEUE.yaml new file mode 100644 index 0000000..b8c0d24 --- /dev/null +++ b/paper-projects/md-metal-ductile-damage-pilot/02_literature/READING_QUEUE.yaml @@ -0,0 +1,13 @@ +schema_version: "1.0" +project_id: "PAPER-TEMPLATE" +items: [] +allowed_statuses: + - queued + - fulltext_ready + - structure_extracted + - section_reading + - summary_draft + - claim_map_ready + - innovation_scan + - reviewed + - published_to_paper_library diff --git a/paper-projects/md-metal-ductile-damage-pilot/02_literature/SEARCH_PROTOCOL.yaml b/paper-projects/md-metal-ductile-damage-pilot/02_literature/SEARCH_PROTOCOL.yaml new file mode 100644 index 0000000..b43fd8c --- /dev/null +++ b/paper-projects/md-metal-ductile-damage-pilot/02_literature/SEARCH_PROTOCOL.yaml @@ -0,0 +1,22 @@ +schema_version: "1.0" +research_question_ref: "待填写" +search_version: 1 +sources: + - openalex + - crossref + - semantic_scholar +domain_sources: [] +queries: [] +date_range: null +languages: [] +include_criteria: [] +exclude_criteria: [] +dedupe_keys: + - doi + - openalex_id + - title_author_year +retraction_and_correction_check: true +open_access_preferred: true +fulltext_policy: "open_or_user_authorized_only" +stop_condition: "待填写" +approved_gate_id: null diff --git a/paper-projects/md-metal-ductile-damage-pilot/03_innovation/INNOVATION_MAP.md b/paper-projects/md-metal-ductile-damage-pilot/03_innovation/INNOVATION_MAP.md new file mode 100644 index 0000000..f7e80bd --- /dev/null +++ b/paper-projects/md-metal-ductile-damage-pilot/03_innovation/INNOVATION_MAP.md @@ -0,0 +1,7 @@ +# 创新候选图 + +> 所有条目初始均为“候选”,不得直接写成已证明创新。 + +| 候选 ID | 一句话主张 | 最近似工作 | 区别特征 | 最小实验 | 反证条件 | 证据包 | 审核状态 | +|---|---|---|---|---|---|---|---| +| NOV-001 | 待填写 | 待填写 | 待填写 | 待填写 | 待填写 | 无 | UNKNOWN | diff --git a/paper-projects/md-metal-ductile-damage-pilot/04_execution/SYNTHETIC_FIXTURE.json b/paper-projects/md-metal-ductile-damage-pilot/04_execution/SYNTHETIC_FIXTURE.json new file mode 100644 index 0000000..b1e9e21 --- /dev/null +++ b/paper-projects/md-metal-ductile-damage-pilot/04_execution/SYNTHETIC_FIXTURE.json @@ -0,0 +1,19 @@ +{ + "material": "fcc-Ni", + "temperature_k": 300, + "strain_rate_s": 100000000, + "loading_mode": "uniaxial_tension", + "data_classification": "synthetic_fixture", + "source_uri": null, + "source_checksum": null, + "observations": [ + {"strain": 0.00, "stress_gpa": 0.0, "dislocation_density_m2": 1000000000000, "void_fraction": 0.000, "centrosymmetry": 0.2}, + {"strain": 0.02, "stress_gpa": 2.1, "dislocation_density_m2": 2000000000000, "void_fraction": 0.000, "centrosymmetry": 0.3}, + {"strain": 0.04, "stress_gpa": 4.0, "dislocation_density_m2": 3000000000000, "void_fraction": 0.000, "centrosymmetry": 0.4}, + {"strain": 0.06, "stress_gpa": 5.5, "dislocation_density_m2": 4000000000000, "void_fraction": 0.001, "centrosymmetry": 0.5}, + {"strain": 0.08, "stress_gpa": 6.2, "dislocation_density_m2": 5000000000000, "void_fraction": 0.002, "centrosymmetry": 0.6}, + {"strain": 0.10, "stress_gpa": 5.8, "dislocation_density_m2": 6000000000000, "void_fraction": 0.003, "centrosymmetry": 0.7}, + {"strain": 0.12, "stress_gpa": 4.5, "dislocation_density_m2": 7000000000000, "void_fraction": 0.004, "centrosymmetry": 0.8}, + {"strain": 0.14, "stress_gpa": 2.8, "dislocation_density_m2": 8000000000000, "void_fraction": 0.005, "centrosymmetry": 0.9} + ] +} diff --git a/paper-projects/md-metal-ductile-damage-pilot/04_execution/WORK_PACKAGES.yaml b/paper-projects/md-metal-ductile-damage-pilot/04_execution/WORK_PACKAGES.yaml new file mode 100644 index 0000000..227e031 --- /dev/null +++ b/paper-projects/md-metal-ductile-damage-pilot/04_execution/WORK_PACKAGES.yaml @@ -0,0 +1,55 @@ +schema_version: "1.0" +project_id: "md-metal-ductile-damage-pilot" +work_packages: + - work_package_id: WP1-LITERATURE + objective: 建立金属韧性损伤分子动力学研究的可定位文献证据图 + non_goals: [不绕过全文许可, 不把题录摘要冒充全文] + input_refs: [00_intake/IDEA.md, 01_scope/RESEARCH_CHARTER.md] + output_contract: Search ledger, ReadingCards, claim-evidence map + acceptance_checks: [gold query 人工验收, 关键机制 claim 有全文 locator] + dependencies: [] + risk_level: L2 + evidence_policy_id: paper-locator-v1 + effort_estimate_and_assumptions: 需许可全文和科研负责人抽检 + rollback: 保留原始检索与排除记录,撤销错误筛选 revision + owner: paper_search/paper_reader + status: blocked + - work_package_id: WP2-MD-PROTOCOL + objective: 固化材料、势函数、边界、平衡、加载和观测协议 + non_goals: [不进行无限参数寻优, 不自动启动高成本模拟] + input_refs: [01_scope/RESEARCH_CHARTER.md] + output_contract: MD protocol, environment manifest, input checksums + acceptance_checks: [所有物理量带单位, 势函数与环境版本可追溯] + dependencies: [WP1-LITERATURE] + risk_level: L3 + evidence_policy_id: md-protocol-v1 + effort_estimate_and_assumptions: 需科研负责人和真实计算资源边界 + rollback: 停止运行并保留失败 manifest,不覆盖原始输入 + owner: orchestrator/human-researcher + status: blocked + - work_package_id: WP3-DAMAGE-ANALYSIS + objective: 对登记的轨迹衍生观测执行确定性损伤后处理并保存 lineage + non_goals: [不开放任意 shell, 不把相关性自动写成因果] + input_refs: [04_execution/SYNTHETIC_FIXTURE.json] + output_contract: RunManifest, RESULT.json, evidence item, checksums + acceptance_checks: [单调应变与有限值校验, 幂等, 输入输出 hash, synthetic 显式标记] + dependencies: [WP2-MD-PROTOCOL] + risk_level: L2 + evidence_policy_id: metal-ductile-damage-v1 + effort_estimate_and_assumptions: 当前仅 synthetic 软件 fixture;真实 MD 待提供 + rollback: 失败状态 append-only 保存,不生成结果证据 + owner: execution_service + status: in_progress + - work_package_id: WP4-MANUSCRIPT + objective: 生成只消费已放行证据的缺口稿与审核记录 + non_goals: [不填补缺失结果, 不自动投稿] + input_refs: [WP1-LITERATURE, WP3-DAMAGE-ANALYSIS] + output_contract: manuscript revision, review report, GateDecision + acceptance_checks: [synthetic 不能关闭 Results Gate, 数字和引用可追溯] + dependencies: [WP1-LITERATURE, WP3-DAMAGE-ANALYSIS] + risk_level: L3 + evidence_policy_id: manuscript-result-v1 + effort_estimate_and_assumptions: 真实结果与全文证据尚未提供 + rollback: 保留原稿和审核历史,定向回退到证据或执行阶段 + owner: writer/reviewer + status: blocked diff --git a/paper-projects/md-metal-ductile-damage-pilot/04_execution/runs/manifest_16b49559507942be/RESULT.json b/paper-projects/md-metal-ductile-damage-pilot/04_execution/runs/manifest_16b49559507942be/RESULT.json new file mode 100644 index 0000000..bdd1e91 --- /dev/null +++ b/paper-projects/md-metal-ductile-damage-pilot/04_execution/runs/manifest_16b49559507942be/RESULT.json @@ -0,0 +1,30 @@ +{ + "manifest_id": "manifest_16b49559507942be", + "profile": "metal_ductile_damage_v1", + "data_classification": "synthetic_fixture", + "input_checksum": "sha256:a356d812098484494ac517b66fbfb175344fa89fd4cbba79c858ad640af5a6be", + "metrics": { + "material": "fcc-Ni", + "temperature_k": 300.0, + "strain_rate_s": 100000000.0, + "loading_mode": "uniaxial_tension", + "observation_count": 8, + "elastic_slope_gpa": 100.0, + "peak_stress_gpa": 6.2, + "peak_stress_strain": 0.08, + "damage_onset_strain": 0.1, + "half_peak_stress_strain": 0.14, + "mechanism_indicators": [ + "dislocation_activity_increased", + "void_fraction_increased", + "lattice_disorder_increased" + ], + "interpretation_boundary": "Indicators are temporal correlations in submitted summaries; they do not by themselves establish a causal damage mechanism." + }, + "validation": [ + "strain is strictly increasing", + "all submitted values are finite", + "at least one defect observable is present", + "post-peak stress-loss onset was located" + ] +} diff --git a/paper-projects/md-metal-ductile-damage-pilot/04_execution/runs/manifest_16b49559507942be/RUN_MANIFEST.json b/paper-projects/md-metal-ductile-damage-pilot/04_execution/runs/manifest_16b49559507942be/RUN_MANIFEST.json new file mode 100644 index 0000000..52819e7 --- /dev/null +++ b/paper-projects/md-metal-ductile-damage-pilot/04_execution/runs/manifest_16b49559507942be/RUN_MANIFEST.json @@ -0,0 +1,39 @@ +{ + "manifest_id": "manifest_16b49559507942be", + "project_id": "md-metal-ductile-damage-pilot", + "work_package_id": null, + "research_run_id": null, + "profile": "metal_ductile_damage_v1", + "data_classification": "synthetic_fixture", + "input_checksum": "sha256:a356d812098484494ac517b66fbfb175344fa89fd4cbba79c858ad640af5a6be", + "source_uri": null, + "source_checksum": null, + "random_seed": 0, + "parameters": {}, + "environment": { + "python": "3.12.10", + "executor": "metal_ductile_damage_v1:1" + }, + "idempotency_key": "ar005:cli:synthetic:001", + "status": "completed", + "execution_id": "execution_b10aefcbfc3a4876", + "output_artifacts": [ + { + "artifact_id": "artifact_execution_b10aefcbfc3a4876", + "kind": "metal_ductile_damage_result", + "uri": "paper-projects/md-metal-ductile-damage-pilot/04_execution/runs/manifest_16b49559507942be/RESULT.json", + "checksum": "sha256:2b15685e405902c0277f3e9b6ead8035472e4095607242b261acfd3275ea48a3", + "summary": "profile=metal_ductile_damage_v1; classification=synthetic_fixture; observations=8" + } + ], + "validation": [ + "registered profile", + "bounded structured input", + "no shell execution", + "strain is strictly increasing", + "all submitted values are finite", + "at least one defect observable is present", + "post-peak stress-loss onset was located" + ], + "created_at": "2026-08-22T14:41:21.253103Z" +} diff --git a/paper-projects/md-metal-ductile-damage-pilot/04_execution/runs/manifest_50ef1020804544d8/RESULT.json b/paper-projects/md-metal-ductile-damage-pilot/04_execution/runs/manifest_50ef1020804544d8/RESULT.json new file mode 100644 index 0000000..7585bb4 --- /dev/null +++ b/paper-projects/md-metal-ductile-damage-pilot/04_execution/runs/manifest_50ef1020804544d8/RESULT.json @@ -0,0 +1,30 @@ +{ + "manifest_id": "manifest_50ef1020804544d8", + "profile": "metal_ductile_damage_v1", + "data_classification": "synthetic_fixture", + "input_checksum": "sha256:a356d812098484494ac517b66fbfb175344fa89fd4cbba79c858ad640af5a6be", + "metrics": { + "material": "fcc-Ni", + "temperature_k": 300.0, + "strain_rate_s": 100000000.0, + "loading_mode": "uniaxial_tension", + "observation_count": 8, + "elastic_slope_gpa": 100.0, + "peak_stress_gpa": 6.2, + "peak_stress_strain": 0.08, + "damage_onset_strain": 0.1, + "half_peak_stress_strain": 0.14, + "mechanism_indicators": [ + "dislocation_activity_increased", + "void_fraction_increased", + "lattice_disorder_increased" + ], + "interpretation_boundary": "Indicators are temporal correlations in submitted summaries; they do not by themselves establish a causal damage mechanism." + }, + "validation": [ + "strain is strictly increasing", + "all submitted values are finite", + "at least one defect observable is present", + "post-peak stress-loss onset was located" + ] +} diff --git a/paper-projects/md-metal-ductile-damage-pilot/04_execution/runs/manifest_50ef1020804544d8/RUN_MANIFEST.json b/paper-projects/md-metal-ductile-damage-pilot/04_execution/runs/manifest_50ef1020804544d8/RUN_MANIFEST.json new file mode 100644 index 0000000..a42a66f --- /dev/null +++ b/paper-projects/md-metal-ductile-damage-pilot/04_execution/runs/manifest_50ef1020804544d8/RUN_MANIFEST.json @@ -0,0 +1,39 @@ +{ + "manifest_id": "manifest_50ef1020804544d8", + "project_id": "md-metal-ductile-damage-pilot", + "work_package_id": null, + "research_run_id": null, + "profile": "metal_ductile_damage_v1", + "data_classification": "synthetic_fixture", + "input_checksum": "sha256:a356d812098484494ac517b66fbfb175344fa89fd4cbba79c858ad640af5a6be", + "source_uri": null, + "source_checksum": null, + "random_seed": 0, + "parameters": {}, + "environment": { + "python": "3.12.10", + "executor": "metal_ductile_damage_v1:1" + }, + "idempotency_key": "ar005:journey:synthetic:001", + "status": "completed", + "execution_id": "execution_b320d036557c4f1c", + "output_artifacts": [ + { + "artifact_id": "artifact_execution_b320d036557c4f1c", + "kind": "metal_ductile_damage_result", + "uri": "paper-projects/md-metal-ductile-damage-pilot/04_execution/runs/manifest_50ef1020804544d8/RESULT.json", + "checksum": "sha256:acadcc76056f10cf639b3a32c7e9b2f1fd723b8e60f2c22578fa262c999e9e48", + "summary": "profile=metal_ductile_damage_v1; classification=synthetic_fixture; observations=8" + } + ], + "validation": [ + "registered profile", + "bounded structured input", + "no shell execution", + "strain is strictly increasing", + "all submitted values are finite", + "at least one defect observable is present", + "post-peak stress-loss onset was located" + ], + "created_at": "2026-08-22T14:32:34.045471Z" +} diff --git a/paper-projects/md-metal-ductile-damage-pilot/04_execution/runs/manifest_5d651346dd864227/RESULT.json b/paper-projects/md-metal-ductile-damage-pilot/04_execution/runs/manifest_5d651346dd864227/RESULT.json new file mode 100644 index 0000000..0b931a1 --- /dev/null +++ b/paper-projects/md-metal-ductile-damage-pilot/04_execution/runs/manifest_5d651346dd864227/RESULT.json @@ -0,0 +1,30 @@ +{ + "manifest_id": "manifest_5d651346dd864227", + "profile": "metal_ductile_damage_v1", + "data_classification": "synthetic_fixture", + "input_checksum": "sha256:a356d812098484494ac517b66fbfb175344fa89fd4cbba79c858ad640af5a6be", + "metrics": { + "material": "fcc-Ni", + "temperature_k": 300.0, + "strain_rate_s": 100000000.0, + "loading_mode": "uniaxial_tension", + "observation_count": 8, + "elastic_slope_gpa": 100.0, + "peak_stress_gpa": 6.2, + "peak_stress_strain": 0.08, + "damage_onset_strain": 0.1, + "half_peak_stress_strain": 0.14, + "mechanism_indicators": [ + "dislocation_activity_increased", + "void_fraction_increased", + "lattice_disorder_increased" + ], + "interpretation_boundary": "Indicators are temporal correlations in submitted summaries; they do not by themselves establish a causal damage mechanism." + }, + "validation": [ + "strain is strictly increasing", + "all submitted values are finite", + "at least one defect observable is present", + "post-peak stress-loss onset was located" + ] +} diff --git a/paper-projects/md-metal-ductile-damage-pilot/04_execution/runs/manifest_5d651346dd864227/RUN_MANIFEST.json b/paper-projects/md-metal-ductile-damage-pilot/04_execution/runs/manifest_5d651346dd864227/RUN_MANIFEST.json new file mode 100644 index 0000000..7d9a60d --- /dev/null +++ b/paper-projects/md-metal-ductile-damage-pilot/04_execution/runs/manifest_5d651346dd864227/RUN_MANIFEST.json @@ -0,0 +1,39 @@ +{ + "manifest_id": "manifest_5d651346dd864227", + "project_id": "md-metal-ductile-damage-pilot", + "work_package_id": "wp_4b831f25186146aa", + "research_run_id": "run_b33c1d0b69564e6f", + "profile": "metal_ductile_damage_v1", + "data_classification": "synthetic_fixture", + "input_checksum": "sha256:a356d812098484494ac517b66fbfb175344fa89fd4cbba79c858ad640af5a6be", + "source_uri": null, + "source_checksum": null, + "random_seed": 0, + "parameters": {}, + "environment": { + "python": "3.12.10", + "executor": "metal_ductile_damage_v1:1" + }, + "idempotency_key": "ar005:workflow:synthetic:001", + "status": "completed", + "execution_id": "execution_0edd188fa0284d80", + "output_artifacts": [ + { + "artifact_id": "artifact_execution_0edd188fa0284d80", + "kind": "metal_ductile_damage_result", + "uri": "paper-projects/md-metal-ductile-damage-pilot/04_execution/runs/manifest_5d651346dd864227/RESULT.json", + "checksum": "sha256:f5239017c31d35bc534e731e5bebc3c33c8df99c2a74cfd3ec452bb1f6b20f00", + "summary": "profile=metal_ductile_damage_v1; classification=synthetic_fixture; observations=8" + } + ], + "validation": [ + "registered profile", + "bounded structured input", + "no shell execution", + "strain is strictly increasing", + "all submitted values are finite", + "at least one defect observable is present", + "post-peak stress-loss onset was located" + ], + "created_at": "2026-08-22T14:34:14.370710Z" +} diff --git a/paper-projects/md-metal-ductile-damage-pilot/05_writing/MANUSCRIPT_DRAFT.md b/paper-projects/md-metal-ductile-damage-pilot/05_writing/MANUSCRIPT_DRAFT.md new file mode 100644 index 0000000..2478923 --- /dev/null +++ b/paper-projects/md-metal-ductile-damage-pilot/05_writing/MANUSCRIPT_DRAFT.md @@ -0,0 +1,49 @@ +# Research draft: 基于分子动力学分析金属韧性损伤中位错演化、空洞形核长大和应力下降的时序关系 + +## Abstract + +本稿是证据约束的研究草稿。研究目标、方法和结果须在对应 Gate 通过后才能升级为可发布表述。 + +## Introduction + +基于分子动力学分析金属韧性损伤中位错演化、空洞形核长大和应力下降的时序关系 + +## Related Work + +- [paper_f46ecbdcf61b4249] The process of fracture in ductile metals involves the nucleation, growth, and linking of voids. (locator: abstract) +- [paper_1ee7d9d9c77b4cc0] Void coalescence and interaction in dynamic fracture of ductile metals have been investigated using three-dimensional strain-controlled multimillion atom molecular dynamics simulations of copper. (locator: abstract) + +## Proposed Contribution + +- Test whether the proposed idea (基于分子动力学分析金属韧性损伤中位错演化、空洞形核长大和应力下降的时序关系) closes a documented research gap.(状态:hypothesis;验证:Run the planned baseline and ablation work packages; reject the candidate if the claimed benefit is not reproduced or prior work already covers it.) + +## Method + +方法由工作包驱动;每项实现、数据、基线与消融均需产出带校验信息的证据。 + +## Experiments + +执行计划应记录环境、输入、版本、指标、随机性与失败结果。 + +## Results + +【待真实实验】此处禁止填入推测数值。 + +## Discussion + +缺少可追溯实验结果;Results 只能保留待填占位,不得声称性能提升。 +当前执行结果来自 synthetic_fixture,只能验证软件链路,不能作为真实 MD 机理结论。 + +## Conclusion + +仅总结已由证据支持的内容;创新性与外部发布仍由审核和人工门禁控制。 + +## References + +- [paper_f46ecbdcf61b4249] Void growth in bcc metals simulated with molecular dynamics using the Finnis–Sinclair potential. 2009. 10.1080/14786430903222529 +- [paper_1ee7d9d9c77b4cc0] Three-dimensional molecular dynamics simulations of void coalescence during dynamic fracture of ductile metals. 2005. 10.1103/physrevb.71.064112 + +## Unresolved Evidence Gaps + +- 缺少可追溯实验结果;Results 只能保留待填占位,不得声称性能提升。 +- 当前执行结果来自 synthetic_fixture,只能验证软件链路,不能作为真实 MD 机理结论。 diff --git a/paper-projects/md-metal-ductile-damage-pilot/05_writing/OUTLINE.md b/paper-projects/md-metal-ductile-damage-pilot/05_writing/OUTLINE.md new file mode 100644 index 0000000..4a9c343 --- /dev/null +++ b/paper-projects/md-metal-ductile-damage-pilot/05_writing/OUTLINE.md @@ -0,0 +1,25 @@ +# 论文提纲 + +> 写作 Agent 只能引用已审核 claim 和真实运行结果。没有证据的位置使用显式占位符,不得补造。 + +## 标题候选 + +- 待填写。 + +## 摘要论证链 + +- 问题:待填写 +- 缺口:待填写 +- 方法:待填写 +- 结果:待真实运行后填写 +- 贡献:待审核 + +## 正文结构 + +1. Introduction +2. Related Work +3. Method +4. Experimental Setup +5. Results +6. Discussion and Limitations +7. Conclusion diff --git a/paper-projects/md-metal-ductile-damage-pilot/06_review/REVIEW_LOG.md b/paper-projects/md-metal-ductile-damage-pilot/06_review/REVIEW_LOG.md new file mode 100644 index 0000000..3b921cb --- /dev/null +++ b/paper-projects/md-metal-ductile-damage-pilot/06_review/REVIEW_LOG.md @@ -0,0 +1,5 @@ +# 审核与修订日志 + +| 审核 ID | 日期 | 对象/版本 | 问题级别 | 结论 | 回退目标 | 复验条件 | Gate ID | +|---|---|---|---|---|---|---|---| +| REV-001 | 未记录 | 未定义 | 未分级 | 未审核 | 无 | 未定义 | 无 | diff --git a/paper-projects/md-metal-ductile-damage-pilot/07_delivery/DELIVERY_CHECKLIST.md b/paper-projects/md-metal-ductile-damage-pilot/07_delivery/DELIVERY_CHECKLIST.md new file mode 100644 index 0000000..181d2f8 --- /dev/null +++ b/paper-projects/md-metal-ductile-damage-pilot/07_delivery/DELIVERY_CHECKLIST.md @@ -0,0 +1,11 @@ +# 本地交付清单 + +- [ ] 稿件源文件与渲染文件一致 +- [ ] 所有关键 claim 绑定有效 evidence ID +- [ ] 引用可解析、未撤稿/更正状态已检查 +- [ ] 图表可追溯到真实数据和 RunManifest +- [ ] 代码、数据、环境和参数达到项目定义的复现级别 +- [ ] 局限性、负结果和未解决问题没有被隐藏 +- [ ] 审核打回项全部关闭或明确保留 +- [ ] `.project-to-act/PROJECT_ACCEPTANCE.md` 已用新鲜证据检查 +- [ ] 如涉及外部投稿/发布,已有 H3 人工批准 diff --git a/paper-projects/md-metal-ductile-damage-pilot/PROJECT_MANIFEST.yaml b/paper-projects/md-metal-ductile-damage-pilot/PROJECT_MANIFEST.yaml new file mode 100644 index 0000000..6e97587 --- /dev/null +++ b/paper-projects/md-metal-ductile-damage-pilot/PROJECT_MANIFEST.yaml @@ -0,0 +1,23 @@ +schema_version: '1.0' +project_id: md-metal-ductile-damage-pilot +slug: md-metal-ductile-damage-pilot +title: 金属韧性损伤分子动力学机理分析 +owner: Codex +status: waiting_evidence +created_at: '2026-08-22' +language: zh-CN +target_venue: null +research_domains: +- molecular-dynamics +- metallic-ductile-damage +- dislocation-and-void-mechanisms +project_to_act_dir: .project-to-act +langgraph_thread_id: run_b33c1d0b69564e6f +database_project_ref: md-metal-ductile-damage-pilot +object_store_prefix: projects/md-metal-ductile-damage-pilot +privacy_level: private +external_publication_enabled: false +notes: +- 项目由 AutoResearch 从受控模板实例化。 +- 当前为 AR-005 software pilot;synthetic fixture 不构成真实科研结果。 +- 不得在本文件保存 API key、账号令牌或完整敏感个人信息。 diff --git a/paper-projects/md-metal-ductile-damage-pilot/README.md b/paper-projects/md-metal-ductile-damage-pilot/README.md new file mode 100644 index 0000000..4991821 --- /dev/null +++ b/paper-projects/md-metal-ductile-damage-pilot/README.md @@ -0,0 +1,12 @@ +# 金属韧性损伤分子动力学机理分析 Pilot + +这是 AutoResearch AR-005 的实例化论文项目,当前用于验证领域范围、真实受管 LLM、M08 RunManifest/执行 lineage、稿件缺口和 Reviewer Gate。 + +当前结论边界: + +- 两条相关论文记录来自 OpenAlex,当前只使用公开题录/摘要,不冒充授权全文; +- `04_execution/SYNTHETIC_FIXTURE.json` 明确是软件测试数据,不是 LAMMPS/MD 模拟结果; +- `04_execution/runs/` 证明确定性后处理、hash 与审计链可运行; +- `05_writing/MANUSCRIPT_DRAFT.md` 是被 Gate 阻断的缺口稿,不是可发布论文。 + +目录职责沿用 `_template`,项目目标、进度、功能、版本和验收以本目录 `.project-to-act/` 为唯一事实源。真实科研继续前必须补许可全文、材料/晶向/势函数协议、带 checksum 的 MD 输入或轨迹衍生观测、独立复验和人工审核。 diff --git a/paper-projects/md-metal-ductile-damage-pilot/evidence/README.md b/paper-projects/md-metal-ductile-damage-pilot/evidence/README.md new file mode 100644 index 0000000..4f78772 --- /dev/null +++ b/paper-projects/md-metal-ductile-damage-pilot/evidence/README.md @@ -0,0 +1,5 @@ +# 证据目录 + +这里保存项目级 evidence manifest 和 bundle。PDF、数据集、代码快照和大日志使用受控对象存储或项目 artifact 路径,本目录只保存稳定 ID、hash、locator、许可、可见范围和引用关系。 + +禁止保存 API key、会话令牌、完整敏感个人信息或未经授权的付费全文。 diff --git a/paper-projects/md-metal-ductile-damage-pilot/handoffs/README.md b/paper-projects/md-metal-ductile-damage-pilot/handoffs/README.md new file mode 100644 index 0000000..40575e4 --- /dev/null +++ b/paper-projects/md-metal-ductile-damage-pilot/handoffs/README.md @@ -0,0 +1,3 @@ +# 交接目录 + +每次跨 Agent 交互必须保存通过 schema 的 `HandoffEnvelope` 和 `HandoffResult`。完整聊天、整篇全文和未裁剪工具日志不能作为交接 payload;使用 artifact/evidence/claim ID 按需读取。 diff --git a/paper-projects/md-metal-ductile-damage-pilot/knowledge-links/README.md b/paper-projects/md-metal-ductile-damage-pilot/knowledge-links/README.md new file mode 100644 index 0000000..849e310 --- /dev/null +++ b/paper-projects/md-metal-ductile-damage-pilot/knowledge-links/README.md @@ -0,0 +1,3 @@ +# 知识链接 + +保存本论文项目引用的 paper/experience/knowledge/user/project/evidence 稳定 ID,不复制各库正文。 diff --git a/paper-projects/md-metal-ductile-damage-pilot/state/README.md b/paper-projects/md-metal-ductile-damage-pilot/state/README.md new file mode 100644 index 0000000..fef08e6 --- /dev/null +++ b/paper-projects/md-metal-ductile-damage-pilot/state/README.md @@ -0,0 +1,3 @@ +# 状态投影 + +这里存放供人阅读的项目状态快照。LangGraph checkpointer 才是节点级运行状态的规范来源;本目录内容可重建,不能反向覆盖 checkpoint。 diff --git a/paper-projects/md-metal-ductile-damage-pilot/state/RUN_INDEX.jsonl b/paper-projects/md-metal-ductile-damage-pilot/state/RUN_INDEX.jsonl new file mode 100644 index 0000000..d8ff598 --- /dev/null +++ b/paper-projects/md-metal-ductile-damage-pilot/state/RUN_INDEX.jsonl @@ -0,0 +1,4 @@ +{"run_id": "run_30b03b3cff5049a8", "status": "blocked", "lifecycle_state": "waiting_evidence", "paper_count": 0, "reading_card_count": 0, "manuscript_id": null, "gate_decision_ids": [], "blockers": ["No real paper records are available for reading."], "interrupts": [], "updated_at": "2026-08-22T14:29:56.153691Z"} +{"run_id": "run_c3357978832d4b77", "status": "blocked", "lifecycle_state": "waiting_evidence", "paper_count": 0, "reading_card_count": 0, "manuscript_id": null, "gate_decision_ids": [], "blockers": ["No real paper records are available for reading."], "interrupts": [], "updated_at": "2026-08-22T14:30:53.163413Z"} +{"run_id": "run_8dd367666bd4426f", "status": "blocked", "lifecycle_state": "waiting_evidence", "paper_count": 0, "reading_card_count": 0, "manuscript_id": null, "gate_decision_ids": [], "blockers": ["No real paper records are available for reading."], "interrupts": [], "updated_at": "2026-08-22T14:32:38.902178Z"} +{"run_id": "run_b33c1d0b69564e6f", "status": "blocked", "lifecycle_state": "waiting_evidence", "paper_count": 2, "reading_card_count": 2, "manuscript_id": "manuscript_095a925913474da4", "gate_decision_ids": ["gate_873fc56fde094591", "gate_c2754f01a668429e"], "blockers": ["evidence score 65 is below required 75", "required work and result lineage are not closed loop"], "interrupts": [], "updated_at": "2026-08-22T14:34:33.050323Z"} diff --git a/pyproject.toml b/pyproject.toml index d8715cb..de4f30e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "autoresearch" -version = "0.1.0" +version = "0.2.0.dev0" description = "Evidence-governed, multi-agent research workflow built with LangGraph" readme = "README.md" requires-python = ">=3.12" diff --git a/src/autoresearch/__init__.py b/src/autoresearch/__init__.py index 2571710..eea2f41 100644 --- a/src/autoresearch/__init__.py +++ b/src/autoresearch/__init__.py @@ -3,4 +3,4 @@ from autoresearch.application import AutoResearchApplication __all__ = ["AutoResearchApplication"] -__version__ = "0.1.0" +__version__ = "0.2.0.dev0" diff --git a/src/autoresearch/agents/base.py b/src/autoresearch/agents/base.py index d3072e9..e64f9cd 100644 --- a/src/autoresearch/agents/base.py +++ b/src/autoresearch/agents/base.py @@ -20,6 +20,8 @@ class WorkflowState(TypedDict, total=False): reading_card_ids: list[str] innovation_ids: list[str] work_package_ids: list[str] + execution_manifest_ids: list[str] + execution_run_ids: list[str] manuscript_id: str | None review_ids: list[str] evidence_ids: list[str] diff --git a/src/autoresearch/agents/orchestrator.py b/src/autoresearch/agents/orchestrator.py index 3e21e5f..7161e6c 100644 --- a/src/autoresearch/agents/orchestrator.py +++ b/src/autoresearch/agents/orchestrator.py @@ -17,6 +17,7 @@ new_id, utc_now, ) +from autoresearch.domain_profiles import select_domain_profile from autoresearch.evidence import EvidenceService from autoresearch.handoffs import HandoffService from autoresearch.llm import LLMService @@ -59,13 +60,16 @@ def _queries(idea: str) -> list[str]: def scope(self, raw_state: WorkflowState) -> dict: state = ResearchState.model_validate(raw_state) queries = self._queries(state.idea) + domain_profile = select_domain_profile(state.idea) llm_note = None + llm_scope: dict[str, list[str] | str] | None = None if self.llm.available: try: result = self.llm.complete_json( system=( - "You decompose a research idea. Return JSON with a 'search_queries' " - "array only. Do not claim novelty or results." + "You scope a research idea without claiming novelty or results. Return " + "JSON with search_queries, research_questions, variables, and " + "falsification_tests arrays. Preserve unknowns and physical units." ), user=state.idea, ) @@ -74,7 +78,18 @@ def scope(self, raw_state: WorkflowState) -> dict: queries = list( dict.fromkeys([str(item)[:300] for item in candidates if str(item).strip()]) )[:8] - llm_note = "Managed LLM refined the search query plan." + if result: + llm_scope = { + key: [str(item)[:500] for item in result.get(key, [])[:8]] + for key in ( + "research_questions", + "variables", + "falsification_tests", + ) + if isinstance(result.get(key), list) + } + llm_scope["model"] = self.llm.settings.llm_model or "configured-model" + llm_note = "Managed LLM refined the structured research scope." except Exception as exc: llm_note = f"LLM query refinement unavailable: {type(exc).__name__}; fallback used." @@ -83,11 +98,20 @@ def scope(self, raw_state: WorkflowState) -> dict: "charter_id": charter_id, "project_id": state.project_id, "idea": state.idea, - "research_questions": [ + "domain_profile": domain_profile.profile_id if domain_profile else "general_v1", + "research_questions": list(domain_profile.research_questions) + if domain_profile + else [ f"What measurable problem does this idea address: {state.idea[:220]}?", "Which prior methods are the strongest baselines and boundary cases?", "What result would falsify the proposed contribution?", ], + "variables": list(domain_profile.variables) if domain_profile else [], + "falsification_tests": list(domain_profile.falsification_tests) + if domain_profile + else ["The proposed contribution must be rejected if its registered test fails."], + "required_inputs": list(domain_profile.required_inputs) if domain_profile else [], + "llm_scope": llm_scope, "success_criteria": [ "Every material claim links to evidence IDs.", "At least two independent sources support manuscript-ready claims.", @@ -127,7 +151,7 @@ def scope(self, raw_state: WorkflowState) -> dict: ) ) - packages = [ + packages = domain_profile.work_packages(state.project_id) if domain_profile else [ WorkPackage( project_id=state.project_id, title="WP1 Literature evidence", diff --git a/src/autoresearch/api.py b/src/autoresearch/api.py index d908f6c..78b730a 100644 --- a/src/autoresearch/api.py +++ b/src/autoresearch/api.py @@ -16,6 +16,7 @@ PaperQuestion, ProjectCreate, ResumeRequest, + RunManifestCreate, RunRequest, UserProfileItem, WikiPage, @@ -27,7 +28,7 @@ def create_api(application: AutoResearchApplication | None = None) -> FastAPI: runtime = application or AutoResearchApplication() app = FastAPI( title="AutoResearch API", - version="0.1.0", + version="0.2.0.dev0", description="Evidence-governed five-agent research workflow", ) app.state.runtime = runtime @@ -56,6 +57,8 @@ def start_run(request: RunRequest): return runtime.run(request) except KeyError as exc: raise HTTPException(status_code=404, detail=str(exc)) from exc + except ValueError as exc: + raise HTTPException(status_code=422, detail=str(exc)) from exc @app.get("/runs/{run_id}") def get_run(run_id: str, include_checkpoint: bool = False): @@ -115,10 +118,46 @@ def revise_manuscript( except KeyError as exc: raise HTTPException(status_code=404, detail="manuscript not found") from exc + @app.get("/manuscripts/{manuscript_id}") + def get_manuscript(manuscript_id: str): + manuscript = runtime.get_manuscript(manuscript_id) + if manuscript is None: + raise HTTPException(status_code=404, detail="manuscript not found") + return manuscript + @app.get("/projects/{project_id}/work-packages") def list_work_packages(project_id: str): return runtime.execution.list_work_packages(project_id) + @app.post("/execution/manifests", status_code=201) + def create_execution_manifest(request: RunManifestCreate): + try: + return runtime.create_run_manifest(request) + except KeyError as exc: + raise HTTPException(status_code=404, detail=str(exc)) from exc + except ValueError as exc: + raise HTTPException(status_code=422, detail=str(exc)) from exc + + @app.get("/projects/{project_id}/execution/manifests") + def list_execution_manifests(project_id: str): + return runtime.execution.list_manifests(project_id) + + @app.post("/execution/manifests/{manifest_id}/execute") + def execute_manifest(manifest_id: str): + try: + return runtime.execute_manifest(manifest_id) + except KeyError as exc: + raise HTTPException(status_code=404, detail="manifest not found") from exc + except RuntimeError as exc: + raise HTTPException(status_code=409, detail=str(exc)) from exc + + @app.get("/execution/runs/{execution_id}") + def get_execution(execution_id: str): + result = runtime.execution.get_execution(execution_id) + if result is None: + raise HTTPException(status_code=404, detail="execution not found") + return result + @app.patch("/work-packages/{work_package_id}") def update_work_package(work_package_id: str, request: WorkPackageUpdate): try: diff --git a/src/autoresearch/application.py b/src/autoresearch/application.py index 1da0cfc..3e90cdd 100644 --- a/src/autoresearch/application.py +++ b/src/autoresearch/application.py @@ -19,6 +19,7 @@ from autoresearch.contracts import ( EvidenceItem, EvolutionProposal, + ExecutionRecord, ExperienceRecord, Manuscript, ManuscriptRevisionRequest, @@ -26,6 +27,8 @@ ReadingAnswer, ResearchState, ResumeRequest, + RunManifest, + RunManifestCreate, RunRequest, RunStatus, UserProfileItem, @@ -82,7 +85,11 @@ def __init__(self, settings: Settings | None = None): self.profile = UserProfileService(self.store, self.knowledge) self.experiences = ExperienceService(self.store, self.knowledge) self.evolution = EvolutionService(self.store) - self.execution = ExecutionService(self.store, self.evidence) + self.execution = ExecutionService( + self.store, + self.evidence, + self.settings.projects_dir, + ) self.orchestrator_agent = OrchestratorAgent( self.store, @@ -134,6 +141,7 @@ def __init__(self, settings: Settings | None = None): paper_reader=self.paper_reader_agent, writer=self.writer_agent, reviewer=self.reviewer_agent, + execution=self.execution, evidence=self.evidence, gates=self.gates, state_machine=self.state_machine, @@ -203,6 +211,21 @@ def run(self, request: RunRequest) -> dict[str, Any]: ], run_status=RunStatus.RUNNING, ) + if request.execution_input is not None: + manifest = self.execution.create_manifest( + RunManifestCreate( + project_id=request.project_id, + research_run_id=state.run_id, + input_data=request.execution_input, + idempotency_key=( + request.execution_idempotency_key + or f"{state.run_id}:metal-damage-v1" + ), + ) + ) + state = state.model_copy( + update={"execution_manifest_ids": [manifest.manifest_id]} + ) config = {"configurable": {"thread_id": state.run_id}} self.store.append_event( "run.started", @@ -260,6 +283,12 @@ def record_experience(self, item: ExperienceRecord) -> ExperienceRecord: def update_work_package(self, work_package_id: str, update: WorkPackageUpdate) -> WorkPackage: return self.execution.update_work_package(work_package_id, update) + def create_run_manifest(self, request: RunManifestCreate) -> RunManifest: + return self.execution.create_manifest(request) + + def execute_manifest(self, manifest_id: str) -> ExecutionRecord: + return self.execution.execute(manifest_id) + def revise_manuscript( self, manuscript_id: str, request: ManuscriptRevisionRequest ) -> Manuscript: @@ -269,6 +298,9 @@ def revise_manuscript( mode=request.mode, ) + def get_manuscript(self, manuscript_id: str) -> Manuscript | None: + return self.writing.get(manuscript_id) + def answer_paper(self, paper_id: str, question: str) -> ReadingAnswer: return self.reader.answer_question(paper_id, question) @@ -278,7 +310,7 @@ def propose_evolution(self, proposal: EvolutionProposal) -> EvolutionProposal: def doctor(self) -> dict[str, Any]: return { "ok": True, - "version": "0.1.0", + "version": "0.2.0.dev0", "python_profile": "3.12+", "agents": [agent_id.value for agent_id in self.agents], "agent_count": len(self.agents), diff --git a/src/autoresearch/cli.py b/src/autoresearch/cli.py index 643a45b..9c2c84e 100644 --- a/src/autoresearch/cli.py +++ b/src/autoresearch/cli.py @@ -15,9 +15,11 @@ EvolutionProposal, KnowledgePartition, ManuscriptRevisionRequest, + MetalDamageAnalysisInput, PaperRecord, ProjectCreate, ResumeRequest, + RunManifestCreate, RunRequest, ) @@ -81,12 +83,30 @@ def run( bool, typer.Option("--request-release", help="Pause at the L4 human release gate"), ] = False, + execution_file: Annotated[ + Path | None, + typer.Option( + "--execution-file", + exists=True, + dir_okay=False, + help="JSON object with registered metal-damage observations", + ), + ] = None, + execution_idempotency_key: Annotated[ + str | None, + typer.Option("--execution-idempotency-key"), + ] = None, ) -> None: """Execute from idea until completion, an evidence block, or a human interrupt.""" seeds: list[PaperRecord] = [] if seed_file: raw_items = json.loads(seed_file.read_text(encoding="utf-8")) seeds = [PaperRecord.model_validate({**raw, "project_id": project_id}) for raw in raw_items] + execution_input = None + if execution_file: + execution_input = MetalDamageAnalysisInput.model_validate_json( + execution_file.read_text(encoding="utf-8") + ) runtime = AutoResearchApplication() try: _echo( @@ -95,6 +115,8 @@ def run( project_id=project_id, idea=idea, seed_papers=seeds, + execution_input=execution_input, + execution_idempotency_key=execution_idempotency_key, request_release=request_release, ) ) @@ -103,6 +125,60 @@ def run( runtime.close() +@app.command("register-execution") +def register_execution( + project_id: Annotated[str, typer.Argument()], + input_file: Annotated[Path, typer.Option("--input-file", exists=True, dir_okay=False)], + idempotency_key: Annotated[str, typer.Option("--idempotency-key")], + work_package_id: Annotated[str | None, typer.Option("--work-package-id")] = None, +) -> None: + """Register a bounded metal-damage post-processing manifest without running shell code.""" + input_data = MetalDamageAnalysisInput.model_validate_json( + input_file.read_text(encoding="utf-8") + ) + runtime = AutoResearchApplication() + try: + _echo( + runtime.create_run_manifest( + RunManifestCreate( + project_id=project_id, + work_package_id=work_package_id, + input_data=input_data, + idempotency_key=idempotency_key, + ) + ) + ) + finally: + runtime.close() + + +@app.command("execute-manifest") +def execute_manifest( + manifest_id: Annotated[str, typer.Argument()], +) -> None: + """Execute one registered deterministic manifest and emit its structured record.""" + runtime = AutoResearchApplication() + try: + _echo(runtime.execute_manifest(manifest_id)) + finally: + runtime.close() + + +@app.command("execution-status") +def execution_status( + execution_id: Annotated[str, typer.Argument()], +) -> None: + """Read a persisted execution record.""" + runtime = AutoResearchApplication() + try: + record = runtime.execution.get_execution(execution_id) + if record is None: + raise typer.BadParameter(f"unknown execution: {execution_id}") + _echo(record) + finally: + runtime.close() + + @app.command() def resume( run_id: Annotated[str, typer.Argument(help="Interrupted LangGraph run ID")], diff --git a/src/autoresearch/contracts.py b/src/autoresearch/contracts.py index 34158de..baad842 100644 --- a/src/autoresearch/contracts.py +++ b/src/autoresearch/contracts.py @@ -97,6 +97,18 @@ class ClaimStatus(StrEnum): REFUTED = "refuted" +class DataClassification(StrEnum): + SYNTHETIC_FIXTURE = "synthetic_fixture" + MD_TRAJECTORY_SUMMARY = "md_trajectory_summary" + + +class ExecutionStatus(StrEnum): + PLANNED = "planned" + RUNNING = "running" + COMPLETED = "completed" + FAILED = "failed" + + class ArtifactRef(BaseModel): artifact_id: str kind: str @@ -264,6 +276,87 @@ class WorkPackageUpdate(BaseModel): note: str = Field(default="", max_length=2000) +class DuctileDamageObservation(BaseModel): + strain: float = Field(ge=0, le=10) + stress_gpa: float = Field(ge=-1000, le=1000) + dislocation_density_m2: float | None = Field(default=None, ge=0) + void_fraction: float | None = Field(default=None, ge=0, le=1) + centrosymmetry: float | None = Field(default=None, ge=0) + + +class MetalDamageAnalysisInput(BaseModel): + material: str = Field(min_length=1, max_length=120) + temperature_k: float = Field(gt=0, le=10000) + strain_rate_s: float = Field(gt=0) + loading_mode: str = Field(default="uniaxial_tension", max_length=80) + data_classification: DataClassification = DataClassification.SYNTHETIC_FIXTURE + source_uri: str | None = Field(default=None, max_length=1000) + source_checksum: str | None = Field( + default=None, + pattern=r"^sha256:[0-9a-f]{64}$", + ) + observations: list[DuctileDamageObservation] = Field(min_length=5, max_length=10000) + + @model_validator(mode="after") + def real_summary_requires_provenance(self) -> MetalDamageAnalysisInput: + if ( + self.data_classification == DataClassification.MD_TRAJECTORY_SUMMARY + and (not self.source_uri or not self.source_checksum) + ): + raise ValueError( + "md_trajectory_summary requires source_uri and a sha256 source_checksum" + ) + return self + + +class RunManifestCreate(BaseModel): + project_id: str + work_package_id: str | None = None + research_run_id: str | None = None + profile: str = Field(default="metal_ductile_damage_v1", max_length=120) + input_data: MetalDamageAnalysisInput + random_seed: int = Field(default=0, ge=0, le=2**31 - 1) + parameters: dict[str, float | int | str | bool] = Field(default_factory=dict, max_length=50) + idempotency_key: str = Field(min_length=8, max_length=200, pattern=r"^[A-Za-z0-9._:-]+$") + + +class RunManifest(BaseModel): + manifest_id: str = Field(default_factory=lambda: new_id("manifest")) + project_id: str + work_package_id: str | None = None + research_run_id: str | None = None + profile: str + data_classification: DataClassification + input_checksum: str + source_uri: str | None = None + source_checksum: str | None = None + random_seed: int + parameters: dict[str, float | int | str | bool] = Field(default_factory=dict) + environment: dict[str, str] = Field(default_factory=dict) + idempotency_key: str + status: ExecutionStatus = ExecutionStatus.PLANNED + execution_id: str | None = None + output_artifacts: list[ArtifactRef] = Field(default_factory=list) + validation: list[str] = Field(default_factory=list) + created_at: datetime = Field(default_factory=utc_now) + + +class ExecutionRecord(BaseModel): + execution_id: str = Field(default_factory=lambda: new_id("execution")) + manifest_id: str + project_id: str + status: ExecutionStatus + actual_execution: bool = True + started_at: datetime = Field(default_factory=utc_now) + completed_at: datetime | None = None + metrics: dict[str, float | int | str | list[str]] = Field(default_factory=dict) + artifact_refs: list[ArtifactRef] = Field(default_factory=list) + evidence_ids: list[str] = Field(default_factory=list) + validation: list[str] = Field(default_factory=list) + error_type: str | None = None + error_message: str | None = Field(default=None, max_length=1000) + + class Manuscript(BaseModel): manuscript_id: str = Field(default_factory=lambda: new_id("manuscript")) project_id: str @@ -389,6 +482,8 @@ class ResearchState(BaseModel): reading_card_ids: list[str] = Field(default_factory=list) innovation_ids: list[str] = Field(default_factory=list) work_package_ids: list[str] = Field(default_factory=list) + execution_manifest_ids: list[str] = Field(default_factory=list) + execution_run_ids: list[str] = Field(default_factory=list) manuscript_id: str | None = None review_ids: list[str] = Field(default_factory=list) evidence_ids: list[str] = Field(default_factory=list) @@ -404,6 +499,13 @@ class RunRequest(BaseModel): project_id: str idea: str = Field(min_length=1, max_length=5000) seed_papers: list[PaperRecord] = Field(default_factory=list) + execution_input: MetalDamageAnalysisInput | None = None + execution_idempotency_key: str | None = Field( + default=None, + min_length=8, + max_length=200, + pattern=r"^[A-Za-z0-9._:-]+$", + ) request_release: bool = False diff --git a/src/autoresearch/domain_profiles.py b/src/autoresearch/domain_profiles.py new file mode 100644 index 0000000..0c71059 --- /dev/null +++ b/src/autoresearch/domain_profiles.py @@ -0,0 +1,141 @@ +from __future__ import annotations + +from dataclasses import dataclass + +from autoresearch.contracts import WorkPackage + + +@dataclass(frozen=True) +class ResearchDomainProfile: + profile_id: str + name: str + keywords: tuple[str, ...] + research_questions: tuple[str, ...] + variables: tuple[str, ...] + falsification_tests: tuple[str, ...] + required_inputs: tuple[str, ...] + + def matches(self, idea: str) -> bool: + lowered = idea.casefold() + return sum(keyword.casefold() in lowered for keyword in self.keywords) >= 2 + + def work_packages(self, project_id: str) -> list[WorkPackage]: + return [ + WorkPackage( + project_id=project_id, + title="WP1 Literature evidence", + objective="Build a locatable evidence map for metallic ductile-damage mechanisms.", + tasks=[ + ( + "Search dislocation, void nucleation/growth, stress triaxiality, " + "and MD studies" + ), + "Separate material, potential, temperature, strain-rate, and size effects", + "Create reading cards and nearest-work comparison", + ], + expected_artifacts=["search ledger", "reading cards", "claim-evidence map"], + acceptance_checks=[ + "no invented records", + "mechanism claims retain paper locators", + "conflicting mechanisms remain visible", + ], + ), + WorkPackage( + project_id=project_id, + title="WP2 MD protocol", + objective="Register a reproducible atomistic loading and observation protocol.", + tasks=[ + ( + "Lock material, crystal/orientation, geometry, boundary conditions, " + "and potential" + ), + ( + "Lock minimization/equilibration/loading, thermostat, timestep, " + "and dump cadence" + ), + "Register stress, strain, dislocation, void, and lattice-disorder observables", + ], + expected_artifacts=["protocol", "environment manifest", "input checksum"], + acceptance_checks=[ + "all physical parameters carry units", + "potential and input data are versioned", + "no unbounded parameter search", + ], + dependencies=["WP1 Literature evidence"], + ), + WorkPackage( + project_id=project_id, + title="WP3 Damage analysis", + objective="Post-process registered trajectory-derived observations with lineage.", + tasks=[ + "Validate monotonic strain and finite observables", + "Measure elastic slope, peak stress, damage onset, and stress-loss strain", + "Relate changes in dislocation density, void fraction, and lattice disorder", + ], + expected_artifacts=["run manifest", "result JSON", "artifact checksums"], + acceptance_checks=[ + "input/output checksums are recorded", + "synthetic fixtures stay labelled", + "mechanism indicators are not promoted to causal proof", + ], + dependencies=["WP2 MD protocol"], + ), + WorkPackage( + project_id=project_id, + title="WP4 Manuscript", + objective="Draft only evidence-supported mechanism statements and visible gaps.", + tasks=["Outline", "Draft", "Result lineage audit", "Reviewer-directed revision"], + expected_artifacts=["manuscript draft", "review report"], + acceptance_checks=[ + "all numeric results cite execution evidence", + "synthetic results cannot close the manuscript gate", + "alternative mechanisms and limitations remain explicit", + ], + dependencies=["WP1 Literature evidence", "WP3 Damage analysis"], + ), + ] + + +METAL_DUCTILE_DAMAGE = ResearchDomainProfile( + profile_id="metal_ductile_damage_v1", + name="Molecular-dynamics metallic ductile-damage analysis", + keywords=( + "分子动力学", + "金属", + "韧性损伤", + "molecular dynamics", + "ductile damage", + "dislocation", + "void", + ), + research_questions=( + "在给定晶体取向、温度、应变率与势函数下,屈服到失稳经历哪些可观测阶段?", + "位错演化、空洞形核/长大与晶格无序分别在何种应变区间先于应力下降出现?", + "材料尺寸、边界条件、缺陷和应力三轴度改变时,损伤起始与断裂指标是否保持一致?", + "哪些观测只能说明相关性,哪些额外对照或消融能够反驳候选机理?", + ), + variables=( + "material/crystal orientation", + "interatomic potential version", + "temperature K", + "strain rate s^-1", + "stress-strain response GPa", + "dislocation density m^-2", + "void fraction", + "centrosymmetry/lattice disorder", + ), + falsification_tests=( + "若损伤指标变化不领先于或不伴随持续应力下降,则该指标不能支持主导机理表述。", + "若更换尺寸、取向或势函数后机制次序反转,则结论必须限定适用边界。", + "若去除初始缺陷后候选机制仍无可重复差异,则缺陷驱动假设被削弱。", + ), + required_inputs=( + "许可明确的相关论文或用户授权全文", + "带 checksum 的 MD 输入/轨迹衍生观测", + "材料、势函数、边界、温度、应变率、seed 和软件环境", + ), +) + + +def select_domain_profile(idea: str) -> ResearchDomainProfile | None: + return METAL_DUCTILE_DAMAGE if METAL_DUCTILE_DAMAGE.matches(idea) else None diff --git a/src/autoresearch/execution_service.py b/src/autoresearch/execution_service.py index 39fac3f..0273be2 100644 --- a/src/autoresearch/execution_service.py +++ b/src/autoresearch/execution_service.py @@ -1,14 +1,81 @@ from __future__ import annotations -from autoresearch.contracts import WorkPackage, WorkPackageUpdate +import hashlib +import json +import math +import sys +import threading +from pathlib import Path +from typing import Any + +from autoresearch.contracts import ( + ArtifactRef, + DataClassification, + EvidenceGrade, + EvidenceItem, + EvidenceType, + ExecutionRecord, + ExecutionStatus, + MetalDamageAnalysisInput, + RunManifest, + RunManifestCreate, + WorkPackage, + WorkPackageUpdate, + utc_now, +) from autoresearch.evidence import EvidenceService from autoresearch.storage import RecordStore class ExecutionService: - def __init__(self, store: RecordStore, evidence: EvidenceService): + """Registered deterministic research execution with auditable artifact lineage.""" + + PROFILE = "metal_ductile_damage_v1" + EXECUTOR_VERSION = "1" + + def __init__(self, store: RecordStore, evidence: EvidenceService, projects_dir: Path): self.store = store self.evidence = evidence + self.projects_dir = projects_dir.resolve() + self._lock = threading.RLock() + + @staticmethod + def _canonical(value: Any) -> bytes: + return json.dumps( + value, + ensure_ascii=False, + sort_keys=True, + separators=(",", ":"), + default=str, + ).encode("utf-8") + + @classmethod + def _checksum(cls, value: Any) -> str: + return "sha256:" + hashlib.sha256(cls._canonical(value)).hexdigest() + + @classmethod + def observation_checksum(cls, input_data: MetalDamageAnalysisInput) -> str: + observations = [item.model_dump(mode="json") for item in input_data.observations] + return cls._checksum(observations) + + @classmethod + def input_checksum(cls, input_data: MetalDamageAnalysisInput) -> str: + return cls._checksum(input_data.model_dump(mode="json")) + + def _project_dir(self, project_id: str) -> Path: + project_dir = (self.projects_dir / project_id).resolve() + if not project_dir.is_relative_to(self.projects_dir) or not project_dir.is_dir(): + raise KeyError(f"unknown project: {project_id}") + return project_dir + + @staticmethod + def _write_json(path: Path, value: Any) -> str: + path.parent.mkdir(parents=True, exist_ok=True) + temporary = path.with_suffix(path.suffix + ".tmp") + content = json.dumps(value, ensure_ascii=False, indent=2, default=str) + "\n" + temporary.write_bytes(content.encode("utf-8")) + temporary.replace(path) + return "sha256:" + hashlib.sha256(content.encode("utf-8")).hexdigest() def list_work_packages(self, project_id: str) -> list[WorkPackage]: return [ @@ -59,3 +126,490 @@ def update_work_package( actor="execution_service", ) return revised + + def create_manifest(self, request: RunManifestCreate) -> RunManifest: + with self._lock: + return self._create_manifest(request) + + def _create_manifest(self, request: RunManifestCreate) -> RunManifest: + if request.profile != self.PROFILE: + raise ValueError(f"unregistered execution profile: {request.profile}") + self._project_dir(request.project_id) + if request.work_package_id: + package_raw = self.store.get("work_package", request.work_package_id) + if package_raw is None: + raise KeyError(request.work_package_id) + package = WorkPackage.model_validate(package_raw) + if package.project_id != request.project_id: + raise ValueError("work package and manifest project identities differ") + if request.research_run_id: + research_run = self.store.get("run", request.research_run_id) + if research_run and research_run.get("project_id") != request.project_id: + raise ValueError("research run and manifest project identities differ") + input_checksum = self.input_checksum(request.input_data) + if ( + request.input_data.data_classification + == DataClassification.MD_TRAJECTORY_SUMMARY + and request.input_data.source_checksum != self.observation_checksum(request.input_data) + ): + raise ValueError( + "source_checksum must match the canonical submitted observation sequence" + ) + scope = f"execution_manifest:{request.project_id}" + remembered = self.store.get_idempotent(scope, request.idempotency_key) + if remembered: + raw = self.store.get("run_manifest", remembered["manifest_id"]) + if raw is None: + raise RuntimeError("idempotency record references a missing manifest") + existing = RunManifest.model_validate(raw) + if existing.input_checksum != input_checksum: + raise ValueError("idempotency key was already used with different input") + return existing + manifest = RunManifest( + project_id=request.project_id, + work_package_id=request.work_package_id, + research_run_id=request.research_run_id, + profile=request.profile, + data_classification=request.input_data.data_classification, + input_checksum=input_checksum, + source_uri=request.input_data.source_uri, + source_checksum=request.input_data.source_checksum, + random_seed=request.random_seed, + parameters=request.parameters, + environment={ + "python": ( + f"{sys.version_info.major}.{sys.version_info.minor}." + f"{sys.version_info.micro}" + ), + "executor": f"{self.PROFILE}:{self.EXECUTOR_VERSION}", + }, + idempotency_key=request.idempotency_key, + validation=["registered profile", "bounded structured input", "no shell execution"], + ) + self.store.put( + "execution_input", + manifest.manifest_id, + request.input_data.model_dump(mode="json"), + project_id=request.project_id, + partition="execution-private", + ) + self.store.put( + "run_manifest", + manifest.manifest_id, + manifest, + project_id=request.project_id, + partition="execution", + ) + if not self.store.remember_idempotent( + scope, request.idempotency_key, {"manifest_id": manifest.manifest_id} + ): + remembered = self.store.get_idempotent(scope, request.idempotency_key) + assert remembered is not None + raw = self.store.get("run_manifest", remembered["manifest_id"]) + assert raw is not None + return RunManifest.model_validate(raw) + + manifest_path = ( + self._project_dir(request.project_id) + / "04_execution" + / "runs" + / manifest.manifest_id + / "RUN_MANIFEST.json" + ) + self._write_json(manifest_path, manifest.model_dump(mode="json")) + self.store.append_event( + "execution.manifest_registered", + { + "manifest_id": manifest.manifest_id, + "profile": manifest.profile, + "input_checksum": manifest.input_checksum, + "data_classification": manifest.data_classification, + }, + project_id=manifest.project_id, + actor="execution_service", + ) + return manifest + + def get_manifest(self, manifest_id: str) -> RunManifest | None: + raw = self.store.get("run_manifest", manifest_id) + return RunManifest.model_validate(raw) if raw else None + + def bind_manifest( + self, + manifest_id: str, + *, + work_package_id: str | None, + research_run_id: str, + ) -> RunManifest: + manifest = self.get_manifest(manifest_id) + if manifest is None: + raise KeyError(manifest_id) + if manifest.research_run_id not in {None, research_run_id}: + raise ValueError("manifest is already bound to a different research run") + if work_package_id: + package_raw = self.store.get("work_package", work_package_id) + if package_raw is None: + raise KeyError(work_package_id) + package = WorkPackage.model_validate(package_raw) + if package.project_id != manifest.project_id: + raise ValueError("work package and manifest project identities differ") + bound = manifest.model_copy( + update={ + "work_package_id": work_package_id or manifest.work_package_id, + "research_run_id": research_run_id, + } + ) + self.store.put( + "run_manifest", + bound.manifest_id, + bound, + project_id=bound.project_id, + partition="execution", + ) + manifest_path = ( + self._project_dir(bound.project_id) + / "04_execution" + / "runs" + / bound.manifest_id + / "RUN_MANIFEST.json" + ) + self._write_json(manifest_path, bound.model_dump(mode="json")) + return bound + + def list_manifests(self, project_id: str) -> list[RunManifest]: + return [ + RunManifest.model_validate(raw) + for raw in self.store.list("run_manifest", project_id=project_id) + ] + + @staticmethod + def _linear_slope(points: list[tuple[float, float]]) -> float: + mean_x = sum(point[0] for point in points) / len(points) + mean_y = sum(point[1] for point in points) / len(points) + denominator = sum((point[0] - mean_x) ** 2 for point in points) + if denominator <= 0: + raise ValueError("initial strain window cannot estimate elastic slope") + return sum((x - mean_x) * (y - mean_y) for x, y in points) / denominator + + @staticmethod + def _validate_input(input_data: MetalDamageAnalysisInput) -> list[str]: + observations = input_data.observations + strains = [item.strain for item in observations] + values = [ + value + for item in observations + for value in ( + item.strain, + item.stress_gpa, + item.dislocation_density_m2, + item.void_fraction, + item.centrosymmetry, + ) + if value is not None + ] + if not all(math.isfinite(value) for value in values): + raise ValueError("observations must contain only finite numeric values") + if any( + current <= previous + for previous, current in zip(strains, strains[1:], strict=False) + ): + raise ValueError("strain must be strictly increasing") + if strains[-1] - strains[0] <= 1e-8: + raise ValueError("strain span is too small for damage analysis") + defect_fields = [ + item.dislocation_density_m2 is not None + or item.void_fraction is not None + or item.centrosymmetry is not None + for item in observations + ] + if not any(defect_fields): + raise ValueError("at least one defect observable is required") + return [ + "strain is strictly increasing", + "all submitted values are finite", + "at least one defect observable is present", + ] + + def _analyze(self, input_data: MetalDamageAnalysisInput) -> tuple[dict[str, Any], list[str]]: + validation = self._validate_input(input_data) + observations = input_data.observations + initial_count = max(3, min(len(observations), len(observations) // 5 + 1)) + modulus = self._linear_slope( + [(item.strain, item.stress_gpa) for item in observations[:initial_count]] + ) + peak_index = max(range(len(observations)), key=lambda index: observations[index].stress_gpa) + peak = observations[peak_index] + damage_index = next( + ( + index + for index in range(peak_index + 1, len(observations)) + if observations[index].stress_gpa <= 0.95 * peak.stress_gpa + ), + None, + ) + half_stress_index = next( + ( + index + for index in range(peak_index + 1, len(observations)) + if observations[index].stress_gpa <= 0.5 * peak.stress_gpa + ), + None, + ) + + first = observations[0] + last = observations[-1] + indicators: list[str] = [] + if ( + first.dislocation_density_m2 is not None + and last.dislocation_density_m2 is not None + and last.dislocation_density_m2 > first.dislocation_density_m2 + ): + indicators.append("dislocation_activity_increased") + if ( + first.void_fraction is not None + and last.void_fraction is not None + and last.void_fraction - first.void_fraction >= 0.005 + ): + indicators.append("void_fraction_increased") + if ( + first.centrosymmetry is not None + and last.centrosymmetry is not None + and last.centrosymmetry > first.centrosymmetry + ): + indicators.append("lattice_disorder_increased") + if damage_index is None: + validation.append("no sustained five-percent post-peak stress loss was observed") + else: + validation.append("post-peak stress-loss onset was located") + + metrics: dict[str, Any] = { + "material": input_data.material, + "temperature_k": input_data.temperature_k, + "strain_rate_s": input_data.strain_rate_s, + "loading_mode": input_data.loading_mode, + "observation_count": len(observations), + "elastic_slope_gpa": round(modulus, 8), + "peak_stress_gpa": round(peak.stress_gpa, 8), + "peak_stress_strain": round(peak.strain, 8), + "damage_onset_strain": ( + round(observations[damage_index].strain, 8) + if damage_index is not None + else "not_detected" + ), + "half_peak_stress_strain": ( + round(observations[half_stress_index].strain, 8) + if half_stress_index is not None + else "not_detected" + ), + "mechanism_indicators": indicators, + "interpretation_boundary": ( + "Indicators are temporal correlations in submitted summaries; they do not by " + "themselves establish a causal damage mechanism." + ), + } + return metrics, validation + + def execute(self, manifest_id: str) -> ExecutionRecord: + with self._lock: + return self._execute(manifest_id) + + def _execute(self, manifest_id: str) -> ExecutionRecord: + manifest = self.get_manifest(manifest_id) + if manifest is None: + raise KeyError(manifest_id) + if manifest.status == ExecutionStatus.COMPLETED and manifest.execution_id: + raw = self.store.get("execution_record", manifest.execution_id) + if raw is None: + raise RuntimeError("completed manifest references a missing execution record") + return ExecutionRecord.model_validate(raw) + if manifest.status == ExecutionStatus.RUNNING: + raise RuntimeError("manifest is already running") + + input_raw = self.store.get("execution_input", manifest.manifest_id) + if input_raw is None: + raise RuntimeError("manifest input is missing") + input_data = MetalDamageAnalysisInput.model_validate(input_raw) + if self.input_checksum(input_data) != manifest.input_checksum: + raise RuntimeError("manifest input checksum mismatch") + + record = ExecutionRecord( + manifest_id=manifest.manifest_id, + project_id=manifest.project_id, + status=ExecutionStatus.RUNNING, + ) + running_manifest = manifest.model_copy( + update={"status": ExecutionStatus.RUNNING, "execution_id": record.execution_id} + ) + self.store.put( + "run_manifest", + manifest.manifest_id, + running_manifest, + project_id=manifest.project_id, + partition="execution", + ) + try: + metrics, validation = self._analyze(input_data) + result_payload = { + "manifest_id": manifest.manifest_id, + "profile": manifest.profile, + "data_classification": manifest.data_classification, + "input_checksum": manifest.input_checksum, + "metrics": metrics, + "validation": validation, + } + relative_uri = ( + f"paper-projects/{manifest.project_id}/04_execution/runs/" + f"{manifest.manifest_id}/RESULT.json" + ) + output_path = ( + self._project_dir(manifest.project_id) + / "04_execution" + / "runs" + / manifest.manifest_id + / "RESULT.json" + ) + output_checksum = self._write_json(output_path, result_payload) + artifact = ArtifactRef( + artifact_id=f"artifact_{record.execution_id}", + kind="metal_ductile_damage_result", + uri=relative_uri, + checksum=output_checksum, + summary=( + f"profile={manifest.profile}; classification={manifest.data_classification}; " + f"observations={len(input_data.observations)}" + ), + ) + evidence_grade = ( + EvidenceGrade.E1 + if manifest.data_classification == DataClassification.SYNTHETIC_FIXTURE + else EvidenceGrade.E2 + ) + result_evidence = self.evidence.add( + EvidenceItem( + project_id=manifest.project_id, + evidence_type=EvidenceType.EXPERIMENT, + grade=evidence_grade, + title="Registered metal ductile-damage post-processing run", + claim=( + f"The registered post-processing run measured peak stress " + f"{metrics['peak_stress_gpa']} GPa at strain " + f"{metrics['peak_stress_strain']} and damage-onset strain " + f"{metrics['damage_onset_strain']}. The listed mechanism indicators are " + "temporal correlations, not standalone causal proof." + ), + source_uri=relative_uri, + source_id=record.execution_id, + locator="RESULT.json metrics and validation", + checksum=output_checksum, + independent_source=f"execution:{record.execution_id}", + metadata={ + "manifest_id": manifest.manifest_id, + "data_classification": manifest.data_classification, + "input_checksum": manifest.input_checksum, + "actual_execution": True, + }, + ), + actor="execution_service", + ) + completed = record.model_copy( + update={ + "status": ExecutionStatus.COMPLETED, + "completed_at": utc_now(), + "metrics": metrics, + "artifact_refs": [artifact], + "evidence_ids": [result_evidence.evidence_id], + "validation": validation, + } + ) + completed_manifest = running_manifest.model_copy( + update={ + "status": ExecutionStatus.COMPLETED, + "output_artifacts": [artifact], + "validation": [*running_manifest.validation, *validation], + } + ) + self.store.put( + "execution_record", + completed.execution_id, + completed, + project_id=manifest.project_id, + partition="execution", + ) + self.store.put( + "run_manifest", + manifest.manifest_id, + completed_manifest, + project_id=manifest.project_id, + partition="execution", + ) + manifest_path = output_path.with_name("RUN_MANIFEST.json") + self._write_json(manifest_path, completed_manifest.model_dump(mode="json")) + self.store.append_event( + "execution.completed", + { + "manifest_id": manifest.manifest_id, + "execution_id": completed.execution_id, + "input_checksum": manifest.input_checksum, + "output_checksum": output_checksum, + "evidence_id": result_evidence.evidence_id, + }, + project_id=manifest.project_id, + actor="execution_service", + ) + if manifest.work_package_id: + self.update_work_package( + manifest.work_package_id, + WorkPackageUpdate( + status="completed", + evidence_ids=[result_evidence.evidence_id], + note=f"Registered execution {completed.execution_id} completed.", + ), + ) + return completed + except Exception as exc: + failed = record.model_copy( + update={ + "status": ExecutionStatus.FAILED, + "completed_at": utc_now(), + "error_type": type(exc).__name__, + "error_message": str(exc)[:1000], + } + ) + failed_manifest = running_manifest.model_copy( + update={"status": ExecutionStatus.FAILED, "validation": [str(exc)[:1000]]} + ) + self.store.put( + "execution_record", + failed.execution_id, + failed, + project_id=manifest.project_id, + partition="execution", + ) + self.store.put( + "run_manifest", + manifest.manifest_id, + failed_manifest, + project_id=manifest.project_id, + partition="execution", + ) + self.store.append_event( + "execution.failed", + { + "manifest_id": manifest.manifest_id, + "execution_id": failed.execution_id, + "error_type": failed.error_type, + }, + project_id=manifest.project_id, + actor="execution_service", + ) + return failed + + def get_execution(self, execution_id: str) -> ExecutionRecord | None: + raw = self.store.get("execution_record", execution_id) + return ExecutionRecord.model_validate(raw) if raw else None + + def list_executions(self, project_id: str) -> list[ExecutionRecord]: + return [ + ExecutionRecord.model_validate(raw) + for raw in self.store.list("execution_record", project_id=project_id) + ] diff --git a/src/autoresearch/graph.py b/src/autoresearch/graph.py index ca03e5e..91c6867 100644 --- a/src/autoresearch/graph.py +++ b/src/autoresearch/graph.py @@ -13,6 +13,7 @@ from autoresearch.agents.writer import WriterAgent from autoresearch.contracts import ( AgentId, + ArtifactRef, EvidenceGrade, EvidenceItem, EvidenceType, @@ -25,6 +26,7 @@ utc_now, ) from autoresearch.evidence import EvidenceService +from autoresearch.execution_service import ExecutionService from autoresearch.gates import GateService from autoresearch.state_machine import StateMachine @@ -52,6 +54,7 @@ def build_research_graph( paper_reader: PaperReaderAgent, writer: WriterAgent, reviewer: ReviewerAgent, + execution: ExecutionService, evidence: EvidenceService, gates: GateService, state_machine: StateMachine, @@ -67,6 +70,116 @@ def build_research_graph( writer_graph = writer.build_graph() manuscript_review_graph = reviewer.build_manuscript_graph() + def execute_registered_work(raw_state: WorkflowState) -> dict: + state = ResearchState.model_validate(raw_state) + if not state.execution_manifest_ids: + return state.model_copy( + update={ + "diagnostics": [ + *state.diagnostics, + "No registered execution input was supplied; result gaps remain open.", + ], + "updated_at": utc_now(), + } + ).model_dump(mode="json") + + work_package_id = next( + ( + package_id + for package_id in state.work_package_ids + if ( + (raw := execution.store.get("work_package", package_id)) is not None + and str(raw.get("title", "")).startswith("WP3") + ) + ), + None, + ) + try: + manifest = execution.bind_manifest( + state.execution_manifest_ids[0], + work_package_id=work_package_id, + research_run_id=state.run_id, + ) + execution_record = execution.execute(manifest.manifest_id) + except Exception as exc: + return state.model_copy( + update={ + "diagnostics": [ + *state.diagnostics, + f"Registered execution rejected: {type(exc).__name__}.", + ], + "blockers": [ + *state.blockers, + "Registered execution input or lineage validation failed.", + ], + "updated_at": utc_now(), + } + ).model_dump(mode="json") + + manifest_ids = list(dict.fromkeys([*state.execution_manifest_ids, manifest.manifest_id])) + execution_ids = list( + dict.fromkeys([*state.execution_run_ids, execution_record.execution_id]) + ) + evidence_ids = list( + dict.fromkeys([*state.evidence_ids, *execution_record.evidence_ids]) + ) + handoff = dict(state.handoff or {}) + if execution_record.status == "completed" and handoff: + artifact_refs = [ + *handoff.get("artifact_refs", []), + ArtifactRef( + artifact_id=manifest.manifest_id, + kind="run_manifest", + uri=( + f"paper-projects/{state.project_id}/04_execution/runs/" + f"{manifest.manifest_id}/RUN_MANIFEST.json" + ), + checksum=manifest.input_checksum, + summary=f"profile={manifest.profile}", + ).model_dump(mode="json"), + *[artifact.model_dump(mode="json") for artifact in execution_record.artifact_refs], + ] + handoff["artifact_refs"] = artifact_refs[:20] + handoff["evidence_ids"] = evidence_ids[:100] + execution.store.put( + "handoff", + handoff["handoff_id"], + handoff, + project_id=state.project_id, + partition="handoffs", + ) + execution.store.append_event( + "handoff.enriched", + { + "handoff_id": handoff["handoff_id"], + "manifest_id": manifest.manifest_id, + "execution_id": execution_record.execution_id, + }, + project_id=state.project_id, + actor="execution_service", + ) + diagnostics = [ + *state.diagnostics, + ( + f"Registered execution {execution_record.execution_id} " + f"finished with status={execution_record.status}." + ), + ] + blockers = list(state.blockers) + if execution_record.status != "completed": + blockers.append("Registered execution did not produce qualifying result evidence.") + return state.model_copy( + update={ + "execution_manifest_ids": manifest_ids, + "execution_run_ids": execution_ids, + "evidence_ids": evidence_ids, + "handoff": handoff or state.handoff, + "diagnostics": diagnostics, + "blockers": blockers, + "updated_at": utc_now(), + } + ).model_dump(mode="json") + def after_final_review(state: WorkflowState) -> str: if _is_blocked(state) == "stop": return "stop" @@ -191,6 +304,7 @@ def release_gate(raw_state: WorkflowState) -> dict: builder.add_node("paper_reader", reader_graph) builder.add_node("reviewer_innovation", innovation_review_graph) builder.add_node("orchestrator_execution", execution_graph) + builder.add_node("execution_service", execute_registered_work) builder.add_node("writer", writer_graph) builder.add_node("reviewer_manuscript", manuscript_review_graph) builder.add_node("release_gate", release_gate) @@ -208,7 +322,8 @@ def release_gate(raw_state: WorkflowState) -> dict: _is_blocked, {"continue": "orchestrator_execution", "stop": END}, ) - builder.add_edge("orchestrator_execution", "writer") + builder.add_edge("orchestrator_execution", "execution_service") + builder.add_edge("execution_service", "writer") builder.add_edge("writer", "reviewer_manuscript") builder.add_conditional_edges( "reviewer_manuscript", diff --git a/src/autoresearch/storage.py b/src/autoresearch/storage.py index 00e8b5a..cf36149 100644 --- a/src/autoresearch/storage.py +++ b/src/autoresearch/storage.py @@ -211,3 +211,11 @@ def remember_idempotent(self, scope: str, key: str, result: dict[str, Any]) -> b (scope, key, self._json(result), utc_now().isoformat()), ) return cursor.rowcount == 1 + + def get_idempotent(self, scope: str, key: str) -> dict[str, Any] | None: + with self.connection() as connection: + row = connection.execute( + "SELECT result_json FROM idempotency WHERE scope=? AND idempotency_key=?", + (scope, key), + ).fetchone() + return json.loads(row["result_json"]) if row else None diff --git a/src/autoresearch/writing_service.py b/src/autoresearch/writing_service.py index 00fe246..b8d8d3b 100644 --- a/src/autoresearch/writing_service.py +++ b/src/autoresearch/writing_service.py @@ -38,6 +38,10 @@ def _citations(cards: list[ReadingCard]) -> str: for card in cards ) + def get(self, manuscript_id: str) -> Manuscript | None: + raw = self.store.get("manuscript", manuscript_id) + return Manuscript.model_validate(raw) if raw else None + def draft( self, project_id: str, @@ -55,6 +59,13 @@ def draft( and item.grade in {EvidenceGrade.E2, EvidenceGrade.E3} and item.valid ] + synthetic_execution = [ + item + for item in evidence + if item.evidence_type == EvidenceType.EXPERIMENT + and item.metadata.get("data_classification") == "synthetic_fixture" + and item.valid + ] gaps: list[str] = [] if not experiment_evidence: gaps.append("缺少可追溯实验结果;Results 只能保留待填占位,不得声称性能提升。") @@ -62,6 +73,10 @@ def draft( gaps.append("阅读卡不足两篇,相关工作覆盖度尚未闭环。") if not innovations: gaps.append("尚无经阅读 Agent 形成的创新候选。") + if synthetic_execution and not experiment_evidence: + gaps.append( + "当前执行结果来自 synthetic_fixture,只能验证软件链路,不能作为真实 MD 机理结论。" + ) innovation_text = ( "\n".join( diff --git a/tests/test_execution_service.py b/tests/test_execution_service.py new file mode 100644 index 0000000..0353754 --- /dev/null +++ b/tests/test_execution_service.py @@ -0,0 +1,271 @@ +from __future__ import annotations + +import asyncio +import hashlib +import json +from pathlib import Path + +import httpx +import pytest + +from autoresearch.api import create_api +from autoresearch.application import AutoResearchApplication +from autoresearch.contracts import ( + DataClassification, + ExecutionStatus, + MetalDamageAnalysisInput, + PaperRecord, + RunManifestCreate, + RunRequest, +) + + +def _damage_input( + *, + classification: DataClassification = DataClassification.SYNTHETIC_FIXTURE, + source_checksum: str | None = None, +) -> MetalDamageAnalysisInput: + stresses = [0.0, 2.1, 4.0, 5.5, 6.2, 5.8, 4.5, 2.8] + observations = [ + { + "strain": index * 0.02, + "stress_gpa": stress, + "dislocation_density_m2": 1.0e12 * (index + 1), + "void_fraction": 0.001 * max(0, index - 2), + "centrosymmetry": 0.2 + index * 0.1, + } + for index, stress in enumerate(stresses) + ] + return MetalDamageAnalysisInput( + material="fcc-Ni", + temperature_k=300, + strain_rate_s=1.0e8, + data_classification=classification, + source_uri=("artifact://md/trajectory-summary.json" if source_checksum else None), + source_checksum=source_checksum, + observations=observations, + ) + + +def _papers(project_id: str, tmp_path: Path) -> list[PaperRecord]: + paths = [] + for index in range(2): + path = tmp_path / f"metal-damage-{index}.txt" + path.write_text( + "Molecular dynamics tensile loading records stress strain response, dislocation " + "evolution, void growth, and lattice disorder. The method compares boundary " + "conditions and reports limitations; the indicators alone do not prove causality.", + encoding="utf-8", + ) + paths.append(path) + return [ + PaperRecord( + project_id=project_id, + title=f"Atomistic ductile damage study {index}", + abstract=( + "This molecular dynamics study evaluates metallic ductile damage with " + "traceable stress-strain and defect observations." + ), + authors=[f"Researcher {index}"], + year=2024 + index, + doi=f"10.1000/md-damage-{index}", + source="user_seed", + source_record_id=f"md-seed-{index}", + full_text_path=str(path), + ) + for index, path in enumerate(paths) + ] + + +def test_domain_profile_materializes_metal_damage_scope( + runtime: AutoResearchApplication, + project, +): + result = runtime.run( + RunRequest( + project_id="demo", + idea="分子动力学中的金属韧性损伤机理分析", + ) + ) + assert result["status"] == "blocked" + charter = runtime.store.list("research_charter", project_id="demo")[-1] + assert charter["domain_profile"] == "metal_ductile_damage_v1" + assert "void fraction" in charter["variables"] + packages = runtime.execution.list_work_packages("demo") + assert [package.title for package in packages] == [ + "WP1 Literature evidence", + "WP2 MD protocol", + "WP3 Damage analysis", + "WP4 Manuscript", + ] + + +def test_manifest_execution_is_idempotent_and_preserves_lineage( + runtime: AutoResearchApplication, + project, +): + input_data = _damage_input() + request = RunManifestCreate( + project_id="demo", + input_data=input_data, + idempotency_key="demo:damage:fixture:001", + ) + first = runtime.create_run_manifest(request) + second = runtime.create_run_manifest(request) + assert second.manifest_id == first.manifest_id + assert first.input_checksum == runtime.execution.input_checksum(input_data) + + executed = runtime.execute_manifest(first.manifest_id) + repeated = runtime.execute_manifest(first.manifest_id) + assert executed.status == ExecutionStatus.COMPLETED + assert repeated.execution_id == executed.execution_id + assert executed.metrics["peak_stress_gpa"] == 6.2 + assert executed.metrics["damage_onset_strain"] == 0.1 + assert "void_fraction_increased" in executed.metrics["mechanism_indicators"] + assert executed.artifact_refs[0].checksum + assert executed.evidence_ids + + project_dir = Path(runtime.settings.projects_dir) / "demo" / "04_execution" / "runs" + result_path = project_dir / first.manifest_id / "RESULT.json" + manifest_path = project_dir / first.manifest_id / "RUN_MANIFEST.json" + assert result_path.is_file() + assert manifest_path.is_file() + stored = json.loads(result_path.read_text(encoding="utf-8")) + assert stored["input_checksum"] == first.input_checksum + actual_result_checksum = "sha256:" + hashlib.sha256(result_path.read_bytes()).hexdigest() + assert executed.artifact_refs[0].checksum == actual_result_checksum + evidence = runtime.evidence.get(executed.evidence_ids[0]) + assert evidence is not None + assert evidence.grade == "E1" + assert evidence.metadata["data_classification"] == "synthetic_fixture" + changed = input_data.model_copy(update={"temperature_k": 600}) + with pytest.raises(ValueError, match="idempotency key"): + runtime.create_run_manifest(request.model_copy(update={"input_data": changed})) + + +def test_invalid_observation_sequence_fails_without_result_evidence( + runtime: AutoResearchApplication, + project, +): + input_data = _damage_input() + observations = list(input_data.observations) + observations[3] = observations[3].model_copy(update={"strain": observations[2].strain}) + invalid = input_data.model_copy(update={"observations": observations}) + manifest = runtime.create_run_manifest( + RunManifestCreate( + project_id="demo", + input_data=invalid, + idempotency_key="demo:damage:invalid:001", + ) + ) + execution = runtime.execute_manifest(manifest.manifest_id) + assert execution.status == ExecutionStatus.FAILED + assert execution.error_type == "ValueError" + assert execution.evidence_ids == [] + assert runtime.execution.get_manifest(manifest.manifest_id).status == ExecutionStatus.FAILED + + +def test_real_summary_requires_matching_observation_checksum( + runtime: AutoResearchApplication, + project, +): + provisional = _damage_input( + classification=DataClassification.MD_TRAJECTORY_SUMMARY, + source_checksum="sha256:" + "0" * 64, + ) + checksum = runtime.execution.observation_checksum(provisional) + input_data = provisional.model_copy(update={"source_checksum": checksum}) + manifest = runtime.create_run_manifest( + RunManifestCreate( + project_id="demo", + input_data=input_data, + idempotency_key="demo:damage:md-summary:001", + ) + ) + execution = runtime.execute_manifest(manifest.manifest_id) + evidence = runtime.evidence.get(execution.evidence_ids[0]) + assert evidence is not None + assert evidence.grade == "E2" + + +def test_workflow_executes_fixture_but_keeps_manuscript_blocked( + runtime: AutoResearchApplication, + project, + tmp_path: Path, +): + result = runtime.run( + RunRequest( + project_id="demo", + idea="分子动力学中的金属韧性损伤机理分析", + seed_papers=_papers("demo", tmp_path), + execution_input=_damage_input(), + execution_idempotency_key="demo:workflow:damage:001", + ) + ) + state = result["state"] + assert len(state.get("execution_manifest_ids", [])) == 1, state + assert len(state["execution_run_ids"]) == 1 + execution = runtime.execution.get_execution(state["execution_run_ids"][0]) + assert execution is not None and execution.status == ExecutionStatus.COMPLETED + assert result["status"] == "blocked" + manuscript = runtime.store.get("manuscript", state["manuscript_id"]) + assert any("synthetic_fixture" in gap for gap in manuscript["unresolved_gaps"]) + + async def fetch_manuscript(): + transport = httpx.ASGITransport(app=create_api(runtime)) + async with httpx.AsyncClient(transport=transport, base_url="http://testserver") as client: + return await client.get(f"/manuscripts/{state['manuscript_id']}") + + response = asyncio.run(fetch_manuscript()) + assert response.status_code == 200 and response.json()["release_ready"] is False + + +def test_workflow_consumes_checksum_bound_md_summary( + runtime: AutoResearchApplication, + project, + tmp_path: Path, +): + provisional = _damage_input( + classification=DataClassification.MD_TRAJECTORY_SUMMARY, + source_checksum="sha256:" + "0" * 64, + ) + checksum = runtime.execution.observation_checksum(provisional) + real_summary = provisional.model_copy(update={"source_checksum": checksum}) + result = runtime.run( + RunRequest( + project_id="demo", + idea="分子动力学中的金属韧性损伤机理分析", + seed_papers=_papers("demo", tmp_path), + execution_input=real_summary, + execution_idempotency_key="demo:workflow:damage:md-summary:001", + ) + ) + state = result["state"] + assert result["status"] == "completed" + assert state["lifecycle_state"] == "draft_reviewed" + manuscript = runtime.store.get("manuscript", state["manuscript_id"]) + assert manuscript["unresolved_gaps"] == [] + assert "6.2 GPa" in manuscript["sections"]["Results"] + + +def test_execution_http_contract(runtime: AutoResearchApplication, project): + async def scenario(): + transport = httpx.ASGITransport(app=create_api(runtime)) + async with httpx.AsyncClient(transport=transport, base_url="http://testserver") as client: + payload = { + "project_id": "demo", + "input_data": _damage_input().model_dump(mode="json"), + "idempotency_key": "demo:http:damage:001", + } + created = await client.post("/execution/manifests", json=payload) + assert created.status_code == 201 + manifest_id = created.json()["manifest_id"] + executed = await client.post(f"/execution/manifests/{manifest_id}/execute") + assert executed.status_code == 200 + execution_id = executed.json()["execution_id"] + fetched = await client.get(f"/execution/runs/{execution_id}") + assert fetched.status_code == 200 + assert fetched.json()["status"] == "completed" + assert (await client.get("/manuscripts/missing")).status_code == 404 + + asyncio.run(scenario()) From 4bacb906a1269cc6b106af1861d395784cde7e3b Mon Sep 17 00:00:00 2001 From: zzg Date: Sun, 23 Aug 2026 02:03:29 +0800 Subject: [PATCH 2/4] feat: generate complete metal damage manuscript framework --- .ai-team/TASK.md | 12 +- .project-to-act/PROJECT_ACCEPTANCE.md | 7 +- .project-to-act/PROJECT_FEATURES.md | 5 +- .project-to-act/PROJECT_OVERVIEW.md | 7 +- .project-to-act/PROJECT_PROGRESS.md | 3 +- .project-to-act/PROJECT_VERSIONS.md | 3 +- .../.project-to-act/PROJECT_ACCEPTANCE.md | 5 +- .../.project-to-act/PROJECT_FEATURES.md | 3 +- .../.project-to-act/PROJECT_OVERVIEW.md | 7 +- .../.project-to-act/PROJECT_PROGRESS.md | 3 +- .../.project-to-act/PROJECT_VERSIONS.md | 3 +- .../05_writing/MANUSCRIPT_DRAFT.md | 199 +++++++++++++++--- .../05_writing/OUTLINE.md | 50 +++-- .../06_review/REVIEW_LOG.md | 1 + src/autoresearch/writing_service.py | 137 +++++++++++- tests/test_execution_service.py | 31 +++ tests/test_workflow.py | 2 +- 17 files changed, 409 insertions(+), 69 deletions(-) diff --git a/.ai-team/TASK.md b/.ai-team/TASK.md index 3fa67a3..c7dbb0b 100644 --- a/.ai-team/TASK.md +++ b/.ai-team/TASK.md @@ -1,7 +1,7 @@ # Current Task - ID: `AR-005` -- Title: `Implement the M08 metal ductile-damage research pilot vertical slice` +- Title: `Implement the M08 metal ductile-damage pilot and complete manuscript framework` - Status: `handoff` - Owner: `Codex` - Next owner: `user/team` @@ -20,6 +20,7 @@ - [x] API 与 CLI 使用同一应用服务,可创建/读取 manifest、执行/读取 run,并可从主 workflow 消费合格结果证据。 - [x] 用真实本地 HTTP listener 跑通正常计算、无输入阻断和 synthetic 不可作为论文真实结果的客户旅程,报告进入 append-only evidence ledger。 - [x] 使用 `llm-api-config` 的受管配置实际调用 LLM,对指定选题返回结构化研究范围;失败时明确记录 blocked/inconclusive,不用离线结果冒充外部成功。 +- [x] 在不伪造真实 MD 数值、图表或引用的前提下,形成摘要、引言、相关工作、假设、方法、实验设计、结果组织、讨论、局限性、结论和补全清单均有正文的完整论文论述稿。 - [x] Ruff、pytest warnings-as-errors、doctor、Project-to-Act validate、repo-task-sync check 全部通过并记录新鲜证据。 ## Invariants @@ -37,6 +38,7 @@ - 当前实现聚焦 M08 与领域 Pilot 纵切,不把 PostgreSQL/pgvector/Neo4j、多租户 Web UI 或真实 LAMMPS 大规模模拟虚报为本分支完成。 - 领域执行采用固定注册表中的确定性后处理器;输入为轨迹衍生的结构化观测,不开放任意命令执行。 - 真实 LLM 用于研究范围和检索计划深化,科研结果与 Gate 仍由证据和确定性服务控制。 +- 用户要求的“正文填充”按完整研究论证稿实现:能够由作者直接审阅和继续补数据,但现有 synthetic 数值不进入材料结果,真实 Results Gate 继续阻断。 ## Completed @@ -49,16 +51,18 @@ - 已实例化 `paper-projects/md-metal-ductile-damage-pilot`,完整维护独立 Project-to-Act、fixture、run artifact、缺口稿件和运行索引。 - 已用受管 GLM/DeepSeek 和真实 loopback listener 测试;GLM/provider timeout 与 AAWO 15 秒 timeout 保留为非通过证据,DeepSeek observed run 与 6 条 HTTP 旅程通过。 - 已完成 25 tests、84% coverage、Ruff、doctor、CLI、YAML、双账本和协作校验。 +- 已将原缺口稿扩写为约 9800 字符的中文论文论述稿,并同步完成论文提纲、真实结果落位表、事件定义、对照矩阵、复验方案和证据边界。 +- 已修正 WritingService 的金属韧性损伤领域化生成路径并新增回归,后续工作流在无真实结果时仍生成完整论述章节,不再退化为一行占位稿。 ## Pending - AR-005 软件分支无剩余实现项。 -- 项目级后续仍需用户/科研负责人提供许可全文、目标材料/晶向/势函数、真实 MD 数据和独立复验;这些外部输入不属于本分支可伪造完成的范围。 +- 项目级后续仍需用户/科研负责人确认或提供许可全文、目标材料/晶向/势函数、真实 MD 数据和独立复验;完整正文框架已经形成,但这些外部输入仍不能伪造完成。 - PostgreSQL/队列/容器 executor/Web UI/生产灾备仍按 M17 推进,不属于 AR-005。 ## Next step -由用户/团队审阅并合并本完整分支;随后以 Pilot 账本的下一步导入许可全文和带 checksum 的真实 `md_trajectory_summary`。 +由用户/团队审阅完整论述稿并确认材料体系与模拟协议;随后导入许可全文和带 checksum 的真实 `md_trajectory_summary`,按稿件中的结果落位表补齐图表与材料结论。 ## Verification @@ -74,4 +78,4 @@ - From: `Codex` - To: `user/team` -- Summary: AR-005 完整分支已收口;M08 软件纵切、真实 LLM/HTTP 证据和实例化 Pilot 均在分支内。synthetic fixture 与题录/摘要只验证软件路径,真实论文/MD/生产 Gate 仍保持未通过。 +- Summary: AR-005 完整分支已收口;M08 软件纵切、真实 LLM/HTTP 证据、实例化 Pilot 和正文完整的研究论述稿均在分支内。synthetic fixture 与题录/摘要只验证软件路径,真实论文结果/MD/生产 Gate 仍保持未通过。 diff --git a/.project-to-act/PROJECT_ACCEPTANCE.md b/.project-to-act/PROJECT_ACCEPTANCE.md index 3c3bf6b..5363f2c 100644 --- a/.project-to-act/PROJECT_ACCEPTANCE.md +++ b/.project-to-act/PROJECT_ACCEPTANCE.md @@ -6,8 +6,8 @@ ## 当前验收结论 - 结论:0.1.0 Foundation 与 AR-005 M08 软件纵切验收通过;生产级 P1–P6 与真实论文/MD 项目仍未验收 -- 验收范围:严格五 Agent 本地架构、M08 RunManifest/执行 lineage、CLI/API、证据门禁、实例化项目和隔离客户旅程 -- 最后检查:2026-08-22(AR-005 收口检查) +- 验收范围:严格五 Agent 本地架构、M08 RunManifest/执行 lineage、领域化完整论文框架、CLI/API、证据门禁、实例化项目和隔离客户旅程 +- 最后检查:2026-08-23(AR-005 稿件框架检查) - 遗留问题:许可全文/gold corpus/真实 MD/独立复验、生产数据库、备份恢复、Web/认证和真实投稿均未验收 ## 验收标准 @@ -43,6 +43,7 @@ | A-FND-012 | CLI/API 可运行且配置摘要不泄露秘密 | 已通过 | doctor、ASGI、真实 listener、受管 LLM 最小调用 | E-RUN-001、E-JOURNEY-001、E-LIVE-001 | | A-FND-013 | 自进化没有自动改源码/策略的执行路径 | 已通过 | proposal-only 状态测试与接口审查 | E-RUN-001 | | A-FND-014 | 文档覆盖架构、功能、接口、运维、安全、开发和测试 | 已通过 | 文件清单与链接检查 | E-RUN-001 | +| A-FND-015 | 金属韧性损伤 Writer 在缺真实结果时仍生成完整论述框架,且不伪造数值 | 已通过 | 领域工作流回归、全稿结构和占位检查 | E-WRITE-001 | ### 产品运行时验收标准 @@ -84,6 +85,7 @@ | E-COLLAB-001 | 2026-08-22 | VibeCollab check;根/模板 Project-to-Act validate | 0 | AR-001 9/9;两账本 zero issues | 当前 Git 任务、代码、验收和长期账本一致;private sessions disabled | `.ai-team/TASK.md`、`evidence/FOUNDATION_EVIDENCE.md` | TASK 或账本变化前 | | E-M08-001 | 2026-08-22 | Ruff;25 tests warnings-as-errors;84% coverage;doctor;CLI register/execute/status;双账本/YAML 校验 | 0 | `0.2.0.dev0`;收口提交后以 Git commit 固化 | 金属韧性损伤 Profile、RunManifest、幂等、hash、失败不产证据、E1/E2 和 workflow Gate 通过 | `evidence/AR005_M08_EVIDENCE.md`、`tests/test_execution_service.py` | 代码/fixture 变化前 | | E-M08-LIVE-001 | 2026-08-22 | 受管 GLM/DeepSeek;127.0.0.1:8137;AAWO HTTP journeys | GLM/provider timeout 和 AAWO 15s timeout 如实保留;6 pass | run `run_c3357978832d4b77`、`run_b33c1d0b69564e6f`;报告 hash 见证据文件 | DeepSeek scope 成功;synthetic 执行 completed 但稿件 blocked | `evidence/journeys/ar005-*`、`evidence/AR005_M08_EVIDENCE.md` | 7 天或模型/HTTP 合同变化前 | +| E-WRITE-001 | 2026-08-23 | Ruff;26 tests warnings-as-errors;doctor;根/论文 Project-to-Act validate;repo-task-sync;全稿章节/占位/diff 检查 | 0 | 收口提交后以 Git commit 固化 | Writer 生成领域化完整框架;手工稿约 9800 字符、35 个标题、0 个待填写/TODO;synthetic 结果仍阻断 | `src/autoresearch/writing_service.py`、`tests/test_execution_service.py`、论文 `05_writing/` | Writer、稿件或研究边界变化前 | ## Gate 记录 @@ -97,6 +99,7 @@ 按时间倒序追加:日期、检查范围、证据 ID、结果、遗留问题和结论。失败、跳过与过期证据也必须如实记录。 +- 2026-08-23|AR-005 稿件框架:Writer 领域化框架回归、26 tests、Ruff、doctor、双账本和协作校验通过;完整论述稿无待填写/TODO,synthetic 未写成材料结果|E-WRITE-001|论文框架 PASS|遗留:无许可全文/真实 MD/独立复验/图表|结论:A-FND-015 通过;A-RUN-018 真实论文试点仍未通过。 - 2026-08-22|AR-005 M08 软件纵切:25 tests、84% coverage、doctor、CLI、双账本、真实 DeepSeek scope、AAWO 6 pass/1 timeout inconclusive、真实 OpenAlex 题录+synthetic 完整 run 和稿件缺口|E-M08-001、E-M08-LIVE-001|G-M08-SW-001 PASS|遗留:无许可全文/真实 MD/独立复验/生产 executor|结论:A-RUN-010 软件合同通过;A-RUN-018 真实论文试点仍未通过。 - 2026-08-22|0.1.0 foundation 收口:18 tests、84% coverage、warnings-as-errors、doctor、四个真实 HTTP 客户旅程、OpenAlex/Crossref 最小 live、S2 受限诊断、受管 LLM 最小 connectivity、子项目运行投影与完整文档|E-RUN-001、E-JOURNEY-001、E-LIVE-001、E-PROJECT-001|G-FND-001 PASS|遗留:严格产品 A-RUN-002–020 多数仍需生产级/真实论文证据|结论:基础架构和功能纵向切片可运行,不可声明真实论文或生产交付完成。 diff --git a/.project-to-act/PROJECT_FEATURES.md b/.project-to-act/PROJECT_FEATURES.md index 336a47b..dd7dbf0 100644 --- a/.project-to-act/PROJECT_FEATURES.md +++ b/.project-to-act/PROJECT_FEATURES.md @@ -50,8 +50,8 @@ | F-AG-015 | 论文读取 Agent:claim-evidence map | P0 | 进行中 | F-EG-003 / P2 | 每个关键主张绑定页/节/图/表和 evidence ID | E-RUN-001 | | F-AG-016 | 论文读取 Agent:跨论文综合与冲突分析 | P0 | 已规划 | F-KB-007 / P2 | 支持/反驳/不可比/未知分开,来源独立性可见 | 无 | | F-AG-017 | 论文读取 Agent:创新候选挖掘 | P0 | 进行中 | F-AG-016 / P3 | 输出最近似工作、区别特征、最小实验、反证条件,不直接宣称创新 | E-RUN-001、E-JOURNEY-001 | -| F-AG-018 | 写作 Agent:证据化提纲与论证链 | P0 | 进行中 | F-AG-017,F-EX-004 / P4 | 每节输入 claim/evidence/result 明确,缺口使用占位符 | E-RUN-001 | -| F-AG-019 | 写作 Agent:稿件生成、修改与润色 | P0 | 进行中 | F-AG-018 / P4 | 不新增无证据事实/数字,保留不确定性,版本差异可审计 | E-RUN-001、E-JOURNEY-001 | +| F-AG-018 | 写作 Agent:证据化提纲与论证链 | P0 | 进行中 | F-AG-017,F-EX-004 / P4 | 每节输入 claim/evidence/result 明确,缺口以完整论述和显式证据边界呈现 | E-RUN-001、E-WRITE-001 | +| F-AG-019 | 写作 Agent:稿件生成、修改与润色 | P0 | 进行中 | F-AG-018 / P4 | 不新增无证据事实/数字,领域化完整框架、保留不确定性,版本差异可审计 | E-RUN-001、E-JOURNEY-001、E-WRITE-001 | | F-AG-020 | 写作 Agent:引用与目标模板适配 | P1 | 已规划 | F-KB-009 / P4 | BibTeX/CSL 与正文引用双向一致,引用均可解析 | 无 | | F-AG-021 | 审核 Agent:证据/方法/创新/结果/引用审核 | P0 | 进行中 | F-EG-004 / P1–P4 | 输出问题等级、依据、回退目标和复验条件;不能修改证据自批 | E-RUN-001 | | F-AG-022 | 审核 Agent:定向打回与复验 | P0 | 进行中 | F-WF-004 / P1 | 明确打回阻止前进;修订后按原标准复验并保留历史 | E-RUN-001、E-JOURNEY-001 | @@ -144,6 +144,7 @@ 按时间倒序追加:日期、功能 ID、变化、原因、影响、证据 ID 和确认来源。 +- 2026-08-23|领域化 Writer|F-AG-018/019 增加金属韧性损伤完整研究论证框架,证据不足时不再只输出一行占位稿;synthetic 与真实 Results 的 Gate 不变|证据:E-WRITE-001|确认来源:用户本轮指令。 - 2026-08-22|M08 金属韧性损伤纵切|F-EX-002/003/004 继续保持“进行中”,但已增加可运行的 RunManifest、固定 Profile、输入/来源/输出 hash、幂等、失败记录、E1/E2 分类和 synthetic 稿件阻断;未因软件 fixture 将真实实验或通用生产能力标完成|证据:E-M08-001、E-M08-LIVE-001|确认来源:用户本轮指令。 - 2026-08-22|Foundation 纵向切片|将已落地但尚未满足生产级完成条件的功能更新为“进行中”;F-WF-002(严格五 Agent 父图)和 F-EG-009(证据不足/未闭环/打回 fail-closed)有新鲜证据后标为“已完成”|原因:代码、测试、真实本地 HTTP 旅程和文档已经交付,但真实论文/gold corpus/生产数据库/灾备仍未验收|影响:下一阶段从真实文献与执行证据继续,而非重做基础骨架|证据:E-RUN-001、E-JOURNEY-001、E-LIVE-001、E-PROJECT-001|确认来源:用户本轮构建指令。 diff --git a/.project-to-act/PROJECT_OVERVIEW.md b/.project-to-act/PROJECT_OVERVIEW.md index 0076d69..aa6264a 100644 --- a/.project-to-act/PROJECT_OVERVIEW.md +++ b/.project-to-act/PROJECT_OVERVIEW.md @@ -10,8 +10,8 @@ - 项目负责人:用户/项目负责人(具体姓名不在账本保存) - 风险等级:T4 / 高(长周期多 Agent、外部论文源、实验/写作与可选外部动作) - 当前阶段:0.1.0 Foundation Preview 已验收,P1–P5 生产级条件进行中 -- 当前状态:严格五 Agent 本地运行时与 M08 金属韧性损伤软件纵切已交付;真实 LLM/HTTP 边界有证据,真实论文/MD 试点与生产部署未完成 -- 最后更新:2026-08-22 +- 当前状态:严格五 Agent 本地运行时、M08 金属韧性损伤软件纵切和领域化完整论文框架已交付;真实 LLM/HTTP 边界有证据,真实论文结果/MD 试点与生产部署未完成 +- 最后更新:2026-08-23 ## 项目目标 @@ -66,7 +66,7 @@ ## 当前焦点 - 下一里程碑:0.2.0 Literature Pilot,以“分子动力学中的金属韧性损伤机理分析”为首个领域纵切 -- 当前工作重点:AR-005 优先补 M08 RunManifest、确定性损伤后处理、artifact lineage、幂等和结果门禁;不重做 foundation 骨架 +- 当前工作重点:审阅 AR-005 完整论述稿,锁定材料与模拟协议,导入真实 MD 与全文证据;不重做 foundation 骨架 - 主要阻塞:选题已明确,但尚无许可全文、真实 LAMMPS/MD 输出、目标 venue 和实验资源边界;不阻塞软件纵切,阻塞真实科研结论验收 ## 按需读取索引 @@ -83,6 +83,7 @@ 按时间倒序追加:决定 ID、日期、决定、原因、影响、证据 ID、确认来源和复审条件。 +- D-004|2026-08-23|证据不足时 Writer 仍生成正文完整的研究论证框架,以显式证据边界替代空白 Results 占位|原因:用户指出占位稿不可审阅,要求至少搭建完整框架并填充正文|影响:金属韧性损伤选题获得领域化摘要、问题、假设、方法、实验设计、结果组织、讨论、局限性和结论;真实结果 Gate 仍 fail-closed|证据:E-WRITE-001|确认来源:用户本轮指令|复审:真实结果或目标稿件类型变化时。 - D-003|2026-08-22|采用 VibeCollab repo-task-sync 管理当前 Git 任务,Project-to-Act 继续作为平台长期唯一治理账本,论文子项目保留独立账本|原因:用户指定拉取 vibecollab skill,并要求项目文件夹管控进度|影响:.ai-team 管短周期 task/handoff,根和子项目账本管范围/里程碑/验收,禁止三份事实互相覆盖|证据:E-PROJECT-001、E-JOURNEY-001|确认来源:用户本轮指令|复审:协作模型或 canonical ledger 变化时。 - D-002|2026-08-21|目标运行时明确为 LangGraph;嵌套 DeepReason 框架只作为能力与测试参考|原因:用户明确要求 LangGraph,且嵌套仓库 AGENTS.md 的 OpenCode 原生方向不满足本次目标|影响:新实现不能沿用旧拓扑作为主运行时|证据:E-BASE-001|确认来源:用户本轮指令|复审:除非用户改变技术路线。 - D-001|2026-08-21|业务 Agent 固定为总控、搜索、读取、写作、审核五个;证据/门禁/状态/记忆等均为服务|原因:严格落实用户角色划分并避免隐藏 Agent 膨胀|影响:现有 14 Agent 模板必须重构|证据:E-PLAN-001|确认来源:用户本轮指令|复审:仅在用户明确批准角色变化时。 diff --git a/.project-to-act/PROJECT_PROGRESS.md b/.project-to-act/PROJECT_PROGRESS.md index d390510..80c912c 100644 --- a/.project-to-act/PROJECT_PROGRESS.md +++ b/.project-to-act/PROJECT_PROGRESS.md @@ -14,7 +14,7 @@ | P1-02 最小 LangGraph 父图与五子图 | 进行中 | Codex | SQLite checkpoint/interrupt/resume 已通过;进程重启、分布式幂等待补 | E-RUN-001、E-JOURNEY-001 | 2026-08-22 | | P2 论文搜索与读取纵向切片 | 进行中 | Codex | 三连接器、题录、阅读卡、locator、陪读已落地;gold corpus 和人工质量验收待补 | E-RUN-001、E-LIVE-001 | 2026-08-22 | | P3 创新挖掘与工作执行 | 进行中 | Codex | AR-005 已补 RunManifest、确定性金属损伤后处理、artifact lineage、幂等和 synthetic 结果门禁;真实 MD 待补 | E-RUN-001、E-M08-001、E-M08-LIVE-001 | 2026-08-22 | -| P4 写作与审核 | 进行中 | Codex | 缺口稿、版本化修改/润色、L3 审核打回已通过;真实 claim audit 待补 | E-RUN-001、E-JOURNEY-001 | 2026-08-22 | +| P4 写作与审核 | 进行中 | Codex | 领域化完整论文框架、版本化修改/润色、L3 审核打回已通过;真实 Results 与 claim audit 待补 | E-RUN-001、E-JOURNEY-001、E-WRITE-001 | 2026-08-23 | | P5 Wiki+Graph、画像与自进化 | 进行中 | Codex | 分区 Wiki/一跳图、画像、经验晋级和 proposal-only 已落地;向量/灰度/回滚待补 | E-RUN-001 | 2026-08-22 | | P6 真实端到端试点与 v1.0 | 已规划 | 待指定 | `PROJECT_ACCEPTANCE.md` 全部运行时标准通过 | 无 | 未开始 | @@ -37,6 +37,7 @@ 按时间倒序追加:日期、完成事项、证据 ID、遗留问题、下一步和确认来源。不要覆盖旧记录。 +- 2026-08-23|补齐 AR-005 论文框架和 Writer 生成能力|手工论述稿约 9800 字符、35 个标题且无待填写占位;Writer 对金属韧性损伤选题生成领域化完整章节,缺真实结果时保留论述而非空壳,并继续阻断材料结论;26 tests、Ruff、doctor 和双账本校验通过|证据:E-WRITE-001|遗留:全文、真实 MD、图表和 claim audit|下一步:确认模拟协议并接入真实数据|确认来源:用户本轮指令。 - 2026-08-22|完成 AR-005 M08 金属韧性损伤软件纵切|新增领域 Profile、RunManifest、确定性后处理、artifact lineage、执行 API/CLI、25 tests、真实受管 DeepSeek scope、6 条 pass/1 条 timeout inconclusive HTTP 旅程和实例化论文 Project-to-Act|证据:E-M08-001、E-M08-LIVE-001|遗留:许可全文、真实 MD 数据、独立复验、生产 executor 未完成|下一步:真实 `md_trajectory_summary` 与人工科研验收|确认来源:用户本轮指令。 - 2026-08-22|启动 AR-005 M08 金属韧性损伤研究纵切|已创建 `codex/ar-005-md-ductile-damage-pilot` 并完整携带 AR-004 参考架构工作区;确定性执行、受管 LLM 和真实 HTTP 旅程进入实现|证据:待生成 E-M08-001/E-M08-LIVE-001|遗留:真实许可 corpus 和真实 MD 输出未提供,synthetic fixture 只能验证软件链路|下一步:实现 RunManifest、后处理器和 lineage 门禁|确认来源:用户本轮指令。 diff --git a/.project-to-act/PROJECT_VERSIONS.md b/.project-to-act/PROJECT_VERSIONS.md index 441d155..072d4c2 100644 --- a/.project-to-act/PROJECT_VERSIONS.md +++ b/.project-to-act/PROJECT_VERSIONS.md @@ -8,7 +8,7 @@ - 版本代号:M08 Metal Damage Software Pilot - 发布状态:本地开发分支包含 M08 金属韧性损伤纵切;不是正式发布,也未完成真实论文验收 - 兼容性说明:已验证 Python 3.12.10、LangGraph 1.2.11、langgraph-checkpoint-sqlite 3.1.1、Pydantic 2.13.4、FastAPI 0.141.1;SQLite profile 仅承诺本地单进程基础验证。 -- 最后更新:2026-08-22 +- 最后更新:2026-08-23 ## 下一版本计划 @@ -33,6 +33,7 @@ 按时间倒序追加:版本号、日期、状态、主要变更、原因、兼容性、证据 ID 和 Gate 结果。 +- `0.2.0.dev0`|2026-08-23|AR-005 稿件框架更新|金属韧性损伤 Writer 由占位式草稿升级为领域化完整论述框架,同时保留真实 Results Gate|原因:用户要求正文可审阅且不能只留占位|兼容性:Manuscript 合同不变;领域稿新增章节键|证据:E-WRITE-001|Gate:框架 PASS,真实论文/MD Gate 未通过。 - `0.2.0.dev0`|2026-08-22|AR-005 开发分支|金属韧性损伤领域 Profile、RunManifest、确定性后处理、artifact lineage、execution API/CLI、真实 LLM/HTTP Pilot 和实例化 Project-to-Act|原因:用户要求参考新架构继续完整功能开发并以该领域真实测试|兼容性:保留 0.1.0 五 Agent/SQLite 合同;新增可选 execution_input 与执行接口|证据:E-M08-001、E-M08-LIVE-001|Gate:软件纵切待最终收口,真实论文/MD/生产 Gate 未通过。 - `0.1.0`|2026-08-22|Foundation Preview|严格五 Agent、LangGraph/SQLite、证据门禁、检索/阅读/创新/工作包/稿件、Wiki+Graph、画像/经验/提案、CLI/API 和项目运行投影|原因:用户要求架构和基础功能完整落地|兼容性:仅经验证的 Python 3.12 本地 profile;旧规划文档无运行时兼容承诺|证据:E-RUN-001、E-JOURNEY-001、E-LIVE-001、E-PROJECT-001|Gate:G-FND-001 PASS,P1–P6 严格 Gate 未全部通过。 diff --git a/paper-projects/md-metal-ductile-damage-pilot/.project-to-act/PROJECT_ACCEPTANCE.md b/paper-projects/md-metal-ductile-damage-pilot/.project-to-act/PROJECT_ACCEPTANCE.md index 238b8f5..6c28c9e 100644 --- a/paper-projects/md-metal-ductile-damage-pilot/.project-to-act/PROJECT_ACCEPTANCE.md +++ b/paper-projects/md-metal-ductile-damage-pilot/.project-to-act/PROJECT_ACCEPTANCE.md @@ -4,7 +4,7 @@ - 结论:AR-005 软件 Pilot 通过;真实金属韧性损伤科研项目未验收 - 验收范围:领域 Profile、受管 LLM、真实 HTTP 边界、RunManifest、deterministic 后处理、artifact lineage、synthetic Gate -- 最后检查:2026-08-22 +- 最后检查:2026-08-23 - 遗留问题:许可全文、真实 MD 数据、独立复验、人工 ReadingCard、真实结果稿和本地复现包 ## 验收标准 @@ -22,6 +22,7 @@ | PA-009 | 稿件 claim、数字、图表和引用全绑定真实证据 | 已阻塞 | manuscript claim audit | 无 | | PA-010 | 本地交付与复现包完整 | 已阻塞 | delivery checklist | 无 | | PA-011 | 外部投稿有 H3 人工批准 | 未检查 | G9 与外部结果审计 | 无 | +| PA-012 | 论文框架与非结果正文完整,且 synthetic 未被写成材料结论 | 已通过 | 章节、占位符、结果边界与 Git diff 检查 | PE-WRITE-001 | ## 证据索引 @@ -30,6 +31,7 @@ | PE-M08-001 | 2026-08-22 | Ruff、25 tests、manifest/idempotency/hash/失败/完整 workflow 回归 | 0 | 收口提交前补 Git commit;artifact 自带 sha256 | M08 软件纵切通过,synthetic E1 | `tests/test_execution_service.py`、`04_execution/runs/` | 代码或 fixture 变化前 | | PE-LLM-001 | 2026-08-22 | 受管 GLM 调用 + 受管 DeepSeek 真实 run + AAWO 读取复核 | GLM timeout;DeepSeek pass | run `run_c3357978832d4b77` | DeepSeek 完成结构化范围;无论文时 blocked | `evidence/journeys/ar005-llm-scope*.json` | 7 天或模型配置变化前 | | PE-HTTP-001 | 2026-08-22 | 127.0.0.1:8137 客户旅程,真实题录+synthetic 执行+稿件缺口 | 0 | run `run_b33c1d0b69564e6f` | 执行 completed;稿件 waiting_evidence;6 条 pass、1 条 timeout inconclusive | `evidence/journeys/ar005-*.json`、ledger | HTTP 合同变化前 | +| PE-WRITE-001 | 2026-08-23 | Writer 领域回归、Ruff、26 tests、doctor、双账本/协作校验、全稿章节/正文长度/占位/synthetic 边界检查 | 0 | 收口提交前补 Git commit | 约 9800 字符、35 个标题、0 个待填写/TODO;后续工作流生成完整框架,真实结果仍明确阻断 | `src/autoresearch/writing_service.py`、`tests/test_execution_service.py`、`05_writing/` | Writer、稿件或研究边界变化前 | ## Gate 记录 @@ -40,4 +42,5 @@ ## 验收记录 +- 2026-08-23|完整论文论述稿|非结果章节和结果组织正文已完整展开,事件定义与当前实现一致,未把 synthetic fixture 写成材料结果|结论:论文框架验收通过;真实科研 G6–G8 仍阻塞。 - 2026-08-22|AR-005 软件 Pilot|25 tests、真实 DeepSeek scope、AAWO HTTP 旅程、synthetic manifest/执行/稿件阻断均符合合同|结论:软件纵切通过;真实科研项目仍阻塞于全文与 MD 数据。 diff --git a/paper-projects/md-metal-ductile-damage-pilot/.project-to-act/PROJECT_FEATURES.md b/paper-projects/md-metal-ductile-damage-pilot/.project-to-act/PROJECT_FEATURES.md index 5b1f394..50b18bf 100644 --- a/paper-projects/md-metal-ductile-damage-pilot/.project-to-act/PROJECT_FEATURES.md +++ b/paper-projects/md-metal-ductile-damage-pilot/.project-to-act/PROJECT_FEATURES.md @@ -21,11 +21,12 @@ | PF-005 | 综合与创新候选 | P0 | 已阻塞 | PF-004 | 最近似工作、区别与反证检索通过审核 | 无 | | PF-006 | RunManifest 与确定性执行 | P0 | 进行中 | PF-002 | synthetic 软件纵切已通过;真实 MD 输入和复验未完成 | PE-M08-001、PE-HTTP-001 | | PF-007 | artifact hash 与 lineage | P0 | 进行中 | PF-006 | 输入/输出/环境/运行/证据/稿件数字可追溯 | PE-M08-001 | -| PF-008 | 稿件生成与缺口保护 | P0 | 进行中 | PF-003,PF-006 | synthetic 缺口可见;真实 claim audit 待补 | PE-HTTP-001 | +| PF-008 | 稿件生成与缺口保护 | P0 | 进行中 | PF-003,PF-006 | 完整论述稿和 synthetic 边界可见;真实 Results 与 claim audit 待补 | PE-HTTP-001、PE-WRITE-001 | | PF-009 | Reviewer 打回与结果 Gate | P0 | 进行中 | PF-008 | synthetic 无法放行已验证;真实复验待补 | PE-HTTP-001 | | PF-010 | 本地复现交付 | P0 | 已阻塞 | PF-004–PF-009 | 真实数据、许可、复验和交付清单闭环 | 无 | | PF-011 | 外部投稿/发布 | P2 | 候选 | PF-010 + H3 | 默认关闭;具名人工批准与外部结果审计 | 无 | ## 功能变更历史 +- 2026-08-23|PF-008|论文由占位缺口稿升级为正文完整的研究论述稿和结果落位提纲,仍以显式证据边界阻断 synthetic 材料结论|证据:PE-WRITE-001|确认来源:用户本轮指令。 - 2026-08-22|PF-001–PF-011|把模板状态替换为 AR-005 实际软件 Pilot 状态;RunManifest/lineage/synthetic Gate 有证据,真实全文/MD/投稿保持未完成|证据:PE-M08-001、PE-LLM-001、PE-HTTP-001|确认来源:用户本轮指令。 diff --git a/paper-projects/md-metal-ductile-damage-pilot/.project-to-act/PROJECT_OVERVIEW.md b/paper-projects/md-metal-ductile-damage-pilot/.project-to-act/PROJECT_OVERVIEW.md index 83fe65e..a19ad99 100644 --- a/paper-projects/md-metal-ductile-damage-pilot/.project-to-act/PROJECT_OVERVIEW.md +++ b/paper-projects/md-metal-ductile-damage-pilot/.project-to-act/PROJECT_OVERVIEW.md @@ -10,8 +10,8 @@ - 项目负责人:Codex(软件纵切);真实科研结论负责人待指定 - 风险等级:T3 / 中高(外部 LLM 与论文元数据、科研结果误表述风险;外部发布关闭) - 当前阶段:G0/G1 软件纵切已建立,G3–G8 真实科研验收未完成 -- 当前状态:真实受管 LLM 已深化范围;两条 OpenAlex 题录参与 HTTP 纵切;synthetic 后处理已完成并被 Reviewer 正确阻断 -- 最后更新:2026-08-22 +- 当前状态:真实受管 LLM 已深化范围;两条 OpenAlex 题录参与 HTTP 纵切;完整研究论述稿已形成;synthetic 后处理已完成并被 Reviewer 正确阻断真实结果 +- 最后更新:2026-08-23 ## 项目目标 @@ -51,7 +51,7 @@ ## 当前焦点 - 下一里程碑:0.2.0-md-evidence,导入带 checksum 的真实 MD 轨迹衍生观测并完成独立复验。 -- 当前工作重点:收口 AR-005 软件纵切、Project-to-Act、HTTP 旅程和证据包。 +- 当前工作重点:审阅完整论文论述稿,锁定材料与模拟协议,并准备导入真实 MD 结果。 - 主要阻塞:尚无授权全文、真实 MD 输出、目标材料/晶向/势函数和科研负责人确认,不能通过 G3–G8。 ## 按需读取索引 @@ -65,4 +65,5 @@ ## 路线变更记录 +- 2026-08-23|将缺口占位稿扩展为正文完整的研究论述稿|原因:用户要求至少搭好论文框架并填充正文|影响:摘要、引言、相关工作、假设、方法、实验设计、结果组织、讨论、局限性、结论和结果补全清单均可审阅;真实 Results Gate 不因文本完整而放行|证据:PE-WRITE-001|确认来源:用户本轮指令。 - 2026-08-22|将该项目定位为金属韧性损伤 M08 软件 Pilot,而非已完成真实论文试点|原因:用户指定选题并要求真实 LLM/Agent 测试,但未提供许可全文和真实 MD 数据|影响:synthetic 结果只验证软件链路,G3–G8 保持阻断|证据:PE-M08-001、PE-LLM-001|确认来源:用户本轮指令。 diff --git a/paper-projects/md-metal-ductile-damage-pilot/.project-to-act/PROJECT_PROGRESS.md b/paper-projects/md-metal-ductile-damage-pilot/.project-to-act/PROJECT_PROGRESS.md index d17579a..5db7ae7 100644 --- a/paper-projects/md-metal-ductile-damage-pilot/.project-to-act/PROJECT_PROGRESS.md +++ b/paper-projects/md-metal-ductile-damage-pilot/.project-to-act/PROJECT_PROGRESS.md @@ -9,7 +9,7 @@ | T-020 文献检索与筛选 | 进行中 | 论文搜索 Agent | HTTP Pilot 使用两条真实 OpenAlex 题录;gold query/许可全文待补 | PE-HTTP-001 | 2026-08-22 | | T-030 论文读取与创新候选 | 已阻塞 | 论文读取 Agent | 需要许可全文和人工 ReadingCard 基准 | 无 | 2026-08-22 | | T-040 工作包与研究执行 | 进行中 | Codex/总控 Agent | synthetic 纵切已闭环;真实 MD RunManifest 与复验待补 | PE-M08-001、PE-HTTP-001 | 2026-08-22 | -| T-050 稿件写作与修改 | 进行中 | 写作 Agent | 缺口稿已生成;真实 Results 和 claim audit 待补 | PE-HTTP-001 | 2026-08-22 | +| T-050 稿件写作与修改 | 进行中 | 写作 Agent | 完整研究论述稿与结果落位表已生成;真实 Results 和 claim audit 待补 | PE-HTTP-001、PE-WRITE-001 | 2026-08-23 | | T-060 审核与本地交付 | 已阻塞 | 审核 Agent | synthetic 缺口已正确打回;真实 G7/G8 未通过 | PE-HTTP-001 | 2026-08-22 | ## 阻塞项 @@ -29,6 +29,7 @@ ## 进度历史 +- 2026-08-23|完整论文论述稿|将原占位缺口稿扩写为摘要、引言、相关工作、研究假设、MD 方法、时序定义、实验矩阵、结果组织、讨论、局限性、结论和补全清单均有正文的可审阅草稿;synthetic 数值未写成材料结果|证据:PE-WRITE-001|遗留:真实全文、MD 结果和 claim audit|下一步:科研负责人确认模拟协议并接入真实数据。 - 2026-08-22|AR-005 软件纵切|真实 DeepSeek 受管模型完成结构化范围深化;首次 GLM 调用 ReadTimeout 被保留为失败证据;五 Agent HTTP 流程使用两条 OpenAlex 题录和 synthetic fixture,执行完成但稿件被正确阻断|证据:PE-LLM-001、PE-HTTP-001|遗留:无许可全文和真实 MD 结果|下一步:真实数据与人工科研确认。 - 2026-08-22|项目实例化|创建独立 Project-to-Act、idea、manifest 和本地项目目录|证据:PE-M08-001|下一步:范围与执行纵切。 diff --git a/paper-projects/md-metal-ductile-damage-pilot/.project-to-act/PROJECT_VERSIONS.md b/paper-projects/md-metal-ductile-damage-pilot/.project-to-act/PROJECT_VERSIONS.md index a1f153d..164f829 100644 --- a/paper-projects/md-metal-ductile-damage-pilot/.project-to-act/PROJECT_VERSIONS.md +++ b/paper-projects/md-metal-ductile-damage-pilot/.project-to-act/PROJECT_VERSIONS.md @@ -5,7 +5,7 @@ - 版本号:`0.1.0-software-pilot` - 发布状态:本地软件纵切可复核;不是科研结果版本,也不可投稿 - 兼容性说明:Python 3.12、SQLite、五 Agent AutoResearch;执行 Profile 为 `metal_ductile_damage_v1` -- 最后更新:2026-08-22 +- 最后更新:2026-08-23 ## 下一版本计划 @@ -25,4 +25,5 @@ ## 版本历史 +- `0.1.0-software-pilot`|2026-08-23|稿件内容更新|在不改变软件/科研 Gate 的前提下,将缺口稿扩为完整研究论述稿与结果落位提纲|证据:PE-WRITE-001|Gate:论文框架 PASS,真实科研 G3–G8 未通过。 - `0.1.0-software-pilot`|2026-08-22|本地可复核|受管 LLM、真实题录、M08 deterministic execution、synthetic 边界和 Reviewer 打回已验证|证据:PE-M08-001、PE-LLM-001、PE-HTTP-001|Gate:软件 Pilot PASS,科研 G3–G8 未通过。 diff --git a/paper-projects/md-metal-ductile-damage-pilot/05_writing/MANUSCRIPT_DRAFT.md b/paper-projects/md-metal-ductile-damage-pilot/05_writing/MANUSCRIPT_DRAFT.md index 2478923..d2b868d 100644 --- a/paper-projects/md-metal-ductile-damage-pilot/05_writing/MANUSCRIPT_DRAFT.md +++ b/paper-projects/md-metal-ductile-damage-pilot/05_writing/MANUSCRIPT_DRAFT.md @@ -1,49 +1,194 @@ -# Research draft: 基于分子动力学分析金属韧性损伤中位错演化、空洞形核长大和应力下降的时序关系 +# 基于分子动力学的金属韧性损伤演化分析:位错活动、空洞演化与承载失稳的时序关联 -## Abstract +## 摘要 -本稿是证据约束的研究草稿。研究目标、方法和结果须在对应 Gate 通过后才能升级为可发布表述。 +金属在拉伸载荷下的韧性损伤通常伴随位错活动、局部晶格无序、空洞形核与长大以及承载能力下降。仅依据应力—应变曲线的峰值或峰后下降,难以判断这些微观过程在何时发生,也难以区分相伴演化与主导机制。本文建立一套面向分子动力学轨迹的时序分析方案:在材料、晶体取向、势函数、温度、应变率、尺寸、边界条件、初始缺陷和随机种子均被登记的前提下,同步提取应力—应变响应、位错密度、空洞体积分数和晶格无序指标;以峰值应力、持续承载下降和半峰值承载为力学事件,以缺陷指标的可重复变化为微观事件,比较不同运行和对照条件下的事件顺序。为避免把单次序列中的共同变化直接解释为因果关系,本文同时给出多随机种子重复、缺陷对照、尺寸与晶向对照、势函数敏感性分析及否证条件。当前工作已完成结构化输入校验、确定性后处理、运行清单、输入输出校验和结果谱系的验证,但现有数值来自明确标记的 synthetic fixture,仅能证明分析链可运行,不能形成真实材料的损伤机理结论。本文因此给出完整的研究设计、指标定义、结果组织方式和判定边界,为导入真实分子动力学数据后的复验与论文定稿提供基础。 -## Introduction +**关键词:** 分子动力学;金属韧性损伤;位错演化;空洞形核与长大;晶格无序;应力—应变;时序分析 -基于分子动力学分析金属韧性损伤中位错演化、空洞形核长大和应力下降的时序关系 +## 1 引言 -## Related Work +金属韧性断裂不是由单一瞬时事件构成。材料在外载作用下先经历弹性响应,随后通过位错形核、滑移和相互作用进入塑性阶段;当局部应力状态、初始缺陷和微观结构满足一定条件时,空洞可能形核并持续长大,最终通过局部化或聚合导致承载能力显著下降。分子动力学能够直接记录原子位置、速度、局部结构和体系应力随时间的变化,因此适合研究这些过程在原子尺度上的演化顺序。 -- [paper_f46ecbdcf61b4249] The process of fracture in ductile metals involves the nucleation, growth, and linking of voids. (locator: abstract) -- [paper_1ee7d9d9c77b4cc0] Void coalescence and interaction in dynamic fracture of ductile metals have been investigated using three-dimensional strain-controlled multimillion atom molecular dynamics simulations of copper. (locator: abstract) +现有分析中,应力—应变曲线常被用来识别屈服、峰值强度和失稳,但曲线本身只反映体系整体响应。相同的峰后下降可能对应不同的微观演化路径:位错增殖可能在应力达到峰值前已经发生,空洞增长可能主要集中在峰后区间,局部晶格无序也可能受到温控、自由表面、边界条件或势函数的影响。如果不把微观指标和力学事件放在同一应变轴上比较,就难以判断某一指标是损伤的前兆、伴随现象,还是失稳后的结果。 -## Proposed Contribution +本研究关注的核心问题是:在给定材料与模拟条件下,位错活动、空洞形核与长大、晶格无序和应力下降呈现怎样的时序关系;当温度、应变率、晶体取向、尺寸、初始缺陷或势函数发生变化时,这一关系是否仍可重复。这里的“时序关系”指各观测事件在统一加载变量上的先后次序和间隔,不预先等同于因果关系。只有当事件次序能够跨独立运行重复,并且在针对性的对照或消融中发生可解释变化时,候选机理才获得更强支持。 -- Test whether the proposed idea (基于分子动力学分析金属韧性损伤中位错演化、空洞形核长大和应力下降的时序关系) closes a documented research gap.(状态:hypothesis;验证:Run the planned baseline and ablation work packages; reject the candidate if the claimed benefit is not reproduced or prior work already covers it.) +围绕这一问题,本文构建从模拟条件登记、轨迹衍生观测、事件识别、多运行比较到证据审查的完整分析框架。论文的直接目标有三项:其一,给出力学事件和缺陷事件的可计算定义;其二,建立能够反驳候选解释的对照矩阵;其三,使每个结果均可追溯到输入、环境、随机种子、后处理版本和输出校验。当前稿件已完成研究框架和分析方法的展开,真实材料结论仍等待登记的分子动力学数据和独立复验。 -## Method +## 2 相关工作与问题界定 -方法由工作包驱动;每项实现、数据、基线与消融均需产出带校验信息的证据。 +韧性断裂研究通常把空洞形核、长大与连接视为重要演化过程。已有分子动力学工作研究了体心立方金属中的空洞长大[1],也有大规模三维模拟讨论铜中空洞相互作用与聚合[2]。这些研究说明,原子尺度模拟能够解析空洞附近的局部响应,并为连续介质模型难以直接观测的缺陷过程提供信息。 -## Experiments +本研究不试图用两条题录概括完整领域,也不把摘要信息视为全文证据。当前可确认的文献线索主要支持两个判断:空洞演化是金属韧性损伤研究中的明确对象;空洞之间的相互作用可以通过三维分子动力学模拟进行研究。位错活动、空洞演化、晶格无序和整体应力之间的统一时序关系,仍需要在许可全文检索、阅读卡和最近似工作比较完成后确定其文献缺口和创新边界。 -执行计划应记录环境、输入、版本、指标、随机性与失败结果。 +基于当前证据,本文把研究缺口限定为项目内部需要回答的问题,而不宣称这是尚未被任何已有工作覆盖的普遍空白。具体而言,后续工作需要检验:多种缺陷指标是否可以在统一事件轴上形成稳定次序;这种次序对材料和模拟条件是否敏感;不同指标的共同变化能否通过缺陷、尺寸、晶向或势函数对照加以区分。该界定为可证伪研究问题,不构成已经通过反检索确认的创新性结论。 -## Results +## 3 研究问题、候选假设与判定原则 -【待真实实验】此处禁止填入推测数值。 +### 3.1 研究问题 -## Discussion +本文设置四个相互关联的问题。 -缺少可追溯实验结果;Results 只能保留待填占位,不得声称性能提升。 -当前执行结果来自 synthetic_fixture,只能验证软件链路,不能作为真实 MD 机理结论。 +第一,在单轴拉伸过程中,从初始弹性响应到峰后失稳可以划分为哪些可重复的力学和微观事件?第二,位错密度、空洞体积分数和晶格无序指标的显著变化分别发生在峰值应力、持续承载下降和半峰值承载之前还是之后?第三,改变温度、应变率、晶体取向、尺寸、初始缺陷和势函数后,事件顺序及其间隔是否保持一致?第四,哪些观测只能支持时序相关,哪些对照能够削弱或支持具体的机制解释? -## Conclusion +### 3.2 候选假设 -仅总结已由证据支持的内容;创新性与外部发布仍由审核和人工门禁控制。 +H1:位错活动的增加先于明显承载下降,并参与峰值应力前后的塑性耗散。该假设只有在多次独立运行中均观察到位错事件领先于持续应力下降,且其空间分布与局部塑性区相符时才获得支持。如果位错变化不领先应力下降,或者改变取向、尺寸后次序不稳定,则该假设需要收缩适用范围或被否定。 -## References +H2:空洞体积分数的持续增加与峰后承载下降相关。该假设要求空洞指标在峰后阶段持续增长,并对初始缺陷或应力状态对照表现出可重复差异。如果无缺陷对照与含缺陷体系没有稳定差别,或空洞变化仅出现在整体失稳之后,则不能把空洞演化解释为承载下降的前置机制。 -- [paper_f46ecbdcf61b4249] Void growth in bcc metals simulated with molecular dynamics using the Finnis–Sinclair potential. 2009. 10.1080/14786430903222529 -- [paper_1ee7d9d9c77b4cc0] Three-dimensional molecular dynamics simulations of void coalescence during dynamic fracture of ductile metals. 2005. 10.1103/physrevb.71.064112 +H3:晶格无序能够作为局部损伤演化的辅助指标。该假设要求无序区域在空间和时间上与局部塑性或空洞演化共同定位,同时排除温控、自由表面和边界伪影。如果无序指标主要随温度波动或边界区域变化,而与损伤位置不一致,则其用途应限于结构状态描述,不能作为独立损伤判据。 -## Unresolved Evidence Gaps +### 3.3 判定原则 -- 缺少可追溯实验结果;Results 只能保留待填占位,不得声称性能提升。 -- 当前执行结果来自 synthetic_fixture,只能验证软件链路,不能作为真实 MD 机理结论。 +候选假设的判定分为三个层次。单次运行中两个指标按先后出现,只支持个案时序描述;相同条件下多随机种子重复出现相同顺序,可支持条件内的稳定关联;针对性对照使某一事件或事件间隔发生可重复变化,才可能加强机制解释。即使完成对照,结论仍需限定在登记的材料、势函数、尺度、边界和加载条件内,不直接外推到宏观构件或一般工程应变率。 + +## 4 分子动力学模型与分析方法 + +### 4.1 总体流程 + +研究流程由模拟建模、轨迹输出、结构识别、时序对齐和证据审查组成。每次运行首先登记材料体系、晶体取向、原子间势函数及其版本、模型尺寸、边界条件、初始缺陷、温度、应变率、时间步长、平衡流程、加载方式、输出步长、随机种子和软件环境。模拟完成后,从同一运行中提取应力—应变序列、位错密度、空洞体积分数和晶格无序指标,并以应变作为统一自变量。后处理只读取结构化观测,不执行任意外部命令;每次分析均保存 RunManifest、输入校验和、输出校验和、运行状态及数据谱系。 + +### 4.2 原子模型与边界条件 + +真实研究需要在运行前锁定目标金属及晶体结构,并明确加载轴与晶体取向的对应关系。模型尺寸应同时满足两个要求:能够容纳目标缺陷和局部塑性区,并允许开展尺寸敏感性对照。周期性边界、自由表面或混合边界会改变位错传播、空洞演化和应力状态,因此必须作为独立实验变量登记,不能在不同运行之间隐式变化。 + +初始缺陷分为无预置空洞、单空洞和经批准的缺陷构型。无预置空洞体系用于观察缺陷是否在加载过程中自发出现;预置空洞体系用于研究空洞附近的局部响应及其长大过程。两类体系不能直接混为一组统计,因为它们回答的问题不同。所有初始构型均需保存可定位文件和校验和,以防止几何差异被误归因于温度、应变率或晶向。 + +### 4.3 势函数、平衡与加载协议 + +原子间势函数决定材料的晶格稳定性、缺陷能和相互作用描述,是本研究的关键适用边界。正式运行应说明势函数名称、来源、文件校验和、元素映射和适用范围,并通过基本结构稳定性检查后再进入拉伸模拟。若两个势函数给出不同的事件顺序,论文应报告这种敏感性,而不是选择更符合预期的一组结果。 + +加载前的模型需要依次完成能量最小化和热平衡。平衡阶段应检查温度、压力或目标应力以及体系能量是否进入稳定波动区间。拉伸阶段采用登记的单轴加载方式,并保存工程应变或真实应变的定义。横向方向是否控压、加载方向是否保持周期性以及恒温器作用于哪些自由度,都需要在方法和运行清单中保持一致。时间步长、加载速率和轨迹输出间隔应经过收敛性或敏感性检查,以避免数值分辨率不足改变事件先后关系。 + +### 4.4 观测量 + +应力—应变响应用于描述整体承载过程。应力分量、符号约定、体积定义和单位转换必须在所有运行中一致。初始弹性斜率通过早期应变窗口内的线性拟合估计;该值主要用于检查运行的一致性,不在缺少独立验证时直接等同于宏观实验弹性模量。 + +位错活动以登记算法得到的位错总长度、位错类型或体积归一化位错密度表示。正式结果不能只报告单个总量,还需结合原子构型快照确认位错来源、滑移区域和边界影响。若算法在高无序或自由表面附近无法稳定识别,应在结果中报告未识别区域及其影响。 + +空洞演化以经验证的空隙识别方法得到的空洞数量、等效尺寸、总体积分数和空间位置描述。空洞体积分数的变化需要与模型总体积定义一致,并区分预置空洞的几何膨胀、新空洞形核和多个空洞的连接。只有时间曲线而没有空间构型时,不能可靠区分这三种过程。 + +晶格无序通过中心对称参数或其他已登记结构指标描述。该指标容易受到热振动、表面原子和局部应变影响,因此应结合基线分布、空间筛选和温度对照确定阈值。正文统一使用“晶格无序指标”作为概念名称,具体采用的算法和阈值在真实协议锁定后写入参数表。 + +### 4.5 力学事件定义 + +设第 \(i\) 个观测点的应变和轴向应力分别为 \(\varepsilon_i\) 和 \(\sigma_i\)。峰值应力事件定义为观测序列中 \(\sigma_i\) 最大的点,对应应变记为 \(\varepsilon_{\mathrm{peak}}\)。持续承载下降起点在当前后处理器中定义为峰值之后首次满足 \(\sigma_i \leq 0.95\sigma_{\mathrm{peak}}\) 的观测点,其应变记为 \(\varepsilon_{\mathrm{d}}\)。半峰值承载事件定义为峰值之后首次满足 \(\sigma_i \leq 0.50\sigma_{\mathrm{peak}}\) 的观测点,其应变记为 \(\varepsilon_{50}\)。若序列内未达到相应阈值,则结果记录为未检测到,而不是外推事件位置。 + +这组定义提供统一、可复算的力学参照,但5%和50%阈值本身不是自然常数。真实研究需要进行阈值敏感性分析,并检查输出步长是否足以分辨事件间隔。对于存在应力振荡的序列,还需预先规定平滑或持续性判定方法,避免把单点波动误判为损伤起始。 + +### 4.6 微观事件与时序比较 + +位错、空洞和晶格无序事件分别依据其时间序列的基线变化、持续性和空间证据识别。真实分析中应在看到结果前固定事件判据,并通过多个随机种子检查判据的稳定性。当前软件纵切只验证了缺陷指标是否从序列首端到末端增加,以及承载下降事件能否被确定性定位;它尚未实现可用于科研结论的变点检测和空间共同定位。因此,正式结果必须在后续版本中补充事件阈值、置信区间或运行间离散度。 + +对任一微观事件 \(m\),定义相对峰值和相对损伤起点的应变间隔为 + +\[ +\Delta\varepsilon_{m,\mathrm{peak}}=\varepsilon_m-\varepsilon_{\mathrm{peak}},\qquad +\Delta\varepsilon_{m,\mathrm{d}}=\varepsilon_m-\varepsilon_{\mathrm{d}}. +\] + +负值表示微观事件先于对应力学事件,正值表示其后发生。论文应同时报告事件顺序、间隔及独立运行中的离散性。只有顺序一致而间隔差异较大时,结论应限于“先于”或“后于”;若顺序本身随条件变化,则应进一步分析变化来自材料机制、统计波动还是判据敏感性。 + +### 4.7 数据质量与可追溯性 + +后处理输入必须满足应变严格递增、所有提交数值有限且至少包含一种缺陷观测。每次运行保存输入数据分类、来源定位、来源校验和、分析 Profile、参数、环境、随机种子、状态、输出校验和及上游谱系。相同幂等键对应同一输入时不得重复执行;输入变化却沿用原键时应阻断。失败运行保留状态和原因,但不能生成结果证据。 + +数据分为 synthetic fixture 和真实轨迹衍生观测。前者只用于验证软件计算、错误处理和门禁行为;后者必须具有来源 URI 或受控路径、匹配的校验和、完整环境和运行状态,才能进入真实结果审查。论文中的每个数字、图和机制陈述都应绑定相应运行及证据标识。 + +## 5 实验设计与复现方案 + +### 5.1 基准组 + +基准组用于确定单一、可复现的研究对象。正式运行前需要锁定材料、晶体取向、模型尺寸、势函数、边界条件、温度、应变率、初始缺陷、平衡时长和加载终止条件。基准组至少包含多个独立随机种子,以区分稳定事件顺序与单次热涨落。所有重复运行采用相同分析代码和判据,失败运行纳入清单,不以补跑结果覆盖。 + +### 5.2 对照与敏感性矩阵 + +实验矩阵围绕候选假设设置,而不是进行无界参数搜索。缺陷对照比较无预置空洞与预置空洞体系,用于检验空洞相关事件对初始缺陷的敏感性;晶向对照检验滑移活动和事件顺序是否依赖加载方向;温度和应变率对照描述热激活与加载时间尺度变化下的适用边界;尺寸对照检查自由表面、周期边界和有限体系是否主导观测;势函数对照用于识别模型选择对缺陷演化次序的影响。 + +每类对照只改变预先登记的变量,其余条件尽量保持一致。若无法做到单变量比较,应在结果中明确列出同时变化的条件,并停止作单因素归因。对照矩阵的终止条件包括:预定重复数完成、候选假设已被反例否定、事件无法在当前输出分辨率下可靠识别,或计算成本达到人工批准的上限。 + +### 5.3 输出与统计表达 + +每个条件下报告应力—应变曲线、事件应变、位错密度、空洞体积分数、晶格无序指标和关键构型快照。多随机种子结果以单次轨迹和汇总统计并列展示,避免只保留最符合预期的曲线。若样本数不足以支持推断统计,应报告逐次结果、范围和不一致案例,不使用未经设计支持的显著性表述。 + +图表至少包括原子模型及加载方向、应力—应变与力学事件、三类微观指标的同步曲线、关键应变下的空间构型,以及不同对照条件下的事件顺序。参数表记录模型、势函数、软件、时间步长、平衡和加载设置;运行表记录 RunManifest、随机种子、输入输出校验和、完成状态和独立复验状态。 + +### 5.4 独立复验 + +独立复验应在与首轮分析相分离的运行环境中读取同一输入或经批准的等价输入,重新生成轨迹衍生观测和结果校验和。复验不仅比较最终数字,还要核对事件顺序、关键构型和失败记录。若软件版本、硬件并行设置或势函数文件不同,差异必须进入复验报告,不能把不一致结果合并为同一重复组。 + +## 6 结果组织与当前证据状态 + +### 6.1 已验证的分析能力 + +当前版本已经验证结构化观测的输入校验、早期应变窗口线性斜率估计、峰值应力定位、峰后5%持续承载下降定位、半峰值承载事件定位,以及位错密度、空洞体积分数和晶格无序指标的方向性检查。运行记录能够保存输入校验和、输出校验和、环境、随机种子、状态和数据谱系;无效序列、幂等键冲突和失败执行不能生成结果证据。 + +上述能力通过明确标记为 synthetic fixture 的序列进行了软件验证。该序列包含预设的应力峰后下降和缺陷指标增加,因此只能说明计算逻辑能识别预置模式。它没有来自分子动力学积分的原子轨迹、势函数、模型构型和真实运行环境,不能用于说明某种金属的峰值应力、损伤起始应变、位错演化或空洞机制。为避免证据混用,当前稿件不把这些 synthetic 数值写入材料结果表,也不据此给出机制结论。 + +### 6.2 真实结果的报告顺序 + +真实数据导入后,结果首先报告基准组的应力—应变响应及运行间离散性,并检查早期斜率、峰值位置和峰后事件是否稳定。随后把位错、空洞和晶格无序曲线映射到同一应变轴,通过关键构型确认每个事件的空间含义。第三步比较各微观事件相对 \(\varepsilon_{\mathrm{peak}}\)、\(\varepsilon_{\mathrm{d}}\) 和 \(\varepsilon_{50}\) 的间隔。最后利用缺陷、晶向、温度、应变率、尺寸和势函数对照检验顺序是否改变。 + +结果叙述遵循“观测—重复性—对照—解释边界”的顺序。例如,只有当多次运行均显示位错事件早于持续承载下降,才报告稳定的领先关系;只有当缺陷对照使空洞事件和应力下降同步发生可重复变化时,才讨论缺陷驱动解释;若晶格无序随温度变化而显著移动,却与空洞位置不共同定位,则应把它作为温度或结构状态敏感指标处理。 + +### 6.3 当前不能回答的问题 + +由于尚未登记真实分子动力学输入、势函数、轨迹衍生观测和独立复验,本阶段不能判断H1、H2或H3是否成立,也不能给出目标材料的强度、损伤起始应变、空洞增长速率或位错密度范围。当前文献队列只有两条公开题录与摘要线索,尚不能确认本文方法相对最近似工作的创新程度。结果 Gate 因此保持阻断,直至真实数据、全文证据和人工审核满足项目标准。 + +## 7 讨论 + +### 7.1 时序关系的解释范围 + +统一事件轴能够把整体力学响应和微观结构变化放在同一加载过程内比较,但“先发生”不等于“导致”。位错活动可能释放局部应力,也可能只是外加载荷驱动下与其他缺陷共同发展的响应;空洞体积分数增加可能参与承载下降,也可能在局部失稳后迅速放大;晶格无序既可能标识塑性区,也可能来自热振动、表面或算法阈值。机制判断必须依赖空间共同定位、重复运行和针对性对照,而不能只依赖曲线之间的视觉一致性。 + +本文采用事件间隔而不是单一相关系数作为主要组织方式,是因为研究问题关心损伤过程的阶段性和先后顺序。不过,事件判据仍会受到采样频率、平滑方式和阈值影响。真实分析应同时报告原始序列、事件定义和敏感性结果,使读者能够判断观察到的领先或滞后是否超过时间分辨率。 + +### 7.2 不同模拟条件的作用 + +晶体取向会改变可用滑移路径和加载方向上的响应,尺寸与边界条件会改变缺陷传播和自由表面影响,温度与应变率共同决定体系可访问的演化时间尺度,势函数则决定原子相互作用的模型表达。这些因素不是附加的稳健性装饰,而是结论适用范围的一部分。如果某一事件顺序只在单一势函数、尺寸或取向下出现,论文应把结论限定到该组合,而不是概括为一般金属韧性损伤规律。 + +初始缺陷同样需要单独讨论。预置空洞可以稳定研究空洞长大,但它改变了应力集中和形核问题;无预置空洞体系更接近研究形核过程,却可能对尺寸、表面和模拟时长更敏感。两类模型分别回答“已有空洞如何演化”和“空洞能否在给定条件下形成”,不应以同一结论替代。 + +### 7.3 可复验性与证据治理 + +分子动力学结果对输入构型、势函数文件、软件版本、随机种子和后处理判据敏感。仅保存最终曲线不足以复验事件顺序。RunManifest 和校验和把论文结果连接到具体输入与输出,失败记录则用于防止只展示成功案例。数据分类进一步阻止 synthetic fixture 与真实轨迹在写作阶段混用。该治理方式不会替代物理验证,但能够明确每个数字和图表的来源,使科研判断建立在可检查的运行对象上。 + +## 8 局限性 + +当前稿件有四项明确限制。第一,目标材料、晶体取向、势函数和真实参数矩阵尚未由科研负责人锁定,方法部分仍是可执行协议而非已经完成的模拟设置。第二,当前后处理器只实现基础事件定位和缺陷指标方向性检查,尚缺少经真实轨迹验证的变点检测、空间共同定位和不确定性估计。第三,文献证据目前限于两条公开题录与摘要线索,不能支持完整相关工作综述或创新性结论。第四,尚无真实分子动力学数据和独立复验,因此本文不能报告材料机理、定量强度或普适事件顺序。 + +这些限制决定了当前版本的用途:它是一份正文完整的研究论证稿和实验协议,可用于科研负责人审阅、锁定参数并接入真实数据;它不是可投稿的结果论文。真实数据到位后,应优先完成文献证据图、参数与运行表、事件判据验证和独立复验,再更新摘要、结果、讨论和结论中的材料性表述。 + +## 9 结论 + +本文围绕金属拉伸过程中的位错活动、空洞演化、晶格无序和承载下降,提出统一应变轴上的时序分析框架。该框架以峰值应力、持续承载下降和半峰值承载作为力学参照,以可重复的缺陷指标变化作为微观事件,并通过多随机种子、缺陷、晶向、温度、应变率、尺寸和势函数对照限制机制解释。运行清单、输入输出校验和、数据分类和结果谱系用于保证每项结果可追溯,并阻止 synthetic 软件数据进入真实材料结论。 + +当前证据证明分析链和门禁能够按预定合同运行,但尚不能回答位错、空洞或晶格无序在真实材料损伤中的具体先后顺序。论文下一阶段的决定性工作是锁定材料与模拟协议,导入带校验和的真实分子动力学观测,完成独立复验,并据此检验H1—H3。任何最终机制表述都应限定于通过复验的材料、势函数、尺度、边界和加载条件。 + +## 10 数据、代码与证据声明 + +当前项目保留了 synthetic fixture、确定性后处理结果、RunManifest 和校验和,用于软件验证。synthetic fixture 不属于真实分子动力学数据,不作为本文材料结论的数据来源。真实数据、势函数许可、原子构型、轨迹文件和独立复验产物尚未纳入当前版本。外部投稿与公开发布处于关闭状态。 + +## 参考文献 + +- [1] [paper_f46ecbdcf61b4249] Void growth in bcc metals simulated with molecular dynamics using the Finnis–Sinclair potential. 2009. 10.1080/14786430903222529 +- [2] [paper_1ee7d9d9c77b4cc0] Three-dimensional molecular dynamics simulations of void coalescence during dynamic fracture of ductile metals. 2005. 10.1103/physrevb.71.064112 + +## 附录A:真实结果补全清单 + +1. 锁定材料、晶体取向、势函数、模型尺寸、边界条件、温度、应变率、初始缺陷、时间步长、平衡和加载协议。 +2. 为每个条件登记多个随机种子,保存输入、轨迹衍生观测、环境、RunManifest 和校验和。 +3. 验证位错、空洞和晶格无序识别算法,并冻结事件判据与后处理版本。 +4. 生成基准组曲线、关键构型、事件间隔和运行间离散性。 +5. 完成缺陷、晶向、温度、应变率、尺寸和势函数对照,记录反例与失败运行。 +6. 在独立环境中复验关键事件顺序和输出校验和。 +7. 将每个数字、图表和机制表述绑定到真实 evidence ID,再更新摘要、结果和结论。 + +## 附录B:当前证据缺口 + +- 许可明确的全文与人工 ReadingCard 尚未建立。 +- 最近似工作、区别特征和创新候选尚未通过反检索审核。 +- 真实 MD 输入、轨迹衍生观测、势函数文件校验和和独立复验尚未提供。 +- 结果图表、材料机理结论和可投稿版本仍由 Results Gate 阻断。 diff --git a/paper-projects/md-metal-ductile-damage-pilot/05_writing/OUTLINE.md b/paper-projects/md-metal-ductile-damage-pilot/05_writing/OUTLINE.md index 4a9c343..d11acc5 100644 --- a/paper-projects/md-metal-ductile-damage-pilot/05_writing/OUTLINE.md +++ b/paper-projects/md-metal-ductile-damage-pilot/05_writing/OUTLINE.md @@ -1,25 +1,45 @@ # 论文提纲 -> 写作 Agent 只能引用已审核 claim 和真实运行结果。没有证据的位置使用显式占位符,不得补造。 +## 拟定题目 -## 标题候选 +基于分子动力学的金属韧性损伤演化分析:位错活动、空洞演化与承载失稳的时序关联 -- 待填写。 +## 论文定位 + +本文以单轴拉伸分子动力学模拟为研究手段,围绕“位错活动—空洞形核与长大—晶格无序—宏观承载下降”的时间顺序建立可复验分析框架。现阶段稿件已形成完整论证结构和方法正文;真实材料体系、势函数、轨迹数据与独立复验尚未锁定,因此结果部分只陈述已验证的软件分析能力和证据边界,不写入 synthetic fixture 数值作为材料结论。 ## 摘要论证链 -- 问题:待填写 -- 缺口:待填写 -- 方法:待填写 -- 结果:待真实运行后填写 -- 贡献:待审核 +- 问题:仅观察应力—应变曲线难以区分屈服、缺陷积累、空洞演化和失稳之间的先后关系。 +- 缺口:已有研究分别关注空洞长大或聚合,但当前项目尚缺少把多种原子尺度指标放在统一事件轴上、并对证据来源进行约束的可复验分析。 +- 方法:登记材料与模拟边界,提取应力、位错密度、空洞体积分数和晶格无序指标,定义峰值、持续承载下降和半峰值承载事件,比较多 seed 与对照组的事件顺序。 +- 当前结果:确定性后处理、输入校验、RunManifest、checksum、lineage 和 synthetic 结果阻断已经验证;真实材料机理结果尚待 MD 数据。 +- 贡献边界:当前贡献是研究设计与可审计分析框架,不是已经证实的普适损伤机理。 ## 正文结构 -1. Introduction -2. Related Work -3. Method -4. Experimental Setup -5. Results -6. Discussion and Limitations -7. Conclusion +1. 引言 +2. 相关工作与问题界定 +3. 研究问题、候选假设与判定原则 +4. 分子动力学模型与分析方法 +5. 实验设计与复现方案 +6. 结果组织与当前证据状态 +7. 讨论 +8. 局限性 +9. 结论 +10. 数据、代码与证据声明 +11. 参考文献 + +## 真实结果落位表 + +| 论文位置 | 所需真实输入 | 计划产物 | 可支持的表述 | +|---|---|---|---| +| 图1 | 原子模型、边界和缺陷构型 | 模型示意与加载方向 | 已登记的模拟对象和边界 | +| 图2 | 多 seed 应力—应变序列 | 均值曲线、离散带与事件点 | 屈服、峰值和承载下降的位置 | +| 图3 | 位错识别结果 | 位错密度曲线与构型快照 | 位错活动与应力事件的时序关联 | +| 图4 | 空洞识别结果 | 空洞体积分数与空间演化 | 空洞形核、长大和聚合的演化区间 | +| 图5 | 晶格无序指标 | 无序原子比例/场与空间快照 | 无序区与局部塑性或空洞的共同定位 | +| 图6 | 温度、应变率、取向、尺寸和缺陷对照 | 事件顺序比较 | 候选机理的适用边界与反例 | +| 表1 | 势函数、软件、时间步长、平衡与加载参数 | 可复现参数表 | 方法可复核性 | +| 表2 | RunManifest、checksum、seed 和状态 | 运行清单 | 结果来源与独立复验状态 | +| 表3 | 各运行的事件应变及顺序 | 时序统计表 | 相关性结论及其重复性 | diff --git a/paper-projects/md-metal-ductile-damage-pilot/06_review/REVIEW_LOG.md b/paper-projects/md-metal-ductile-damage-pilot/06_review/REVIEW_LOG.md index 3b921cb..895affb 100644 --- a/paper-projects/md-metal-ductile-damage-pilot/06_review/REVIEW_LOG.md +++ b/paper-projects/md-metal-ductile-damage-pilot/06_review/REVIEW_LOG.md @@ -3,3 +3,4 @@ | 审核 ID | 日期 | 对象/版本 | 问题级别 | 结论 | 回退目标 | 复验条件 | Gate ID | |---|---|---|---|---|---|---|---| | REV-001 | 未记录 | 未定义 | 未分级 | 未审核 | 无 | 未定义 | 无 | +| REV-002 | 2026-08-23 | `05_writing/MANUSCRIPT_DRAFT.md` 完整论述稿 | 中 | 论文框架与非结果正文可审阅;真实结果继续阻断 | 真实 MD 执行与文献证据 | 真实 Results、引用与 claim audit 完成 | PG-G6-001 | diff --git a/src/autoresearch/writing_service.py b/src/autoresearch/writing_service.py index b8d8d3b..25e0d8e 100644 --- a/src/autoresearch/writing_service.py +++ b/src/autoresearch/writing_service.py @@ -42,6 +42,118 @@ def get(self, manuscript_id: str) -> Manuscript | None: raw = self.store.get("manuscript", manuscript_id) return Manuscript.model_validate(raw) if raw else None + @staticmethod + def _is_metal_damage_idea(idea: str) -> bool: + normalized = idea.casefold() + has_md = "分子动力学" in normalized or "molecular dynamics" in normalized + has_damage = any( + term in normalized + for term in ( + "金属", + "韧性损伤", + "位错", + "空洞", + "metal", + "ductile", + "dislocation", + "void", + ) + ) + return has_md and has_damage + + @staticmethod + def _metal_damage_sections( + *, + citations: str, + innovation_text: str, + references: str, + results: str, + gaps: list[str], + has_experiment_evidence: bool, + ) -> dict[str, str]: + result_status = ( + results + if has_experiment_evidence + else ( + "当前没有可追溯的真实分子动力学实验结果。本节不填入推测数值。" + "真实数据导入后,将依次报告多随机种子应力—应变响应、位错事件、" + "空洞形核与长大事件、晶格无序事件,以及各事件相对峰值应力和持续" + "承载下降起点的应变间隔。" + ) + ) + gap_text = "\n".join(f"- {gap}" for gap in gaps) or "当前没有登记的证据缺口。" + return { + "Abstract": ( + "金属韧性损伤伴随整体承载响应与多种原子尺度缺陷的共同演化。" + "本文建立面向拉伸分子动力学轨迹的时序分析框架,在材料、晶体取向、" + "势函数、温度、应变率、尺寸、边界条件、初始缺陷和随机种子均被登记" + "的前提下,同步比较应力—应变响应、位错密度、空洞体积分数和晶格无序" + "指标。研究以峰值应力、持续承载下降和半峰值承载为力学事件,以可重复" + "的缺陷指标变化为微观事件,并通过多随机种子、缺陷、尺寸、晶向和势函数" + "对照限制机制解释。当前稿件只把带有效来源和运行谱系的真实结果写入结果" + "章节;synthetic fixture 仅用于软件验证。" + ), + "Introduction": ( + "金属在拉伸载荷下从弹性响应进入塑性变形和失稳时,位错活动、局部" + "晶格无序、空洞形核与长大可能发生在不同阶段。应力—应变曲线能够标识" + "整体屈服、峰值与峰后下降,却不能单独说明微观缺陷何时出现,也不能把" + "相伴变化直接解释为主导机制。\n\n" + "本研究关注位错活动、空洞演化、晶格无序与承载下降在统一应变轴上的" + "先后次序。事件次序需要跨独立运行重复,并通过缺陷、晶向、尺寸、温度、" + "应变率或势函数对照检验。所有结论均限定于登记的材料、势函数、尺度、" + "边界和加载条件,不从原子尺度模拟直接外推到宏观构件。" + ), + "Related Work": ( + f"{citations}\n\n" + "当前相关工作只采用已登记阅读卡及其 locator。题录或摘要能够支持候选" + "筛选,但不能替代许可全文,也不足以确认统一时序分析是否构成未被覆盖的" + "研究创新。最近似工作和区别特征需要在全文阅读与反检索后确定。" + ), + "Research Questions and Hypotheses": ( + "研究问题是:在给定模拟条件下,位错、空洞和晶格无序事件分别先于还是" + "后于峰值应力与持续承载下降;改变关键条件后,该顺序是否保持一致。候选" + "假设包括位错活动先于明显承载下降、空洞体积分数持续增加与峰后下降相关," + "以及晶格无序可作为局部损伤的辅助指标。若事件顺序不能重复、缺陷对照无" + "稳定差异,或无序指标主要由温控和边界伪影驱动,相应假设应被削弱或否定。" + ), + "Proposed Contribution": innovation_text, + "Method": ( + "每次运行登记材料与晶向、势函数版本、模型尺寸、边界、初始缺陷、温度、" + "应变率、时间步长、平衡和加载流程、输出步长、随机种子及软件环境。轨迹" + "后处理把应力、位错密度、空洞体积分数和晶格无序映射到同一应变序列。\n\n" + "峰值应力事件取序列最大应力点;持续承载下降起点取峰值之后首次不高于峰值" + "95%的点;半峰值承载事件取峰值之后首次不高于峰值50%的点。输入必须满足" + "应变严格递增、数值有限并至少含一种缺陷观测。每次分析保存 RunManifest、" + "输入输出 checksum、状态和 lineage;失败运行不生成结果证据。" + ), + "Experimental Design": ( + "基准组采用预先锁定的单一材料与加载协议,并包含多个独立随机种子。对照" + "矩阵分别改变初始缺陷、晶体取向、温度、应变率、模型尺寸和势函数;无法" + "保持其余条件一致时,不作单因素归因。结果同时报告单次序列、运行间离散性、" + "事件顺序和关键构型。独立复验需要核对输入、环境、关键事件与输出校验和," + "不能用补跑覆盖失败记录。" + ), + "Results": result_status, + "Discussion": ( + "事件领先只支持时序关系,不自动证明因果。机制解释还需要空间共同定位、" + "独立重复和针对性对照,并检查采样间隔、阈值、温控、自由表面和势函数" + "敏感性。当前证据缺口如下:\n" + f"{gap_text}" + ), + "Limitations": ( + "在真实材料、势函数、轨迹、全文证据和独立复验尚未登记时,稿件不能报告" + "材料强度、损伤起始应变或普适机理。当前基础后处理还需要用真实轨迹验证" + "微观事件判据、空间共同定位和不确定性表达。" + ), + "Conclusion": ( + "本文形成了金属韧性损伤的可复验时序分析框架,并规定了力学事件、缺陷" + "观测、对照矩阵与证据谱系。最终机理结论必须由带来源校验和的真实分子" + "动力学结果、独立复验和文献审核共同支持;未满足这些条件时,稿件保持" + "研究论证稿状态。" + ), + "References": references or "尚无参考文献。", + } + def draft( self, project_id: str, @@ -93,12 +205,21 @@ def draft( results = ( "\n".join(f"- {item.claim} [{item.evidence_id}]" for item in experiment_evidence) if experiment_evidence - else "【待真实实验】此处禁止填入推测数值。" + else "当前没有可追溯的真实实验结果;本节不填入推测数值。" ) - manuscript = Manuscript( - project_id=project_id, - title=f"Research draft: {idea[:100]}", - sections={ + if self._is_metal_damage_idea(idea): + title = "基于分子动力学的金属韧性损伤演化分析:位错活动、空洞演化与承载失稳的时序关联" + sections = self._metal_damage_sections( + citations=self._citations(cards), + innovation_text=innovation_text, + references=references, + results=results, + gaps=gaps, + has_experiment_evidence=bool(experiment_evidence), + ) + else: + title = f"Research draft: {idea[:100]}" + sections = { "Abstract": ( "本稿是证据约束的研究草稿。研究目标、方法和结果须在对应 Gate " "通过后才能升级为可发布表述。" @@ -116,7 +237,11 @@ def draft( "仅总结已由证据支持的内容;创新性与外部发布仍由审核和人工门禁控制。" ), "References": references or "尚无参考文献。", - }, + } + manuscript = Manuscript( + project_id=project_id, + title=title, + sections=sections, evidence_ids=[item.evidence_id for item in evidence if item.valid], unresolved_gaps=gaps, release_ready=False, diff --git a/tests/test_execution_service.py b/tests/test_execution_service.py index 0353754..9e48fda 100644 --- a/tests/test_execution_service.py +++ b/tests/test_execution_service.py @@ -248,6 +248,37 @@ def test_workflow_consumes_checksum_bound_md_summary( assert "6.2 GPa" in manuscript["sections"]["Results"] +def test_metal_damage_draft_has_complete_framework_without_fake_results( + runtime: AutoResearchApplication, + project, + tmp_path: Path, +): + result = runtime.run( + RunRequest( + project_id="demo", + idea="分子动力学中的金属韧性损伤机理分析", + seed_papers=_papers("demo", tmp_path), + ) + ) + state = result["state"] + manuscript = runtime.store.get("manuscript", state["manuscript_id"]) + + assert manuscript["release_ready"] is False + assert manuscript["title"].startswith("基于分子动力学的金属韧性损伤演化分析") + assert { + "Research Questions and Hypotheses", + "Method", + "Experimental Design", + "Results", + "Discussion", + "Limitations", + "Conclusion", + }.issubset(manuscript["sections"]) + assert "峰值之后首次不高于峰值95%" in manuscript["sections"]["Method"] + assert "当前没有可追溯的真实分子动力学实验结果" in manuscript["sections"]["Results"] + assert "synthetic fixture" not in manuscript["sections"]["Results"] + + def test_execution_http_contract(runtime: AutoResearchApplication, project): async def scenario(): transport = httpx.ASGITransport(app=create_api(runtime)) diff --git a/tests/test_workflow.py b/tests/test_workflow.py index 9186373..869bd1b 100644 --- a/tests/test_workflow.py +++ b/tests/test_workflow.py @@ -96,7 +96,7 @@ def test_abstracts_create_draft_but_final_gate_blocks_result_claims( draft = ( Path(runtime.settings.projects_dir) / "demo" / "05_writing" / "MANUSCRIPT_DRAFT.md" ).read_text(encoding="utf-8") - assert "【待真实实验】" in draft + assert "当前没有可追溯的真实实验结果" in draft answer = runtime.answer_paper( state["paper_ids"][0], From ca00cbb8eea386a423efd5ac397f8969852637f3 Mon Sep 17 00:00:00 2001 From: zzg Date: Sun, 23 Aug 2026 02:04:01 +0800 Subject: [PATCH 3/4] docs: record manuscript framework evidence --- .project-to-act/PROJECT_ACCEPTANCE.md | 2 +- .../.project-to-act/PROJECT_ACCEPTANCE.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.project-to-act/PROJECT_ACCEPTANCE.md b/.project-to-act/PROJECT_ACCEPTANCE.md index 5363f2c..e56846a 100644 --- a/.project-to-act/PROJECT_ACCEPTANCE.md +++ b/.project-to-act/PROJECT_ACCEPTANCE.md @@ -85,7 +85,7 @@ | E-COLLAB-001 | 2026-08-22 | VibeCollab check;根/模板 Project-to-Act validate | 0 | AR-001 9/9;两账本 zero issues | 当前 Git 任务、代码、验收和长期账本一致;private sessions disabled | `.ai-team/TASK.md`、`evidence/FOUNDATION_EVIDENCE.md` | TASK 或账本变化前 | | E-M08-001 | 2026-08-22 | Ruff;25 tests warnings-as-errors;84% coverage;doctor;CLI register/execute/status;双账本/YAML 校验 | 0 | `0.2.0.dev0`;收口提交后以 Git commit 固化 | 金属韧性损伤 Profile、RunManifest、幂等、hash、失败不产证据、E1/E2 和 workflow Gate 通过 | `evidence/AR005_M08_EVIDENCE.md`、`tests/test_execution_service.py` | 代码/fixture 变化前 | | E-M08-LIVE-001 | 2026-08-22 | 受管 GLM/DeepSeek;127.0.0.1:8137;AAWO HTTP journeys | GLM/provider timeout 和 AAWO 15s timeout 如实保留;6 pass | run `run_c3357978832d4b77`、`run_b33c1d0b69564e6f`;报告 hash 见证据文件 | DeepSeek scope 成功;synthetic 执行 completed 但稿件 blocked | `evidence/journeys/ar005-*`、`evidence/AR005_M08_EVIDENCE.md` | 7 天或模型/HTTP 合同变化前 | -| E-WRITE-001 | 2026-08-23 | Ruff;26 tests warnings-as-errors;doctor;根/论文 Project-to-Act validate;repo-task-sync;全稿章节/占位/diff 检查 | 0 | 收口提交后以 Git commit 固化 | Writer 生成领域化完整框架;手工稿约 9800 字符、35 个标题、0 个待填写/TODO;synthetic 结果仍阻断 | `src/autoresearch/writing_service.py`、`tests/test_execution_service.py`、论文 `05_writing/` | Writer、稿件或研究边界变化前 | +| E-WRITE-001 | 2026-08-23 | Ruff;26 tests warnings-as-errors;doctor;根/论文 Project-to-Act validate;repo-task-sync;全稿章节/占位/diff 检查 | 0 | implementation commit `4bacb90` | Writer 生成领域化完整框架;手工稿约 9800 字符、35 个标题、0 个待填写/TODO;synthetic 结果仍阻断 | `src/autoresearch/writing_service.py`、`tests/test_execution_service.py`、论文 `05_writing/` | Writer、稿件或研究边界变化前 | ## Gate 记录 diff --git a/paper-projects/md-metal-ductile-damage-pilot/.project-to-act/PROJECT_ACCEPTANCE.md b/paper-projects/md-metal-ductile-damage-pilot/.project-to-act/PROJECT_ACCEPTANCE.md index 6c28c9e..879082f 100644 --- a/paper-projects/md-metal-ductile-damage-pilot/.project-to-act/PROJECT_ACCEPTANCE.md +++ b/paper-projects/md-metal-ductile-damage-pilot/.project-to-act/PROJECT_ACCEPTANCE.md @@ -31,7 +31,7 @@ | PE-M08-001 | 2026-08-22 | Ruff、25 tests、manifest/idempotency/hash/失败/完整 workflow 回归 | 0 | 收口提交前补 Git commit;artifact 自带 sha256 | M08 软件纵切通过,synthetic E1 | `tests/test_execution_service.py`、`04_execution/runs/` | 代码或 fixture 变化前 | | PE-LLM-001 | 2026-08-22 | 受管 GLM 调用 + 受管 DeepSeek 真实 run + AAWO 读取复核 | GLM timeout;DeepSeek pass | run `run_c3357978832d4b77` | DeepSeek 完成结构化范围;无论文时 blocked | `evidence/journeys/ar005-llm-scope*.json` | 7 天或模型配置变化前 | | PE-HTTP-001 | 2026-08-22 | 127.0.0.1:8137 客户旅程,真实题录+synthetic 执行+稿件缺口 | 0 | run `run_b33c1d0b69564e6f` | 执行 completed;稿件 waiting_evidence;6 条 pass、1 条 timeout inconclusive | `evidence/journeys/ar005-*.json`、ledger | HTTP 合同变化前 | -| PE-WRITE-001 | 2026-08-23 | Writer 领域回归、Ruff、26 tests、doctor、双账本/协作校验、全稿章节/正文长度/占位/synthetic 边界检查 | 0 | 收口提交前补 Git commit | 约 9800 字符、35 个标题、0 个待填写/TODO;后续工作流生成完整框架,真实结果仍明确阻断 | `src/autoresearch/writing_service.py`、`tests/test_execution_service.py`、`05_writing/` | Writer、稿件或研究边界变化前 | +| PE-WRITE-001 | 2026-08-23 | Writer 领域回归、Ruff、26 tests、doctor、双账本/协作校验、全稿章节/正文长度/占位/synthetic 边界检查 | 0 | implementation commit `4bacb90` | 约 9800 字符、35 个标题、0 个待填写/TODO;后续工作流生成完整框架,真实结果仍明确阻断 | `src/autoresearch/writing_service.py`、`tests/test_execution_service.py`、`05_writing/` | Writer、稿件或研究边界变化前 | ## Gate 记录 From 398e0d974a6c9545ec08e80054242c5b69ce030a Mon Sep 17 00:00:00 2001 From: zzg Date: Sun, 23 Aug 2026 12:09:28 +0800 Subject: [PATCH 4/4] feat: add evidence-aware research workbench --- .ai-team/TASK.md | 91 +- .project-to-act/PROJECT_ACCEPTANCE.md | 10 +- .project-to-act/PROJECT_FEATURES.md | 5 +- .project-to-act/PROJECT_OVERVIEW.md | 5 +- .project-to-act/PROJECT_PROGRESS.md | 5 +- .project-to-act/PROJECT_VERSIONS.md | 5 +- README.md | 9 + docs/DEVELOPMENT.md | 11 + docs/FUNCTION_MATRIX.md | 4 +- docs/WEB_UI.md | 44 + evidence/AR006_WEB_EVIDENCE.md | 56 + evidence/ui/ar006-workbench-wide.png | Bin 0 -> 108619 bytes evidence/ui/ar006-writer-mobile.png | Bin 0 -> 46265 bytes evidence/ui/ar006-writer-wide.png | Bin 0 -> 112619 bytes web/.env.example | 1 + web/.gitignore | 5 + web/README.md | 22 + web/app/globals.css | 2918 +++++++ web/app/layout.tsx | 15 + web/app/page.tsx | 5 + web/components/research-workbench.test.tsx | 73 + web/components/research-workbench.tsx | 817 ++ web/eslint.config.mjs | 7 + web/lib/api.ts | 91 + web/lib/demo-data.ts | 150 + web/lib/types.ts | 86 + web/next-env.d.ts | 7 + web/next.config.ts | 18 + web/package-lock.json | 8523 ++++++++++++++++++++ web/package.json | 32 + web/tsconfig.json | 41 + web/vitest.config.ts | 14 + web/vitest.setup.ts | 1 + 33 files changed, 13013 insertions(+), 58 deletions(-) create mode 100644 docs/WEB_UI.md create mode 100644 evidence/AR006_WEB_EVIDENCE.md create mode 100644 evidence/ui/ar006-workbench-wide.png create mode 100644 evidence/ui/ar006-writer-mobile.png create mode 100644 evidence/ui/ar006-writer-wide.png create mode 100644 web/.env.example create mode 100644 web/.gitignore create mode 100644 web/README.md create mode 100644 web/app/globals.css create mode 100644 web/app/layout.tsx create mode 100644 web/app/page.tsx create mode 100644 web/components/research-workbench.test.tsx create mode 100644 web/components/research-workbench.tsx create mode 100644 web/eslint.config.mjs create mode 100644 web/lib/api.ts create mode 100644 web/lib/demo-data.ts create mode 100644 web/lib/types.ts create mode 100644 web/next-env.d.ts create mode 100644 web/next.config.ts create mode 100644 web/package-lock.json create mode 100644 web/package.json create mode 100644 web/tsconfig.json create mode 100644 web/vitest.config.ts create mode 100644 web/vitest.setup.ts diff --git a/.ai-team/TASK.md b/.ai-team/TASK.md index c7dbb0b..61adc79 100644 --- a/.ai-team/TASK.md +++ b/.ai-team/TASK.md @@ -1,81 +1,80 @@ # Current Task -- ID: `AR-005` -- Title: `Implement the M08 metal ductile-damage pilot and complete manuscript framework` +- ID: `AR-006` +- Title: `Build the AutoResearch evidence-aware research workbench` - Status: `handoff` - Owner: `Codex` - Next owner: `user/team` ## Goal -在完整保留 AR-004 参考架构文档的基础上,优先补齐 M08 工作执行与结果闭环,并用“分子动力学中的金属韧性损伤机理分析”建立可运行的领域纵向切片。该纵切必须贯通受管 LLM 研究范围深化、论文证据、领域工作包、RunManifest、受控分析执行、artifact lineage、结果门禁、缺口稿件、审核打回和项目文件投影。 +交付一个可运行、响应式的 AutoResearch 前端工作台,把 idea 交流、论文搜集、论文阅读、论文写作与实时修改组织成一条可见的证据链。前端必须复用现有 FastAPI/Application Service 合同,明确区分真实 API 数据、浏览器本地草稿和界面预览数据,不得把 UI 状态冒充科研结论、Gate 通过或生产集成。 ## Acceptance scenarios -- [x] 当前分支完整包含 AR-004 的参考架构文档、README 入口和本任务代码/台账更新。 -- [x] 业务 Agent registry 仍恰好包含 `orchestrator`、`paper_search`、`paper_reader`、`writer`、`reviewer`;执行器是确定性服务节点,不新增业务 Agent。 -- [x] 金属韧性损伤领域 Profile 能生成可证伪问题、变量、停止条件和领域工作包,且未知项不会被写成事实。 -- [x] `RunManifest` 记录输入校验、数据分类、分析 Profile、参数、环境、seed、状态、输出 hash 和 lineage;相同幂等键不会重复执行。 -- [x] 内置金属损伤后处理器能对受控应力-应变/缺陷序列生成可复核指标和 JSON artifact;无效数据、伪造完成状态和 synthetic fixture 的稿件放行均 fail closed。 -- [x] API 与 CLI 使用同一应用服务,可创建/读取 manifest、执行/读取 run,并可从主 workflow 消费合格结果证据。 -- [x] 用真实本地 HTTP listener 跑通正常计算、无输入阻断和 synthetic 不可作为论文真实结果的客户旅程,报告进入 append-only evidence ledger。 -- [x] 使用 `llm-api-config` 的受管配置实际调用 LLM,对指定选题返回结构化研究范围;失败时明确记录 blocked/inconclusive,不用离线结果冒充外部成功。 -- [x] 在不伪造真实 MD 数值、图表或引用的前提下,形成摘要、引言、相关工作、假设、方法、实验设计、结果组织、讨论、局限性、结论和补全清单均有正文的完整论文论述稿。 -- [x] Ruff、pytest warnings-as-errors、doctor、Project-to-Act validate、repo-task-sync check 全部通过并记录新鲜证据。 +- [x] Next.js 使用已修复 CVE-2025-55182 的版本,独立安装、开发和生产构建命令可运行。 +- [x] 桌面端提供项目栏、五阶段证据脊柱、主工作区和证据检查器;移动端无横向溢出且关键操作可触达。 +- [x] Idea 工作台可记录交流、创建项目并通过现有 `/projects`、`/runs` 合同启动研究;失败和 blocked 结果有明确状态。 +- [x] 论文搜集界面支持检索过滤、题录队列和 seed paper 编辑;启动 run 时通过既有合同提交,不伪造外部检索成功。 +- [x] 论文阅读界面支持文献切换、摘要/定位信息、阅读笔记和证据限定问答;未连接 API 时返回可行动的未发送状态。 +- [x] 写作界面支持章节导航、实时编辑、字数统计、浏览器本地自动保存、版本快照和受控修订请求;本地保存不显示为服务端同步。 +- [x] 审核界面展示 Gate、证据覆盖、缺口、审计事件和定向修订入口,不允许 UI 绕过人工批准或确定性 Gate。 +- [x] 前端至少覆盖 loading、empty、error、success/blocked 状态,并通过 lint、typecheck、单元测试和生产构建。 +- [x] 使用真实本地浏览器验证宽屏与移动视口的布局、关键交互和无水平溢出,并保留截图证据。 +- [x] Python 回归、doctor、Project-to-Act validate 与 `node .ai-team/check.mjs --base main` 通过,协作账本与代码同一提交。 ## Invariants -- 保持恰好五个业务 Agent;证据、门禁、执行、状态、知识、画像和项目管理均为确定性服务或节点。 -- Gate 决定、状态转换、证据有效性、数据分类和人工批准不能由 LLM 覆盖。 -- `synthetic_fixture` 只证明软件链路和计算逻辑,不得写成真实 MD 模拟或真实金属损伤机理结论。 -- 只有带输入 checksum、运行状态、输出 checksum 和验证记录的执行结果才能成为实验证据;计划或失败输出永远不是结果。 -- 不运行任意用户 shell 命令,不自动修改/合并代码,不启动无限优化循环,不外发或发布稿件。 -- 不提交密钥、`.env.local`、原始提示、模型原始响应、完整论文或未经许可数据。 +- 保持恰好五个业务 Agent;前端阶段、证据检查器、自动保存和 API 适配层都不是新 Agent。 +- UI 只调用受支持的 HTTP/Application Service 边界,不自行实现 Gate、状态转换或人工批准。 +- `synthetic_fixture`、界面预览和浏览器 localStorage 只证明交互路径,不能展示为真实论文、真实 MD 结果或服务端已同步。 +- 证据不足、API 不可达、unknown、blocked、denied 与成功必须视觉和语义上分开。 +- 不提交密钥、`.env.local`、原始模型响应、许可不明全文或外部论文副本。 +- 原工作树中的未提交 LLM/阅读/写作改动属于用户;AR-006 在独立 worktree 中实现,不覆盖或携带这些变更。 ## Decisions -- AR-005 从当前 AR-004 未提交工作区切出,作为包含参考架构、实现、项目账本和测试证据的完整分支。 -- 当前实现聚焦 M08 与领域 Pilot 纵切,不把 PostgreSQL/pgvector/Neo4j、多租户 Web UI 或真实 LAMMPS 大规模模拟虚报为本分支完成。 -- 领域执行采用固定注册表中的确定性后处理器;输入为轨迹衍生的结构化观测,不开放任意命令执行。 -- 真实 LLM 用于研究范围和检索计划深化,科研结果与 Gate 仍由证据和确定性服务控制。 -- 用户要求的“正文填充”按完整研究论证稿实现:能够由作者直接审阅和继续补数据,但现有 synthetic 数值不进入材料结果,真实 Results Gate 继续阻断。 +- 从 AR-005 已提交快照 `ca00cbb` 创建独立分支 `codex/ar-006-research-workbench`,避免污染原工作树的未提交后端修改。 +- 使用 Next.js 16.3.2 + React 19 + TypeScript;该 Next.js 版本高于 CVE-2025-55182 的 16.0.7 修复线。 +- 视觉方向为“研究台账 × 编辑工作台”,以五阶段“证据脊柱”作为签名交互;采用石墨/纸张色和单一青色操作强调。 +- 参考 AI-Scientist、STORM、PaperQA、GPT Researcher 与 Open Deep Research 的阶段/证据/阅读模式,只吸收信息架构,不复制代码或视觉。 +- 初版通过同源 `/backend/*` 代理连接默认 `http://127.0.0.1:8010`;连接失败时保留明确的离线预览和浏览器草稿状态。 ## Completed -- 已恢复 0.1.0 Foundation、M00–M17 计划和 Project-to-Act 事实源。 -- 已核对 AR-004 参考架构、当前未提交文件和 `origin/main` 一致基线。 -- 已创建 `codex/ar-005-md-ductile-damage-pilot`,完整携带当前工作区。 -- 已实现 `metal_ductile_damage_v1`、领域工作包、RunManifest、deterministic 后处理、artifact lineage、E1/E2 分类、幂等和失败不产证据。 -- 已把 M08 服务节点接入 LangGraph,并把大输入保存在 execution store、state 只携带稳定 ID。 -- 已扩展 execution/manuscript API 与 register/execute/status CLI,版本升级为 `0.2.0.dev0`。 -- 已实例化 `paper-projects/md-metal-ductile-damage-pilot`,完整维护独立 Project-to-Act、fixture、run artifact、缺口稿件和运行索引。 -- 已用受管 GLM/DeepSeek 和真实 loopback listener 测试;GLM/provider timeout 与 AAWO 15 秒 timeout 保留为非通过证据,DeepSeek observed run 与 6 条 HTTP 旅程通过。 -- 已完成 25 tests、84% coverage、Ruff、doctor、CLI、YAML、双账本和协作校验。 -- 已将原缺口稿扩写为约 9800 字符的中文论文论述稿,并同步完成论文提纲、真实结果落位表、事件定义、对照矩阵、复验方案和证据边界。 -- 已修正 WritingService 的金属韧性损伤领域化生成路径并新增回归,后续工作流在无真实结果时仍生成完整论述章节,不再退化为一行占位稿。 +- 已读取 AGENTS、PROJECT、TASK、repo-task-sync 与 Project-to-Act 账本,确认 M13 Web UI 是待开发范围。 +- 已完成 GitHub 定向参考调研并确定前端信息架构与视觉方向。 +- 已隔离原工作树的用户修改并建立 AR-006 独立工作树。 +- 已交付 Next.js 16.3.2 五阶段工作台、证据脊柱、三栏研究台、移动端底部阶段栏和证据抽屉。 +- 已接通 `/health`、`/projects`、`/runs`、论文问答、稿件读取与 revision;E0 题录不提交,blocked/unknown/错误状态分开。 +- 已实现章节实时编辑、浏览器 localStorage 自动保存、版本快照、字数/引用/缺口统计和受控修订入口。 +- 已完成 3 个前端交互测试、Next 生产构建、26 个 Python 回归、doctor、Project-to-Act 校验和真实隔离 HTTP 客户旅程。 +- 已在 1440×1000 与 390×844 浏览器视口确认 0 px 横向溢出,并保存生产模式截图。 ## Pending -- AR-005 软件分支无剩余实现项。 -- 项目级后续仍需用户/科研负责人确认或提供许可全文、目标材料/晶向/势函数、真实 MD 数据和独立复验;完整正文框架已经形成,但这些外部输入仍不能伪造完成。 -- PostgreSQL/队列/容器 executor/Web UI/生产灾备仍按 M17 推进,不属于 AR-005。 +- AR-006 本地单用户前端无剩余实现项。 +- 平台级后续仍需受控论文/稿件/审核列表 API、流式 run 事件、认证、协作和生产托管;真实 corpus/MD Gate 保持未通过。 ## Next step -由用户/团队审阅完整论述稿并确认材料体系与模拟协议;随后导入许可全文和带 checksum 的真实 `md_trajectory_summary`,按稿件中的结果落位表补齐图表与材料结论。 +由用户/团队审阅研究工作台;下一前端任务应先补后端列表/流式事件与认证合同,再扩展跨项目和多人协作界面。 ## Verification -- [x] `.venv\Scripts\python.exe -m ruff check src tests` -- [x] `.venv\Scripts\python.exe -m pytest -W error -q` -- [x] `.venv\Scripts\autoresearch.exe doctor` -- [x] Project-to-Act 根与 Pilot `--validate` -- [x] 真实 HTTP 客户旅程报告状态核验与 evidence ledger 检查 -- [x] 受管 LLM 真实结构化调用(只记录脱敏元数据、状态和 artifact hash) +- [x] `npm.cmd run lint` +- [x] `npm.cmd run typecheck` +- [x] `npm.cmd run test` +- [x] `npm.cmd run build` +- [x] `.venv\\Scripts\\python.exe -m ruff check src tests` +- [x] `.venv\\Scripts\\python.exe -m pytest -W error -q` +- [x] `.venv\\Scripts\\autoresearch.exe doctor` +- [x] Root Project-to-Act `--validate` +- [x] Browser wide/mobile validation and screenshots - [x] `node .ai-team/check.mjs --base main` ## Handoff note - From: `Codex` - To: `user/team` -- Summary: AR-005 完整分支已收口;M08 软件纵切、真实 LLM/HTTP 证据、实例化 Pilot 和正文完整的研究论述稿均在分支内。synthetic fixture 与题录/摘要只验证软件路径,真实论文结果/MD/生产 Gate 仍保持未通过。 +- Summary: AR-006 五阶段本地研究工作台已收口;真实 API、blocked Gate、本地草稿、宽屏/移动端和全仓回归均有证据。认证/协作/生产部署与真实论文结论未虚报完成。 diff --git a/.project-to-act/PROJECT_ACCEPTANCE.md b/.project-to-act/PROJECT_ACCEPTANCE.md index e56846a..992df1a 100644 --- a/.project-to-act/PROJECT_ACCEPTANCE.md +++ b/.project-to-act/PROJECT_ACCEPTANCE.md @@ -5,10 +5,10 @@ ## 当前验收结论 -- 结论:0.1.0 Foundation 与 AR-005 M08 软件纵切验收通过;生产级 P1–P6 与真实论文/MD 项目仍未验收 -- 验收范围:严格五 Agent 本地架构、M08 RunManifest/执行 lineage、领域化完整论文框架、CLI/API、证据门禁、实例化项目和隔离客户旅程 +- 结论:0.1.0 Foundation、AR-005 M08 软件纵切与 AR-006 本地研究工作台验收通过;生产级 P1–P6 与真实论文/MD 项目仍未验收 +- 验收范围:严格五 Agent 本地架构、M08 RunManifest/执行 lineage、领域化完整论文框架、CLI/API/Web、证据门禁、实例化项目和隔离客户旅程 - 最后检查:2026-08-23(AR-005 稿件框架检查) -- 遗留问题:许可全文/gold corpus/真实 MD/独立复验、生产数据库、备份恢复、Web/认证和真实投稿均未验收 +- 遗留问题:许可全文/gold corpus/真实 MD/独立复验、生产数据库、备份恢复、Web 认证/协作/托管和真实投稿均未验收 ## 验收标准 @@ -44,6 +44,7 @@ | A-FND-013 | 自进化没有自动改源码/策略的执行路径 | 已通过 | proposal-only 状态测试与接口审查 | E-RUN-001 | | A-FND-014 | 文档覆盖架构、功能、接口、运维、安全、开发和测试 | 已通过 | 文件清单与链接检查 | E-RUN-001 | | A-FND-015 | 金属韧性损伤 Writer 在缺真实结果时仍生成完整论述框架,且不伪造数值 | 已通过 | 领域工作流回归、全稿结构和占位检查 | E-WRITE-001 | +| A-FND-016 | 本地 Web 工作台覆盖五阶段并准确区分 E0、本地草稿、blocked 与 API 成功 | 已通过 | 前端 lint/typecheck/test/build、真实隔离 HTTP journey、宽屏/移动端浏览器检查 | E-WEBUI-001 | ### 产品运行时验收标准 @@ -86,6 +87,7 @@ | E-M08-001 | 2026-08-22 | Ruff;25 tests warnings-as-errors;84% coverage;doctor;CLI register/execute/status;双账本/YAML 校验 | 0 | `0.2.0.dev0`;收口提交后以 Git commit 固化 | 金属韧性损伤 Profile、RunManifest、幂等、hash、失败不产证据、E1/E2 和 workflow Gate 通过 | `evidence/AR005_M08_EVIDENCE.md`、`tests/test_execution_service.py` | 代码/fixture 变化前 | | E-M08-LIVE-001 | 2026-08-22 | 受管 GLM/DeepSeek;127.0.0.1:8137;AAWO HTTP journeys | GLM/provider timeout 和 AAWO 15s timeout 如实保留;6 pass | run `run_c3357978832d4b77`、`run_b33c1d0b69564e6f`;报告 hash 见证据文件 | DeepSeek scope 成功;synthetic 执行 completed 但稿件 blocked | `evidence/journeys/ar005-*`、`evidence/AR005_M08_EVIDENCE.md` | 7 天或模型/HTTP 合同变化前 | | E-WRITE-001 | 2026-08-23 | Ruff;26 tests warnings-as-errors;doctor;根/论文 Project-to-Act validate;repo-task-sync;全稿章节/占位/diff 检查 | 0 | implementation commit `4bacb90` | Writer 生成领域化完整框架;手工稿约 9800 字符、35 个标题、0 个待填写/TODO;synthetic 结果仍阻断 | `src/autoresearch/writing_service.py`、`tests/test_execution_service.py`、论文 `05_writing/` | Writer、稿件或研究边界变化前 | +| E-WEBUI-001 | 2026-08-23 | Next lint/typecheck/3 tests/build;隔离 FastAPI `/health`+`/projects`+`/runs`;1440×1000/390×844 浏览器布局、交互与溢出检查 | 0 | AR-006 收口提交后以 Git commit 固化 | 五阶段本地 Web 可运行;真实 run blocked/waiting_evidence;宽/移动 0 px 横向溢出;localStorage 不冒充服务端同步 | `evidence/AR006_WEB_EVIDENCE.md`、`evidence/ui/`、`web/` | 前端、HTTP 合同或响应式 CSS 变化前 | ## Gate 记录 @@ -94,11 +96,13 @@ | G-P0-001 | 2026-08-21 | P0 规划交付 | 计划书、功能表、根账本、论文项目模板 | PASS | E-PLAN-001、E-TPL-001 | 无豁免 | | G-FND-001 | 2026-08-22 | Foundation Preview 本地交付 | 源码、文档、测试、CLI/API、本地客户旅程 | PASS | E-RUN-001、E-JOURNEY-001、E-LIVE-001、E-PROJECT-001 | 无豁免;真实论文与生产 Gate 保持未通过 | | G-M08-SW-001 | 2026-08-22 | M08 软件纵切 | 领域 Profile、RunManifest、deterministic execution、artifact lineage、synthetic Gate | PASS | E-M08-001、E-M08-LIVE-001 | 无豁免;真实 MD/论文/生产 Gate 保持未通过 | +| G-M13-UI-001 | 2026-08-23 | M13 本地 Web 纵切 | 五阶段工作台、API 边界、本地实时编辑、证据检查器、响应式布局 | PASS | E-WEBUI-001 | 无豁免;认证/协作/生产托管与真实论文 Gate 保持未通过 | ## 验收记录 按时间倒序追加:日期、检查范围、证据 ID、结果、遗留问题和结论。失败、跳过与过期证据也必须如实记录。 +- 2026-08-23|AR-006 本地研究工作台:Next.js 16.3.2 lint/typecheck/3 tests/build 通过;隔离 FastAPI 健康连接和 blocked run 通过;宽屏/移动端均无横向溢出,E0 与本地草稿边界可见|E-WEBUI-001|G-M13-UI-001 PASS|遗留:认证、列表/流式事件、协作、生产托管和真实 corpus|结论:A-FND-016 通过;Web 生产与真实论文验收仍未通过。 - 2026-08-23|AR-005 稿件框架:Writer 领域化框架回归、26 tests、Ruff、doctor、双账本和协作校验通过;完整论述稿无待填写/TODO,synthetic 未写成材料结果|E-WRITE-001|论文框架 PASS|遗留:无许可全文/真实 MD/独立复验/图表|结论:A-FND-015 通过;A-RUN-018 真实论文试点仍未通过。 - 2026-08-22|AR-005 M08 软件纵切:25 tests、84% coverage、doctor、CLI、双账本、真实 DeepSeek scope、AAWO 6 pass/1 timeout inconclusive、真实 OpenAlex 题录+synthetic 完整 run 和稿件缺口|E-M08-001、E-M08-LIVE-001|G-M08-SW-001 PASS|遗留:无许可全文/真实 MD/独立复验/生产 executor|结论:A-RUN-010 软件合同通过;A-RUN-018 真实论文试点仍未通过。 diff --git a/.project-to-act/PROJECT_FEATURES.md b/.project-to-act/PROJECT_FEATURES.md index dd7dbf0..c1d0723 100644 --- a/.project-to-act/PROJECT_FEATURES.md +++ b/.project-to-act/PROJECT_FEATURES.md @@ -27,7 +27,7 @@ | F-GOV-002 | 论文项目文件夹模板与独立账本 | P0 | 已完成 | P0 | 目录 allowlist 完整、模板 `--validate` 通过、占位边界清晰 | E-TPL-001 | | F-GOV-003 | 论文项目实例化服务 | P0 | 进行中 | F-GOV-002 / P1 | 从模板创建唯一 project_id,原子写入 manifest/账本,失败可回滚 | E-RUN-001、E-PROJECT-001 | | F-GOV-004 | 里程碑到 Project-to-Act 同步 | P1 | 进行中 | F-WF-004 / P4 | 只在范围/里程碑/验收变化时更新,不把逐节点日志写入账本 | E-PROJECT-001 | -| F-GOV-005 | Web/API/CLI 三种受控入口 | P1 | 进行中 | P1–P6 | 入口共享同一权限、schema、状态和 Gate,不出现旁路 | E-RUN-001、E-JOURNEY-001 | +| F-GOV-005 | Web/API/CLI 三种受控入口 | P1 | 进行中 | P1–P6 | 入口共享同一权限、schema、状态和 Gate,不出现旁路 | E-RUN-001、E-JOURNEY-001、E-WEBUI-001 | ### B. 五 Agent 业务能力 @@ -132,7 +132,7 @@ | F-QA-001 | 固定单元/合同/集成/对抗测试套件 | P0 | 进行中 | 全阶段 | 每个 schema/Gate/状态边有正例和负例,CI 可复现 | E-RUN-001、E-JOURNEY-001 | | F-QA-002 | 搜索、阅读、创新、写作 gold corpus | P0 | 已规划 | 真实 idea / P2–P4 | 人工标注集版本化、许可清晰、评测报告可复查 | 无 | | F-QA-003 | 端到端真实论文项目验收 | P0 | 已规划 | P6 | 从 idea 到本地稿件完成,证据/Gate/handoff/run 链完整 | 无 | -| F-QA-004 | 结构化 telemetry 与审计 UI | P1 | 进行中 | F-EG-012 / P6 | 可按 run/node/agent/tool/gate/evidence 查看,不泄露秘密/全文 | E-RUN-001 | +| F-QA-004 | 结构化 telemetry 与审计 UI | P1 | 进行中 | F-EG-012 / P6 | 可按 run/node/agent/tool/gate/evidence 查看,不泄露秘密/全文 | E-RUN-001、E-WEBUI-001 | | F-QA-005 | 备份、恢复、迁移与索引重建 | P0 | 已规划 | P6 | 数据库/对象/图/向量/账本恢复演练通过并校验 hash | 无 | | F-QA-006 | 秘密、隐私、ACL、版权和许可检查 | P0 | 进行中 | 全阶段 | secret scan、越权负例、OA/授权策略和删除流程通过 | E-RUN-001、E-LIVE-001 | | F-QA-007 | 本地交付与复现包导出 | P1 | 已规划 | P4/P6 | 稿件/引用/图表/代码数据引用/环境/证据索引清单完整 | 无 | @@ -144,6 +144,7 @@ 按时间倒序追加:日期、功能 ID、变化、原因、影响、证据 ID 和确认来源。 +- 2026-08-23|Web 研究工作台|F-GOV-005/F-QA-004 增加 Next.js 五阶段工作台、证据脊柱、E0/blocked/离线状态、浏览器本地稿件自动保存与 Gate 检查器;状态保持“进行中”,因为认证、完整列表 API、流式事件和生产部署未完成|证据:E-WEBUI-001|确认来源:用户本轮指令。 - 2026-08-23|领域化 Writer|F-AG-018/019 增加金属韧性损伤完整研究论证框架,证据不足时不再只输出一行占位稿;synthetic 与真实 Results 的 Gate 不变|证据:E-WRITE-001|确认来源:用户本轮指令。 - 2026-08-22|M08 金属韧性损伤纵切|F-EX-002/003/004 继续保持“进行中”,但已增加可运行的 RunManifest、固定 Profile、输入/来源/输出 hash、幂等、失败记录、E1/E2 分类和 synthetic 稿件阻断;未因软件 fixture 将真实实验或通用生产能力标完成|证据:E-M08-001、E-M08-LIVE-001|确认来源:用户本轮指令。 diff --git a/.project-to-act/PROJECT_OVERVIEW.md b/.project-to-act/PROJECT_OVERVIEW.md index aa6264a..9edfc89 100644 --- a/.project-to-act/PROJECT_OVERVIEW.md +++ b/.project-to-act/PROJECT_OVERVIEW.md @@ -10,7 +10,7 @@ - 项目负责人:用户/项目负责人(具体姓名不在账本保存) - 风险等级:T4 / 高(长周期多 Agent、外部论文源、实验/写作与可选外部动作) - 当前阶段:0.1.0 Foundation Preview 已验收,P1–P5 生产级条件进行中 -- 当前状态:严格五 Agent 本地运行时、M08 金属韧性损伤软件纵切和领域化完整论文框架已交付;真实 LLM/HTTP 边界有证据,真实论文结果/MD 试点与生产部署未完成 +- 当前状态:严格五 Agent 本地运行时、M08 金属韧性损伤软件纵切、领域化完整论文框架和 AR-006 本地研究工作台已交付;真实 LLM/HTTP/UI 边界有证据,真实论文结果/MD 试点、多用户认证与生产部署未完成 - 最后更新:2026-08-23 ## 项目目标 @@ -66,7 +66,7 @@ ## 当前焦点 - 下一里程碑:0.2.0 Literature Pilot,以“分子动力学中的金属韧性损伤机理分析”为首个领域纵切 -- 当前工作重点:审阅 AR-005 完整论述稿,锁定材料与模拟协议,导入真实 MD 与全文证据;不重做 foundation 骨架 +- 当前工作重点:用 AR-006 研究工作台审阅完整论述稿,锁定材料与模拟协议,导入真实 MD 与全文证据;前端生产认证、流式事件与跨项目列表仍待后端合同支持 - 主要阻塞:选题已明确,但尚无许可全文、真实 LAMMPS/MD 输出、目标 venue 和实验资源边界;不阻塞软件纵切,阻塞真实科研结论验收 ## 按需读取索引 @@ -83,6 +83,7 @@ 按时间倒序追加:决定 ID、日期、决定、原因、影响、证据 ID、确认来源和复审条件。 +- D-005|2026-08-23|采用“研究台账 × 编辑工作台”交付本地单用户 Web UI,并以五阶段证据脊柱投影现有工作流|原因:用户要求完整前端覆盖 idea、搜集、阅读、写作和实时修改|影响:新增 Next.js 工作台、同源 API 代理、E0 预览阻断、浏览器本地稿件自动保存与宽屏/移动端验收;认证/协作/流式事件仍不冒充完成|证据:E-WEBUI-001|确认来源:用户本轮指令|复审:后端列表/事件/认证合同变化时。 - D-004|2026-08-23|证据不足时 Writer 仍生成正文完整的研究论证框架,以显式证据边界替代空白 Results 占位|原因:用户指出占位稿不可审阅,要求至少搭建完整框架并填充正文|影响:金属韧性损伤选题获得领域化摘要、问题、假设、方法、实验设计、结果组织、讨论、局限性和结论;真实结果 Gate 仍 fail-closed|证据:E-WRITE-001|确认来源:用户本轮指令|复审:真实结果或目标稿件类型变化时。 - D-003|2026-08-22|采用 VibeCollab repo-task-sync 管理当前 Git 任务,Project-to-Act 继续作为平台长期唯一治理账本,论文子项目保留独立账本|原因:用户指定拉取 vibecollab skill,并要求项目文件夹管控进度|影响:.ai-team 管短周期 task/handoff,根和子项目账本管范围/里程碑/验收,禁止三份事实互相覆盖|证据:E-PROJECT-001、E-JOURNEY-001|确认来源:用户本轮指令|复审:协作模型或 canonical ledger 变化时。 - D-002|2026-08-21|目标运行时明确为 LangGraph;嵌套 DeepReason 框架只作为能力与测试参考|原因:用户明确要求 LangGraph,且嵌套仓库 AGENTS.md 的 OpenCode 原生方向不满足本次目标|影响:新实现不能沿用旧拓扑作为主运行时|证据:E-BASE-001|确认来源:用户本轮指令|复审:除非用户改变技术路线。 diff --git a/.project-to-act/PROJECT_PROGRESS.md b/.project-to-act/PROJECT_PROGRESS.md index 80c912c..fbb7442 100644 --- a/.project-to-act/PROJECT_PROGRESS.md +++ b/.project-to-act/PROJECT_PROGRESS.md @@ -16,6 +16,7 @@ | P3 创新挖掘与工作执行 | 进行中 | Codex | AR-005 已补 RunManifest、确定性金属损伤后处理、artifact lineage、幂等和 synthetic 结果门禁;真实 MD 待补 | E-RUN-001、E-M08-001、E-M08-LIVE-001 | 2026-08-22 | | P4 写作与审核 | 进行中 | Codex | 领域化完整论文框架、版本化修改/润色、L3 审核打回已通过;真实 Results 与 claim audit 待补 | E-RUN-001、E-JOURNEY-001、E-WRITE-001 | 2026-08-23 | | P5 Wiki+Graph、画像与自进化 | 进行中 | Codex | 分区 Wiki/一跳图、画像、经验晋级和 proposal-only 已落地;向量/灰度/回滚待补 | E-RUN-001 | 2026-08-22 | +| P5-UI 研究工作台 | 进行中 | Codex | 本地单用户五阶段 Web、真实 API blocked journey、宽屏/移动端和本地自动保存已通过;认证、流式事件、列表 API 与协作待补 | E-WEBUI-001 | 2026-08-23 | | P6 真实端到端试点与 v1.0 | 已规划 | 待指定 | `PROJECT_ACCEPTANCE.md` 全部运行时标准通过 | 无 | 未开始 | ## 阻塞项 @@ -31,12 +32,14 @@ 1. 完成 AR-005 金属韧性损伤领域 Profile、RunManifest、artifact lineage 和确定性后处理纵切。 2. 用受管 LLM 和真实本地 HTTP listener 验证该选题的范围深化、执行阻断与 synthetic 边界。 3. 取得许可全文和真实 MD 输出后建立 gold query、ReadingCard/locator 人工基准与结果复验。 -4. 在生产化前引入 PostgreSQL/pgvector 与可重建图投影,不先扩建无证据 UI。 +4. 基于 AR-006 已验证工作台补齐受控论文/稿件/审核列表 API、流式 run 事件和认证;界面继续只投影后端事实。 +5. 在生产化前引入 PostgreSQL/pgvector 与可重建图投影,不用界面完成度代替数据层证据。 ## 进度历史 按时间倒序追加:日期、完成事项、证据 ID、遗留问题、下一步和确认来源。不要覆盖旧记录。 +- 2026-08-23|完成 AR-006 本地研究工作台纵切|Next.js 16.3.2 五阶段界面、证据脊柱、同源 API、本地稿件自动保存、E0/unresolved/blocked 边界、3 个交互测试和生产构建通过;真实隔离 FastAPI run 返回 blocked/waiting_evidence;1440×1000 与 390×844 均 0 px 横向溢出|证据:E-WEBUI-001|遗留:认证、完整列表 API、流式事件、协作与生产托管|下一步:补平台查询/事件合同和真实 corpus|确认来源:用户本轮指令。 - 2026-08-23|补齐 AR-005 论文框架和 Writer 生成能力|手工论述稿约 9800 字符、35 个标题且无待填写占位;Writer 对金属韧性损伤选题生成领域化完整章节,缺真实结果时保留论述而非空壳,并继续阻断材料结论;26 tests、Ruff、doctor 和双账本校验通过|证据:E-WRITE-001|遗留:全文、真实 MD、图表和 claim audit|下一步:确认模拟协议并接入真实数据|确认来源:用户本轮指令。 - 2026-08-22|完成 AR-005 M08 金属韧性损伤软件纵切|新增领域 Profile、RunManifest、确定性后处理、artifact lineage、执行 API/CLI、25 tests、真实受管 DeepSeek scope、6 条 pass/1 条 timeout inconclusive HTTP 旅程和实例化论文 Project-to-Act|证据:E-M08-001、E-M08-LIVE-001|遗留:许可全文、真实 MD 数据、独立复验、生产 executor 未完成|下一步:真实 `md_trajectory_summary` 与人工科研验收|确认来源:用户本轮指令。 diff --git a/.project-to-act/PROJECT_VERSIONS.md b/.project-to-act/PROJECT_VERSIONS.md index 072d4c2..f10f40c 100644 --- a/.project-to-act/PROJECT_VERSIONS.md +++ b/.project-to-act/PROJECT_VERSIONS.md @@ -5,8 +5,8 @@ ## 当前版本 - 版本号:`0.2.0.dev0` -- 版本代号:M08 Metal Damage Software Pilot -- 发布状态:本地开发分支包含 M08 金属韧性损伤纵切;不是正式发布,也未完成真实论文验收 +- 版本代号:M08 Metal Damage Pilot + Research Workbench +- 发布状态:本地开发分支包含 M08 金属韧性损伤纵切和 AR-006 Next.js 研究工作台;不是正式发布,也未完成真实论文、认证或生产验收 - 兼容性说明:已验证 Python 3.12.10、LangGraph 1.2.11、langgraph-checkpoint-sqlite 3.1.1、Pydantic 2.13.4、FastAPI 0.141.1;SQLite profile 仅承诺本地单进程基础验证。 - 最后更新:2026-08-23 @@ -33,6 +33,7 @@ 按时间倒序追加:版本号、日期、状态、主要变更、原因、兼容性、证据 ID 和 Gate 结果。 +- `0.2.0.dev0`|2026-08-23|AR-006 研究工作台|新增 Next.js 五阶段 Web、证据脊柱、同源 API、浏览器本地实时编辑、E0/blocked 状态和宽屏/移动端验收|原因:用户要求完整前端覆盖 idea 到实时修改|兼容性:FastAPI/Python 合同不变;Node.js/Next.js 为独立前端依赖|证据:E-WEBUI-001|Gate:本地 UI 纵切 PASS,认证/协作/真实论文/生产 Gate 未通过。 - `0.2.0.dev0`|2026-08-23|AR-005 稿件框架更新|金属韧性损伤 Writer 由占位式草稿升级为领域化完整论述框架,同时保留真实 Results Gate|原因:用户要求正文可审阅且不能只留占位|兼容性:Manuscript 合同不变;领域稿新增章节键|证据:E-WRITE-001|Gate:框架 PASS,真实论文/MD Gate 未通过。 - `0.2.0.dev0`|2026-08-22|AR-005 开发分支|金属韧性损伤领域 Profile、RunManifest、确定性后处理、artifact lineage、execution API/CLI、真实 LLM/HTTP Pilot 和实例化 Project-to-Act|原因:用户要求参考新架构继续完整功能开发并以该领域真实测试|兼容性:保留 0.1.0 五 Agent/SQLite 合同;新增可选 execution_input 与执行接口|证据:E-M08-001、E-M08-LIVE-001|Gate:软件纵切待最终收口,真实论文/MD/生产 Gate 未通过。 diff --git a/README.md b/README.md index b3eceff..5f4f85b 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,14 @@ AutoResearch 是一个以证据为边界、以项目文件夹为账本的 LangGr .venv\Scripts\autoresearch.exe serve --host 127.0.0.1 --port 8010 +启动研究工作台: + + cd web + npm.cmd install + npm.cmd run dev + +打开 `http://127.0.0.1:3000`。前端通过同源代理调用现有 FastAPI;API 不可达时会进入明确的离线预览,浏览器自动保存不会显示成服务端同步。详见 `docs/WEB_UI.md`。 + 文档入口: - docs/AutoResearch_参考架构与功能设计.md:整合开源优秀设计的目标架构、完整功能、来源边界与 M00–M17 映射。 @@ -36,6 +44,7 @@ AutoResearch 是一个以证据为边界、以项目文件夹为账本的 LangGr - docs/EVIDENCE_AND_GATES.md:证据等级与 L0–L4 门禁。 - docs/KNOWLEDGE_AND_EVOLUTION.md:Wiki+Graph、画像、经验与自进化。 - docs/API.md、docs/OPERATIONS.md、docs/DEVELOPMENT.md:接口、运行和开发。 +- docs/WEB_UI.md:研究工作台、API 边界、五阶段交互和前端验证。 - docs/AutoResearch_详细计划书.md:完整产品路线。 - docs/AutoResearch_任务拆解书.md:按模块、依赖、负责人和验收目标分配工作。 - docs/AutoResearch_任务拆解书_详细版.md:需要继续细化时使用的历史详细拆解附录。 diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index b3f7e74..e9ea4b9 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -8,6 +8,7 @@ - src/autoresearch/*_service.py:确定性业务服务。 - configs:Agent、workflow、evidence policy 的可读配置。 - tests:合同、Gate、知识分区、工作流、API/CLI 测试。 +- web:Next.js 研究工作台、同源 API 代理、浏览器本地草稿和前端测试。 - paper-projects/_template:论文项目 canonical 模板。 - .project-to-act:产品级长期治理账本。 - .ai-team:VibeCollab 当前任务事实源。 @@ -21,6 +22,16 @@ node .ai-team/check.mjs --base main py -3.12 C:\Users\zzg\.codex\skills\project-to-act\scripts\init_project_management.py --project-root . --validate +前端变更还需运行: + + cd web + npm.cmd run lint + npm.cmd run typecheck + npm.cmd run test + npm.cmd run build + +并使用真实本地 API 分别检查宽屏与移动端布局、关键交互和横向溢出。 + 任何“完成”提交都必须同时更新测试、.ai-team/TASK.md 和必要的 Project-to-Act 里程碑。 ## 3. 设计约束 diff --git a/docs/FUNCTION_MATRIX.md b/docs/FUNCTION_MATRIX.md index 73abf04..9e190d2 100644 --- a/docs/FUNCTION_MATRIX.md +++ b/docs/FUNCTION_MATRIX.md @@ -33,7 +33,7 @@ | 经验沉淀 | ExperienceService | 候选、等级、重复次数、双批准晋级 | 自动反例集、跨项目验证和过期降级 | | 自进化 | EvolutionService | proposal-only、审核/人工批准状态 | 沙箱回归、canary、自动回滚编排 | | 项目管理 | ProjectService | 从模板实例化独立 Project-to-Act 文件夹 | 跨项目仪表盘和归档/迁移工具 | -| 接口 | CLI + FastAPI | doctor/project/run/resume/evidence/knowledge/profile/evolution | Web UI、认证、多租户限流 | +| 接口 | CLI + FastAPI + Next.js Web | doctor/project/run/resume/evidence/knowledge/profile/evolution;五阶段研究工作台、证据检查器与本地稿件自动保存 | 认证、多租户限流、论文列表 API 与流式事件 | | 审计 | RecordStore | append-only event timeline | WORM 存储、签名审计和外部 SIEM | | M08 执行 | metal_ductile_damage_v1 | 输入/来源 hash、幂等、失败记录、结果 artifact、E1/E2 分类 | 容器执行器、远程 worker、独立 grader 和真实 MD Pilot | @@ -45,4 +45,4 @@ - 自动获取受版权限制全文。 - 自动投稿、邮件发送或公开发布。 - PostgreSQL/pgvector/Neo4j 生产部署、HA、备份恢复演练。 -- Web 前端、多租户认证、成本和并发基线。 +- 多租户认证、成本和并发基线;Web 前端当前只验证本地单用户边界,不等于生产部署。 diff --git a/docs/WEB_UI.md b/docs/WEB_UI.md new file mode 100644 index 0000000..9e0eab2 --- /dev/null +++ b/docs/WEB_UI.md @@ -0,0 +1,44 @@ +# AutoResearch 研究工作台 + +`web/` 是 AutoResearch 的 Next.js 前端入口。它把五个业务阶段组织成一条“证据脊柱”:Idea 交流、论文搜集、论文阅读、论文写作和审核修改。证据脊柱是界面导航与状态投影,不是新的 Agent 或工作流实现。 + +## 运行 + +先在仓库根目录启动 API: + + .venv\Scripts\autoresearch.exe serve --host 127.0.0.1 --port 8010 + +再启动前端: + + cd web + npm.cmd install + npm.cmd run dev + +默认打开 `http://127.0.0.1:3000`。如 API 使用其他地址,在 `web/.env.local` 设置 `AUTORESEARCH_API_URL`;该文件不得提交。 + +## 五阶段能力 + +| 阶段 | 可操作能力 | 数据边界 | +|---|---|---| +| Idea 交流 | 追加研究约束、创建项目、启动 run | 对话先进入本地 idea 草稿;点击启动后才调用 `/projects` 与 `/runs` | +| 论文搜集 | 搜索/过滤、题录候选、研究队列、seed paper 状态 | E0 界面预览和未核验手工题录不会提交;外部检索成功只以后端 run 为准 | +| 论文阅读 | 文献切换、摘要/状态、证据限定问答 | 只有后端返回的 paper ID 可调用 `/papers/{id}/questions`;E0 问题保持 unresolved | +| 论文写作 | 章节导航、实时编辑、字数、自动保存、版本快照、受控修订 | 自动保存只写 localStorage;受控修订调用 `/manuscripts/{id}/revisions` 并保留 release Gate | +| 审核修改 | Gate、证据覆盖、缺口、blocker、定向返回写作 | UI 不计算或覆盖 Gate,也不填充人工批准身份 | + +## 状态与失败 + +- `API x.y.z`:健康检查成功,允许提交受控请求。 +- `离线预览`:只保留界面预览和浏览器本地草稿,启动 run 被禁用。 +- `blocked`:HTTP 请求成功,但工作流因证据不足 fail closed;不是网络错误。 +- `E0`:预览或未核验候选,不支持科研 claim。 +- `已保存到浏览器`:仅本地自动保存,不表示服务端同步。 + +## 验证 + + npm.cmd run lint + npm.cmd run typecheck + npm.cmd run test + npm.cmd run build + +AR-006 还在真实本地 FastAPI listener 上验证了健康连接与 blocked run,并在 1440×1000 和 390×844 视口检查关键界面。截图位于 `evidence/ui/`,详细证据见 `evidence/AR006_WEB_EVIDENCE.md`。 diff --git a/evidence/AR006_WEB_EVIDENCE.md b/evidence/AR006_WEB_EVIDENCE.md new file mode 100644 index 0000000..0c77b80 --- /dev/null +++ b/evidence/AR006_WEB_EVIDENCE.md @@ -0,0 +1,56 @@ +# AR-006 Research Workbench Evidence + +Date: 2026-08-23 + +## Scope + +This evidence covers the local single-user Next.js research workbench only. It does not claim multi-user authentication, production deployment, live external literature quality, real MD results, or publication readiness. + +## Implementation boundary + +- Framework: Next.js 16.3.2, React 19.2, TypeScript. +- Next.js 16.3.2 is above the 16.0.7 security fix line required for CVE-2025-55182. +- The browser calls `/backend/*`; Next.js proxies to the existing AutoResearch FastAPI. +- E0 preview records are excluded from submitted seed papers. +- localStorage autosave is labeled browser-local and is separate from manuscript revision APIs. +- Gate, blocked state, evidence IDs and human approval remain backend-owned. + +## Automated checks + +| Check | Exit | Result | +|---|---:|---| +| `npm.cmd run lint` | 0 | ESLint passed | +| `npm.cmd run typecheck` | 0 | TypeScript passed | +| `npm.cmd run test` | 0 | 3 interaction tests passed | +| `npm.cmd run build` | 0 | Next.js production build passed | +| `python.exe -m ruff check src tests` | 0 | Python lint passed | +| `python.exe -m pytest -W error -q` | 0 | 26 backend tests passed | +| `autoresearch.exe doctor` | 0 | five agents, offline-safe runtime and stores healthy | +| Project-to-Act `--validate` | 0 | managed ledger valid, zero issues | +| `node .ai-team/check.mjs --base main` | 0 | AR-006 handoff valid, 10/10 functional acceptance | + +The interaction tests cover stage navigation and empty-state recovery, E0 paper questions remaining unresolved, and manuscript edits being persisted only to browser storage. + +## Real local boundary + +- FastAPI listener: `127.0.0.1:8310`, isolated database and paper-project path under ignored `var/ui-pilot/`. +- Frontend production server: `127.0.0.1:3106`. +- Health state observed in the rendered UI: `API 0.2.0.dev0`. +- A real local `/projects` + `/runs` journey returned HTTP 201 with run `run_f9c5342b231b494802`, terminal business status `blocked`, lifecycle `waiting_evidence`, and one evidence ID. +- This is a passing fail-closed software journey, not a successful research result. + +## Visual and responsive checks + +| Viewport | Horizontal overflow | Result | Screenshot | +|---|---:|---|---| +| 1440×1000 | 0 px | three-pane workbench and evidence inspector visible | `evidence/ui/ar006-workbench-wide.png` | +| 1440×1000 | 0 px | manuscript outline/editor/revision/inspector visible; local save label observed | `evidence/ui/ar006-writer-wide.png` | +| 390×844 | 0 px | rail hidden, bottom stage navigation visible, editor width 360 px | `evidence/ui/ar006-writer-mobile.png` | + +Browser interaction also verified that the mobile stage navigation changes the active workspace and that the production build has no Next.js development overlay. + +## Remaining boundaries + +- The current backend does not expose project-wide paper/manuscript/review list endpoints, so the UI can show full paper metadata only for local candidates or records returned during the active run. +- External connector quality, licensed full-text rendering, streaming run events, authentication, collaboration and production hosting remain unverified. +- The screenshot manuscript content is a browser-local draft and remains blocked from release. diff --git a/evidence/ui/ar006-workbench-wide.png b/evidence/ui/ar006-workbench-wide.png new file mode 100644 index 0000000000000000000000000000000000000000..aecb30835be1aeba6a9c17108613cc1ccd61e3af GIT binary patch literal 108619 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 V64?D|N`tSJ+R;H`^~3-FCIBMg$ff`Q literal 0 HcmV?d00001 diff --git a/evidence/ui/ar006-writer-mobile.png b/evidence/ui/ar006-writer-mobile.png new file mode 100644 index 0000000000000000000000000000000000000000..4a2c246decf91a47f94f3874fee32c4b11e6d576 GIT binary patch literal 46265 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

eu{~v?gj6YbJ<-(m2o6>N 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 literal 0 HcmV?d00001 diff --git a/web/.env.example b/web/.env.example new file mode 100644 index 0000000..d5a3e8d --- /dev/null +++ b/web/.env.example @@ -0,0 +1 @@ +AUTORESEARCH_API_URL=http://127.0.0.1:8010 diff --git a/web/.gitignore b/web/.gitignore new file mode 100644 index 0000000..2769818 --- /dev/null +++ b/web/.gitignore @@ -0,0 +1,5 @@ +.next/ +node_modules/ +coverage/ +*.tsbuildinfo +.env.local diff --git a/web/README.md b/web/README.md new file mode 100644 index 0000000..9b18448 --- /dev/null +++ b/web/README.md @@ -0,0 +1,22 @@ +# AutoResearch Web + +Next.js 研究工作台,覆盖 idea 交流、论文搜集、论文阅读、写作实时修改与证据审核。 + +## 启动 + +先启动仓库根目录的 FastAPI(默认 `127.0.0.1:8010`),然后: + +```powershell +cd web +npm.cmd install +npm.cmd run dev +``` + +打开 `http://127.0.0.1:3000`。如果后端使用其他地址,复制 `.env.example` 为未提交的 `.env.local` 并修改 `AUTORESEARCH_API_URL`。 + +## 数据边界 + +- `/backend/*` 由 Next.js 同源代理到现有 AutoResearch FastAPI。 +- API 未连接时,界面明确显示“离线预览”;预览题录均为 E0,不构成论文证据。 +- 编辑器自动保存只写浏览器 localStorage,界面显示“本地草稿”,不冒充服务端同步。 +- 受控修订调用现有 `/manuscripts/{id}/revisions`,不会覆盖原稿,也不会绕过 release Gate。 diff --git a/web/app/globals.css b/web/app/globals.css new file mode 100644 index 0000000..4713888 --- /dev/null +++ b/web/app/globals.css @@ -0,0 +1,2918 @@ +:root { + --paper-field: #f1f3ee; + --paper: #fbfcf8; + --paper-raised: #ffffff; + --ledger: #e3e7df; + --ledger-strong: #cbd2c9; + --graphite: #18221f; + --graphite-soft: #4d5c57; + --graphite-muted: #7b8883; + --research-teal: #006d65; + --research-teal-dark: #00544e; + --research-teal-soft: #dfeeea; + --annotation: #c87a24; + --annotation-soft: #f7ebdc; + --critical: #b34a47; + --critical-soft: #f7e6e3; + --success: #377a57; + --success-soft: #e4f0e8; + --focus-ring: #3c9089; + --rail-width: 256px; + --inspector-width: 318px; + --radius-xs: 5px; + --radius-sm: 8px; + --radius-md: 11px; + --font-ui: "Segoe UI Variable", "Aptos", "Microsoft YaHei UI", sans-serif; + --font-document: "Iowan Old Style", "Source Han Serif SC", "Songti SC", serif; + --font-data: "Cascadia Code", "JetBrains Mono", monospace; +} + +* { + box-sizing: border-box; +} + +html, +body { + width: 100%; + min-height: 100%; + margin: 0; + background: var(--paper-field); + color: var(--graphite); + font-family: var(--font-ui); +} + +body { + overflow: hidden; +} + +button, +input, +textarea { + color: inherit; + font: inherit; +} + +button { + border: 0; +} + +button, +a { + -webkit-tap-highlight-color: transparent; +} + +button:focus-visible, +input:focus-visible, +textarea:focus-visible { + outline: 2px solid var(--focus-ring); + outline-offset: 2px; +} + +button:disabled { + cursor: not-allowed; + opacity: 0.5; +} + +.app-shell { + display: grid; + grid-template-columns: var(--rail-width) minmax(0, 1fr); + width: 100vw; + height: 100vh; + overflow: hidden; + background: var(--paper-field); +} + +.app-shell:has(.desktop-inspector) { + grid-template-columns: var(--rail-width) minmax(0, 1fr) var(--inspector-width); +} + +.icon-button { + display: inline-grid; + width: 32px; + height: 32px; + padding: 0; + place-items: center; + border: 1px solid transparent; + border-radius: var(--radius-sm); + background: transparent; + color: var(--graphite-soft); + cursor: pointer; + transition: background 150ms ease, border-color 150ms ease, color 150ms ease; +} + +.icon-button:hover { + border-color: var(--ledger); + background: var(--paper-raised); + color: var(--graphite); +} + +.button { + display: inline-flex; + min-height: 38px; + align-items: center; + justify-content: center; + gap: 7px; + padding: 0 14px; + border: 1px solid transparent; + border-radius: var(--radius-sm); + cursor: pointer; + font-size: 13px; + font-weight: 650; + letter-spacing: 0.01em; + transition: background 150ms ease, border-color 150ms ease, transform 150ms ease; +} + +.button:active:not(:disabled) { + transform: translateY(1px); +} + +.button.primary { + background: var(--research-teal); + color: #fff; +} + +.button.primary:hover:not(:disabled) { + background: var(--research-teal-dark); +} + +.button.secondary { + border-color: var(--ledger-strong); + background: var(--paper-raised); + color: var(--graphite); +} + +.button.secondary:hover:not(:disabled) { + border-color: #aab5ad; + background: #f8faf6; +} + +.button.small { + min-height: 32px; + padding: 0 10px; + font-size: 12px; +} + +.button.full { + width: 100%; +} + +.eyebrow { + color: var(--research-teal); + font-family: var(--font-data); + font-size: 10px; + font-weight: 700; + letter-spacing: 0.12em; + text-transform: uppercase; +} + +.project-rail { + z-index: 20; + display: flex; + min-width: 0; + height: 100vh; + flex-direction: column; + border-right: 1px solid var(--ledger-strong); + background: #eef1eb; +} + +.brand-lockup { + display: flex; + height: 66px; + align-items: center; + gap: 10px; + padding: 0 17px; + border-bottom: 1px solid var(--ledger); +} + +.brand-mark { + position: relative; + display: flex; + width: 33px; + height: 33px; + align-items: center; + justify-content: center; + gap: 2px; + overflow: hidden; + border: 1px solid #aeb9b1; + border-radius: 50%; + color: var(--graphite); + font-family: var(--font-data); + font-size: 10px; + font-weight: 800; +} + +.brand-mark::after { + position: absolute; + width: 5px; + height: 5px; + border: 2px solid var(--research-teal); + border-radius: 50%; + content: ""; + transform: translateY(12px); +} + +.brand-mark i { + width: 1px; + height: 17px; + background: var(--research-teal); + transform: rotate(28deg); +} + +.brand-lockup > div:nth-child(2) { + display: flex; + min-width: 0; + flex: 1; + flex-direction: column; +} + +.brand-lockup strong { + font-size: 14px; + letter-spacing: -0.015em; +} + +.brand-lockup small { + margin-top: 2px; + color: var(--graphite-muted); + font-family: var(--font-data); + font-size: 9px; + letter-spacing: 0.03em; +} + +.mobile-close, +.mobile-menu { + display: none; +} + +.workspace-switcher { + display: grid; + grid-template-columns: 34px minmax(0, 1fr) 16px; + align-items: center; + gap: 9px; + margin: 14px 12px 9px; + padding: 9px; + border: 1px solid var(--ledger-strong); + border-radius: var(--radius-md); + background: rgba(255, 255, 255, 0.48); + cursor: default; +} + +.workspace-avatar { + display: grid; + width: 34px; + height: 34px; + place-items: center; + border-radius: var(--radius-sm); + background: var(--research-teal-soft); + color: var(--research-teal); +} + +.workspace-switcher > div:nth-child(2) { + display: flex; + min-width: 0; + flex-direction: column; + gap: 2px; +} + +.workspace-switcher span, +.workspace-switcher strong { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.workspace-switcher span { + color: var(--graphite-muted); + font-size: 10px; +} + +.workspace-switcher strong { + font-size: 12px; +} + +.stage-nav { + padding: 9px 9px 0; +} + +.nav-label { + margin: 0 8px 7px; + color: var(--graphite-muted); + font-family: var(--font-data); + font-size: 9px; + font-weight: 700; + letter-spacing: 0.1em; + text-transform: uppercase; +} + +.stage-nav > button { + display: grid; + width: 100%; + grid-template-columns: 28px minmax(0, 1fr) 18px; + align-items: center; + gap: 6px; + padding: 10px 8px; + border-radius: var(--radius-sm); + background: transparent; + color: var(--graphite-soft); + cursor: pointer; + text-align: left; + transition: background 150ms ease, color 150ms ease; +} + +.stage-nav > button:hover { + background: rgba(255, 255, 255, 0.55); + color: var(--graphite); +} + +.stage-nav > button.active { + background: var(--paper-raised); + color: var(--graphite); +} + +.stage-nav-index { + color: var(--graphite-muted); + font-family: var(--font-data); + font-size: 9px; +} + +.stage-nav button.active .stage-nav-index { + color: var(--research-teal); + font-weight: 800; +} + +.stage-nav button > span:nth-child(2) { + display: flex; + min-width: 0; + flex-direction: column; + gap: 2px; +} + +.stage-nav button strong { + font-size: 12px; + font-weight: 650; +} + +.stage-nav button small { + overflow: hidden; + color: var(--graphite-muted); + font-size: 9px; + text-overflow: ellipsis; + white-space: nowrap; +} + +.stage-nav button em { + display: grid; + min-width: 19px; + height: 19px; + padding: 0 4px; + place-items: center; + border-radius: 10px; + background: var(--critical-soft); + color: var(--critical); + font-family: var(--font-data); + font-size: 9px; + font-style: normal; +} + +.rail-secondary { + display: flex; + flex-direction: column; + gap: 2px; + margin: 14px 13px 0; + padding-top: 13px; + border-top: 1px solid var(--ledger-strong); +} + +.rail-secondary button { + display: flex; + align-items: center; + gap: 10px; + padding: 8px 10px; + border-radius: var(--radius-sm); + background: transparent; + color: var(--graphite-soft); + cursor: pointer; + font-size: 11px; + text-align: left; +} + +.rail-secondary button:hover { + background: rgba(255, 255, 255, 0.5); + color: var(--graphite); +} + +.rail-status { + display: grid; + grid-template-columns: 9px minmax(0, 1fr) 28px; + align-items: center; + gap: 8px; + margin: auto 12px 12px; + padding: 10px; + border: 1px solid var(--ledger-strong); + border-radius: var(--radius-md); + background: rgba(255, 255, 255, 0.42); +} + +.status-dot { + width: 7px; + height: 7px; + border-radius: 50%; + background: var(--graphite-muted); +} + +.status-dot--online { + background: var(--success); + box-shadow: 0 0 0 3px var(--success-soft); +} + +.status-dot--offline { + background: var(--annotation); + box-shadow: 0 0 0 3px var(--annotation-soft); +} + +.status-dot--checking { + animation: pulse 1.4s ease-in-out infinite; +} + +.rail-status > div:nth-child(2) { + display: flex; + min-width: 0; + flex-direction: column; + gap: 2px; +} + +.rail-status strong, +.rail-status small { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.rail-status strong { + font-size: 10px; +} + +.rail-status small { + color: var(--graphite-muted); + font-size: 8px; +} + +.workbench { + display: grid; + min-width: 0; + height: 100vh; + grid-template-rows: 54px 76px minmax(0, 1fr); + background: var(--paper); +} + +.topbar { + z-index: 10; + display: flex; + min-width: 0; + align-items: center; + justify-content: space-between; + padding: 0 17px 0 20px; + border-bottom: 1px solid var(--ledger); + background: rgba(251, 252, 248, 0.94); +} + +.breadcrumb { + display: flex; + min-width: 0; + align-items: center; + gap: 6px; + color: var(--graphite-muted); + font-size: 11px; +} + +.breadcrumb strong { + overflow: hidden; + color: var(--graphite); + font-weight: 600; + text-overflow: ellipsis; + white-space: nowrap; +} + +.topbar-actions { + display: flex; + flex-shrink: 0; + align-items: center; + gap: 5px; +} + +.guarded-badge { + display: flex; + height: 28px; + align-items: center; + gap: 6px; + margin-right: 5px; + padding: 0 9px; + border: 1px solid #bbd5ce; + border-radius: 15px; + background: var(--research-teal-soft); + color: var(--research-teal-dark); + font-size: 10px; + font-weight: 650; +} + +.avatar-button { + display: grid; + width: 29px; + height: 29px; + margin-left: 2px; + place-items: center; + border-radius: 50%; + background: var(--graphite); + color: #fff; + cursor: pointer; + font-size: 11px; + font-weight: 700; +} + +.evidence-spine { + display: grid; + min-width: 0; + grid-template-columns: 120px minmax(0, 1fr); + align-items: center; + gap: 18px; + padding: 0 24px; + border-bottom: 1px solid var(--ledger); + background: #f7f9f4; +} + +.spine-title { + display: flex; + flex-direction: column; + gap: 3px; +} + +.spine-title span { + font-size: 11px; + font-weight: 700; +} + +.spine-title small { + overflow: hidden; + color: var(--graphite-muted); + font-family: var(--font-data); + font-size: 8px; + text-overflow: ellipsis; + white-space: nowrap; +} + +.spine-track { + position: relative; + display: grid; + grid-template-columns: repeat(5, 1fr); + align-items: start; +} + +.spine-track::before, +.spine-progress { + position: absolute; + top: 11px; + right: 10%; + left: 10%; + height: 1px; + content: ""; +} + +.spine-track::before { + background: var(--ledger-strong); +} + +.spine-progress { + right: auto; + left: 10%; + max-width: 80%; + background: var(--research-teal); + transition: width 220ms ease; +} + +.spine-track button { + z-index: 1; + display: flex; + flex-direction: column; + align-items: center; + gap: 5px; + background: transparent; + color: var(--graphite-muted); + cursor: pointer; +} + +.spine-track button > span { + display: grid; + width: 23px; + height: 23px; + place-items: center; + border: 1px solid var(--ledger-strong); + border-radius: 50%; + background: #f7f9f4; + font-family: var(--font-data); + font-size: 8px; +} + +.spine-track button strong { + font-size: 9px; + font-weight: 600; +} + +.spine-track button.active, +.spine-track button.past { + color: var(--research-teal); +} + +.spine-track button.active > span, +.spine-track button.past > span { + border-color: var(--research-teal); + background: var(--research-teal); + color: #fff; +} + +.spine-track button.active > span { + box-shadow: 0 0 0 4px var(--research-teal-soft); +} + +.spine-track button.blocked { + color: var(--critical); +} + +.spine-track button.blocked > span { + border-color: var(--critical); + background: var(--critical-soft); + color: var(--critical); +} + +.stage-workspace { + min-width: 0; + min-height: 0; + overflow: auto; + scrollbar-color: #c3cbc3 transparent; + scrollbar-width: thin; +} + +.stage-heading { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 28px; + margin-bottom: 22px; +} + +.stage-heading > div:first-child { + max-width: 710px; +} + +.stage-heading h1 { + margin: 7px 0 8px; + font-size: clamp(25px, 2.1vw, 34px); + font-weight: 690; + letter-spacing: -0.035em; + line-height: 1.14; +} + +.stage-heading p { + margin: 0; + color: var(--graphite-soft); + font-size: 12px; + line-height: 1.65; +} + +.stage-heading.compact { + align-items: center; + margin-bottom: 18px; +} + +.stage-heading.compact h1 { + margin: 4px 0 4px; + font-size: 25px; +} + +.start-button { + flex-shrink: 0; + margin-top: 17px; +} + +.idea-studio, +.library-view, +.review-view { + width: min(100%, 1040px); + margin: 0 auto; + padding: 29px 34px 80px; +} + +.run-banner { + display: flex; + align-items: center; + gap: 11px; + margin: -8px 0 18px; + padding: 10px 12px; + border: 1px solid var(--ledger-strong); + border-radius: var(--radius-sm); + background: #f6f8f4; + font-family: var(--font-data); + font-size: 10px; +} + +.run-banner > div { + display: flex; + align-items: center; + gap: 7px; + margin-right: auto; +} + +.run-banner strong { + padding: 3px 7px; + border-radius: 10px; + background: var(--annotation-soft); + color: #8c5619; + font-size: 8px; + text-transform: uppercase; +} + +.run-banner small { + color: var(--graphite-muted); +} + +.conversation-ledger { + position: relative; + display: flex; + flex-direction: column; + padding: 4px 0 8px; +} + +.conversation-ledger::before { + position: absolute; + top: 18px; + bottom: 20px; + left: 17px; + width: 1px; + background: var(--ledger-strong); + content: ""; +} + +.conversation-date { + z-index: 1; + display: flex; + justify-content: center; + margin-bottom: 17px; +} + +.conversation-date span { + padding: 3px 9px; + border: 1px solid var(--ledger); + border-radius: 11px; + background: var(--paper); + color: var(--graphite-muted); + font-family: var(--font-data); + font-size: 8px; +} + +.message { + position: relative; + display: grid; + grid-template-columns: 35px minmax(0, 1fr); + gap: 13px; + margin-bottom: 17px; +} + +.message-avatar { + z-index: 1; + display: grid; + width: 35px; + height: 35px; + place-items: center; + border: 1px solid var(--ledger-strong); + border-radius: 50%; + background: var(--paper-raised); + color: var(--research-teal); + font-size: 10px; + font-weight: 700; +} + +.message--researcher .message-avatar { + border-color: var(--graphite); + background: var(--graphite); + color: #fff; +} + +.message--system .message-avatar { + background: #f2f3ef; + color: var(--graphite-muted); +} + +.message-content { + padding: 12px 14px; + border: 1px solid var(--ledger); + border-radius: var(--radius-sm); + background: var(--paper-raised); +} + +.message--researcher .message-content { + background: #f4f7f2; +} + +.message--system .message-content { + border-style: dashed; + background: transparent; +} + +.message-content header { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 5px; +} + +.message-content header strong { + font-size: 11px; +} + +.message-content time { + color: var(--graphite-muted); + font-family: var(--font-data); + font-size: 8px; +} + +.message-content p { + margin: 0; + color: #33413d; + font-size: 12px; + line-height: 1.75; +} + +.message-content footer { + display: flex; + flex-wrap: wrap; + gap: 5px; + margin-top: 10px; +} + +.message-content footer span { + padding: 3px 7px; + border-radius: 10px; + background: var(--research-teal-soft); + color: var(--research-teal-dark); + font-size: 9px; +} + +.idea-composer { + overflow: hidden; + margin: 4px 0 0 48px; + border: 1px solid #aebcb5; + border-radius: var(--radius-md); + background: var(--paper-raised); + box-shadow: 0 2px 8px rgba(24, 34, 31, 0.05); +} + +.idea-composer:focus-within { + border-color: var(--research-teal); + box-shadow: 0 0 0 3px var(--research-teal-soft); +} + +.idea-composer textarea { + width: 100%; + min-height: 76px; + padding: 14px; + resize: vertical; + border: 0; + outline: 0; + background: transparent; + font-size: 12px; + line-height: 1.6; +} + +.composer-footer { + display: flex; + align-items: center; + justify-content: space-between; + padding: 8px 9px 8px 13px; + border-top: 1px solid var(--ledger); + background: #f8faf6; +} + +.composer-footer > span { + display: flex; + align-items: center; + gap: 6px; + color: var(--graphite-muted); + font-size: 9px; +} + +.composer-footer button { + display: inline-flex; + height: 29px; + align-items: center; + gap: 6px; + padding: 0 10px; + border-radius: var(--radius-xs); + background: var(--graphite); + color: #fff; + cursor: pointer; + font-size: 10px; + font-weight: 650; +} + +.library-toolbar { + display: flex; + align-items: center; + gap: 10px; + margin-bottom: 12px; +} + +.search-field { + display: flex; + min-width: 200px; + height: 38px; + flex: 1; + align-items: center; + gap: 8px; + padding: 0 11px; + border: 1px solid var(--ledger-strong); + border-radius: var(--radius-sm); + background: var(--paper-raised); + color: var(--graphite-muted); +} + +.search-field:focus-within { + border-color: var(--research-teal); +} + +.search-field input { + min-width: 0; + flex: 1; + border: 0; + outline: 0; + background: transparent; + font-size: 11px; +} + +.filter-tabs { + display: flex; + height: 38px; + padding: 3px; + border: 1px solid var(--ledger-strong); + border-radius: var(--radius-sm); + background: #f0f3ed; +} + +.filter-tabs button { + display: flex; + align-items: center; + gap: 5px; + padding: 0 10px; + border-radius: var(--radius-xs); + background: transparent; + color: var(--graphite-soft); + cursor: pointer; + font-size: 10px; +} + +.filter-tabs button.active { + background: var(--paper-raised); + color: var(--graphite); + box-shadow: 0 1px 2px rgba(24, 34, 31, 0.09); +} + +.filter-tabs button span { + color: var(--graphite-muted); + font-family: var(--font-data); + font-size: 8px; +} + +.filter-button { + width: 38px; + height: 38px; + border-color: var(--ledger-strong); + background: var(--paper-raised); +} + +.preview-boundary { + display: flex; + align-items: flex-start; + gap: 8px; + margin-bottom: 11px; + padding: 9px 11px; + border: 1px solid #ead4b8; + border-radius: var(--radius-sm); + background: var(--annotation-soft); + color: #754a1d; + font-size: 9px; + line-height: 1.5; +} + +.preview-boundary svg { + flex-shrink: 0; + margin-top: 1px; +} + +.paper-list { + overflow: hidden; + border: 1px solid var(--ledger-strong); + border-radius: var(--radius-md); + background: var(--paper-raised); +} + +.paper-list-head, +.paper-row { + display: grid; + grid-template-columns: minmax(300px, 1.8fr) minmax(150px, 0.8fr) 64px 88px; + align-items: center; + gap: 13px; +} + +.paper-list-head { + min-height: 36px; + padding: 0 14px; + border-bottom: 1px solid var(--ledger); + background: #f3f5f0; + color: var(--graphite-muted); + font-family: var(--font-data); + font-size: 8px; + letter-spacing: 0.04em; + text-transform: uppercase; +} + +.paper-row { + position: relative; + min-height: 78px; + padding: 11px 14px; + border-bottom: 1px solid var(--ledger); + cursor: pointer; + transition: background 150ms ease; +} + +.paper-row:last-child { + border-bottom: 0; +} + +.paper-row:hover, +.paper-row.selected { + background: #f6f8f4; +} + +.paper-row.selected::before { + position: absolute; + top: 10px; + bottom: 10px; + left: 0; + width: 2px; + background: var(--research-teal); + content: ""; +} + +.paper-title-cell { + display: flex; + min-width: 0; + align-items: center; + gap: 10px; +} + +.paper-kind { + display: grid; + width: 32px; + height: 32px; + flex-shrink: 0; + place-items: center; + border-radius: var(--radius-sm); + background: #edf0eb; + color: var(--graphite-soft); +} + +.paper-title-cell > div { + min-width: 0; +} + +.paper-title-cell h3 { + overflow: hidden; + margin: 0 0 5px; + font-size: 11px; + font-weight: 650; + line-height: 1.4; + text-overflow: ellipsis; + white-space: nowrap; +} + +.paper-title-cell p { + overflow: hidden; + margin: 0; + color: var(--graphite-muted); + font-size: 8px; + text-overflow: ellipsis; + white-space: nowrap; +} + +.paper-topics { + display: flex; + min-width: 0; + flex-wrap: wrap; + gap: 4px; +} + +.paper-topics span { + padding: 3px 6px; + border-radius: 9px; + background: #eef1ed; + color: var(--graphite-soft); + font-size: 8px; +} + +.evidence-grade { + display: inline-grid; + min-width: 30px; + height: 20px; + padding: 0 6px; + place-items: center; + border: 1px solid var(--ledger-strong); + border-radius: 10px; + background: #f2f4f0; + color: var(--graphite-muted); + font-family: var(--font-data); + font-size: 8px; + font-weight: 700; +} + +.evidence-grade--e1, +.evidence-grade--e2 { + border-color: #b9d6ce; + background: var(--research-teal-soft); + color: var(--research-teal-dark); +} + +.queue-cell button { + display: inline-flex; + height: 27px; + align-items: center; + gap: 4px; + padding: 0 8px; + border: 1px solid var(--ledger-strong); + border-radius: var(--radius-xs); + background: var(--paper-raised); + color: var(--graphite-soft); + cursor: pointer; + font-size: 9px; +} + +.queue-cell button.queued { + border-color: #b7d3cc; + background: var(--research-teal-soft); + color: var(--research-teal-dark); +} + +.stage-footer { + display: flex; + align-items: center; + justify-content: space-between; + gap: 15px; + margin-top: 14px; + color: var(--graphite-muted); + font-size: 9px; +} + +.empty-state { + display: flex; + min-height: 270px; + flex-direction: column; + align-items: center; + justify-content: center; + border: 1px dashed var(--ledger-strong); + border-radius: var(--radius-md); + color: var(--graphite-muted); + text-align: center; +} + +.empty-state h3 { + margin: 13px 0 5px; + color: var(--graphite); + font-size: 14px; +} + +.empty-state p { + margin: 0 0 15px; + font-size: 10px; +} + +.reader-view { + display: grid; + min-width: 0; + min-height: 100%; + grid-template-columns: 230px minmax(0, 1fr); +} + +.reader-library { + min-width: 0; + padding: 20px 13px; + border-right: 1px solid var(--ledger); + background: #f5f7f2; +} + +.reader-library > header, +.outline-panel > header, +.revision-panel > header, +.findings-panel > header, +.blocker-panel > header { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 8px; +} + +.reader-library h2, +.outline-panel h2, +.revision-panel h2, +.findings-panel h2, +.blocker-panel h2 { + margin: 4px 0 0; + font-size: 14px; +} + +.reader-library > header > span { + display: grid; + min-width: 22px; + height: 22px; + place-items: center; + border-radius: 11px; + background: var(--ledger); + color: var(--graphite-soft); + font-family: var(--font-data); + font-size: 8px; +} + +.mini-search { + display: flex; + height: 32px; + align-items: center; + gap: 7px; + margin: 14px 0 10px; + padding: 0 9px; + border: 1px solid var(--ledger-strong); + border-radius: var(--radius-xs); + background: var(--paper-raised); + color: var(--graphite-muted); +} + +.mini-search input { + min-width: 0; + flex: 1; + border: 0; + outline: 0; + background: transparent; + font-size: 9px; +} + +.reader-paper-list { + display: flex; + flex-direction: column; + gap: 3px; +} + +.reader-paper-list button { + display: grid; + grid-template-columns: 7px minmax(0, 1fr); + gap: 8px; + padding: 9px 8px; + border-radius: var(--radius-sm); + background: transparent; + cursor: pointer; + text-align: left; +} + +.reader-paper-list button:hover, +.reader-paper-list button.active { + background: var(--paper-raised); +} + +.reader-paper-list button.active { + box-shadow: inset 2px 0 0 var(--research-teal); +} + +.reader-paper-list button > span:last-child { + display: flex; + min-width: 0; + flex-direction: column; + gap: 4px; +} + +.reader-paper-list strong { + display: -webkit-box; + overflow: hidden; + font-size: 10px; + font-weight: 620; + line-height: 1.4; + -webkit-box-orient: vertical; + -webkit-line-clamp: 2; +} + +.reader-paper-list small { + overflow: hidden; + color: var(--graphite-muted); + font-size: 8px; + text-overflow: ellipsis; + white-space: nowrap; +} + +.grade-dot { + width: 6px; + height: 6px; + margin-top: 4px; + border: 1px solid var(--graphite-muted); + border-radius: 50%; +} + +.grade-dot--e1, +.grade-dot--e2 { + border-color: var(--research-teal); + background: var(--research-teal); +} + +.reading-paper { + width: min(100%, 790px); + margin: 0 auto; + padding: 30px 44px 80px; +} + +.reading-title { + padding-bottom: 20px; + border-bottom: 1px solid var(--ledger); +} + +.reading-meta { + display: flex; + align-items: center; + gap: 8px; + color: var(--graphite-muted); + font-family: var(--font-data); + font-size: 8px; +} + +.reading-title h1 { + margin: 13px 0 7px; + font-family: var(--font-document); + font-size: clamp(22px, 2.1vw, 31px); + font-weight: 650; + letter-spacing: -0.02em; + line-height: 1.35; +} + +.reading-title > p { + margin: 0; + color: var(--graphite-muted); + font-size: 10px; +} + +.reading-tabs { + display: flex; + gap: 20px; + margin: 0 0 24px; + border-bottom: 1px solid var(--ledger); +} + +.reading-tabs button { + position: relative; + height: 42px; + padding: 0; + background: transparent; + color: var(--graphite-muted); + cursor: pointer; + font-size: 10px; +} + +.reading-tabs button.active { + color: var(--graphite); + font-weight: 650; +} + +.reading-tabs button.active::after { + position: absolute; + right: 0; + bottom: -1px; + left: 0; + height: 2px; + background: var(--research-teal); + content: ""; +} + +.paper-section { + position: relative; + margin-bottom: 20px; +} + +.section-kicker { + color: var(--research-teal); + font-family: var(--font-data); + font-size: 9px; + font-weight: 700; + letter-spacing: 0.05em; + text-transform: uppercase; +} + +.paper-section p { + margin: 8px 0 0; + font-family: var(--font-document); + font-size: 14px; + line-height: 1.95; +} + +.paper-section.highlighted { + padding: 13px 15px 13px 40px; + border-left: 2px solid var(--annotation); + background: var(--annotation-soft); +} + +.paper-section.highlighted p { + font-family: var(--font-ui); + font-size: 11px; + line-height: 1.7; +} + +.highlight-rail { + position: absolute; + top: 13px; + left: 13px; + color: var(--annotation); +} + +.reading-notes { + margin-top: 27px; + padding-top: 18px; + border-top: 1px solid var(--ledger); +} + +.reading-notes > header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 11px; +} + +.reading-notes > header > div { + display: flex; + align-items: center; + gap: 7px; +} + +.reading-notes > header strong { + font-size: 11px; +} + +.reading-notes > header span { + color: var(--graphite-muted); + font-size: 9px; +} + +.qa-answer { + margin-bottom: 8px; + padding: 11px 12px; + border: 1px solid var(--ledger); + border-radius: var(--radius-sm); + background: var(--paper-raised); +} + +.qa-answer.unresolved { + border-color: #e4c9a6; + background: #fdf8f1; +} + +.qa-answer > strong { + font-size: 10px; +} + +.qa-answer p { + margin: 6px 0 9px; + font-size: 10px; + line-height: 1.6; +} + +.qa-answer footer { + display: flex; + align-items: center; + justify-content: space-between; + gap: 10px; + color: var(--graphite-muted); + font-size: 8px; +} + +.qa-answer footer span { + display: flex; + align-items: center; + gap: 4px; +} + +.qa-answer footer em { + font-family: var(--font-data); + font-style: normal; +} + +.reader-ask { + display: flex; + height: 42px; + align-items: center; + margin-top: 12px; + overflow: hidden; + border: 1px solid var(--ledger-strong); + border-radius: var(--radius-sm); + background: var(--paper-raised); +} + +.reader-ask:focus-within { + border-color: var(--research-teal); +} + +.reader-ask input { + min-width: 0; + height: 100%; + flex: 1; + padding: 0 12px; + border: 0; + outline: 0; + background: transparent; + font-size: 10px; +} + +.reader-ask button { + display: grid; + width: 42px; + height: 42px; + place-items: center; + background: var(--graphite); + color: #fff; + cursor: pointer; +} + +.stage-footer.in-paper span { + display: flex; + align-items: center; + gap: 6px; +} + +.writer-view { + display: grid; + min-width: 0; + min-height: 100%; + grid-template-columns: 180px minmax(300px, 1fr) 256px; +} + +.outline-panel, +.revision-panel { + min-width: 0; + padding: 18px 13px; + background: #f5f7f2; +} + +.outline-panel { + border-right: 1px solid var(--ledger); +} + +.revision-panel { + border-left: 1px solid var(--ledger); +} + +.outline-list { + display: flex; + flex-direction: column; + gap: 2px; + margin-top: 14px; +} + +.outline-list button { + position: relative; + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + gap: 4px; + padding: 8px 9px; + border-radius: var(--radius-xs); + background: transparent; + cursor: pointer; + text-align: left; +} + +.outline-list button:hover, +.outline-list button.active { + background: var(--paper-raised); +} + +.outline-list button.active { + box-shadow: inset 2px 0 0 var(--research-teal); +} + +.outline-list button > span { + overflow: hidden; + font-size: 10px; + font-weight: 620; + text-overflow: ellipsis; + white-space: nowrap; +} + +.outline-list button small { + grid-row: 2; + color: var(--graphite-muted); + font-size: 8px; +} + +.outline-list button em { + display: grid; + min-width: 18px; + height: 18px; + grid-column: 2; + grid-row: 1 / 3; + place-items: center; + border-radius: 9px; + background: var(--annotation-soft); + color: #8a5317; + font-family: var(--font-data); + font-size: 8px; + font-style: normal; +} + +.outline-summary { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 6px; + margin-top: 16px; + padding-top: 12px; + border-top: 1px solid var(--ledger); +} + +.outline-summary > div { + display: flex; + flex-direction: column; + gap: 3px; +} + +.outline-summary span { + color: var(--graphite-muted); + font-size: 8px; +} + +.outline-summary strong { + font-family: var(--font-data); + font-size: 13px; +} + +.editor-panel { + display: grid; + min-width: 0; + min-height: 0; + grid-template-rows: 47px minmax(0, 1fr); + background: #eaede7; +} + +.editor-toolbar { + display: flex; + min-width: 0; + align-items: center; + justify-content: space-between; + gap: 12px; + padding: 0 12px 0 16px; + border-bottom: 1px solid var(--ledger-strong); + background: var(--paper); +} + +.editor-breadcrumb { + display: flex; + min-width: 0; + align-items: center; + gap: 5px; + color: var(--graphite-muted); + font-size: 9px; +} + +.editor-breadcrumb strong { + overflow: hidden; + color: var(--graphite); + text-overflow: ellipsis; + white-space: nowrap; +} + +.editor-actions { + display: flex; + flex-shrink: 0; + align-items: center; + gap: 8px; +} + +.save-indicator { + display: flex; + align-items: center; + gap: 5px; + color: var(--graphite-muted); + font-size: 8px; +} + +.save-indicator--saving, +.save-indicator--changed { + color: var(--annotation); +} + +.editor-canvas { + min-width: 0; + min-height: 0; + overflow: auto; + padding: 24px 22px 60px; +} + +.document-page { + display: flex; + width: min(100%, 700px); + min-height: 720px; + flex-direction: column; + margin: 0 auto; + padding: 52px clamp(35px, 6vw, 74px) 32px; + border: 1px solid #d9ddd5; + background: var(--paper-raised); + box-shadow: 0 2px 8px rgba(24, 34, 31, 0.06); +} + +.document-title { + width: 100%; + padding: 0 0 18px; + border: 0; + border-bottom: 1px solid var(--ledger); + outline: 0; + background: transparent; + font-family: var(--font-document); + font-size: 25px; + font-weight: 650; +} + +.document-body { + width: 100%; + min-height: 500px; + flex: 1; + margin-top: 22px; + resize: none; + border: 0; + outline: 0; + background: transparent; + font-family: var(--font-document); + font-size: 14px; + line-height: 2.05; +} + +.document-page footer { + display: flex; + justify-content: flex-end; + gap: 12px; + padding-top: 13px; + border-top: 1px solid var(--ledger); + color: var(--graphite-muted); + font-family: var(--font-data); + font-size: 8px; +} + +.revision-panel > header { + display: block; +} + +.revision-panel > header p { + margin: 7px 0 0; + color: var(--graphite-muted); + font-size: 9px; + line-height: 1.55; +} + +.revision-mode { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 3px; + margin: 16px 0 13px; + padding: 3px; + border: 1px solid var(--ledger-strong); + border-radius: var(--radius-sm); + background: #eaede7; +} + +.revision-mode button { + display: flex; + height: 29px; + align-items: center; + justify-content: center; + gap: 5px; + border-radius: var(--radius-xs); + background: transparent; + color: var(--graphite-soft); + cursor: pointer; + font-size: 9px; +} + +.revision-mode button.active { + background: var(--paper-raised); + color: var(--graphite); + box-shadow: 0 1px 2px rgba(24, 34, 31, 0.08); +} + +.revision-panel label, +.paper-modal label { + display: flex; + flex-direction: column; + gap: 6px; + color: var(--graphite-soft); + font-size: 9px; + font-weight: 650; +} + +.revision-panel textarea { + min-height: 104px; + padding: 10px; + resize: vertical; + border: 1px solid var(--ledger-strong); + border-radius: var(--radius-sm); + outline: 0; + background: var(--paper-raised); + font-size: 10px; + line-height: 1.6; +} + +.revision-panel textarea:focus { + border-color: var(--research-teal); +} + +.server-revision-state { + display: flex; + align-items: center; + gap: 6px; + margin: 10px 0; + color: var(--annotation); + font-size: 8px; +} + +.server-revision-state.connected { + color: var(--success); +} + +.revision-boundary { + display: flex; + align-items: flex-start; + gap: 7px; + margin-top: 12px; + padding: 9px; + border: 1px solid var(--ledger); + border-radius: var(--radius-sm); + background: #eef1ec; + color: var(--graphite-muted); +} + +.revision-boundary svg { + flex-shrink: 0; + color: var(--research-teal); +} + +.revision-boundary p { + margin: 0; + font-size: 8px; + line-height: 1.55; +} + +.revision-boundary code { + font-family: var(--font-data); + font-size: 7px; +} + +.review-overview { + display: grid; + grid-template-columns: minmax(280px, 1.5fr) repeat(3, minmax(110px, 0.55fr)); + gap: 9px; + margin-bottom: 16px; +} + +.gate-card, +.review-stat { + min-height: 112px; + border: 1px solid var(--ledger-strong); + border-radius: var(--radius-md); + background: var(--paper-raised); +} + +.gate-card { + display: flex; + align-items: center; + gap: 14px; + padding: 15px; +} + +.gate-card.blocked { + border-color: #dfb8b3; + background: #fcf5f3; +} + +.gate-seal { + display: grid; + width: 48px; + height: 48px; + flex-shrink: 0; + place-items: center; + border: 1px solid #daa8a3; + border-radius: 50%; + color: var(--critical); +} + +.gate-card span, +.review-stat span { + color: var(--graphite-muted); + font-family: var(--font-data); + font-size: 8px; +} + +.gate-card h2 { + margin: 4px 0 3px; + color: var(--critical); + font-family: var(--font-data); + font-size: 17px; + letter-spacing: 0.06em; +} + +.gate-card p, +.review-stat small { + margin: 0; + color: var(--graphite-soft); + font-size: 9px; + line-height: 1.45; +} + +.review-stat { + display: flex; + flex-direction: column; + justify-content: center; + padding: 15px; +} + +.review-stat strong { + margin: 4px 0 2px; + font-family: var(--font-data); + font-size: 25px; + font-weight: 500; +} + +.review-columns { + display: grid; + grid-template-columns: minmax(380px, 1.55fr) minmax(240px, 0.8fr); + gap: 12px; +} + +.findings-panel, +.blocker-panel { + overflow: hidden; + border: 1px solid var(--ledger-strong); + border-radius: var(--radius-md); + background: var(--paper-raised); +} + +.findings-panel > header, +.blocker-panel > header { + padding: 14px 15px; + border-bottom: 1px solid var(--ledger); + background: #f7f8f5; +} + +.findings-panel article { + display: grid; + grid-template-columns: 40px minmax(0, 1fr); + gap: 11px; + padding: 13px 15px; + border-bottom: 1px solid var(--ledger); +} + +.findings-panel article:last-child { + border-bottom: 0; +} + +.finding-level { + display: grid; + height: 20px; + place-items: center; + border-radius: 10px; + background: #eef0ec; + color: var(--graphite-soft); + font-size: 8px; +} + +.finding-level--阻断 { + background: var(--critical-soft); + color: var(--critical); +} + +.finding-level--主要 { + background: var(--annotation-soft); + color: #8e571b; +} + +.findings-panel article h3 { + margin: 2px 0 5px; + font-size: 11px; +} + +.findings-panel article p { + margin: 0; + color: var(--graphite-soft); + font-size: 9px; + line-height: 1.55; +} + +.findings-panel article footer { + display: flex; + align-items: center; + justify-content: space-between; + margin-top: 8px; +} + +.findings-panel article footer span { + color: var(--graphite-muted); + font-family: var(--font-data); + font-size: 8px; +} + +.findings-panel article footer button { + display: flex; + align-items: center; + gap: 4px; + background: transparent; + color: var(--research-teal); + cursor: pointer; + font-size: 8px; +} + +.blocker-panel { + align-self: start; +} + +.blocker-item { + display: grid; + grid-template-columns: 24px minmax(0, 1fr); + gap: 9px; + padding: 12px 14px; + border-bottom: 1px solid var(--ledger); +} + +.blocker-item > span { + display: grid; + width: 22px; + height: 22px; + place-items: center; + border: 1px solid #e2b5af; + border-radius: 50%; + color: var(--critical); + font-family: var(--font-data); + font-size: 8px; +} + +.blocker-item p { + margin: 2px 0 0; + color: var(--graphite-soft); + font-size: 9px; + line-height: 1.55; +} + +.empty-compact { + display: flex; + align-items: center; + gap: 7px; + padding: 15px; + color: var(--success); + font-size: 9px; +} + +.audit-mini { + margin: 12px; + padding: 11px; + border-radius: var(--radius-sm); + background: #f3f5f1; +} + +.audit-mini header { + display: flex; + align-items: center; + gap: 6px; + margin-bottom: 8px; +} + +.audit-mini header strong { + font-size: 9px; +} + +.audit-mini ol { + margin: 0; + padding: 0; + list-style: none; +} + +.audit-mini li { + display: flex; + justify-content: space-between; + gap: 8px; + padding: 6px 0; + border-top: 1px solid var(--ledger); + font-size: 8px; +} + +.audit-mini time { + flex-shrink: 0; + color: var(--graphite-muted); +} + +.evidence-inspector { + z-index: 15; + min-width: 0; + height: 100vh; + overflow-y: auto; + border-left: 1px solid var(--ledger-strong); + background: #f3f5f0; +} + +.evidence-inspector > header { + display: flex; + height: 66px; + align-items: center; + justify-content: space-between; + padding: 0 15px; + border-bottom: 1px solid var(--ledger); +} + +.evidence-inspector > header h2 { + margin: 4px 0 0; + font-size: 13px; +} + +.inspector-scope { + display: flex; + flex-direction: column; + gap: 3px; + padding: 15px; + border-bottom: 1px solid var(--ledger); +} + +.inspector-scope span, +.inspector-scope small { + color: var(--graphite-muted); + font-size: 8px; +} + +.inspector-scope strong { + font-size: 11px; +} + +.inspector-scope small { + font-family: var(--font-data); +} + +.inspector-section { + margin: 11px; + padding: 12px; + border: 1px solid var(--ledger-strong); + border-radius: var(--radius-sm); + background: rgba(255, 255, 255, 0.62); +} + +.inspector-section > header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 10px; +} + +.inspector-section > header > strong { + font-size: 9px; +} + +.inspector-section > header > span:not(.status-chip):not(.evidence-grade) { + color: var(--graphite-muted); + font-family: var(--font-data); + font-size: 8px; +} + +.status-chip { + display: inline-flex; + height: 20px; + align-items: center; + padding: 0 7px; + border-radius: 10px; + font-family: var(--font-data); + font-size: 7px; + font-weight: 700; +} + +.status-chip.blocked { + background: var(--critical-soft); + color: var(--critical); +} + +.gate-detail, +.inspector-paper { + display: flex; + align-items: flex-start; + gap: 9px; +} + +.gate-detail > svg { + flex-shrink: 0; + color: var(--critical); +} + +.gate-detail strong, +.inspector-paper strong { + display: block; + font-size: 9px; + line-height: 1.4; +} + +.gate-detail p, +.inspector-paper p { + margin: 4px 0 0; + color: var(--graphite-muted); + font-size: 8px; + line-height: 1.5; +} + +.coverage-row { + display: grid; + grid-template-columns: 61px minmax(0, 1fr) 17px; + align-items: center; + gap: 7px; + margin-top: 8px; + font-size: 8px; +} + +.coverage-row > div { + height: 4px; + overflow: hidden; + border-radius: 2px; + background: var(--ledger); +} + +.coverage-row i { + display: block; + height: 100%; + background: var(--research-teal); +} + +.coverage-row.warning i { + background: var(--annotation); +} + +.coverage-row strong { + text-align: right; +} + +.inspector-paper > svg { + flex-shrink: 0; + color: var(--graphite-muted); +} + +.inline-warning { + display: flex; + align-items: center; + gap: 5px; + margin-top: 9px; + padding-top: 8px; + border-top: 1px solid var(--ledger); + color: var(--annotation); + font-size: 8px; +} + +.boundary-list { + display: flex; + flex-direction: column; + gap: 7px; + margin: 0; + padding: 0; + list-style: none; +} + +.boundary-list li { + display: flex; + align-items: center; + gap: 6px; + color: var(--graphite-soft); + font-size: 8px; +} + +.boundary-list li svg { + color: var(--success); +} + +.boundary-list li:nth-last-child(-n + 2) svg { + color: var(--annotation); +} + +.inspector-action { + display: flex; + width: calc(100% - 22px); + height: 34px; + align-items: center; + justify-content: center; + gap: 6px; + margin: 11px; + border: 1px solid var(--ledger-strong); + border-radius: var(--radius-sm); + background: var(--paper-raised); + color: var(--research-teal); + cursor: pointer; + font-size: 9px; + font-weight: 650; +} + +.mobile-evidence-button, +.mobile-stage-nav, +.mobile-inspector, +.mobile-drawer-backdrop { + display: none; +} + +.modal-backdrop { + z-index: 100; + position: fixed; + inset: 0; + display: grid; + place-items: center; + padding: 18px; + background: rgba(15, 22, 20, 0.35); + backdrop-filter: blur(3px); +} + +.paper-modal { + width: min(100%, 470px); + padding: 20px; + border: 1px solid var(--ledger-strong); + border-radius: var(--radius-md); + background: var(--paper-raised); + box-shadow: 0 12px 32px rgba(15, 22, 20, 0.14); +} + +.modal-heading { + display: flex; + align-items: flex-start; + justify-content: space-between; + margin-bottom: 18px; +} + +.modal-heading small { + color: var(--research-teal); + font-family: var(--font-data); + font-size: 9px; +} + +.modal-heading h2 { + margin: 4px 0 0; + font-size: 18px; +} + +.paper-modal label { + margin-top: 12px; +} + +.paper-modal input { + height: 39px; + padding: 0 11px; + border: 1px solid var(--ledger-strong); + border-radius: var(--radius-sm); + outline: 0; + font-size: 10px; +} + +.paper-modal input:focus { + border-color: var(--research-teal); +} + +.modal-boundary { + display: flex; + align-items: flex-start; + gap: 7px; + margin-top: 15px; + padding: 9px; + border-radius: var(--radius-sm); + background: var(--annotation-soft); + color: #754a1d; + font-size: 9px; + line-height: 1.5; +} + +.modal-boundary svg { + flex-shrink: 0; +} + +.modal-actions { + display: flex; + justify-content: flex-end; + gap: 8px; + margin-top: 18px; +} + +.toast { + z-index: 150; + position: fixed; + right: 18px; + bottom: 18px; + display: flex; + max-width: min(390px, calc(100vw - 36px)); + align-items: center; + gap: 8px; + padding: 11px 13px; + border: 1px solid var(--ledger-strong); + border-radius: var(--radius-sm); + background: var(--graphite); + color: #fff; + box-shadow: 0 8px 24px rgba(15, 22, 20, 0.18); + font-size: 10px; + line-height: 1.45; + animation: toast-in 180ms ease-out; +} + +.toast--success svg { + color: #89d2a8; +} + +.toast--error svg { + color: #f0a29c; +} + +.toast--neutral svg { + color: #e7bd79; +} + +.spin { + animation: spin 850ms linear infinite; +} + +@keyframes spin { + to { transform: rotate(360deg); } +} + +@keyframes pulse { + 50% { opacity: 0.35; transform: scale(0.8); } +} + +@keyframes toast-in { + from { opacity: 0; transform: translateY(7px); } +} + +@media (max-width: 1320px) { + :root { + --rail-width: 228px; + --inspector-width: 292px; + } + + .stage-nav button small, + .workspace-switcher span { + display: none; + } + + .writer-view { + grid-template-columns: 164px minmax(280px, 1fr) 226px; + } + + .revision-panel { + padding-right: 11px; + padding-left: 11px; + } + + .paper-list-head, + .paper-row { + grid-template-columns: minmax(270px, 1.7fr) minmax(120px, 0.7fr) 55px 78px; + gap: 9px; + } +} + +@media (max-width: 1080px) { + .app-shell, + .app-shell:has(.desktop-inspector) { + grid-template-columns: minmax(0, 1fr); + } + + .project-rail { + position: fixed; + top: 0; + bottom: 0; + left: 0; + width: min(290px, 88vw); + transform: translateX(-102%); + transition: transform 200ms ease; + box-shadow: 8px 0 28px rgba(15, 22, 20, 0.12); + } + + .project-rail--open { + transform: translateX(0); + } + + .mobile-close, + .mobile-menu { + display: inline-grid; + } + + .topbar { + justify-content: flex-start; + gap: 8px; + } + + .topbar-actions { + margin-left: auto; + } + + .desktop-inspector { + display: none; + } + + .inspector-toggle { + display: none; + } + + .mobile-evidence-button { + z-index: 30; + position: fixed; + right: 16px; + bottom: 18px; + display: flex; + height: 36px; + align-items: center; + gap: 6px; + padding: 0 11px; + border: 1px solid #b9d4cd; + border-radius: 18px; + background: var(--research-teal); + color: #fff; + box-shadow: 0 6px 18px rgba(0, 63, 58, 0.18); + font-size: 10px; + font-weight: 650; + } + + .mobile-evidence-button span { + display: grid; + min-width: 18px; + height: 18px; + place-items: center; + border-radius: 9px; + background: #fff; + color: var(--research-teal-dark); + font-family: var(--font-data); + font-size: 8px; + } + + .mobile-drawer-backdrop { + z-index: 80; + position: fixed; + inset: 0; + display: flex; + justify-content: flex-end; + background: rgba(15, 22, 20, 0.3); + } + + .mobile-inspector { + display: block; + width: min(330px, 92vw); + border-left: 1px solid var(--ledger-strong); + animation: drawer-in 180ms ease-out; + } + + @keyframes drawer-in { + from { transform: translateX(100%); } + } +} + +@media (max-width: 820px) { + body { + overflow: hidden; + } + + .workbench { + grid-template-rows: 52px 66px minmax(0, 1fr); + padding-bottom: 58px; + } + + .topbar { + padding: 0 12px; + } + + .breadcrumb > span, + .breadcrumb svg, + .guarded-badge { + display: none; + } + + .breadcrumb strong { + max-width: 48vw; + } + + .evidence-spine { + grid-template-columns: minmax(0, 1fr); + padding: 0 12px; + } + + .spine-title { + display: none; + } + + .spine-track button strong { + font-size: 8px; + } + + .idea-studio, + .library-view, + .review-view { + padding: 20px 16px 82px; + } + + .stage-heading, + .stage-heading.compact { + flex-direction: column; + gap: 10px; + align-items: stretch; + } + + .stage-heading h1 { + font-size: 25px; + } + + .start-button, + .stage-heading.compact > .button { + align-self: flex-start; + margin-top: 0; + } + + .message { + grid-template-columns: 30px minmax(0, 1fr); + gap: 9px; + } + + .message-avatar { + width: 30px; + height: 30px; + } + + .conversation-ledger::before { + left: 14px; + } + + .idea-composer { + margin-left: 39px; + } + + .library-toolbar { + flex-wrap: wrap; + } + + .search-field { + width: 100%; + flex-basis: 100%; + } + + .filter-tabs { + flex: 1; + } + + .filter-tabs button { + flex: 1; + justify-content: center; + } + + .paper-list-head { + display: none; + } + + .paper-row { + grid-template-columns: minmax(0, 1fr) auto; + gap: 9px; + min-height: 96px; + } + + .paper-title-cell { + grid-column: 1 / 2; + } + + .paper-topics { + grid-column: 1 / 2; + padding-left: 42px; + } + + .paper-row > div:nth-child(3) { + position: absolute; + top: 12px; + right: 12px; + } + + .queue-cell { + grid-column: 2; + grid-row: 2; + } + + .reader-view { + display: block; + } + + .reader-library { + padding: 12px; + border-right: 0; + border-bottom: 1px solid var(--ledger); + } + + .reader-library > header, + .mini-search { + display: none; + } + + .reader-paper-list { + flex-direction: row; + overflow-x: auto; + padding-bottom: 3px; + } + + .reader-paper-list button { + width: 190px; + flex: 0 0 190px; + } + + .reading-paper { + padding: 22px 18px 90px; + } + + .reading-title h1 { + font-size: 23px; + } + + .writer-view { + display: block; + } + + .outline-panel { + padding: 9px 12px; + overflow-x: auto; + border-right: 0; + border-bottom: 1px solid var(--ledger); + } + + .outline-panel > header, + .outline-summary { + display: none; + } + + .outline-list { + flex-direction: row; + margin: 0; + } + + .outline-list button { + min-width: max-content; + grid-template-columns: auto auto; + } + + .outline-list button small, + .outline-list button em { + display: none; + } + + .editor-panel { + min-height: calc(100vh - 176px); + } + + .editor-toolbar { + padding: 0 10px; + } + + .save-indicator { + display: none; + } + + .editor-canvas { + padding: 12px 10px 80px; + } + + .document-page { + min-height: 620px; + padding: 35px 24px 24px; + } + + .document-title { + font-size: 22px; + } + + .revision-panel { + border-top: 1px solid var(--ledger-strong); + border-left: 0; + padding: 17px 16px 90px; + } + + .review-overview { + grid-template-columns: 1fr 1fr 1fr; + } + + .gate-card { + grid-column: 1 / -1; + } + + .review-stat { + min-height: 92px; + padding: 11px; + } + + .review-stat strong { + font-size: 20px; + } + + .review-columns { + grid-template-columns: 1fr; + } + + .mobile-stage-nav { + z-index: 35; + position: fixed; + right: 0; + bottom: 0; + left: 0; + display: grid; + height: 58px; + grid-template-columns: repeat(5, 1fr); + border-top: 1px solid var(--ledger-strong); + background: rgba(251, 252, 248, 0.97); + backdrop-filter: blur(12px); + } + + .mobile-stage-nav button { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 3px; + background: transparent; + color: var(--graphite-muted); + font-size: 8px; + } + + .mobile-stage-nav button.active { + color: var(--research-teal); + } + + .mobile-stage-nav button.active::before { + position: absolute; + top: 0; + width: 28px; + height: 2px; + background: var(--research-teal); + content: ""; + } + + .mobile-evidence-button { + right: 10px; + bottom: 67px; + } + + .toast { + right: 12px; + bottom: 72px; + } +} + +@media (max-width: 520px) { + .evidence-spine { + overflow: hidden; + } + + .spine-track button strong { + display: none; + } + + .spine-track::before, + .spine-progress { + top: 11px; + } + + .topbar-actions .icon-button:not(.mobile-menu), + .avatar-button { + display: none; + } + + .stage-heading h1 { + font-size: 23px; + } + + .idea-composer { + margin-left: 0; + } + + .review-overview { + grid-template-columns: 1fr 1fr; + } + + .review-stat:last-child { + grid-column: 1 / -1; + } + + .gate-card { + align-items: flex-start; + } + + .gate-seal { + width: 40px; + height: 40px; + } + + .document-page footer { + flex-wrap: wrap; + } +} + +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + scroll-behavior: auto !important; + transition-duration: 0.01ms !important; + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + } +} diff --git a/web/app/layout.tsx b/web/app/layout.tsx new file mode 100644 index 0000000..a7d0838 --- /dev/null +++ b/web/app/layout.tsx @@ -0,0 +1,15 @@ +import type { Metadata } from "next"; +import "./globals.css"; + +export const metadata: Metadata = { + title: "AutoResearch · 研究工作台", + description: "Evidence-aware research workbench for AutoResearch", +}; + +export default function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) { + return ( + + {children} + + ); +} diff --git a/web/app/page.tsx b/web/app/page.tsx new file mode 100644 index 0000000..4e77182 --- /dev/null +++ b/web/app/page.tsx @@ -0,0 +1,5 @@ +import { ResearchWorkbench } from "@/components/research-workbench"; + +export default function Home() { + return ; +} diff --git a/web/components/research-workbench.test.tsx b/web/components/research-workbench.test.tsx new file mode 100644 index 0000000..e68093c --- /dev/null +++ b/web/components/research-workbench.test.tsx @@ -0,0 +1,73 @@ +import { cleanup, fireEvent, render, screen, waitFor } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; + +import { ResearchWorkbench } from "@/components/research-workbench"; + +describe("ResearchWorkbench", () => { + beforeEach(() => { + window.localStorage.clear(); + vi.stubGlobal( + "fetch", + vi.fn().mockResolvedValue( + new Response( + JSON.stringify({ + ok: true, + version: "0.2.0.dev0", + agents: ["orchestrator", "paper_search", "paper_reader", "writer", "reviewer"], + }), + { status: 200, headers: { "Content-Type": "application/json" } }, + ), + ), + ); + }); + + afterEach(() => { + cleanup(); + vi.unstubAllGlobals(); + }); + + it("moves through the research stages and exposes an actionable empty state", async () => { + const user = userEvent.setup(); + render(); + + await user.click(screen.getByRole("button", { name: /02.*论文搜集.*检索/ })); + expect(screen.getByRole("heading", { name: "论文搜集" })).toBeInTheDocument(); + + await user.type(screen.getByPlaceholderText("按标题、主题或方法筛选…"), "不存在的检索词"); + expect(screen.getByText("没有符合条件的论文")).toBeInTheDocument(); + expect(screen.getByRole("button", { name: "清除筛选" })).toBeEnabled(); + }); + + it("keeps E0 paper questions unresolved instead of inventing an answer", async () => { + const user = userEvent.setup(); + render(); + + await user.click(screen.getByRole("button", { name: /03.*论文阅读.*定位证据/ })); + const question = screen.getByLabelText("针对论文提问"); + await user.type(question, "这篇论文支持什么事件顺序?"); + fireEvent.submit(question.closest("form")!); + + expect(await screen.findByText(/问题未发送:这是一条 E0 预览题录/)).toBeInTheDocument(); + expect(screen.getByText("unresolved")).toBeInTheDocument(); + }); + + it("autosaves live manuscript edits only to browser storage", async () => { + const user = userEvent.setup(); + render(); + + await user.click(screen.getByRole("button", { name: /04.*论文写作.*章节草稿/ })); + const editor = screen.getByLabelText("论文正文编辑器"); + await user.clear(editor); + await user.type(editor, "这是一次本地实时修改。" ); + + await waitFor( + () => { + const saved = window.localStorage.getItem("autoresearch:ar006:manuscript-draft"); + expect(saved).toContain("这是一次本地实时修改"); + }, + { timeout: 1800 }, + ); + expect(screen.getByText("已保存到浏览器")).toBeInTheDocument(); + }); +}); diff --git a/web/components/research-workbench.tsx b/web/components/research-workbench.tsx new file mode 100644 index 0000000..3a98d6a --- /dev/null +++ b/web/components/research-workbench.tsx @@ -0,0 +1,817 @@ +"use client"; + +import { + AlertCircle, + ArrowRight, + BookOpen, + Bot, + Check, + CheckCircle2, + ChevronDown, + ChevronRight, + CircleDot, + Clock3, + FilePenLine, + FileSearch, + FlaskConical, + FolderKanban, + Gauge, + GitBranch, + Highlighter, + Library, + Link2, + ListFilter, + LoaderCircle, + Menu, + MessageSquareText, + MoreHorizontal, + PanelRightClose, + PanelRightOpen, + PenLine, + Plus, + Quote, + RefreshCw, + Save, + Search, + Send, + Settings2, + ShieldCheck, + Sparkles, + Tags, + TextCursorInput, + X, +} from "lucide-react"; +import { FormEvent, useEffect, useMemo, useRef, useState } from "react"; + +import { api, ApiError, errorMessage } from "@/lib/api"; +import { + INITIAL_MESSAGES, + INITIAL_SECTIONS, + PREVIEW_PAPERS, + REVIEW_FINDINGS, + STAGES, +} from "@/lib/demo-data"; +import type { + BackendHealth, + ConnectionState, + ManuscriptResponse, + ManuscriptSection, + ReadingAnswer, + ResearchMessage, + ResearchRun, + SeedPaper, + StageId, +} from "@/lib/types"; + +const PROJECT_ID = "md-metal-ductile-damage-pilot"; +const PROJECT_TITLE = "金属韧性损伤分子动力学机理分析"; +const STORAGE_KEY = "autoresearch:ar006:manuscript-draft"; + +type Notice = { tone: "success" | "error" | "neutral"; text: string } | null; +type SaveState = "saved" | "saving" | "changed"; + +function nowTime() { + return new Intl.DateTimeFormat("zh-CN", { hour: "2-digit", minute: "2-digit" }).format( + new Date(), + ); +} + +function shortId(value: string) { + return value.length > 17 ? `${value.slice(0, 9)}…${value.slice(-5)}` : value; +} + +function normalizeManuscript(manuscript: ManuscriptResponse): ManuscriptSection[] { + return Object.entries(manuscript.sections).map(([key, body], index) => ({ + id: key, + label: `${index + 1}. ${key}`, + shortLabel: key, + body, + citations: manuscript.evidence_ids.length, + gaps: manuscript.unresolved_gaps.length, + })); +} + +function connectionLabel(connection: ConnectionState, health: BackendHealth | null) { + if (connection === "checking") return "正在检查 API"; + if (connection === "online") return `API ${health?.version ?? "已连接"}`; + return "离线预览"; +} + +export function ResearchWorkbench() { + const [activeStage, setActiveStage] = useState("idea"); + const [connection, setConnection] = useState("checking"); + const [health, setHealth] = useState(null); + const [messages, setMessages] = useState(INITIAL_MESSAGES); + const [ideaInput, setIdeaInput] = useState(""); + const [researchIdea, setResearchIdea] = useState(INITIAL_MESSAGES[0].body); + const [papers, setPapers] = useState(PREVIEW_PAPERS); + const [queuedPaperIds, setQueuedPaperIds] = useState>(new Set()); + const [paperQuery, setPaperQuery] = useState(""); + const [selectedPaperId, setSelectedPaperId] = useState(PREVIEW_PAPERS[0].paper_id); + const [paperFilter, setPaperFilter] = useState<"all" | "queued" | "indexed">("all"); + const [showAddPaper, setShowAddPaper] = useState(false); + const [newPaperTitle, setNewPaperTitle] = useState(""); + const [newPaperSource, setNewPaperSource] = useState(""); + const [readingQuestion, setReadingQuestion] = useState(""); + const [readingAnswers, setReadingAnswers] = useState([]); + const [askingPaper, setAskingPaper] = useState(false); + const [sections, setSections] = useState(INITIAL_SECTIONS); + const [activeSectionId, setActiveSectionId] = useState(INITIAL_SECTIONS[0].id); + const [saveState, setSaveState] = useState("saved"); + const [draftHydrated, setDraftHydrated] = useState(false); + const [versionCount, setVersionCount] = useState(3); + const [revisionInstruction, setRevisionInstruction] = useState(""); + const [revisionMode, setRevisionMode] = useState<"revise" | "polish">("revise"); + const [isRevising, setIsRevising] = useState(false); + const [run, setRun] = useState(null); + const [isStarting, setIsStarting] = useState(false); + const [notice, setNotice] = useState(null); + const [inspectorOpen, setInspectorOpen] = useState(true); + const [mobileNavOpen, setMobileNavOpen] = useState(false); + const [mobileInspectorOpen, setMobileInspectorOpen] = useState(false); + const noticeTimer = useRef(null); + + const selectedPaper = papers.find((paper) => paper.paper_id === selectedPaperId) ?? papers[0]; + const activeSection = + sections.find((section) => section.id === activeSectionId) ?? sections[0]; + const queuedPapers = papers.filter((paper) => queuedPaperIds.has(paper.paper_id)); + const eligibleSeedPapers = queuedPapers.filter((paper) => paper.evidenceGrade !== "E0"); + const filteredPapers = useMemo(() => { + const query = paperQuery.trim().toLowerCase(); + return papers.filter((paper) => { + const matchesQuery = + !query || + paper.title.toLowerCase().includes(query) || + paper.topics.some((topic) => topic.toLowerCase().includes(query)); + const matchesFilter = + paperFilter === "all" || + (paperFilter === "queued" && queuedPaperIds.has(paper.paper_id)) || + (paperFilter === "indexed" && paper.evidenceGrade !== "E0"); + return matchesQuery && matchesFilter; + }); + }, [paperFilter, paperQuery, papers, queuedPaperIds]); + + const runEvidenceCount = run?.state.evidence_ids?.length ?? 0; + const unresolvedGaps = sections.reduce((sum, section) => sum + section.gaps, 0); + + function showNotice(next: Exclude) { + setNotice(next); + if (noticeTimer.current) window.clearTimeout(noticeTimer.current); + noticeTimer.current = window.setTimeout(() => setNotice(null), 4200); + } + + async function checkHealth() { + try { + const result = await api.health(); + setHealth(result); + setConnection(result.ok ? "online" : "offline"); + } catch { + setHealth(null); + setConnection("offline"); + } + } + + useEffect(() => { + const healthTimer = window.setTimeout(() => void checkHealth(), 0); + return () => { + window.clearTimeout(healthTimer); + if (noticeTimer.current) window.clearTimeout(noticeTimer.current); + }; + }, []); + + useEffect(() => { + const draftTimer = window.setTimeout(() => { + try { + const draft = window.localStorage.getItem(STORAGE_KEY); + if (draft) { + const parsed = JSON.parse(draft) as ManuscriptSection[]; + if (Array.isArray(parsed) && parsed.length) setSections(parsed); + } + } catch { + // A malformed local draft is ignored; the checked-in preview remains available. + } finally { + setDraftHydrated(true); + } + }, 0); + return () => window.clearTimeout(draftTimer); + }, []); + + useEffect(() => { + if (!draftHydrated) return; + const timer = window.setTimeout(() => { + window.localStorage.setItem(STORAGE_KEY, JSON.stringify(sections)); + setSaveState("saved"); + }, 700); + return () => window.clearTimeout(timer); + }, [draftHydrated, sections]); + + function addIdeaMessage(event: FormEvent) { + event.preventDefault(); + const body = ideaInput.trim(); + if (!body) return; + setMessages((current) => [ + ...current, + { id: `msg-${Date.now()}`, role: "researcher", author: "你", time: nowTime(), body }, + { + id: `msg-system-${Date.now()}`, + role: "system", + author: "本地工作区", + time: nowTime(), + body: "这条补充已加入当前 idea 草稿。点击“启动研究链”后才会提交到受控工作流。", + }, + ]); + setResearchIdea((current) => `${current}\n${body}`); + setIdeaInput(""); + } + + async function startResearch() { + if (connection !== "online") { + showNotice({ tone: "error", text: "API 未连接:请先启动 FastAPI,再提交研究任务。" }); + return; + } + setIsStarting(true); + try { + try { + await api.createProject({ project_id: PROJECT_ID, title: PROJECT_TITLE, idea: researchIdea }); + } catch (error) { + if (!(error instanceof ApiError && error.status === 409)) throw error; + } + const nextRun = await api.startRun({ + project_id: PROJECT_ID, + idea: researchIdea, + seed_papers: eligibleSeedPapers, + }); + setRun(nextRun); + const existingIds = new Set(papers.map((paper) => paper.paper_id)); + const indexed = (nextRun.state.paper_ids ?? []) + .filter((paperId) => !existingIds.has(paperId)) + .map((paperId) => ({ + paper_id: paperId, + project_id: PROJECT_ID, + title: `已入库题录 · ${shortId(paperId)}`, + abstract: "题录已由 AutoResearch 写入 evidence store;当前 API 尚未提供题录列表读取,详细元数据保持未知。", + authors: [], + source: "AutoResearch evidence store", + status: "indexed", + evidenceGrade: "E1", + topics: ["已入库", "元数据待读取"], + })); + if (indexed.length) { + setPapers((current) => [...current, ...indexed]); + setSelectedPaperId(indexed[0].paper_id); + } + if (nextRun.state.manuscript_id) { + try { + const manuscript = await api.getManuscript(nextRun.state.manuscript_id); + setSections(normalizeManuscript(manuscript)); + setActiveSectionId(Object.keys(manuscript.sections)[0]); + } catch { + // The run result still remains valid even if the follow-up read fails. + } + } + const isBlocked = nextRun.status === "blocked" || (nextRun.state.blockers?.length ?? 0) > 0; + showNotice({ + tone: isBlocked ? "neutral" : "success", + text: isBlocked ? "工作流已返回 blocked;阻塞原因已进入审核面板。" : "研究链已启动并返回运行记录。", + }); + } catch (error) { + showNotice({ tone: "error", text: errorMessage(error) }); + } finally { + setIsStarting(false); + } + } + + function togglePaperQueue(paperId: string) { + setQueuedPaperIds((current) => { + const next = new Set(current); + if (next.has(paperId)) next.delete(paperId); + else next.add(paperId); + return next; + }); + } + + function addPaper(event: FormEvent) { + event.preventDefault(); + if (!newPaperTitle.trim() || !newPaperSource.trim()) return; + const paper: SeedPaper = { + paper_id: `local-${Date.now()}`, + project_id: PROJECT_ID, + title: newPaperTitle.trim(), + abstract: "用户录入的本地题录候选。正式作为证据前仍需后端核验来源与 locator。", + authors: [], + source: newPaperSource.trim(), + status: "preview", + evidenceGrade: "E0", + topics: ["手工录入", "待核验"], + }; + setPapers((current) => [paper, ...current]); + setSelectedPaperId(paper.paper_id); + setNewPaperTitle(""); + setNewPaperSource(""); + setShowAddPaper(false); + showNotice({ tone: "neutral", text: "题录已加入本地候选,保持 E0,尚未写入证据库。" }); + } + + async function askSelectedPaper(event: FormEvent) { + event.preventDefault(); + const question = readingQuestion.trim(); + if (!question || !selectedPaper) return; + if (connection !== "online" || selectedPaper.evidenceGrade === "E0") { + setReadingAnswers((current) => [ + ...current, + { + answer_id: `local-${Date.now()}`, + paper_id: selectedPaper.paper_id, + question, + answer: + selectedPaper.evidenceGrade === "E0" + ? "问题未发送:这是一条 E0 预览题录,尚无可定位的论文证据。" + : "问题未发送:AutoResearch API 当前不可达。", + locators: [], + evidence_ids: [], + confidence: 0, + unresolved: true, + }, + ]); + setReadingQuestion(""); + return; + } + setAskingPaper(true); + try { + const answer = await api.askPaper(selectedPaper.paper_id, question); + setReadingAnswers((current) => [...current, answer]); + setReadingQuestion(""); + } catch (error) { + showNotice({ tone: "error", text: errorMessage(error) }); + } finally { + setAskingPaper(false); + } + } + + function updateSection(body: string) { + setSaveState("changed"); + setSections((current) => + current.map((section) => (section.id === activeSection.id ? { ...section, body } : section)), + ); + } + + function createVersion() { + window.localStorage.setItem(`${STORAGE_KEY}:v${versionCount + 1}`, JSON.stringify(sections)); + setVersionCount((current) => current + 1); + showNotice({ tone: "success", text: "已创建浏览器本地版本快照;未写入服务端。" }); + } + + async function requestRevision() { + const manuscriptId = run?.state.manuscript_id; + if (!manuscriptId) { + showNotice({ tone: "error", text: "当前没有服务端 manuscript ID,无法请求受控修订。" }); + return; + } + if (!revisionInstruction.trim()) { + showNotice({ tone: "error", text: "请先填写修订要求。" }); + return; + } + setIsRevising(true); + try { + const manuscript = await api.reviseManuscript( + manuscriptId, + revisionInstruction.trim(), + revisionMode, + ); + setSections(normalizeManuscript(manuscript)); + setActiveSectionId(Object.keys(manuscript.sections)[0]); + setRevisionInstruction(""); + setVersionCount((current) => current + 1); + showNotice({ tone: "success", text: "已生成新稿件版本;release Gate 仍由后端决定。" }); + } catch (error) { + showNotice({ tone: "error", text: errorMessage(error) }); + } finally { + setIsRevising(false); + } + } + + function navigate(stage: StageId) { + setActiveStage(stage); + setMobileNavOpen(false); + } + + return ( +

+ + +
+
+ +
+ 研究项目{PROJECT_TITLE} +
+
+
证据受控
+ + + +
+
+ + + +
+ {activeStage === "idea" && ( + void startResearch()} + starting={isStarting} + run={run} + connection={connection} + /> + )} + {activeStage === "library" && ( + setShowAddPaper(true)} + eligibleCount={eligibleSeedPapers.length} + onRead={() => setActiveStage("reader")} + /> + )} + {activeStage === "reader" && ( + answer.paper_id === selectedPaper?.paper_id)} + question={readingQuestion} + onQuestion={setReadingQuestion} + onAsk={askSelectedPaper} + asking={askingPaper} + onWrite={() => setActiveStage("writer")} + /> + )} + {activeStage === "writer" && activeSection && ( + void requestRevision()} + revising={isRevising} + hasServerManuscript={Boolean(run?.state.manuscript_id)} + /> + )} + {activeStage === "review" && ( + setActiveStage("writer")} /> + )} +
+
+ + {inspectorOpen && ( + setInspectorOpen(false)} + className="desktop-inspector" + /> + )} + + + + + + {mobileInspectorOpen && ( +
setMobileInspectorOpen(false)}> + setMobileInspectorOpen(false)} + className="mobile-inspector" + /> +
+ )} + + {showAddPaper && ( +
setShowAddPaper(false)}> +
event.stopPropagation()}> +
+
本地候选

添加论文题录

+ +
+ + +
新题录先保存为 E0。本地录入不代表来源已核验或全文已授权。
+
+
+
+ )} + + {notice &&
{notice.tone === "success" ? : notice.tone === "error" ? : }{notice.text}
} +
+ ); +} + +function StageIcon({ stage, size = 16 }: { stage: StageId; size?: number }) { + const props = { size, strokeWidth: 1.8 }; + if (stage === "idea") return ; + if (stage === "library") return ; + if (stage === "reader") return ; + if (stage === "writer") return ; + return ; +} + +function EvidenceSpine({ activeStage, run, onNavigate }: { activeStage: StageId; run: ResearchRun | null; onNavigate: (stage: StageId) => void }) { + const activeIndex = STAGES.findIndex((stage) => stage.id === activeStage); + return ( +
+
研究证据链{run ? `RUN ${shortId(run.run_id)}` : "尚未启动 run"}
+
+
+ {STAGES.map((stage, index) => { + const isActive = stage.id === activeStage; + const isPast = index < activeIndex; + const isBlocked = stage.id === "review" && Boolean(run?.state.blockers?.length); + return ( + + ); + })} +
+
+ ); +} + +function IdeaStudio({ messages, input, onInput, onSubmit, onStart, starting, run, connection }: { + messages: ResearchMessage[]; + input: string; + onInput: (value: string) => void; + onSubmit: (event: FormEvent) => void; + onStart: () => void; + starting: boolean; + run: ResearchRun | null; + connection: ConnectionState; +}) { + return ( +
+
+
Research intake

把一个想法,变成可检验的问题

先澄清未知项和停止条件,再把任务交给研究链。这里的对话不会自动成为证据。

+ +
+ + {run &&
运行 {shortId(run.run_id)}
{run.status}{run.state.lifecycle_state ?? "状态未知"}
} + +
+
今天
+ {messages.map((message) => ( +
+
{message.role === "researcher" ? "你" : message.role === "agent" ? : }
+
{message.author}

{message.body}

{message.tags &&
{message.tags.map((tag) => {tag})}
}
+
+ ))} +
+ +
+