merge: feature/w6-fav-subscriber-shadow

This commit is contained in:
weichen
2026-09-17 04:26:18 +00:00
14 changed files with 363 additions and 9 deletions
@@ -2,7 +2,7 @@
title: "FAV U7 incremental — remaining port inventory" title: "FAV U7 incremental — remaining port inventory"
status: open status: open
created: 2026-09-16 created: 2026-09-16
updated: 2026-09-16 updated: 2026-09-17
--- ---
# FAV U7 增量:未移植清单 # FAV U7 增量:未移植清单
@@ -17,5 +17,8 @@ updated: 2026-09-16
| ingest hooks | `internal/server/vault.go` OnFinalArtifact* | 会写 staging;需与 publisher 门闩同批审批 | | ingest hooks | `internal/server/vault.go` OnFinalArtifact* | 会写 staging;需与 publisher 门闩同批审批 |
| schema JSON | `*.schema.json` | 可选文档资产 | | schema JSON | `*.schema.json` | 可选文档资产 |
| durable Register(fav) | `cmd/events` | events 硬门 + 单独 cutover 审批 | | durable Register(fav) | `cmd/events` | events 硬门 + 单独 cutover 审批 |
| live staging → artifact → publication | publisher Drain / ingest hooks | 每级观察窗 ≥7 天;本轮 **未** 放量 |
**W6 已移植(仍非 live)**:`internal/app/fav.EventConsumer.ObserveShadow` 只计算 intended outcome(`WouldStage`/`WouldPublish` 恒 false);`FAV_DEGRADED_READ` 为 true 时 shadow 直接拒绝;`HandleLease` 与 `Registry.Register(fav)` 仍 fail-closed。
**红线保持**:`FAV_DEGRADED_READ=false`;fav consumer mode=off;禁止整支 merge。 **红线保持**:`FAV_DEGRADED_READ=false`;fav consumer mode=off;禁止整支 merge。
+3 -3
View File
@@ -5,7 +5,7 @@
## 焦点 ## 焦点
1. **partial/deferred 全量收口**:W1–W4 **done**;W5a **done**(矩阵自举行仍 partial);续 W6–W7 1. **partial/deferred 全量收口**:W1–W4 **done**;W5a **done**(矩阵自举行仍 partial);W6 **shadow done**(矩阵 FAV 仍 partial,live 未放量);续 W7
2. **运维**:nightly `DATA_DIR=dev` 已对齐;KB 宿主机分层加载 / fail-closed **done**(`fix/kb-host-auth-layered-load`) 2. **运维**:nightly `DATA_DIR=dev` 已对齐;KB 宿主机分层加载 / fail-closed **done**(`fix/kb-host-auth-layered-load`)
3. **红线**:禁止整支 merge FAV;不默认升 `MULTI_INTENT_PLAN_TIER=full`(升 `confirm` 须 W4 闭环后独立授权);`SEMANTIC_SUMMARY_REPLACE` 不随 W2 开启;FAV live 顺序 staging→artifact→publication、每级观察窗证据驱动(下限 ≥7 天)、`FAV_DEGRADED_READ=false`;flag 默认 off;项目 143 只读 3. **红线**:禁止整支 merge FAV;不默认升 `MULTI_INTENT_PLAN_TIER=full`(升 `confirm` 须 W4 闭环后独立授权);`SEMANTIC_SUMMARY_REPLACE` 不随 W2 开启;FAV live 顺序 staging→artifact→publication、每级观察窗证据驱动(下限 ≥7 天)、`FAV_DEGRADED_READ=false`;flag 默认 off;项目 143 只读
@@ -18,7 +18,7 @@
| Cursor | 2026-09-17-w3-pi-change-report-narrative | feature/w3-pi-change-report-narrative-live | - | docgov | done | | Cursor | 2026-09-17-w3-pi-change-report-narrative | feature/w3-pi-change-report-narrative-live | - | docgov | done |
| Cursor | 2026-09-17-w4-multi-intent-u9-u12 | feature/w4-u9-segment-live | - | intent-plan | done | | Cursor | 2026-09-17-w4-multi-intent-u9-u12 | feature/w4-u9-segment-live | - | intent-plan | done |
| Cursor | 2026-09-17-w5a-software-bootstrap-ui | feature/w5a-software-bootstrap-ui-pi | - | workflow-design | done | | Cursor | 2026-09-17-w5a-software-bootstrap-ui | feature/w5a-software-bootstrap-ui-pi | - | workflow-design | done |
| Cursor | 2026-09-17-w6-fav-subscriber-shadow | feature/w6-fav-subscriber-shadow | - | events | planned | | Cursor | 2026-09-17-w6-fav-subscriber-shadow | feature/w6-fav-subscriber-shadow | - | events | done |
| Cursor | 2026-09-17-w7-d7-inventory | feature/w7-d7-data-inventory | - | chat | planned | | Cursor | 2026-09-17-w7-d7-inventory | feature/w7-d7-data-inventory | - | chat | planned |
| Cursor | 2026-09-17-kb-host-auth-fix | fix/kb-host-auth-layered-load | - | infra | done | | Cursor | 2026-09-17-kb-host-auth-fix | fix/kb-host-auth-layered-load | - | infra | done |
| Cursor | 2026-09-17-w1-ci-e2e-green-nightly | feature/w1-ci-e2e-green-nightly | - | ci | done | | Cursor | 2026-09-17-w1-ci-e2e-green-nightly | feature/w1-ci-e2e-green-nightly | - | ci | done |
@@ -118,7 +118,7 @@
| holder | phase | since | scope | queue | | holder | phase | since | scope | queue |
|--------|-------|-------|-------|-------| |--------|-------|-------|-------|-------|
| - | idle | - | - | W6–W7 | | - | idle | - | - | W7 |
**phase**: `idle` | `merging` | `smoke` | `e2e` **phase**: `idle` | `merging` | `smoke` | `e2e`
@@ -27,7 +27,7 @@ mutex: architecture-docs
3. [x] W3 PU7 PI change-report 叙事(mutex `docgov`;开 flag + 真实 PI + fallback 负例 + UI 区分) 3. [x] W3 PU7 PI change-report 叙事(mutex `docgov`;开 flag + 真实 PI + fallback 负例 + UI 区分)
4. [x] W4 PU8 U9–U12(mutex `intent-plan`;逐 action 独立 flag,U9→U12 顺序;升 confirm 另开独立授权,不升 full) 4. [x] W4 PU8 U9–U12(mutex `intent-plan`;逐 action 独立 flag,U9→U12 顺序;升 confirm 另开独立授权,不升 full)
5. [ ] W5 纯软件自举(mutex `workflow-design`;SWBOOT-10 非 stub + 真实 PI + UI 二次验收) 5. [ ] W5 纯软件自举(mutex `workflow-design`;SWBOOT-10 非 stub + 真实 PI + UI 二次验收)
6. [ ] W6 FAV(mutex `events`;subscriber 审计 → shadow → live staging→artifact→publication;每级观察窗 ≥7 天;`FAV_DEGRADED_READ=false`;禁止整支 merge) 6. [ ] W6 FAV(mutex `events`;**shadow 已合** → live staging→artifact→publication 仍未开;每级观察窗 ≥7 天;`FAV_DEGRADED_READ=false`;禁止整支 merge)
7. [ ] W7 D7 数据治理(mutex `chat`,排在 W2 后;盘点 → 按 ADR 口径整改;合规签字前数字 `pending-compliance-signoff`;违规升 gap-matrix) 7. [ ] W7 D7 数据治理(mutex `chat`,排在 W2 后;盘点 → 按 ADR 口径整改;合规签字前数字 `pending-compliance-signoff`;违规升 gap-matrix)
8. [ ] 逐行升 done(保留 lineage)+ CURRENT / 契约状态同步 8. [ ] 逐行升 done(保留 lineage)+ CURRENT / 契约状态同步
9. [x] §7 四项开放问题决策落盘(2026-09-17) 9. [x] §7 四项开放问题决策落盘(2026-09-17)
@@ -0,0 +1,51 @@
---
title: "W6 FAV subscriber shadow"
status: done
created: 2026-09-17
updated: 2026-09-17
owner: Cursor
branch: feature/w6-fav-subscriber-shadow
worktree: "-"
mutex: events
---
# W6 FAV subscriber shadow
## 目标
手工移植 FAV subscriber **shadow**:只计算 intended outcome,不写业务副作用;`FAV_DEGRADED_READ=false`;`FAV_PUBLISHER_ENABLED` 默认 false。矩阵保持 `partial`。
## 范围
- `ObserveShadow` 只读计算
- vault status / CLI 标明 shadow 与 live 未放量
- 负例:degraded read 拒绝;HandleLease / Register(fav) 仍 fail-closed
## 非目标
- 整支 merge `feature/final-artifact-vault`
- live staging → artifact → publication
- 启用 `FAV_DEGRADED_READ`
- 升 FEATURE-MATRIX FAV 为 `done`
## 步骤
1. [x] `ObserveShadow` + 负例单测
2. [x] 冒烟 + 报告(live 未放量)
3. [x] 合 `dev`;矩阵保持 partial
## 验收标准
- [x] WouldStage / WouldPublish 恒 false
- [x] FAV_DEGRADED_READ=true 时 shadow 拒绝
- [x] 报告写清 shadow 证据与「live 未放量」
## 相关链接
- Remaining port: [../../audit/2026-09-16-fav-u7-remaining-port.md](../../audit/2026-09-16-fav-u7-remaining-port.md)
- U7 报告: [../../test-reports/2026-09-16-fav-u7-incremental-test-report.md](../../test-reports/2026-09-16-fav-u7-incremental-test-report.md)
- Test report: [../../test-reports/2026-09-17-w6-fav-subscriber-shadow-test-report.md](../../test-reports/2026-09-17-w6-fav-subscriber-shadow-test-report.md)
## 证据 / 备注
不占 live 观察窗;≥7 天窗口仍属后续 live 切片。
+1 -1
View File
@@ -66,7 +66,7 @@
| CI / E2E | PR CI、PG 集成、治理 PG、已签真实 PI 门禁;**nightly** schedule/`workflow_dispatch` + infra/product 分类 receipt | done | 加固(阻止**下一次**晋升,不自动否定已发布) | [BUG-08](../test-reports/2026-09-16-ci-governance-pg-bug08-test-report.md);[PI 三工作流](../test-reports/2026-09-16-pi-three-workflow-e2e-test-report.md);[Wave2B](../test-reports/2026-09-16-wave2b-nightly-promotion-receipt-test-report.md);[Wave2C](../test-reports/2026-09-16-wave2c-same-image-digest-test-report.md);[W1 green](../test-reports/2026-09-17-w1-ci-e2e-green-nightly-test-report.md) + [receipt `6e2476f0…`](../assets/promotion-receipts/6e2476f0f2e408e111fe06ca7c31a38294a340b3.json) | **before**:partial(FullNone product_fail / secrets 文案过时)→ **after**:secrets 已配;duplicate goose 00048 已清;FullNone+U8 nightly `verdict=pass` | `ci` | | CI / E2E | PR CI、PG 集成、治理 PG、已签真实 PI 门禁;**nightly** schedule/`workflow_dispatch` + infra/product 分类 receipt | done | 加固(阻止**下一次**晋升,不自动否定已发布) | [BUG-08](../test-reports/2026-09-16-ci-governance-pg-bug08-test-report.md);[PI 三工作流](../test-reports/2026-09-16-pi-three-workflow-e2e-test-report.md);[Wave2B](../test-reports/2026-09-16-wave2b-nightly-promotion-receipt-test-report.md);[Wave2C](../test-reports/2026-09-16-wave2c-same-image-digest-test-report.md);[W1 green](../test-reports/2026-09-17-w1-ci-e2e-green-nightly-test-report.md) + [receipt `6e2476f0…`](../assets/promotion-receipts/6e2476f0f2e408e111fe06ca7c31a38294a340b3.json) | **before**:partial(FullNone product_fail / secrets 文案过时)→ **after**:secrets 已配;duplicate goose 00048 已清;FullNone+U8 nightly `verdict=pass` | `ci` |
| Artifact 同 digest 晋升 | 候选 commit 一次构建、按 digest 晋升、promotion receipt | done | 加固 | [计划 PU5B](../remediation/2026-09-16-feature-matrix-production-readiness-plan.md);[Wave2B](../test-reports/2026-09-16-wave2b-nightly-promotion-receipt-test-report.md);[Wave2C](../test-reports/2026-09-16-wave2c-same-image-digest-test-report.md) | **关闭**:same-image pin(`candidate-images-*` + `PINNED_IMAGE_MODE` + promote `--require-digests`)。**声明**:单机 retag/digest pin;远程 registry 推送另立任务 | `infra` | | Artifact 同 digest 晋升 | 候选 commit 一次构建、按 digest 晋升、promotion receipt | done | 加固 | [计划 PU5B](../remediation/2026-09-16-feature-matrix-production-readiness-plan.md);[Wave2B](../test-reports/2026-09-16-wave2b-nightly-promotion-receipt-test-report.md);[Wave2C](../test-reports/2026-09-16-wave2c-same-image-digest-test-report.md) | **关闭**:same-image pin(`candidate-images-*` + `PINNED_IMAGE_MODE` + promote `--require-digests`)。**声明**:单机 retag/digest pin;远程 registry 推送另立任务 | `infra` |
| 可靠事件核心 | PG dispatcher、notify/advance/projection live、生产闭包 hook 退出、Phase2 选择性解耦已合入 | done | non_blocker | [E0A–E2](../test-reports/2026-09-15-event-e0a-production-scope-contract-test-report.md) 等;[复盘](../ops/research/2026-09-15-track-e-retrospective.md);[shared live](../test-reports/2026-09-15-event-notify-shared-live-test-report.md);[phase2 closeout](../test-reports/2026-09-15-phase2-closeout-test-report.md) | **before**:与 FAV off 混成 partial → **after**:FAV 独立行。选择性后续解耦按收益触发 | `architecture-docs` / `events` | | 可靠事件核心 | PG dispatcher、notify/advance/projection live、生产闭包 hook 退出、Phase2 选择性解耦已合入 | done | non_blocker | [E0A–E2](../test-reports/2026-09-15-event-e0a-production-scope-contract-test-report.md) 等;[复盘](../ops/research/2026-09-15-track-e-retrospective.md);[shared live](../test-reports/2026-09-15-event-notify-shared-live-test-report.md);[phase2 closeout](../test-reports/2026-09-15-phase2-closeout-test-report.md) | **before**:与 FAV off 混成 partial → **after**:FAV 独立行。选择性后续解耦按收益触发 | `architecture-docs` / `events` |
| Final Artifact Vault(FAV) | 不可变终态制品库;`FAV_DEGRADED_READ=false` | partial | deferred_enhancement | [Track E 复盘](../ops/research/2026-09-15-track-e-retrospective.md);[U7 报告](../test-reports/2026-09-16-fav-u7-incremental-test-report.md);[short-term](../ops/short-term/2026-09-16-fav-u7-incremental.md) | U7 增量已合 `dev`(publisher/consumer 默认 off);仍**禁止**整支 merge;`FAV_DEGRADED_READ` 默认 false | `events` | | Final Artifact Vault(FAV) | 不可变终态制品库;`FAV_DEGRADED_READ=false` | partial | deferred_enhancement | [Track E 复盘](../ops/research/2026-09-15-track-e-retrospective.md);[U7 报告](../test-reports/2026-09-16-fav-u7-incremental-test-report.md);[W6 shadow](../test-reports/2026-09-17-w6-fav-subscriber-shadow-test-report.md);[short-term](../ops/short-term/2026-09-17-w6-fav-subscriber-shadow.md) | **after W6**:subscriber `ObserveShadow` 只计算 intended outcome,不写副作用。仍**禁止**整支 merge;`FAV_DEGRADED_READ` 默认 false;**live 未放量**(staging→artifact→publication 各 ≥7 天观察窗) | `events` |
--- ---
@@ -0,0 +1,92 @@
# W6 FAV subscriber shadow 测试报告
## 一、 测试背景 (Background)
FAV 禁止整支 merge `feature/final-artifact-vault`。U7 增量已合 `dev`(publisher/consumer 默认 off)。W6 只手工移植 subscriber **shadow**:计算 intended outcome,不写 staging/artifact/publication,不开启 `FAV_DEGRADED_READ`。live 观察窗 ≥7 天,本轮不开。
## 二、 测试目标 (Test Objectives)
- `ObserveShadow` 只读:`WouldStage`/`WouldPublish`/`DegradedReadUsed` 恒 false
- `FAV_DEGRADED_READ=true`(结构字段或进程 env)时 shadow 拒绝
- `HandleLease` 与 `Registry.Register(fav)` 仍 fail-closed
- vault status / `artifact-vault status` 标明 `shadow_observe` 与 live 未放量
- 仓库默认 `FAV_PUBLISHER_ENABLED=false`、`FAV_DEGRADED_READ=false`
- FEATURE-MATRIX FAV **保持 partial**
## 三、 测试方案与环境 (Test Plan & Environment)
| 项 | 值 |
|----|----|
| OS | Linux 7.0.0-31-generic |
| Go | go1.27.1 linux/amd64 |
| 分支 | `feature/w6-fav-subscriber-shadow`(自 `dev@49d901e`) |
| 层级 | 单元测试 + CLI status;无真实 PI(本波无 PI 路径) |
| 证据 | `.doc/test-reports/assets/2026-09-17-w6-fav-subscriber-shadow/` |
核心用例矩阵:
| ID | 用例 | 期望 |
|----|------|------|
| S1 | 零值 consumer | DefaultEnabled=false;HandleLease → ErrDisabledConsumer |
| S2 | Enabled=true 仍无 registry 审批 | HandleLease 拒绝;Register(fav) 失败 |
| S3 | ObserveShadow | logical=`document:doc_1`;不声明 writes |
| S4 | AllowDegradedRead / env true | shadow error;不写 vault |
| S5 | GET vault/status | consumer_mode=off;would_stage/publish=false;live_not_opened=true |
| S6 | artifact-vault status | remaining_to_port 含 live 观察窗;shadow_observe=true |
## 四、 预期结果 (Expected Results)
- 所有默认路径 FAIL-CLOSED:不 Drain、不 Stage、不 Publish、不 claim fav lease
- shadow 不是写回执
- live staging→artifact→publication **NOT OPENED**
## 五、 实际测试结果与判定 (Actual Results & Evaluation)
### 5.1 S1–S4 fav subscriber
- **测试输入 (Input)**:`go test -count=1 ./internal/app/fav -run TestFavSubscriber -v`
- **实际输出 (Actual Output)**:三用例全部 PASS。见 `assets/2026-09-17-w6-fav-subscriber-shadow/unit-tests.txt`
- **评价判定 (Evaluation / Verdict)**:PASS
- **性能与耗时 (Duration & Latency)**:0.002s
### 5.2 S5 vault status
- **测试输入 (Input)**:`go test ./internal/server -run 'TestVaultDefaults|TestVaultStatus'`
- **实际输出 (Actual Output)**:`consumer_mode=off`、`shadow_observe=true`、`would_stage=false`、`would_publish=false`、`live_not_opened=true`
- **评价判定 (Evaluation / Verdict)**:PASS
- **性能与耗时 (Duration & Latency)**:0.007s
### 5.3 S6 CLI stub
- **测试输入 (Input)**:`go build ./cmd/artifact-vault && artifact-vault status`
- **实际输出 (Actual Output)**:见 `artifact-vault-status.json`。`consumer_mode=off`、`degraded_read=false`、`shadow_observe=true`、`would_stage=false`、`would_publish=false`;`remaining_to_port` 含 durable Register 与 live 三级观察窗。
- **评价判定 (Evaluation / Verdict)**:PASS
- **性能与耗时 (Duration & Latency)**:<1s
### 5.4 冒烟
- **测试输入 (Input)**:`go vet ./...`;`unset TEST_DATABASE_URL; go test -short ./...`;`./scripts/arch-check.sh --mode block`
- **实际输出 (Actual Output)**:`VET_EXIT=0`、`TEST_EXIT=0`、`arch-check block: production gate passed`。见 `smoke.txt`
- **评价判定 (Evaluation / Verdict)**:PASS
- **性能与耗时 (Duration & Latency)**:约 14s(short tests;`internal/server` 10.993s)
### 5.5 live 放量
- **测试输入 (Input)**:compose 默认 `FAV_DEGRADED_READ` / `FAV_PUBLISHER_ENABLED`
- **实际输出 (Actual Output)**:docker-compose.dev.yml 仍 `${FAV_DEGRADED_READ:-false}` / `${FAV_PUBLISHER_ENABLED:-false}`;未改仓库默认;未开 staging Drain。
- **评价判定 (Evaluation / Verdict)**:PASS(**live 未放量**,符合本波边界)
- **性能与耗时 (Duration & Latency)**:N/A
## 六、 自动化测试套件汇总 (Test Suite Execution)
```text
ok internal/app/fav TestFavSubscriber* PASS
ok internal/server TestVaultDefaultsStayDisabled / TestVaultStatusReportsOff PASS
go test -short ./... TEST_EXIT=0
go vet ./... VET_EXIT=0
arch-check --mode block production gate passed
```
## 七、 测试结论与后续优化建议 (Conclusion & Next Steps)
W6 **shadow 切片 PASS**,可合 `dev`。FEATURE-MATRIX FAV **保持 partial**:live staging→artifact→publication 未开,观察窗 ≥7 天未开始计时。禁止整支 merge FAV;`FAV_DEGRADED_READ` 保持 false。durable `Register(fav)` 仍须单独 cutover 审批。
@@ -0,0 +1,21 @@
{
"binary": "artifact-vault",
"consumer": "fav",
"consumer_mode": "off",
"degraded_read": false,
"note": "do not enable FAV_DEGRADED_READ or fav consumer without approval; subscriber shadow is read-only",
"publisher_default": false,
"remaining_to_port": [
"enumerate.go (EnumerateTerminals / LoadExclusions)",
"TerminalRow + Backfill(rows) API (diverged from dataset Candidate path)",
"full recover-plan / recover-apply / gc-preflight / destroy",
"server ingest hooks (OnFinalArtifact*)",
"durable fav event Register after separate approval",
"live staging → artifact → publication (each ≥7d observation)"
],
"shadow_observe": true,
"slice": "u7-incremental-stub",
"source_branch": "feature/final-artifact-vault",
"would_publish": false,
"would_stage": false
}
@@ -0,0 +1,75 @@
=== go vet ===
VET_EXIT:0
=== go test -short ===
? chat-agent-system/cmd/artifact-vault [no test files]
ok chat-agent-system/cmd/bizdev-cli 2.318s
? chat-agent-system/cmd/dataset [no test files]
ok chat-agent-system/cmd/document-content-reconcile (cached)
? chat-agent-system/cmd/document-revision-backfill [no test files]
? chat-agent-system/cmd/eventmode [no test files]
? chat-agent-system/cmd/events [no test files]
? chat-agent-system/cmd/recategorize [no test files]
? chat-agent-system/cmd/server [no test files]
ok chat-agent-system/internal/agent 3.521s
ok chat-agent-system/internal/app 0.017s
ok chat-agent-system/internal/app/chatflow (cached)
ok chat-agent-system/internal/app/docadvance 0.002s
ok chat-agent-system/internal/app/docflow (cached)
ok chat-agent-system/internal/app/docnotify (cached)
ok chat-agent-system/internal/app/docproject (cached)
ok chat-agent-system/internal/app/fav 0.002s
ok chat-agent-system/internal/app/intentflow (cached)
ok chat-agent-system/internal/app/stageflow (cached)
ok chat-agent-system/internal/app/uow (cached)
ok chat-agent-system/internal/archtest 0.156s
ok chat-agent-system/internal/artifactvault (cached)
ok chat-agent-system/internal/auth (cached)
ok chat-agent-system/internal/authz (cached)
ok chat-agent-system/internal/bizdev 0.147s
ok chat-agent-system/internal/chat 0.012s
ok chat-agent-system/internal/config (cached)
ok chat-agent-system/internal/coordinator (cached)
ok chat-agent-system/internal/ctxmgr (cached)
ok chat-agent-system/internal/dataset (cached)
ok chat-agent-system/internal/db (cached)
ok chat-agent-system/internal/doccontent (cached)
ok chat-agent-system/internal/docduty (cached)
ok chat-agent-system/internal/docgov (cached)
ok chat-agent-system/internal/docquery (cached)
ok chat-agent-system/internal/docrevision (cached)
ok chat-agent-system/internal/events (cached)
ok chat-agent-system/internal/freedoc (cached)
ok chat-agent-system/internal/fsutil (cached)
ok chat-agent-system/internal/httpx (cached)
ok chat-agent-system/internal/intent (cached)
ok chat-agent-system/internal/kb (cached)
ok chat-agent-system/internal/maturity (cached)
ok chat-agent-system/internal/notification (cached)
? chat-agent-system/internal/observability [no test files]
ok chat-agent-system/internal/projectcontext (cached)
ok chat-agent-system/internal/projectinstance (cached)
ok chat-agent-system/internal/projectworkflow (cached)
ok chat-agent-system/internal/quote (cached)
ok chat-agent-system/internal/replyexport (cached)
ok chat-agent-system/internal/server 10.993s
ok chat-agent-system/internal/squad (cached)
ok chat-agent-system/internal/storage (cached)
ok chat-agent-system/internal/stream (cached)
ok chat-agent-system/internal/stream/transport (cached)
ok chat-agent-system/internal/swboot (cached)
ok chat-agent-system/internal/taskrun 0.194s
ok chat-agent-system/internal/template 0.999s
ok chat-agent-system/internal/upload (cached)
? chat-agent-system/internal/workflow [no test files]
ok chat-agent-system/internal/workflow/runtime (cached)
ok chat-agent-system/internal/workflowdef (cached)
ok chat-agent-system/internal/workspace (cached)
? chat-agent-system/templates [no test files]
TEST_EXIT:0
=== arch-check ===
arch-check mode=block
boundaries=/home/weichen/projects/Agent-System/.doc/system-design/module-boundaries.yaml
ok chat-agent-system/internal/archtest 0.133s
ok chat-agent-system/internal/archtest 0.144s
arch-check block: production gate passed
ARCH_EXIT:0
@@ -0,0 +1,15 @@
=== 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.002s
=== RUN TestVaultDefaultsStayDisabled
--- PASS: TestVaultDefaultsStayDisabled (0.00s)
=== RUN TestVaultStatusReportsOff
--- PASS: TestVaultStatusReportsOff (0.00s)
PASS
ok chat-agent-system/internal/server 0.007s
EXIT:0
+6 -2
View File
@@ -32,9 +32,13 @@ func main() {
"full recover-plan / recover-apply / gc-preflight / destroy", "full recover-plan / recover-apply / gc-preflight / destroy",
"server ingest hooks (OnFinalArtifact*)", "server ingest hooks (OnFinalArtifact*)",
"durable fav event Register after separate approval", "durable fav event Register after separate approval",
"live staging → artifact → publication (each ≥7d observation)",
}, },
"source_branch": "feature/final-artifact-vault", "shadow_observe": true,
"note": "do not enable FAV_DEGRADED_READ or fav consumer without approval", "would_stage": false,
"would_publish": false,
"source_branch": "feature/final-artifact-vault",
"note": "do not enable FAV_DEGRADED_READ or fav consumer without approval; subscriber shadow is read-only",
}) })
case "help", "-h", "--help": case "help", "-h", "--help":
usage() usage()
+47
View File
@@ -8,6 +8,8 @@ package fav
import ( import (
"context" "context"
"fmt" "fmt"
"os"
"strings"
"chat-agent-system/internal/artifactvault" "chat-agent-system/internal/artifactvault"
"chat-agent-system/internal/events" "chat-agent-system/internal/events"
@@ -24,6 +26,8 @@ type EventConsumer struct {
// Enabled must stay false by default. Even when true, events.Registry // Enabled must stay false by default. Even when true, events.Registry
// still rejects Register(ConsumerFAV) until the hard gate is lifted. // still rejects Register(ConsumerFAV) until the hard gate is lifted.
Enabled bool Enabled bool
// AllowDegradedRead must stay false. Shadow refuses if this is true.
AllowDegradedRead bool
} }
// HandleLease refuses work while the subscriber is disabled (default). // HandleLease refuses work while the subscriber is disabled (default).
@@ -44,3 +48,46 @@ func (c *EventConsumer) HandleLease(ctx context.Context, lease events.Lease, tx
func DefaultEnabled() bool { func DefaultEnabled() bool {
return false return false
} }
// IntendedOutcome is a read-only shadow calculation. It must never be treated
// as a write receipt; WouldStage/WouldPublish stay false in this slice.
type IntendedOutcome struct {
EventName string
AggregateType string
AggregateID string
LogicalResource string
WouldStage bool
WouldPublish bool
DegradedReadUsed bool
}
// ObserveShadow computes the intended FAV outcome without business side effects.
// FAV_DEGRADED_READ must stay false; live staging/artifact/publication is not opened.
func (c *EventConsumer) ObserveShadow(lease events.Lease) (IntendedOutcome, error) {
out := IntendedOutcome{
EventName: lease.EventName,
AggregateType: lease.AggregateType,
AggregateID: lease.AggregateID,
LogicalResource: strings.TrimSpace(lease.AggregateType) + ":" + strings.TrimSpace(lease.AggregateID),
}
if c == nil {
return out, fmt.Errorf("%w: fav", events.ErrDisabledConsumer)
}
if c.AllowDegradedRead || envTruthy("FAV_DEGRADED_READ") {
return out, fmt.Errorf("fav shadow refuses FAV_DEGRADED_READ")
}
out.WouldStage = false
out.WouldPublish = false
out.DegradedReadUsed = false
_ = c.Publisher // never Drain / Stage / Publish here
return out, nil
}
func envTruthy(key string) bool {
switch strings.ToLower(strings.TrimSpace(os.Getenv(key))) {
case "1", "true", "yes", "on":
return true
default:
return false
}
}
+33
View File
@@ -33,3 +33,36 @@ func TestFavSubscriberStillDisabledWhenFlagOnWithoutRegistryApproval(t *testing.
t.Fatal("Registry.Register(fav) must remain fail-closed") t.Fatal("Registry.Register(fav) must remain fail-closed")
} }
} }
func TestFavSubscriberShadowComputesIntendedOutcomeWithoutWrites(t *testing.T) {
t.Setenv("FAV_DEGRADED_READ", "false")
t.Setenv("FAV_PUBLISHER_ENABLED", "false")
c := &fav.EventConsumer{Enabled: false, Env: "dev"}
out, err := c.ObserveShadow(events.Lease{
EventName: "document.finalized", AggregateType: "document", AggregateID: "doc_1",
})
if err != nil {
t.Fatal(err)
}
if out.WouldStage || out.WouldPublish || out.DegradedReadUsed {
t.Fatalf("shadow must not claim writes: %+v", out)
}
if out.LogicalResource != "document:doc_1" {
t.Fatalf("logical=%s", out.LogicalResource)
}
err = c.HandleLease(context.Background(), events.Lease{}, nil)
if !errors.Is(err, events.ErrDisabledConsumer) {
t.Fatalf("HandleLease still disabled: %v", err)
}
c.AllowDegradedRead = true
if _, err := c.ObserveShadow(events.Lease{}); err == nil {
t.Fatal("shadow must refuse degraded read")
}
c.AllowDegradedRead = false
t.Setenv("FAV_DEGRADED_READ", "true")
if _, err := c.ObserveShadow(events.Lease{AggregateType: "document", AggregateID: "doc_2"}); err == nil {
t.Fatal("shadow must refuse process env FAV_DEGRADED_READ")
}
}
+5 -1
View File
@@ -95,7 +95,11 @@ func (s *Server) handleVaultStatus(w http.ResponseWriter, r *http.Request) {
"degraded_read": degraded, "degraded_read": degraded,
"store_configured": storeReady, "store_configured": storeReady,
"subscriber_default": false, "subscriber_default": false,
"note": "fav consumer remains OFF until separate approval; full FAV branch not merged", "shadow_observe": true,
"would_stage": false,
"would_publish": false,
"live_not_opened": true,
"note": "fav consumer remains OFF until separate approval; shadow computes intended outcome only; full FAV branch not merged",
}) })
} }
+9
View File
@@ -57,6 +57,15 @@ func TestVaultStatusReportsOff(t *testing.T) {
if body["degraded_read"] != false { if body["degraded_read"] != false {
t.Fatalf("degraded_read=%v", body["degraded_read"]) t.Fatalf("degraded_read=%v", body["degraded_read"])
} }
if body["shadow_observe"] != true {
t.Fatalf("shadow_observe=%v", body["shadow_observe"])
}
if body["would_stage"] != false || body["would_publish"] != false {
t.Fatalf("shadow must not claim writes: %+v", body)
}
if body["live_not_opened"] != true {
t.Fatalf("live_not_opened=%v", body["live_not_opened"])
}
} }
func TestValidateIndependentVaultRejectsDataRoot(t *testing.T) { func TestValidateIndependentVaultRejectsDataRoot(t *testing.T) {