Commit Graph

93 Commits

Author SHA1 Message Date
姚章浩 b4add6c9b0 fix(library): 收口 B1-R1/B2-R1/B5-R1 并让恢复链经过正式队列终态
B1-R1: 演练写集改为受限 restore-drill(V2 信封/contract 1/schema 摘要,零对象、零索引、零本地映射,来源身份必填)。B2-R1: verify 作业先建不可变 operation 并携带 operation_id,同 job recover 的网关 claim 同时携带原 operation 与 recovery 身份。B5-R1: 前端按 scope 世代与动作序号丢弃迟到响应,不污染新 scope 或父级旧回调。harness 的 recovery 改由生产 jobs.Worker.RunOnce 消费并读回 jobs 终态;原失败窗口作业保持 failed,不改写历史。
2026-11-03 13:02:42 +08:00
姚章浩 c31a7df4ca style(test-scope): 可选范围只追加两条,恢复原有紧凑格式
build / build (push) Successful in 1m50s
上一笔把 scripts/test-scope.json 整体重排成缩进格式,diff 变成 196 行噪音。
这里恢复文件原本的一行一条格式,只保留新增的 TestCandidateEmptyTargetFixture /
TestCandidateAttestation 两条;内容(112 条可选)未变。
2026-11-02 11:02:52 +08:00
姚章浩 770252b55a test(candidate): 空目标候选夹具与网关审计续签
父要在真实浏览器里点"签发授权 → 启动同步",需要一个目标确实为空、来源确实存在
的候选环境。两个 test-only 入口(默认 skip,已登记 scripts/test-scope.json 的可选范围):

- TestCandidateEmptyTargetFixture:用已有 DS02/r38 夹具装配十表全非空的独立 fullshape
  测试来源并封存,登记为 test_sealed;同时用产品自己的 ScopeContentEmpty 证明目标范围
  为空(KB/tenant 骨架在、十表内容 0 行),并另建同 mapping 的运行期索引(夹具索引会在
  测试结束时删除)。目标指纹用夹具真实桶名重算,保证与 API/worker 同一份 env 一致。
- TestCandidateAttestation:签发/续签 object-gateway 审计(有效期上限 1 小时),
  四个证据摘要绑真实文件(写入者清单、只读策略、reader 拒写探测、真实排空记录)。

不改产品代码、不放宽任何准入;来源明确标 fixture-assembled-legal-fullshape,
不冒称原 C01 两份封存或 Capture/Export HTTP。
2026-11-02 11:02:38 +08:00
姚章浩 07a9caf22b test(ds02): 收紧映射/引用/哨兵与进程身份 oracle
build / build (push) Failing after 19s
为什么:Astra 复验已独立复现旧 LocalIDs 循环的三种盲区——目标互换、错误来源集合、
不存在的目标都能绿;引用把 pre/next/parent 混在一个 OR 里,parent 分支没有覆盖;
成功路径没有序列非空比对;零 Embedding 只测了一个 client;旧侧对照用 rev-list 的
258 冒充 0.1.258(该工作树 identity 实际是 0.1.148);runner 在异常/超时路径不保证
清理,读回把任何非零退出都当"不存在"。

改了什么:
- 抽出独立语义 mapping checker:三表 (table,column,source,target) 精确集合 + 真实行
  对应;持久账本正例与语义副本变异走同一 checker,digest gate 与语义 gate 分开。
- 测试自带十表 scope 声明并与 schema 列集合对拍;逐种引用非空命中,软导航字段显式
  列为范围外;外围 ES 完整 _source、res-other 字节各补负控制。
- 零 Embedding 改为发布段独立记账:ES + 对象存储两个可达 client,自定义 /v1/encode
  负控制;范围只声明本测试可达 client,正式全链留给 DS03。
- 冻结输入 bundle(十表行/目标种子/序列/ES/对象/DDL,逐表 hash):旧 6fe98c9 与新源码
  同一 bundle 文件;旧侧真实 23505,新侧发布成功 + 独立 oracle。
- 测试进程身份:runner 注入 test-only nonce/commit/树摘要,进程自报 executable hash。
- 历史兼容矩阵改同 catalog 初始化,三条序列显式存在并逐例 before/after。
- runner v2(证据根,不在本仓):失败关闭、finally 清理、所有权重核、keep-failure。

验证:make verify 16 项全绿;ds02_runner_v2 三次隔离运行(含负控制与 keep-failure)
raw 见 handoffs/develop-release-contract/ds02-v2-*;旧侧探针 ds02-bundle-old2。
2026-11-02 03:46:26 +08:00
姚章浩 eea0a0ec3b test(release): 整库发布独立oracle与历史兼容矩阵
build / build (push) Successful in 1m25s
十表全形状夹具补独立 oracle:逐列内容、全部引用、索引正文/向量、对象字节与
零 Embedding 调用;期望由夹具声明推导,不调用被测映射器生成。Wiki 日志本地号
映射断言双射;正文/向量/引用/映射对真实目标变异后 oracle 必须红、改回必须绿。

补历史兼容矩阵:旧 manifest、V1 台账、空作业/目标身份等缺证明组合在正式入口
被拒绝,拒绝前后 PG/索引/对象版本/序列哈希不变;不做 V1→V2 迁移。

旧版对照:0.1.258(6fe98c9)在同一隔离库/桶/索引以相同输入复现
wiki_log_entries_pkey/23505;当前代码同一输入发布成功并分配本地号。
2026-11-02 02:58:50 +08:00
姚章浩 99055a582e test(library): 补孤儿校验锚点失败与介质失效的真实隔离用例
孤儿校验:缺 knowledge_id 的索引文档被拒绝、未知 knowledge_id 如实报孤儿、
数据库层中途失败(真实 statement timeout,SQLSTATE 57014)冒泡为错误而不是
"没有孤儿"。

锚点:真实 PG 约束拒绝写版本锚点时,作业在任何阶段写入前失败、目标零写入、
版本表不留本次行;去掉故障后同一请求写满四阶段。

