feat(ECR-041): OpsCMS Banner 薄写面(Write-Wave 首刀)

Admin POST/PUT + admin.cms.write/审计;C 端 GET /home/banners;首页投影回退静态 homeFeeds;migration 000051;不碰 FeedSlot/支付/UGC。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
jackyu66git
2026-08-13 19:55:27 +08:00
co-authored by Cursor
parent ad45d17dff
commit 9707b72808
35 changed files with 1203 additions and 29 deletions
@@ -0,0 +1,37 @@
# Backend Design: ECR-041 Banner Write
| Field | Value |
|-------|-------|
| ID | BD-2026-041 |
| Status | **Approved** |
| Coding | **Authorized · Implemented · Closed with ECR-041** |
| Level | L2 |
| Migration | `000051_ops_banner_write` |
## Backend Change Boundary
```text
Domain: Banner (write + C-end read)
App: AdminHandler / HomeHandler → admin.Service / home → AdminRepo
API:
POST /api/v1/admin/cms/banners
PUT /api/v1/admin/cms/banners/{id}
GET /api/v1/home/banners?placement=home
Permission: admin.cms.write (additive); keep admin.cms.read
Audit: admin_audit_logs on POST/PUT
Table: reuse ops_banners (ECR-024)
```
## Out of boundary
FeedSlot 写 · ScheduledPublication 写 · UGC · 真支付 · soft-delete · Crisis/Handoff/ContentSafety 写 · 新 RBAC 子系统
## Rollback
1. migration down(撤权限种子)
2. 移除写路由与 C 端读取路由
3. H5 回退静态 `homeFeeds`
## Refs
Spec `ops-banner-write.md` · ECR-041 · WRITE_WAVE_AUTHORIZATION · predecessor BD-2026-024
+3 -1
View File
@@ -2,7 +2,9 @@
## 2026-08-13
- **仓侧治理最小补丁(非 ESS 内核):** `scripts/repo-governance-check.py`migration 号唯一 · ECR identity 唯一);TRACEABILITY 增加 **Next ECR=`ECR-041`** · **Max Migration=`000050`**`PROJECT_RULES` 写入预占/同号双义禁止/migration 分配流程;历史分叉 H1 固定为 `ECR-012-star` / `014-plan` / `015-code` / `016-insight`
- **ECR-041 Closed** OpsCMS Banner 薄写面(`admin.cms.write` · `GET /home/banners` · 首页投影回退 · migration `000051`)· Write-Wave 首刀;未做 FeedSlot
- **Write-Wave 开启:** `docs/WAVE0/WRITE_WAVE_AUTHORIZATION.md` · Continuous Loop 限定写面边界
- **仓侧治理最小补丁(非 ESS 内核):** `scripts/repo-governance-check.py`TRACEABILITY **Next ECR=`ECR-042`** · **Max Migration=`000051`**
- **Migration 撞号修复:** `000015``000023` 双文件冲突已重编号为唯一序列至 `000050`;说明见 `docs/MIGRATION_RENUMBER.md`
- **Merge** `merge/ops-local-into-main` — 合并本地 Ops 目录扩展(ECR-013A…040)与远程 mainECR-009…016
注意:分叉期间 ECR-012/014/015/016 编号在两边含义不同,以 TRACEABILITY 双列说明为准
+17
View File
@@ -0,0 +1,17 @@
# CODE_REVIEW — ECR-041
**Verdict:** Approve
**Scope:** Banner write only · FeedSlot untouched
## Checks
- [x] Spec / ECR / BD boundary respected
- [x] No soft-delete · no payment · no UGC
- [x] Permission additive `admin.cms.write`
- [x] Audit on POST/PUT
- [x] C-end + static fallback
- [x] Integration AC green
## Residual
- Banner 投影字段(icon/tone)用缺省;完整 Feeds 槽位属后续 FeedSlot ECR
+27
View File
@@ -0,0 +1,27 @@
ecr: ECR-041
capability: OpsCMS
bounded_context: Ops_CMS_NoUGC
parent: WAVE0-FROZEN
write_wave: docs/WAVE0/WRITE_WAVE_AUTHORIZATION.md
predecessor: ECR-024
change:
type: additive
breaking_change: false
migration_required: true
compatibility_notes: >
Banner write + C-end home banners. Forbidden: FeedSlot write / UGC / real payment / soft-delete.
apis:
- method: POST
path: /api/v1/admin/cms/banners
change: added
- method: PUT
path: /api/v1/admin/cms/banners/{id}
change: added
- method: GET
path: /api/v1/home/banners
change: added
perms:
- code: admin.cms.write
change: added
migration: 000051_ops_banner_write
+54
View File
@@ -0,0 +1,54 @@
# ECR-041
**Title:** OpsCMS · Banner 薄写面(Write-Wave 首刀)
**Status:** **Closed**2026-08-13 · Write-Wave Continuous Loop
**Date:** 2026-08-13
**Change Level:** L2
**Parent:** WAVE0-FROZEN · **Write-Wave:** `docs/WAVE0/WRITE_WAVE_AUTHORIZATION.md`
**Predecessor:** ECR-024 Closed
**Coding:** Done · Migration `000051_ops_banner_write`
## Change
1. Active Spec`.ai/product/feature-spec/ops-banner-write.md`
2. Admin`POST/PUT /admin/cms/banners*` + 权限 `admin.cms.write` + 审计
3. C 端:`GET /home/banners`user-h5 首页推荐区 API 优先、静态 `homeFeeds` 回退
4. admin-h5 CMS Banner 编辑(**不**做 FeedSlot 写)
5. OpenAPI · contract_diff · migration(编码时锁 `Max Migration+1`
## Motivation
只读 Banner 目录无法运营;首页推荐区仍静态。Write-Wave 首刀用最低风险写面验证闭环;支付仍最后。
## Scope
### Allowed
- 上列 Spec / Banner 写读闭环 / 加法权限码 / 审计 / 首页投影
### ForbiddenNon-goals
- **FeedSlot 写**(后续独立 ECR)· ScheduledPublication 写
- UGC · soft-delete · 真支付
- Crisis / Handoff / ContentSafety 高风险写
- 合并 Banner+FeedSlot · 扩大为本 ECR 多 Capability
- 新权限模型(子系统)· 改 ESS/Loop/Adapter
## Acceptance
见 Spec §8 AC-F/S/A/C/O;实现后 TEST_REPORT 映射。
## Trace
Spec `ops-banner-write` · BD-2026-041 · PRODUCT_SPEC/ECR-041 · CONTRACT_DIFF(实现时)· WRITE_WAVE_AUTHORIZATION
## Coding gate
```text
repo-governance-check PASS
→ ess-validate design/implement as required
→ BD Approved
→ THEN Continuous Loop may start coding
```
本文件交付时 **禁止改 `apps/` / `packages/`**
@@ -0,0 +1,24 @@
# HANDOFF — Architect → Engineer · ECR-041
**Date:** 2026-08-13
**ECR:** ECR-041 Banner 薄写面
**Auth:** `docs/WAVE0/WRITE_WAVE_AUTHORIZATION.md` · Human Coding Start
**Spec:** `.ai/product/feature-spec/ops-banner-write.md`
**BD:** `docs/BACKEND_DESIGN/BD-2026-041-banner-write.md`
## Do
1. Migration `000051_ops_banner_write` — grant `admin.cms.write` to `super_admin`
2. Admin `POST/PUT /admin/cms/banners*` + auditC 端 `GET /home/banners`
3. user-h5 首页推荐区投影;空/失败回退 `homeFeeds`
4. admin-h5 CMS Banner 编辑(FeedSlot **只读**
5. OpenAPI · CONTRACT_DIFF · integration AC
6. `repo-governance-check` PASS · TEST_REPORT · Close
## Don't
FeedSlot 写 · soft-delete · 真支付 · UGC · Crisis/Handoff/ContentSafety · 扩 scope
## Done when
Spec §8 AC 有测试证据;ECR Status ClosedMax Migration=`000051`
@@ -0,0 +1,4 @@
# HANDOFF — Engineer → Reviewer · ECR-041
TestOpsCMSBannerWrite PASS · homeCatalog PASS · repo-governance PASS.
Ready for Closed. FeedSlot 写未实现(按边界)。
@@ -0,0 +1,5 @@
# PRODUCT_SPEC — ECR-041
对齐 `.ai/product/feature-spec/ops-banner-write.md` · Human Approved scope · L2 · Banner **写面**(非 FeedSlot)。
Write-Wave: `docs/WAVE0/WRITE_WAVE_AUTHORIZATION.md` · Coding NOT STARTED.
+5 -4
View File
@@ -53,17 +53,18 @@
- **本仓:** `ess_intake: strict` · Retro FAIL · 单 ECR Context
- **Ops foundation** `docs/WAVE0/` · `.ai/domain/boundary-rules.md` · `glossary.yaml` · Loop: `docs/WAVE0/LOOP_AUTHORIZATION.md`
- ECR: main 线 ECR-006016 ClosedOps 扩展 ECR-013A/B · 017040;分叉已固定为 `ECR-012-star` / `014-plan` / `015-code` / `016-insight`(见 TRACEABILITY
- **Next ECR / Max Migration** 以 `docs/TRACEABILITY.md` 顶部锚点为准(仓侧治理,非 Loop
- **Next ECR / Max Migration** 以 `docs/TRACEABILITY.md` 顶部锚点为准 · 现 **Next=`ECR-042`** · **Max=`000051`**
- **Write-Wave** `docs/WAVE0/WRITE_WAVE_AUTHORIZATION.md` · Active · **ECR-041 Banner Closed**;下一刀候选评估(非自动无限)
- EXP: (无)
- STATE: `docs/STATE/`ECR-016 main · 以及 Ops 目录 Closed 条目
- STATE: `docs/STATE/`ECR-041
- TRACEABILITY: `docs/TRACEABILITY.md` · 门禁 `scripts/repo-governance-check.py`
- ADR: `.ai/adr/0007-ess-ai-dual-track.md`
- Product status: `.ai/product/p1-status.md`**P1 Complete**)· `.ai/product/p2-status.md`**P2 Complete**
- Active Spec: `account-auth` · `explore-test` · `home` · `star-profile` · `life-rhythm` · `input-compliance` · `ops-system` · `profile`
- Active Spec: `account-auth` · `explore-test` · `home` · `star-profile` · `life-rhythm` · `input-compliance` · `ops-system` · `profile` · `ops-banner-write`
## WIP(尚未单独 ECR
(无显著未挂 ECR 的后端 WIP;工作区其它 FE chrome 属既有 Closed ECR 或 L0 UI
(无FeedSlot 写面须新 ECR,不在 041
## Pointers
+10
View File
@@ -0,0 +1,10 @@
# STATE — ECR-041
| Field | Value |
|-------|-------|
| Status | **Closed** |
| Spec | ops-banner-write |
| BD | BD-2026-041 |
| Migration | 000051_ops_banner_write |
| Write-Wave | Active · next candidate FeedSlot(另 ECR |
| Closed | 2026-08-13 |
+35
View File
@@ -0,0 +1,35 @@
# TEST_REPORT — ECR-041 Banner Write
**Date:** 2026-08-13
**Commit:** (pending commit)
## Commands
```bash
cd apps/api && go test ./internal/integration/ -count=1 -run 'TestOpsCMSBanner'
npm run test -w @yuxingu/user-h5 -- src/lib/homeCatalog.spec.ts
python3 scripts/repo-governance-check.py
```
## Results
| Check | Result |
|-------|--------|
| TestOpsCMSBanners (read) | PASS |
| TestOpsCMSBannerWrite | PASS |
| homeCatalog vitest (3) | PASS |
| repo-governance-check | PASS · max migration 000051 |
## AC map
| AC | Evidence |
|----|----------|
| AC-F-01 | Write test create → list/get |
| AC-F-02 | PUT active=false → C-end excludes |
| AC-F-03 | GET /home/banners contains active home |
| AC-F-04 | duplicate code → 409 |
| AC-F-05 | external link_path → 400 |
| AC-S-01/02 | 401 unauth; read-only role POST → 403 |
| AC-A-01 | audit rows create+update |
| AC-C-01/02 | H5 mapHomeBannersToFeeds + static fallback |
| AC-O-01 | no FeedSlot write routes added |
+4 -2
View File
@@ -4,8 +4,8 @@
| Anchor | Value | Rule |
|--------|-------|------|
| **Next ECR** | `ECR-041` | 开新 ECR 前占用此号;写入 `docs/ECR/` 后把本行改为下一空闲裸号(或 `python scripts/repo-governance-check.py --print-anchors` |
| **Max Migration** | `000050` | 新建 migration 必须读仓内实际 max 后 +1;禁止凭记忆;合入前 `repo-governance-check` PASS |
| **Next ECR** | `ECR-042` | 下一空闲裸号(候选:FeedSlot 写面,须符合 Write-Wave);或 `--print-anchors` |
| **Max Migration** | `000051` | 新建 migration 必须读仓内实际 max 后 +1;禁止凭记忆;合入前 `repo-governance-check` PASS |
- ECR identity **全局唯一**(含已 Closed);禁止同号双义。分叉只用 `ECR-NNN-suffix` / `ECR-NNNA`
- Migration **6 位版本号唯一**。撞号修复见 `docs/MIGRATION_RENUMBER.md`
@@ -64,5 +64,7 @@
| ECR-038 | GrowthInsights · ReportTemplate | **Closed** | Spec ops-report-template.md · BD-2026-038 |
| ECR-039 | GrowthInsights · FunnelDefinition | **Closed** | Spec ops-funnel-definition.md · BD-2026-039 |
| ECR-040 | ExploreConfig · ScaleDefinition | **Closed** | Spec ops-scale-definition.md · BD-2026-040 |
| WRITE-WAVE | Ops 写面加深授权 | **Active** | `docs/WAVE0/WRITE_WAVE_AUTHORIZATION.md` · 真支付最后 · 禁 UGC/soft-delete |
| ECR-041 | OpsCMS · Banner 薄写面 | **Closed** | Spec ops-banner-write · BD-2026-041 · migration **000051** · 禁 FeedSlot |
> **Migration** 合并后 `000015``000023` 曾撞号,已重编号至 `000050`。以 `apps/api/migrations/` 与 `docs/MIGRATION_RENUMBER.md` 为准(文档中旧号引用可能滞后)。
+70
View File
@@ -0,0 +1,70 @@
# Write-Wave Authorization — Ops 写面加深(Human
| Field | Value |
|-------|-------|
| Date | 2026-08-13 |
| Authorizer | Human |
| Mode | **Continuous Loop · Write-Wave only** |
| First slice | **ECR-041 · OpsCMS · Banner 薄写面** |
| Payment | **最后**(本波次禁止) |
| Parent freeze | WAVE0-FROZEN · ESS 0.2.0-intake FROZEN · Loop/Adapter 不改 |
## Intent
在只读目录队列(ECR-024…040**STOP** 之后,开启 **Ops 写面加深** 波次。
首刀验证完整闭环:
```text
Write → Read → C 端生效 → 权限 → 审计 / 可回滚
```
## Continuous Loop(范围内)
在本授权有效且当前 ECR 属于 Write-Wave 边界时,允许 Agent
1. Approve ECR + BD(契约齐备 · `ess-validate` PASS · `repo-governance-check` PASS
2. Coding Start Authorization
3. Reviewer Approve → ClosedTEST_REPORT 绿 · AC 映射齐)
4. `git commit`(**不 push**,除非另授权)
5. **仅当下一刀仍符合下方 In 边界** 时,可起草下一候选 ECR;**不得**默认自动扩大波次范围
## InWrite-Wave
| 允许 | 说明 |
|------|------|
| ECR-041 Banner 写 | 唯一当前 Active 切片;不顺带 FeedSlot |
| 后续候选(须单独 ECR | 同属 OpsCMS / ExploreConfig / AICoreConfig **低中风险写**,且不触碰 STOP 表 |
| 既有 RBAC 上 **加法权限码** | 如 `admin.cms.write`(不发明新权限体系) |
| 审计 | `admin_audit_logs` |
| 回滚 | migration down · 路由/UI 可撤 · C 端静态回退 |
## Out / STOP(不得自动开新 ECR · 不得扩 ECR-041
任一条触发即 **STOP**,回到 Human
- 真支付 · UGC · soft-delete
- Crisis / Handoff 写 · Content Safety **高风险写**
- 改变 Governance Boundary / WAVE0 boundary-rules 写所有权
- **新增权限模型**(新 RBAC 子系统;≠ 加法权限码)
- 原架构假设不成立
- 扩大 ECR-041 范围 · **合并 Banner + FeedSlot**
- 任何超出本文件 In 表的 Capability
## Relation to prior Loop auth
| Doc | Status |
|-----|--------|
| `docs/WAVE0/LOOP_AUTHORIZATION.md` | 只读队列 **STOP after ECR-040****仍有效**;不复活只读连跑 |
| **本文件** | 写面波次权威;与上者并列,互不吞并 |
## First slice lock
- **ECR-041** = Banner only
- FeedSlot = 后续独立 ECR
- SystemPrompt ≠ 首刀
- 编码前:Active Spec · ECR · BD · contract_diff**本回合仅交付授权 + Spec,不写码**
## Repo anchors
- Next ECR / Max Migration`docs/TRACEABILITY.md`
- Gate`python scripts/repo-governance-check.py`