feat(ECR-024): OpsCMS Banner 只读并 Closed

运营横幅目录(ops_banners + admin /cms),锁定 024–040 全队列。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
jackyu66git
2026-08-08 03:05:55 +08:00
co-authored by Cursor
parent ac1aec857d
commit 32ac559385
30 changed files with 808 additions and 3 deletions
+172
View File
@@ -0,0 +1,172 @@
#!/usr/bin/env python3
"""Scaffold ESS thin-slice artifacts for Ops Loop (docs only)."""
import argparse
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
def write(path: Path, text: str):
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(text.strip() + "\n", encoding="utf-8")
print("wrote", path.relative_to(ROOT))
def main():
p = argparse.ArgumentParser()
p.add_argument("--ecr", required=True) # 024
p.add_argument("--slug", required=True) # banner
p.add_argument("--title", required=True)
p.add_argument("--capability", required=True)
p.add_argument("--bc", required=True)
p.add_argument("--concept", required=True)
p.add_argument("--predecessor", required=True) # ECR-023
p.add_argument("--migration", required=True) # 000025
p.add_argument("--perm", required=True)
p.add_argument("--apis", required=True, help="comma paths like GET /admin/cms/banners|GET /admin/cms/banners/{id}")
p.add_argument("--non-goals", required=True)
p.add_argument("--seed", default="")
p.add_argument("--new-perm", action="store_true")
args = p.parse_args()
ecr = f"ECR-{args.ecr}"
bd = f"BD-2026-{args.ecr}"
spec = f"ops-{args.slug}.md"
apis = [a.strip() for a in args.apis.split("|")]
write(ROOT / f".ai/product/feature-spec/{spec}", f"""
# Feature Spec: {args.capability} · {args.concept}Ops · {ecr}
> Status: `Active`Loop continuous · **{ecr}**
> Parent: WAVE0-FROZEN · Predecessor: {args.predecessor} Closed
> Capability: `{args.capability}` · BC: `{args.bc}`
> 授权:`docs/WAVE0/LOOP_AUTHORIZATION.md`
## Non-goals
{args.non_goals}
## L2 Domain
| 概念 | 语义 |
|------|------|
| `{args.concept}` | 本切片只读目录;code 唯一(若适用) |
## L3 API
| Method | Path | 权限 | 语义 |
|--------|------|------|------|
""" + "\n".join(
f"| {a.split()[0]} | `{a.split()[1]}` | `{args.perm}` | 只读 |"
for a in apis
) + f"""
## Migration
`{args.migration}`:表 + 种子(若有){' + 授予 ' + args.perm if args.new_perm else '(权限复用)'}
## L4 AC
| ID | Then |
|----|------|
| AC-F-01 | list 含种子或空列表合法 |
| AC-F-02 | 已知 id get 200 |
| AC-F-03 | 未知 id → 404 |
| AC-S-01 | 无 Admin → 401 |
| AC-S-02 | 无权限 → 403 |
| AC-P-01 | list &lt; 500ms |
| AC-O-01 | N/A 只读 |
contract_diff: `docs/CONTRACT_DIFF/{ecr}.yaml`
""")
write(ROOT / f"docs/ECR/{ecr}-{args.slug}.md", f"""
# {ecr}
**Title:** {args.title}
**Status:** **Approved**Coding authorized · Loop continuous
**Parent:** WAVE0-FROZEN · **Predecessor:** {args.predecessor} Closed
**Change Level:** L2
## Change
{args.concept} 只读 · `{args.migration}` · `{args.perm}`
## Forbidden
{args.non_goals}
## Linked
Spec `{spec}` · {bd} · CONTRACT_DIFF/{ecr}.yaml
""")
write(ROOT / f"docs/BACKEND_DESIGN/{bd}-{args.slug}.md", f"""
# Backend Design: {ecr} {args.concept}
| ID | {bd} |
| Status | Approved |
| Coding | Loop authorized |
| Level | L2 |
| Migration | YES {args.migration} |
## Backend Change Boundary
```text
Domain: {args.concept} (read)
App: AdminHandler → admin.Service → AdminRepo
API: {'; '.join(apis)}
Permission: {args.perm}
Migration: {args.migration}
```
## Out of boundary
{args.non_goals}
Rollback: down migration + remove routes/UI
""")
api_yaml = "\n".join(
f" - method: {a.split()[0]}\n path: /api/v1{a.split()[1]}\n change: added"
for a in apis
)
perm_line = f" - code: {args.perm}\n change: {'added' if args.new_perm else 'unchanged'}"
write(ROOT / f"docs/CONTRACT_DIFF/{ecr}.yaml", f"""
ecr: {ecr}
capability: {args.capability}
bounded_context: {args.bc}
parent: WAVE0-FROZEN
predecessor: {args.predecessor}
change:
type: additive
breaking_change: false
migration_required: true
compatibility_notes: >
Adds {args.concept} read catalog. Forbidden: UGC / real payment.
apis:
{api_yaml}
perms:
{perm_line}
""")
write(ROOT / f"docs/PRODUCT_SPEC/{ecr}-{args.slug}.md", f"# PRODUCT_SPEC — {ecr}\n\n对齐 {spec} · Approved · Loop · L2 · {args.concept} 只读")
write(ROOT / f"docs/ENGINEERING_SPEC/{ecr}-{args.slug}.md", f"# ENGINEERING_SPEC — {ecr}\n\n1. migration {args.migration}\n2. AdminRepo/Service/Handler\n3. OpenAPI + admin-h5\n4. Integration · Closed")
write(ROOT / f"docs/HANDOFF/{ecr}-architect-to-engineer.md", f"# HANDOFF — {ecr} Architect → Engineer\n\nLoop continuous · Approved + Coding. Migration {args.migration}. Forbidden: UGC/真支付.")
write(ROOT / f"docs/STATE/{ecr}.md", f"# STATE — {ecr}\n\n| Status | **Approved** · Coding |\n| Phase | coding |\n| Spec | {spec} |\n| Updated | 2026-08-08 |")
write(ROOT / f"docs/TASKS/TASK-{args.ecr}-{ecr.replace('-','')}.yaml", f"""
id: TASK-{args.ecr}-{ecr.replace('-','')}
ecr: {ecr}
title: {args.title}
role: engineer
status: active
change_level: L2
parent: WAVE0-FROZEN
predecessor: {args.predecessor}
acceptance:
- Spec AC mapped
- {args.concept} read only
- No UGC / payment
""")
if __name__ == "__main__":
main()