介质失效:归档 part 缺失/摘要不符时恢复停在内容层,保持 held + RestoreBlocked,
服务与入口都不恢复、校验不再跑。
2026-11-01 17:21:42 +08:00
姚章浩 8df19f9368 test(library): 补软删除共享资源与索引 oracle 隔离用例
真实本机 PG/ES/MinIO 上补五组场景:7 张带 deleted_at 表的软删除 before-image 与
恢复逐列复原;同 _id 属其它知识库时外部文档 _source/seq_no/term 逐字节不变;
正文/向量/metadata 任一篡改都让独立 oracle 变红;晚阶段失败后既有对象精确版本
不变;同 ID 共享资源在 handle/定位/字节三类差异下写前拒绝。另在复用用例里补
"no-op 复用不改写对象版本"的断言。

需隔离服务的用例继续在 test-scope.json 显式声明:普通 CI 跳过不算通过。
2026-11-01 12:08:55 +08:00
姚章浩 8febe12d7d test(library): 隔离数据契约与 r11 身份自然键矩阵
真实本机 PG/ES/MinIO:v071 目录镜像 10 模式、结构引用/游标 15 模式、
8 表×2 外部范围身份矩阵、13 模式自然键(含软删除与合法同 parent 正例)、
备份期间三注入、收尾故障与授权撤销两个真实治理 PG 用例。

需隔离服务的用例在 test-scope.json 显式声明:普通 CI 跳过不算通过。
2026-11-01 11:35:09 +08:00
姚章浩 ee9d078794 fix(release): 分配目标分块序列并保持原值恢复
build / build (push) Successful in 1m26s
2026-10-31 06:08:05 +08:00
姚章浩 7ab2eb1ccf fix(library): 按目标身份隔离版本授权与执行历史
build / build (push) Successful in 1m47s
2026-10-31 02:15:15 +08:00
姚章浩 06c2db19fc fix(library): 删除 handoff 路径残留的无条件 stage 写
build / build (push) Successful in 1m31s
BeginLibraryHandoff 事务里已经把 handoff 阶段推进为 running,函数返回后又补了一次
无条件 UpdateLibraryStage:既重复事务里的写,又在 Begin 返回与这次写之间给交接留出
竞态窗口(此时所有权可能已被 release 拿走)。删掉这次调用,handoff 路径的
running/succeeded/failed 三类阶段写只发生在 Begin/Complete/Fail 三个持有 job 行锁的
事务方法里。

回归:新增源码契约测试(library_handoff.go 里不得再出现 store.UpdateLibraryStage,
且必须走三个事务方法),以及行为测试(所有权不属于本次 handoff 时零阶段行、作业零改动;
用真实 handoff ID 重放则幂等成功且同样零写入)。既有事务行为测试保持不变。

验证:隔离 fixture PostgreSQL + 本 SHA libraryctl 实跑 cmd/worker 全包通过;完整门禁见提交日志。
未部署、未推送;云 SSH 与 UI 仍未验证。
2026-10-30 19:42:13 +08:00
姚章浩 8a68ed1819 fix(library): 交接所有权 CAS、回传游标跨轮与快照原产地
父审查确认四项是原 finding 未闭环,本包按真实调用链修完(先 RED 再修)。

R4a 正常流程必现阻断:API 先入 handoff 队列作业并关联,交接却只接受"未关联",
每次正常发布都被自己的所有权检查挡住。新增 governance.LibraryHandoffQueueJobID
(jobs.id 为 varchar(36),原实现拼后缀超长,插入即失败);EnqueueAndAttachLibraryJob
增加"声明的前驱队列作业号",只允许当前无人持有或正是该 handoff 时替换,任意替换拒绝。
测试从 API 真实前驱(已关联)开始,覆盖正常交接、错误前驱拒绝、交接后重放、
以及 release 与 handoff 两条队列行都存在。

R4b 迟到 handoff 污染已交接作业:失败出口(含 Push 错误)改为所有权 CAS
(FailLibraryHandoffIfOwner),只在业务作业仍由本次 handoff 持有时写 state/stage/recovery;
已交接/已终态零修改。所有权判定改为只看 library_jobs.queue_job_id(阶段行是否存在
与所有权无关,原实现会误判)。阶段写入也带所有权。用测试屏障确定性地复现"失败出口
到来时所有权已切给 release",验证 release running 与 succeeded 两种状态下零修改。

R2 回传游标每轮重置:改为跨轮保留(单轮仍 20 页预算),读到不足一页才回到起点;
新增 pullCursor 与跨轮覆盖测试(2100 条引用、每轮 5 页仍全部覆盖,不重复不漏取)。

R3 原产地误判:封存成功后会再登记 production_effective(同一快照),原实现把
sealed+target 判成冲突,正常再发布/历史恢复被误拒。现在原产地只由捕获事件决定
(test_sealed=源 / production_prebackup=目标),effective/recovery_verified 只是引用;
二者冲突或都没有捕获事件仍拒绝。测试覆盖正常成功链、真冲突、无捕获事件三种。

验证:隔离 fixture PostgreSQL 实跑 cmd/worker 全包通过(含上述四条真实调用链);
libraryhandoff/librarysnapshot/httpapi/governance/platform-postgres/libraryctl 全绿;
完整 ci_checks 见提交时日志。未部署、未推送、未构建镜像;真实跨主机 SSH 链路仍未验证。
2026-10-30 19:22:48 +08:00
姚章浩 9300865d23 fix(library): 搬运复用前复验字节、回传游标分页与发布队列事务交接
父独立审查退回的四个 finding,逐项修在本包内,不改架构。

1 复用路径没复验字节:remoteEvidence 只读清单+摘要就宣布可用。现在新增
libraryctl snapshot-verify(整版逐部件重算)并在复用前调用;blob 缺失/篡改必须
在入发布队列之前失败。新增真实 local 跨进程用例:清单不动、删 blob / 改 blob 后
再 Push 必须拒绝。

