姚章浩
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
姚章浩
dab74b706b
feat(qaops): 增加版本化部门区域归属配置与预览
2026-10-17 11:39:38 +08:00
姚章浩
fd04bf7886
feat(qaops): 接入独立只读问答查询与治理覆盖复检
2026-10-17 09:04:19 +08:00
姚章浩
b7f7e4f121
fix(deploy): 上线前校验 .env 键值形状(两行粘成一行的坏值不再能上线)
...
build / build (push) Successful in 1m1s
事故复盘(2026-10-14,测试实例 p1 整体不可用约 6 分钟):
- 我用 `rstrip("\n") + 新行` 往 `.env` 追加 `VERSION_HISTORY_LINK_ORIGIN`,而 `.env` 最后一行
当时没有结尾换行 ⇒ 两行**粘成一行**:
`VERSION_HISTORY_GITEA_BASE_URL=http://192.168.50.223:3000VERSION_HISTORY_LINK_ORIGIN=https://gitea.zheromiku.top `
- 配置校验按设计拒绝这个非法 origin ⇒ `cmd/api`/`cmd/worker` 启动即退出 ⇒ 容器 crash-loop
(fail-closed 是对的,但代价是平台起不来)。
修复:
- `scripts/deploy-api-web.sh` 新增 **1a 步护栏**:上线前解析 `.env`,只要某行的值里又出现 `KEY=`
(两行粘连)或键名不合法 ⇒ 打印 `ENV_VALUE_LOOKS_MERGED` 并**拒绝部署**(不动线上);
- `internal/appconfig` 在校验失败时补线索:值里含 `=` ⇒ 提示"`.env` 里两行可能粘成了一行";
- 回归用例 `TestLoadRejectsMergedVersionHistoryEnvValue`(粘行必拒 + 线索文案 + 合法值仍放行)。
验证:护栏对"粘行样例/键名非法"退出码 1、对"正常样例/当前真实 .env"退出码 0;Go 用例通过;
make verify 15/15。线上已修 `.env` 并重启 api+worker(healthy),重新同步后页面链接已全部走公网 origin。
2026-10-14 11:17:35 +08:00
姚章浩
90db243a68
fix(web): 旧 index.html 的哈希资源回退到「上一版构建」,页面不再空白/裸奔
...
build / build (push) Successful in 56s
现象(主人反馈:页面**偶尔**变成"只有文字没样式"):旧入口 index.html 引用的是上一版的
`index-CddbQIqQ.js` / `index-DLg1Y9tN.css`,而新镜像里只剩当次构建的两个文件
⇒ JS 404 白屏、CSS 404 只有文字没样式。触发条件是"短时间内多次重建 web + CDN/浏览器还拿着旧入口"。
修复(三层,各管一段):
1. **入口不缓存**(上一笔已做):`index.html` → `no-cache, must-revalidate`,CDN/浏览器每次回源校验;
2. **旧哈希资源仍可取**(本笔):`/assets/` 未命中时先回退到只读挂载的
`deploy/legacy-assets/assets`(**上一版构建**的资源),再 404 ⇒ 还持有旧入口的客户端
不会因为一个文件 404 而白屏/裸奔;该目录由 `scripts/deploy-api-web.sh` 在**每次重建前**
从"正在运行的那个镜像"里落盘(`LEGACY_ASSETS=` 会打在部署日志里),并加进 .gitignore;
3. **未命中不再回退成 HTML**:避免浏览器把 index.html 当 JS 执行报一堆语法错误。
验证:旧入口的两个引用 `assets/index-CddbQIqQ.js`、`assets/index-DLg1Y9tN.css` 现在都是 200
(旧客户端可用);当前入口的 `assets/index-Dbnd-DW-.js`、`assets/index-Bgkt4A1i.css` 也是 200;
不存在的文件名仍明确 404;`nginx -t` 通过;本机 8088 与公网入口 kg.zheromiku.top 一致;make verify 15/15。
2026-10-14 10:58:01 +08:00
姚章浩
4d43a9386a
fix(version-history): tag 之前的提交不再被编号 + 部署脚本注入构建身份
...
build / build (push) Successful in 1m5s
真实数据发现(本机 p1 实例第一次同步后核对):
- **缺陷**:tag `v0.1.0` 之前的 20 个提交,提交内 CHANGELOG 顶部**也已经**是 0.1.0(旧分支遗留条目),
而它们的祖先集合被那个**更晚才打**的 tag 全覆盖 ⇒ 按提交数算得 0 ⇒ 被错标成「0.1.0 开发版」。
它们的正确判定是「历史提交」:那个 tag 是后来才打的,当时还没有发布基线。
- **修复**:基线 tag 必须是该提交的**祖先(或就是该提交)**,否则不给号;新增原因码
`release_tag_created_later`(页面文案:'该版本是在更晚的提交上发布的 —— 这个提交当时还没有 tag')。
回归用例两条:`TestDeriveVersionsDoesNotNumberCommitsThatPredateTheTag`、
`TestDeriveVersionsSideBranchWithoutTagStaysHistorical`(不含 tag 的侧分支同样不给号)。
- **部署脚本注入构建身份**:`scripts/deploy-api-web.sh` 原先不传 `-ldflags -X`,产物 `CommitSHA=unknown`
⇒ 页面只能显示"当前运行版本未知"(契约上诚实,但没用)。现在按 `scripts/version.py --identity`
(唯一来源,与 Makefile/CI 同规则)注入 VERSION/RELEASE_VERSION/BUILD_KIND/COMMITS_SINCE_RELEASE/
COMMIT_SHA/BUILD_TIME;取不到身份**拒绝继续**(宁可不上线,也不上线一个认不出自己的产物),
并在收尾核对注入的 SHA 真的进了产物。
- compose.yaml:把 VERSION_HISTORY_* 五项接进应用环境(api/worker 共享锚点),留空即未接线。
验证:真实仓库复验(114 条对账一致)⇒ 正式 1 / 开发 4 / 历史 109(原来错的 19 条现在都是
`release_tag_created_later`);p1 实例重新同步后当前快照分布一致,带号的记录只剩 1 正式 + 4 开发
(含真实存在的「同号不同 SHA」:950c1b4 与 3a1d32d 都是 0.1.1,各占一行);
make verify 15/15 success;前端 18 用例 + typecheck 通过。
2026-10-14 10:00:27 +08:00
姚章浩
0974d8dfa2
feat(version-history): 「版本记录」页 —— 追踪远端 main 全部提交(只读 + 管理员手动同步)
...
build / build (push) Successful in 57s
按已确认的契约实现(口径见 docs/VERSION_HISTORY.md):
- **版本号口径(只给有证据的)**:注释 tag + tag 指向该提交 + 该提交内 CHANGELOG 顶部三者一致 ⇒ 正式版本;
tag 之后的提交按 `<主>.<次>.<自该 tag 的提交数>` 显示开发号(与 scripts/version.py **同规则**);
证据不足 ⇒ 「历史提交 · 短SHA」+ 原因码,**不补造版本号**。完整 SHA 是唯一身份、版本号不是唯一键
(tag 之后两条并行分支各自的第 1 个提交都算 0.1.1 ⇒ 两条都保留,用短 SHA 区分)。
- **迁移 000046**:version_history_commits(提交记录,主键 repo_key+commit_sha)+ version_history_sync_runs
(同步状态)。快照 = 一次同步:抓完并与 X-Total-Count 对账通过才切换;失败不切换快照、不清空历史、
last_success_at 不动(页面显示"上次同步失败(原因)· 仍展示上次成功数据");「同步成功」≠「历史完整」。
- **只读客户端(匿名,不使用任何凭据)**:白名单 origin + 固定仓库、拒绝任意 URL 与跨主机重定向、
响应上限 5MB / 超时 15s、仅 5xx/429 退避重试(最多 3 次);逐页抓到短页为止;每次全量扫 tag
⇒ 旧提交后补的 tag 会更新既有记录(不新增第二行)。
- **接口**(三个都登记进 api/openapi.yaml):GET /api/v1/build-info(viewer)、
GET /api/v1/version-history(viewer;游标绑定快照,过期返回 409 cursor_expired;不返回伪造 total)、
POST /api/v1/version-history/sync(admin;并发同步复用同一作业,靠 IdempotencyKey)。
- **「当前运行」只认精确匹配**:产物注入的完整 SHA 命中某条记录才标该条;开发构建显式标注"开发构建";
没有可用标识 ⇒ "当前运行版本未知";**不拿最新 main / 最新 tag 顶替**,CI 成功不等于已部署。
- **前端**:一级菜单第 5 项(版本记录)+ 时间线页面(全部/正式/开发/历史四段筛选、正文展开、
加载更多、760px 单列无横向溢出、提交正文与变更记录按纯文本渲染);无打 tag / 发布 / 部署入口。
新增配置(留空即未接线,页面降级):VERSION_HISTORY_GITEA_BASE_URL / _REPO / _ALLOWED_ORIGINS /
_HTTP_TIMEOUT / _MAX_PAGES。
验证:`make verify` 15/15 success(tests-go 578 通过 / 0 失败;tests-web 209 通过 / 0 失败);
新增 Go 用例 30(版本口径与图遍历 12、客户端守卫 6、同步编排 5、接口 7);前端新增 18(组件 10 + 客户端 8);
真实仓库匿名快照拉取(`VERSION_HISTORY_LIVE_BASE_URL=… go test -run TestLiveSyncAgainstRealRepository`):
抓到 113 条与 X-Total-Count 一致、v0.1.0 是注释 tag 且目标与 `git rev-parse v0.1.0^{commit}` 一致、
开发号逐个等于 `git rev-list --count v0.1.0..<sha>`(正式 1 / 开发 22 / 历史 90)。
2026-10-13 19:40:19 +08:00
t
950c1b429b
feat(release): 抬档门禁(破坏性 ⇒ 0.x 抬次 / ≥1.0.0 抬主)+ 1.0.0 前置(稳定契约承诺)
...
原来预检只"打印破坏性条数",抬哪一档靠人自觉 ⇒ 改成硬门禁:
- **抬档门禁**:破坏性条数取自**目标提交里候选版本那一段**(发版事实看提交,不看工作区)。
`0.x` 阶段有破坏性 ⇒ 必须抬**次版本**(或主版本),只抬修订 ⇒ 阻断并报出该抬到哪一档;
`≥1.0.0` 之后有破坏性 ⇒ 必须抬**主版本**,抬次/修订都阻断。
另加"候选号必须大于上一个正式版本"(版本只增不减)。
- **1.0.0 前置**:上一版本是 `0.x` 时,候选 `1.0.0` = 对外承诺契约稳定 ⇒ 必须先有写实的
《稳定契约承诺》(覆盖 接口 / 数据模型 / 配置 / CLI 输出与退出码,不留 `待填`/`TODO`/`TBD`),
**且候选发布记录里链接它**,缺任一条 ⇒ 阻断;`1.x → 2.0.0` 不重复要求。
- 预检新增"版本走向"事实行(上一版本 → 候选 / 抬哪一档 / 破坏性 N 条 ⇒ 要求哪一档 ✅ ⛔ );
`docs/RELEASE_RUNBOOK.md` 新增"抬哪一档"对照表与《稳定契约承诺》段(故意留占位)。
验证:release_preflight --self-test 32 分支全过(含新增 9 条);反向夹具新增 §24 六条真实临时仓库用例
(0.x 只抬修订 ⇒ 阻断、0.x 抬次 ⇒ 放行、≥1.0.0 抬次 ⇒ 阻断、1.0.0 留占位 ⇒ 阻断、写实+链接 ⇒ 放行、
写实但没链接 ⇒ 仍阻断)⇒ 夹具 91/0;make verify 15/15。
于是 0.1.0 之后的第一个提交(本提交)开发号 = 0.1.1。
2026-10-13 17:13:44 +08:00
t
6de5d52363
feat(version): 开发号随提交自动递增 + 首个正式版本 0.1.0
...
build / build (push) Successful in 51s
按使用者要求定的版本规则(自动升的是**开发标识**,发布版本与 tag 仍由人批准):
- **开发号自动递增**:display_version(development) = `<正式版本的主.次>.<自 v<正式版本> 以来的提交数>`
(正式 0.1.0 之后第 1 个提交 0.1.1、第 12 个 0.1.12;恰在发布提交上 = 正式号)。
它**不写进 CHANGELOG**(发布记录只在发版时动),由 git 算出来 ⇒ 每个提交都有自己的号、数字只增。
- **浅克隆不猜号**:`--depth` 会把条数算少 ⇒ 浅仓库里拒绝算号,退回 `<版本>-dev+g<短SHA>`;
检出步骤在仓库有 tag 时 `--unshallow` 补全历史。
- 接线三处齐全:`version.py --identity` → 门禁 `-X …CommitsSinceRelease=<n>`;Makefile 的
`COMMITS_SINCE_RELEASE`;Dockerfile 同名 build-arg;Go 侧新增 `CommitsSinceRelease` 与
DisplayVersion() 新规则;契约测试**交叉核对**"注入的提交数 == git 算出的"(防注入错号)。
- **首个正式版本 0.1.0**:CHANGELOG 的 `[Unreleased]` 提成 `## [0.1.0] - 2026-10-13`;
头部补"版本怎么走"两行;RELEASE_RUNBOOK 补递增规则与"下一次发布把次版本加一"。
- 发版预检新增:打印**当前开发号**,并挡住"候选正式版本 <= 当前开发号"(会把开发号当正式号;建议次版本加一)。
验证:version.py --self-test(含 tag 后第 2 个提交 = 0.2.2、恰在 tag 上 = 0.2.0、无 tag 不猜号);
release_preflight --self-test(21 分支,含开发号两条);go test 本地三态 + 临时仓库 tag+2 提交实测
(Python 与 Go 都得出 0.2.2);反向夹具 85/0;make verify 15/15 success。
2026-10-13 16:45:56 +08:00
姚章浩
97f4b29661
fix(version): 就绪看目标提交的顶部版本 + 禁用自动发布路径
...
build / build (push) Successful in 52s
补两处残余(本轮不推、不打真实 tag、不部署;全部用临时仓库与 spy 验):
1) version_in_commit 必须比"目标提交 CHANGELOG 的**顶部**已发布版本",不是"历史里出现过":
- 新增 top_version_in_commit(),version_in_commit() 直接比它;预检输出把该提交的顶部版本打出来
(诊断可审计:例如"顶部是 `## [0.3.0]`,不是 0.2.0"),阻断信息里也带上真实顶部版本。
- 顺带兜住"最新在最上面 / 版本只增不减"的排序要求。
- 负例(自检 + 夹具各一遍):顶部 0.3.0、历史里 0.2.0 ⇒ 候选 0.2.0 拒绝;候选 0.3.0 通过。
2) 按只读约定**禁用 --execute 自动发布路径**:
- 删掉写路径(打 tag / 推 tag / 建 Release 的代码整段移除),`--execute` / `--approve-version` /
`--approve-sha` 只保留参数兼容(不报 unknown argument),传了就**明确拒绝**(rc=2)。
- 拒绝发生在收集事实之前 ⇒ **零写操作、零 API 调用**;自检用 spy 断言拒绝路径
"一个子进程都没跑、一个 API 都没发",并扫描生产路径不许出现直接执行 git 写命令的行。
- 输出里也不再推荐自动路径;RELEASE_RUNBOOK 第 4 步改为由人执行。
验证:release_preflight --self-test 绿(就绪 20 分支 + 顶部/历史两态 + --execute 拒绝零写 +
源码写路径扫描);反向夹具 84/0(含 --execute rc=2 且跑完仓里 tag 不变);make verify 15/15 success。
2026-10-13 15:48:50 +08:00
姚章浩
7a9d51cc70
fix(version): 正式构建核证据 + 预检分"建议/就绪" + CI 就绪按必需 context
...
按要求补三项(本轮不推、不打真实 tag、不部署;全部用临时仓库与假对象验):
1) 正式构建身份不能只认 --release:
- 新增 release_evidence():核①完整 40 位 SHA ②**指向该提交的注释 tag** v<版本号>
③该提交里 CHANGELOG 顶部版本一致。缺任一条 ⇒ rc=2,身份退回 development 并把
release_blocked 的原因写进 JSON(给机器读,也给人看)。
- version.py --self-test:六条负例(有版本无 tag / unknown SHA / 短 SHA / tag 指错提交 /
轻量 tag / 版本不一致)+ 证据齐正例,**tag 全部在 mktemp 临时仓库里创建**。
- Go 侧新增 TestReleaseBuildRequiresEvidence:release 构建必须 CommitSHA 完整且
v<ReleaseVersion> 是**注释 tag** 并指向该提交(独立于 Python 的二次复核)。
2) 发布预检区分"准备建议"与"发布就绪":
- 新增纯函数 readiness(facts) → (是否就绪, 阻塞项, 建议);输出分两段。
- **就绪要求候选版本已写进目标提交**(version_in_commit:查 git show <sha>:CHANGELOG.md,
工作区写了没提交不算);首发按**显式指定版本**处理,**不把"记录里有这个版本"当已发布**
(已发布的证据是 tag,不是记录条目)。
- 预检**不执行、也不在输出里推荐自动发布路径**(正式发版由人按 docs/RELEASE_RUNBOOK.md)。
3) CI 就绪判据:必需 context 集合来自 .gitea/required-contexts.txt(缺失/为空 ⇒ 没有策略 ⇒
阻断,不用观察到的状态顶替);**全部 success 才通过**,失败/缺项/pending/查询异常一律阻断。
Release 查询加 release_state():**只有 HTTP 404 才算不存在**,403/5xx/超时算查询异常 ⇒ 阻断。
验证:version.py --self-test 绿;release_preflight --self-test 绿(就绪判定 19 个分支 +
临时仓库验证 git show 语义 + 五个 HTTP 码语义);go test 三态(未注入 SKIP / 开发 PASS /
自称正式 RED);反向夹具 83/0(含临时仓库 go test 六例、预检"记录未进提交"两态);
make verify 15/15 success。
2026-10-13 15:26:49 +08:00
姚章浩
05144309c3
feat(version): 构建身份(release_version/build_kind/display_version),无基线也放行
...
解决"开发构建沿用已发布版本号,分不清开发版与正式版":
- 新增 `scripts/version.py --identity`:release_version(无基线即空)/ build_kind /
commit_sha(完整 40 位)/ display_version / built_at。显示规则:发布 `X.Y.Z`、
开发 `X.Y.Z-dev+g<短SHA>`、无基线 `dev+g<短SHA>`、什么都拿不到 `dev`。
- `build_kind=release` 只认**显式发布上下文**(`BUILD_KIND=release` / `--release`);
声明发布构建却没有已发布版本 ⇒ 拒绝(rc=2)。普通 main 构建永远是 development,
**不许只显示上一正式版本号、不许自称正式版**。
- Go 侧 `internal/version` 加 ReleaseVersion/BuildKind/DisplayVersion()/IsRelease()/Info(),
原 Number/CommitSHA/BuildTime 语义不变(向后兼容);契约测试改成:发布构建必须等于
CHANGELOG 顶部、开发构建必须是 dev 形状;`make verify` 的 contracts 项新增
**负例**「自称正式版 ⇒ 必须红」(只有正例的契约会被静默掏空)。
- 构建接线:Makefile/Dockerfile/ci_checks `--internal-build` 统一走 identity;
Makefile 的 `make version` 打印身份 JSON;Dockerfile 新增 RELEASE_VERSION/BUILD_KIND。
- `tests-go` 的"按设计跳过"声明补上 `TestDevBuildDoesNotClaimRelease`(它的强制点在 contracts)。
验证:go test 三态实测(未注入 SKIP、开发身份 PASS、自称正式版 FAIL);
--internal-build 产出并打印 dev+g<sha>;--internal-identity 契约项 rc=0;
make verify 15/15 success;反向夹具 80/0。
2026-10-13 14:59:36 +08:00
姚章浩
4f8ee0a8c5
feat(version): 变更记录改固定四类,破坏性变化带发布文档链接
...
按项目方给的规则重做 CHANGELOG 与检查器(本轮不推、不打 tag、不部署):
- 条目形状:四个自定义必填节(改了什么/兼容性与迁移影响/怎么验收/已知问题)
→ 固定四类 Added/Changed/Fixed/Security;分类可省略,**不许空栏目**;每个版本块
至少一个非空分类;`[Unreleased]` 段不得写日期。
- 破坏性变化:固定写在 Changed 里、以 `**破坏性**` 开头,**必须链接发布文档**;
仓内 `docs/….md` 链接**对账**(不存在 ⇒ 红)。写在别的分类里也判红。
- 结构判据补强:非法日期、重复版本、顺序必须严格递减、前导零;围栏代码块里的
`##`/`###` 不算标题;**块正文以任意二级标题为界**(「## 能力基线(未发布)」这类
不带方括号的标题原先不是边界,会把后文算成上一节的内容 ⇒ 空栏目被兜成"有内容",
夹具真的抓到了这个假绿)。
- tag 一致性:远端专属清单 `.ci-remote-tags` → `ls-remote` → 本地 refs 三级来源;
**没核对远端就不许说校验成功**(CI/预检用 `--require-remote`,拿不到清单即红),
非正式 tag(如本地 `x`)不当作发布版本、不判红,没有 tag 不判红。
- `0.1.0` 定为**未发布的能力基线**(远端无 tag、无 Release):改成不带方括号的
「## 能力基线(未发布)」,不补造 tag/发布日期/发布事实。
- 过程记录迁出:审查轮次与命令输出 → `docs/archive/CI_接入留档.md`(原文保留);
未合并事项 → `docs/BACKLOG.md`;补 `docs/RELEASE_RUNBOOK.md`(破坏性变化的链接目标,
同时修掉 CHANGELOG 里指向不存在文档的悬空引用)。
- 发版预检:无版本基线时把「首个正式版本号由人指定」列成阻塞项(不再要求先有基线才能跑);
打印破坏性变化条数;远端 tag 查不到时明说未核对并阻塞;不再硬读已删除的『已知问题』节。
- traps trap-5 口径修正:不是"不用 tag",而是"构建时不要用 git describe 取版本号"。
验证:check_changelog --self-test 全过;反仓夹具 80/0(含五条真改文件的结构负例、
六条只在临时仓库里造 tag 的场景);make verify 15/15 success。
2026-10-13 14:59:35 +08:00
姚章浩
0c0363512d
test(ci): 反向夹具补版本记录与发版预检(22/23 节,共 74 条)
...
新增:缺『已知问题』节/前导零版本号/注释 tag 与提交内版本不符/轻量 tag 逐条判红;无 tag 与『只有 Unreleased』不判红;预检在脏工作区不给可发结论、跑完不留 tag、输出里没有写操作。夹具总数 60 → 74。
2026-10-12 17:58:06 +08:00
姚章浩
40d84994cb
feat(ci): 版本记录检查(四节 + 正式 tag 一致性)+ 只读发版预检
...
check_changelog.py:正式版本条目必须四节齐备且非空(改了什么/兼容性与迁移影响/怎么验收/已知问题)、版本号三段无前导零、[Unreleased] 在最上面;新增 --tags 核对正式 tag(必须注释 tag)与它指到那个提交里的版本记录一致。只有 Unreleased 的新工程放行;没有正式 tag 不判红(开发版本不打 tag 是常态)。ci_checks.py:新增必需检查 version(门禁 15 项),策略版本 ci-checks/2。ci_checkout.sh:记录远端 tag 清单并取 tag 对象供核对。release_preflight.py:只读预检(版本建议/完整 SHA/CI 证据/CHANGELOG/远端 tag 与 Release/遗留项),写类 git 命令与 POST 在预检路径上被拒(--self-test 证明);批准后 --execute 才打注释 tag、推 tag、建 Release 并读回。CHANGELOG 0.1.0 补上原本缺的两节。
2026-10-12 17:57:43 +08:00
姚章浩
d167a2b937
fix(ci): 第一步改最小 bootstrap(空工作区可启动),新分支范围以默认分支为基准
...
空工作区启动:workflow 第一步不再调用仓内脚本(此时它还不存在,实测 rc=127),改成只用 runner 环境变量的最小 bootstrap:校验协议/凭据 → git init+remote add → 取事件完整 SHA → checkout → 断言 HEAD==事件 SHA;检出后才调用 ci_checkout.sh --post-checkout 做再断言与补取(默认分支引用、事件 before)。按任意 SHA fetch 在服务端默认不可用(服务器不允许请求未公开的对象),改成取分支历史 + --deepen 再核事件 SHA。新分支范围:after --not --remotes 会把刚 fetch 的同名远端分支(=after)当排除集、排除自身得到 0 条;改为以远端默认分支为基准 refs/remotes/origin/<default>..<after>,取不到基准就明确失败。新增 scripts/ci_chain_fixture.py:本地 HTTP git 服务 + 把 workflow 的 bootstrap run 块原样抽出来在空目录执行,正/负例 12 项断言。
2026-10-12 16:45:10 +08:00
姚章浩
164dc2f72c
test(ci): 反向夹具扩到 54 条,含预览发布与合并调用链负例
...
新增 14~19 节:假 bridge 数 publish 请求(坏报告 0 次/好报告 1 次)、--verify-only 单跑核对、检出协议保留、合并调用链(假 Gitea 跑真实 merge_pr,13 分支)、生成器落齐新脚本与空策略文件。首轮 1 处失败暴露真实缺陷:无远端引用时 --not --remotes 退化成整部历史(把 Initial commit 算进来)⇒ 改为只核 HEAD 并写明。
2026-10-12 11:15:18 +08:00
姚章浩
88da069e1f
ci(checks): 必需与可选测试按声明区分,未声明就跳过的用例判红
...
test-scope.json 提供声明:package/test 至少给一个、必须给 reason 与 owner、package 级要写 expected_skips(跳过数涨了要重新声明)、review_by 过期即失效。文件缺失或解析不了 = 声明不出来 ⇒ 不许默认放行。
2026-10-12 11:15:18 +08:00
姚章浩
1f74794696
test(ci): 加反向验收夹具(21 条,逐条证明门禁真的会红)
...
一个"永远返回 0 的校验器"比没有校验更糟 —— 它给人一种已经检查过的错觉。
所以每条判据都要有反例,而且反例必须在**隔离副本**里做,不往日常 main 推故意失败的提交。
scripts/ci_negative_fixtures.sh 的两道保险:必须显式设 CI_NEGATIVE_FIXTURE=1,
且工作区有 origin 远端时拒绝运行。逐项做完即恢复,并断言恢复后会重新变绿。
覆盖:格式(Go / 前端各一条)、前端用例失败、变更记录缺日期、版本用例被 SKIP(变异测试)、
缺锁文件、例外表缺失、合成密钥、事件 SHA 错配、必需项未运行不得报全绿(全量跑一次看 not_run)、
通知不可达不改质量结论、汇总报告缺失必须非 0。
实测(在 /home/zheromiku/.hermes/cache/scratch/kg-neg 的副本里,副本无 origin):
21 条全部符合预期、0 失败,夹具退出码 0。
顺带修掉一个真问题:夹具第一稿把"注入路径写错"改在 build-go 那一处(同名但无关),
于是看起来"契约没红"—— 其实是夹具没生效。现在断言注入点在文件里唯一命中。
2026-10-12 00:11:10 +08:00
姚章浩
a3a1319a11
feat(ci): 唯一权威门禁 make verify(14 项必需检查,汇总不认"未运行")
...
一次性把本地与 CI 的门禁收成一条命令:make verify = python3 scripts/ci_checks.py。
两边跑的是同一份逻辑,不再各写一套步骤(两边各写一套的结局是迟早只改一边)。
14 项必需检查:identity(检出 == 事件完整 SHA,只在 CI 必需)、commits、deps-install、
format、static-go、static-web、build-go、build-web、tests-go、tests-web、contracts、
security-secrets、security-deps、manifests-locked。make list 打印清单。
三条判据写死在代码里:
- 失败即停,但剩下的记 not_run 并如实写进报告;aggregate() 只认 success,
未运行/被跳过都不算通过(不适用要在清单里声明 required_in,不许运行时悄悄 skip)。
- 工具缺失、依赖没按锁装、输出解析不了 ⇒ 失败,不当成"检查通过"。
- 测试必须真跑:go test 加 -count=1 并盘点通过/跳过数;前端读 vitest 的 JSON 报告,
空套件不算通过。跳过原因会打进报告(当前 72 个集成用例因缺 TEST_DATABASE_URL 跳过)。
新增四个自带反向自检的脚本(--self-test 故意弄坏必须红):
scripts/check_format.py(Go 判输出而非退出码 + 前端只能减的基线)、
scripts/check_secrets.py(只打印位置与类别,绝不打印值)、
scripts/check_deps_vuln.py(govulncheck 锁 v1.1.4 + npm audit 分级 + 到期例外)、
scripts/ci_checks.py(门禁 + 汇总)。
验证(本机实跑,全部为真实退出码):
make verify → 总体 success(档案 local),identity=skipped 已在清单里声明为 CI 必需;
tests-go 执行 31 个包、通过 549 / 失败 0 / 跳过 72;tests-web 106 个套件、191 个用例全过;
security-deps:govulncheck v1.1.4 0 命中、npm audit 生产与全量均 0。
2026-10-12 00:10:51 +08:00
姚章浩
5f3a71cade
feat(version): 版本号唯一来源 = CHANGELOG.md,并要求写清改了什么与怎么验收
...
build / build (push) Successful in 26s
背景:本仓此前没有任何版本/构建元信息 —— 没有 ldflags 注入、也没有产物可读的
版本号,"线上跑的是哪个提交"答不出来;"这次改了什么、怎么验收"也没有载体。
落地:
- CHANGELOG.md:**版本号的唯一来源**(正本 §6.1:版本号只准有一处)。刻意不放
VERSION 文件、也不靠 git tag —— CI 是 `git fetch --depth 50`,取不到 tag,
`git describe` 会静默退化。每个已发布版本必须有两节:### 改了什么 与 ### 怎么验收。
- scripts/version.py:从 CHANGELOG 顶部解析版本号;解析不到即非 0 退出(构建据此失败)。
- scripts/check_changelog.py:结构自检(两节齐、带日期、版本号不重复且严格递减、
小节不为空),带 --self-test 反向自检。
- internal/version:三个构建期注入变量 + 契约测试(注入的号必须等于 CHANGELOG 顶部)。
- Makefile:build 带 -ldflags 注入;verify 增加 changelog-check 与 version-contract。
- Dockerfile:ARG VERSION/COMMIT_SHA/BUILD_TIME + ldflags(未透传时落 dev —— 故意显眼,
好过看起来正常的假版本号)。
- CI:新增「版本与变更记录契约」步,并显式断言契约测试真的 PASS(被跳过的断言等于没有断言)。
- docs/提交规范.md(ci_gate.py 的报错文案一直在指它)、docs/agents/traps.md(7 条实测陷阱)。
验证(都真跑过):
- make version → 0.1.0;make changelog-check → 合规
- make version-contract → PASS;注入 9.9.9(CHANGELOG 里没有)→ FAIL(反向自检)
- 删掉真实 CHANGELOG 的「怎么验收」小节 → 报红(对真实文件做,不只是夹具)
- go build ./... 与 go vet ./internal/version/... 通过
未做(会与在审 PR #2 冲突,待其合并后在 main 上补):
- GET /version 端点(internal/httpapi/server.go)与 cmd/api 的 -version 开关
- compose.yaml 透传 build args(当前由 compose 构建的镜像会报 dev)
⚠️ 未跑 make verify 全量(含 go test ./... 与前端),CI 是本次的测试证据。
2026-10-10 18:13:24 +08:00
姚章浩
fc9dd4c328
chore(gitignore): 补根级 node_modules 规则,撤出已入库的构建产物
...
根级 node_modules 没有被任何规则覆盖(只写了带斜杠的 /web/node_modules/,
只管 web 下),于是 node_modules/.vite/vitest/.../results.json 随 a1b46d2
进了库。另有 8 个 scripts/__pycache__/*.pyc 是历史遗留 —— 规则在,但
.gitignore 对已入库文件无效(正本 §1.4-1)。两者一并撤出,保留磁盘文件。
验证:git ls-files 里不再有 node_modules / __pycache__ / .pyc。
2026-10-10 17:42:10 +08:00
姚章浩
fa244c447c
fix(deploy): 重建 api 会静默关掉生产门禁 —— 加前置保护 + 门禁状态回显
...
界面验收第三遍(PDF)踩到:授权发布被回绝 "production release requests are disabled"。
根因:scripts/deploy-api-web.sh 的 COMPOSE_FILES 默认只叠 compose.yaml,重建 api 时没叠
compose.release-gates.yaml → PRODUCTION_RELEASE_REQUESTS_ENABLED / PRODUCTION_BACKUP_RESTORE_VERIFIED
变 false,生产授权接口被静默关闭(页面只在点击那一刻提示一句,极易误判成功能坏了)。
- 前置保护:正在运行的 api 已开闸、而本次 COMPOSE_FILES 不含门禁文件时直接拒绝
(REFUSED_WOULD_CLOSE_PRODUCTION_GATES,并打印保留门禁的正确命令;确要关闸用 ALLOW_GATE_OFF=1)
- 结尾新增 === 7b 打印重建后的两道门禁状态,避免"以为开着"
- 已用同一段判定逻辑覆盖三种情形(已开闸+不叠门禁→拒绝 / 已开闸+叠门禁→放行 / 显式 ALLOW_GATE_OFF→放行)
验收第三遍同时确认:PDF→MinerU 解析链路是通的(入库 completed、验收"内容处理:通过"),
发布首轮 completed/verified(in_scope=8 foreign=8 降级保留正常),回滚 rolled_back、
目标库 enable_status=disabled。证据补进 docs/USER_FLOWS_ACCEPTANCE.md。
2026-10-10 11:10:51 +08:00
姚章浩
3f603aa293
feat(backup): 系统设置里的存储与备份——定时时间与储存天数可配、下发到目标主机 cron
...
需求:整库逻辑备份做定时,但要在「系统设置 → 存储与备份」里设置定时时间与储存天数。
后端
- migration 000045 backup_schedule(单行:enabled/schedule_time/retention_days/retention_months/
last_applied_*),store + 领域校验(HH:MM、1~365 天、0~24 月,非法值 400 且不落库不下发)
- internal/backuphost:通过 ssh 在目标主机上读回/下发计划并立即备份(未接线时 Available=false,
页面降级提示;主机侧错误带输出回传)。最近一次只认有 MANIFEST.json 的完整目录,跳 *.incomplete
- 接口 GET/PUT /api/v1/backup-schedule、GET …/host、POST …/apply、POST …/run;
写操作经 classifyRole 限管理员;run 为异步 202 + 在途单飞(http 写超时 30s,备份约 35s)
- openapi.yaml 补齐 5 条路由(契约测试要求每条都写 400/500)
主机侧
- scripts/install_backup_schedule.sh(幂等):写 /etc/knowledge-governance/backup-schedule.env 与
/etc/cron.d/kb-logical-backup,参数校验,--show/--disable,最后一行 SCHEDULE_APPLIED 供平台核对
- logical-backup.sh 从该文件读保留策略;失败或被中断把目录改名 *.incomplete,不留'像成功'的半成品
- install --show 只回显计划行(原先把 SHELL=/PATH= 当计划显示)
前端
- web/src/backupSchedule.ts(严格解析:缺计划字段即报错,不把默认值伪装成设置)
- BackupScheduleSection:容器 + 纯视图(管理员表单/只读、已一致/待下发、在途轮询)
运行前提(已落实):运行镜像加 openssh-client,且容器 uid 1000 在 /etc/passwd 有名字
(否则 ssh 报 No user exists for uid 1000);凭据只读挂载 secrets/backup-host(git 忽略)
实测:主机 cron 每天 03:30 / 保留 7 天 / 每月 3 份;立即备份一次 34 秒 / 742.7 MiB / 失败 0;
Go 与前端测试全绿(170 vitest)。
2026-10-09 13:50:23 +08:00
姚章浩
4455d02262
test(backup): 留档回填演练脚本 + 实测通过
...
scripts/restore_drill.py:把留档 PG 行按原文 \copy FROM 回填进目标库,核对每张表行数与清单、
抽样页面正文/标题逐字段比对,再用临时跟踪表 _drill_restored 精确删除并断言残留为 0。
实测(本地测试库 weknora_full,留档 20261009T041913Z-0b217e8a):13,539 行全部回填成功、
8 张表行数与清单逐一致、冲突 0、抽样页正文一致(388 字符)、残留 0。
同时记录一个格式事实:留档 JSONL 是 psql \copy text 格式(转义),按行 json.loads 会失败
(knowledges 3830/3830、wiki_pages 218/1982),回填/程序化读取须先按 text 格式反转义。
2026-10-09 13:07:41 +08:00
姚章浩
19e0596e1d
fix(backup): 整库逻辑备份按库容错,默认跳过 RDS 管理库 rdsadmin
...
单库 pg_dump 失败(权限/编码/管理库)不再中止整次备份:清掉半个目录、记进 MANIFEST.failed_databases、
轮转照常执行、最后按失败情况给退出码。云主机实跑:33 秒 / 742.6 MiB / 8 库 / 失败 0。
2026-10-09 12:25:12 +08:00
姚章浩
a1be19bb0f
docs(backup): 补实测耗时(2m52s 快路径 / 19m40s 含 ES 回传)与 ES 导出哈希非确定性判据
2026-10-09 12:22:55 +08:00
姚章浩
b32755b35f
feat(backup): 备份目录契约 + 导入前留档覆盖 ES 层
...
- scripts/backup_layout.sh:定义 $KB_BACKUP_ROOT/{target-kb-snapshots,rds-logical,es-snapshots,logs}
与 /etc/knowledge-governance/backup.env,任意主机跑同一份(测试云主机 → 未来生产云主机)
- scripts/es_kb_export.py:只读 scroll 导出目标库 ES 文档(gzip JSONL,_id/_source,可按 _id 重建)
- scripts/target_kb_snapshot.sh:留档改为 PG 行 + ES 文档两层;PULL_ES 默认 0(692 MB / 家用上行 19m40s,
留在目标主机,清单记 location=cloud:<host>:<path>);挂载源与落点统一走 KB_BACKUP_ROOT
- scripts/logical-backup.sh:自管整库逻辑备份(pg_dump -Fd -j4 -Z6,按天轮转)
- internal/releasesnapshot:门禁要求 PG 与 ES 两层齐备(ES 计数 >0);云侧留档核声明与哈希、
声明本地却缺失则 fail-closed;新增 3 条用例(共 10)
- cmd/guardcheck:用真实产物复核门禁判断
- docs:PRODUCTION_BACKUP_RECOVERY.md 重写(契约/RDS 控制台/恢复路径/边界);P6 同步留档语义
2026-10-09 12:19:15 +08:00
姚章浩
9155bd3170
fix(artifact): 类型误标回退 + 服务端阻断判可重试 + 空白单元格重读
...
build / build (push) Successful in 23s
压测/取件实测暴露的三类问题(均在真实 worker 容器里复现并已验证修复):
- 名为 .axls 实为 docx 的节点(清单扩展名与真实类型不符):dws 明确回报真实类型
→ 回退 drive download 取原始字节(实测该节点 243,949B docx 可正常取到),
不再直接失败
- server model operate block 判可重试:同一节点 5 连测 3/5 成功(另一支 timeout),
原判终态会把可成功节点永久失败
- axls 大表偶发返回全空单元格(12 次取件出现 1 次):全空时重读一次再上报,
避免被平台空壳判定当「空文档」终态
新增 3 个回归测试(18 tests / 19 断言全绿)
2026-10-09 12:15:22 +08:00
姚章浩
6496ee3571
fix(artifact): 在线文档大小预检只对普通文件生效,压测暴露的取件器错误映射补齐
...
build / build (push) Successful in 25s
- 大小预检此前对 adoc/axls 也用目录大小,而在线文档目录大小是容器大小
(实测有 1.40GB 的 adoc,导出 docx 仅 49.5MB)→ 3 个可导出文档被误判 over_limit
- axls 转换器的 dws 失败改为抛 DwsCallError,取件器映射成平台错误码
(此前进程无结果退出,平台只看到 artifact_fetch_no_output)
- export_failed 改判 retryable:8 路混合时巨型文档服务端导出偶发失败,1/2/4 路与串行必成
- 新增 2 个回归测试 + dws_error_map 1 个断言;文档补并发实测表与回归清单
2026-10-09 11:51:26 +08:00
姚章浩
1fa35e9870
feat(release): 发布前快照硬门禁 + 云内快照 + ES/OSS 保护
...
按生产级备份组合落地:
- internal/releasesnapshot:新增门禁包,要求 root 下存在覆盖目标库、未过期
(默认 24h)、含 wiki_pages.jsonl 与 sha256 的快照;本地文件再校验哈希。
7 条单测(过期/换库/篡改/缺页/未接线/半成品)。
- 发布器:写目标库之前(Publish/Delete/Rollback 三条路)先过门禁;映射目标
(源库→目标库,共享真实库)或 policy=always 时必须有快照;门禁未接线
同样拒绝(fail-closed)。新错误码 release_target_snapshot_missing(不可重试)
+ 页面中文提示。6 条发布器单测。
- worker 配置新增 target_snapshot{root,max_age_hours,policy},校验 policy/
max_age/绝对路径;compose.release-sim.yaml 只读挂载快照目录。
- scripts/target_kb_snapshot.sh:默认云内执行(与 DB 同 VPC,凭据取自 WeKnora
容器)+ 默认不导 chunks(44s 出 8 张表);修复 \copy 不能跨行/不能带 WITH、
ES 计数改用 knowledge_base_id.keyword。
- ES:注册 kb_backup 仓库(path.repo),首个快照 36s/4.7GB/0 分片失败,
SLM kb-hourly(3d)+kb-daily(7d) 已建并执行成功。
- OSS:目标桶开启版本控制(此前未开启)。
- 文档 P6 增补硬门禁与云内执行;真实快照目录上门禁实测:两个绑定库通过、
未覆盖库拒绝、未接线拒绝。
2026-10-09 10:59:55 +08:00
姚章浩
f4e1ecc5f1
feat(artifact): 钉钉取件优先走 dws(DingTalk Workspace CLI),老路径保留为回退
...
- artifact_fetch.py: 新增 dingtalk dws 取件器(adoc→doc export、axls→sheet 转换、其余→drive 直下),
ARTIFACT_DINGTALK_FETCHER=dws|auto|legacy 三级开关,auto 仅对可重试错误回落老路径;
平台侧判定(pick_suffix/SLIDES/magic/大小上限/sha256/spool/空壳检查)全部保留;
结果 JSON 字段不变(Go runner 用 DisallowUnknownFields 解析 stdout),取件器来源只写 stderr。
- axls_to_xlsx.py: 纯标准库 axls→xlsx(文本写入 sharedStrings 以通过空壳判定)。
- dws_error_map.py: dws 错误→平台 {ok,code,retryable,missing};未分类错误默认不重试。
- Dockerfile: alpine 增加 gcompat/libc6-compat;dws v1.0.63 固定版本+sha256 校验;构建期 dws version 自检。
- compose.yaml: worker 增加 dws 环境与凭据挂载(DWS_HOME 方案,XDG_DATA_HOME 不被 dws 识别)。
- 测试: artifact_fetch_test 新增 11 项(路由/回落语义/契约字段锁定);dws_error_map 17 断言。
- 实测: 真容器(--read-only --tmpfs --user 1000:1000) 端到端 adoc/axls/pdf 3/3 成功,docx sha 与平台基准一致。
2026-10-09 10:57:11 +08:00
姚章浩
8bb5cd9a62
feat(ops): 目标知识库只读行级快照工具 + 发布前备份约定
...
发布链不做发布前备份(Prepare 只归档本包、previousRevision 首次为 absent),
门禁 PRODUCTION_BACKUP_RESTORE_VERIFIED 只是人工开关 —— 目标库覆盖前没有现场。
- scripts/target_kb_snapshot.sh:KB 维度只读导出(SELECT + \copy),输出
JSONL + MANIFEST.json(行数/字节/sha256/覆盖范围/ES 文档数)+ export.sql
+ README.md(恢复步骤)。支持指定或读取已绑定目标库,INCLUDE_CHUNKS=0 出小快照。
- 文档 P6 新增「发布前备份」:覆盖范围、边界(不含 ES/OSS 本体,整库级走 RDS
PITR)、以及共享真实库发布/撤权/回滚前必跑快照并记入发布单的约定。
2026-10-09 10:05:23 +08:00
姚章浩
6440e3f679
feat(release): 发布目标知识库可选(源库 → 目标库映射)
...
- 迁移 000044:production_kb_bindings + release_item_executions.target_knowledge_base_id
- 系统设置新增「生产知识库绑定(对内/对外)」:从生产 WeKnora 实际列表选库,
保存时真校验(不存在拒收;生产端不可达只提示并如实回报),仅管理员可改
- 执行计划按绑定解析目标库并冻结进 release_item_executions;空目标直接拒收;
已发布过的条目保留原目标(重试/回滚回到同一处)
- DocumentBundle.RemapKnowledgeBase:knowledges/chunks/knowledge_tags/wiki_pages/
wiki_folders 的 knowledge_base_id 与 ES 文档字段改写为目标库,重算表/文档摘要并重新封装
- 契约比对改为「声明的映射对」(KnowledgeBaseMapping),其余字段继续 fail-closed
- 契约检查器 / 目标身份 / 验证器全部走映射后的目标库;对象通道无需改写
- Compose 拆分:数据面接线 compose.release-sim.yaml + 两道门禁 compose.release-gates.yaml
- 测试:RemapKnowledgeBase 全通道/无操作/fail-closed、映射契约、绑定接口、RBAC、真实 PG 集成
2026-10-08 15:41:13 +08:00
姚章浩
e1c5f6895a
chore(deploy): 部署与备份脚本、示例配置与忽略规则
...
build / build (push) Failing after 21s
- 新增 scripts/deploy-api-web.sh(API/Web 构建发布)与 scripts/wiki_cleanup_backup.sh(wiki 清理前备份)
- 更新 .env.example、compose.yaml 与 README 说明;忽略 /spool/ingestion 运行期产物
2026-09-30 10:54:07 +08:00
姚章浩
e53b98f37d
fix(alist): 网关鉴权头改为 X-Gateway-Token
...
- 依据 2026-09-28 实测:旧头对新网关 4/4 源返回 401,X-Gateway-Token 返回 200
- 取件通道 scripts/artifact_fetch.py 两处头名同步;镜像内生效需重建 worker
- 修正过期结论:下载直链 /p/<path>?sign=… 为签名鉴权,请求头不影响(无头亦 206)
- 同步文档与回归断言表述(重定向隔离测试仍 PASS)
2026-09-30 10:54:07 +08:00
姚章浩
ea6e56b304
feat(release): 文档发布会带上知识库 wiki 快照并支持撤权剥离
...
发布通道原来只有三条:PostgreSQL 关系行、Elasticsearch 分片、OSS 对象。
WeKnora 的 wiki 图谱是 wiki_pages 一张纯 PG 表(in_links/out_links 存 slug),
所以它不在任何通道里——撤权后生产 wiki 会一直留着已撤权文档的页面与引用。
本次新增第 5 条通道:库级 wiki 快照,挂在现有文档发布包里,检索与 wiki
同一个 release 单元(同一次执行、同一次校验、同一次回滚):
- 导出:ExportWiki 按 knowledge_base_id 全量读 wiki_pages/wiki_folders;
收敛门禁(库内无解析中/合成中文档)不满足直接失败并给中文原因;
未启用 wiki 且无 wiki 行的库不参与同步(生产 wiki 保持原样)。
- 撤权:item.Action == "delete" 时在导出端剥离该文档的 wiki 贡献
(source_refs/chunk_refs 去条目、无引用页面整页不带、摘死链、清 parent_slug、
无页面引用的空文件夹及其祖先一并去掉),使“源端仍在只停止发布”和
“源端删除”两种情况收敛到同一结果。
- 导入:库级 replace-by-kb(同一事务内 DELETE by kb 后灌入快照,文件夹先于页面),
新增 ReplaceWiki 供撤权路径使用(文档行只 disable,wiki 整体替换)。
- 校验:VerifyWiki 用生产 WeKnora wiki API 比对页面 slug 集合,撤权时额外断言
生产 wiki 不再引用被撤权文档。
驱动与验收:新增 cmd/release-channel-e2e(复用发布 worker 真实代码路径,
支持 publish/withdraw/rollback),scripts/wiki_e2e_test5.sh、
scripts/wiki_e2e_newkb.sh 两套端到端脚本;证据见
docs/WIKI_CO_TRANSPORT_ACCEPTANCE.md。
2026-09-30 10:54:07 +08:00
姚章浩
822896ea6c
fix(fetch): 钉钉空文档显式报 empty_document
...
DingTalk exports empty online documents to valid but contentless docx/xlsx;
WeKnora then rejects them with a generic remote_parse_failed. Detect blank
exports (no text, no embedded media) at fetch time and report the cause in
Chinese, and add the matching dead-letter hint.
2026-09-30 10:54:07 +08:00
姚章浩
257be88ecf
fix(fetch): UTF-8 sniff boundary must not reject valid documents
...
validate_magic read exactly 4096 bytes and strict-decoded them; a multibyte
character straddling the boundary made perfectly valid .md/.txt files fail
with invalid_format. Only tolerate 'unexpected end of data' when the file
actually continues past the sniff window; genuinely corrupt bytes still fail.
2026-09-30 10:54:07 +08:00