From 77f4695fbef7ef29c784ec8509d080d513c54b8f Mon Sep 17 00:00:00 2001 From: XingfenD Date: Thu, 1 Oct 2026 00:59:56 +0800 Subject: [PATCH] docs: P8 batch-1 spec+plan (triaged 400s, admin slug guard, race test, CHANGELOG copy, feature-flags UI) + roadmap --- docs/ROADMAP.md | 5 +- docs/plans/2026-10-01-p8-long-tail.md | 37 +++++++++++++ docs/specs/2026-10-01-p8-long-tail-design.md | 57 ++++++++++++++++++++ 3 files changed, 97 insertions(+), 2 deletions(-) create mode 100644 docs/plans/2026-10-01-p8-long-tail.md create mode 100644 docs/specs/2026-10-01-p8-long-tail-design.md diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index e70ab73..0661d9c 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -33,7 +33,7 @@ | # | 子项目 | 内容 | 状态 | |---|--------|------|------| | P7 | 管理后台增强 | 用户列表 / 角色管理 UI(替代 CLI `user set-role`)+ 审计日志 | **完成**(server `ca24805` 0.12.0 / crearte `900f13a` 0.18.0,2026-09-30 合并推送。audit_log 表 0010 + 组中间件统一记录(owner 定稿:admin 路由收敛为组,Full-Path 模板入史,GET 也记,失败尝试照记,未遂 401/403 不记);用户列表/PATCH 角色/审计查询三端点;唯一 admin 降级 409 护栏 CLI/API 双覆盖;前端 /admin/users + /admin/audit 两页,降级确认双要点文案;本机六腿验收全绿:容器四连+fresh-DB `-count=1` 10 ok 零 FAIL 零 skip、废 token 端到端 401、护栏矩阵(非唯一降 OK/唯一降 CLI 拒+API 409)、审计流水含 409 失败尝试、真栈 full-loop 分支后端 1 passed×2、vitest 488/typecheck/admin-flow 6/主套件 68) | -| P8 | 长尾打包 | 权限开关 UI 全量(`inlineStyle/wasm/coop/fullscreen/gamepad`)、后端 triage 小项(`games.Detail` 400 细分、admin 路由 slug 校验、approve 同名竞态测试)、账号注销、静态兜底目录、CHANGELOG 模板文案 | 待启动 | +| P8 | 长尾打包 | 权限开关 UI 全量(`inlineStyle/wasm/coop/fullscreen/gamepad`)、后端 triage 小项(`games.Detail` 400 细分、admin 路由 slug 校验、approve 同名竞态测试)、账号注销、静态兜底目录、CHANGELOG 模板文案 | **分批进行中**(第一批=②⑤①,2026-10-01 开工:server 0.14.0 / crearte 0.20.0;③账号注销为新功能待产品决策、④静态兜底按需,均不入第一批) | ## 排序理由 @@ -53,8 +53,9 @@ | P4 作者主页 | `docs/specs/2026-09-29-author-page-design.md` | `docs/plans/2026-09-29-author-page.md`(已执行,2026-09-29) | | P1 prod 写侧可用 | `docs/specs/2026-09-29-prod-write-side-design.md` | `docs/plans/2026-09-29-prod-write-side.md`(已执行,2026-09-29) | | P5 收藏/评分 | `docs/specs/2026-09-30-favorites-ratings-design.md` | `docs/plans/2026-09-30-favorites-ratings.md`(已执行,2026-09-30) | -| P6 hosted 投稿(方案 A) | `docs/specs/2026-09-30-hosted-submission-design.md` | `docs/plans/2026-09-30-hosted-submission.md`(执行中,2026-09-30) | +| P6 hosted 投稿(方案 A) | `docs/specs/2026-09-30-hosted-submission-design.md` | `docs/plans/2026-09-30-hosted-submission.md`(已执行,2026-09-30) | | 创作者中心 `/creator`(维护者直接需求) | `docs/specs/2026-09-30-creator-center-design.md` | `docs/plans/2026-09-30-creator-center.md`(已执行,2026-09-30) | | P2 CI+测试基线 | `docs/specs/2026-09-30-ci-test-baseline-design.md` | `docs/plans/2026-09-30-ci-test-baseline.md`(已执行,2026-09-30) | | P3 备份+可观测 | `docs/specs/2026-09-30-backup-observability-design.md` | `docs/plans/2026-09-30-backup-observability.md` | | P7 管理后台增强 | `docs/specs/2026-09-30-admin-console-design.md` | `docs/plans/2026-09-30-admin-console.md`(已执行,2026-09-30) | +| P8 长尾打包(第一批:②⑤①) | `docs/specs/2026-10-01-p8-long-tail-design.md` | `docs/plans/2026-10-01-p8-long-tail.md`(执行中,2026-10-01) | diff --git a/docs/plans/2026-10-01-p8-long-tail.md b/docs/plans/2026-10-01-p8-long-tail.md new file mode 100644 index 0000000..cfba6e2 --- /dev/null +++ b/docs/plans/2026-10-01-p8-long-tail.md @@ -0,0 +1,37 @@ +# P8 长尾打包(第一批)· 实施计划 + +依据:`docs/specs/2026-10-01-p8-long-tail-design.md`(D-1…D-5)。分支名 `feat/p8-long-tail`,各仓实现者自拉自提交**不 push**;合主由控制者 `--no-ff`。 + +## Task 1 · server(crearte-server,→0.14.0) + +基线 master(`3e11446` 起)。改动点: + +1. **②-1 `games.Detail` 400 细分**(`src/internal/handler/games.go:44-50`):把单一 `"invalid game id"` 拆为可区分消息——user 不合 `UsernamePattern` → `user: invalid format`;slug 不合 `SlugPattern` → `slug: invalid format`(两者都错时按 user 优先,或返回逗号并列,实现者择一但须有测试钉住)。沿用 `WriteError(400,"invalid_request",…)`,不改状态码。核既有 `games_test.go`/`router_test.go` 是否断言该文案,有则同步。 +2. **②-2 admin 工作路由校验**:`handler/admin.go` 四条 handler(Unpublish/Republish/SetWorkFeatures/RevokeVersion)入口补 `UsernamePattern/SlugPattern` 前置校验(与 `games.Detail:47` 同款),非法即 `WriteError(400,"invalid_request","invalid work id")` 并 return。**不改** `adminWorkID`(`:115`,纯拼接 helper)。若抽出小 helper(如 `validWorkParams(c) bool`)四 handler 共用,允许。 +3. **②-3 approve 同名竞态测试**:新增测试(`internal/service/content_test.go` 或新文件,视该包既有并发测试风格)——并发对同一 `work_id` 的两个 new_work 提交走 approve,断言:仅一条成功;其余返回 `ErrWorkIDTaken`/`ErrSubmissionConflict`;库中该 work 恰一行。**只加测试不改行为**。若既有测试用真库则归集成腿、若用 memory store 则单测即可(实现者按仓内既有模式定,注明)。 +4. **⑤ CHANGELOG 头部**(`docs/CHANGELOG.md` 顶部):去「模板/建议统一记录」腔,改为项目口径(保留一句格式出处),不动历史条目。 +5. `docs/CHANGELOG.md` 追加 `## [0.14.0] - 2026-10-01`(Fixed:②-1/②-2;Tests:②-3;Docs:⑤),中英成对。 + +红线:zero 新依赖/迁移;approve 行为不变;路由常量不动。 + +验证:容器内 `gofmt -l` 空、`go vet ./...`、`go build ./...`、`go test -p 1 ./internal/...` RC=0(容器姿势同 AGENTS.md,goproxy.cn)。集成腿由控制者跑。 + +## Task 2 · crearte(→0.20.0) + +基线 master(`9bd9372` 起),源码根 `crearte/src`。改动点: + +1. **① 权限开关全量**(`app/content/features.ts`):`EditableFeatures` 扩至七键 `eval, inlineScript, inlineStyle, wasm, coop, fullscreen, gamepad`;`EMPTY_FEATURES` 七键 false;`FEATURE_ITEMS` 补五条(**每条中文 label + hint 写清风险与适用场景**——这是本任务主要工作量);`collectFeatures` 输出七键齐全;`featuresToForm` 回读七键缺省 false;`hasAnyFeature` 自然覆盖。 +2. 三处 UI 自动同步(均读 `FEATURE_ITEMS`,核实无硬编码遗漏):投稿表单勾选区、`AdminView.vue` 作品展开行、`AdminSubmissionView.vue` 只读展示。若某处有超出 `FEATURE_ITEMS` 的定制文案/布局,补齐。 +3. 测试:`features` 单测(collectFeatures 七键、featuresToForm 回读、hasAnyFeature);投稿表单/审核详情相关 vitest 补断言(新开关出现、勾选进载荷)。 +4. **⑤ CHANGELOG 头部**(`docs/CHANGELOG.md` 顶部):「本**模板**…建议统一记录」改为项目口径。 +5. `docs/CHANGELOG.md` 追加 `## [0.20.0] - 2026-10-01`,中英成对。 + +红线:不动运行时三件套 / GameHost / useGameFrame(flag 消费端已存在);不为单处 UI 另开硬编码;不动管理面路由。 + +验证(`crearte/src`):`npx vitest run`、`npm run typecheck`、`npx playwright test e2e/admin-flow.spec.ts`、`npm run e2e` 全 RC=0。 + +## 合主与验收(控制者) + +双路独立审查(依据 spec §1-§3 全量对照)→ 六腿验收 → `--no-ff` 合主推送 → wrapper 记账 0.2.8 → 删分支。 + +**注(教训落地)**:审查员任务书须显式要求「对照 spec 全文,任务书与 spec 冲突时以 spec 为准,并报告 spec 中未落地项」——P6 D-D 的漏项即由此产生。 diff --git a/docs/specs/2026-10-01-p8-long-tail-design.md b/docs/specs/2026-10-01-p8-long-tail-design.md new file mode 100644 index 0000000..d9ef18d --- /dev/null +++ b/docs/specs/2026-10-01-p8-long-tail-design.md @@ -0,0 +1,57 @@ +# P8 长尾打包(第一批)· 设计 spec + +日期:2026-10-01 | 决策:owner「可以」批准本批范围与顺序(②⑤ 后端小项+文案,① 权限开关 UI)。 +本批**不含** ③账号注销(新功能,待产品决策)与 ④静态兜底目录(按需)。 + +## 0. 一句话 + +清账批:把前几波刻意记为遗留的碎片收干净——后端三处 triage 小修、两份 CHANGELOG 去模板腔、前端把后端已支持但 UI 未开放的五个权限开关补齐。 + +## 1. 现状勘查(grounded,2026-10-01) + +### ②-1 `games.Detail` 400 细分 +`handler/games.go:44-50`:user/slug 格式校验失败时统一 `WriteError(400, "invalid_request", "invalid game id")`——调用方无法区分「user 格式非法」「slug 格式非法」。校验辅助已存在:`service.UsernamePattern`(`namespace.go:11`)、`SlugPattern`(`:12`)。改进:逐字段细分错误消息(如 `user: invalid format` / `slug: invalid format`),沿用 `ValidationError.Fields` 的既有表达风格。 + +### ②-2 admin 工作路由 slug 校验 +`router.go:26-29` 四条路由(unpublish/republish/features/revoke)→ `handler/admin.go` 各 handler 直接调 `adminWorkID(c)`(`:115-117` = `JoinWorkID(c.Param("user"), c.Param("slug"))`),**无格式校验**:非法格式会带进 service/DB,最终多半落 404(掩盖了「格式错」与「不存在」的区别)。对照公开面 `games.Detail:47` 是有校验的。改进:四条 admin 路由的 handler 入口补 `UsernamePattern/SlugPattern` 前置校验,非法即 400。 + +### ②-3 approve 同名竞态测试 +`JoinWorkID(user, slug)` 同名撞车在 service 层有处理(approve precheck + `Works().Create` 的 `ErrConflict`→`ErrWorkIDTaken`),但无**并发**针对性测试。补一个并发 approve 同名 new_work 的竞态测试,断言最终只有一条成功、其余拿 `ErrWorkIDTaken`/`ErrSubmissionConflict`,且库中不出现重复行。 + +### ⑤ CHANGELOG 模板腔 +- `crearte/docs/CHANGELOG.md` 头部仍写「本**模板**的重要变更建议统一记录在此文件中」+「参考了 Keep a Changelog 的思路,也可以根据团队习惯调整」——仓库已迭代到 0.19.x,是项目不是模板。 +- `crearte-server/docs/CHANGELOG.md` 头部「本项目的重要变更统一记录在此文件中」尚可,但英文行 "should be documented" 偏模板口吻。 +改进:两仓头部改为项目自己的口径(保留 Keep a Changelog 的引用作格式出处说明),去掉「模板/建议/可以调整」的占位口吻。 + +### ① 权限开关 UI 全量 +后端 feature key 全集(`service/validate.go:60`):`eval, inlineScript, inlineStyle, wasm, coop, fullscreen, gamepad`(7 个)。 +前端 `content/features.ts` 的 `EditableFeatures`/`FEATURE_ITEMS` **只开放前 2 个**,注释明写「其余 flag(inline…epad)schema 与后端均已支持,但 UI 暂不开放(YAGNI):默认值已覆盖绝大多数作品,按需再添」。 +本批补齐剩余 **5 个**:`inlineStyle`、`wasm`、`coop`、`fullscreen`、`gamepad`。三处 UI 共用 `FEATURE_ITEMS`(投稿表单勾选区、admin 作品管理展开行、审核详情只读展示),故扩 `EditableFeatures`/`FEATURE_ITEMS`/`collectFeatures`/`featuresToForm`/`hasAnyFeature` 即三处同步生效;后端零改动。 + +## 2. 决策 + +- **D-1 错误粒度**:`games.Detail` 沿用 400 + `invalid_request`,仅**细分 message**(`user:`/`slug:` 前缀)——不改状态码、不加新错误码,最小变更;既有测试只断言 400 不断言文案者不受影响,若有断言文案则同步更新(实现者核)。 +- **D-2 admin 校验位置**:在 **handler 入口**补校验(与公开面 `games.Detail` 同层),不在 `adminWorkID` 内改(其为纯拼接 helper,改它会波及调用点语义);四条 handler 复用同一小段校验。 +- **D-3 开关默认值全 false**:五个新开关默认**关闭**(与现有一致——「默认值已覆盖绝大多数作品」)。`collectFeatures` 输出**七键齐全**(与既有「两键总是存在」纪律一致,后端据此区分「显式空对象=不放宽」vs「旧提交无键=保留」)。 +- **D-4 文案优先**:五个新开关的 `hint` 要写清风险与适用场景(这是①的主要工作量);文案风格沿用既有两条。 +- **D-5 ⑤ 仅头部**:只改 CHANGELOG 头部占位话术,不动历史条目。 + +## 3. 范围 + +**server(→0.14.0)**:②-1 错误细分、②-2 admin 校验、②-3 竞态测试、⑤ 头部文案;CHANGELOG 0.14.0 记 ②+⑤。 +**crearte(→0.20.0)**:① 五开关 UI 全量(三处同步 + 测试)、⑤ 头部文案;CHANGELOG 0.20.0 记 ①+⑤。 +**deploy**:零改动。 + +## 4. 红线 + +- 不动 approve 落库逻辑本身(②-3 只**加测试**,不改行为);不动路由常量值;不动 CSP 执行面(① 只补 UI,运行时的 flag 消费逻辑已存在)。 +- 不新增依赖、不新增迁移。 +- 前端三处 UI 一致性:投稿表单 / admin 展开行 / 审核只读展示由同一 `FEATURE_ITEMS` 驱动,不得为某一处另开硬编码。 + +## 5. 验收 + +1. server 容器四连 RC=0;新增/改动的单测全绿;②-3 竞态测试在 `-race` 下通过(若仓内既有 race 跑法,沿用)。 +2. 空库 `-count=1` 集成腿全过、零静默 skip。 +3. curl 冒烟:`GET /api/games/<非法user>/x` 与 `/x/<非法slug>` 各返 400 且 message 可区分;admin 四路由对非法 slug 返 400(负例)。 +4. FE:vitest / typecheck / admin-flow / 主套件 RC=0;五开关在投稿表单可见可勾选、载荷含七键、admin 展开行可改、审核详情只读展示。 +5. 双路独立审查(**依据 spec §1-§3 全量**,非仅任务书)PASS 后合主;wrapper 记账(ROADMAP P8 行分批完成、CHANGELOG 0.2.8、spec/plan 索引)。