docs(ops): redline evidence batch without capability release

Record SWBOOT L1–L4 (no real repo write), FAV ObserveShadow gates and
observation-window prerequisites, D7 Track E ID-only checklist, and stronger
PI narrative fallback negatives while keeping all redline flags off.
This commit is contained in:
WeiCHEN
2026-09-22 21:26:27 +08:00
parent 364ed7f1a3
commit bc873f4400
25 changed files with 720 additions and 1 deletions
@@ -0,0 +1,67 @@
---
title: "D7 Track E ID-only 合规清单(无物理删除)"
status: active
created: 2026-09-22
updated: 2026-09-22
compliance: pending-compliance-signoff
related_task: "ops/short-term/2026-09-22-redline-evidence.md"
related_adr: "adr/2026-09-17-d7-chat-data-retention.md"
---
# D7 Track E ID-only 合规清单
> 用途:在 D7 ADR 仍为 `pending-compliance-signoff` 期间,只做表面盘点与 ID-only 合规核对。**禁止**按 365/180/30 天执行物理删除或备份清除。本清单不是删除作业授权。
## 0. 硬约束
- [x] ADR 状态仍为 `pending-compliance-signoff`(见 [ADR](../../adr/2026-09-17-d7-chat-data-retention.md))
- [x] 本轮零 `DELETE FROM chat_*`、零备份清除、零 retention 作业
- [x] FEATURE-MATRIX「对话数据治理(D7)」保持 `deferred`(本批不改矩阵)
- [ ] 合规签字或改数后,另开整改任务再勾选删除路径
## 1. 数据表面盘点(只读)
| ID | 表面 | 检查方法 | 期望 | 本批结果 |
|----|------|----------|------|----------|
| S1 | `chat_sessions` / `chat_messages` | 代码检索写路径;对照 W7 计数资产 | 有 `deleted_at` 列;无物理 DELETE 写者;无 soft-delete 写路径亦可接受(缺口属签字后整改) | PASS:`rg` 无 `DELETE FROM chat_*`;无 chat `SET deleted_at` 写者 |
| S2 | `context_memory_chunks`(`chat_history`) | 对照 W7 盘点 | 派生副本随正文;无独立 retention 删 | PASS(静态);未跑共享 SQL |
| S3 | `freeform_documents` | 已知 soft-delete UI | 与聊天独立周期;本轮不删 | PASS(不触碰) |
| S4 | `event_envelopes.payload` | 对照 Track E / W7 keys | 无 `content` / 消息正文 | PASS(沿用 W7:`has_content=f`);本批未重连 PG |
| S5 | `event_dead_letters` / attempts | 结构 / 既有盘点 | 仅 ID + reason / error_code | PASS(沿用 W7) |
| S6 | 应用日志 | 规范 + 既有抽检 | 无 chat 正文;DSN 脱敏 | PASS(沿用 W7);本批未 `docker logs` |
| S7 | `scripts/backup-system-db.sh` | 阅读脚本 | `pg_dump -Fc` 整库含明文;本轮不执行 | PASS:未执行 dump |
| S8 | `data/*/workspaces` | 目录约定 | 非终态;无自动 30 天清理 | PASS(无作业) |
| S9 | FAV / `final_artifacts` | consumer mode | 与聊天解耦;fav off | PASS:Register(fav) fail-closed |
证据基线:[W7 报告](../../test-reports/2026-09-17-w7-d7-inventory-test-report.md)、[盘点审计](../../audit/2026-09-17-d7-data-inventory.md)、[红线短文](../short-term/2026-09-22-redline-evidence.md)。
## 2. Track E ID-only 字段规则
日志 / DLQ / reconciliation / 对外 summary **仅允许**:
| 允许 | 禁止 |
|------|------|
| event_id / delivery_id / run_id / plan_id / project_id / doc_id | 消息 `content` / prompt 全文 |
| digest / sha256 / summary_ref | 用户 PII 明文堆栈 |
| typed error_code / allowlist 摘要键 | 未 allowlist 的 payload 整包 |
| 状态枚举(pending/leased/…) | 备份文件路径中的密钥 |
核对动作:
- [x] `events.SanitizeSummary` / StaticContracts 方向:生产路径不以正文入 envelope(W7 已证 keys)
- [x] 发现「已签字基线被违反」才写 gap-matrix;本批**未发现** → 不改 gap-matrix
- [ ] 签字后:收紧 `taskrun.completed` 等额外 ID 字段 allowlist(follow-up,非 blocker)
## 3. 签字后才允许的整改(本清单不执行)
1. 聊天 soft-delete 写路径 → 观察窗 → 物理清理
2. 同步清理 `context_memory_chunks` 中 `chat_history`
3. 备份二次批准 + 精确 delete manifest(对齐 R22)
4. 字段级加密选型(ADR 明确首期不做默认加密)
## 4. 完成定义(本批)
- [x] 表面表 S1–S9 有结论
- [x] ID-only 规则成文
- [x] 无物理删除证据
- [x] ADR 仍 `pending-compliance-signoff`
@@ -0,0 +1,139 @@
---
title: "红线证据批次(无能力放量)"
task_id: "2026-09-22-redline-evidence"
status: done
created: 2026-09-22
updated: 2026-09-22
owner: Cursor
task_type: research
task_tier: multi_stage
policy_revision: "agent-governance-2026-09-21-r1 @ local-dev 364ed7f1a3f0393ef77ec4623a9518116ed2cb87"
branch: feature/redline-evidence-2026-09-22
worktree: .worktrees/redline-evidence-2026-09-22
base_dev_sha: "364ed7f1a3f0393ef77ec4623a9518116ed2cb87"
write_scope:
- ".doc/ops/short-term/"
- ".doc/ops/checklists/"
- ".doc/test-reports/"
- "internal/docrevision/narrative_test.go"
allowed_environments: []
head_sha: ""
mutex: "redline-evidence-docs"
review_state: not_required
integration_state: pending
deployment: not_required
cleanup: pending
cleanup_owner: "独立维护者"
cleanup_trigger: "合入 dev 且无进程占用后复查 remove"
remote_sync: not_requested
---
# 红线证据批次(无能力放量)
## 用户目标与授权
- 用户目标:在 worktree `redline-evidence-2026-09-22` 落 SWBOOT / FAV / D7 / PI narrative 红线证据;**不**做能力放量、**不**翻转 flag。
- 交付终点:短文 + test-report + D7 Track E 清单 + 叙事负例单测;本地 commit;不 push。
- 明确禁止项:
- SWBOOT-10 保持 fail-closed stub(不实现真实 patch/merge/push)
- FAV:无整支 merge、无 live;`FAV_DEGRADED_READ` 保持 false;不启用 `Register(fav)`
- D7:retention 不做物理删除;ADR 保持 `pending-compliance-signoff`
- PI change-report narrative flag 保持 OFF
- `MULTI_INTENT_PLAN_TIER` 保持 legacy;`SEMANTIC_SUMMARY_REPLACE` 保持未接线
## 范围与非目标
- 允许:只读复验、证据报告、观察窗前置登记、Track E ID-only 清单、叙事 fallback 负例测试。
- 非目标:能力 release、compose/env flag 翻转、共享环境写操作、FAV live、SWBOOT-10 实施、D7 物理删除、features.go 与 drift worktree 冲突时的重复改码。
## 步骤与验收
1. [x] SWBOOT:跑验收清单中**无需真实仓写**的 L1–L4 项;L5/L6 标 pending auth — 证据:[报告](../../test-reports/2026-09-22-redline-evidence-swboot-test-report.md)
2. [x] FAV:审计 `ObserveShadow`(WouldStage/WouldPublish false);登记观察窗启动前置;注明 `registry.go` / `dispatcher.go` 硬门 — 见下文
3. [x] D7:盘点 DB/备份/日志表面 + Track E ID-only 合规清单(无删除)— [清单](../checklists/2026-09-22-d7-track-e-id-only-compliance.md)
4. [x] PI narrative:补强 flag-on fallback 负例;flag 保持 off;`features.go` reason 跳过(drift worktree 已改)— 见下文
## FAV 审计与观察窗前置
### 代码门禁(本轮未改)
| 门禁 | 位置 | 行为 |
|------|------|------|
| `Registry.Register(fav)` | `internal/events/registry.go` | 恒返回 `ErrDisabledConsumer` |
| `AssertLiveConsumers` / `LiveConsumers` | 同上 | 拒绝 / 剔除 `fav` |
| `PeekPendingLeases` / `ClaimLeases` / `SetMode` | `internal/events/dispatcher.go` | consumer=`fav` 直接拒绝;claim SQL 含 `d.consumer <> 'fav'` |
| `ObserveShadow` | `internal/app/fav/subscriber.go` | `WouldStage`/`WouldPublish`/`DegradedReadUsed` 恒 false;`FAV_DEGRADED_READ` 为真则拒绝 |
| `HandleLease` | 同上 | 默认 disabled;即使 `Enabled=true` 仍 fail-closed 至 cutover 审批 |
复验命令(本 worktree,`-short`):
```text
go test -count=1 -short ./internal/app/fav/ ./internal/events/ -run 'Fav|Register|ObserveShadow|Cutover|Disabled'
→ PASS
```
Compose 默认(dev/test/main):`FAV_DEGRADED_READ=false`、`FAV_PUBLISHER_ENABLED=false`(main 硬编码 `"false"`)。
### 观察窗启动前置(登记,本轮不开始计时)
在授权开启 live **之前**须同时满足;任一缺失则观察窗**不得**起算:
1. 用户显式授权:允许 `fav` durable consumer cutover(单独审批,非整支 merge `feature/final-artifact-vault`)。
2. 解除 `events` 硬门:`Registry.Register(fav)` / `SetMode(fav,*)` / claim 路径可按审批切片放行(本批禁止改)。
3. 顺序固定:`staging → artifact → publication`;每级 `shadow → drain → live`。
4. 每级观察窗下限:≥7 天 **或** ≥N 终态样本(取更严);崩溃/重启幂等证据齐全。
5. `FAV_DEGRADED_READ` **全程 false**(不随 live 开启)。
6. 三环境 compose 与 sysadmin 成熟度目录一致声明;无旁路 `cmd/events` 注册。
7. 独立 mutex `events` 持有者;CURRENT 集成槽位已认领。
8. 回滚预案:任一级失败立即回 `shadow`/`off`,不前进下一级。
**本轮结论**:shadow 可观测;live 观察窗 **未开始**。
## D7 表面盘点(无删除)
静态复验(相对 [W7 盘点](../../audit/2026-09-17-d7-data-inventory.md) / [报告](../../test-reports/2026-09-17-w7-d7-inventory-test-report.md)):
| 表面 | 位置 / 入口 | 本轮结论 |
|------|-------------|---------|
| 会话正文 | `chat_sessions` / `chat_messages`(`deleted_at` 列) | Go 检索无 `DELETE FROM chat_*`;无 chat soft-delete 写路径 |
| 派生记忆 | `context_memory_chunks`(含 `chat_history`) | 随正文留存;无 retention 作业 |
| 私密文档 | `freeform_documents` soft-delete | 与聊天独立;本轮不触碰 |
| 事件 envelope / DLQ | `event_envelopes` / `event_dead_letters` | Track E:只允许 ID/digest/typed error / allowlist 摘要 |
| 应用日志 | Docker `json-file` / slog | 禁止消息正文;DSN 脱敏 |
| 备份 | `scripts/backup-system-db.sh`(`pg_dump -Fc` 整库) | 含 chat 明文;本轮**未**执行 dump / 未清备份 |
| Agent workspace | `data/*/workspaces` | 非终态;无 30 天清理作业(ADR 待签) |
| FAV 终态 | `final_artifacts` | 与聊天口径解耦;consumer off |
ADR [`2026-09-17-d7-chat-data-retention`](../../adr/2026-09-17-d7-chat-data-retention.md) 仍为 `pending-compliance-signoff`。合规签字前禁止按 365/180/30 物理删除。
## PI narrative
- 仓库默认 / compose:`PI_CHANGE_REPORT_NARRATIVE=false`(dev/test/main)。
- 新增/加宽负例:`internal/docrevision/narrative_test.go`(SHA mismatch、wrong schema kind、top-level error_code、empty raw、empty fetcher body、authorized-flip 路径仍默认 off)。
- **跳过** `internal/sysadmin/features.go` reason 更新:并行 worktree `feature/status-drift-reconcile-2026-09-22` 已改 SEMANTIC_SUMMARY_SHADOW / PI_CHANGE_REPORT_NARRATIVE 文案;本批若改会产生冲突。集成时以 drift 为准。
红线保持:`MULTI_INTENT_PLAN_TIER=legacy`;`SEMANTIC_SUMMARY_REPLACE` 未接线。
## 验证与证据
| 范围 / 被测 SHA | 命令与环境 | 预期 | 实际 / 判定 | 证据 |
|---|---|---|---|---|
| SWBOOT L1–L4(基线 `364ed7f1`) | 见 SWBOOT 报告 | PASS;L5/L6 pending | PASS | [swboot-test-report](../../test-reports/2026-09-22-redline-evidence-swboot-test-report.md);[assets](../../test-reports/assets/2026-09-22-redline-evidence/) |
| FAV shadow / registry | `go test … fav/ events/` | WouldStage/Publish false;Register fail-closed | PASS | [fav-shadow.txt](../../test-reports/assets/2026-09-22-redline-evidence/fav-shadow.txt) |
| D7 / Track E | 静态检索 + 清单 | 无删除;ID-only 清单落盘 | PASS | [checklist](../checklists/2026-09-22-d7-track-e-id-only-compliance.md) |
| PI narrative 负例 | `go test ./internal/docrevision/ -run 'Narrative\|…'` | flag off;flag-on fallback | PASS | [pi-narrative.txt](../../test-reports/assets/2026-09-22-redline-evidence/pi-narrative.txt) |
| arch | `./scripts/arch-check.sh --mode block` | production gate passed | PASS | [arch-check.txt](../../test-reports/assets/2026-09-22-redline-evidence/arch-check.txt) |
## 收口与交接
- 本地提交在本分支;**未**合入 `dev`;**未** push;**无**部署。
- `features.go` 以 status-drift 分支为准合并。
- cleanup:合入并确认 tip 为 dev 祖先后由独立维护者 remove worktree。
- 下一步(需另授权):SWBOOT L5/L6 / SWBOOT-10 安全评审;FAV live 观察窗;D7 合规签字;PI narrative flag 翻转。
## 相关链接
- 验收清单:[../checklists/2026-09-14-software-project-bootstrap-acceptance.md](../checklists/2026-09-14-software-project-bootstrap-acceptance.md)
- W6 FAV shadow:[2026-09-17-w6-fav-subscriber-shadow.md](./2026-09-17-w6-fav-subscriber-shadow.md)
- W7 D7 盘点:[2026-09-17-w7-d7-inventory.md](./2026-09-17-w7-d7-inventory.md)
- Wave 7 编排:[2026-09-18-wave7-roadmap-orchestration.md](./2026-09-18-wave7-roadmap-orchestration.md)
@@ -0,0 +1,111 @@
# 红线证据 · SWBOOT L1–L4 验收报告(无真实仓写)
## 一、测试背景 (Background)
对照 [软件项目自举验收清单](../ops/checklists/2026-09-14-software-project-bootstrap-acceptance.md) 分层 L1–L6。本批**仅**执行无需真实仓库 patch/merge/push 的工程门禁子集;SWBOOT-10 保持 `ERR_SWBOOT10_STUB_ONLY` fail-closed。L5(真实 PI Runner)与 L6(无头浏览器页面)标 **pending auth**,不冒充 PASS。
## 二、测试目标 (Test Objectives)
- 复验 L1 catalog/manifest/policy、L2 领域服务(内存/短测)、L3 前端软件域守卫、L4 stub/固定输出(含 controlled_patch 拒绝)
- 证明 SWBOOT-10 stub 仍拒绝 `ExecuteControlledPatch` / `ApproveMerge`
- 不写真实软件仓;不升 FEATURE-MATRIX「纯软件项目自举」状态
## 三、测试方案与环境 (Test Plan & Environment)
| 项 | 值 |
|----|----|
| OS | Darwin 25.6.0(arm64) |
| 分支 | `feature/redline-evidence-2026-09-22` |
| 基线 SHA | `364ed7f1a3f0393ef77ec4623a9518116ed2cb87` |
| 方法 | `go test -short`、`arch-check --mode block`、`npx tsx --test`(局部) |
| 共享环境 | **未使用**(无 rebuild / 无 Playwright / 无真实 PI) |
### 分层映射
| 层 | 本批范围 | 状态 |
|----|----------|------|
| L1 | `internal/swboot` catalog/preview/allowlist;`internal/workflowdef` software_bootstrap/delivery policy | **RUN / PASS** |
| L2 | `internal/projectworkflow` creation/catalog;controlled_patch API 拒绝(短测,无共享 PG 写) | **RUN / PASS**(隔离短测;空库全迁移 / 并发 PG 未跑) |
| L3 | `web/src/lib/createProjectSubmitGuard.test.ts`(软件域不克隆医疗 7 包) | **RUN / PASS**(单文件;全量 `npm test`/`build` 因本机缺全局 `tsx` PATH 未整套跑,用 `npx tsx` 精确用例) |
| L4 | stub:`RepoAllowlist` patch/merge stub;`PiSoftwareBootstrapAdvisor` 拒 `controlled_patch` | **RUN / PASS** |
| L5 | 真实 PI Agent Runner UI/小队 | **pending auth** |
| L6 | Ubuntu Playwright 真实页面 UI-SW-00–14 | **pending auth** |
## 四、预期结果 (Expected Results)
- L1–L4 适用项 PASS;SWBOOT-10 stub 负例 PASS
- L5/L6 明确 pending,不写假 PASS
- 清单 §4 controlled patch 负例中「只在 feature worktree 写入 / 真实 merge」等需真实写的项保持 NOT RUN
## 五、实际测试结果与判定 (Actual Results & Evaluation)
### 5.1 L1 Manifest / catalog
- **Input**:`go test -count=1 -short ./internal/swboot/ ./internal/workflowdef/ -run 'SWBOOT|Software|RepoAllow|Controlled|ProjectTypes|CreationPreview|SuggestDelivery|catalog'`
- **Actual**:`ok` swboot / workflowdef;software_bootstrap 接受 `non_medical_software`、拒绝 `medical_device`;solo+controlled_patch 预览无效并含 `ERR_EXECUTION_MODE_DISABLED`
- **Verdict**:PASS
- **Duration**:&lt;2s
### 5.2 L2 领域服务(短测)
- **Input**:`go test -count=1 -short ./internal/projectworkflow/ -run 'SWBOOT|Software|Controlled|software_bootstrap'`
- **Actual**:`ok`;`controlled_patch` 创建路径拒绝(需 SWBOOT-10 executor)
- **Verdict**:PASS(短测范围);空库 schema 升级 / 共享 PG 鉴权并发 **NOT RUN**(需授权环境)
- **Duration**:&lt;1s
### 5.3 L3 前端软件域守卫
- **Input**:`npx --yes tsx --test src/lib/createProjectSubmitGuard.test.ts`(cwd=`web/`)
- **Actual**:4/4 pass,含 `shouldCloneMedicalTemplates` 对 `non_medical_software` 为 false
- **Verdict**:PASS(精确用例);全仓 `npm test` / `npm run build` 本轮未整跑
- **Duration**:~0.3s(tsx 启动另计)
### 5.4 L4 Stub / 固定输出
- **Input**:`TestRepoAllowlistStub`;`go test ./internal/app/ -run 'PiSoftware|Advise|controlled'`
- **Actual**:`ExecuteControlledPatch` / `ApproveMerge` → `ERR_SWBOOT10_STUB_ONLY`;advisor 拒 `controlled_patch`
- **Verdict**:PASS(stub-only,非真实执行器)
- **Duration**:&lt;1s
### 5.5 工程门禁冒烟
- **Input**:`./scripts/arch-check.sh --mode block`;`go vet ./internal/swboot/ ./internal/app/fav/ ./internal/events/ ./internal/docrevision/`
- **Actual**:`arch-check block: production gate passed`;vet 无输出
- **Verdict**:PASS
- **Duration**:~1.6s
### 5.6 L5 / L6
- **Input**:未请求真实 PI / Playwright 授权;未占集成槽位
- **Actual**:未执行
- **Verdict**:**pending auth**(NOT RUN)
### 5.7 清单 §4 未来 controlled patch 负例
| 项 | 本批 |
|----|------|
| repo 未绑定 / 非 allowlist / `.env` 排除 | 单元 stub 部分覆盖 allowlist;真实绑定 **NOT RUN** |
| 只在 feature worktree 写入 | **NOT RUN**(禁止真实写) |
| 测试失败不自动 merge / 批准≠merge/push | stub `ApproveMerge` PASS;真实路径 **NOT RUN** |
| 保护分支 force push 拒绝 | **NOT RUN** |
## 六、自动化测试套件汇总 (Test Suite Execution)
```text
go test -count=1 -short ./internal/swboot/ ./internal/workflowdef/ ./internal/projectworkflow/ ./internal/app/
go test -count=1 -short ./internal/app/fav/ ./internal/events/ -run 'Fav|Register|ObserveShadow|Cutover|Disabled'
./scripts/arch-check.sh --mode block
npx tsx --test web/src/lib/createProjectSubmitGuard.test.ts
# L5/L6: NOT RUN (pending auth)
```
全部适用命令退出码 0。未运行共享环境 dump、未写业务仓。
命令输出片段(脱敏/截断):[assets/2026-09-22-redline-evidence/](./assets/2026-09-22-redline-evidence/)(`swboot-l1.txt`、`arch-check.txt`、`env.txt`)。
## 七、测试结论与后续 (Conclusion & Next Steps)
- **SWBOOT L1–L4(无真实仓写子集)PASS**;SWBOOT-10 仍为 fail-closed stub。
- **L5/L6 pending auth**:需占用集成槽位、真实 PI Runner、Playwright 容器后再跑 UI-SW-00–14。
- 升 FEATURE-MATRIX done 仍须独立安全评审后的受控执行器(本批不做)。
- 任务短文:[../ops/short-term/2026-09-22-redline-evidence.md](../ops/short-term/2026-09-22-redline-evidence.md)
@@ -0,0 +1,7 @@
=== arch-check ===
arch-check mode=block
boundaries=/private/tmp/Agent-System/.worktrees/redline-evidence-2026-09-22/.doc/system-design/module-boundaries.yaml
ok chat-agent-system/internal/archtest 0.976s
ok chat-agent-system/internal/archtest 0.494s
arch-check block: production gate passed
EXIT:0
@@ -0,0 +1,11 @@
=== chat physical delete / soft-delete write paths ===
NONE
=== chat_messages UPDATE (content edit paths, not retention) ===
internal/ctxmgr/recategorize.go:99: UPDATE chat_messages
internal/ctxmgr/message_update.go:30: UPDATE chat_messages
internal/ctxmgr/message_update.go:77: UPDATE chat_messages
internal/ctxmgr/message_update.go:118: UPDATE chat_messages
=== ADR compliance field ===
6:compliance: pending-compliance-signoff
18:**提议 / 工程草案**(2026-09-17)。数字与删除口径标 `pending-compliance-signoff`:合规签字或改数前,W7 仅允许数据分类盘点与现状审计,不得按未签字数字执行物理删除或备份清除。接受后状态改为 `accepted`。
54:- W7 可按本草案启动盘点;物理删除与备份清除在 `pending-compliance-signoff` 解除前禁止按建议数字自动执行。
@@ -0,0 +1,18 @@
=== D7 DELETE / soft-delete / retention ===
internal/artifactvault/registry.go:265: source_run_id, payload_sha256, terminal_state, policy_version, publication_status, retention_class
internal/artifactvault/registry.go:296: terminal_state, policy_version, publication_status, retention_class, requested_at
internal/artifactvault/types.go:134: RetentionClass string `json:"retention_class,omitempty"`
---backup---
3:# 系统业务库备份脚本 (backup-system-db.sh)
7:# 2. 采用 pg_dump -Fc 导出系统业务库(含 task_runs、工作流治理等);
11:# ./scripts/backup-system-db.sh <dev|test|main> <目标备份目录>
15:# 2. ${HOME}/.secrets/backup-env 中 SYSTEM_DB_PASSWORD_<ENV>
17:# 当前实现经 docker exec 以库内角色执行 pg_dump,通常不需要宿主机口令;
32: $0 dev ./backups/system_dev_20260912
33: $0 test /tmp/agent_system_test_backup
36: SYSTEM_DB_PASSWORD 或 \${HOME}/.secrets/backup-env 或 configs/.env.<env>
81: local secrets_file="${HOME}/.secrets/backup-env"
115:# 仅保留变量供未来宿主机连接扩展;当前 pg_dump 走容器内角色。
161:echo "[导出] pg_dump -Fc ${DB_NAME} ..."
163:docker exec "${CONTAINER_NAME}" pg_dump -U "${DB_USER}" -d "${DB_NAME}" -Fc -Z 6 -f "${TMP_CONTAINER_DUMP}"
---log driver---
@@ -0,0 +1,4 @@
all_payload_fields_sorted= ['candidate_id', 'doc_id', 'environment', 'impacted_doc_id', 'plan_id', 'project_id', 'report_id', 'revision', 'risk', 'run_id', 'session_id', 'snapshot_id', 'status', 'step_id', 'summary_ref', 'task_key', 'user_id', 'version_no']
forbidden_hits= NONE
has_summary_ref= True
contract_count= 11
@@ -0,0 +1,19 @@
=== Track E contracts / content forbidden ===
internal/events/registry.go:50: {EventName: EventIntentPlanAwaiting, SchemaVersion: 1, Enabled: true, Producers: []string{"app/intentflow", "events"}, Consumers: []string{ConsumerProjection, ConsumerNotify}, PayloadFields: []string{"plan_id", "revision", "status", "environment", "project_id", "user_id", "session_id", "summary_ref"}},
internal/events/registry.go:51: {EventName: EventIntentPlanStepChanged, SchemaVersion: 1, Enabled: true, Producers: []string{"app/intentflow", "events"}, Consumers: []string{ConsumerProjection, ConsumerNotify}, PayloadFields: []string{"plan_id", "revision", "step_id", "status", "environment", "project_id", "user_id", "session_id", "summary_ref"}},
internal/events/registry.go:52: {EventName: EventIntentPlanCompleted, SchemaVersion: 1, Enabled: true, Producers: []string{"app/intentflow", "events"}, Consumers: []string{ConsumerProjection, ConsumerNotify}, PayloadFields: []string{"plan_id", "revision", "status", "environment", "project_id", "user_id", "session_id", "summary_ref"}},
internal/events/registry.go:148: PayloadFields []string `json:"payload_fields"`
.doc/system-design/module-boundaries.yaml:141: - doccontent
.doc/system-design/module-boundaries.yaml:147: - doccontent
.doc/system-design/module-boundaries.yaml:149: doccontent: []
.doc/system-design/module-boundaries.yaml:343: artifact_payload_staging:
=== RUN TestClassifySafeKnownContract
--- PASS: TestClassifySafeKnownContract (0.00s)
=== RUN TestSanitizeSummaryDropsPayload
--- PASS: TestSanitizeSummaryDropsPayload (0.00s)
=== RUN TestIntentPlanContractsOmitSensitivePayload
--- PASS: TestIntentPlanContractsOmitSensitivePayload (0.00s)
=== RUN TestRegistryParityWithYAML
--- PASS: TestRegistryParityWithYAML (0.00s)
PASS
ok chat-agent-system/internal/events 0.490s
@@ -0,0 +1,6 @@
SHA=364ed7f1a3f0393ef77ec4623a9518116ed2cb87 DATE=2026-09-22T13:25:12Z OS=Darwin GO=go1.27.1
darwin
arm64
go1.27.1
Darwin WeiCHENdeMac-mini.local 25.6.0 Darwin Kernel Version 25.6.0: Fri Jul 31 19:17:26 PDT 2026; root:xnu-12377.161.14~5/RELEASE_ARM64_T6041 arm64
2026-09-22T13:26:20Z
@@ -0,0 +1,6 @@
docker-compose.main.yml:132: FAV_DEGRADED_READ: "false"
docker-compose.main.yml:133: FAV_PUBLISHER_ENABLED: "false"
docker-compose.test.yml:129: FAV_DEGRADED_READ: ${FAV_DEGRADED_READ:-false}
docker-compose.test.yml:130: FAV_PUBLISHER_ENABLED: ${FAV_PUBLISHER_ENABLED:-false}
docker-compose.dev.yml:133: FAV_DEGRADED_READ: ${FAV_DEGRADED_READ:-false}
docker-compose.dev.yml:134: FAV_PUBLISHER_ENABLED: ${FAV_PUBLISHER_ENABLED:-false}
@@ -0,0 +1,10 @@
=== RUN TestAssertLiveConsumersRejectsFAV
--- PASS: TestAssertLiveConsumersRejectsFAV (0.00s)
=== RUN TestAssertLiveConsumersRejectsUnknown
--- PASS: TestAssertLiveConsumersRejectsUnknown (0.00s)
=== RUN TestLiveConsumersOmitsFAV
--- PASS: TestLiveConsumersOmitsFAV (0.00s)
=== RUN TestManifestJSONIncludesDisabledFAV
--- PASS: TestManifestJSONIncludesDisabledFAV (0.00s)
PASS
ok chat-agent-system/internal/events 0.321s
@@ -0,0 +1,9 @@
13: ConsumerFAV = "fav"
22: EventFAVArtifactStaged = "fav.artifact_staged"
49: {EventName: EventFAVArtifactStaged, SchemaVersion: 1, Enabled: false, Producers: []string{"app/docflow"}, Consumers: []string{ConsumerFAV}, PayloadFields: []string{"doc_id", "snapshot_id"}},
84: if cons == ConsumerFAV || strings.EqualFold(cons, "fav") {
85: return fmt.Errorf("%w: fav", ErrDisabledConsumer)
102: if n == ConsumerFAV {
119:func (r *Registry) Register(consumer string) error {
123: if consumer == ConsumerFAV || strings.EqualFold(consumer, "fav") {
124: return fmt.Errorf("%w: fav", ErrDisabledConsumer)
@@ -0,0 +1,20 @@
=== FAV shadow ===
=== RUN TestFavSubscriberDefaultsDisabled
--- PASS: TestFavSubscriberDefaultsDisabled (0.00s)
=== RUN TestFavSubscriberStillDisabledWhenFlagOnWithoutRegistryApproval
--- PASS: TestFavSubscriberStillDisabledWhenFlagOnWithoutRegistryApproval (0.00s)
=== RUN TestFavSubscriberShadowComputesIntendedOutcomeWithoutWrites
--- PASS: TestFavSubscriberShadowComputesIntendedOutcomeWithoutWrites (0.00s)
PASS
ok chat-agent-system/internal/app/fav 0.293s
=== RUN TestValidateOpsCutoverRejectsFavAndNonDev
--- PASS: TestValidateOpsCutoverRejectsFavAndNonDev (0.00s)
=== RUN TestValidateOpsCutoverAllowsThreeConsumers
--- PASS: TestValidateOpsCutoverAllowsThreeConsumers (0.00s)
=== RUN TestClaimIDsDisabled
--- PASS: TestClaimIDsDisabled (0.00s)
=== RUN TestManifestJSONIncludesDisabledFAV
--- PASS: TestManifestJSONIncludesDisabledFAV (0.00s)
PASS
ok chat-agent-system/internal/events 0.303s
EXIT:0
@@ -0,0 +1,19 @@
=== FAV compose defaults ===
---
internal/events/registry.go:81: return fmt.Errorf("%w: %s", ErrDisabledConsumer, eventName)
internal/events/registry.go:85: return fmt.Errorf("%w: fav", ErrDisabledConsumer)
internal/events/registry.go:124: return fmt.Errorf("%w: fav", ErrDisabledConsumer)
internal/app/fav/subscriber.go:4:// a separate approval. Registry.Register("fav") and SetMode(fav, live) already
internal/app/fav/subscriber.go:41: return fmt.Errorf("%w: fav", events.ErrDisabledConsumer)
internal/app/fav/subscriber.go:44: return fmt.Errorf("%w: fav publisher cutover not approved", events.ErrDisabledConsumer)
internal/app/fav/subscriber.go:53:// as a write receipt; WouldStage/WouldPublish stay false in this slice.
internal/app/fav/subscriber.go:59: WouldStage bool
internal/app/fav/subscriber.go:60: WouldPublish bool
internal/app/fav/subscriber.go:64:// ObserveShadow computes the intended FAV outcome without business side effects.
internal/app/fav/subscriber.go:66:func (c *EventConsumer) ObserveShadow(lease events.Lease) (IntendedOutcome, error) {
internal/app/fav/subscriber.go:74: return out, fmt.Errorf("%w: fav", events.ErrDisabledConsumer)
internal/app/fav/subscriber.go:79: out.WouldStage = false
internal/app/fav/subscriber.go:80: out.WouldPublish = false
internal/events/dispatcher.go:126: return nil, fmt.Errorf("%w: fav", ErrDisabledConsumer)
internal/events/dispatcher.go:165: return nil, fmt.Errorf("%w: fav", ErrDisabledConsumer)
internal/events/dispatcher.go:338: return fmt.Errorf("%w: fav", ErrDisabledConsumer)
@@ -0,0 +1,6 @@
=== RUN TestVaultDefaultsStayDisabled
--- PASS: TestVaultDefaultsStayDisabled (0.00s)
=== RUN TestVaultStatusReportsOff
--- PASS: TestVaultStatusReportsOff (0.00s)
PASS
ok chat-agent-system/internal/server 0.681s
@@ -0,0 +1 @@
364ed7f1a3f0393ef77ec4623a9518116ed2cb87
@@ -0,0 +1,5 @@
> shuimu-workbench@1.0.0 build
> tsc && vite build && node scripts/precompress-dist.mjs
sh: tsc: command not found
@@ -0,0 +1,7 @@
=== L3 frontend quick ===
> shuimu-workbench@1.0.0 test
> tsx --test "src/**/!(*InteractiveCard*|*chatWindowArtifact*|*deliverableSync*).test.ts" && vitest run src/components/chat/InteractiveCard.test.ts src/components/chat/chatWindowArtifact.test.ts src/components/chat/deliverableSync.test.ts --run
sh: tsx: command not found
EXIT:0
@@ -0,0 +1,5 @@
=== PI narrative flag defaults ===
docker-compose.test.yml:128: PI_CHANGE_REPORT_NARRATIVE: ${PI_CHANGE_REPORT_NARRATIVE:-false}
docker-compose.main.yml:131: PI_CHANGE_REPORT_NARRATIVE: ${PI_CHANGE_REPORT_NARRATIVE:-false}
docker-compose.dev.yml:131: PI_CHANGE_REPORT_NARRATIVE: ${PI_CHANGE_REPORT_NARRATIVE:-false}
.env.example:117:PI_CHANGE_REPORT_NARRATIVE=false
@@ -0,0 +1,50 @@
=== PI narrative ===
=== RUN TestMaterializeAgentOutput_FlagOnFetcherNarrativeComplete
--- PASS: TestMaterializeAgentOutput_FlagOnFetcherNarrativeComplete (0.00s)
=== RUN TestPIChangeReportNarrativeFlagDefaultOff
--- PASS: TestPIChangeReportNarrativeFlagDefaultOff (0.00s)
=== RUN TestResolveChangeReport_FlagOffUsesDeterministic
--- PASS: TestResolveChangeReport_FlagOffUsesDeterministic (0.00s)
=== RUN TestResolveChangeReport_FlagOnValidNarrativeKeepsServerRisk
--- PASS: TestResolveChangeReport_FlagOnValidNarrativeKeepsServerRisk (0.00s)
=== RUN TestResolveChangeReport_FlagOnMalformedFullFallback
=== RUN TestResolveChangeReport_FlagOnMalformedFullFallback/bad_json
=== RUN TestResolveChangeReport_FlagOnMalformedFullFallback/bad_schema_version
=== RUN TestResolveChangeReport_FlagOnMalformedFullFallback/wrong_schema_kind
=== RUN TestResolveChangeReport_FlagOnMalformedFullFallback/digest_mismatch
=== RUN TestResolveChangeReport_FlagOnMalformedFullFallback/from_sha_mismatch
=== RUN TestResolveChangeReport_FlagOnMalformedFullFallback/to_sha_mismatch
=== RUN TestResolveChangeReport_FlagOnMalformedFullFallback/timeout_envelope
=== RUN TestResolveChangeReport_FlagOnMalformedFullFallback/top-level_error_code
=== RUN TestResolveChangeReport_FlagOnMalformedFullFallback/partial_flag
=== RUN TestResolveChangeReport_FlagOnMalformedFullFallback/empty_summary
=== RUN TestResolveChangeReport_FlagOnMalformedFullFallback/empty_raw
--- PASS: TestResolveChangeReport_FlagOnMalformedFullFallback (0.00s)
--- PASS: TestResolveChangeReport_FlagOnMalformedFullFallback/bad_json (0.00s)
--- PASS: TestResolveChangeReport_FlagOnMalformedFullFallback/bad_schema_version (0.00s)
--- PASS: TestResolveChangeReport_FlagOnMalformedFullFallback/wrong_schema_kind (0.00s)
--- PASS: TestResolveChangeReport_FlagOnMalformedFullFallback/digest_mismatch (0.00s)
--- PASS: TestResolveChangeReport_FlagOnMalformedFullFallback/from_sha_mismatch (0.00s)
--- PASS: TestResolveChangeReport_FlagOnMalformedFullFallback/to_sha_mismatch (0.00s)
--- PASS: TestResolveChangeReport_FlagOnMalformedFullFallback/timeout_envelope (0.00s)
--- PASS: TestResolveChangeReport_FlagOnMalformedFullFallback/top-level_error_code (0.00s)
--- PASS: TestResolveChangeReport_FlagOnMalformedFullFallback/partial_flag (0.00s)
--- PASS: TestResolveChangeReport_FlagOnMalformedFullFallback/empty_summary (0.00s)
--- PASS: TestResolveChangeReport_FlagOnMalformedFullFallback/empty_raw (0.00s)
=== RUN TestResolveChangeReport_AuthorizedFlipPathKeepsFlagOffDefault
--- PASS: TestResolveChangeReport_AuthorizedFlipPathKeepsFlagOffDefault (0.00s)
=== RUN TestCreateAgentCandidate_FlagOffIgnoresNarrativeEnrichment
--- PASS: TestCreateAgentCandidate_FlagOffIgnoresNarrativeEnrichment (0.00s)
=== RUN TestCreateAgentCandidate_FlagOnTimeoutFallsBackWithoutError
--- PASS: TestCreateAgentCandidate_FlagOnTimeoutFallsBackWithoutError (0.00s)
=== RUN TestCreateAgentCandidate_FlagOnFetcherApplies
--- PASS: TestCreateAgentCandidate_FlagOnFetcherApplies (0.00s)
=== RUN TestCreateAgentCandidate_FlagOnFetcherErrorFallsBack
--- PASS: TestCreateAgentCandidate_FlagOnFetcherErrorFallsBack (0.00s)
=== RUN TestCreateAgentCandidate_FlagOnEmptyFetcherBodyFallsBack
--- PASS: TestCreateAgentCandidate_FlagOnEmptyFetcherBodyFallsBack (0.00s)
=== RUN TestNarrativeModelInput_IncludesClippedChangeText
--- PASS: TestNarrativeModelInput_IncludesClippedChangeText (0.00s)
PASS
ok chat-agent-system/internal/docrevision 0.350s
EXIT:0
@@ -0,0 +1,29 @@
=== SWBOOT L1 unit ===
=== RUN TestBuildCreationPreview_RejectsControlledPatchAndSoloPrivacy
--- PASS: TestBuildCreationPreview_RejectsControlledPatchAndSoloPrivacy (0.00s)
=== RUN TestRepoAllowlistStub
--- PASS: TestRepoAllowlistStub (0.00s)
PASS
ok chat-agent-system/internal/swboot 0.290s
=== RUN TestOfficialWorkflowKeys
--- PASS: TestOfficialWorkflowKeys (0.00s)
=== RUN TestCompatibleWithProductDomain
--- PASS: TestCompatibleWithProductDomain (0.00s)
=== RUN TestValidateDefinitionV2_SoftwareManifests
--- PASS: TestValidateDefinitionV2_SoftwareManifests (0.00s)
PASS
ok chat-agent-system/internal/workflowdef 0.383s
=== RUN TestCreateSoftwareProject_NoMedicalDefaults
--- PASS: TestCreateSoftwareProject_NoMedicalDefaults (0.00s)
PASS
ok chat-agent-system/internal/projectworkflow 0.600s
=== RUN TestPiSoftwareBootstrapAdvisor_NoTools
--- PASS: TestPiSoftwareBootstrapAdvisor_NoTools (0.00s)
=== RUN TestPiSoftwareBootstrapAdvisor_RejectsControlledPatch
--- PASS: TestPiSoftwareBootstrapAdvisor_RejectsControlledPatch (0.00s)
=== RUN TestPiSoftwareBootstrapAdvisor_LiveMiniMax
pi_swboot_test.go:55: set RUN_PI_E2E=1 for live PI software bootstrap
--- SKIP: TestPiSoftwareBootstrapAdvisor_LiveMiniMax (0.00s)
PASS
ok chat-agent-system/internal/app 0.722s
EXIT:0
@@ -0,0 +1,82 @@
=== SWBOOT L2 short (authz/create/reject) ===
=== RUN TestPG_GoverningAccess_CompletedAndTerminatedDeny
access_governing_pg_test.go:157: TEST_DATABASE_URL not set; skipping PostgreSQL integration test
--- SKIP: TestPG_GoverningAccess_CompletedAndTerminatedDeny (0.00s)
=== RUN TestPG_GoverningAccess_QueryErrorFailClosed
--- PASS: TestPG_GoverningAccess_QueryErrorFailClosed (0.00s)
=== RUN TestPG_ActivateOfficialWorkflowRuntimeMetadata
activation_official_pg_test.go:18: TEST_DATABASE_URL not set; skipping PostgreSQL integration test
--- SKIP: TestPG_ActivateOfficialWorkflowRuntimeMetadata (0.00s)
=== RUN TestPG_ActivationAndStageAdvance
pg_integration_test.go:49: TEST_DATABASE_URL not set; skipping PostgreSQL integration test
--- SKIP: TestPG_ActivationAndStageAdvance (0.00s)
=== RUN TestPG_RDevActivateWithNAClose
rdev_pg_test.go:15: TEST_DATABASE_URL not set; skipping PostgreSQL integration test
--- SKIP: TestPG_RDevActivateWithNAClose (0.00s)
=== RUN TestActiveWorkflowScope_NoLiveInstanceReturnsEmpty
--- PASS: TestActiveWorkflowScope_NoLiveInstanceReturnsEmpty (0.00s)
=== RUN TestTaskKeyInScope_EmptyAllowsAll
--- PASS: TestTaskKeyInScope_EmptyAllowsAll (0.00s)
=== RUN TestTaskKeyInScope_CanonicalAlias
--- PASS: TestTaskKeyInScope_CanonicalAlias (0.00s)
=== CONT TestClassifyDocDuty
=== RUN TestClassifyDocDuty/all_zero
=== CONT TestNormalizeAuthorOnlyDuties
--- PASS: TestNormalizeAuthorOnlyDuties (0.00s)
=== PAUSE TestClassifyDocDuty/all_zero
=== RUN TestClassifyDocDuty/negative
=== PAUSE TestClassifyDocDuty/negative
=== RUN TestClassifyDocDuty/one_role
=== PAUSE TestClassifyDocDuty/one_role
=== RUN TestClassifyDocDuty/two_roles
=== PAUSE TestClassifyDocDuty/two_roles
=== RUN TestClassifyDocDuty/full_distinct
=== PAUSE TestClassifyDocDuty/full_distinct
=== RUN TestClassifyDocDuty/full_duplicate
=== PAUSE TestClassifyDocDuty/full_duplicate
=== CONT TestClassifyDocDuty/all_zero
=== CONT TestResolveDocTaskDuty
--- PASS: TestResolveDocTaskDuty (0.00s)
=== CONT TestCountFullDutyDocs
--- PASS: TestCountFullDutyDocs (0.00s)
=== CONT TestValidateDocTaskDuties
--- PASS: TestValidateDocTaskDuties (0.00s)
=== CONT TestNormalizeDocTaskDuties
--- PASS: TestNormalizeDocTaskDuties (0.00s)
=== CONT TestDocTaskRequiresDuty
--- PASS: TestDocTaskRequiresDuty (0.00s)
=== CONT TestEvaluateStageReadiness_AuthorOnlyIsManaged
--- PASS: TestEvaluateStageReadiness_AuthorOnlyIsManaged (0.00s)
=== CONT TestComputePlanHash_LegacySnapshotStableWithoutDutyPolicy
--- PASS: TestComputePlanHash_LegacySnapshotStableWithoutDutyPolicy (0.00s)
=== CONT TestEvaluateStageReadiness_DutyModes
--- PASS: TestEvaluateStageReadiness_DutyModes (0.00s)
=== CONT TestValidateAuthorOnlyDocTaskDuties
=== CONT TestClassifyDocDuty/two_roles
--- PASS: TestValidateAuthorOnlyDocTaskDuties (0.00s)
=== CONT TestClassifyDocDuty/one_role
=== CONT TestClassifyDocDuty/negative
=== CONT TestClassifyDocDuty/full_duplicate
=== CONT TestClassifyDocDuty/full_distinct
--- PASS: TestClassifyDocDuty (0.00s)
--- PASS: TestClassifyDocDuty/all_zero (0.00s)
--- PASS: TestClassifyDocDuty/two_roles (0.00s)
--- PASS: TestClassifyDocDuty/negative (0.00s)
--- PASS: TestClassifyDocDuty/one_role (0.00s)
--- PASS: TestClassifyDocDuty/full_duplicate (0.00s)
--- PASS: TestClassifyDocDuty/full_distinct (0.00s)
PASS
ok chat-agent-system/internal/projectinstance 0.547s
=== RUN TestProjectTypesCatalog
--- PASS: TestProjectTypesCatalog (0.00s)
=== RUN TestBuildCreationPreview_SelfBootstrapSolo
--- PASS: TestBuildCreationPreview_SelfBootstrapSolo (0.00s)
=== RUN TestBuildCreationPreview_RejectsControlledPatchAndSoloPrivacy
--- PASS: TestBuildCreationPreview_RejectsControlledPatchAndSoloPrivacy (0.00s)
=== RUN TestRepoAllowlistStub
--- PASS: TestRepoAllowlistStub (0.00s)
=== RUN TestSuggestDeliveryAfterBootstrapNeverActivates
--- PASS: TestSuggestDeliveryAfterBootstrapNeverActivates (0.00s)
PASS
ok chat-agent-system/internal/swboot 0.796s
EXIT:0
@@ -0,0 +1,11 @@
=== SWBOOT L4 stub advisor ===
=== RUN TestPiSoftwareBootstrapAdvisor_NoTools
--- PASS: TestPiSoftwareBootstrapAdvisor_NoTools (0.00s)
=== RUN TestPiSoftwareBootstrapAdvisor_RejectsControlledPatch
--- PASS: TestPiSoftwareBootstrapAdvisor_RejectsControlledPatch (0.00s)
=== RUN TestPiSoftwareBootstrapAdvisor_LiveMiniMax
pi_swboot_test.go:55: set RUN_PI_E2E=1 for live PI software bootstrap
--- SKIP: TestPiSoftwareBootstrapAdvisor_LiveMiniMax (0.00s)
PASS
ok chat-agent-system/internal/app 0.309s
EXIT:0
+78 -1
View File
@@ -123,16 +123,22 @@ func TestResolveChangeReport_FlagOnMalformedFullFallback(t *testing.T) {
t.Cleanup(docrevision.ResetPIChangeReportNarrativeForTest)
fromSHA, toSHA, ruleRisk, impacts, diff := sampleDiff(t)
digest := docrevision.NarrativeInputDigest(fromSHA, toSHA, ruleRisk, impacts, diff.Hunks)
cases := []struct {
name string
raw string
}{
{"bad json", `{not-json`},
{"bad schema version", `{"schema_version":99,"schema_kind":"change_report_narrative_v1","input_digest":"x","summary":["a"]}`},
{"wrong schema kind", `{"schema_version":1,"schema_kind":"other_v1","input_digest":"` + digest + `","from_sha":"` + fromSHA + `","to_sha":"` + toSHA + `","summary":["PI only"]}`},
{"digest mismatch", mustNarrativeJSON(t, fromSHA, toSHA, "deadbeef", []string{"ok"})},
{"from_sha mismatch", mustNarrativeJSONWithSHAs(t, "wrong-from", toSHA, digest, []string{"PI only"})},
{"to_sha mismatch", mustNarrativeJSONWithSHAs(t, fromSHA, "wrong-to", digest, []string{"PI only"})},
{"timeout envelope", `{"result_v1":{"outcome":"error","error_code":"TIMEOUT"},"narrative":{"schema_version":1,"schema_kind":"change_report_narrative_v1"}}`},
{"top-level error_code", `{"schema_version":1,"schema_kind":"change_report_narrative_v1","input_digest":"` + digest + `","from_sha":"` + fromSHA + `","to_sha":"` + toSHA + `","error_code":"RUNNER_FAILED","summary":["PI only"]}`},
{"partial flag", `{"schema_version":1,"schema_kind":"change_report_narrative_v1","input_digest":"x","partial":true,"summary":["a"]}`},
{"empty summary", mustNarrativeJSON(t, fromSHA, toSHA, docrevision.NarrativeInputDigest(fromSHA, toSHA, ruleRisk, impacts, diff.Hunks), nil)},
{"empty summary", mustNarrativeJSON(t, fromSHA, toSHA, digest, nil)},
{"empty raw", ""},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
@@ -154,11 +160,19 @@ func TestResolveChangeReport_FlagOnMalformedFullFallback(t *testing.T) {
t.Fatalf("mixed narrative field leaked: %q", s)
}
}
if rep.DeterministicFacts["narrative_applied"] == true {
t.Fatal("fallback must not claim narrative_applied")
}
})
}
}
func mustNarrativeJSON(t *testing.T, fromSHA, toSHA, digest string, summary []string) string {
t.Helper()
return mustNarrativeJSONWithSHAs(t, fromSHA, toSHA, digest, summary)
}
func mustNarrativeJSONWithSHAs(t *testing.T, fromSHA, toSHA, digest string, summary []string) string {
t.Helper()
raw, err := json.Marshal(map[string]any{
"schema_version": 1,
@@ -174,6 +188,43 @@ func mustNarrativeJSON(t *testing.T, fromSHA, toSHA, digest string, summary []st
return string(raw)
}
// TestResolveChangeReport_AuthorizedFlipPathKeepsFlagOffDefault documents the
// pre-authorization contract: default remains off; when a future owner flips the
// flag for a controlled window, malformed PI output still full-falls-back.
func TestResolveChangeReport_AuthorizedFlipPathKeepsFlagOffDefault(t *testing.T) {
docrevision.ResetPIChangeReportNarrativeForTest()
t.Cleanup(docrevision.ResetPIChangeReportNarrativeForTest)
if docrevision.PIChangeReportNarrativeEnabled() {
t.Fatal("PI_CHANGE_REPORT_NARRATIVE must remain off until separately authorized")
}
fromSHA, toSHA, ruleRisk, impacts, diff := sampleDiff(t)
digest := docrevision.NarrativeInputDigest(fromSHA, toSHA, ruleRisk, impacts, diff.Hunks)
piRaw := mustNarrativeJSON(t, fromSHA, toSHA, digest, []string{"should stay ignored while off"})
offRep, err := docrevision.ResolveChangeReport(piRaw, fromSHA, toSHA, ruleRisk, impacts, diff)
if err != nil {
t.Fatal(err)
}
if offRep.AnalysisStatus != docrevision.AnalysisFallback {
t.Fatalf("default-off must ignore PI narrative, got %s", offRep.AnalysisStatus)
}
docrevision.SetPIChangeReportNarrativeForTest(true)
onBad, err := docrevision.ResolveChangeReport(`{"schema_version":1,"schema_kind":"change_report_narrative_v1"}`, fromSHA, toSHA, ruleRisk, impacts, diff)
if err != nil {
t.Fatal(err)
}
if onBad.AnalysisStatus != docrevision.AnalysisFallback {
t.Fatalf("authorized-on malformed must still fallback, got %s", onBad.AnalysisStatus)
}
docrevision.SetPIChangeReportNarrativeForTest(false)
if docrevision.PIChangeReportNarrativeEnabled() {
t.Fatal("test toggle must restore off before exit")
}
}
func TestCreateAgentCandidate_FlagOffIgnoresNarrativeEnrichment(t *testing.T) {
docrevision.SetPIChangeReportNarrativeForTest(false)
t.Cleanup(docrevision.ResetPIChangeReportNarrativeForTest)
@@ -289,6 +340,32 @@ func TestCreateAgentCandidate_FlagOnFetcherErrorFallsBack(t *testing.T) {
}
}
func TestCreateAgentCandidate_FlagOnEmptyFetcherBodyFallsBack(t *testing.T) {
docrevision.SetPIChangeReportNarrativeForTest(true)
t.Cleanup(docrevision.ResetPIChangeReportNarrativeForTest)
svc := docrevision.NewService(nil)
svc.NarrativeFetcher = func(_ context.Context, _ docrevision.NarrativeFetchSpec) (string, error) {
return " ", nil
}
ctx := context.Background()
st, err := svc.OpenTemplateVersion(ctx, "doc_flag_on_empty", "# base\n\nbody\n", docrevision.ActorSnapshot{DisplayName: "t"}, 1, "")
if err != nil {
t.Fatal(err)
}
cand, rep, err := svc.CreateAgentCandidate(ctx, docrevision.AgentCandidateRequest{
DocID: "doc_flag_on_empty", RunID: "r-empty", InputSnapshotID: st.HeadSnapshotID,
OutputMarkdown: "# base\n\nbody changed\n",
ActorKind: docrevision.ActorAgent,
})
if err != nil {
t.Fatalf("empty fetcher body must full-fallback without error, got %v", err)
}
if cand == nil || rep == nil || rep.AnalysisStatus != docrevision.AnalysisFallback {
t.Fatalf("want fallback candidate, got cand=%v rep=%+v", cand, rep)
}
}
func TestNarrativeModelInput_IncludesClippedChangeText(t *testing.T) {
long := strings.Repeat("甲", 280)
spec := docrevision.NarrativeFetchSpec{