2 回传枚举被固定 LIMIT 50 截断:LibraryJobSnapshots 只取成功作业且按摘要字典序。
改为 LibrarySnapshotCandidates:合并 library_versions、library_jobs.snapshot_id、
library_jobs.target_snapshot_id 三处引用,按 (created_at, snapshot_id) 游标分页;
回传循环翻页到不足一页为止。失败/待恢复作业留下的预备份、第 51 条以后、历史恢复
输入快照都能被枚举。另修"本地只有清单、blob 已损坏就永远跳过":改用整版 Load 判定,
并给 Unpack 增加显式 RepairCorruptBlobs(只在权威云端读回时开启)。

3 身份映射错误:搬运拿目标 tenant/KB 去核对封存快照,而封存清单写的是**源**身份
(云测试验收目标 a8e9c8ad/2e980686 与源 KB 必然不同)。现在 expectedSnapshotIdentity
按版本台账判定:test_sealed → 用部署注入的源绑定(LIBRARY_SOURCE_TENANT_ID /
LIBRARY_SOURCE_KB_*);预备份/生效/恢复版本 → 用目标绑定;没有登记或两类冲突一律拒绝。
身份永远来自独立绑定与台账,不接受包自证。

4 两步入队有竞态:Enqueue 成功后云执行器可能立刻领取并开始维护,Attach 失败却把作业
标失败并声称"未维护"。改为 EnqueueAndAttachLibraryJob 单事务(锁行 + 插入 + 关联 +
状态守卫),要么都成功要么都不发生;重复执行幂等,已关联别的队列作业则拒绝。
另修终态重放:AdvanceLibraryJob 无状态守卫,迟到的失败出口会把已成功作业改写;
新增 AdvanceLibraryJobIfActive(仅 pending/running 可推进),拿不到就明确记录"未覆盖"。

测试:libraryhandoff 用例覆盖跨租户/错误 scope/清单在而字节坏/本地损坏自愈;
隔离 fixture 库覆盖"校验通过才入队、失败不入队"、>50 条分页与失败作业预备份、
幂等重放不重复投递、迟到失败不改写终态。

未部署、未推送、未构建镜像;真实跨主机 SSH 链路仍未验证。
2026-10-30 19:10:02 +08:00
姚章浩 6400fd1bbe feat(library): 快照搬运先于维护、双向校验与云端版本回传
为什么改:云端 release-executor 与目标同机后,本机封存的快照必须"先到、先验证"
才允许开维护窗口。此前只有 pack/unpack 两条人工命令:UI 点发布时执行器读到的是
本机归档路径,云端根本没有这份快照,作业只能在装配阶段失败;而预备份落在云端,
本机页面也读不到。用户已批准补齐这条链路。

搬运(新包 internal/libraryhandoff):本机 Load 逐 blob 重算 → Pack → SSH 流式写入
云端暂存 → 原子改名 → 云端 snapshot-unpack 再逐 blob 校验 → 读回版本清单摘要自证 →
scope/tenant/知识库身份与治理库作业一致。只有全部通过才投递 library.sync|restore,
失败时发布作业根本不存在(目标不进维护、不写知识)。远端命令是固定 argv,容器名与
路径只来自部署配置;同 ID 异内容拒绝;同一快照本机 flock 串行化;暂存 bundle 用后即删,
归档本体永不自动清理。

回传:capture 角色按已成功作业的 snapshot_id 扫描,把本机缺失的云端快照 Fetch 回来
(云端 pack → SSH 流回 → 本机 Unpack 逐 blob 校验)。回传成功后 API/页面既有的列表、
详情、差异、存储与历史恢复路径读到的就是真实字节;失败则版本不出现在本机,不会被误报成可恢复。

角色:搬运与回传只注册在 capture;若接线到 full(同时消费发布作业)启动即失败,
避免与云端执行器抢同一批作业。release-executor 仍不拥有治理库 DDL,也不消费
QA/邮件/来源扫描。

验证:internal/libraryhandoff 全绿(跨进程 push/fetch、身份不符拒绝、幂等复用、
并发串行、篡改与未知快照不落版本、路径穿越拒绝、远端错误不当作"没有");
cmd/worker 在隔离 fixture 库(独立测试 PG)上验证"校验通过才入队发布、失败不入队";
httpapi 队列决策单测;相关包 go test 全绿;go vet 全仓通过。

未部署、未推送、未构建镜像:云端接线与验收仍由父执行。真实云链路(含 SSH 通道)
本轮没有云侧写操作,只有本机跨进程与隔离 fixture 证据。
2026-10-30 18:58:34 +08:00
姚章浩 e6472b0ec6 feat(library): 云测试发布目标接入的角色分离、快照搬运与图谱门禁
为什么改:发布执行链是"与目标同机"假设写死的(本机 docker.sock、本机 drain.json、
本机 Redis/PG),把目标换成云测试不能只改展示 URL;直接搬整套 worker 又会抢走
本机封存/QA/周报队列并按本机配置写错目标。所以先把"谁消费哪段队列"变成显式角色,
再补齐跨主机搬运与目标侧门禁。

角色(LIBRARY_WORKER_ROLE=full/release-executor/capture)不只影响 handler 列表,
还决定是否拥有 DDL(release-executor 只读校验迁移版本,落后即拒绝启动)、
收拾哪些 library_jobs.kind、是否跑 release 接管扫描、是否起 scheduler/QA/周报/探测。
capture 保留本机原有全部业务;release-executor 只挂 sync/restore/recover/baseline。

快照跨主机:新增内容寻址的 bundle(版本清单先落、blob 按 sha256 命名、清单最后
Link 作提交点),pack/unpack 幂等且拒绝覆盖与同名不同内容;执行器使用前仍逐 blob
重算 sha256,因此传输不可变有两道证据。

