Commit Graph

5 Commits

Author SHA1 Message Date
姚章浩 e744114ff4 fix(catalog): 打通共享授权扫描与统一来源清单显示
build / build (push) Successful in 1m18s
2026-10-26 18:36:11 +08:00
Hermes c8f05ee46f fix(qa): 数据源登记改走 dws,不再要求扫码
build / build (push) Failing after 15s
侧车扫码会话只用于解析空间;dws 已有有效凭据且能给出空间 id 与根节点,
登记直接用 dws 结果生成确定性键,同名来源回 200 already_present,认不出的空间才回落侧车。
2026-10-26 17:58:44 +08:00
姚章浩 9a8a3e7795 fix(dingtalk): 登记钉钉知识库 409 space_registration_conflict —— 归档行占名 + 错误被吞
build / build (push) Successful in 57s
现象:点「公司级技术支持知识库」登记,返回 HTTP 409:space_registration_conflict。

根因(真数据取证):
- source_connections.name 有唯一约束。这 3 个钉钉来源是**早期路径**登记的
  (base_url 为空,id 与 dws 现在推导的确定性 id 不同):
  sc-18d10ca0… 公司级技术支持知识库 / sc-eaef0ec1… ATOM知识库 / sc-dc7cea1c… 技术诊断部知识沉淀。
- 上一步「去掉来源」只是归档下架,**名字仍占着唯一槽位** ⇒ 重新登记时 INSERT 撞
  UNIQUE(name)。
- 处理器把**所有**存储层错误都翻译成同一个 409 space_registration_conflict(真实原因被吞)。

修复:
- store:同名的另一行若**已归档** ⇒ 改名腾位(`…(已下架 · <短ID>)`,归档行不显示,
  数据与审计一行不动);若**仍在使用** ⇒ ErrDingtalkSpaceNameTaken(绝不悄悄改名)。
  同一个登记键解析到别的 root 时:在用中的行报 ErrDingtalkSpaceIdentityConflict;
  归档过的行允许就地改写("去掉后再用 dws 重新获取"的语义)。
- httpapi:分类映射 —— space_name_conflict / space_identity_conflict(409 + 可行动 hint),
  其余内部故障改回 500 space_registration_failed,不再伪装成冲突。
- web:读一次响应体同时取 error 与 hint,**hint 优先**;补两个新码的中文说明。

验证:真 postgres 集成测试复刻线上形态(旧行占名 + 归档 ⇒ 登记成功、旧行改名保持归档、
内容不丢;同名在用 ⇒ 拒绝;同键换 root 在用 ⇒ 拒绝、归档 ⇒ 就地改写);handler 分类测试
(409/409/500,且不回显内部细节);前端 hint 优先用例;make verify 15/15。
2026-10-14 12:59:52 +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