Conversation
背景:解析完成后、入库前的人工复核目前只能删掉重传再解析。本 PR 在既有的单文件操作
菜单(下载 / 解析 / 入库 / 重新入库 / 删除)里新增「编辑文件」,复用既有 parsed 状态
(前端该状态文案就是「待入库」),不新增状态、不引入编辑器依赖:编辑区是原生 textarea
+项目既有的 MarkdownPreview 左右分栏,预览与最终展示同一套渲染栈。
范围只放开 parsed:该状态没有派生索引,产物对象就是权威内容,覆盖写回确定性路径
{kb_id}/parsed/{file_id}.md 即发布,状态保持 parsed,用户继续走既有「入库」。已入库
内容的编辑不在本阶段——那必须复用「重新入库」Durable Task,由任务取得 ownership 后
再切换权威内容;在同步接口里另实现一套清理会留下「接口返回 409/500,但新产物已持久化、
文件仍是 indexed + 旧向量」的组合。
并发把期望版本做成落库条件:revision 是文件行的 updated_at(随内容读取一起返回、保存
原样回传),与允许状态一起构成同一条 UPDATE 的等值条件(update_fields_if_status 新增
expected_updated_at)。任何对文件的写入都会推进 updated_at,因此两个并发保存只有一条
能命中、另一条 409;编辑期间解析/入库推进状态或版本同样让条件落空。校验与发布因此是
原子步骤。顺序为先条件更新、再覆盖产物:条件落空时产物尚未写入。
验证:后端单测 18 条;test/unit/knowledge 193 条通过(另 1 条失败是临时验证目录缺
uv.lock 的环境问题);新增一条在 CI 内执行的真实 PostgreSQL 回归
(test/integration/services/test_durable_task_repository.py,隔离 schema,无需凭据),
覆盖「正确版本命中并推进 updated_at / 过期版本落空 / data 为空仍校验过滤条件」,
删掉版本条件或恢复短路这两个变异都会让它以正确原因失败;该文件全量 20 条通过。
真实 HTTP 集成用例(含 asyncio.gather 两个同时提交的保存只允许一个 200)已写好,
但知识库 HTTP 集成套件不在 CI 且本机缺 TEST_USERNAME/TEST_PASSWORD,标记为未执行。
前端单测与浏览器脚本相应更新;新增决策记录(含被否掉的「读产物算哈希」方案)。
OIDC 只把会话写回 172.25.104.79:5173,localhost:5173 是另一个来源(localStorage 不共享), 在那里跑脚本会停在登录页。
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
这是 #1035 的按建议拆分版:只做「待入库(
parsed)解析产物的编辑」这一条闭环,#1035 中的已入库编辑与索引清理已全部移除。变更说明
背景:解析完成后、入库前的人工复核目前只能看、不能改,发现解析问题只能删掉重传重跑解析。本 PR 在既有的单文件操作菜单(下载 / 解析 / 入库 / 重新入库 / 删除)中新增**「编辑文件」**:
复用既有
parsed状态(前端该状态文案就是「待入库」),不新增状态;保存只覆盖解析产物对象,状态保持
parsed,用户继续走既有「入库」流程;不引入编辑器依赖:编辑区是原生
textarea+ 项目既有的MarkdownPreview左右分栏,预览与最终展示、被检索引用时是同一套渲染栈。任务类型:
feature目标:待入库的解析产物可人工修正。
非目标:不做已入库文件的编辑(那必须复用既有「重新入库」Durable Task,由任务取得 ownership 后再切换权威内容,属后续独立 PR);不做版本历史、审计流水、协同编辑;不对外部 API 或 Agent 工具暴露该写入口(保持「Agent 只读知识库」)。
substantial / trivial 判断:非 trivial(新增 HTTP 端点、新增持久内容发布点与并发控制、新增前端交互)。决策记录:
docs/develop-guides/decisions/implemented/2026-09-18-parsed-only-markdown-edit.md。工程主张与 Owner
parsed文件的产物可被覆盖,状态不变。 Owner:KnowledgeBase.update_file_markdown;commit point:{kb_id}/parsed/{file_id}.md的覆盖写;观察边界:knowledge_files.status仍是parsed,内容视图读到新产物。该状态没有派生索引,产物对象就是权威内容,覆盖即发布,因此不需要草稿/版本切换。KnowledgeFileRepository.update_fields_if_status(expected_updated_at=...)。期望版本(文件行的updated_at)与允许状态构成同一条 UPDATE 的等值条件,所以「校验 + 发布」是原子步骤;另一个请求拿到None→ 409。编辑期间解析/入库推进状态或版本同样让条件落空。EDITABLE_MARKDOWN_STATUSES = {parsed}与前端canEditParsedContent(同集合);已入库文件仍可预览分块,但没有编辑入口。require_knowledge_base_manage+_ensure_database_supports_documents(只读连接器在此拦下,403 先于 404/400)。验证情况
主张 1:待入库文件保存后产物换新、状态不变
knowledge_files.status/ MinIO 解析产物对象| 42.0 |,保存后接口回读为| 4.2 |且旧值消失、basic仍为parsed、响应回传的新版本可用于再次保存test_edit_parsed_document_writes_content_and_keeps_status(真实 HTTP + PostgreSQL + MinIO)test_待入库文件保存后状态保持待入库主张 2:两个同时提交的保存只有一个能命中
update_fields_if_status的expected_updated_at等值条件UPDATE … WHERE status='parsed' AND updated_at=<同一期望版本>→ 结果['editA: 命中', 'editB: 落空(409)'],落库为 editAupdated_at(onupdate生效,版本会推进),WHERE 同时含状态与版本test/integration/services/test_durable_task_repository.py::test_update_fields_if_status_requires_matching_expected_version(隔离 schema 的真实 PostgreSQL)——正确版本命中并推进版本、过期版本落空、data为空时仍逐条校验过滤条件。两个变异(删掉版本等值条件、恢复「data为空即按 id 取记录」的短路)都会让它以正确原因失败test_concurrent_edits_leave_exactly_one_winner(asyncio.gather两个同时提交的保存,断言[200, 409])——见「未验证范围」,本机未执行test_版本落空时返回冲突且不写产物Not run(HTTP 并发用例)主张 3:保存顺序是先条件更新再覆盖产物
test_保存顺序是先条件更新再覆盖产物(断言调用序列["cas","save"])、test_版本落空时返回冲突且不写产物(断言事件只有cas)主张 4:已入库内容不能从这个接口改
EDITABLE_MARKDOWN_STATUSESindexed/error_indexing/done/error_parsing;集成测试test_edit_rejects_indexed_document_and_keeps_chunks(断言分块数与文件版本均未变)Not run(HTTP 用例)主张 5:接口的错误码与权限
test_edit_document_rejects_invalid_requests与test_edit_document_requires_manage_permission(403 且版本未推进)Not run(HTTP 用例)主张 6:前端可编辑集合与后端同集合,保存携带版本
web/test/unit/knowledge_markdown_edit.test.js(可编辑集合只含 parsed;已入库文件仍可预览分块但无编辑入口;保存请求体带revision)主张 7:前端编辑交互不静默丢草稿、放弃不改状态
web/test/browser/parsedMarkdownEdit.js(仓库既有run-code约定):筛选「待入库」后入口可用并能进入编辑态、未保存草稿时 ESC 弹确认而非静默关闭、放弃后该文件仍在「待入库」简化 / 删除验收
本 PR 相对 #1035 是做减法:删除
purge_indexed_chunks钩子与其 Milvus 覆写、was_indexed分支、前后端「保存会清索引」的交互与状态集合、以及为此收窄的 CAS 语义。负向搜索确认全仓已无purge_indexed_chunks/was_indexed/willPurgeIndexOnSave引用(milvus.py因此回到与 main 一致)。重新引入的条件是第二阶段的已入库编辑,届时走「重新入库」任务而不是本地清理。独立语义 Review
两轮全新上下文的独立 Reviewer 覆盖需求、完整 diff、测试与规范:
operator_id非空(data为空时 repository 会短路成按 id 取记录)、前端存在「有入口但必然保存失败」的组合。onupdate推进版本、统计刷新不影响回传的版本」;同时指出该 guard 当时没有任何自动回归(唯一覆盖它的 HTTP 集成用例不在 CI),据此补了 CI 内执行的 repository 层回归(含两个变异校验)。第二轮另指出 GET 侧两次查询之间的窗口、权限用例断言被弱化、朴素时间语义注释缺失——均已修。未验证范围与风险
Not run,且它不在 CI 里:本机未配置TEST_USERNAME/TEST_PASSWORD(conftest.py会 skip),而.github/workflows/system-tests.yml是逐条点名 integration 文件、不跑全量,test/integration/api/test_knowledge_router.py未被点名。因此本 PR 中依赖它的断言([200, 409]并发用例、403、已入库被拒、MinIO 回读)目前只有「写好的证据」。为避免核心 guard 零回归,并发语义的自动回归已放到 CI 内执行的test/integration/services/test_durable_task_repository.py(见主张 2)。isEditing/draftChanged等只在浏览器脚本层覆盖;仓库没有 jsdom / happy-dom / @vue/test-utils,未擅自引入测试依赖。updated_at:由应用创建的记录恒有(列默认值);手工插入的历史数据若缺该字段,编辑入口不会出现(fail-closed,不会静默覆盖)。updated_at作版本的碰撞窗口:两条写入落在同一微秒的理论窗口存在(每次条件更新都走一次数据库往返,正常时序不可达);换计数器列可彻底消除,但需要新列与历史数据回填。界面变更
文件行操作菜单新增「编辑文件」(仅待入库文件);进入编辑态后左侧源码、右侧实时预览、两侧按滚动比例同步;有未保存草稿时关闭需确认。
截图取自一个临时演示库(库名与文件名均为演示用假数据),画面只包含行菜单与弹窗本体:
保存后(产物已换新,文件仍在「待入库」,可继续走既有「入库」):
关联事项
上一版为 #1035(同时支持 parsed 与已入库编辑),按维护者意见关闭并拆分:本 PR 只做第一阶段,已入库编辑 + 重新入库发布时点留待第二阶段。无关联 Issue。
补充说明
implemented记录,并列出并发控制这一处仍有替代项的取舍(含被否掉的 sha256 方案与原因)。docker/nginx/default.conf的client_max_body_size为 20M(server 级硬墙),且 JSON 转义会膨胀(换行 →\n两字节),故取 5 MiB 留余量;将来调高必须同步改 nginx 配置。_ensure_database_supports_documents在路由层拦下(400)。