目标侧门禁:目标知识库 graph_enabled=true 时拒绝发布(发布只写 PG/ES/OSS,不写
Neo4j;未知也拒绝),只读检测、不改目标配置、不做图谱迁移。

云端入口:新增 drain 感知的 ingress 闸门(建连超时与读超时分离,半关闭单向传播,
SIGTERM 排空后写 drain.json 并 exit 0/2)、compose 叠加片段(app 不再发布端口、
frontend 与 adapter 的入口收敛到闸门、指向真实网络 weknora-test-v071-network)、
执行器启动脚本与治理 PG 反向隧道脚本(默认 unix socket,不占 TCP)。

验证:deploy/cloud-test/test_gate.py 13 项全 PASS(含 12 秒静默流、半关闭、2MiB 完整、
排空 exit 0/active_at_close=1、排空超时 exit 2、上游不可达不挂住);go test 相关包全绿;
scripts/ci_checks.py 16 项全部 success(EXIT=0,1241 用例通过/0 失败/191 已声明可选集成跳过);
云测试只读预检 17 项 / 6 FAIL,FAIL 项即当前部署缺口(无闸门、app 仍发布端口、
adapter 与 frontend 仍直连 app、云端归档根未建),未部署、未改云端任何状态。
2026-10-30 18:38:38 +08:00
姚章浩 4f0852a94d fix(ci): 契约门同时核进程退出码与负例归因
为什么:版本契约门只看 `--- PASS: X` 两行,rc1 取到却没用。同一进程里别的测试
失败时 go test 仍会打印这两个 PASS 行、退出码非 0——那种"绿"证明不了这一轮测试
成功,负构建因编译/网络错误非 0 也会被当成"断言还活着"。审计 F19 的原始触发即此。

改了什么:判据抽成 `judge_contract_positive` / `judge_contract_negative`,正向要求
rc=0 + 每个必需测试真 PASS + 无 FAIL 事件 + 未跳过;负例要求非 0 且失败在预期断言上,
命中编译/网络/工具链标记即判红。判据的反例自检 `contract_judgement_problems()` 同时在
`make verify` 的 contracts 步骤与 `--self-test` 里跑,判据被改坏在本地就红。
POLICY_REVISION 提到 ci-checks/4。

怎么验:`python3 scripts/ci_checks.py --self-test` rc=0(10 汇总分支 + 10 契约反例);
`--internal-contracts $(command -v go)` rc=0;反例探针(假 go 打印两个 PASS 后 exit 1)
现在 rc=1,修复前同一输入 rc=0。
2026-10-30 16:14:46 +08:00
姚章浩 d0310761b6 fix(worker): 接管复用预备份不再抹掉写集合证据
BackupPrepared 每轮都新造一份证据,而版本登记是 evidence=EXCLUDED.evidence
整份替换。接管复用时 BackupRef 只带快照 id(没有字节数)、写集合预检又只在
首轮执行,于是已登记的 writeset_digest/writeset_rows 被抹掉、backup_bytes
记成 0;同一作业第二次中断后再接管就取不回写集合,恢复链彻底断开
(2026-10-30T00:58Z 演练 …-1793321916382871758-retry,摘要 fddd98cf70… → 空)。

现在证据按「同一作业 + 同一份预备份」合并进已持久化那一行,身份不符一律失败;
接管路径用 prebackupBytesOf 从首次登记的证据取回真实字节,缺失即拒绝(不再写 0);
生效版本证据统一由 effectiveEvidence 构造;同一作业的写集合只允许落定一次。

验证:真实 PG(一次性库 127.0.0.1:25471)上先复现再修
- 修前红:cmd/worker 两条新集成用例分别停在「第 1 次接管后写集合丢失」
  与「复用未登记的预备份竟然成功」
- 修后绿:两轮中断+接管+第三次完整恢复后 writeset_digest/backup_bytes(4096)/
  job/backup_snapshot/backup_reused 逐项存活,created_at 仍是首次登记时刻
- 反向自检(各自单独变异后复跑,均红):去掉合并、去掉身份核对、
  去掉摘要冲突判定、去掉复用字节硬门禁
- make verify:16 项全部 success(含 tests-go 真跑集成用例)
2026-10-30 09:14:10 +08:00
姚章浩 780ab988c4 fix(worker): 历史快照恢复接管按作业身份与新授权判定
build / build (push) Successful in 1m20s
真实现场 2026-10-30T00:19Z:新恢复作业恢复历史快照 fc595511(已被 0d982f76
顶替),写入 database 后被 SIGKILL;重启接管时 classifyTakeover 只要看到
该快照有 SupersededAt 就判 superseded,没有判这行历史版本属于哪个作业,
把历史那次发布被顶替当成这次作业过期,作业 failed/prebackup,而恢复文案
又写"无需恢复",目标半写且维护锁未释放。

修法:被顶替分支先分清归属——本作业 finalize 成功或版本行属于本作业 ⇒ 仍
拒绝;历史行属于别的作业时,仅当本次是 restore、授权签发于顶替之后、scope
与 instance 两把锁仍由本作业独占且指纹一致、预备份登记属于本作业且清单字节
仍等于摘要,才按 fresh 继续重跑整条链路。任一证据缺失/读不到仍拒绝。

同时把续作早退统一走 failLibraryJobOrTakeover:有持久化预备份时保留
needs_restore 并点名 backup,不再写"无需恢复"。

验证:
- 先写失败回归(历史行+新授权 ⇒ 期望 fresh,实得 superseded),修后绿;
- 真实 PostgreSQL 正负例:授权撤销/实例锁释放/预备份归属被改/清单字节被改
  均回到 superseded;续作早退保留备份与 needs_restore;
- 本地 make verify 16 项必需检查全绿(identity 本地档案跳过)。
2026-10-30 08:51:07 +08:00
姚章浩 8a3aa800a3 fix(scripts): 演练判据改用认领与决策行并校验预备份复用
v4 判据(都带作业身份,且时间戳必须晚于杀点):

