Files
姚章浩 c27a0c14cc
build / build (push) Successful in 1m22s
fix(library): 基线契约读目标真实 schema/模型,演练标内容风险
真实演练暴露两个只有跑起来才看得见的问题:

1. 归档要求完整兼容性契约(v0.7.1 / schema 74 / elasticsearch_v8 / 1024 维 /
   映射摘要),而基线装配只填了 embedding 与映射,捕获被
   "unsupported snapshot compatibility contract" 拒绝。
   现在 schema 版本读目标库 schema_migrations(dirty 或版本不符直接拒),
   embedding 优先读目标库模型绑定(本机目标 models 表为空、引用悬空,
   此时回落部署声明并说清来源),版本/引擎用受支持栈常量,映射取目标当前值。
2. 演练在"还没写目标内容"时就允许 restore_blocked=false 的窗口以 blocked 收尾,
   写出 content_risk=false + restore_blocked=true 的非法 journal(恢复器拒绝加载,
   目标停在停机态)。现在写 canary 时先 MarkContentRisk,allowRestore 按
   "没碰内容 ⇒ 安全恢复;碰过 ⇒ 必须恢复并校验通过"。

另外给演练工具加 -recover:只把非终态窗口按原运行状态恢复,用于演练失败后
把目标救回来(不改治理记录)。

验证:库内模拟真实目标不可得,契约分支用真实 kgseal 演练覆盖:
snapshot 5cb945e6… 捕获 143ms → 基线一致 → canary 后校验如预期失败
(knowledges 1 行摘要不一致)→ 恢复后校验通过 → 窗口 succeeded、服务与入口恢复。
2026-10-29 19:41:12 +08:00

