姚章浩
5defe238e0
fix(library): 跨范围同步按目标范围改写并校验残留
...
build / build (push) Successful in 1m34s
真实事故:源 KB(10000/0c84a098) 同步到目标 KB(10001/e64e82b5) 时,归档行被原样
插入目标库,落在来源租户/知识库;资源阶段随即报"生产缺少资源 ... 的存储路径"
(资源查询按目标租户过滤,看不见刚写进去的行)。回滚只按目标范围回放,于是页面
报"已恢复到预备份",目标却多出 4 文件/38 分块/21 资源/4 绑定。
改:
- 写入与校验共用 retargetRow:tenant_id / knowledge_base_id / 资源定位符(后端、
provider、桶、租户前缀) / storage_backend_id / provider / embedding_model_id
统一映射到目标范围;行里的来源身份对不上就失败,宁可不写。
- Verify 增加"本次写集合仍以来源身份残留"判定;Restore 增加整库指纹比对(预备份
时刻 vs 恢复后)与孤儿资源清理,回滚后用 Verify 复核,恢复工具退出 0 不再等于
恢复完成。
- 新增 cmd/libraryresidue:按归档身份精确、可备份、可复核地清理已知的范围外残留。
验证:make verify 全绿;真实 PG+ES 用例复现"源目标 KB 不同 + 目标非空 + 资源阶段
失败",断言回滚后无来源范围残留、其它租户行未动、恢复后内容=预备份;正向用例断言
行落在目标范围且资源定位符指向目标存储。
2026-10-29 22:11:59 +08:00
姚章浩
c27a0c14cc
fix(library): 基线契约读目标真实 schema/模型,演练标内容风险
...
build / build (push) Successful in 1m22s
真实演练暴露两个只有跑起来才看得见的问题:
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
姚章浩
a42b101a7b
fix(library): 基线崩溃续作与维护原状态保留
...
build / build (push) Successful in 1m25s
父审发现三处会让目标卡死或状态对不上实际入口,逐条修:
1. 登记成功但没来得及收尾:崩溃重入会撞上"已有生效版本"直接失败,
目标留在维护里、占用也不释放。现在按「本作业+同一快照」识别为自己的
版本,只做收尾(恢复服务/开入口/释放两把占用),不重新捕获、不重写;
别人的版本仍然拒绝。捕获完成但登记失败则明确失败并写"未对目标写入"。
2. 重入覆盖原运行状态:崩溃时服务已被停住,再读一次只会读到 stopped,
写回去会让恢复阶段永远不启动写入者。AcquireInstance/LibraryMaintenance
在 owner 相同的重入时保留已持久化原状态;新作业接管已释放窗口用自己的观察。
真实 PostgreSQL 集成用例覆盖(先红后绿)。
3. 版本登记移进维护窗口:捕获/校验/登记要么一起成立、要么目标原样恢复;
记录失败时窗口已开放,治理状态同步释放,不再"显示维护中、实际已开"。
索引映射读不到时拒绝登记,不再用空摘要写证据。
顺带:
- 基线窗口统一收尾抽成 settleBaselineWindow,四种结局各有测试(成功/健康失败/
held 保持维护/已恢复则释放),并用真实 DB 用例证明 baseline 与 sync/restore
共用写互斥、幂等重试不产生第二个作业。
- 新增 cmd/librarydrill:维护隔离 → 整库预备份 → 一致性校验 → canary 漂移检测
→ 按预备份恢复 → 逐类复核 → 按原状态恢复服务。用与 worker 相同的 LIBRARY_*
配置,绝不触碰封存源。
- 目标/预备份/维护栈装配收成 internal/librarystack 一份(worker 与演练共用)。
验证:go build/vet/test 全过;TEST_DATABASE_URL 指向临时 PostgreSQL 跑
TestMaintenanceReentryKeepsOriginalServiceState(修复前失败:got app=stopped)
与 TestLibraryWriteMutexCoversBaselineJobs;本地门禁 15/15。
2026-10-29 19:08:58 +08:00
姚章浩
517d5f862d
feat(library): 首次发布接上目标权威基线与整库空判定
...
目标非空又没有生效版本时,首次发布以前只能停在"权威版本未知"。现在有了
真实入口:管理员发起 baseline 作业,worker 在维护隔离下把目标整库捕获成
归档快照,再用同一套 Verify 证明目标与快照逐类一致,最后登记
production_effective(证据写明 origin=target_baseline、快照 ID、逐表行数、
目标索引映射)。不写目标内容、不清空目标、不手写版本。
同时修两处会误导人的判定:
- productionScopeEmpty 原来只数 knowledges,"文档删了但分块/Wiki/资源还在"
的目标会被当成空库;改用适配层同一份表清单与范围过滤逐表计数。
- 概览里的"测试封存候选"原来取该 scope 最新一份归档,而归档里还有生产
预备份与目标基线快照;改为只认版本登记里 kind=test_sealed 的那一份。
迁移 075 放开 kind='baseline' 并把它并入 sync/restore 的写互斥索引;
worker 重启时用同一作业 id 接管未完成的基线窗口。维护接口补 capability,
页面才能区分"未运行维护"与"维护能力没接线"。
验证:go build ./... / go vet ./... 通过;新增用例覆盖管理员校验、已有生效
版本拒绝、目标读不到时零作业零版本、候选来自登记、缺项文案指向基线入口、
capability 逐项缺失;反向自检(把候选改回按最新归档取、把文案改回旧句、
去掉管理员判断)三项均按预期变红后复原。
2026-10-29 18:16:21 +08:00
姚章浩
24d2546cbb
fix(library): 接管不再把版本查询失败当成没有收尾证据
...
build / build (push) Successful in 1m28s
resumed 分支里 LibraryVersions 的错误被忽略,FinalizeOnly 保持 false 后继续完整
apply:对"已登记版本并释放窗口、只差终态"的作业,这等于把同一份内容重写一遍目标,
与"最终只复核"的承诺相反。
现在接管先分类:finalize 阶段成功作为独立证据(不受版本查询影响)→ 只收尾;查询
失败且无阶段证据 → 未知,零写入、可重试;本快照被别的快照顶替 → 拒绝覆盖;其余才
按正常流程。
同时修释放幂等:Update*/Release* 对"已由本作业释放"的行返回 ErrMaintenanceReleased,
收尾重试视为已完成(部分释放后不会永久卡死),别的作业仍报未持有。真 PG 负例验证
两种情形。
2026-10-29 14:24:12 +08:00
姚章浩
c14dd01f08
fix(library): 封存读资源失败带上资源行与底层原因
...
build / build (push) Successful in 1m18s
真实封存卡在 `read library resource` 时没有任何可判断信息:对象缺失、桶写错、
宿主机解析不到端点都会走到同一个字符串。现在错误带资源行 id 与底层原因
(不含凭据),本轮就是用它在候选栈定位到"源对象被上一次 B02 演练删掉后没真正写回"。
2026-10-29 13:32:55 +08:00
姚章浩
6ed06357f9
fix(library): 期望执行器身份随 capture 请求送达并在执行前核对
...
build / build (push) Successful in 1m27s
原来 worker 先 health 再 capture,两次拨号之间 socket 可能被换成另一个实例的
执行器:错的 agent 已经把写入者停了、快照也出了,事后 postcheck 只能拒绝登记,
副作用无法撤销。
现在 capture 请求自带期望身份(治理侧配置生成:project 必填,source_app /
archive_root 配了就绑定)与覆盖三项的稳定指纹;接收方在操作互斥内、执行任何源
操作之前比对,缺字段、指纹与字段不一致、project/source_app 不符都直接拒绝,
**错的执行器一次 capture 都不跑**。封存结果回显该指纹,worker 事后复核按它对照,
postcheck 只作为证据防线。恢复路径不要求期望(它只把写入者恢复回来)。
负例是确定性的:两个真实 agent 各占一个 socket,把调用方指向错的那个,断言它的
capture 调用为 0 且仍能回答 health;同一份期望打到对的 socket 则只跑一次。
2026-10-29 13:23:56 +08:00
姚章浩
cfad003499
fix(release-worker): 启动失败带端点与脱敏原因
...
正式 release-worker 崩溃重启时日志只有一句 connect release target database,
主机名、库名和底层原因都不在里面。现在错误里带 host:port/db、路由标识与原因。
端点去口令不等于整条日志安全:pgx 的解析错误可能原样回显连接串。新增
internal/pgdsn.Redact(按已知口令精确替换 + 按 URL/keyword 形状兜底),
错误保留 Unwrap 以便 errors.Is/As,技术原因不丢。cmd/worker 里只留库名的旧实现
换成同一个包,避免两份脱敏逻辑。libraryctl 用法补上 dead-letters。
2026-10-29 13:01:46 +08:00
姚章浩
47eb23cf68
feat(library): 封存执行器可用性与还原条件来自真实观测
...
页面上的"可发起封存"原来只判 LIBRARY_AGENT_SOCKET 非空:正式栈的 API 根本没挂
那个 socket,而 agent 曾顶着正式单元名冻结候选实例——配置非空、页面可点、真发起
却封错库。
现在:agent 新增 health 动作(不占封存操作锁、不跑子进程,只回答身份/降级/忙),
worker 每 30 秒探测并写入迁移 074 的 library_executor_status;overview、快照列表与
POST /library/captures 共用同一份判定(过期、不可达、实例不符、降级、正忙都拒绝并
给原因)。封存结果由 agent 附上自报身份,worker 在停写入者之前再探测一次、执行后
用结果复核,不一致就不登记测试封存——心跳只能证明"刚才"。
期望身份用 LIBRARY_SOURCE_*(封存源),不是 LIBRARY_TARGET_*(同步目标),否则
正确的执行器会被判成不匹配。另加 GET /library/restore-availability:恢复能否执行由
双闸、该快照的恢复授权与实例维护状态决定,页面不再写死 disabled。
真跑(候选隔离栈,两处 socket 隔离不变):overview 返回 executor
{kgmaint-weknora/kgmaint-app};停 agent 后转不可达并 503 带原因;重启后恢复。
2026-10-29 13:01:37 +08:00
姚章浩
e508abd958
fix(library): 死信解除绑定封存源实例而不是同步目标
...
build / build (push) Successful in 1m18s
同步目标与封存源常常不是同一个实例(kgseal vs kgmaint);用目标名去绑来源死信,
真实观测的 project 永远对不上,解除会被一律拒绝。新增 LIBRARY_SOURCE_INSTANCE,
留空回落到目标实例名,API 写记录与 worker 取指纹共用同一个来源实例名。
试过:真实 API GET/POST、go test ./internal/{httpapi,appconfig} 与 make verify 全绿。
2026-10-29 07:13:18 +08:00
姚章浩
c542a1333a
fix(library): 资源行的存储后端列也改到目标实例
...
build / build (push) Successful in 1m17s
原版应用先按 resources.storage_backend_id 找文件服务;只改定位符不改这一列,
目标仍报 storage backend not found 并拒绝整条路径(真实下载 500 code1007)。
行变换同时改写该列,写入与校验共用。
试过:目标同步后 /api/v1/knowledge/{id}/download 500 → 修后复测读回 507B 且
sha256 与源对象一致;go test ./internal/releaseadapter/v071 与 make verify 全绿。
2026-10-29 07:08:39 +08:00
姚章浩
1aecd9d6af
fix(library): 资源定位符同时改到目标存储后端
...
build / build (push) Successful in 1m16s
storage://<id> 是来源实例 storage_backends 的主键;只换桶不改 id 时,目标原版
应用以 storage backend not found 落回默认服务并拒绝整条路径(真实下载 500
code1007)。写入前按租户+目标桶解析目标后端,写入与校验共用同一份行变换;
解析不到就拒绝写入,不再产出目标读不出来的定位符。
试过:目标实例真实同步后 /api/v1/knowledge/{id}/download 读回 500 → 修后复测;
go test ./internal/releaseadapter/v071 与 make verify 15 项全绿。
2026-10-29 07:04:45 +08:00
姚章浩
c2402947a9
feat(library): 死信按精确指纹解除,保留上游记录
...
build / build (push) Successful in 1m26s
上游 task_dead_letters 没有解决标记,purge/DELETE 会销毁"失败过"的唯一证据。
治理侧新增解除记录(迁移 073):绑定实时行逐字节指纹、重放引用、源侧观测摘要与
操作人;行被改过、重放后又出现新死信、源侧未结清都不命中,封存照旧阻断。
指纹先压紧空白再摘要,避免 PG to_jsonb::text 与 json.Marshal 的空白差异。
试过:go test ./internal/{librarydeadletter,libraryagent,httpapi,releaseadapter/v071,
platform/postgres},make verify 15 项全绿。
2026-10-29 06:55:13 +08:00
姚章浩
a6406209bd
fix(httpapi): 恢复作业 id 改成维护窗口可接受的形状
...
build / build (push) Successful in 1m18s
librestore-<scope>-<时间戳.微秒> 带 ".",冻结协调器的 job 白名单只收
[A-Za-z0-9_-],所以每次恢复都在开窗口时被拒(invalid freeze request),
作业随后被接管扫成失败。改纳秒整数,并加测试锁住该约束。
试过:make verify 15 项全绿;真实恢复复现该错误后修复。
2026-10-29 06:28:29 +08:00
姚章浩
b061a9686c
fix(v071): 资源换桶的变换在校验时同用
...
build / build (push) Successful in 1m17s
只改写入侧会让"行内容摘要"对不上:校验拿变换前的归档行与目标里的变换后行比较,
真实同步在 verify 阶段报"生产 resources 内容与快照不一致"并回滚。
把行变换抽成 rowTransform,写入与校验共用同一份。
试过:make verify 15 项全绿;真实同步复现该失败后修复(下一轮重跑验证)。
2026-10-29 06:24:14 +08:00
姚章浩
cb0090802b
fix(v071): 同步资源行时把定位符的桶换成目标桶
...
build / build (push) Has been cancelled
同步把资源字节写进目标桶,但 resources.physical_path 是从快照原样搬的,
里面还写着来源桶(storage://…/minio://kgmaint-1028a/…)。目标实例之后按这一行
取图/附件就会指向测试环境的桶,或直接取不到——跨环境引用,必须在本轮修掉:
写入前换桶、键不变,定位符解析不出来就报错(不写指向别处的行)。
试过:make verify 15 项全绿;新增定位符用例(嵌套/单层/裸键/非法);
真实同步后目标桶里对象键与行内定位符一致。
2026-10-29 06:21:53 +08:00
姚章浩
7d4ffdbfe5
fix(v071): 全新目标实例的空索引按空基线处理
...
build / build (push) Successful in 1m15s
全新 WeKnora 会用空映射({})先建好索引,范围字段等第一次写入才动态生成。
首次同步在读目标当前状态时就报 "missing release scope fields" 并失败,等于
"往空的生产实例做第一次同步"这条路根本走不通。
现在:"没有范围字段 + 索引确实 0 篇文档" = 空基线;有文档却看不出归属仍然报错
(绝不把无法归属的文档当空库覆盖)。Count 与整库导出共用同一判断。
试过:make verify 15 项全绿;新增空/非空两种索引的用例;真实 sync 复现原错误。
2026-10-29 06:17:26 +08:00
姚章浩
84e8a0dfdc
style(libraryagent): gofmt
...
build / build (push) Successful in 1m14s
上一个提交漏跑 gofmt,本地门禁 format 失败;只改空白。
2026-10-29 06:12:46 +08:00
姚章浩
faaf003707
fix(libraryagent): 把宿主侧对象存储端点纳入固定命令形态
...
build / build (push) Failing after 17s
上一提交只给 libraryctl 加了 -source-s3-endpoint,agent 却不知道这个 flag,
systemd 起不来(exit 2)。端点属于"这台主机怎么访问这个源的存储",必须由 agent
自己钉住并随固定声明传给 libraryctl;请求仍然不能改。没配置时不凭空加 flag。
试过:make verify 15 项全绿;新增 agent 声明用例(有端点必传、无端点不多传)。
2026-10-29 06:11:29 +08:00
姚章浩
f2d6f3f646
fix(httpapi): 封存队列作业 id 按 jobs.id 列宽拼短
...
build / build (push) Successful in 1m17s
真实请求 POST /api/v1/library/captures 一律 500:拼出来的
"library-capture-<scope>-<nano>" 是 45 个字符,而 jobs.id 是 varchar(36),
PG 直接拒绝(value too long)。此前页面一直停在"未接线",这条路径从没被
真实请求覆盖过,所以只有接上 agent 之后才暴露。
改为 libcap-<scope>-<nano>(35 字符),并用测试锁住列宽与形状。
试过:go test ./internal/httpapi -run LibraryCapture 全绿;修复前用真实
DATABASE_URL 复现 enqueue error: value too long for type character varying(36)。
2026-10-29 06:05:16 +08:00
姚章浩
37a8370f7b
fix(v071): 索引范围过滤接受未写 tenant_id 的文档
...
build / build (push) Successful in 1m15s
真实 v0.7.1 的 app 自己写索引时不带 tenant_id,治理侧同步写入时才补齐。
原来只按 term tenant_id 过滤,等于把 app 写入的正文整库漏掉:真跑时索引里有
2 条文档,导出 0 条,候选校验报 legacy coverage,封存被拒。
改为"显式本租户 或 完全没写租户字段"(写了别的租户仍然排除),知识库身份
仍是独立 term,范围没有放宽。Count/PruneScope/整库导出共用同一写法。
试过:make verify 15 项全绿;新增 3 个过滤条件用例;真实 preflight 由
candidate-invalid(0 条) 变为 candidate-valid(2 条)。
2026-10-29 06:00:30 +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
姚章浩
1f482e4b49
feat(library): Wiki 撤销按贡献比对,维护窗口在页面如实呈现
...
build / build (push) Successful in 1m18s
为什么:删除一篇与别人共同贡献同一 Wiki 页的文档后,页面还在、名字也在,
只有那篇文档的正文与引用被摘掉。原来的页面级差异会把这种语义重算显示成
"无变化",操作员据此会误判撤销没有生效。
怎么改:librarydiff 新增 CompareWiki,以 slug 为身份把正文/来源文档/引用分块/
出入边/目录分别取集合比较,输出 sources_removed、citations 变化与具体类别;
overview 的 diff.wiki 增加逐页明细(最多 50 页,超出置 pages_truncated),
说明文字明确"重算发生在源侧,本平台只如实比对与保留结果"。
页面:新增目标维护面板(实例级/库级、持有、入口开关、阶段、进度、恢复错误),
只有窗口仍持有且作业失败时才给"按预备份恢复";没有维护记录时写未接入 + fail closed。
变更页把 Wiki 逐页差异列出来源/引用怎么变。kind 补上 restore。
验过:go test ./internal/librarydiff ./internal/httpapi;web vitest 453 项;
make verify 全绿(15 项,无未运行项)。真实撤销语义验收见交付报告。
2026-10-29 04:45:34 +08:00
姚章浩
0ef79846e8
fix(library): 后台静默必须在关入口之后证明,并在停写者后复核
...
build / build (push) Successful in 1m19s
原顺序先证明后台空、再关入口排空:排空期间还在服务的在途请求会派生新任务,
那一刻的空到停写者时已经失效,而 app 收到 SIGTERM 会 exit(1) 把任务腰斩。
现在固定为:关入口并排空 → 证明后台静默(连续为空 + 稳定窗口 + 任务指纹)→
停写入者 → 停完再复核(队列非空/指纹变化即判 background_truncated)。
scheduled/retry 自行产生任务或任务卡住时 fail closed;旧 journal 的
background_idle 布尔不再当证明;入口关到一半失败时恢复入口并保持维护。
2026-10-29 04:02:59 +08:00
姚章浩
c7dafe987e
fix(library): 恢复校验按归档身份核对共享资源表
...
build / build (push) Successful in 1m12s
resources 是多库共享表:采集看正文里的 resource://handle,校验只看 chunks 重算可见集,
两边口径不同,恢复得一字不差也会被判 15/19 而拒绝开放。改成按归档 id 核对存在性与内容摘要。
2026-10-29 02:03:55 +08:00
姚章浩
f055fc174d
style(library): gofmt 恢复重排文件
build / build (push) Successful in 1m19s
2026-10-29 01:38:46 +08:00
姚章浩
b007acd443
feat(library): 恢复失败后可以重新排队,不必手工改库
...
build / build (push) Has been cancelled
失败作业的幂等键把恢复入口锁死,运维修好故障也点不动第二次。
现在按窗口重排失败作业(同窗口有活作业则拒绝并发),CLI/API 都能真恢复。
2026-10-29 01:38:39 +08:00
姚章浩
7e4900b51b
fix(library): 恢复前按实际运行状态重新停写入者
...
build / build (push) Successful in 1m16s
上一次窗口写下的 StopRequested 只是历史,写入者被人工拉起来后必须再停一次,
否则恢复校验会跑在半开的目标上。补一条回归测试。
2026-10-29 01:30:59 +08:00
姚章浩
1a04dfb4fa
fix(library): 恢复作业的队列 id 不能超过列宽
...
build / build (push) Successful in 1m15s
"librecover-"+窗口作业 id 超过 varchar(36),投递直接失败——恢复入口等于打不开。
按窗口摘要取 33 字符的确定性 id。
2026-10-29 01:07:11 +08:00
姚章浩
faf8027890
fix(library): 修 Docker inspect 反序列化,拓扑校验才读得到端口
...
build / build (push) Successful in 1m14s
containerState 里把 Config 映射成 map[string]string,真实返回是对象,
于是每次拓扑校验都报 invalid docker response——所有窗口都被误拒。
2026-10-29 00:56:22 +08:00
姚章浩
3643e9bf82
fix(library): 维护收尾必须按预备份恢复并校验
...
build / build (push) Successful in 1m20s
删掉会自己开入口、只看服务健康的 FinalizeAfterOperatorRecovery。
新的 RecoverWindow 绑定原作业/目标指纹/持久化预备份,在维护关闭下
执行恢复与逐类校验,通过才恢复服务与入口;失败继续维护。
预备份身份与内容风险在第一笔写入前一起落盘;后台任务必须静默后才停写者;
写入者发布端口即拒绝开窗口;维护占用改为按实例互斥(000072)。
2026-10-29 00:50:32 +08:00
姚章浩
ec737a9a65
fix(library): 排空证据要读闸门记录的 active_at_close
...
build / build (push) Successful in 1m16s
闸门写的是 active_at_close / drain_ms,Go 侧却去读 active(排空后必然 0)
与 waited_ms(已改名),于是证据里恒显示"关闭时无在途连接"。改为读真实字段。
2026-10-29 00:12:10 +08:00
姚章浩
96fffb5081
fix(library): 排空证据必须由本次关闭产生
...
build / build (push) Successful in 1m15s
读上一次的 drain.json 会把"上次排空过"当成本次证据。关闭前先删并确认
不存在,关闭后校验 mtime 不早于本次关闭;闸门没在跑就失败而不是读旧文件。
2026-10-28 23:54:55 +08:00
姚章浩
962bed8f53
feat(library): 人工恢复后按证据收尾维护窗口
...
build / build (push) Successful in 1m15s
回滚失败会按设计保持维护,但此前没有任何"人工确认后收尾"的路径:
journal 一直非终态,之后所有窗口都会被拦住。新增 libraryctl finalize:
不启动服务、不释放治理占用,只核对原本在跑的服务与入口是否真的在服务,
通过才把 journal 标成 operator_recovered。
2026-10-28 23:42:28 +08:00
姚章浩
38d04488de
feat(library): 入口闸门排空 + 维护占用可再接管
...
build / build (push) Successful in 1m28s
被代理的 WeKnora 收到 SIGTERM 会先关监听再 exit(1),在途请求被切断。
排空改到真正的连接入口:闸门停止接客、等在途连接归零、写 drain.json
并 exit 0;协调器只认这条证据,写者退出码只作记录。
顺带修掉维护占用的一条死锁:释放后的窗口此前不能被新作业接管,
等于第一次同步之后谁也再进不去;持有中的窗口仍只允许原作业重入。
2026-10-28 23:35:15 +08:00
姚章浩
6fe98c9a30
feat(library): 同步/恢复改为实例级维护窗口
...
build / build (push) Successful in 1m26s
只锁治理库 scope 挡不住 WeKnora 直连和后台写入。把封存链路的
维护机制接到发布路径:持久化 owner/目标身份/原服务状态,关入口、
停写入者、验证排空与入口不可达,再写目标;写完先恢复原运行状态
与入口才释放占用。内容风险一旦落盘,重启恢复不再自动启动写入者。
新增 000071 迁移记录 level/指纹/服务/入口状态;未接线的目标直接
拒绝写,不用库级锁冒充系统维护。
2026-10-28 23:01:29 +08:00
姚章浩
fe6e218d9c
fix(library): 整库只读接口改清单级读取,修掉真实超时
...
build / build (push) Successful in 1m17s
候选归档 79 版 / 56MB blob 上,overview、storage、snapshots 三个只读接口
都在 10s 请求期限后返回 503。分段计时定位到唯一瓶颈是按版本重读全部 blob:
Open 0ms、LoadManifest×79 11~22ms、ContentUsage 15~25ms、
Usage(逐版 Load 验字节)13.75~14.11s;单版 Load 261~290ms。
- Usage 拆成 Usage(逐版复核字节,同步/恢复与运维 CLI 用)与
UsageFromManifests(同一份去重实现,只做清单级校验),列举路径改用后者;
- library_storage.go 每版两次 Load、snapshots 每版一次 Load 改为一次清单读取;
- wiki/文档差异只读所需部件,被读部件仍由 OpenPart 复核摘要;
- LoadManifest 与 Load 共用同一套清单判据(严格解码 + 重新编码校验);
- 列举响应带 verification=manifest / verified=false,详情整版复核后才是 content,
避免把"清单可读"读成"完整性已验证";
- libraryWithDeadline 去掉超时后再写已结束响应的 goroutine 写法
(候选环境实测 http: superfluous response.WriteHeader call)。
回归:多版本共享大 blob 时枚举不读 blob 字节(篡改后枚举仍成功),
而 Load/OpenPart/Usage 仍拒绝坏 blob;坏清单两条路径都整条报错。
make verify 15 项全绿(local 档案)。
2026-10-28 21:52:14 +08:00
姚章浩
1609a0c9be
perf(librarysnapshot): 盘点改用 LoadManifest,去掉按版本重算 blob
...
build / build (push) Successful in 1m18s
实测:Load 单版 252ms、遍历 79 版 13.8s(2.77GB),这是 overview/storage 超时根因;
不是锁也不是连接池(Open 0ms,无 flock)。
- 新增 LoadManifest:只校验清单摘要与结构;同步/恢复仍走 Load 全量校验。
- ContentUsage 改用 LoadManifest。
- 已知未修完:两个接口仍 503,还有枚举调用点用 Load,待逐个替换。
2026-10-28 21:08:47 +08:00
姚章浩
d1dbb2b945
style: gofmt library_maintenance.go
build / build (push) Successful in 1m21s
2026-10-28 20:47:58 +08:00
姚章浩
288e01a01a
feat(library): 目标维护窗口(持久所有权/独占/release-on-verify)+ known-broken 记录
...
build / build (push) Failing after 16s
为什么:用户确认维护窗口方案,需要真实占有目标而不是 503 页面。
- 迁移 000070 library_maintenance:scope 唯一持有者、作业/目标身份、服务原状态、阶段。
- worker 在写目标前 acquire,验证+登记成功才 release;回滚失败/未验证保持 needs_restore。
- API GET /library/maintenance + 同步入口在他人持有时 409。
- 已知缺陷如实记录:候选实例该路由 404;kill 取证脚本竞态导致三项断言为 false。
2026-10-28 20:45:02 +08:00
姚章浩
b76a433574
fix(httpapi): 整库只读接口加请求级超时,避免归档大时请求挂死
...
build / build (push) Successful in 1m14s
为什么:候选前端联调时 /library/overview 与 /library/storage 会挂到连接被重置,
浏览器无法渲染。加 10s 超时并返回明确降级原因,同时差异计算放进看门狗。
2026-10-28 20:36:59 +08:00
姚章浩
ea4cffbcaa
style: gofmt release_gate_test.go
build / build (push) Successful in 1m20s
2026-10-28 19:14:35 +08:00
姚章浩
c72b71d78a
fix(library): 预备份硬门禁 + 崩溃接管 + 五项入口验收
...
build / build (push) Failing after 17s
为什么:父审指出预备份引用持久化失败仍继续写目标、接管依赖启动扫描且漏掉租约未过期、
恢复失败被标成"无需恢复"、版本登记与作业状态分叉。
- BackupPrepared 返回 error,持久化失败即零写入;预备份同时登记 production_prebackup 版本。
- 接管改为按队列租约判定 + 运行期 30s 扫描 + 原子抢占 + 固定幂等键 + attempt 归零。
- 接管场景保留预备份、不再宣称"未写无需恢复"。
- 恢复/重放同一快照重新激活 production_effective(清 superseded_at)。
- finalize 续作只做校验+登记(finalize_only),不重写生产。
- 索引映射基线缺失时采用首次观察值并登记;索引校验加孤儿文档检查(按 knowledge_id)。
真实验收(候选 API :18089 + 真实 PG/ES/MinIO):
A0 首次发布、A1 撤销、B 按版本恢复、C 写阶段 SIGKILL 接管、
D 跨 kind 互斥与多 worker 单次执行、E1 登记失败、E2 finalize 续作、E3 预备份失败零写入。
2026-10-28 19:11:37 +08:00
姚章浩
ac3b395102
fix(library): 对齐真实 WeKnora Schema + 入口级真实服务验收
...
build / build (push) Successful in 1m21s
为什么:候选环境改用真实 WeKnora 表结构后,暴露出多处"按猜的列名/形状"的假设,
以及若干 varchar 长度、对象定位符、空索引基线问题,导致真实链路跑不通。
- knowledge_tag_relations 无 tenant_id/KB 列 → 归属走父表;resources 作用域=绑定+chunk 引用。
- 写入顺序按真实外键;无代理键表用内容摘要核对。
- 队列作业 ID / 版本记录 ID 改固定长度摘要(列宽 varchar(36)/varchar(64))。
- 对象定位符拆 storage://<backend>/oss://<bucket>/<key>;provider 可配置。
- 空索引基线全链路一致(Current/Export/Backup/Archive),非空仍严格比对映射。
- finalize 阶段:版本登记失败不报成功;崩溃接管复用已持久化预备份。
- 迁移 000069:同一目标的 sync/restore 跨 kind 互斥。
2026-10-28 17:53:43 +08:00
姚章浩
a2d051f6b3
feat(library): 按版本恢复入口与失败作业续作
...
build / build (push) Successful in 1m25s
为什么:恢复此前只有"失败自动回滚",没有手动按版本恢复;失败作业也只能重发封存。
- 迁移 000068 允许 kind=restore;POST /library/restores(管理员 + 该快照恢复授权 + 闸口)。
- POST /library/jobs/{id}/resume 对失败/取消的同步/恢复作业按原参数续作。
- worker 统一 runLibraryRelease,同步与恢复共用链路;成功后登记 production_effective 版本。
2026-10-28 16:27:46 +08:00
姚章浩
0b9dbe12cf
fix(library): 资源范围精确关联 + 内容级校验 + 真实对象存储验收
...
build / build (push) Successful in 1m13s
为什么:父审指出资源范围未关联外层 resources,会把别的库/租户资源算进来;
Verify 只看行数与 ID,同 ID 改正文/改向量/损坏对象都能通过;集成测试还在用假对象存储。
- scopeWhere(resources)/resourcePaths 用 resource_bindings.resource_id=resources.id 精确关联,
覆盖 knowledge/chunk 绑定并限制租户;资源部件名与资源 ID 对齐。
- Verify 增加逐行内容摘要、索引文档摘要、对象字节 SHA256 校验。
- 预备份一致性改为内容指纹(数量+摘要),同数量改写也会失败。
- 新增真实 MinIO 对象存储的本机验收用例,覆盖隔离/损坏/独立恢复。
2026-10-28 16:23:28 +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
姚章浩
67a48faf23
test(qawemail): 去掉单测对外部 DNS 的依赖
...
build / build (push) Successful in 1m17s
为什么:normalize 会解析主机名,测试用 smtp.example.com;构建机没有外网 DNS 时
必红(run164 tests-go 3 个失败)。改成 localhost,并补一条 .invalid 保留域必须
判非法的断言,校验规则没有放宽。
2026-10-28 15:30:43 +08:00
姚章浩
2038462dbb
feat(library): Wiki 页面级差异与页面展示
...
build / build (push) Successful in 1m24s
为什么:撤销文档后 Wiki 变化此前只有"快照保存"证据;页面级差异能给出增/改/撤,
但必须写明它不等于实体、概念、边与引用的语义重算。
- overview 差异新增 wiki 字段:读两份快照的 wiki_pages 行数组比较,缺部件时 unknown。
- 发布管理"本次变更"显示 Wiki 新增/更新/撤销页数与页面级声明。
- 真实归档验证:对内 vs 对外 = 新增 31 / 撤销 1,反向镜像一致。
2026-10-28 15:15:33 +08:00