Commit Graph

23 Commits

Author SHA1 Message Date
姚章浩 acf053faf3 feat(release): 建批时冻结 + 验收通过自动批准 —— 把"每份文档 8 步"砍掉两步并去掉两次卡死
背景:发布管理把"逐条核验"的仪式强加给每一份文档。实测一份文档要 9~11 次交互,
且有两处顺序陷阱(冻结后批准 → 建批 422;生成即固化 → 批准被永久钉住)。

P0-1 建批时冻结:
- CreateReleaseBatch 在未指定版本时,自己把当前实时计划冻结成一份**已确认**版本并绑定
  (同一事务;同摘要复用最近版本,planned 或 confirmed 都可复用,因此同一份计划不堆版本)。
- 语义不变:批一旦建立,清单/摘要/验收轮次/目标库绑定全部不可变、回滚按它走;变的只是
  "谁在什么时候按固化键"——从操作员的手动两步变成建批这一下。
- 效果:去掉「生成新版本」「确认冻结」两步;消灭"批准晚于冻结 → 422"这类顺序陷阱;
  前端不再传版本 id,也不再有"所选知识不在冻结清单里"的阻断。
- 计划→清单项的映射抽成 ChangeSetItemsFromPlan 供两处共用,否则两处算出的摘要不同,
  "同一计划复用同一版本"永远不命中。

P0-2 验收通过自动批准:
- 验收三项结论达发布门槛(knowledge_exists/chunks_present=passed,retrieval_hit 与门槛同
  口径)时,由 worker 写入 reviewer_ref='system:auto' 的批准记录,替代"每份文档点一次批准"。
- 策略 TEST_AUTO_REVIEW_POLICY = off | internal(默认,仅对内)| all;值不认识拒绝启动。
  只影响审核这一步:生产授权、双闸与发布前快照门禁不受影响。界面上只读展示当前策略。
- 安全边界(写死在审核 SQL 里):系统只允许写"第一条"审核——人工一旦表过态,机器不得用一条
  更晚的 approved 把人工结论翻回去;人工随时可再审。集成测试去掉该约束即 FAIL(已实测)。
- 自动批准自身的任何失败都不影响验收结论,只上报。

顺带(P1-1 前半,已被 P0-1 取代的部分已删除):
- 写操作失败把服务端 {"error": ...} 的原因带出来,并对已知原因给出下一步(不再只给 HTTP 状态码)。
- 当前的"不在已冻结清单里"标注与就地修复按钮随 P0-1 一起移除(该失败模式已不存在)。

测试:bind_time_freeze_integration_test.go(绑定已确认版本/同计划复用/冻结后不可变/
动作不符仍 NotEligible)、auto_review_integration_test.go(系统只写第一条、人工可再审)、
validate_test.go(对内自动/对外不自动/策略关闭/验收未过不批/自身失败不影响验收)、
auto_review_test.go、config_test 及前端能力解析测试。
实测耗时:本机 Go 全量 26 包通过、前端 178 条通过。
2026-10-10 15:05:20 +08:00
姚章浩 a1b46d2b7c feat(monitor): 需下架车道可逐条移除测试库知识 —— 补齐最后一个没有页面入口的流程
背景:来源侧被判「禁止导入」后,已建好的测试库知识不会自动消失;此前只有变更集
删除路径会入队 test.remove,页面既列不出条目也没有按钮,运维无法闭环(车道只有按
库计数)。

新增:
- GET /api/v1/test-publications?removal=required:条目级列表,判据与车道计数同源
  (superseded_at IS NULL / 有 knowledge_id / completed / retrieval_state NOT IN
  (disabling,disabled) / policy_state='blocked'),过滤是叠加而不是替换。
- POST /api/v1/test-publications/{id}/removal:engineer 级写路由,要求 Idempotency-Key,
  按条目幂等(operator-remove:<id>);置 disabling 并交既有 test.remove 工作流去删除
  远端知识并读回校验。错误映射 404/409(已移除)/409(不可移除)/501(未接线)。