- `scripts/takeover_log_contract.py` 是唯一判据:认领行 + 决策行都要有本作业 id、
  target/backup 一致;只有汇总行、别的作业的认领行、杀之前的旧日志三种一律红;
  `--self-test` 11 条(正例 2 / 负例 9)进 `make verify`(新增第 16 项必需检查);
- `prebackup` 不再强制 `skipped`:真实接管是复用后重跑整条链路,阶段记 succeeded。
  但**不取消备份校验**,改为证明复用同一份——快照 id、清单字节与 mtime、
  版本行首次登记时刻都不变,且证据里有 `backup_reused=true`;
- 杀之前把预备份清单 sha256/mtime 与版本行登记时刻拍进 evidence;
- summary.json 增加 takeover_decision / claim_at / backup_registered_at。

verify: 判据自检 11 条全过;用真实 Go 输出行形状的正例日志 rc=0;旧形状(只有
汇总行)rc=2 且理由点名"汇总";`--check` 前置体检 13 项全 pass(含新增
preflight_log_contract);make verify overall success。
2026-10-30 07:56:16 +08:00
姚章浩 85b3596e0e fix(postgres): 复用预备份时保留首次登记时刻
崩溃接管会复用崩溃前那份预备份;BackupPrepared 会再登记一次版本行,而
`created_at = EXCLUDED.created_at` 把"备份什么时候拍的"洗成了接管时刻 ——
事后无法区分"复用"和"崩溃后重做了备份",而后者正好把半写状态当成可回滚基线。

- 只对 `production_prebackup` 取 LEAST(首次登记时刻只早不晚);
- 生效版本行仍写当前时刻(重新激活一个旧快照后,页面要显示最近一次生效);
- 真实 PG 用例两条负例:撤掉保留规则 ⇒ 红;规则漏到所有 kind ⇒ 红。

verify: 一次性库 kg_takeover_reuse_test 上 PASS;
负例 1(撤掉 CASE)→ "复用后登记时刻被改写";负例 2(CASE 去掉 kind 条件)→
"生效版本重新激活后时刻";make verify overall success。
2026-10-30 07:56:12 +08:00
姚章浩 d544728001 test(worker): 真实PG断言拒绝接管的落库效果
build / build (push) Successful in 1m17s
拒绝接管不只要"分类对",还要三处落库都对:library_jobs 终态 failed + 保留预备份
基线 + 恢复指引里带得上那个基线;library_maintenance 的 released_at 仍为 NULL、
state=needs_restore(目标可能半写,释放等于谎称已恢复)。

用一次性库 kg_takeover_foreign_test 实跑:
- TestFailLibraryTakeoverKeepsMaintenanceAndNamesBackup PASS (1.35s)
- 负例:把恢复指引改成"无需恢复" ⇒ 同一用例红(library_takeover_integration_test.go:117)
- TestTakeoverRefusesSameSnapshotRegisteredByAnotherJob / 超出历史窗口那条仍 PASS
2026-10-30 03:50:49 +08:00
姚章浩 4a70c2a848 fix(scripts): 杀之前等 docker events 监听就位
build / build (push) Successful in 1m17s
SIGKILL 与事件监听抢在同一秒时,die 事件可能早于 attach 被错过,症状是误报
"没有 exitCode=137 证据"(假红)。加 1s 等待,并用注释写清它只会假红、不会假绿。
2026-10-30 03:46:52 +08:00
姚章浩 1d70059d32 fix(scripts): 中断演练改用进程崩溃语义并按快照推导期望
上一轮为什么空等 420s:docker kill 走 Docker API 的"手动停止",unless-stopped
也不拉起,容器一直躺着,最后由人工 docker start 才接管 —— 那不是崩溃自恢复。

改法:
- 命中后从宿主机向容器 PID1 发 SIGKILL(同 uid 才允许,先证明能杀);退出码证据取
  docker events 的 die exitCode=137 —— restart 后 State.ExitCode 会被重置成 0,
  事后 inspect 读不到 137。
- 限期等 restart policy 拉起(同容器/同镜像才算);策略没起来才显式 docker start,
  并记 restart_source=manual_start,绝不当自动恢复。
- 期望内容按目标快照 manifest 推导(knowledges/chunks/wiki_pages),resources 不进
  默认期望(同租户其它知识库资源会保留,trap-16)。
- --kind sync|restore + --snapshot 强校验;目标快照等于中断前生效版本时**杀之前
  就拒绝**(同快照收尾不能当完整续作验收)。
- 新增 verify_stages_complete:database/resources/index/wiki/verify 全部 succeeded
  才算完整续作(上一轮这四个还是 pending,却被判成功)。
- 独立写集合校验器缺失即 fail(旧版记 canonical=not_run 还 pass)。
- save_evidence 读实例维护用 owner_scope(旧版读 scope 列,SQL 报错被吞)。

验证(已跑):
- bash -n 通过;--check 全绿(READY_FOR_PARENT,含 preflight_verifier)
- 负例:WRITESET_VERIFIER=/nonexistent → preflight_verifier fail / exit 2
- 期望推导:fc5955→4/38/31、0d982→1/2/9、0fea→4/38/31;不存在的快照 → exit 1
- 宿主 SIGKILL 能力实测(scratch 容器,非本栈):kill -9 PID1 → 策略自动重启,
  RestartCount 0→1,docker events 记 exitCode=137
2026-10-30 03:40:18 +08:00
姚章浩 ba852f3dc2 fix(worker): 接管判据绑作业身份,同快照别作业登记不算完成
2026-10-29T19:16Z 真实现场:作业只写完 database 阶段(resources/index/wiki/verify
仍 pending),worker 被杀、人工拉起后被判 succeeded,阶段写"已登记生效版本(仅收尾,
未重写生产)",并把生效版本归属改写成这个作业。

根因:classifyTakeover 只看"当前生效版本的目标快照是不是我的目标快照"。版本行主键
由 (kind, scope, 快照) 决定(LibraryVersionID),登记是 ON CONFLICT (id) DO UPDATE、
会改写 source_job_id —— 快照相等这一维里没有作业身份。

