dcdc8d1ddd
全栈知识治理平台首次入库(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 干净
251 lines
6.7 KiB
Markdown
251 lines
6.7 KiB
Markdown
# P4.2 来源只读扫描与变化入账验收
|
||
|
||
## 范围
|
||
|
||
本切片将 PP 本地历史快照接入普通 Worker 的持久扫描链路:
|
||
|
||
```text
|
||
来源管理手工触发
|
||
→ 原子创建 source_scan + source.scan.pp Job
|
||
→ 普通 Worker 只读 ledger/manifest/制品元数据
|
||
→ 与 613 条治理 Source Item 对账
|
||
→ 原子保存聚合、变化记录与连接健康
|
||
→ API/Web 展示扫描历史和变化结果
|
||
```
|
||
|
||
本切片不上传、移动、重命名或删除 PP 文件,不调用 PP 网络写接口,不删除治理对象或 WeKnora Knowledge。`missing` 只记录为 `unavailable`,54 个 `over_limit` 项继续阻断,生产 Release Worker 保持关闭。
|
||
|
||
## 安全配置与装配
|
||
|
||
- `PP_SCAN_ENABLED` 默认 `false`;仅显式启用且 allowed root、ledger、manifest 三个路径完整时注册 `source.scan.pp` Handler。
|
||
- allowed root 必须是绝对规范路径;ledger 和 manifest 必须位于其内。
|
||
- Scanner 拒绝越界、软链接、非普通文件、缺少 `local_path`、打开期间文件身份变化和超过字节上限的元数据文件。
|
||
- `over_limit` 记录允许其根目录内的制品不存在,因为它从未被下载;若制品存在仍必须通过普通文件和非软链接校验。
|
||
- Compose 只给普通 Worker 挂载 PP 数据目录,挂载模式为只读;API、Web、Release Worker均没有该挂载。
|
||
- 实际容器探针:ledger/manifest 可读,向 manifest 追加写入被只读文件系统拒绝。
|
||
- 本地测试 WeKnora Key 仍只挂载给普通 Worker;本切片不调用 WeKnora 上传或删除 API。
|
||
|
||
## Migration 5
|
||
|
||
Migration 5 新增:
|
||
|
||
```text
|
||
source_scans
|
||
source_scan_changes
|
||
idx_source_scans_one_active
|
||
idx_source_scans_connection_created
|
||
idx_source_scan_changes_pending
|
||
```
|
||
|
||
关键约束:
|
||
|
||
- 每个来源连接最多一个 `pending/running` Scan;
|
||
- Scan 状态限定为 `pending/running/completed/failed`;
|
||
- Change 类型限定为 `added/updated/unavailable`;
|
||
- `snapshot_sha256` 只在内部审计,不进入 API;
|
||
- down migration 先删除依赖的 changes 表,再删除 scans 表。
|
||
|
||
一次性 PostgreSQL 执行了:
|
||
|
||
```text
|
||
up to 5 → down to 4 → up to 5
|
||
result: passed
|
||
```
|
||
|
||
持久库迁移前逻辑备份:
|
||
|
||
```text
|
||
tmp/backups/governance-pre-migration5-20260906T011223+0800.dump
|
||
bytes=242987
|
||
mode=0600
|
||
pg_restore catalog=valid
|
||
```
|
||
|
||
持久库应用后:
|
||
|
||
```text
|
||
schema_migrations=1,2,3,4,5
|
||
source_items=618
|
||
content_objects=535
|
||
source_item_versions=618
|
||
test_publications=535
|
||
```
|
||
|
||
旧业务数据计数未因 Migration 5 改变。
|
||
|
||
## API
|
||
|
||
```text
|
||
POST /api/v1/source-connections/{id}/scan
|
||
GET /api/v1/source-connections/{id}/scans?limit=20&offset=0
|
||
GET /api/v1/source-connections/{id}/scans/{scan_id}
|
||
```
|
||
|
||
触发语义:
|
||
|
||
- 成功返回 `202`;
|
||
- 同一连接已有活动扫描返回 `409`;
|
||
- 不存在或不可扫描的连接返回 `404`;
|
||
- 创建 Scan 和 Job 在同一条 PostgreSQL data-modifying CTE 中完成。
|
||
|
||
安全只读投影允许:
|
||
|
||
```text
|
||
Scan: id/state/聚合计数/固定错误码/创建完成时间
|
||
Change: id/source_item_id(可空)/change_type/observed_size_bytes(可空)/created_at
|
||
```
|
||
|
||
明确省略:
|
||
|
||
```text
|
||
snapshot_sha256
|
||
stable_external_id
|
||
previous_revision
|
||
observed_revision
|
||
allowed root、ledger/manifest/local_path
|
||
来源 URL、正文、base_url、secret_ref、Token/API Key/Cookie
|
||
```
|
||
|
||
跨连接读取 Scan 详情返回 `404`。前端再次使用 allowlist 重建对象,不透传服务端额外字段。
|
||
|
||
## 真实 PP 只读扫描
|
||
|
||
真实输入只读基线:
|
||
|
||
```text
|
||
ledger events=1093
|
||
manifest/source items=613
|
||
existing artifacts=559
|
||
historical over_limit without downloaded artifact=54
|
||
```
|
||
|
||
第一次真实扫描按安全规则失败:
|
||
|
||
```text
|
||
state=failed
|
||
error_code=pp_snapshot_invalid
|
||
```
|
||
|
||
根因是初版 Scanner 把 54 个历史 `over_limit` 项未下载的制品也要求为已存在普通文件。修复后规则为:`over_limit` 缺失制品是合法阻断状态,但路径仍必须声明且位于 allowed root 内;其他状态缺失仍 fail-closed。
|
||
|
||
修复后的多次真实扫描均一致:
|
||
|
||
```text
|
||
observed=613
|
||
unchanged=559
|
||
added=0
|
||
updated=0
|
||
over_limit=54
|
||
missing=0
|
||
planned_uploads=0
|
||
changes=0
|
||
```
|
||
|
||
PP ledger、manifest 和来源制品未被修改;没有生成上传计划。
|
||
|
||
## 调度、并发与恢复
|
||
|
||
### 单活动扫描
|
||
|
||
Worker 停止时连续触发同一连接:
|
||
|
||
```text
|
||
first=202
|
||
second=409
|
||
active_scans=1
|
||
```
|
||
|
||
恢复 Worker 后唯一活动扫描完成。
|
||
|
||
### pending 恢复
|
||
|
||
```text
|
||
Worker stopped
|
||
→ POST creates pending Scan/Job
|
||
→ Worker started
|
||
→ Scan completed, Job completed
|
||
```
|
||
|
||
### 过期 running 租约重领
|
||
|
||
真实持久队列构造“旧 Worker 已领取但完成前崩溃”的状态:
|
||
|
||
```text
|
||
before: scan=running job=running attempt=1 lease_expired=true
|
||
restart persistent Worker
|
||
after: scan=completed job=completed attempt=2
|
||
result: observed=613 unchanged=559 over_limit=54
|
||
```
|
||
|
||
Handler 对 `running` Scan 可恢复;若 Scan 已经 `completed` 而 Job 尚未完成,则重领为幂等 no-op,不重复扫描或写 Change。
|
||
|
||
最终状态:
|
||
|
||
```text
|
||
source_scans=6
|
||
completed=5
|
||
failed_fixed_code=1
|
||
active_scans=0
|
||
active_source_scan_jobs=0
|
||
```
|
||
|
||
## Web 与 Firefox
|
||
|
||
“来源管理”保留左侧导航和顶部标签,并新增:
|
||
|
||
- 来源连接的最近扫描聚合;
|
||
- “立即扫描”按钮及活动态禁用;
|
||
- 最近 20 条扫描历史;
|
||
- 单次扫描变化记录安全详情;
|
||
- 零变化时明确显示“本次扫描未发现新增、更新或不可用变化”。
|
||
|
||
Firefox headless 1440×1200 验收:
|
||
|
||
```text
|
||
history_rows=6
|
||
completed=5
|
||
failed=1
|
||
detail_selection=passed
|
||
empty_changes_message=visible
|
||
safe_projection_leaks=0
|
||
root_overflow=false
|
||
table_overflow=false
|
||
```
|
||
|
||
截图:`tmp/P42_SOURCE_SCAN_HISTORY_FIREFOX.png`
|
||
|
||
视觉复核确认:导航、顶部标签、5 条完成/1 条失败、观察数、上限阻断和变化面板均清晰;无横向裁切、遮挡或乱码。
|
||
|
||
## 自动化验证
|
||
|
||
```text
|
||
PP配置安全默认和路径校验:passed
|
||
Worker Handler装配测试:passed
|
||
File Scanner边界与over_limit回归:passed
|
||
Store扫描触发/完成/失败/恢复/安全读取:passed
|
||
HTTP触发/历史/详情/404/409安全投影:passed
|
||
前端allowlist解析、请求和页面渲染:passed
|
||
一次性PostgreSQL真实扫描与变化读取:passed
|
||
Migration 5 up/down/up:passed
|
||
Web tests:31/31 passed
|
||
TypeScript typecheck:passed
|
||
Vite production build:passed
|
||
Compose config:passed
|
||
Compose全镜像build:passed
|
||
Go全仓库test:passed
|
||
Go vet:passed
|
||
Go cmd build:passed
|
||
gofmt差异:0
|
||
Firefox真实页面:passed
|
||
```
|
||
|
||
## 运行边界
|
||
|
||
- 本地治理平台与本地测试 WeKnora 范围内完成;
|
||
- PP 来源只读,未调用来源写/删 API;
|
||
- 未删除 PostgreSQL 或 WeKnora 卷;
|
||
- 未连接或修改生产环境;
|
||
- `RELEASE_WORKER_ENABLED=false`;
|
||
- 54 个 `over_limit` 项继续阻断;
|
||
- 未 commit、未 push。
|