- 界面:需下架车道逐条显示条目 + 「下架(从测试库移除)」,页内二次确认,成功提示,
  完成后计数归零。

修掉过程中踩到的两个真问题:
1. 二次确认的确认态原先经容器 props 传给视图,一次容器改动把三个 props 丢了 ——
   视图永远渲染原按钮、确认步骤静默消失,而所有静态单元测试仍然通过。改为把确认态
   放在视图内部;并把「取消」放在原按钮位置(真实指针连点两下只会落到取消)。
2. jobs 上有部分唯一索引 UNIQUE (business_change_set_id, business_version_id,
   business_target_kb_id, business_action) WHERE business_change_set_id IS NOT NULL。
   本路径给这三列写空串(非 NULL),('','','','remove') 对所有条目是同一个键 ——
   第一条之后每次 INSERT 都撞唯一索引、被 ON CONFLICT DO NOTHING 静默吞掉,留下
   「状态已 disabling、没有任务」的永久卡死条目,而车道恰好隐藏 disabling,运维也
   看不到。改为写 NULL(语义:无变更集身份)+ 冲突目标收敛到 idempotency_key +
   校验 RowsAffected()(0 行回查既有任务;查不到就报错,不提交无任务的 disabling)。
   卡死状态在重试时可自愈。

测试:internal/platform/postgres/test_publication_removal_integration_test.go(两条发布
各自入队、business_* 为 NULL、卡死自愈;把修复退回旧写法该测试即 FAIL,已实测)、
internal/httpapi/test_publication_removal_test.go(权限/幂等键/错误映射/未接线 501/列表
过滤)、前端 testPublications.test.ts + WeKnoraMonitor.test.tsx。

验收(纯界面,两条都走完):解析监控 → 需下架(2)→ 逐条「下架」+ 二次确认 →
retrieval_state=disabled(PDF 03:57:03Z、md 04:08:55Z),jobs completed(attempt 2,
首次 test_remove_pending=远端仍在处理,重试通过;工作流以 GetKnowledge 读回 404 判
成功)→ 车道需下架(0)。核验「确认前不执行」:首次点击后仍 unpublished、移除任务数仍 1。
2026-10-10 12:10:46 +08:00
姚章浩 d0a45d563f fix(monitor): 需下架车道恒为 0(判据用了没人写的状态)+ 隔离会改线上设置的集成测试
界面验收第三遍(禁止导入 → 需下架)暴露两处真问题:

1) 需下架车道死掉:判据 `retrieval_state='enabled'`,而 retrieval_state 在这套状态机里
   描述「是否发布到生产」(unpublished/disabling/disabled/failed),全仓没有任何代码写过
   'enabled',线上 5/5 发布单元都是 unpublished → 车道恒 0,界面「仍在知识库里的文件会
   出现在这里」的承诺不成立。改为与设计注释一致的口径:仍在测试库存活(completed + 有
   knowledge_id + 未 disabling/disabled)+ 来源项 policy_state='blocked'。
   线上对照:旧判据 0 → 新判据 1,命中被禁止导入的 DH-AG95夹爪适配-指令协议.md;
   修后页面显示「需下架(1)· 对内知识库 · 1 份仍在检索中」。新增集成测试覆盖
   存活/未禁止/移除中/已移除/未上传五种情形(真实 Postgres + 独立 schema)。