改法:effectiveOwnedBy(version, jobID, snapshotID) 一份实现,接管与基线共用;
归属别的作业 ⇒ 新的 takeoverForeignEffective,走 failLibraryTakeover:不写目标、
不释放维护、终态失败并指明预备份(目标可能半写,释放等于谎称已恢复)。
本作业自己的 finalize 阶段成功仍是独立证据,不被归属改写推翻。

验证(已跑):
- go test ./cmd/worker -run TestClassifyTakeover → PASS(含 4 条新用例)
- 负例:撤掉身份判定后同两条用例红(classify="pending_final", want "foreign_effective")
- 真实 PG(一次性库 kg_takeover_foreign_test):
  TestTakeoverRefusesSameSnapshotRegisteredByAnotherJob PASS(1.41s),
  负例同一用例红(library_takeover_integration_test.go:53)
- 同一库 TestTakeoverClassificationUsesExactCurrentVersionBeyondHistoryWindow PASS
- make verify:overall success(15 项必需全绿)
2026-10-30 03:40:13 +08:00
姚章浩 a19cf819fc fix(scripts): 修演练脚本的撤注入与 restore 自锁
build / build (push) Successful in 1m17s
两处实测出来的缺陷:

- --restore 也走完整前置体检,而体检里"不许已经带注入"这一条会把它自己锁死
  (上一轮没收尾时它就永远跑不了)。restore 只校验 docker/worker/归档根。
- 注入用的 overlay 被 compose 记进容器的 config_files 标签,restore 时原样重放
  等于把注入又加回去(实测 KG_TEST_BARRIER_DIR 撤不掉)。按文件名排除演练 overlay。

另外演练中断(not_run/接管失败)时明确提示"屏障仍在注入,先 --restore",
不让父以为栈已经干净。
2026-10-30 03:13:35 +08:00
姚章浩 465cae80bd docs: 记录"窗口当索引"陷阱与本次改动
build / build (push) Successful in 1m26s
trap-17:分页窗口当索引 —— "当前生效版本"不能按最近 N 条找。它比 trap-14 更
隐蔽:读成"没有"不报错,只是让错误的选择看起来合理(重写已上线的目标、给已有
权威版本的目标再建基线)。写清判据(250 条历史 + 窗口里看不到 + 精确查询仍命中)
与修法(身份/当前状态一律精确查,窗口只用于列表)。AGENTS.md 的陷阱索引同步加
一行;CHANGELOG 记录当前生效版本查询修复与固定同步屏障;test-scope 声明新增的
真实 PG 用例为"可选未运行"并写清理由(未声明 skip 判红)。
2026-10-30 03:09:37 +08:00
姚章浩 ed307f3c20 feat(scripts): 真中断演练脚本改成父可直接执行
旧 kill-takeover.sh 的判据自相矛盾:要求"作业已 running 且 ledger 齐"才启动,
可它又要先启动再等作业;维护窗口里要求业务 health 200(那时 app 是被有意停掉
的);没杀到任何进程也 exit 0。

这一版把"做成"定义清楚:真 SIGKILL 真容器 → 同一作业被接管 → 目标按期望内容
恢复 → 服务/入口恢复,四条全成立才 exit 0;missed/超时/接管失败各有非零退出码
并写成 not_run。流程:前置体检(目标拓扑、无在飞作业、无未释放维护窗口、镜像
里真的带屏障代码、只允许本机栈)→ 注入屏障 → ARMED 后由人在页面发起作业 →
看到 .hit 锁住这一个作业 → 读回写集合台账(文件名/自述摘要/作业绑定,可选独立
校验器)→ SIGKILL → 等同一作业接管 → 核对 prebackup:skipped、生效版本、写集合
摘要不变、目标计数、维护释放与容器恢复 → 撤掉注入。每一步有界,不做后台 sleep
轮询假装工作。
2026-10-30 03:09:33 +08:00
姚章浩 67743f2285 test(library): 真实PG验证超出历史窗口仍能取回写集合
build / build (push) Successful in 1m27s
fake 版本库不实现 Store 的 1..100 上限,正是它让"限 out of range"一路绿到线上。
这组用例跑真实 PostgreSQL:真实 Run → contentTrackedTarget → 真实
libraryStageRecorder.WriteSetPlanned → 真实 store,断言台账在第一笔写入之前可读、
原备份证据字段与 writeset_digest 原子共存,并把记录推到 250 条更新版本之后
(窗口里已看不到它)仍能按身份取回。

store 级契约用例覆盖:命中、真缺失 found=false、别的作业=冲突、同身份两行=拒绝挑选、
跨 scope/跨 kind 不命中、不完整身份拒绝。变异成"列最近 N 条再筛"时四条全红。
隔离 schema 逻辑收敛到 pgtest 一份,postgres 侧改为委托。
2026-10-30 02:32:26 +08:00
姚章浩 e0768f2caa fix(library): 写集合台账与跨范围冲突预检
build / build (push) Successful in 1m34s
失败恢复会在写完之后按"目标租户里没人引用"清理资源行,于是历史孤儿、同租户其它
知识库暂未绑定的资源、被共享的资源都可能被删掉;而预备份里没有这些行,删了不可
恢复,随后整实例指纹才发现不一致,只能停在 rollback_failed。恢复校验依赖的整实例
证据又只存在内存里,worker 重启后只按快照 id 恢复,等于静默跳过比对。

改法:写第一笔之前把"本次明确写集合 + 写前存在状态 + 预备份时刻整实例内容证据"
算出来并按内容寻址落盘(文件名与治理库里的引用都是 sha256),摘要登记在预备份
版本证据里;恢复只删"写前不存在 ∧ 由本次写入 ∧ 不在恢复内容里"的精确 ID,并对
资源额外要求仍无绑定/正文引用;缺记录、记录被改、绑定对不上都直接失败、维护保持。
resources 的 upsert 补租户限定,跨范围同 ID 在写入之前就被预检拒绝。
整实例指纹从"身份集合"改成"逐行内容摘要",别的知识库同 ID 行被覆盖写坏也会暴露。

