317 Commits

Author SHA1 Message Date
zheromiku 034c0b3a1c Merge pull request 'fix(library): 跨源同步按目标范围改写并校验残留' (#13) from fix/library-cross-scope-sync-residue into main
build / build (push) Successful in 1m18s
2026-10-29 22:13:49 +08:00
姚章浩 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
姚章浩 3284658eb1 feat(web): 发布页接上签发同步授权与建立基线入口
build / build (push) Successful in 1m31s
发布页原来只有一个永远 disabled 的"启动同步",title 还写着"生产同步执行链
尚未接线"——执行链其实已经存在。现在:

- 管理员看到"签发同步授权(30 分钟)"(action=sync,绑定范围+目标快照),
  签发与执行仍是两步,不会自动签发;
- 闸口全满足时"启动同步"真的按 POST /library/syncs 投递,被拒时逐条显示
  接口返回的缺项;
- 目标非空又无生效版本时,管理员看到"建立目标权威基线(维护隔离)";
- 测试封存候选改用接口给的 test_sealed(来自版本登记),不再拿归档里最新
  一份顶替;
- 维护卡片区分"未运行维护 / 维护能力未接线 / 能力未知",逐项列出缺的变量。

验证:vitest 99 个文件 469 个用例全绿,tsc --noEmit 无错;反向自检(把按钮
改回恒 disabled、把维护文案改回旧句)两处按预期变红后复原。
2026-10-29 18:16:25 +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
姚章浩 f9c15a8f14 fix(web): 存储提示改为实际能力
build / build (push) Successful in 1m24s
页面仍写"保留策略与清理在系统设置→存储管理,均未接线",但存储与备份已有只读
去重/保护范围/预览。改为说明可查看什么、什么尚未开放。
2026-10-29 15:24:30 +08:00
姚章浩 271f9f5f09 docs: 台账补记 0.1.198–0.1.199
build / build (push) Successful in 1m20s
记录接管分类与维护释放幂等的真实 SHA、CI run(236)与候选栈身份注入后
/api/v1/build-info 的读回结果。
2026-10-29 14:27:53 +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
姚章浩 691e4e0225 docs: 台账补记 0.1.195–0.1.197
build / build (push) Successful in 1m16s
记录 R1 身份契约、封存资源诊断与 R4 成功边界三个提交的真实 SHA 与 CI run,
标明候选栈 clean 构建(vcs.modified=false)与真实验收覆盖范围。
2026-10-29 14:07:35 +08:00
姚章浩 8d2ed8ebe6 fix(library): 维护释放与服务恢复完成之后才暴露作业成功
build / build (push) Successful in 1m32s
原本 execute 写完生效版本就写 succeeded,而服务恢复、入口开放与维护锁释放发生在
窗口返回之后。任一步失败时外部已看到成功,重启接管又只认非终态,于是目标永远
留在维护里(本轮真实现象:同步 succeeded 后立即恢复被 409)。

现在内容校验与版本登记完成后只记"待收尾":finishLibrarySuccess 先确认目标写入者
与入口健康,再按 scope → instance 顺序释放两把锁(每一步错误都上抛,不再 `_ =` 丢弃),
最后才写 succeeded;任一步失败即落失败并保持 needs_restore。释放与终态写入用
context.WithoutCancel 有界包住,停机不会留下半成品。接管的 finalize-only 判定改为
按证据(finalize 阶段成功或未顶掉的生效版本),只复核与登记,不重写生产。

测试覆盖:调用顺序(成功后两把锁必已释放)、健康失败不释放不报成功、三步释放失败
各自不报成功、终态写入失败提示等待接管、接管证据判定正负例。
2026-10-29 14:02:09 +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
姚章浩 5d42cba6c6 docs: 执行器证据绑定到确切代码 SHA 而不是版本号
build / build (push) Successful in 1m15s
矩阵里写"0.1.187 构建"会与台账的版本号漂移;改成代码 SHA 4c79052(0.1.192 工作树),
与候选二进制来源一致。
2026-10-29 13:12:33 +08:00
姚章浩 a47f9323e0 docs: 台账补记执行器可用性与还原入口(0.1.188–0.1.192)
build / build (push) Successful in 1m29s
记录真实 SHA、CI run 229(head 4c79052,必需 context success)、候选栈二进制来源与
回退目录。代码 SHA 与已部署候选产物分开写,不把文档提交当成部署证据。
2026-10-29 13:07:51 +08:00
姚章浩 4c79052a18 test(release-worker): 测试 DSN 改为拼接构造避免密钥扫描误报
build / build (push) Successful in 1m34s
完整带口令的 URL 出现在源码里会被 security-secrets 判红(测试值也一样)。
按既有做法把连接串拆开拼接,语义不变。
2026-10-29 13:05:23 +08:00
姚章浩 18276d0839 docs: 按用户决定收窄整库还原范围并记录执行器证据
发布管理不承担 Wiki 语义撤销:V03-a 的原始失败证据保留,但移到"模块范围外",
不再阻断发布,也不改判为通过。矩阵同步补上执行器运行时可用性与按版本恢复入口的
真实证据(候选栈),并把尚未跑的部分(真实封存身份复核、新构建上的真实恢复)
写在"已实现·未跑"。正式 release-worker 的崩溃根因按实测写清:目标库主机
prod-pg 不可解析,修正配置属于正式栈变更,需要部署窗口。
2026-10-29 13:01:51 +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
姚章浩 c40696145b feat(library-web): 恢复入口按真实条件启用并支持签发恢复授权
原来"恢复至该版本"写死 disabled,标题还写"生产恢复未接线"——而 POST /library/restores
与授权接口早已可用,等于把整库还原的唯一入口藏起来。

现在页面按 GET /library/restore-availability 启用:缺授权时管理员可签发 30 分钟
有效期的 restore 授权(固定期限,没有"永久授权"),条件满足才可点恢复;工程师只看到
条件与"仅管理员可发起"。封存不可发起时也不再写死"通道未接线",而是显示接口原因、
执行器身份与观测时间。
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
姚章浩 a06945516f docs(acceptance): V03 撤销语义不通过,补 B02/V05/死信实测
build / build (push) Successful in 1m13s
按父审后重跑的真实结果改写验收矩阵:V03-a 单列"失败/不通过"(共享页正文仍带
撤销方独有事实);V05 并发阻断、B02 删源对象后目标应用读回、死信精确解除的
"已做/未演练"分开放;记 R03 实测耗时与正式栈 socket 风险;台账补 0.1.181–0.1.186
与 run 222–227。
2026-10-29 07:22:16 +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
姚章浩 958292d1ff docs(ledger): 按实际运行的二进制记封存闭环时序
build / build (push) Successful in 1m14s
2026-10-29 06:36:05 +08:00
姚章浩 bc49b85b56 docs(acceptance): 封存闭环进"已完成",补死信最小外部契约
build / build (push) Successful in 1m12s
- 矩阵:新增 S06/S06-a/S06-b/V03-c/Neo4j 拒绝行(都有真跑证据);把"封存入口未接线"、
  "CLI 库级窗口"从缺口里移走;排空时序旧结论保留但标注被确定性做法取代。
- 死信解除仍缺产品路径,写出最小外部治理契约(保留原始行、审计、验证真实处理成功后
  按精确项解除、未解除继续阻断)。
- 台账补 0.1.169–0.1.180 的 SHA 与 run(含 211/215 failure、218 cancelled,不涂改)。
- library-capture / 8088 部署方案同步成本轮真实结果。
2026-10-29 06:34:05 +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
姚章浩 2e39e49569 chore: 移出一次性核对小工具(不留在仓库里)
build / build (push) Successful in 1m16s
2026-10-29 06:22:00 +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
姚章浩 4d6ecda310 feat(libraryctl): 源对象存储端点可按宿主机地址覆盖
build / build (push) Successful in 1m13s
容器 env 里的 MINIO_ENDPOINT 往往是服务名(langfuse-minio:9000),封存跑在宿主机上
解析不到,真实封存报 read library resource 并整体失败。
新增 -source-s3-endpoint:只换访问入口,桶与对象键不变;缺 scheme 直接拒绝;
status 会打印实际使用的 endpoint。

试过:make verify 15 项全绿;新增覆盖用例(生效 + 拒绝无 scheme)。
2026-10-29 06:09:56 +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
姚章浩 ee7688f24e docs(matrix): Wiki 撤销检查实际 13 项,改正计数
build / build (push) Successful in 1m13s
2026-10-29 05:18:42 +08:00
姚章浩 9d0fe33c13 docs(ledger): 补记 0.1.162–0.1.167 与验收代码 SHA
build / build (push) Successful in 1m13s
0.1.166(1faf513)是本轮验收代码 SHA;f82eb07 起的文档提交不改代码。
0.1.165 因后续提交被 CI 取消,按事实记为 cancelled,不改写成成功。
2026-10-29 05:15:53 +08:00
姚章浩 f82eb0735f docs(acceptance): 矩阵按四段重排,补封存链路缺口与 8088 部署方案
build / build (push) Successful in 1m14s
矩阵不再在顶部叠报告:已完成 / 缺代码 / 已实现未跑 / 需授权四段分开,
把本轮 Wiki 撤销真实验收(12 项检查)、贡献级差异、维护面板写进对应行,
并如实记下封存链路缺口(CLI 无入口排空证明、API 封存未接线)、
死信无解除契约、Neo4j 场景与排空探针未跑。

新增正式 8088 待验收部署方案:迁移 067–072、loopback 配置差异、
缺配即拒绝的 fail-closed 规则、回滚与账号数据保留。只准备,未执行。
2026-10-29 05:13:15 +08:00
姚章浩 1faf5137ea fix(libraryctl): 用完整容器 ID 判定写入者归属
build / build (push) Successful in 1m14s
`docker ps -q` 默认输出 12 位短 ID,与 declared 里的 64 位 ID 永不相等,
结果连已声明的写入者都被判成"未声明的并发写入者",封存永远开不了窗。
改用 --no-trunc 并按前缀双向匹配。
2026-10-29 05:02:10 +08:00
姚章浩 fd71ea349e fix(libraryctl): 同镜像不等于同一个写入者
build / build (push) Has been cancelled
共享业务栈与专用验收实例共用同一个 WeKnora 镜像,原来只按镜像判"未声明写入者",
任何一台开着共享栈的机器都永远开不了窗。判据改为:同项目或无项目标签即冲突;
另一个项目只有数据库/队列目标与源相同才算冲突。单测覆盖六种组合。
2026-10-29 05:01:22 +08:00
姚章浩 906c282986 fix(libraryctl): 捕获/预检也走同一份 scope 映射
build / build (push) Successful in 1m14s
上一提交只改了 status;capture/preflight 仍直接读 routing bindings,
所以 CLI 封存的还是 bindings 里的库,而不是 API 的目标——契约读取直接失败。
2026-10-29 04:59:57 +08:00
姚章浩 23cc6c0afe fix(libraryctl): 支持 MinIO 源,并与 API 共用同一份 scope 映射
build / build (push) Has been cancelled
为什么:专用验收实例用 S3 兼容的 MinIO,而 CLI 的源发现只认 oss 环境变量,
`STORAGE_TYPE=minio` 直接失败;同时 CLI 只读 routing bindings,API 读
LIBRARY_PRODUCTION_KB_*,两边指向不同知识库时,"封存的是哪个库"就说不清。

怎么改:新增 objectStoreFromSourceEnv(oss/minio,端点/桶缺失即报错,不退回默认值),
scopeBindings 让环境变量优先、bindings 兜底。单测覆盖 minio http/https、oss 与未知类型。
2026-10-29 04:58:45 +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