2) 集成测试改到线上设置(直接踩到并已恢复):ui_settings_integration_test.go 只判断
   TEST_DATABASE_URL 是否存在、无 schema 隔离,而它「创建候选并激活」——跑在共享库上会把
   真实 active 设置挤成 retired。已先备份(backups/ui-settings/…pre-restore.jsonl),再用
   单事务(先 retire 再 activate,uq_ui_setting_active 只允许一条 active)恢复
   uis-d40ea1fc…(15/30/1800/7200 + https://weknora-test.dobot.cn/),并复核线上值。
   根因修复:给该用例加 per-test schema 隔离,复跑后线上设置不再被改动。

同时把两处与事实不符的文案改实话(WeKnoraMonitor.tsx 需下架说明、SourceCatalogBrowser.tsx
禁止导入说明):移除走 test.remove 工作流(删除测试库知识条目,可再入库),入口当前由
变更集删除路径触发,页面尚无独立按钮——条目级列表与下架按钮列为待排期。
2026-10-10 10:55:03 +08:00
姚章浩 e95a50ca3a fix(backup): 保存后即时反映主机状态;最近一次备份不再重复同一时间戳
- 后端 handleSaveBackupSchedule 改为先下发、再组装投影:此前页面保存成功那几行仍是
  下发前的主机计划,看起来像没生效(需等下一次刷新)
- 前端 runMoment:清单里的 created_at 就是目录戳,同值不再重复显示(原为
  '20261009T060736Z · 20261009T060736Z')
- 前端补 runMoment 用例(171 个 vitest 全绿)

面板实测:保存后立即显示 每天 03:30 / 保留 7 天 / 已一致,cron 条目 30 03 * * *
2026-10-09 14:12:10 +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
姚章浩 86732ca987 fix(api): 执行明细补齐 action 与目标知识库字段
逐项明细 DTO 漏了 action(前端 parseReleaseItemExecution 必需),点
「查看结果」整个明细区报 invalid release item execution。同时把遷移 000044
的 target_knowledge_base_id 读出来透出,页面逐项结果显示「目标库」,
让'目标知识库可选'在界面上可核对。测试:Go 契约用例 + 前端解析回归用例。
2026-10-08 17:26:26 +08:00
姚章浩 280a81c855 fix(api): 执行列表返回批次进度字段,修复页面执行列表解析失败
/safeReleaseExecution 没带 batch_size/current_batch/total_batches,而前端
parseReleaseExecution 要求这三个计数,任何一条执行记录都会让整个列表抛
'invalid release execution',页面因此看不到执行历史与回滚/重排按钮,门禁
状态也一并回落到'已锁定'。补上这三个非敏感进度字段。
2026-10-08 17:15:32 +08:00
姚章浩 2de7a43286 feat(release): 生产授权支持页面通道(管理员会话 + 固定确认语句)
页面原本没有任何生产入口,授权/重排/回滚只能带 Bearer 调接口,实际还要 admin 会话。
现在三个端点支持两条通道,门禁语义不变:

- 运维通道:Authorization: Bearer <token>,审计记 PRODUCTION_RELEASE_AUTHORIZER_REF
- 页面通道:已认证 admin 会话 + 固定确认语句,审计记该管理员用户名
- 请求门禁 / 备份恢复门禁 / Release Worker 执行门禁三者不受影响
- 发布管理第 6–8 步加「授权发布 / 重排 / 回滚」按钮,点击需逐字输入确认语句,
  仅 admin 可见;重排沿用原执行授权人,不改写历史
- openapi 与 P6 契约同步;测试:双通道各一条 + 非 admin/错短语拒绝 + 前端按钮与确认门禁
2026-10-08 17:00:49 +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
hermes cddc4c90d1 fix(monitor): 修复取件死信无法重排与列表 NULL 标志导致的 500
- platform/postgres: listTestPublicationsSQL 的 can_start/can_retry 在 LEFT JOIN
  内容对象为空时算出 NULL,Go 侧 bool 扫描失败使整个列表接口 500,前端三个 lane
  全空(表现为“待入库 0 / 全部任务 0”)。改为 COALESCE(..., false)。
- 新增 POST /api/v1/job-failures/{id}/requeue:取件任务的幂等键绑在版本上,
  失败那次已占住它,重新标注同一文件不会再入队;现在按新轮次 (…:r<n>) 重排、
  复位意图取件状态并把死信标记为 requeued(幂等信封 + 审计字段)。
- web: 死信区对 artifact.fetch 显示「重新入队」;系统设置新增「知识库绑定」分区,
  挂上一直没被渲染的 RoutingBindingsSection,导入目标库可在页面上切换。
- 测试:真实 PostgreSQL 集成测试(NULL 折叠 + 重排轮次/幂等/拒绝)、前端死信按钮
  与设置分区渲染测试。
2026-10-08 12:09:34 +08:00
姚章浩 1db21fb899 feat(settings): 服务配置四项化并保存时真校验
- 生产/测试 WeKnora 地址、租户 ID、知识库 ID 由静态 env 收敛为版本化 UI 设置,保留修订号与操作人审计
- 新增 serviceverify:保存前真调目标端点探测,端点无效才拒绝;网络抖动或无凭据仅告警
- 迁移 000042(生产 WeKnora 端点)/000043(服务目标);补齐设置候选与校验契约用例
2026-09-30 10:54:07 +08:00
姚章浩 1d6ba6dc2e fix(monitor): keep failing publications visible outside the queue page
- The attention lane was derived from page 1 of the queue (limit 50, newest
  by updated_at), so failures ranked past 50 silently disappeared from
  "needs attention" as the completed backlog grew.
- Fetch attention items with a dedicated request: the API now accepts a
  comma-separated processing_state list (failed,stalled) and the view prefers
  that result set over the paged queue.
- Batch retry/cancel targets are computed from the same failure set instead of
  the visible page, so actions reach every failing item.
- 全部任务 shows 已显示 N / 共 M plus 加载更多 so rows past 50 are reachable.
- Fix pre-existing ui_settings integration test: strict JSON decoding rejects
  the legacy updated_by field (actor comes from the session).
- Tests: view regression (attention item outside the page, load-more
  affordance) + handler multi-state filter + isolated-DB set filter.
2026-09-30 10:54:07 +08:00
姚章浩 5037c05e93 feat(monitor): 取消失败发布并修正危险按钮对比度
- Parse-monitor: the batch-retry button rendered red-on-red because
  weknoraMonitor.css overrode the global .danger style with text color
  only; make .danger a filled red button with proper hover/disabled states.
- Add operator cancel: 批量取消所选 / per-task 不再处理 buttons move
  failed/stalled publications to a new 'cancelled' state in one audited
  batch operation (POST /api/v1/test-publications/cancel-batch, idempotent,
  migration 000041 widens the CHECK constraints). Queued upload jobs are
  cancelled, open dead letters abandoned; source records and spool files
  stay intact so re-marking in the catalog can retry later.
- Queue status filter and labels learn 已取消; OpenAPI contract updated.
2026-09-30 10:54:07 +08:00
姚章浩 e6ef5a4693 feat(filters): allowlist mode for filename filters
- filter rules gain mode ('exclude'|'allow'); nearest allow rule governs a
  directory subtree, extensions outside it are filtered before fetch
- filename_extension() now requires a well-formed suffix, so dated names like
  'CRA_V3.1手册_2024-07-31_en' no longer fake an extension; extensionless
  files are never filtered
- API accepts {mode, extensions}; UI editor becomes grouped format chips with
  keep/exclude modes (keep-list default: docs/text/scripts/images)
- migration 000036 + integration tests for allow, nesting and restore
2026-09-30 10:54:07 +08:00
姚章浩 b3c9331966 feat(ingest): 源与目录文件名过滤及钉钉文档导出
- filename_filter_rules table (000035) with source-root and per-directory rows
- additive inheritance: source default + every ancestor directory contributes
- filename_is_filtered() SQL predicate applied to catalog browsing, subtree
  counts, change-set diffing, routing fan-out and artifact fetch manifests
- fetch_state 'filtered' (CHECK extended); reconcile cancels/restores fetch
  jobs, retires planned publications and blocks items when rules change
- GET/PUT/DELETE /api/v1/filename-filters (Idempotency-Key, normalized
  extensions) + Settings and Catalog filter editors
- failJobSQL no longer swallows the claimed row when the failure projection
  upserts (fixes spurious 'lease lost' retries)
- DingTalk live fetch: adoc/axls now use the export-job pipeline (HTTP 403
  from file/download fixed), header-signature downloads supported
2026-09-30 10:54:07 +08:00
姚章浩 e60b10256a fix(settings): add DingTalk spaces and render the real login QR
Address two gaps in the first DingTalk-session UI:
- add an explicit admin-only '+ 新增钉钉知识库' action on the data-source
  panel; it accepts an alidocs space URL, resolves the live name/root through
  the authenticated sidecar, writes the inventory root atomically, upserts a
  deterministic source connection, and enqueues its first catalog refresh
- read DingTalk's real QR canvas pixels rather than taking a DOM screenshot;
  select by dark-pixel content, composite onto a white quiet zone, and render
  at 320x320 so the dialog contains a large complete code, not a page thumbnail

The live refresh path resolves registered roots from the connection locator,
while existing name-based DingTalk sources remain compatible.
2026-09-30 10:54:07 +08:00
姚章浩 173b44abca feat(settings): refresh DingTalk scan sessions from the data-source page
DingTalk source scanning depends on a shared browser session whose capture
file had to be replaced by hand whenever it expired (every scan 401s).

- services/dingtalk-login: admin-triggered Playwright sidecar that opens the
  space page in a persistent headless Chromium, publishes the QR through
  session polling, verifies the first authenticated XHR against the box API,
  then writes the capture in place (same inode keeps the worker's ro bind
  mount current); tokens never leave the sidecar
- api: POST/GET/DELETE /api/v1/dingtalk-session authenticated relay; QR is a
  login artifact, so the whole prefix is admin-only in classifyRole; OpenAPI
  contract and proxy/503/hostile-ID tests included
- web: 系统设置 → 数据源配置 钉钉卡片新增扫码刷新会话(仅管理员可见),
  QR 弹窗 + 1.5s 轮询 + 成功/过期/失败状态
- compose: sidecar service owns the ONLY rw mount of secrets/; worker keeps ro
2026-09-30 10:54:07 +08:00
姚章浩 021e91eae4 feat(settings): bind internal/external knowledge bases from the UI
Routing targets move from env-only to a UI-managed binding (migration
000034 routing_kb_bindings, single active row):
- GET/PUT /api/v1/routing-bindings + /options: env defaults act as the
  bootstrap fallback; a UI binding wins and takes effect immediately
  (per-request resolution, no restart), and clearing falls back to env
- PUT validates against the connected WeKnora instance (real knowledge
  base list via the scoped API key) and rejects same-KB bindings;
  saves require the admin role
- route/catalog capabilities now resolve through the binding so both
  annotation paths share one source of truth
- system settings > 服务配置 gains a 知识库绑定 panel: dropdown picker
  (or raw ID when WeKnora is unreachable) showing bound name, origin
  (UI vs env default) and last author
- contract tests: OpenAPI, handler matrix (fallback, validation,
  same-KB rejection, capability resolution), frontend fetch/save layer
2026-09-30 10:54:07 +08:00
姚章浩 4c86c5e659 fix(httpapi): accept planned in the test-publications state filter
validProcessingState predated policy-driven ingestion and rejected
processing_state=planned with a 400, so the 待入库 lane silently fetched
an empty plan list while annotation, fetch and planning had all succeeded.
2026-09-30 10:54:07 +08:00
姚章浩 63f3b2c503 feat(governance): policy-driven ingestion from catalog annotations
Annotation in 来源与目录 is now the only import decision surface:
- migration 000033: catalog_import_intents (durable, rescan-proof per-file
  intent with locator snapshot + fetch lifecycle), source_items.policy_state,
  planned publication state, ui_setting_revisions.ingestion_concurrency
- marking fans a folder decision out to per-file intents, materializes
  versioned items, reuses already-verified local bytes (compare, don't pull),
  and plans one publication per routed KB; blocked retires planned rows
  while live publications stay untouched (需下架 shows in the KB diff)
- artifact.fetch worker lane (scripts/artifact_fetch.py): DingTalk export
  tree/live box API, AList /api/fs/get direct download with SSO-redirect
  fail-closed, PP ledger via locator hash; content-level dedupe on sha256
- 开始处理 promotes planned->pending inside the claim-time concurrency budget
  from system settings; claims enforce it in SQL
- monitor overview rebuilt as knowledge-base diff: planned/needs_removal per
  KB + fetch pipeline summary; frontend lanes 待入库/需下架 replace the
  deprecated snapshot change-set confirmation
- spool: images (png/jpg/gif/bmp/webp) accepted, ppt/pptx rejected in favour
  of PDF twins; DingTalk PPT resolves its converted PDF pair
2026-09-30 10:54:07 +08:00
zheromiku f6cb90cd70 feat(web): login gate, session identity UI and member management
- main.tsx 挂 AuthProvider 门控:未登录→登录页;must_change_password→强制改密页
- 登录页/改密页/顶栏身份(头像+姓名+角色徽章+退出)按 A·Data-console token 样式
- 全局 fetch 拦截器:任意业务请求 401 自动回登录页(会话过期/被撤销不留死表单)
- 系统设置新增「成员与角色」分区(仅 admin 可见):名单、角色调整、启停、重置临时密码、新增成员
- 消灭全部手填操作人:审核/封批/确认计划集/范围规则/来源调度/UI 设置/开始处理/批量重试/
  目录差异确认共 9 处输入框删除,请求体不再携带 actor 字段(服务端从会话注入)
- 测试断言方向翻转:显式验证请求体不含 operator_ref/created_by 等字段
- 附带修复:登录失败计数 SQL 参数类型无法推断导致 500(pgx 扩展协议),显式 cast 解决

前端门禁:tsc 0 错、125 用例全绿、build 通过
2026-09-30 10:54:07 +08:00
zheromiku fbe80ad784 feat(auth): accounts, sessions, RBAC and server-stamped actors
上线账号权限管理(后端):
- 迁移 000032:users + user_sessions(token 只存 sha256,服务端可即时撤销)
- bcrypt 密码、8 次失败锁 15 分钟、统一 401 错误防用户名枚举
- 三角色 viewer/engineer/admin;HttpOnly+SameSite=Lax 会话 cookie,滑动续期
- 强制改密锁定:must_change_password 会话只能走 auth 端点;首登 dobotai
- bootstrap:users 表为空时创建 admin(初始密码 dobotai,强制改密)
- 生产双闸授权/回滚/失败重试收敛为 admin 角色;AuthorizedBy 用会话身份
- 全部业务写端点删除手填 actor 字段(operator_ref/reviewer_ref/created_by/
  sealed_by/confirmed_by),服务端从会话注入;auth 关闭的开发 socket 回退 system
- 成员管理 API:列表/创建/改角色/启停/重置密码,最后一名 active admin 保护
- RBAC 矩阵纯函数契约测试 + 中间件行为测试(登录/锁定/角色/审计戳)
- 附带修复:GET /api/v1/users 曾被 GET=viewer 规则误放行(测试抓到)
2026-09-30 10:54:07 +08:00
zheromiku dcdc8d1ddd feat: 知识治理平台(含已验证发布/回滚契约)
全栈知识治理平台首次入库(P1-P10 + 发布链路模拟验证闭环):

- 治理核心:源目录/变更集/人工审核/发布批次/发布执行(fail-closed 闸口)
- 测试通道:WeKnora 测试 KB 上传/轮询/验收;poll 完成时写入
  parser_generation/index_generation,修复 manual/seed 路径发布闸口永久拦截
- 发布适配器 v071(本地 source=测试角色 / 云端 target=生产角色 模拟验证通过):
  * ES 导出/导入 mapping 自适应(.keyword 子字段、可选 tenant_id)
  * OSS locator 支持嵌套 scheme;chunks.seq_id 由目标序列分配
  * bundle 归档同身份重试原子替换;verifier 按 bundle chunk_type 计数
  * 回滚 job payload 支持 prior_execution_id(strict 解码)
- 三通道发布+回滚演练实测:publish 4/4 verified,检索命中,
  rollback 4/4 rolled_back,目标端 PG is_enabled=false、ES is_enabled=false
- 前端 A·Data-console 风格全站统一,120 用例通过
- 门禁:go vet / go test 全包通过,gofmt 干净
2026-09-30 10:54:06 +08:00