本地验证(已跑):
- make verify 15 项全 success(identity 因本地档案 skipped)
- go test ./internal/releaseadapter/v071/ -run Isolated(真实 PG + 真实 ES +
  真实 MinIO 对象存储,写失败按定位符注入):目标非空/同租户其它知识库/共享资源/
  历史孤儿/跨租户数据在失败恢复后逐行字节不变、本次残留 0;缺写集合拒绝恢复;
  跨范围 ID 冲突在写入前拒绝;wiki 阶段半写回滚干净
- SIGKILL 子进程后按盘上写集合接管恢复,目标原有数据逐行不变
- go test ./internal/libraryrelease/:写集合先于第一笔、规划失败/登记失败都不写目标、
  接管不重新规划
2026-10-29 23:55:13 +08:00
姚章浩 11c52ad81b fix(libraryctl): 封存窗口接上实例级入口闸门与后台静默
build / build (push) Failing after 1m2s
库级窗口永远拿不到在途排空证据:被代理的 WeKnora 收到 SIGTERM 就 exit(1),
写者退出码不能当作请求已排空。capture/recover 改由 librarymaintenance.Manager
装配入口闸门、后台静默检查、目标指纹与入口探针(与发布链路同一套实现),
排空证据来自闸门 drain 记录;缺 LIBRARY_TARGET_* 时在开窗口前拒绝并点名。

维护接线解析收敛到 internal/librarymaintenance/env.go,agent 与 worker 讲同一组
变量名;agent 启动如实打印接线状态;安装脚本强制 0600 agent.env。

试过:make verify 15 项全绿;新增 7 个用例(接线解析/身份绑定/缺闸门拒绝)。
2026-10-29 05:48:38 +08:00
姚章浩 8da4666e76 test(library): 端到端编排在本机真实 PG/ES 上跑通
build / build (push) Successful in 1m14s
为什么:只验证单阶段写入还不够,完整链路(授权→预备份→四类写入→校验)也要在真实服务上跑。
本机 WeKnora 测试栈一次性库与随机索引已通过,CI 无这些服务故在 test-scope 声明为可选。
2026-10-28 15:58:06 +08:00
姚章浩 281699a431 feat(library): 生产整库真实读写/校验/恢复与本机真实服务验收
build / build (push) Successful in 1m21s
为什么:生产目标此前是"无条件失败"的占位,编排再完整也不能落地;本轮把它做成真实实现,
并在本机 WeKnora 测试栈的真实 PG/ES 上跑通导入、撤销、预备份与故障恢复。

- ProductionLibraryTarget:Current 读行数+身份摘要;Apply 按阶段真实写入(database/wiki
  同事务先删本库再插、resources 传对象、index 搬运原始文档与向量);Verify 比对身份摘要;
  Restore 按预备份快照回放。
- 撤销真正生效:索引阶段写入后 PruneScope 删除快照里没有的文档。
- ProductionBackupStore:导出前独立完整预备份,导出前后行数不一致即失败。
- worker 用注入配置装配目标与预备份;缺项把变量名写进阶段并失败,不再有占位对象。
- capacity 安全加法防溢出;保留策略组合语义明确(各维度都超限才候选)。
- 新增本机真实服务集成测试(一次性 kg_library_test_* 库 + 随机 ES 索引),
  覆盖导入/校验/撤销/预备份/故障注入/恢复。
2026-10-28 15:54:01 +08:00
姚章浩 910c0fcd76 feat(library): 页面发起封存的最小权限主机侧执行边界
build / build (push) Failing after 16s
- libraryagent:0700 目录内 0600 unix socket + SO_PEERCRED 同 uid,
  固定 binary/归档/日志/项目/写入者声明,请求只能选范围与作业号
- API:POST/GET /api/v1/library/captures(入队 library.capture + 读队列状态)
- worker:library.capture handler 走 socket;启动先 recover;不挂 Docker socket、
  不持有来源凭据
- web:整库版本页显示真实作业状态并可发起封存(未接线/无权限给出原因)
- scripts/library-agent-install.sh:构建、安装 systemd user 服务并读回权限
2026-10-28 12:11:08 +08:00
姚章浩 045fc44f0e feat(library): 接通整库封存真实入口与只读版本页
build / build (push) Failing after 19s
- libraryctl:status/preflight/capture/list/verify/recover,只读来源会话、
  结清检查、契约推导、维护窗口内停写者、导出十表+索引+对象+Wiki、封存候选后恢复
- libraryfreeze.ProjectController:按 Compose 项目逐服务声明固定身份,
  未声明服务/容器重建即拒绝,仅 SIGTERM,恢复后必须 healthy
- httpapi:GET /api/v1/library/snapshots[/{id}] 只读列出封存产物
- web:「整库版本」页(只读、未接线显示原因、无假按钮)
- api:LIBRARY_ARCHIVE_ROOT + 归档根只读挂载
- docs/library-capture.md:运维手册与 2026-10-28 两次真实封存实测