1579 lines
124 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Changelog
## 开发快照(基线崩溃续作、维护原状态保留与真实恢复演练工具)
### Fixed
- **基线契约按"能验证的来源"填充**:归档要求完整的兼容性契约(v0.7.1 / schema 74 /
elasticsearch_v8 / 1024 维 / 映射摘要)。原先只填了 embedding 与映射,捕获会被
`unsupported snapshot compatibility contract` 拒绝。现在 schema 版本**读目标库**
`schema_migrations`(dirty 或版本不符直接拒绝),embedding 优先读目标库模型绑定、
读不到才回落部署声明,映射取目标当前值,版本/引擎用受支持栈常量。
- **基线作业"登记成功但没来得及收尾"不再被判死**:原来崩溃重入会在
"已有生效版本"检查处直接失败,把目标留在维护里、占用也不释放。
现在按「本作业 + 同一快照」识别为**自己的**版本:只做收尾(恢复服务、开入口、
释放两把占用),不重新捕获、不重写版本;别人登记的生效版本仍然一律拒绝。
捕获完成但登记失败的作业会明确失败并写"未对目标写入",不会假装成功。
- **同一作业重入不再覆盖第一次记下的原运行状态**:崩溃接管时目标已被停住,
再读一次只会读到 `stopped`,写回去会让恢复阶段以为写入者本来就没跑、
永远不启动它。现在 `AcquireInstanceMaintenance` / `AcquireLibraryMaintenance`
在 owner 相同的重入时保留已持久化的原状态;新作业接管已释放窗口仍用自己读到的值。
(真实 PostgreSQL 集成用例覆盖,非内存假实现。)
- **基线的版本登记移进维护窗口**:捕获、校验、登记三件事要么一起成立、要么目标
原样恢复;记录失败时窗口已按原状态开放,治理记录同步释放,**不再出现
"显示保持维护、实际入口已开"**。
- **索引映射读不到时拒绝登记**:不再 `_` 忽略错误后用空摘要写进基线证据。
- 基线窗口的统一收尾抽成 `settleBaselineWindow`:版本已登记走与同步相同的成功边界;
窗口仍 held 才标 `needs_restore`;窗口已恢复则释放占用并失败。
### Added
- **`cmd/librarydrill`:目标实例真实恢复演练**(维护隔离 → 整库预备份 → 一致性校验
→ canary 漂移检测 → 按预备份恢复 → 逐类复核 → 按原状态恢复服务)。它使用与
worker 完全相同的 `LIBRARY_*` 配置与维护协调器,绝不触碰封存源;任一步失败都
保持维护并写报告,不伪造通过。
- `internal/librarystack`:目标/预备份/维护栈的唯一装配点(同步、恢复、基线、演练共用)。
### Changed
- 基线作业的"已有生效版本"判定改为三种状态(own / foreign / none),并在
阶段里写明是否走"只收尾"续作。
## 开发快照(首次发布入口:同步授权、目标权威基线与维护能力)
### Added
- **管理员在发布页签发同步授权(`POST /api/v1/library/authorizations`,`action=sync`)**:
授权把「范围 + 目标快照 + 参数摘要 + 有效期」钉死,仍与执行分成两步 ——
页面只提供入口,不会替你签发,也不会自动重试执行。工程师看得到缺项原因,
但拿不到签发按钮(接口本来就只认管理员)。
- **目标权威基线(迁移 075,作业类型 `baseline`)**:目标非空、又没有生效版本时,
首次发布的真实解法是「维护隔离 → 整库捕获 → 逐类校验 → 登记基线」。
`POST /api/v1/library/baselines` 只建作业;worker 用与同步同一套维护窗口
(关入口、停写入者、证明排空)把目标整库捕获成归档快照,再用同一套 `Verify`
证明目标与这份快照一致,最后登记 `production_effective`(证据写明
`origin=target_baseline`、快照 ID、逐表行数、目标索引映射)。
它**不写目标内容、不清空目标、不手写版本**:没有捕获与校验证据就没有基线。
基线作业与 sync/restore 共用同一把写互斥(同一 scope 只允许一个在飞写作业),
worker 被杀时同一作业 id 会接管那个窗口继续,而不是把目标留在停机状态。
- **维护能力(`GET /api/v1/library/maintenance` 的 `capability`)**:页面区分
「此刻没有维护窗口」与「维护能力没接线」,逐项列出缺少的
`LIBRARY_TARGET_*`;接口没给能力信息时显示"未知",不再用一句
"也没有维护能力配置"糊过去。
### Changed
- **`productionScopeEmpty` 改成整库口径**:原来只数 `knowledges`,"文档删了但
分块/Wiki/资源还在"的目标会被判成空库,从而绕过基线初始化。现在用适配层
同一份表清单与范围过滤(`v071.ScopeContentEmpty`)逐表计数,并把逐表行数
交回接口,"空"只有一份定义。
- **拟发布的测试封存只认版本登记(`kind=test_sealed`)**:归档里同时存在生产
预备份与目标基线快照,按"这个 scope 最新一份"挑会把目标自己的内容显示成
测试候选。现在候选来自登记、再用归档确认它还在本机;不在就说明原因。
- **发布页不再说"生产同步执行链尚未接线"**:同步按钮按真实闸口启用,
点击走 `POST /api/v1/library/syncs`;被拒时逐条显示接口返回的缺项。
- 缺项文案从"生产当前权威版本未知,不能启动写入"改为指向真实入口:
「目标非空且没有生效版本记录:先建立目标权威基线…或确认目标确实是空库」。
### Fixed
- 维护卡片把"没有窗口记录"错误地写成"没有维护能力配置",与实际接线不符。
- 首次发布时 `POST /library/baselines` 在目标读不到时**不建作业、不登记版本**
(拒绝用建基线掩盖连接/配置故障)。
## 开发快照(发布执行器可用性、整库还原入口与诊断)
### Added
- **封存执行器的运行时可用性(迁移 074 `library_executor_status`)**:页面上的
"可以发起封存"不再等于 `LIBRARY_AGENT_SOCKET` 非空。主机侧 agent 新增
`health` 动作(不占封存操作锁、不跑子进程),worker 每 30 秒探测一次并把
"指向哪个实例、归档在哪、是否降级/正在跑、观测时间"落库;API 只读这份观测。
socket 只挂在 worker 容器里,所以由 worker 观测、页面读结果,而不是让 API
假装能 dial。观测过期、不可达、实例不符、降级、正忙时页面与
`POST /library/captures` 用同一份判定拒绝,并逐条给出原因。
- **封存执行前后的身份复核**:封存结果由 agent 附上自报身份(`agent_identity`)。
worker 在停写入者之前再探测一次,执行后再用结果里的身份复核;不一致时作业失败
且**不登记**为测试封存——心跳只能证明"刚才",从心跳到停写入者之间 socket
可能被换掉。
- **按版本恢复的页面入口**:`GET /api/v1/library/restore-availability` 说明
"这份快照现在能不能恢复"(双闸、该快照的恢复授权、目标实例是否在维护)。
页面据此启用"恢复至该版本",管理员可签发 30 分钟有效期的 restore 授权;
工程师只看到条件,不获得入口。
- 可选 `LIBRARY_SOURCE_PROJECT`:声明封存执行器应指向的实例名(默认等于
`LIBRARY_SOURCE_INSTANCE`)。它描述封存源,与同步目标 `LIBRARY_TARGET_*` 分开。
### Changed
- **封存身份核对移进请求本身(TOCTOU)**:原来 worker 先 `health` 再 `capture`,两次
拨号之间 socket 可能被换成另一个 agent——错的执行器已经把写入者停了才被拒登记。
现在 `capture` 请求携带治理侧配置生成的期望身份(`project` 必填,`source_app`/
`archive_root` 配了就一起绑定)与稳定指纹(`sha256:`,覆盖三项);接收方在**操作
互斥内、执行任何源操作之前**比对,缺失或被改过一律拒绝,错的执行器一次 `capture`
都不跑。封存结果回显该指纹,worker 事后复核只作为证据防线,不能替代请求内核对。
恢复路径不受期望约束(它只把写入者恢复回来)。
- `LIBRARY_EXECUTOR_STATUS_MAX_AGE`(默认 120 秒)控制执行器观测的有效期;
未设置不等于"永不过期",过期即"无法确认"。
- 页面/接口不再出现写死的"封存通道未接线""生产恢复未接线":不能发起时给出真实原因、
执行器身份与观测时间。
### Fixed
- **接管在版本查询失败时 fail-open**:`resumed` 后版本查询出错被忽略、`FinalizeOnly` 仍为
false,于是"已成功登记并释放窗口、只差终态"的作业会被**重新写一遍目标**。现在接管先按
证据分类:`finalize` 阶段成功是独立证据(不受版本查询影响);查询失败且无阶段证据 ⇒
**未知**,零写入、作业失败可重试;本快照的生效版本已被别的快照顶替 ⇒ **拒绝覆盖**;
只有真正没有收尾证据时才按正常流程。
- **维护释放部分成功后的重试不再永久卡死**:`Update*/Release*` 对"已由本作业释放"的行
返回可判别的 `ErrMaintenanceReleased`,收尾重试视其为已完成;别的作业来释放仍是
"未持有"错误。真 PG 负例:同作业重复 Update/Release 得到已释放,跨作业得到未持有。
- **作业成功暴露早于维护结束**:原来 `execute` 里写完生效版本就 `AdvanceLibraryJob(succeeded)`,
而服务恢复、入口开放与维护锁释放要在窗口返回后才做。任何一步失败时外部已经看到
`succeeded`,重启接管又只认非终态,锁就永远挂着。现在内容校验/版本登记之后只记
"待收尾",`finishLibrarySuccess` 依次确认目标写入者与入口健康、按 **scope → instance**
顺序释放两把锁(每一步错误都上抛,不再丢弃),最后才写 `succeeded`;任一步失败都把
作业落成失败、维护保持 `needs_restore`。释放与终态写入用 `context.WithoutCancel`
包住,停机不会留下半成品。接管判定改为按证据(finalize 阶段成功或存在未顶掉的生效
版本)转 finalize-only,只复核与登记,不重写生产。
- **封存读资源失败只剩一句 `read library resource`**:对象缺失、桶错、端点解析不到
都会走到同一个字符串,真实封存直接卡在这里无法判断方向。现在带上资源行 id 与底层
原因(`read library resource <id>: <cause>`),不含凭据。
- **正式 release-worker 启动失败无法定位**:配置里的目标库指向不存在的主机时,
日志只有一句 `connect release target database`。现在错误里带库端点
(`host:port/db`,去口令)与路由标识,并保留底层原因;原因文本先经
`pgdsn.Redact` 抹掉口令(pgx 的解析错误可能原样回显连接串),可诊断不等于泄露。
共享的 `internal/pgdsn` 替换了 cmd/worker 里只留库名的旧实现。
- `libraryctl` 用法文本补上 `dead-letters` 子命令。
## 开发快照(死信精确解除:保留上游行,只按指纹放行)
### Added
- **死信解除契约(治理侧)**:上游 `task_dead_letters` 没有"已解决"标记,
purge/DELETE 都会销毁"这条任务失败过"的唯一证据。新增
`library_dead_letter_releases`(迁移 073)记录**一条精确死信**的解除:绑定实时行
逐字节指纹(`sha256(to_jsonb(row))`,先压紧空白)、重放引用(队列/任务号/时间/
证据引用)、源侧观测摘要、操作人与时间。上游行一个字节都不动。
- `GET/POST /api/v1/library/dead-letter-releases`:POST 需工程师及以上 +
`X-Governance-Action: dead-letter-release`,拒绝条件全部显式——观测摘要不符(被
改写/格式化)、源侧未结清、观测里没有这条死信(已被删除)、重放早于失败时间、
重放之后同一任务键又出现新死信(说明重放没成功)、实例不一致。
- `libraryctl dead-letters`(只读):打印整库死信原始行 + 结算计数 + 观测摘要,
供治理平台绑定;平台自己不连源库、不复制"自报成功"。
### Changed
- 死信解除绑定 `LIBRARY_SOURCE_INSTANCE`(封存源),留空回落到 `LIBRARY_TARGET_INSTANCE`:
同步目标与封存源常常不是同一个实例,用目标名去绑来源死信会让真实观测永远对不上
(拒绝而不是误放行)。worker 取指纹、API 写记录用同一个来源实例名。
- 结算/封存不再"死信存在即阻断",而是"每条实时死信都要被精确解除命中":
`checkLibrarySourceSettlementReleased` / `ExportLibraryWithReleases` 对每条行
重算指纹,未命中、行被改过、出现新行都继续阻断并点名。
- 解除指纹经 worker → agent → `libraryctl -released-dead-letters` 逐段校验
(`sha256:<64 hex>`,最多 64 条,不接受通配),agent 的固定命令形态不变。
### Fixed
- **同步过去的资源行指向来源实例的存储后端 id(定位符与独立列两处)**:`storage://<id>/minio://<bucket>/<key>`
里的 `<id>` 是来源 `storage_backends` 的主键,目标实例没有这条行。只换桶不改 id 时,
原版应用落回默认存储服务并拒绝整条路径(真实下载 500 `code 1007
invalid MinIO file path`)。现在写入前按"租户 + 目标桶名"在目标库解析后端 id,
写入与校验共用同一份行变换;解析不到就拒绝写入,不产出目标读不出来的定位符。
`resources.storage_backend_id` 这一列同样要换(应用先按它找文件服务)。
- 指纹对空白敏感会让"同一份内容"两边算不出同一个值(PostgreSQL `to_jsonb::text`
带空格、Go `json.Marshal` 不带);指纹改为先 `json.Compact` 再摘要。
## 开发快照(封存入口接线:库级窗口接上实例级闸门)
### Fixed
- **主机侧封存不再用"库级窗口"跑实例**:`libraryctl capture` / `recover` 改由
`librarymaintenance.Manager` 装配——入口闸门、后台静默检查、目标指纹、入口探针与发布链路
是同一套实现。实例级维护里"写入者 exit=1"不再等于"在途请求被截断":被代理的 WeKnora 收到
SIGTERM 就 `exit(1)` 是已知上游行为,排空证据来自闸门(`active_at_close` / `drained`),
校验没有放松,而是补上了缺的那一层证据。
- 缺少 `LIBRARY_TARGET_INGRESS_*` / `LIBRARY_TARGET_REDIS_*` 时 `capture` 在**开窗口前拒绝**
并点名缺哪个变量;不再退回一个没有排空证据的窗口(那样的"成功"无法复核)。
- 维护接线解析收敛到 `internal/librarymaintenance/env.go`:agent 与 worker 讲同一组变量名,
空白值算缺失、队列库号必须是非负整数、口令不 Trim(前后空格也是口令)。
- `libraryagent` 启动时如实打印维护目标是否接好(缺了就直说"封存会拒绝")。
- `scripts/library-agent-install.sh` 强制 `agent.env`(0600,`EnvironmentFile=`),并把源容器/
项目/写入者声明做成可覆盖变量;没有 env 文件直接 `AGENT_ENV_MISSING` 退出。
- 主机侧封存新增 `-source-s3-endpoint`:容器里的对象存储端点常常只是服务名
(`langfuse-minio:9000`),宿主机解析不到,资源导出会失败(真跑报 `read library resource`)。
覆盖只换访问入口,桶/对象键不变;`status` 如实打印实际 endpoint。
- **封存 API 的队列作业 id 超列宽**:`library-capture-<scope>-<nano>` 是 45 字符,而
`jobs.id` 是 `varchar(36)`,真实请求一律 500(`value too long`)。此前页面停在"未接线"
从未走到这里;改为 `libcap-<scope>-<nano>` 并用测试锁住列宽。
- **索引范围过滤漏掉 app 写入的正文**:v0.7.1 的 app 自己写索引不带 `tenant_id`,
治理侧同步才补齐;只按 `term tenant_id` 过滤会把整库正文漏掉(真跑:索引有 2 条、
导出 0 条、候选校验报 legacy coverage)。改为"本租户 或 未写租户",写了别的租户仍排除。
- **空目标索引不能当成"索引被改坏"**:全新 WeKnora 实例先用空映射建索引(`{}`),
字段等第一次写入才动态生成;首次同步因此在读取目标状态时就失败。现在"没有范围字段
+ 确实 0 篇文档"按空基线处理,有文档却看不出归属仍然报错。
- **资源定位符的桶没有跟着换**:同步把字节写进目标桶,但 `resources.physical_path`
是从快照原样搬的,里面仍写着来源桶(`storage://…/minio://kgmaint-1028a/…`)。
目标实例之后取图/附件会指向测试环境的桶,或直接取不到。现在写入前把定位符里的桶
换成本次目标桶(键不变),解析不出来就报错而不是写一行指向别处的资源。
- **恢复作业 id 让维护窗口拒绝**:`librestore-<scope>-<时间戳.微秒>` 带 `.`,而冻结协调器
只接受 `[A-Za-z0-9_-]`,每次恢复都在开窗口时被拒(`invalid freeze request`),作业随后被
接管扫成失败。改用纳秒整数,并加测试锁住"id 必须是维护窗口可接受的标识"。
### Tests
- `internal/httpapi/library_restore_test.go`:恢复作业 id 必须能被维护窗口接受。
- `internal/releaseadapter/v071/resource_locator_test.go`:嵌套/单层两种定位符换桶、裸键原样、
非法形状拒绝。
- `internal/releaseadapter/v071/search_empty_index_test.go`:空索引=空基线、有文档无范围字段必须报错。
- `internal/httpapi/library_capture_test.go`:队列作业 id 必须放得进 `varchar(36)` 且形状固定。
- `internal/releaseadapter/v071/search_tenant_filter_test.go`:接受未写租户、排除他租户、
无租户字段时不造条件、整库过滤仍带知识库身份。
- `cmd/libraryctl/discover_test.go`:端点覆盖生效且必须带 scheme。
- `internal/librarymaintenance/env_test.go`:整份声明解析、逐项点名缺失、空白拒绝、
口令空格保留、非法队列库号。
- `cmd/libraryctl/maintenance_test.go`:入口/队列/源身份绑到同一份配置、数据库身份不泄漏口令、
写入者声明透传、缺闸门拒绝。
## 开发快照(Wiki 撤销贡献:逐页语义比对 + 维护窗口真实呈现)
### Added
- **Wiki 贡献级差异**(`internal/librarydiff/wiki.go`):以 slug 为身份,把每页拆成
正文 / 来源文档 / 引用分块 / 出边 / 入边 / 目录/状态 分别取集合比较,输出
`sources_added` / `sources_removed` / `citations_before→after` / `changed`。
删除一篇与别人共同贡献同一页的文档后,页面通常仍是「更新」,只看页面级增删改会显示成
「无变化」——现在能指出这份贡献是从哪一页、哪几类语义上被摘掉的。两端同时接受 JSON 数组
与字符串化数组,读不出来直接报错,不把「读不到」当成「没有来源」。
- **接口 `GET /api/v1/library/overview` 的 `diff.wiki` 增加逐页明细**:`pages`(最多 50 页,
超出置 `pages_truncated`)、`sources_added/removed`、`base_count/target_count`;页面级计数
保留,说明文字明确「重算发生在源侧,本平台只如实比对与保留结果」。
- **发布管理页面:目标维护面板**(`MaintenanceCard`):显示维护级别(实例级/库级)、持有
scope+作业、阶段、入口开/关、服务、开始/释放时间、作业进度(阶段名中文化,未映射原样显示)、
恢复错误与处理记录;只有窗口仍持有且作业失败时才出现「按预备份恢复」,动作走
`POST /api/v1/library/maintenance/recover`。没有维护记录时明确写「未接入 + fail closed」,
不画成已就绪。
- **变更页展示逐页贡献差异**:每条页面一行,直接写「更新 发布闸门 · 正文/来源文档/引用分块 ·
来源 -1 · 引用 2→1」。
### Fixed
- `LibraryJobRecord.kind` 之前只有 `seal | sync`,恢复作业(restore)在类型上不存在;
补上后页面不再把恢复作业当成未知类型。
### Fixed
- **`libraryctl` 只认 OSS 源**:`STORAGE_TYPE=minio` 的目标在发现阶段就被拒
(`unsupported source storage type "minio"`),S3 兼容的专用验收实例无法封存。
现在按 `MINIO_ENDPOINT / MINIO_USE_SSL / MINIO_ACCESS_KEY_ID / MINIO_SECRET_ACCESS_KEY /
MINIO_BUCKET_NAME` 映射成 `provider=minio` + path-style;读不到端点/桶就明确报错,
不退回默认值。
- **CLI 与 API 的 scope→知识库 映射不一致**:API 用 `LIBRARY_PRODUCTION_KB_*`,CLI 只读
routing bindings,同一台机器上会封存"配置里那个库"而不是 API 的验收目标。现在环境变量
优先、读不到才退回 bindings。
### Docs
- `docs/library-acceptance-matrix.md` 按「已完成 / 缺代码 / 已实现未跑 / 需授权」
四段重排(不再在顶部叠报告):补上本轮 Wiki 撤销真实验收、贡献级差异、维护面板;
新增封存链路缺口(CLI 无入口排空证明、API 封存未接线)与死信契约缺口。
- 新增 `docs/formal-8088-deployment-plan.md`:版本清单、迁移 067–072、运行配置、
fail-closed 规则、回滚、正式账号与数据保留、部署风险。**只准备,未执行**。
## 开发快照(后台静默必须在关入口之后证明,且要能复核)
### Fixed
- **顺序缺口**:原顺序是「先证明后台静默 → 关入口排空 → 停写者」。排空期间还在服务的
在途请求随时会派生新的异步任务,那个"空"到停写者时早就失效;而 app 收到 SIGTERM 会
exit(1),正在跑的任务被腰斩。现在固定为「关入口并排空 → 证明后台静默 → 停写者 →
停完再复核」,并且在**入口关闭之前禁止取样**(`awaitBackgroundQuiet` 要求
`Gate.Closed && Gate.Drained`)。
- **单次瞬时 0 不算证明**:新证据 `QuiesceProof` 要求连续取样为空、跨过稳定窗口
(默认 1.5s)、任务集合指纹一致;只有 `Idle` 没有指纹的检查器在实例级直接拒绝。
- **停写者后复核**:`verifyBackgroundAfterStop` 再取一次样,队列非空或指纹变化即判
`background_truncated`,按"有任务被截断"失败,绝不继续写目标。
- **scheduled/retry 自行产生任务**:队列长时间没有进展(默认 30s)就直接失败
(`background_busy`),不拿等待时间冒充排空。
- **旧 journal 的 `background_idle` 布尔不再当证明**:只保留为历史字段,恢复流程一律
重新取样(`RecoverWindow` 不读它);只有布尔、没有证明的旧窗口按"未证明"处理。
- **恢复路径同样要求先停任务再停写者**:写入者已停但队列还有任务时立即失败并写清
外部处理方式(先起服务、入口保持关闭让任务跑完,再重试恢复),不再拿"服务没跑"
冒充"后台已静默"。
- **入口关到一半失败会把入口留在关闭状态**:`restore` 现在对实例级维护总是重新打开
入口(不再只看"是否成功关闭过"),放弃窗口时还要用 `ingressReady` 确认入口真的回来了,
否则保持维护不释放——避免"维护已释放、服务在跑、入口却是关的"。
- **zset 队列读取**:对 `scheduled`/`retry` 先 `TYPE` 再 `ZRANGE`,此前直接 `LRANGE`
会报 WRONGTYPE,任何自带重试/定时任务的实例都开不了窗口。
### Added
- `QuiesceSnapshot` / `QuiesceProof` 与 `StableQuiescer`:后台静默从"一次布尔"变成
可复核证据(样本数、稳定窗口、任务指纹、停写后复核时间)。
- `SetQuiesceStability(stable, stall)`:调整稳定窗口与"任务无进展"判定。
- 回归测试:取样必须晚于关入口、排空期间产生的任务要等它结束、队列一直有任务要
fail closed、停写后冒出新任务要判截断、旧布尔不算证明、只有 Idle 的检查器被拒。
## 开发快照(恢复校验按归档身份核对共享表)
### Fixed
- **`resources` 校验改用归档身份集合**:resources 是多知识库共享表,采集口径看
knowledges/chunks/wiki 正文里的 `resource://<handle>`,校验却只看 chunks 并重新推导
"本库可见",两边口径不一致——恢复得一字不差也会被判"行数 15/19"而拒绝开放。
现在按归档列出的 id 逐个核对存在性与逐行内容摘要;少一行、改一个字节都算失败。
## 开发快照(真实依赖跑出来的接线缺陷)
### Fixed
- **Docker inspect 反序列化**:`containerState` 把 `Config` 映射成 `map[string]string`,
真实返回是对象,于是每次拓扑校验都报 `invalid docker response`——所有维护窗口被误拒。
- **后台静默检查没接线**:`openLibraryMaintenance` 漏传目标库/队列,实例级窗口永远报
"缺少后台静默检查"。防线代码在仓库里、运行时却没生效。
- **恢复作业队列 id 超列宽**:`"librecover-"+窗口作业 id` 超过 `varchar(36)`,
恢复入口投递即失败(409 enqueue failed),等价于没有恢复入口。
- **恢复不再信任上一次窗口的 StopRequested**:写入者在窗口后被人工拉起来时,
`stopAll` 要按"现在是否真的在跑"再停一次;否则校验会跑在半开的目标上。
## 开发快照(维护收尾必须按预备份验证 / 后台任务静默 / 实例级互斥)
### Fixed
- **删除 `FinalizeAfterOperatorRecovery`**:它自称"不启动服务",实际会 `gate.OpenIngress`,
而且只看服务健康与入口 200——健康不等于恢复成功,这条路径能把半写内容重新开放。
- 新的唯一收尾入口 `Coordinator.RecoverWindow`:绑定「原作业 + 目标指纹 + journal 里持久化的预备份身份」,
在**维护关闭**状态下(入口关、写入者停、排空确认)执行 `Restore` 与**逐类校验**
(行数/身份集合/逐行内容摘要 + 索引文档 + 对象资源),校验通过才恢复服务、开入口;
校验或恢复失败则保持维护(服务停、入口关、journal 非终态)。
- 预备份身份与「内容风险」在**第一笔写入之前一起落盘**(`Window.SetBackupSnapshot` +
`MarkContentRisk`)。没有预备份身份的窗口之后既不能自动恢复,也不许被任何收尾路径开放。
- 低层恢复接口不再接受任何"已恢复/已验证"的入参:`Restorer`/`Verifier` 由协调器在窗口内调用,
`Verifier` 必须返回校验摘要并被持久化。
### Added
- 真实恢复入口 `POST /api/v1/library/maintenance/recover`:只建 `library.recover` 作业,
由 worker 按预备份恢复 + 校验后释放占用;`libraryctl finalize` 删除(不再有"手工收尾"旁路)。
- 实例级互斥:迁移 `000072_library_instance_maintenance`,维护占用按**实例**建键,
internal 持有期间 external 也进不来(旧实现按 scope 建行,两个 scope 会同时停/起同一实例)。
- 后台静默检查(`targetQuiescer`):窗口关闭入口之前,必须证明目标库 `task_pending_ops=0`、
无未完成解析、asynq 队列(pending/active/scheduled/retry)为空;否则拒绝开窗口。
HTTP 排空不等于后台任务排空——app 内的 asynq worker 会被上游 SIGTERM 的 exit(1) 切断。
- 唯一入口拓扑校验(`Manager.VerifyTopology`):写入者**不许发布任何宿主机端口**,
入口必须是独立声明的闸门且发布的端口与入口 URL 一致;不满足就拒绝开窗口。
### 限制(本轮不承诺)
- 拓扑校验只看"声明的写入者组":新增容器必须显式声明,平台不会自动猜测。
- 后台静默检查依赖目标库表 `task_pending_ops/knowledges` 与 asynq 键名(v0.7.1 现状);
上游改队列实现时需要同步调整。
- 页面只透传新增字段(实例占用/入口状态),专门文案与浏览器验收仍未做。
## 开发快照(目标维护:实例级窗口)
### Added
- 同步/恢复不再只锁治理库 scope:`runLibraryRelease` 现在先开**实例级维护窗口**
(`internal/librarymaintenance` + `internal/libraryfreeze` 复用)——
持久化 owner / 目标精确身份(实例、写入者容器、入口、库/索引/桶 + 指纹)/ 原服务状态,
关闭目标入口(SIGTERM 后监听立即关闭)、停下全部声明写入者,
并验证「写入者真的停了」「在途请求真的排空了」「入口真的连不上」之后才允许写目标。
- `libraryfreeze.RunWindow`:一个窗口里跑调用方的写阶段。第一次写目标前必须
`MarkContentRisk`(fsync 落盘);写完先恢复原运行状态与入口,再释放维护占用。
- 入口探针(`internal/librarymaintenance`):关闭 = 连不上(代理还回 502 不算关闭),
恢复 = HTTP 200;只有原本在跑的服务会被重启。
- 迁移 `000071_library_maintenance_level`:维护记录新增 `level`(instance/library)、
`target_fingerprint`、`services`、`ingress_state`,页面/审计能看清这次到底动的是实例还是只锁了一个库。
- 入口闸门(`LIBRARY_TARGET_INGRESS_SERVICE` + `INGRESS_DRAIN_FILE`):被代理的 WeKnora 收到 SIGTERM
会先关监听再因 `use of closed network connection` 直接 exit(1),在途请求会被切断。
因此排空放在真正的连接入口上做:闸门先停止接受新连接、等在途连接归零、写 `drain.json`
并 exit 0;协调器只认「闸门 exit 0 + phase=drained」这一条证据,写者的退出码只作为记录。
- 排空证据不能被历史文件冒充:关入口前先删掉上一次的 `drain.json` 并确认真的不存在,
关闭后还要求记录 mtime 不早于本次关闭时刻;闸门当时没在跑就直接失败,而不是读旧记录。
- 维护占用释放后可被新作业接管(此前 `ON CONFLICT ... WHERE owner_job_id = ...` 会让
"第一次同步之后谁也再进不去");仍在持有的窗口依然只允许原作业重入。
### Fixed
- 进程被杀/授权过期不再误放行半写目标:journal 里的 `content_risk` 一旦置位,
重启恢复只收尾确定性的事,**不会**自动把写入者启动回来;原作业接管后才继续。
- 回滚失败(`rollback_failed`)或校验失败(`finalize` 不一致)时,服务保持停止、
维护占用保持持有(`needs_restore`),入口不重新开放。
- 接管场景(已有预备份)一律按「内容可能半写」处理,任何早退都不重新开放目标。
- `libraryctl finalize -job <id>`:回滚失败被保持维护之后,人工把目标恢复到可服务状态时的
收尾入口。它不启动任何服务、不释放治理占用,只核对「原本在跑的服务现在在跑且健康 + 入口在服务」,
通过才把 journal 标成 `operator_recovered`。
### 限制(本轮不承诺)
- 维护层级判定是「配置里声明的写入者组」:多实例/多写入者拓扑变更需要重新声明,
不会自动猜测新容器。
- 前端只透传新增字段,页面尚未为 `level/ingress_state` 做专门的文案与布局;
Wiki 语义重算与浏览器整体验收仍未完成。
- 本机专用候选实例(kgmaint-*)仅承载验收数据,不代表远程生产已验证。
## 开发快照(读接口超时:真实修复)
### Fixed
- 整库只读接口不再按版本重读全部 blob:`overview`(internal/external)、`storage`、`snapshots`
从 10s 超时 503 变为真实返回完整数据(候选归档 79 版,本机实测 <1s)。
根因是列举路径用 `Archive.Usage()`(逐版 `Load` 校验全部部件),
外加 `library_storage.go` 每版两次 `Load`、`snapshots` 每版一次 `Load`。
- 新增 `Archive.UsageFromManifests` 给只读列举用:与 `Usage` 共用同一份去重实现,
只把"读清单"换成清单级校验。`Usage`(逐版复核字节)保持不变,仍供同步/恢复与运维 CLI 使用。
- `LoadManifest` 收紧到与 `Load` 相同的清单校验(严格解码 + 重新编码校验,含必需部件与兼容性契约),
`Load` 改为 `LoadManifest` + 逐部件验字节——同一套清单判据只有一份。
- wiki 与文档差异只读所需部件(`wiki_pages` / `knowledges`),被读取的那个部件仍由
`OpenPart` 复核摘要;不再为两页 Wiki 重算整版字节。
- `libraryWithDeadline` 去掉了"另起 goroutine 到点先回 503"的写法:候选环境实测它会在超时后
继续写已结束的响应(日志 `http: superfluous response.WriteHeader call`),并留下仍在扫盘的 goroutine。
改为请求期限交给 ctx,读取在版本之间检查期限,响应只有一个写入者。
### Changed
- 列举口径必须看得见:`snapshots`/`storage` 响应新增 `verification=manifest` 与说明,
每份版本带 `verified=false`;单版详情因为整版复核字节,返回 `verification=content`、`verified=true`。
这样"清单可读"不会被读成"完整性已验证/可发布"。
### 未做(不在本轮范围)
- 运维 CLI `libraryctl` 仍走逐版复核(`Usage`/`Load`):它是执行前的操作入口,慢但真校验,本轮不动。
- 归档规模到上万版本时仍未分页/增量盘点;只保证请求可被期限中止,不承诺增速。
- 维护协议、Wiki 语义重算、最终部署仍未完成,见后续工作。
## 开发快照(读接口超时:根因量化 + 部分修复)
### 实测根因(本机候选归档,真实数字)
| 步骤 | 耗时 |
|---|---|
| Open(仅目录校验,无 flock) | 0 ms |
| Load 单个清单(含逐部件验字节) | 252 ms |
| OpenPart + 读 knowledges | 0 ms(4 行) |
| 遍历全部版本(当时用 Load) | **13833 ms**(79 版 / 2.77 GB 声明字节) |
结论:接口超时不是锁、不是连接池,而是**按版本重复校验全部部件**导致的 O(全部版本字节)。
79 个版本来自历次真实同步的预备份/派生快照,全部留在同一个归档根。
### Changed
- 新增 `Archive.LoadManifest`:只校验清单自身(文件名摘要 == 内容摘要 + 结构),
用于盘点/统计/列版本;`Load` 仍逐部件校验字节,同步/恢复路径不变。
- `ContentUsage` 改用 `LoadManifest`(原先每个版本都 `Load`)。
### 仍未修好(本轮没完成)
- ~~`GET /library/overview`、`GET /library/storage` 仍 **503**(10s 超时)。~~
已在上面「读接口超时:真实修复」一节修好:剩余调用点是 `Usage()`、`library_storage.go` 的
双 `Load` 与 `snapshots` 的每版 `Load`。
## 开发快照(目标维护窗口:半成品,未验证)
### Added(代码已完成,**真实取证未通过**)
- 迁移 000070 `library_maintenance`:一个 scope 一个持有者,记录作业/目标身份/服务原状态/阶段/释放时间。
- `AcquireLibraryMaintenance`(异主占用直接拒绝)、`UpdateLibraryMaintenanceState`、
`ReleaseLibraryMaintenance`、`LibraryMaintenanceOf`。
- worker:写目标前 acquire,写前置 `applying`,只有"验证通过 + 版本登记成功"才 release;
回滚失败/回滚后未验证 → `needs_restore` 且保持持有(不会被 finally 释放)。
- API:`GET /api/v1/library/maintenance`;同步入口在目标被他人持有时返回 409。
### 已知缺陷(本轮没有修完,不能当成可用)
- 候选环境实测 `GET /api/v1/library/maintenance` 返回 **404**:路由已注册在 server.go:451,
但运行实例没匹配上,根因未定位。
- 维护窗口的真实 kill/接管取证未成功:取证脚本在 kill 之前作业已跑完(竞态),
三项断言(持有中、kill 后仍持有、维护期间拒绝新写入)全部 false。
因此维护协议**没有真实证据**,不得声称已实现。
## 开发快照(安全门禁 + 崩溃接管 + 五项真实入口验收)
### Fixed
- **预备份持久化成为硬门禁**:`BackupObserver.BackupPrepared` 现在返回 error,
引用/版本登记失败时 `Run` 直接返回失败且**一个字节都不写目标**(此前只写日志继续写)。
- 预备份同时登记成 `production_prebackup` 版本,失败与成功路径都留证据。
- 中断接管按**队列租约**判定(原来只看 state=running,worker 刚被杀而租约未过期时会漏掉);
运行期每 30 秒扫一次,不再只在启动时扫一次。
- 接管用一次条件更新原子抢占(`running→pending` 只有一个 worker 成功),队列作业 ID/幂等键固定,
并发接管只会得到同一条队列作业;重复接管时把 attempt 归零,否则 `attempt<max_attempts`
让作业永远不能被领取。
- 接管场景下闸口/授权失败不再写"未写无需恢复":保留既有预备份并保持目标占用。
- 恢复/重放同一快照时重新激活 `production_effective` 版本(ON CONFLICT 清空 superseded_at),
否则页面显示"生产当前版本未知"而生产其实已恢复到该快照。
- `finalize` 失败的续作只做"校验 + 登记"(finalize_only),不重写已生效的生产。
- 生产索引映射基线缺失时,以"首次观察到的实际映射"为准并随预备份登记,之后改动会被拒绝。
- 索引校验增加"孤儿文档"检查(按 knowledge_id 判定,父子分块不会误报)。
### Verified(本机候选环境,真实 PG/ES/MinIO,经 HTTP API)
- A0 首次发布:succeeded,4 文档/38 分块/21 资源/31 Wiki/122 索引,登记生效版本与预备份。
- A1 撤销:succeeded,文档从 4 降到 3、分块 38→19、索引 122→58、绑定 4→3,其他租户 0 行。
- B 按版本恢复:无授权 503;恢复授权 → 202 → succeeded,内容完整回到 4/38/122。
- C 写阶段 SIGKILL:预备份已存在 → 杀掉 worker → 重启接管 → 复用同一预备份 → succeeded。
- D 跨 kind 互斥:并发 sync+restore → 一个 202、一个 409;双 worker + 中途杀一个 → 仍单次成功。
- E1 版本登记失败(真实 PG 触发器):作业 failed@finalize,生产已写、无生效版本、预备份保留。
- E2 finalize 续作:只做校验+登记 → succeeded,生效版本=目标快照,生产未被重写,重复续作同一作业。
- E3 预备份登记失败(真实 PG 触发器):作业 failed@prebackup,**生产零变化**。
## 开发快照(真实 Schema 对齐 + 入口级真实服务验收)
### Fixed
- 真实 Schema 对齐:`knowledge_tag_relations` 没有 tenant_id/knowledge_base_id(按真实库确认),
归属改为走父表 `knowledges`;`resources` 的作用域改为"绑定 + 被本库 chunk 正文 resource:// 引用",
与归档侧收集资源部件的规则一致(此前会把被引用的资源算成多余)。
- 表写入顺序按真实外键调整(`resources` 先于 `resource_bindings`)。
- `knowledge_tag_relations` 等无代理键的表改用内容摘要核对,不再要求不存在的 id 列。
- 队列作业 ID 用固定长度摘要(`jobs.id` 是 varchar(36),`<作业号>-queue` 会超长)。
- 生产版本记录 ID 用稳定短摘要(`library_versions.id` 是 varchar(64),拼接完整快照 ID 会超长)。
- 对象路径:真实 `resources.physical_path` 是 `storage://<backend>/oss://<bucket>/<key>`,
写入/读取都先拆成裸 key;对象存储 provider 改为可配置(默认 s3),必须与生产 `resources.provider` 一致。
- 生产索引不存在/为空时按"空基线"处理(Current/Export/Backup/Archive 全链路一致),
非空时仍然严格比对映射,避免把索引丢失当成正常。
- 索引校验按写入时的规范化比较(写入会补齐 tenant_id/knowledge_base_id),
不一致时给出首个差异文档与字段。
### Added
- 收尾阶段 `finalize`:版本登记失败不再报成功,作业标失败并保留预备份,修好后可续作。
- 崩溃接管:worker 启动时接管"running 但队列已无任务"的写作业,复用已持久化的预备份,
绝不重新备份覆盖;没有预备份的判失败。
- 跨 kind 互斥:迁移 000069 把唯一索引从 `(scope,kind)` 改成 `scope`(sync/restore 共用)。
- 按版本恢复 `POST /library/restores`(管理员 + 该快照恢复授权)与续作 `POST /library/jobs/{id}/resume`
(管理员 + 闸口 + 授权,幂等键按源作业固定)。
- 首次发布放行:生产可读且为空视为合法空基线;预备份不再作为启动前门禁(它由作业自身生成)。
### Verified(本机候选环境,真实 PG/ES/MinIO,全部经 HTTP API)
- 环境:候选 API :18089 + 候选 worker + 独立治理库 `kg_candidate_1028165117` +
真实 WeKnora 表结构的生产库 `kg_prod_1028165117` + 独立 ES 索引 + 独立 MinIO 桶。
- 首次发布:生产为空 → 预备份 → 四类写入 → 校验 → 登记 `production_effective` 成功;
真实落库 4 文档 / 38 分块 / 21 资源 / 31 Wiki 页 / 122 索引文档。
- 同请求重复:返回同一作业且 `reused=true`,不重复写生产。
- 未授权快照:503 并写明"没有与目标快照匹配的有效发布授权",不建作业。
- 更新版本:第二份快照同步成功(真实改标题)。
- 失败自动恢复:索引内容不一致、资源绑定行数不一致、对象上传失败等场景均被拒绝并回滚,
回滚后生产回到上一版本(多次真实复现)。
- 收尾失败不报成功:版本登记因 ID 超长失败时作业标 failed、生产写入保留并记录恢复责任。
## 开发快照(按版本恢复 + 作业续作)
### Added
- 迁移 000068:`library_jobs.kind` 允许 `restore`,与封存/同步分开。
- `POST /api/v1/library/restores`:管理员按指定整库快照恢复;要求**针对该快照的恢复授权**
(同步授权不能顶替),闸口与恢复演练证据同样适用,缺项逐条拒绝且不建作业。
- `POST /api/v1/library/jobs/{id}/resume`:失败/取消的同步或恢复作业按原参数重新排队,
旧作业保留为审计记录;封存作业不走续作(由封存入口重发)。
- worker 新增 `library.restore` 处理器,与同步共用"闸口 → 授权 → 预备份 → 四类写入 → 校验"链路;
成功后登记 `production_effective` 版本(带预备份快照与字节数证据),失败/回滚失败分别留痕。
## 开发快照(资源范围修正 + 内容级校验 + 真实对象存储验收)
### Fixed
- 资源范围:`scopeWhere(resources)` 与 `resourcePaths` 现在用 `resource_bindings.resource_id = resources.id`
精确关联,覆盖知识库绑定与分块绑定,并限制绑定租户;此前只按 owner_id 判断会命中整张资源表。
归档资源部件名与资源 ID 对齐(此前按 handle 查路径,导入会找不到对象)。
- 资源路径查询补上 `provider`,对象读写按资源真实 provider 执行。
### Added
- `Verify` 由"行数 + ID 集合"升级为内容级:逐行规范化摘要(仅豁免 created_at/updated_at/processed_at/expires_at)、
归档索引文档与生产索引文档的规范摘要、以及每个资源对象的实际字节 SHA256。
同 ID 改正文、改 Wiki、改向量、损坏对象都会被拒绝。
- 预备份一致性由行数升级为内容指纹:导出前后逐表比对"数量 + 内容摘要",同数量改写也会失败。
- 本机真实对象存储(MinIO)纳入验收:`TestLibraryProductionIsolationRealServices` 用真实桶与对象,
覆盖跨租户/跨库资源隔离、chunk-only 绑定、对象损坏、以及"源对象被改后仍能用预备份恢复"。
### Verified(本机真实服务)
- 三条真实服务用例同时通过:隔离资源+内容校验(`kg_library_test_*` 库、随机 ES 索引、唯一 MinIO 桶)、
单阶段导入/撤销/恢复、完整编排 e2e。跑完删库、删索引、删对象。
## 开发快照(生产整库真实适配 + 本机真实服务验收)
### Added
- `internal/releaseadapter/v071/library_production_target.go` 由占位改为真实实现:
`Current` 读生产每张知识域表的行数与「身份集合摘要」+ 索引条数;`Apply` 按阶段真实写入
(database/wiki 同事务先删本库再插、resources 上传对象、index 批量索引原始文档与向量);
`Verify` 用身份摘要与索引条数比对;`Restore` 按预备份快照回放整条链路。
只触碰知识域表,不写 users/sessions/api_keys;不做解析或 Embedding。
- 撤销真正落地:索引阶段在写入后用 `PruneScope` 删除"快照里没有"的文档,避免只增不减。
- `library_backup_store.go`:写前独立完整预备份——生产 PG(RepeatableRead)+ ES 整库导出 +
资源对象密封成归档版本,导出前后比对行数做静默检查;映射契约不符直接拒绝。
- `search_write.go`:`Count`/`BulkIndex`/`PruneScope`,范围过滤统一按「租户 + 知识库」。
- `cmd/worker/library_wiring.go`:用部署注入的配置装配生产目标与预备份;缺项时把缺的
环境变量名写进阶段并失败,不再使用空占位对象。
- 配置:`LIBRARY_PRODUCTION_DATABASE_URL`、`LIBRARY_PRODUCTION_ELASTIC_URL/INDEX`、
`LIBRARY_PRODUCTION_TENANT_ID`、`LIBRARY_PRODUCTION_KB_INTERNAL/EXTERNAL`、
`LIBRARY_PRODUCTION_S3_*`、`LIBRARY_EMBEDDING_MODEL/DIMENSION`。
### Fixed
- `libraryretention.CheckCapacity` 用安全加法:int64 溢出直接拒绝,不再绕回负数通过门禁。
- 保留策略组合语义写明为「每个已设维度都超限才成为候选」并提供 `policy_note`;
锁定/备份恢复依赖通过 `ProtectedBy` 进入保护集合,永不候选。
### Verified(本机真实服务,非桩)
- `TestLibraryProductionTargetIsolated` 在本机 WeKnora 测试栈真实 PG(一次性 `kg_library_test_*` 库)
与真实 ES(随机索引)跑通:首次导入 → 校验 → 撤销传播(PG 行与 ES 文档都消失、跨租户不受影响)
→ 独立预备份(626 字节)→ 注入故障(删行)→ 校验失败 → 恢复后校验通过。
- `TestLibraryReleaseRunIsolated`:同一环境上跑完整编排(授权 → 预备份 → 数据库/资源/索引/Wiki →
校验),返回 succeeded,预备份快照 8e4ed5ba…(1074 字节)。
## 开发快照(生产发布授权 + 同步/恢复编排 + 存储管理真实化)
### Added
- `internal/libraryretention`:保留策略、依赖保护(生产生效/最近封存/预备份/在飞作业)与清理预览;
预算未设置即不放行的容量门禁;已并入启动同步的缺项列表。
- `GET /api/v1/library/storage`:真实归档用量 + 三类版本保护依据 + 策略 + 预览 + 容量,只读。
- 发布授权:`library_release_authorizations`(目标+快照+参数摘要+有效期,可撤销),
`POST/GET /api/v1/library/authorizations`、`POST /{id}/revoke`,仅管理员签发,有效期 5–1440 分钟。
- `internal/libraryrelease`:预备份 → 数据库/资源/索引/Wiki → 验证 → 失败恢复的编排,
授权过期/目标不符/快照不符/参数变化一律在动生产前拒绝;恢复失败作为独立结论上报。
- worker `library.sync` 接执行编排:先闸口、再授权,写入阶段落库;生产目标适配器未配置时明确失败。
- 页面"生产保护与恢复"显示发布授权状态与有效期;系统设置→存储管理显示容量门禁、受保护版本与清理预览。
### Added(Wiki 页面级差异)
- `GET /library/overview` 的差异结果新增 `wiki`:两份快照 `wiki_pages` 的新增/更新/撤销页数与样例,
明确标注页面级、不代表实体/边/引用重算;缺部件时如实 unknown。
### Fixed
- `internal/qawemail` 单测把 `smtp.example.com` 当成可解析主机,构建机没有外网 DNS 时必红;
改用 `localhost` 并新增"RFC 6761 保留域 .invalid 必须判非法"的确定性断言(不放宽校验)。
### Changed
- 启动同步的缺项包含"没有与目标快照匹配的有效发布授权"与容量预算;作业只有在编排返回成功时才标成功。
## 开发快照(CI 修复 + 真实差异 + 孤儿作业回收)
### Added
- `internal/librarydiff`:按稳定文档 ID 与"排除 updated_at/updated_by 的整行摘要"比较两份归档快照的 `knowledges`,
输出新增/更新/撤销;`GET /api/v1/library/overview` 在生产权威版本与测试封存都存在时给出真实差异,
并标注 `base_snapshot`/`target_snapshot`,缺任一侧仍为 unknown 且说明原因。
- 发布管理"本次变更"按文档身份列出增改撤销,并显示比对的两个版本前缀。
- `ReconcileLibraryJobs`:worker 启动时把"队列里已无对应作业"的孤儿业务作业标失败并写明恢复责任,
避免永久占用 (scope,kind) 互斥索引。
- `docs/library-acceptance-matrix.md`:30 个验收项逐条对应代码/证据/状态(已实现已验证、已实现未验证、阻塞、未实现)。
- `docs/release-ledger.md`:版本→完整 SHA→CI run 与结果→是否部署 的台账。
### Fixed
- 格式问题(CI run149–156 在 format 步骤连续失败):按仓库既有 prettier 配置格式化四个文件,不放宽检查。
- `TestEmbeddedMigrationsAreVersionSorted` 仍写死 65 条迁移,新增 000066 后必红;改为 66 并补 000066 内容断言。
## 开发快照 snapshot/v0.1.114 - 2026-10-28
### Added
- 整库版本与持久作业:新增 `library_versions`(test_sealed / production_effective / production_prebackup 三类分开登记,生产生效版本唯一)、`library_jobs`(业务作业身份、阶段、错误与恢复说明)与 `library_job_stages`(分项计数,NULL 表示总量未知)。互斥与幂等由数据库唯一索引保证,不依赖前端禁用或单 worker。
- 读接口:`GET /api/v1/library/overview`(三类版本 + 差异 + 作业 + 启动缺项)、`GET /api/v1/library/jobs`、`GET /api/v1/library/jobs/{id}`。
- 写接口:`POST /api/v1/library/syncs`,闸口、生产恢复演练证据、生产权威版本、本次预备份缺一即 503 并逐条给出原因,且**不创建**作业。
- 封存作业落库:发起封存先建业务作业(同一目标只能一个在飞),worker 推进 export 阶段、成功登记 test_sealed 版本、失败同时记录阶段与作业状态。
- 发布管理改为读取真实状态:生产当前来自生产生效版本(没有就显示未知)、目标来自测试封存、差异未接线保持 unknown、启动同步按缺项禁用、同步明细与四类分项来自持久阶段。
### Fixed
- 没有当前作业时不再把历史测试封存当作「固定测试版本」已完成:阶段勾选只来自当前作业的阶段证据,并提示「已有测试封存(历史)/ 本次作业尚未开始」。
- 准备信息注明依据哪次封存快照与范围,明确「不代表实时测试库已就绪」,没有放宽任何完整性检查。
## 开发快照 snapshot/v0.1.113 - 2026-10-28
### Changed
- 发布管理按已确认稿重做:顶部只保留「对内知识库 / 对外知识库」两个页签,切换后只渲染选中库的一个作业区,不再并排两张巨型数据卡。
- 同步前布局回到原稿:生产当前 → 拟发布版本行、新增/更新/删除文档与 Wiki 完整快照、准备清单(文档解析与索引、Wiki、数据完整性)、右上「版本与快照 / 启动同步」、下方左「本次变更」右「生产保护与恢复」、底部五段作业阶段。
- 同步中布局:顶部作业阶段/版本/已用时,中间四类数据卡(数据库、原文件、索引、Wiki),底部「同步明细」与异常说明;没有真实总量与完成量的项目只显示状态,不编造百分比。
- 「版本与快照」改为右侧抽屉:按选中库列出全部已封存记录,区分测试封存与生产生效/备份,展开看到文档/索引/资源/Wiki 分项数量、字节与逐项摘要;恢复入口在有真实恢复演练前保持禁用并说明。
- 存储口径从发布主页移到「系统设置 → 存储管理」只读区(跨版本去重内容、blob 表观/分配、未引用、清单),保留策略/依赖保护/清理预览标未接线,自动清理保持关闭。
- 生产段(当前版本、变更差异、预备份、同步、生产验证)没有真实数据的一律显示「未接入 / 尚无记录」;「启动同步」保持禁用并写明原因,不把发起封存冒充启动同步。
### Removed
- 主页面上的研发口径文案(A04/A06、schema、SHA、内部表名等)移入必要的分项详情。
## 开发快照 snapshot/v0.1.112 - 2026-10-28
### Changed
- 发布管理改为一版可操作的整库视图:只给「对内 / 对外」两个目标,把整库版本页的封存信息、完整性结果、分项数量/字节/摘要、封存作业状态与归档存储口径并入同一页;链路按真实能力标注「封存已接通、生产预备份未接线、同步未授权、校验/恢复未接线」,禁用项写明原因,不用假进度。
- 「整库版本」独立导航并入发布管理(不再保留第三个相近入口);文档级旧流程折叠保留在页底,并明确单篇文档发布成功不等于整库发布成功。
### Fixed
- R4:封存子进程不再被默认 SIGKILL。超时/取消先发 SIGTERM 并给宽限期(`StopGrace`),`libraryctl` 把 SIGTERM 转成上下文取消,让停机窗口走恢复路径;任何失败的封存/恢复操作之后 agent 都会再执行一次有界的 `recover`,恢复失败则进入降级状态、阻断后续封存并给出原因(`recover`/`status` 仍可用);agent 启动时先恢复上次遗留的停机窗口,关闭时等待在飞操作完成其恢复责任。
- R5:同一作业号换范围不再被回显成新范围。接口以已存 payload 为准,范围不一致返回 409(`library_capture_scope_conflict`)且不入新队;相同请求重放返回真实 `reused`/存储范围。
- R6:封存作业列表改为按类型查询(`ListByType`),不再先取全局 200 条再过滤;存储不支持按类型查询时明确返回不可用,页面不据此判断空闲。
### Added
- `ListByType` 作用域查询与测试;封存闸口单测(未接线 503、非法 400、范围冲突 409、重放回显)。
- 发布管理新增 6 条渲染测试(两个目标、真实数量、链路禁用原因、权限/未接线分支、作业态、存储口径)。
## 开发快照 snapshot/v0.1.111 - 2026-10-28
### Added
- 主机侧封存 agent(`libraryagent` + systemd user 服务):只监听一个 0700 目录里的 0600 unix socket,校验 `SO_PEERCRED` 同 uid,固定 binary/归档/日志/项目/写入者声明,请求只能选范围与作业号。
- 页面「发起封存」:`POST /api/v1/library/captures` 只入队 `library.capture` 作业,`GET /api/v1/library/captures` 读队列真实状态;未接线时 503 并给出原因,权限不足 403。
- worker `library.capture` 处理器:通过挂载的 socket 调 agent,启动时先做一次 `recover`;容器内**不挂** Docker socket、**不持有**来源凭据。
- `scripts/library-agent-install.sh`:构建、安装、启用 linger 并读回 socket 权限与服务状态。
### Changed
- 整库版本页显示封存作业的真实状态(待执行/执行中/重试/完成/失败与最后错误),完成后自动刷新快照列表。
## 开发快照 snapshot/v0.1.110 - 2026-10-28
### Fixed
- 同一作业号重跑不再必然冲突:命令先读持久日志与已封存清单,按首次请求(时间、范围、契约、绑定库)幂等重放,`source_read=false`、`writers_stopped=false`;同一作业号换了范围或绑定库仍明确拒绝,已封存清单校验失败也拒绝。
- 归档用量改按跨版本去重统计内容字节,并把清单、未被引用 blob、文件实际分配单独列出;不再把"各版本 unique 之和"当归档占用,也不再暗示可回收空间(真实两份清单:各版之和 55,309,695 字节,跨版本去重 55,309,693 字节,磁盘分配 55,373,824 字节)。
### Added
- 冻结窗口内的契约复核:窗口内重新读取来源契约,与窗口前不一致即中止(不封存、恢复写入者)。
- 写入者组之外的同镜像容器检查:若有不属于声明组的运行中容器使用声明写入者的镜像,拒绝封存。
- 补齐 `GET /api/v1/library/snapshots`、`GET /api/v1/library/snapshots/{id}` 的 OpenAPI 契约(上一版遗漏,契约测试可检出)。
### Changed
- 按用户确认取消 A06:文档、页面与接口说明不再把内容/问答/引用图片效果/性能测试列为封版或发布门槛;A04 数据完整性口径保留。
## 开发快照 snapshot/v0.1.108 - 2026-10-28
### Added
- 新增整库封存的真实业务入口 `libraryctl`(status / preflight / capture / list / verify / recover):从本机真实测试栈只读读取来源,核对处理已结清,推出兼容契约,在维护窗口内停住声明过的写入者,导出 PostgreSQL 十表、原始索引正文与向量、对象资源与 Wiki 快照,封存为内容寻址候选产物后恢复写入者并等到 healthy。
- 新增按 Compose 项目固定身份的冻结控制器:整个项目逐服务声明,出现未声明服务或容器被重建即拒绝,只发 SIGTERM,恢复后必须 healthy。
- 新增「整库版本」页(只读):对内/对外最新封存、分项数量与字节、摘要、兼容契约、归档占用;未接线时明确显示原因,不提供未实现的封存按钮。
- 新增运维手册 `docs/library-capture.md`,含 2026-10-28 两次真实封存的快照 ID 与实测数字。
### Changed
- api 增加 `LIBRARY_ARCHIVE_ROOT` 与整库归档根的只读挂载(写入只发生在主机上的 libraryctl)。
### Not included
- 本快照不代表整库业务验收通过:内容/问答/性能验收未绑定,跨系统一致切点、Wiki 删除语义重算、生产预备份与恢复演练、双闸授权、整库同步协调与失败续作仍未完成。
## 开发快照 snapshot/v0.1.105 - 2026-10-28
### Fixed
- 修复成功作业重放遗漏验收策略检查的问题:旧快照缺少当前策略标识时明确拒绝,不静默升级验收口径,不返回成功状态或快照 ID,也不重新暂停测试写入服务。
- 新策略的正常重放与跨库、归档丢失等保护保持有效;此修复不代表内容/问答/性能验收绑定或整库业务链已经完成。
## 开发快照 snapshot/v0.1.104 - 2026-10-28
### Changed
- 按已确认的验收口径 A,正文索引作为原始产物保留,不再因无法从数据库反推临时生成上下文而拒绝候选归档;身份、覆盖、摘要及持久化生成问题的精确核验保持不变。
- 候选清单加入受支持的验收策略标识并纳入快照身份。候选归档不代表语义验收或生产放行;真实冻结接线、快照绑定的内容/问答/性能验收与完整同步恢复仍待完成。
## 开发快照 snapshot/v0.1.103 - 2026-10-28
### Fixed
- 整库索引校验支持从分块持久元数据核验生成问题:严格匹配分块、问题 source_id 与问题原文,拒绝未知、过期或内容不一致的索引。
- 分开核验正文与生成问题的覆盖,问题记录不能掩盖正文索引缺失;元数据中已记录的问题缺少索引同样阻断。未放宽临时章节上下文或未知索引变换的检查。
## 开发快照 snapshot/v0.1.102 - 2026-10-28
### Fixed
- 阻止旧式 status=0 普通分块缺失索引时被误判完整;仅明确的上下文父分块允许没有索引,显式标记已索引的分块仍须存在。
- 缺少可核验的无索引策略时返回覆盖未证实,不推断索引已禁用;Wiki-only 无索引库的完整支持仍待实现,不因此宣称整库验收通过。
## 开发快照 snapshot/v0.1.101 - 2026-10-28
### Fixed
- 修复正常正文索引因标题前缀或正文首尾空白处理而被整库归档误拒的问题;仅按已持久化的标题与正文重建确定性内容,保持逐字核验,不使用后缀包含匹配。
- 无法由持久数据核验的临时章节上下文、非正文类型的变换及未知内容仍阻断;不将本次兼容修复视为完整索引或整库业务验收通过。
## 开发快照 snapshot/v0.1.100 - 2026-10-28
### Fixed
- 测试封存暂停共享写入服务前及暂停后,强制检查整个源数据库的解析状态、待处理任务和死信;其他库、其他租户及已删除或未知任务范围的未完成工作均阻断封存。
- 保留部署侧入口与后台作业检查;数据库任务结清不代表 Wiki 语义验收通过。本次未清理死信、未暂停实际测试 WeKnora、未执行生产发布。
## 开发快照 snapshot/v0.1.99 - 2026-10-28
### Fixed
- 修复整库归档将 Wiki 页面与问题记录的字符串状态误按分块整数状态解析的问题;分块状态仅在分块表中解析,Wiki 状态和正文原样保留。
- 候选捕获回归改用非空 Wiki 页面与问题记录,防止空表夹具漏检。本次仍不代表完整同步恢复业务已交付。
## 开发快照 snapshot/v0.1.98 - 2026-10-28
### Fixed
- 修复整库候选封存只单向校验搜索索引的问题:数据库标记为已索引的分块必须在导出索引中存在,否则拒绝封存;按分块身份而非总数量核对,避免重复索引掩盖缺失。仅存储的父分块仍允许不建索引。
- 本修复增强候选完整性,不代表整库业务入口、生产同步恢复或语义验收已经完成。
## 开发快照 snapshot/v0.1.97 - 2026-10-28
### Fixed
- 补齐已部署 0.1.96 的独立开发说明与精确提交绑定,恢复版本记录核验所需的开发快照证据;不变更正式版本号,不声称整库同步已经交付。
- 更正整库验收文档中仍写“未提交、未部署”的历史状态,保留未接线、未验收与生产执行边界。
## 开发快照 snapshot/v0.1.96 - 2026-10-27
目标提交:9105f8316cc870a62b5ddacd1496f0cb0f798d87
### Added
- 增加整库 PostgreSQL 只读快照、Elasticsearch PIT 导出及独立资源归档,包含完整 Wiki 表、资源关联和摘要校验。
- 增加测试写入服务暂停、持久恢复日志、异常与进程中断恢复原语,并实现候选捕获协调函数。
### Changed
- 本版本仅交付基础模块:尚无整库业务调用入口、版本验收绑定、生产预备份与同步恢复工作流、新发布页面及完整存储治理。隔离测试通过不等于完整业务验收通过;不包含生产执行或实际清理授权。
## [Unreleased]
### Added
- 新增解析监控统一工作台:顶部运行指标与依赖卡片、下方单一文件处理表;来源文件与目标测试库归并最新版本,动作、阶段、状态分列,后端统一筛选、计数及分页。支持单条/跨页勾选重试和版本、失败、重试详情;测试库移除重试独立核验治理规则,不误走上传流程。取消六分页和手动开始入口,生产授权仍在发布管理;本次不改变后台自动调度策略,自动处理闭环需另行端到端验收。
- 问答记录新增“本周”快捷查询,默认展示北京时间本周数据;所选范围自动读取全部记录,校验完整数量后展示,不再需要分页或加载更多。固定展示完整字段,空列不再隐藏,中文原文直接展示、外语未翻译明确标记;保留源端有界传输、取消和失败保护。
- 问答记录新增年份与自然周选择,周次展示起止日期,支持跨年及每年实际周数;按北京时间周一至周日填入查询范围,默认本周且仅查询截至当前的数据。保留自定义时间与原查询按钮,浏览器时区不改变查询边界。
- 问答运营按已确认口径显示知识覆盖率、回答正确率及原始分子/分母、排除数量与构成。使用完整筛选范围和当前有效人工优先结论,精确规则命中的未覆盖可进入覆盖分母;部分覆盖不算完全覆盖,待确认/未评估/失效/不计入按对应指标排除,零分母显示“暂无可计算数据”。返回独立规则版本,AI 模型与绩效范围仍未接入。
- 问答详情支持治理侧人工复核及不可变审计:覆盖、正确性、产品分类、问题类型、根因与理由由管理员明确填写,操作人取登录身份;保存后独立读回复核历史。保留源端判断和自动规则,输入变化使旧人工结论失效,已观测的内容回退不自动恢复旧结论;并发修改冲突、重复请求不重复留档。
- 新增覆盖复检历史、已采集输入快照和完整筛选范围质量数量。批量统计只传指纹与规则命中元数据,原文在读取问答/复核时采集,未采集版本明确提示而非伪造空快照;源事务时点阻止延迟旧扫描回退新输入。基础数量与本期经确认新增的质量比例分开显示;不接 AI 模型、不定义绩效分母、不回写 DobotAI 原问答。
- 「使用统计 → 个人月明细」按北京时间自然月及截止时点展示当前应用可见名册的人员用量(包含本源零记录),支持部门含子部门、区域/待核对状态、姓名/完整部门路径和已有提问/零记录筛选。稳定身份关联去重、不可关联问答单列、过期组织版本阻断翻页;源端姓名可缺失并提示重新同步,不按姓名匹配身份。姓名筛选通过有界只读 POST 请求体传递、不进入 URL,原始员工标识不返回浏览器;名册不作为在职/考核分母,当月进度不作为绩效结论。
- 问答运营按稳定身份关联当前治理通讯录,缺失部门/区域补全并保留源快照及目录/区域版本;空列判断包含完整筛选结果,不混合配置已变化的分页。新增完整时间/关键词范围的使用统计,问答计次、名册内人员去重,跨区/未匹配/身份缺失分列,不输出绩效结论;不回写 DobotAI 原表。
- 「问答运营 → 区域使用」支持管理员从网页发起持久化通讯录同步,重复请求重放、进行中操作合并;按服务器固定范围注册后台执行器,未接线或未检测到心跳时不创建新任务。界面显示真实任务状态、不编造百分比;失败保留旧快照与区域规则,同步完成不覆盖未保存草稿。旧执行器丢失租约后不能激活结果,重启接管和已提交快照的续跑使用原任务。
- 新增治理侧通讯录实时桥接,复用既有周报读取能力,向源端读取真实缺失祖先以补齐部门树;不改旧周报脚本、配置或输出。私有 JSON 与受控子进程执行器拒绝不完整数据、模拟同步、公开文件和异常输出,超时清理临时人员文件。同步默认关闭,容器启用仍需批准的既有周报 SDK/配置及兼容 Python 运行时挂载。
- 区域归属表支持长部门路径换行,避免继承全局不换行规则后将生效区域与归属依据列挤出桌面面板。
- **「问答运营 → 区域使用」区域归属配置切片**:按稳定部门 ID 搜索和勾选完整路径,预览去重归属人数,保存版本后重新读取;父部门包含子部门、子部门显式配置优先,多部门跨区域人员单独待核对。预览不写入,过期版本和不完整部门树被阻断。基础使用统计见本期新增,不代表绩效验收;翻译及 AI 评估模型接入按用户要求暂缓。
- 新增治理侧组织快照与区域修订迁移、受确认保护的后台导入命令;不修改 DobotAI 原表或钉钉组织结构。
- **「问答运营 → 问答记录」首个开发切片**:在现有导航中查看 DobotAI 2.0 钉钉问答,支持半开时间范围、原问题关键词、稳定游标分页和完整详情;按整个筛选结果隐藏空列,反馈聚合不放大问答条数。外语翻译、AI 评估尚未接入,不标为第一阶段完成。
- 治理侧重新检查答案:包含 `暂无已发布` 即未覆盖,引用和源端 covered 不能抵消;未命中仍待评估,源端规则单独保留作对照。独立只读源默认关闭、租户后端固定、管理员会话鉴权,源故障不回退治理主库;安全告警明确保留 TEMP/TLS 尚未整改状态。
- **「版本记录」页**(一级入口):只读时间线追踪**远端 main 可达的全部提交**(含合并提交与已并入 main 的侧分支提交),按提交时间倒序显示标题/正文、`[正式版本] / [开发版本] / [历史提交]` 徽标、短 SHA、提交内变更记录与 `[查看提交]`/`[查看 tag]`;支持「全部 / 正式 / 开发 / 历史」筛选与游标翻页,窄屏单列。
- 新增只读接口 `GET /api/v1/build-info`(当前运行产物的构建身份)、`GET /api/v1/version-history`(快照游标分页,不返回伪造的 total)、`POST /api/v1/version-history/sync`(仅管理员手动同步,并发同步复用同一作业)。
- 新增迁移 `000046_version_history`(提交记录 + 同步状态):`commit_sha` 是唯一身份,**版本号不是唯一键** —— 两个提交算出同一个开发号时各占一行,用 SHA 区分。
- 「当前运行」只认运行产物注入的**完整 SHA 精确匹配**:匹配到就标该条;开发构建显式标注"开发构建";拿不到标识就显示"当前运行版本未知",**不拿最新 main 或最新 tag 顶替**;CI 成功不等于已部署。
### Changed
- 「系统设置 → 数据源配置」**收敛成两个入口**:**dws 导入**(按当前钉钉账号列出可访问的知识库与钉盘空间)与 **PP 导入**。AList(已由钉盘文件替代)、静态登记的钉钉知识库、本地手工导入与 PP legacy 都**不再出现在这一页**;已接入的这类来源用**归档**下架(`POST /api/v1/source-connections/{id}/archive`,管理员、幂等,带写意图头与幂等键):从列表、来源与目录、计划刷新与手动扫描里消失,**目录快照、发布记录与审计数据一行不删**。恢复用 `…/restore`(保持停用、不自动带回调度),或让 dws 重新登记同一来源 —— 钉钉空间登记是确定性 ID + `ON CONFLICT`,**原地复活同一行、不产生重复**。卡片上新增「去掉来源」按钮(管理员,带确认弹窗)。
- 迁移 `000047_source_connection_archive`:新增 `archived_at` / `archived_by` 与只覆盖未归档行的部分索引;列表、目录、计划刷新、手动扫描四类查询一律排除归档行(漏一条就等于入口没去掉)。
- 「系统设置 → 数据源配置」默认只列 PP 资料与「可加入的来源」:AList 来源(已由钉盘文件替代)与静态登记的钉钉知识库来源(改为按当前钉钉账号发现)**不再默认列出**,收进「显示 AList/钉钉知识库来源(N)」折叠开关 —— **来源一个都没删**,展开后照旧可改刷新周期与启停,「来源与目录」里的内容不变。手动导入来源(本地手工验收)不在这次范围内,照旧显示。
### Fixed
- 修复钉钉新登记来源仍按旧离线目录身份取件的问题:从当前来源配置读取空间坐标,实时枚举所在目录并严格匹配节点身份;同名文件不替代、目录不完整不误报删除。修复解析监控遗漏 `version_id` 取件任务及缺少业务元数据的失败记录,正确显示失败原因、重试入口与历史。
- 修复通过带端口的 HTTP 地址访问问答运营时,正常同源操作被误报“拒绝跨站配置请求”:网页代理保留原始 Host 及端口,使缺少 Fetch Metadata 的浏览器也能正确执行 Origin 校验。保留后端跨站、异端口、伪造转发头与缺失操作意图的拒绝保护。
- **登记钉钉知识库失败(HTTP 409 `space_registration_conflict`)的真实原因与修复**:`source_connections.name` 有唯一约束,而早期路径登记的钉钉来源(`base_url` 为空、id 与 dws 现在推导的确定性 id 不同)被**归档后仍占着名字槽位** ⇒ 重新登记时 INSERT 撞唯一约束;处理器又把**所有**存储层错误都翻译成同一个 409,真实原因被吞掉(线上只能看到一个无细节的错误码)。现在:① 同名来源**已归档**时,把它改名腾位(`…(已下架 · <短ID>)`,归档行不显示、数据与审计一行不动);② 同名来源**仍在使用**时返回 `space_name_conflict` + 可行动提示(先「去掉来源」);③ 同一登记键指向别的空间且该行仍在用时返回 `space_identity_conflict`(已归档的行则允许就地改写 —— 这就是"去掉后再用 dws 重新获取");④ 其余内部故障改回 **500** `space_registration_failed`,不再伪装成冲突;前端优先显示后端 `hint`,并为这两个新码补了中文说明。
- 前端入口与静态资源的缓存语义("新功能看起来没上线"与"页面偶尔裸奔/空白"的根因):
- `index.html` 此前**没有缓存头**,CDN/浏览器按启发式缓存它 ⇒ 页面会一直停在旧构建上(实测被腾讯云 EdgeOne 卡住:源站已是新包,公网入口仍发旧包)。现在 `index.html` 每次回源校验(`no-cache, must-revalidate`);
- 带内容哈希的 `/assets/` 长缓存(`immutable`),但**未命中时先回退到"上一版构建的资源"目录、再 404**(`deploy/legacy-assets` 由部署脚本在每次重建前落盘并只读挂载):CDN/浏览器仍持有旧 `index.html` 时,它引用的旧 JS/CSS 依旧能取到 ⇒ 不会出现"JS 404 白屏"或"CSS 404 只有文字没样式";
- 明确 404 时不再回退成 `index.html`(否则浏览器把 HTML 当 JS 执行,报一堆语法错误)。
- **上线前护栏 + 事故复盘**:`scripts/deploy-api-web.sh` 现在先校验 `.env` 的键值形状(值里再出现 `KEY=` 或键名不合法 ⇒ **拒绝上线**),配置校验失败时也会给出"两行粘成一行"的线索。事故:`.env` 最后一行没有结尾换行、又被追加新键 ⇒ 两行粘成一行 ⇒ `VERSION_HISTORY_GITEA_BASE_URL` 变成非法值 ⇒ api/worker 按设计拒绝启动、崩溃重启(平台整体不可用)。已修数据、加护栏、加回归用例。
- 「版本记录」页的链接改为**抓取与链接分离**:抓取仍走配置的(内网)地址,页面上的「查看提交 / 查看 tag」用 `VERSION_HISTORY_LINK_ORIGIN`(公网 origin)拼 —— 原先两者共用内网地址,内网外的浏览器点开是空白;另外 tag 记录原先不带链接(空 `href` 点下去没反应),现在补上链接、没有链接就不渲染该按钮。
### Security
- 版本记录同步**匿名只读**远端仓库(**不使用任何凭据**),来源限配置里批准的 origin 与固定仓库:拒绝任意 URL、拒绝跨主机重定向,单个响应设大小上限与超时、5xx/429 才有退避重试。
- 提交正文与变更记录一律按**纯文本**渲染(不产生 HTML/脚本注入面);页面不含打 tag / 发布 / 部署入口;同步动作仅管理员可用,服务端再次判角色。
> **版本号的唯一来源就是本文件顶部那条已发布版本。** 不放 `VERSION` 文件、不把版本写进源码常量。
>
> **正式版本**用**注释 tag** `vX.Y.Z`(由人批准后创建):CI 只**核验**「tag 的版本 == 它指到那个提交里的
> 版本记录」,**不自动升版本、不打 tag、不发 Release**。日常改动写进 `[Unreleased]`;
> **不要求每个提交都改本文件** —— 有使用者可见的变化才记,其余走提交信息与档案。
>
> ```bash
> python3 scripts/version.py --identity # 构建身份:release_version / build_kind / commit_sha / display_version / built_at
> python3 scripts/check_changelog.py --tags # 结构 + tag 一致性(无正式 tag 不判红)
> ```
>
> 小节**只认四类**(分类可省略,但**不要空栏目**):`### Added` / `### Changed` / `### Fixed` / `### Security`。
> **破坏性变化**写在 `### Changed` 里、以 `**破坏性**` 开头,并链接发布文档(见 `docs/RELEASE_RUNBOOK.md`)。
>
> 过程记录(审查轮次、命令输出、逐条修正明细)不入本文件:归档在 `docs/archive/`,
> 未合并事项在 `docs/BACKLOG.md`。
>
> **版本怎么走**(两行就够):
> · **正式版本** = 下面那条 `## [x.y.z] - 日期`,是**发版事实**,由人批准后打注释 tag `vX.Y.Z`;
> · **开发版本自动递增** = `<正式版本的主.次>.<自那个 tag 以来的提交数>`(正式 `0.1.0` 之后第 12 个提交 =
> `0.1.12`),由 `scripts/version.py` 从 git **算出来**;开发交付的独立说明写入本文件“开发快照”章节,并打 `snapshot/vX.Y.Z` 注释标签 ⇒ 每次开发交付均可追溯,
> 但"发布版本"仍然只能由人定(CI 不会自动升版本、不会打 tag)。
## 开发快照 snapshot/v0.1.63 - 2026-10-25
### Changed
- 周报固定保留编辑正文与排版预览的对照界面,移除编辑/预览/对照切换按钮及无用模式状态/样式;自动保存、复制排版内容和下载保持不变。
## 开发快照 snapshot/v0.1.62 - 2026-10-25
### Added
- 周报增加“复制排版内容”,复制渲染后的富文本及可读纯文本,不复制Markdown标记或原文查看器;编辑/预览/对照模式均取当前最新正文,保留下载MD原文。兼容局域网HTTP复制并明确报告失败。
## 开发快照 snapshot/v0.1.61 - 2026-10-25
### Changed
- 周报中英文正文移除质量统计月份、时间范围及分母规则说明,保留覆盖率、正确率及原自然月计算口径与来源审计。
## 开发快照 snapshot/v0.1.60 - 2026-10-25
### Added
- 右侧问答详情顶部增加上一条、下一条,沿当前筛选后的完整已载入表格顺序切换;首尾自动禁用,后续页载入后自动开放下一条,读取中或失败仍能切换,关闭保留原表格焦点。
## 开发快照 snapshot/v0.1.59 - 2026-10-25
### Fixed
- 流水表表头五项筛选不再传入统计看板;看板及PNG只按上方已查询的时间范围和关键词统计。保留表头对流水、明细导出及对应AI范围的限定,并拒绝将带字段筛选的返回误作全范围看板。
## 开发快照 snapshot/v0.1.58 - 2026-10-25
### Changed
- 问题流水表最左侧增加序号,按当前筛选结果从1连续编号,后台续载与虚拟滚动保持连续;不改变业务字段、原始数据或完整导出口径。
## 开发快照 snapshot/v0.1.57 - 2026-10-25
### Changed
- 五项质量筛选移至表格首行对应列,选择后立即查询;上方查询区不再增加字段选项,零匹配和续载时仍可调整表头条件。
- 未覆盖问答的是否正确统一为待确认;仅未覆盖可显示待确认,已覆盖保留正确/错误及点踩优先。列表、详情、筛选、统计、完整导出和编辑约束共享同一有效规则,原始问答及历史人工/模型证据保留,不重新调用模型。
## 开发快照 snapshot/v0.1.56 - 2026-10-25
### Fixed
- 五项字段筛选独立换行,保留时间、关键词、查询与同步操作的完整可见范围,修正宽屏旧单行样式挤出筛选区的问题。
## 开发快照 snapshot/v0.1.55 - 2026-10-25
### Changed
- 问题流水表首批到达即可浏览,后续在后台自动续载;列表仅传轻量文本预览,完整问答/引用/媒体按需读取,完整明细另行导出,避免等全部正文下载后才显示。
- 新增是否覆盖、是否正确、产品分类、问题类型、根因分析五项筛选,可与时间/关键词组合并查询未设置值;后端按完整总表的当前有效标签筛选,列表、看板及导出条件一致。字段筛选时不套用无明细的历史汇总。
### Fixed
- 续载异常明确保留已读部分和失败状态,查询变更取消旧请求;详情保存和AI更新不被迟到分页覆盖,完整导出拒绝使用截断预览或不完整结果。
## 开发快照 snapshot/v0.1.54 - 2026-10-24
### Changed
- 回答正确性改为直接依据原问题和实际回答判断:已覆盖只保留正确或错误,只有未覆盖允许待确认,不再要求引用原文作为正确结论的前提;未形成有效回答的技术状态保持未评估,点踩仍优先为错误。
- 模型评估、人工复核和详情字段编辑共同约束上述规则。旧已覆盖待确认可限定为仅重评正确性,通过统一有效层写入和不可变审计更新,不修改原始问答、其他已维护字段或历史证据。
## 开发快照 snapshot/v0.1.53 - 2026-10-24
### Changed
- 版本记录按参考图重排为当前构建信息卡和紧凑折叠列表,当前运行记录默认展开,其他记录收起;变更按新增、调整、修复、安全分类展示,原始说明和提交信息可展开核验。
- 增加所有角色可用的只读刷新,保留管理员同步、真实版本身份、筛选及完整历史加载;北京时间、长提交与手机排版统一处理,不增加发布或部署权限。
## 开发快照 snapshot/v0.1.52 - 2026-10-24
### Changed
- 移除系统顶部的已打开页面标签栏及关闭入口,保留左侧功能导航、当前位置、来源路径和表格分页。
- 左侧导航文字放大至16px、图标放大至20px,图标区域24px、导航项最小高度44px,手机抽屉同步生效。
## 开发快照 snapshot/v0.1.51 - 2026-10-24
### Changed
- 按人视图也直接展示所属部门名称,多部门逐行显示、完整路径悬停可看,去掉“个直属部门”折叠摘要;列宽与按区域统一。两个排行复用同一展示实现,移除不再使用的旧分支和样式;统计、兼属和50人分页不变。
## 开发快照 snapshot/v0.1.50 - 2026-10-24
### Changed
- 按区域人员表“所属部门”直接显示部门名称,多部门逐行列出并保留完整路径悬停说明;移除该视图的“个直属部门”折叠入口。人员与部门列加宽,区域列收紧;按人视图保留原详情入口和列宽,排序、去重、周期与分页不变。
## 开发快照 snapshot/v0.1.49 - 2026-10-24
### Changed
- 区域统计“部门/人员”列移除“本部门直属 N 人”小字;保留独立总人数列、部门展开、直属人员明细和兼属标记,统计数据与其他分析视图不变。
## 开发快照 snapshot/v0.1.48 - 2026-10-24
### Changed
- 区域统计按部门表格将“总人数”独立为一列,放在“访问人数”前;移除原访问人数格内的小字总人数。保留含子部门的稳定身份去重口径,人员行对应总人数/访问人数/访问比例均显示“—”,不改变一级默认展开及另外两个分析视图。
## 开发快照 snapshot/v0.1.47 - 2026-10-24
### Changed
- 区域统计按部门视图默认只展开到一级部门,二级及以下初始收起;保留手动逐级展开、直属人员、既有统计口径及按区域/按人视图。
## 开发快照 snapshot/v0.1.46 - 2026-10-21
### Added
- 区域统计新增“按部门/按区域/按人”分析切换;按部门保留原树形与直属人员。按区域查看已保存区域内人员,按人查看完整去重名册,均按提问次数降序并保留零提问人员,固定50人/页与页码选择,展示区域、所属部门、次数和最近提问时间。
### Changed
- 新分析复用同一累计/自然月/自然周快照,不额外创建模型或聚合作业;同区域多部门人员只列一次,跨区域及未归属者单列待核对,不自动选主区域。分析分页与切换只读,不改变原问答、区域映射、部门汇总、已保存周报及外部发送闸门。
## 开发快照 snapshot/v0.1.45 - 2026-10-21
### Added
- 区域统计部门展开后展示直属人员及直属人数;人员行保留本统计范围的提问次数、最近提问时间与零使用人员,多部门人员显示“兼属 N 部门”,悬停可查看完整部门路径,也可点击或键盘展开名单。人员行只读,不参与部门勾选或区域绑定。
### Changed
- 兼属按稳定身份的不同直接部门去重,不把祖先继承计为额外部门,不合并同名不同人。累计、自然月及自然周统计范围、分支/区域去重人数与次数保持,展开人员不会重复累计;无部门关联人员明确列出,未命名如实提示,不新增绩效裁决、源端修改或模型调用。
## 开发快照 snapshot/v0.1.44 - 2026-10-21
### Changed
- 问题统计看板去掉个人月明细入口,十一张卡片右上角新增“统计口径”,悬停、键盘聚焦及触屏可查看实际范围、公式与排除数量;图片导出保留完整十一项及口径入口,不包含交互浮层。
- 覆盖率与正确率趋势改为北京时间逐自然月独立计算,每月1日重新累计;使用总表当前有效结论、人工优先及点踩否,分别按是÷(是+否)计算,待确认等排除,空月/零分母留空。旧周上报、冻结及第40–43周累计证据保留,不再混画到月轴。
- 每周看板的两项质量仪表与新建/主动重新预填周报使用月内累计;跨月周取周一所在月并截止该月底,非跨月周截止该周实际时点,当前周不预读未来。保留已保存周报正文、修订与重试标识,不自动改写旧稿,不修改源问答或触发付费模型及对外发送。
## 开发快照 snapshot/v0.1.43 - 2026-10-20
### Added
- 知识覆盖率与回答正确率趋势新增第40–43周“总表累计”绿色曲线:按北京时间各自然周截止纳入整张总表的累计问题,使用当前有效字段及点踩结论动态计算;覆盖率和正确率分别使用“是÷(是+否)”,待确认等未明确结论不进入对应分母,零分母不伪造百分比。逐点展示实际截止、累计条数、分子分母及规则版本,保留原历史上报和冻结证据。
- 字段依据区区分真实模型返回与本地规则/历史唯一标签复用:语言识别、中文原文保留及历史分类复用逐字段展示来源与回填状态,明确标注“非模型”,不把外部受控任务冒充平台服务模型配置。
- 看板每30秒顺序读取最新总表,当前选周截止推进至当前时点,已闭周查询保持原范围;离开或换条件取消旧请求,读取失败明确提示,不展示过期图表或导出旧图。
## 开发快照 snapshot/v0.1.42 - 2026-10-20
### Fixed
- 问题存在当前点踩时,总表有效“是否正确”固定为“否”,列表、详情和动态质量统计使用同一规则;通过已保存来源关联读取本地镜像最新反馈,避免导入快照漏掉后续点踩。仅点赞或无反馈不推定正确,原问答、旧模型结论、人工复核与字段修改审计保持不变。
- 详情对已点踩问题锁定“是否正确”并简洁说明;后端拒绝改为“是”或“待确认”的冲突选择,其余字段仍可编辑。迁移59只补充派生读模型、反馈索引及总表快照失效通知,不回写 DobotAI 源数据,也不重算或覆盖旧周冻结与历史上报。
## 开发快照 snapshot/v0.1.41 - 2026-10-20
### Added
- 周报生成升级为按北京时间自然周保存的 Markdown 编辑器,提供编辑、预览、对照、自动保存、立即保存及 MD/本周明细 CSV 下载;每个租户每周一份共享草稿,切周或离开前保存未提交输入,刷新与重新打开恢复正文。
- 按周报模板预填英文在上、中文在下的各六节正文,复用正式总表和已保存区域统计;历史周优先使用相应上报/冻结质量指标,缺失才使用当周实时指标。统计建议可人工修改,重点保留待填写,不编造 FAQ 调整或英文事项,不自动发送或归档。
- 治理库新增迁移 58:独立稀疏字段覆盖、不可变字段改动审计、周草稿及逐版保存收据。保存绑定输入与编辑版本,同 nonce 重试不重复留档,冲突和读回失败保留输入;已有草稿重新预填须明确确认,存在人工数据时拒绝删除式降级迁移。
### Changed
- 详情顶部五个质量字段可直接下拉修改,范围取源主表实际使用的覆盖/正确性选项及与模型共享的产品、问题、根因字典;只保存明确选择的字段,不补齐其他空白、不把模型值冒充整条人工复核。移除单独人工复核/反馈展示,保留原始问答、旧人工历史和后台留痕。
- 区域统计的月份及 ISO 周同时支持下拉选择和直接输入,合法值在回车或失焦后应用;未完成、非法及未来输入在本地提示,不发送错误统计请求,保留原自然月/周和缓存口径。
## 开发快照 snapshot/v0.1.40 - 2026-10-19
### Added
- 区域统计明确支持按累计、北京时间自然月、北京时间自然周三种范围;自然周按周一至周日,可选择历史周,校验跨年周次、非法周和未来周,独立缓存包含周次及组织版本。
### Changed
- 部门树默认显示公司、中心及二级部门,更深层收起;保留手动展开与搜索,父行去重人数和提问次数不随收起变化。
- 选中部门后在当前统计页选择区域并绑定,复用预览、双版本锁和保存后独立读回,不跳转另一张映射表;父选仍覆盖后代,既有非选中部门规则保留。
- 区域设置重做为原页面内的名称列表面板,支持新增、改名、删除及未保存草稿保护。删除已绑定区域须确认解除直接绑定,目录与规则一次保存,不删除原问答;重复点击原删除位置只取消确认。
- 区域筛选及绑定选项取已保存目录,新增/改名/删除后刷新,删除当前筛选项回全部;问题统计看板的平均使用频率和使用覆盖率沿用同一已保存组织配置,保留自然月口径与零分母未知,不重新计算冻结历史。
## 开发快照 snapshot/v0.1.39 - 2026-10-19
### Changed
- “问题流水”改为“问题流水表”;详情从右侧非模态弹出,不再生成左侧遮罩或焦点陷阱。移除详情中的区域、组织关联/版本/快照、记录来源、问答状态与流程触发人员展示,原始数据与审计不删除。
- 是否覆盖、是否正确、产品分类、问题类型、根因分析置于详情顶部固定区域,正文独立滚动;查询与立即同步统一尺寸和基线。
- 字段评估与中文翻译统一为“AI分析”,分别预览缺口、确认后按实际需要创建两类持久任务;部分成功重试保留幂等标识,跳过已确认种类与零缺口种类,不覆盖人工或已有非空结论。
### Fixed
- 大流水表保留全量载入与无分页滚动,改为仅渲染可见行和缓冲行、稳定行引用及固定列宽;列表长文本限预览,详情与导出仍取完整原文,重复后台状态不再驱动整表刷新。
- AI执行改为数据库准入控制的默认4并发、独立任务租约与过期栅栏;慢模型调用不阻塞其他工作槽或自动扫描。429遵守共享有界冷却;输出截断单独归因,最多三次有界重试并逐次扩大输出预算,最终失败保留可见状态。
- 兼容源引用的knowledge_title字段,不将只有标题的引用伪造为正文证据;证据不足仍待确认,不将模型自报置信度当作准确率。修正人工复核区仍声称AI未接入的旧提示。
## 开发快照 snapshot/v0.1.38 - 2026-10-19
### Added
- 区域统计新增可展开部门树:部门、访问人数、访问比例、访问次数、最近提问时间五列,访问人数附当前通讯录总人数;整支含自身和全部子部门,按稳定人员身份去重,调岗依当前归属,零分母显示“—”,收起不重算数字。
- 统计仅读独立治理总表中的提问事件,支持累计与北京时间自然月;单并发后台聚合、请求合并和60秒缓存,目录或区域配置版本变化立即隔离旧缓存;加载/失败不伪装零数据,不占用代理转发链路。
### Changed
- 原“区域配置”入口改为“区域统计”,区域设置保留自定义区域新增/改名、未使用区域删除、部门映射预览与双版本锁保存;勾选父部门联选全部后代,取消子支时解除祖先整支选中,防止未选部门被父级新规则误覆盖。
- 统计与区域设置共享选择;搜索、按区域查看及展开收起只改变可见行,既有总表、原始问答、历史趋势、模型任务与生产数据不修改。
## 开发快照 snapshot/v0.1.37 - 2026-10-19
### Added
- 统计看板的覆盖率、正确率趋势支持独立持久保存的用户历史报表值,按北京时间ISO自然周展示、精确到两位小数并注明来源;和系统冻结使用分开的曲线,不伪造记录数、分子分母或冻结时间。
### Changed
- 历史汇总只影响两个趋势图,不覆盖当前周质量或原始问答;关键词筛选不挪用无明细的历史汇总,所选截止后的数据不提前展示,缺失周留空。页面和完整PNG共用相同绘图数据。
- 增加治理侧迁移57:历史统计独立表保留来源指纹与导入审计,禁止原位覆盖/删除,存在历史数据时拒绝破坏性down;普通来源同步不能覆盖该表,不修改DobotAI生产或既有冻结证据。
## 开发快照 snapshot/v0.1.36 - 2026-10-19
### Added
- 管理员可独立配置、真实测试问答评估模型;字段分析与原问题译中支持单条/筛选范围预览、后台持久批次、暂停/恢复/失败重试、真实进度及逐字段依据与模型版本。自动处理默认关闭,首次启用排除历史,关闭暂停未开始任务并停止新增。
- 原问题采用本地语种识别,排除型号、协议、URL与代码的干扰;中文直接复用原文,外语译中保留原始问题和既有人工译文。历史流程人员摘要保留原对象,并使用明确稳定身份与谨慎的唯一姓名兜底关联目录。
### Changed
- 全部问答看板与PNG改读独立正式总表的有效字段,保留旧源和冻结历史,不把历史标签伪装成人工复核。覆盖固定执行精确连续文本规则A,模型只补正式字典内四字段,`待确认`、`未知`及正确行合法空根因不重复排队。
- 流水查询/同步区紧凑展示,移除两条指定技术提示与常驻本地库数量,保留真实失败和权限阻断;支持双击详情与固定浅底深字标签。新增治理侧迁移54–56,不修改DobotAI源库;新源事件结束后才首次收录,后续源更新或删除仍不覆盖总表。
### Fixed
- 总表有效字段使用明确版本来源,前端不再用旧纯规则/人工契约拒绝历史与AI有效标签;原源数据契约和非空人工结论仍严格校验。
- 历史导入记录允许管理员手动补缺,正常自动入场仍限新成功问答;持久重试保留累计尝试次数与固定分母,过期租约及输入/人工版本漂移阻止迟到结果覆盖。
### Security
- 独立主密钥加密存储、write-only服务密钥、管理员同源操作、乐观版本和幂等收据;不回显或默认复制Agent密钥,录入仅有效HTTPS/本机回环。模型请求保留公网地址校验、禁止重定向与证书校验,允许独立安全DNS适配Fake-IP而不放宽私网边界。
- 调用只携带所需原问答/获准片段,不发送无关人员与部门;保持原始payload与历史非空业务值,不回写源生产库、不启动全历史处理或外部发送。
## 开发快照 snapshot/v0.1.33 - 2026-10-19
### Added
- 问题流水读取治理侧独立正式总表,合并历史 Excel 与已收录的 2.0 问答;历史译文、答案、覆盖/正确标注、分类及人员字段保留原值,缺失不造值,来源可辨。
- 新增受输入哈希、已批准逐行清单和真实恢复备份约束的历史导入命令,固定首表与提问时间/提问人/原始问题组合;保留批次、原字段、行号、重复跳过与缺字段暂缓证据。重复导入只读回收据,不覆盖后续主表修改。
### Changed
- 新增治理侧加表迁移 53。2.0 镜像仍独立同步,正式主表只接收新事件;稳定来源关联与原始事件键不随本地修改丢失,源端更新和删除不覆盖或清除主表。
- 主表评估/复核与可替换的源副本分开持久化;首次收录保留已有评估及审计来源,后续人工复核绑定主表自身的输入版本,不被源同步失效。原 2.0 看板、历史冻结及周报统计范围不因导入被静默混算。
### Security
- 主表列表、详情、复核与历史接口复用固定租户、管理员会话、严格参数、同源写意图、乐观锁和幂等回执;拒绝缓存、稳定 ID 与原始溯源不可漂移。字段计数和质量范围只读标量元数据,不为全范围统计解压长答案。
- 导入仅连接本地治理库,不连接或修改 DobotAI 2.0 生产源、现有隧道与外部表;应用回退保留加表,有历史导入、本地修改或新增复核时拒绝有损结构回退。
## 开发快照 snapshot/v0.1.32 - 2026-10-18
### Added
- 问题统计看板按参考布局提供完整十一项:2.0 累计问题、本周新增、覆盖率与正确率半环、问题类型饼图、产品横条排行、所选自然周逐日折线、区域平均使用频率与使用覆盖率、两条历史质量周趋势。未知分类保留为未分类,未知分母及历史缺失点不伪造为零。
- 支持导出当前完整看板 PNG,包含所有图形、中文、图例与数值,独立于屏幕滚动高度;不包含导航、筛选控件、按钮或个人月明细。桌面与窄屏共享同一份数据及响应式图形。
- 治理侧保存自然周质量冻结快照与逐条标量贡献;普通 worker 在符合采集条件的下一周一自动冻结上一周,保存实际时点、源观测、版本和分子分母。首次启用前、错过采集或没有完整观测的历史周保持空白,后续人工改判不回写已冻结比率。
### Changed
- 看板通过单一只读一致性接口聚合治理副本、有效质量与组织版本,不连接源端、不下载全部长答案;累计、所选自然周和截止月分别计算,原问题关键词按字面筛选。区域分母使用配置后的通讯录去重人数,仅用于使用统计,个人月明细与四页共享查询保留。
- 新增加表迁移 52。应用回退保留加表;发现冻结历史时拒绝有损 down。未接入翻译/AI 评估模型、绩效名单、1.0 归档或自动发送。
### Security
- 统计接口维持固定租户、管理员会话、同源动作头、严格 JSON 和 no-store;输入指纹变化返回冲突,不混合新旧结论。数据库拒绝修改、删除或追加已提交的冻结贡献;浏览器查询不写指标,PNG 使用自包含 SVG,不加载外部图片或执行原始 HTML。
## 开发快照 snapshot/v0.1.31 - 2026-10-18
### Added
- 区域配置支持新增、改名业务区域;稳定区域 ID 与部门映射同版本保存,改名不丢失归属。部门按一级、二级、三级及更深层级展示,支持层级/完整路径筛选、多选及批量归属。
- 勾选、批量应用、预览和保存分离;取消本次勾选不删除既有映射,批量配置父部门不清除未选子部门的显式规则。预览核对去重人数,保存后独立读取并校验配置版本。
### Changed
- 新增治理库迁移 51,为历史区域修订补齐原四区目录;业务目录与映射原子留档,跨页问答、看板、使用统计、个人月明细和周报共用同一版本及名称。旧请求未传目录时沿用当前目录,不恢复固定四区白名单。
- 迁移回退发现自定义区域历史时拒绝丢失目录;应用回退可保留加列迁移。统计人数不升级为在职或绩效分母,不改钉钉组织、DobotAI 原始数据及旧 Excel 流程。
### Security
- 配置请求拒绝缺失版本、重复 JSON 字段及嵌套 null;区域目录校验稳定 ID、重名及保留键,名称按文本展示,自定义名称不读取原型属性。保留管理员同源写入、租户隔离和双版本冲突保护。
## 开发快照 snapshot/v0.1.30 - 2026-10-18
### Changed
- 继续清理问答运营的冗余提示:质量统计移除完整范围留档、模型接入和读取时间说明;个人月明细移除重复的绩效/零记录解释及读取时间,保留实际数量、范围版本、分页和真实错误。质量统计按钮使用简短名称,不改统计与审计逻辑。
## 开发快照 snapshot/v0.1.29 - 2026-10-18
### Added
- 问答运营调整为“问题流水、问题统计看板、区域配置、周报生成”四页,流水与看板共用年份、自然周和查询条件;区域页不再显示问题查询框,完整字段及人工复核入口保留。
- 重做统计看板:展示完整筛选范围的问题量、覆盖率、正确率、人工复核、北京时间每日趋势、质量分布、区域、问题类型和产品分类;个人月明细折叠保留,未知分类与零分母不虚构结论。
- 周报页提供手动草稿、Markdown 预览及 MD/完整明细 CSV 下载。核对副本版本、完整数量、质量范围与覆盖分布后生成;CSV 保留完整十五列并防护公式注入,不发送、不归档、不作为正式持久化周报验收。
### Changed
- 删除问答记录、同步栏和区域页中的冗余解释文字、读取时间与源连接提示,保留真实错误、未同步、权限阻断和安全告警。
## 开发快照 snapshot/v0.1.28 - 2026-10-18
### Added
- 问答运营新增持久本地副本,每 5 分钟后台同步并支持立即同步;列表、详情、检索、质量范围和使用统计改为读取本地数据库。
- AI 答案详情提供安全 Markdown 预览与原文查看,支持表格、代码块和链接;不执行原始 HTML,外部图片默认不请求。
### Changed
- 增加治理侧问答副本表与同步状态;同步由普通 worker 执行,使用独立只读源连接,源失败保留最后成功副本,权限校验失败阻断读取。
- 副本激活与质量输入变化的复核失效同事务提交;同步持久去重、租约校验及重启恢复不改变源问答或现有生产写入。
### Fixed
- 本地读取不再逐条重复写质量留档或解压完整范围长答案来统计字段,保留完整列、输入版本一致性和真实缺失值。
- 初次观察即已同步完成时自动重查列表,避免较早的首次未同步错误一直停留。
## 开发快照 snapshot/v0.1.27 - 2026-10-18
### Fixed
- 并行分支存在同号开发提交时,历史变更说明与补记标签计划按完整 SHA 选择,不再被前一条同号记录遮蔽;不强改推导版本号或移动既有标签。
## 开发快照 snapshot/v0.1.26 - 2026-10-18
### Fixed
- 版本记录收录已交付开发快照与当前运行提交,不再只同步 main;开发版本显示对应的独立变更说明。
- 修复同步重复返回旧已完成任务:进行中同步合并,完成后允许新一轮同步。
- 构建启动后自动请求版本同步;部署后独立读回当前完整 SHA、开发标签与变更说明,缺失时不宣称交付完成。
### Added
- 增加开发快照计划、显式发布及远端读回工具;历史说明按原提交补记,正式 tag 与 Release 保持独立授权。
## 开发快照 snapshot/v0.1.25 - 2026-10-18
目标提交:90901a4458054aca721047c9a2896f2130d46b5c
历史开发快照补记,不改变原提交、原提交时间或正式发布状态。
### Added
- 问答记录新增“本周”快捷查询,默认展示北京时间本周数据;所选范围自动读取全部记录,校验完整数量后展示,不再需要分页或加载更多。固定展示完整字段,空列不再隐藏,中文原文直接展示、外语未翻译明确标记;保留源端有界传输、取消和失败保护。
## 开发快照 snapshot/v0.1.24 - 2026-10-18
目标提交:3566f208e0e15fdec2cc98c29f93c46f5f7c91ca
历史开发快照补记,不改变原提交、原提交时间或正式发布状态。
### Added
- 问答记录新增年份与自然周选择,周次展示起止日期,支持跨年及每年实际周数;按北京时间周一至周日填入查询范围,默认本周且仅查询截至当前的数据。保留自定义时间与原查询按钮,浏览器时区不改变查询边界。
## 开发快照 snapshot/v0.1.23 - 2026-10-18
目标提交:2ea2111420e501fec37e0ea0c4bcd3cd8c866403
历史开发快照补记,不改变原提交、原提交时间或正式发布状态。
### Fixed
- 修复通过带端口的 HTTP 地址访问问答运营时,正常同源操作被误报“拒绝跨站配置请求”:网页代理保留原始 Host 及端口,使缺少 Fetch Metadata 的浏览器也能正确执行 Origin 校验。保留后端跨站、异端口、伪造转发头与缺失操作意图的拒绝保护。
## 开发快照 snapshot/v0.1.22 - 2026-10-18
目标提交:828c2c5fa0d4e3e0eee3987085a4d782872f7e95
历史开发快照补记,不改变原提交、原提交时间或正式发布状态。
### Added
- 问答运营按已确认口径显示知识覆盖率、回答正确率及原始分子/分母、排除数量与构成。使用完整筛选范围和当前有效人工优先结论,精确规则命中的未覆盖可进入覆盖分母;部分覆盖不算完全覆盖,待确认/未评估/失效/不计入按对应指标排除,零分母显示“暂无可计算数据”。返回独立规则版本,AI 模型与绩效范围仍未接入。
- 新增覆盖复检历史、已采集输入快照和完整筛选范围质量数量。批量统计只传指纹与规则命中元数据,原文在读取问答/复核时采集,未采集版本明确提示而非伪造空快照;源事务时点阻止延迟旧扫描回退新输入。基础数量与本期经确认新增的质量比例分开显示;不接 AI 模型、不定义绩效分母、不回写 DobotAI 原问答。
## 开发快照 snapshot/v0.1.21 - 2026-10-17
目标提交:4b3aaff3219e78ae3dfd5d12ba36f82e323a21ba
历史开发快照补记,不改变原提交、原提交时间或正式发布状态。
### Added
- 问答详情支持治理侧人工复核及不可变审计:覆盖、正确性、产品分类、问题类型、根因与理由由管理员明确填写,操作人取登录身份;保存后独立读回复核历史。保留源端判断和自动规则,输入变化使旧人工结论失效,已观测的内容回退不自动恢复旧结论;并发修改冲突、重复请求不重复留档。
- 新增覆盖复检历史、已采集输入快照和完整筛选范围质量数量。批量统计只传指纹与规则命中元数据,原文在读取问答/复核时采集,未采集版本明确提示而非伪造空快照;源事务时点阻止延迟旧扫描回退新输入。仅提供数量,不接 AI 模型、不定义质量率或绩效分母、不回写 DobotAI 原问答。
## 开发快照 snapshot/v0.1.20 - 2026-10-17
目标提交:9556db1ef01add21064553f64be6a7e25f838e9e
历史开发快照补记,不改变原提交、原提交时间或正式发布状态。
### Added
- 「使用统计 → 个人月明细」按北京时间自然月及截止时点展示当前应用可见名册的人员用量(包含本源零记录),支持部门含子部门、区域/待核对状态、姓名/完整部门路径和已有提问/零记录筛选。稳定身份关联去重、不可关联问答单列、过期组织版本阻断翻页;源端姓名可缺失并提示重新同步,不按姓名匹配身份。姓名筛选通过有界只读 POST 请求体传递、不进入 URL,原始员工标识不返回浏览器;名册不作为在职/考核分母,当月进度不作为绩效结论。
## 开发快照 snapshot/v0.1.19 - 2026-10-17
目标提交:56b8cabed9213911b6beb4aeaac9baa22e872a00
历史开发快照补记,不改变原提交、原提交时间或正式发布状态。
### Added
- 问答运营按稳定身份关联当前治理通讯录,缺失部门/区域补全并保留源快照及目录/区域版本;空列判断包含完整筛选结果,不混合配置已变化的分页。新增完整时间/关键词范围的使用统计,问答计次、名册内人员去重,跨区/未匹配/身份缺失分列,不输出绩效结论;不回写 DobotAI 原表。
- 新增治理侧通讯录实时桥接,复用既有周报读取能力,向源端读取真实缺失祖先以补齐部门树;不改旧周报脚本、配置或输出。私有 JSON 与受控子进程执行器拒绝不完整数据、模拟同步、公开文件和异常输出,超时清理临时人员文件。同步默认关闭,容器启用仍需批准的既有周报 SDK/配置及兼容 Python 运行时挂载。
- **「问答运营 → 区域使用」区域归属配置切片**:按稳定部门 ID 搜索和勾选完整路径,预览去重归属人数,保存版本后重新读取;父部门包含子部门、子部门显式配置优先,多部门跨区域人员单独待核对。预览不写入,过期版本和不完整部门树被阻断。基础使用统计见本期新增,不代表绩效验收;翻译及 AI 评估模型接入按用户要求暂缓。
- **「问答运营 → 问答记录」首个开发切片**:在现有导航中查看 DobotAI 2.0 钉钉问答,支持半开时间范围、原问题关键词、稳定游标分页和完整详情;按整个筛选结果隐藏空列,反馈聚合不放大问答条数。外语翻译、AI 评估尚未接入,不标为第一阶段完成。
## 开发快照 snapshot/v0.1.18 - 2026-10-17
目标提交:cc4906b6a9f33683a5c0017d83b24d96d5c997b1
历史开发快照补记,不改变原提交、原提交时间或正式发布状态。
### Added
- 「问答运营 → 区域使用」支持管理员从网页发起持久化通讯录同步,重复请求重放、进行中操作合并;按服务器固定范围注册后台执行器,未接线或未检测到心跳时不创建新任务。界面显示真实任务状态、不编造百分比;失败保留旧快照与区域规则,同步完成不覆盖未保存草稿。旧执行器丢失租约后不能激活结果,重启接管和已提交快照的续跑使用原任务。
- 新增治理侧通讯录实时桥接,复用既有周报读取能力,向源端读取真实缺失祖先以补齐部门树;不改旧周报脚本、配置或输出。私有 JSON 与受控子进程执行器拒绝不完整数据、模拟同步、公开文件和异常输出,超时清理临时人员文件;问答组织关联尚未接入。同步默认关闭,容器启用仍需批准的既有周报 SDK/配置及兼容 Python 运行时挂载。
- **「问答运营 → 区域使用」区域归属配置切片**:按稳定部门 ID 搜索和勾选完整路径,预览去重归属人数,保存版本后重新读取;父部门包含子部门、子部门显式配置优先,多部门跨区域人员单独待核对。预览不写入,过期版本和不完整部门树被阻断。当前不是使用/绩效统计验收,翻译及 AI 评估模型接入按用户要求暂缓。
## 开发快照 snapshot/v0.1.17 - 2026-10-17
目标提交:6adfdcdd4fe0e5c35018bf0e4857c203dd24e5d1
历史开发快照补记,不改变原提交、原提交时间或正式发布状态。
### Added
- 区域归属表支持长部门路径换行,避免继承全局不换行规则后将生效区域与归属依据列挤出桌面面板。
## 开发快照 snapshot/v0.1.16 - 2026-10-17
目标提交:5874134e049b0896a202b41d9d400d1be5386f89
历史开发快照补记,不改变原提交、原提交时间或正式发布状态。
### Added
- 新增治理侧通讯录实时桥接,复用既有周报读取能力,向源端读取真实缺失祖先以补齐部门树;不改旧周报脚本、配置或输出。私有 JSON 与受控子进程执行器拒绝不完整数据、模拟同步、公开文件和异常输出,超时清理临时人员文件;网页触发的后台同步队列及问答组织关联尚未接入。
## 开发快照 snapshot/v0.1.15 - 2026-10-17
目标提交:dab74b706b5b87da3ecb71a8e0f6b6caa5fba4ad
历史开发快照补记,不改变原提交、原提交时间或正式发布状态。
### Added
- **「问答运营 → 区域使用」区域归属配置切片**:按稳定部门 ID 搜索和勾选完整路径,预览去重归属人数,保存版本后重新读取;父部门包含子部门、子部门显式配置优先,多部门跨区域人员单独待核对。预览不写入,过期版本和不完整部门树被阻断。当前不是实时通讯录同步或使用/绩效统计验收,翻译及 AI 评估模型接入按用户要求暂缓。
- 新增治理侧组织快照与区域修订迁移、受确认保护的后台导入命令;不修改 DobotAI 原表或钉钉组织结构。
## 开发快照 snapshot/v0.1.14 - 2026-10-17
目标提交:fd04bf7886aa4eba68b247dd7c1092894a5282da
历史开发快照补记,不改变原提交、原提交时间或正式发布状态。
### Added
- **「问答运营 → 问答记录」首个开发切片**:在现有导航中查看 DobotAI 2.0 钉钉问答,支持半开时间范围、原问题关键词、稳定游标分页和完整详情;按整个筛选结果隐藏空列,反馈聚合不放大问答条数。外语翻译、AI 评估、人员区域映射尚未接入,不标为第一阶段完成。
- 治理侧重新检查答案:包含 `暂无已发布` 即未覆盖,引用和源端 covered 不能抵消;未命中仍待评估,源端规则单独保留作对照。独立只读源默认关闭、租户后端固定、管理员会话鉴权,源故障不回退治理主库;安全告警明确保留 TEMP/TLS 尚未整改状态。
- **「版本记录」页**(一级入口):只读时间线追踪**远端 main 可达的全部提交**(含合并提交与已并入 main 的侧分支提交),按提交时间倒序显示标题/正文、`[正式版本] / [开发版本] / [历史提交]` 徽标、短 SHA、提交内变更记录与 `[查看提交]`/`[查看 tag]`;支持「全部 / 正式 / 开发 / 历史」筛选与游标翻页,窄屏单列。
## 开发快照 snapshot/v0.1.13 - 2026-10-14
目标提交:c1d49adb622f3bf18e51bddb415795a894ed227e
历史开发快照补记,不改变原提交、原提交时间或正式发布状态。
### Changed
- 补三Agent交接闭环工作流(状态机、快照tag与验收模板)。
## 开发快照 snapshot/v0.1.12 - 2026-10-14
目标提交:9a8a3e77956149d116360bbb561efd67e20fb062
历史开发快照补记,不改变原提交、原提交时间或正式发布状态。
### Fixed
- **登记钉钉知识库失败(HTTP 409 `space_registration_conflict`)的真实原因与修复**:`source_connections.name` 有唯一约束,而早期路径登记的钉钉来源(`base_url` 为空、id 与 dws 现在推导的确定性 id 不同)被**归档后仍占着名字槽位** ⇒ 重新登记时 INSERT 撞唯一约束;处理器又把**所有**存储层错误都翻译成同一个 409,真实原因被吞掉(线上只能看到一个无细节的错误码)。现在:① 同名来源**已归档**时,把它改名腾位(`…(已下架 · <短ID>)`,归档行不显示、数据与审计一行不动);② 同名来源**仍在使用**时返回 `space_name_conflict` + 可行动提示(先「去掉来源」);③ 同一登记键指向别的空间且该行仍在用时返回 `space_identity_conflict`(已归档的行则允许就地改写 —— 这就是"去掉后再用 dws 重新获取");④ 其余内部故障改回 **500** `space_registration_failed`,不再伪装成冲突;前端优先显示后端 `hint`,并为这两个新码补了中文说明。
## 开发快照 snapshot/v0.1.11 - 2026-10-14
目标提交:2c7265efc47a2cd6b324bf278fa5ddb0c4a9fe57
历史开发快照补记,不改变原提交、原提交时间或正式发布状态。
### Changed
- 「系统设置 → 数据源配置」**收敛成两个入口**:**dws 导入**(按当前钉钉账号列出可访问的知识库与钉盘空间)与 **PP 导入**。AList(已由钉盘文件替代)、静态登记的钉钉知识库、本地手工导入与 PP legacy 都**不再出现在这一页**;已接入的这类来源用**归档**下架(`POST /api/v1/source-connections/{id}/archive`,管理员、幂等,带写意图头与幂等键):从列表、来源与目录、计划刷新与手动扫描里消失,**目录快照、发布记录与审计数据一行不删**。恢复用 `…/restore`(保持停用、不自动带回调度),或让 dws 重新登记同一来源 —— 钉钉空间登记是确定性 ID + `ON CONFLICT`,**原地复活同一行、不产生重复**。卡片上新增「去掉来源」按钮(管理员,带确认弹窗)。
- 迁移 `000047_source_connection_archive`:新增 `archived_at` / `archived_by` 与只覆盖未归档行的部分索引;列表、目录、计划刷新、手动扫描四类查询一律排除归档行(漏一条就等于入口没去掉)。
## 开发快照 snapshot/v0.1.10 - 2026-10-14
目标提交:64b32c2da64881b3c9bf6de6b8f9af3f833128fd
历史开发快照补记,不改变原提交、原提交时间或正式发布状态。
### Changed
- 「系统设置 → 数据源配置」默认只列 PP 资料与「可加入的来源」:AList 来源(已由钉盘文件替代)与静态登记的钉钉知识库来源(改为按当前钉钉账号发现)**不再默认列出**,收进「显示 AList/钉钉知识库来源(N)」折叠开关 —— **来源一个都没删**,展开后照旧可改刷新周期与启停,「来源与目录」里的内容不变。手动导入来源(本地手工验收)不在这次范围内,照旧显示。
## 开发快照 snapshot/v0.1.9 - 2026-10-14
目标提交:b7f7e4f12101569db5bb76af9d836043eefc342b
历史开发快照补记,不改变原提交、原提交时间或正式发布状态。
### Fixed
- **上线前护栏 + 事故复盘**:`scripts/deploy-api-web.sh` 现在先校验 `.env` 的键值形状(值里再出现 `KEY=` 或键名不合法 ⇒ **拒绝上线**),配置校验失败时也会给出"两行粘成一行"的线索。事故:`.env` 最后一行没有结尾换行、又被追加新键 ⇒ 两行粘成一行 ⇒ `VERSION_HISTORY_GITEA_BASE_URL` 变成非法值 ⇒ api/worker 按设计拒绝启动、崩溃重启(平台整体不可用)。已修数据、加护栏、加回归用例。
## 开发快照 snapshot/v0.1.8 - 2026-10-14
目标提交:4cb51108454c3559e8a6305ad3316f74ed4e2daf
历史开发快照补记,不改变原提交、原提交时间或正式发布状态。
### Fixed
- 「版本记录」页的链接改为**抓取与链接分离**:抓取仍走配置的(内网)地址,页面上的「查看提交 / 查看 tag」用 `VERSION_HISTORY_LINK_ORIGIN`(公网 origin)拼 —— 原先两者共用内网地址,内网外的浏览器点开是空白;另外 tag 记录原先不带链接(空 `href` 点下去没反应),现在补上链接、没有链接就不渲染该按钮。
## 开发快照 snapshot/v0.1.7 - 2026-10-14
目标提交:90db243a68a5d16d643dd0273614b93666d65ca7
历史开发快照补记,不改变原提交、原提交时间或正式发布状态。
### Fixed
- 前端入口与静态资源的缓存语义("新功能看起来没上线"与"页面偶尔裸奔/空白"的根因):
## 开发快照 snapshot/v0.1.6 - 2026-10-14
目标提交:062a3b3cb60ede5ebcca64a0f14df53fffa1f359
历史开发快照补记,不改变原提交、原提交时间或正式发布状态。
### Fixed
- 前端入口的缓存策略:`index.html` 此前没有缓存头,CDN/浏览器会启发式缓存它,于是**页面会一直停在旧构建上**(实测被腾讯云 EdgeOne 卡住:源站已是新包,公网入口仍发旧包,看起来像"新菜单/新页面没上线")。现在 `index.html` 每次回源校验(`no-cache, must-revalidate`)、带内容哈希的 `/assets/` 长缓存(`immutable`),旧哈希资源明确 404 而不是回退成 HTML。
## 开发快照 snapshot/v0.1.5 - 2026-10-14
目标提交:4d43a9386a8a97969a1568956213f6f17b13edfb
历史开发快照补记,不改变原提交、原提交时间或正式发布状态。
### Changed
- tag 之前的提交不再被编号 + 部署脚本注入构建身份。
## 开发快照 snapshot/v0.1.4 - 2026-10-13
目标提交:0974d8dfa2aad5650eefbcee3edb365a8afd4868
历史开发快照补记,不改变原提交、原提交时间或正式发布状态。
### Added
- **「版本记录」页**(一级菜单第 5 项):只读时间线追踪**远端 main 可达的全部提交**(含合并提交与已并入 main 的侧分支提交),按提交时间倒序显示标题/正文、`[正式版本] / [开发版本] / [历史提交]` 徽标、短 SHA、提交内变更记录与 `[查看提交]`/`[查看 tag]`;支持「全部 / 正式 / 开发 / 历史」筛选与游标翻页,窄屏单列。
- 新增只读接口 `GET /api/v1/build-info`(当前运行产物的构建身份)、`GET /api/v1/version-history`(快照游标分页,不返回伪造的 total)、`POST /api/v1/version-history/sync`(仅管理员手动同步,并发同步复用同一作业)。
- 新增迁移 `000046_version_history`(提交记录 + 同步状态):`commit_sha` 是唯一身份,**版本号不是唯一键** —— 两个提交算出同一个开发号时各占一行,用 SHA 区分。
- 「当前运行」只认运行产物注入的**完整 SHA 精确匹配**:匹配到就标该条;开发构建显式标注"开发构建";拿不到标识就显示"当前运行版本未知",**不拿最新 main 或最新 tag 顶替**;CI 成功不等于已部署。
### Security
- 版本记录同步**匿名只读**远端仓库(**不使用任何凭据**),来源限配置里批准的 origin 与固定仓库:拒绝任意 URL、拒绝跨主机重定向,单个响应设大小上限与超时、5xx/429 才有退避重试。
- 提交正文与变更记录一律按**纯文本**渲染(不产生 HTML/脚本注入面);页面不含打 tag / 发布 / 部署入口;同步动作仅管理员可用,服务端再次判角色。
## 开发快照 snapshot/v0.1.3 - 2026-10-13
目标提交:ad9c5d20d467a077e88bbfe63b1a56355d263e51
历史开发快照补记,不改变原提交、原提交时间或正式发布状态。
### Changed
- Merge remote-tracking branch 'origin/main'。
## 开发快照 snapshot/v0.1.1 - 2026-10-13
目标提交:3a1d32da8fba90eb5081865b1f3ad424a46639ee
历史开发快照补记,不改变原提交、原提交时间或正式发布状态。
### Changed
- 重写平台定位与能力边界并归档原说明。
## 开发快照 snapshot/v0.1.1 - 2026-10-13
目标提交:950c1b429bc814d9fa7c2c1449f999a05537af21
历史开发快照补记,不改变原提交、原提交时间或正式发布状态。
### Changed
- 抬档门禁(破坏性 ⇒ 0.x 抬次 / ≥1.0.0 抬主)+ 1.0.0 前置(稳定契约承诺)。
## [0.1.0] - 2026-10-13
### Added
- 唯一权威门禁 `make verify`:一条命令跑完必需检查并产出结构化报告,本地与 CI 跑**同一条命令**。
- 静态预览发布前先做同一份完整核对:报告有任何缺项就**一个 publish 请求都不发**。
### Changed
- 依赖升级:`vite` 7.1.3 → 7.3.7、`vitest` 3.2.4 → 5.0.3(`npm audit` 全量 0 命中)。
- 前端存量格式债记入基线 `web/.prettier-baseline.json`(79 个文件,**只能减**,2026-12-31 到期)。
### Fixed
- 新分支推送时提交范围恒为空(基准改为远端默认分支)。
- 空工作区下 CI 起不来(工作流首步改为最小 bootstrap)。
- 事件负载缺 `total_commits` 时提交范围解析抛未收敛异常。
### Security
- 依赖漏洞例外登记 `security/dependency-exceptions.json`:缺字段 / 过期 / 批准人是占位值 ⇒ 例外无效。
## 能力基线(未发布):2026-10-10 的上线能力
> 这一条**不是正式发布版本**:当时本仓既没有版本号、也没有变更记录,**没有 tag、没有 Release**。
> 它只是把已上线能力补记在此,供此后发版对照;版本号体系从**下一个正式版本**开始(首个正式版本号由人指定)。
- **来源与目录**:列出可选的钉钉知识库 → 勾选 → 变成来源;快照与变更集。
- **发布管理**:计划 → 建批 → 锁定 → 授权发布(落 WeKnora 生产)。
- **监控 + 需下架车道**:测试库知识逐条下架。
- **钉钉侧**:知识库发现 / 注册、取件走 dws;登录与系统设置。
- **运行形态**:`knowledge-governance-p1-*` —— api / web / worker / release-worker / dingtalk-login /
postgres 共 6 个容器;web 发布在 `:8088`。
- **验收口径**:`docs/USER_FLOWS_ACCEPTANCE.md`(C1~C9)与 `docs/OPERATIONS_RUNBOOK.md`。
⚠️ 上面的端口与容器名前缀是**本机运行栈**的实测值;换环境要按那边的运行栈改,别无脑照抄。