不代表整库业务验收通过:内容/问答/性能验收、跨系统切点、Wiki 删除语义重算、
生产预备份/恢复演练/双闸/同步协调仍未完成。
2026-10-28 11:39:50 +08:00
姚章浩 864a1632e0 test(snapshot): 增加真实测试源只读诊断并记录孤立索引阻塞
build / build (push) Successful in 1m11s
2026-10-28 08:32:23 +08:00
姚章浩 9f990158b9 test(snapshot): 联合验证真实隔离存储与暂停恢复链
build / build (push) Successful in 1m8s
2026-10-28 08:22:21 +08:00
姚章浩 9105f8316c feat(snapshot): 增加整库独立快照及测试停写恢复基础
build / build (push) Successful in 1m20s
2026-10-27 23:44:12 +08:00
姚章浩 74aef1e373 fix(fetch): 修复钉钉来源身份与取件失败监控
build / build (push) Successful in 1m17s
2026-10-27 14:36:24 +08:00
姚章浩 e744114ff4 fix(catalog): 打通共享授权扫描与统一来源清单显示
build / build (push) Successful in 1m18s
2026-10-26 18:36:11 +08:00
hermes 43b1e423fe fix(qa): 规范化邮件调度测试并声明其外部依赖
build / build (push) Successful in 1m11s
gofmt 修正;该集成测试需要一次性库与测试密钥,按仓库约定在 test-scope.json 声明为可选未运行。
2026-10-26 10:01:14 +08:00
姚章浩 533db535f1 feat(qaops): 接入真实模型补缺任务与总表看板
build / build (push) Successful in 1m12s
按已确认需求提供独立安全模型配置、真实探测、正式字典字段评估与原问题译中,持久批次支持只补空白、暂停恢复、有限重试、幂等和逐字段依据。覆盖固定规则、本地语言、组织关联与主表有效看板共用治理侧读模型,保留源生产和冻结历史。

验证:完整 make verify 退出0,702项Go与329项前端通过;默认118项外部/隔离集成明确跳过,另跑完整镜像及组织隔离PG竞态。12并发、输入漂移、过期租约、暂停/恢复及有限重试通过。用户授权的百炼 deepseek-v4.1-flash 完成真实后台与浏览器评估/翻译;552行、16列、11块看板及完整PNG、独立配置页测试/保存、重开恢复和零JS异常通过。仅非业务样本外发,自动处理关闭,原payload不变。

上线前治理库已做导出快照原生备份并真实恢复,60表行数与17个QA表内容哈希一致;不修改DobotAI生产或隧道。本提交时尚未部署,线上验收须独立完成。
2026-10-19 14:29:49 +08:00
姚章浩 f5e0f8d20f feat(qa-ops): 建立独立正式主表并保真导入历史问答
build / build (push) Successful in 1m7s
将可维护总表与可替换2.0镜像分离,稳定来源关联和原始事件键保留;
源端更新/删除不覆盖主表或人工复核。首次收录保留原评估审计,
主表输入/复核独立持久化;历史原标注不冒充新AI或冻结证据。

历史导入绑定源哈希、逐行批准清单与真实恢复备份;三字段精确去重,
批次及原字段留档,重复执行不覆盖后续修改。仅操作本地治理库。

验证:完整make verify退出0;镜像与区域两组隔离PG race退出0;
真实恢复数据隔离导入12522、跳过792、暂缓1,总表15050;
重放保留模拟修改,结构down拒绝丢历史;真实浏览器第41周551条完整,
历史原标注和独立主表复核读回通过,移动布局及JS异常0。
2026-10-19 08:49:18 +08:00
姚章浩 3e7fcce0b1 feat(qa-ops): 持久本地问答副本与安全答案预览
build / build (push) Successful in 1m11s
新增迁移50与每5分钟/手动持久同步,API只读本地;同步失败保留、权限阻断、租约与质量失效原子激活。
保留全量稳定分页/固定列,已存质量批量读取,元数据不反复解压长答案;详情安全Markdown与原文。

已运行整仓 make verify、源与副本隔离PG -race、真实只读源及隔离浏览器。
实际同步2525条;固定窗口640条本地中位1.46秒,对照直接源33.10秒。
停worker、缺源凭据、重启API后仍可查询;未更新8088,未修改生产原问答。
2026-10-18 19:10:47 +08:00
姚章浩 74ba737b1e fix(version): 按完整提交区分同号历史开发说明
build / build (push) Successful in 1m4s
历史分支中存在两条开发号 0.1.1;归档选择按 SHA 匹配,保留原编号与冲突标签。
增加同号前置说明遮蔽及错 SHA 不复用回归,标签计划使用相同精确选择方式。
已运行:定向 Go 测试、快照工具临时仓正反例及整仓 make verify;未部署。
2026-10-18 18:18:43 +08:00
姚章浩 013ce4a24e fix(version): 补全开发快照记录与同步交付闭环
build / build (push) Successful in 1m3s
收录 main、注释开发快照和完整运行 SHA;开发说明绑定原提交,补记 0.1.1–0.1.25。
进行中同步按仓库合并,完成后重新执行;启动入队,部署脚本读回版本证据。
开发快照独立于正式标签与 Release,既有冲突标签不移动。

已运行:整仓 make verify,Go 673 通过、前端 274 通过,92 个已声明可选跳过,必需跳过 0。
隔离 PostgreSQL 重复同步队列用例及 -race 通过;未更新现有 8088、未修改生产问答。
2026-10-18 18:11:49 +08:00
姚章浩 4b3aaff321 feat(qa-ops): 完成复检留档与人工复核闭环
治理侧保存输入观测版本、自动复检及人工复核历史,输入变化失效、版本冲突和幂等重放不覆盖人工记录。批量质量数量使用完整筛选元数据,未采集原文不伪造快照,延迟旧扫描不回退新输入。

验证:全仓 make verify 退出码0,Go656及前端253通过;五包竞态、源/组织/质量隔离PG、真实只读源和隔离网页读回复核通过。默认91个可选集成跳过不计通过。未接AI模型,不写DobotAI源库,未推送、未合并、未部署。
2026-10-17 22:37:26 +08:00
姚章浩 9556db1ef0 feat(qaops): 增加个人自然月用量与零记录人员明细 2026-10-17 19:59:33 +08:00
姚章浩 56b8cabed9 feat(qaops): 接入问答组织补全与区域使用统计 2026-10-17 19:02:13 +08:00
姚章浩 cc4906b6a9 feat(qaops): 接通网页通讯录持久化同步与后台接管 2026-10-17 17:36:07 +08:00
姚章浩 5874134e04 feat(qaops): 接入真实通讯录采集并补全根部门 2026-10-17 16:11:29 +08:00