Files
crearte-monorepo/docs/plans/2026-10-03-p15a-approve-phases.md
XingfenD 02188eb3e0 docs: re-pin both P15 specs after task-level review adjudication
Twenty-four pinned corrections across the two specs and their plans, every one
traced to a measured finding. Both task-level reviewers overturned claims I had
written into the specs, and I reproduced each against the base tree before
accepting it (git show, never the working tree, which already carries the fix).

P15-A spec gains two sections. T1.4 records the reviewer's candidate guard for
phase-independence: the spec called "do not merge phases four and six" the most
important constraint in the batch, yet its only stated mitigation was the
byte-level comparison, which is one-shot evidence — after the report is filed,
nothing reddens when someone merges them. The reviewer built CG1/CG2/CG3 and
verified them both ways: green on the legal tree, red on four distinct merge and
degeneration forms, all assertion-red rather than compile-red, while the three
shipped guards stayed green throughout. T4 records the adjudication of nine
findings, including two that undercut the guard the batch itself added: a
half-finished dir parameter that redirects which files are counted but not which
are read, so a scan pointed elsewhere can satisfy its own precondition while the
169-line function it exists to catch stays invisible; and a span scanner keyed to
line-initial func, which means a 125-line package-level closure passes all four
gates and all three guards. Also pinned: the gofmt gate in the four-gate recipe.
gofmt -l lists unformatted files and still exits zero, so the recipe everyone ran
reported gofmt_exit=0 as evidence of formatting cleanliness. Implementer and I
shared that recipe, which is why the same blind spot was computed twice and
caught by neither.

Phase intervals are corrected to the real base-tree line numbers. The prior text
told the implementer to move lines 212-213 into a helper; 213 is the slog.Info
the same spec requires to stay in the caller, so following it would have swapped
what moves with what stays. Phases six and seven also overlapped, each claiming
the transaction error mapping. The skeleton now passes a pointer where its own
note eighteen lines later demanded a pointer receiver, and the transaction
helper's signature carries submissionID, which removes the batch's dependence on
a cross-repository equality argument for the only acceptance criterion it has.

P15-B spec corrects the artifact criterion I had backwards. I wrote that deleting
a token should change the var(--color-*) count; that count measures usage, the
token had zero usage across seven utility suffixes, and it stayed at 75. Had it
moved, that would have meant something referenced the token, contradicting the
dead-token premise. Definition-side and usage-side are separate measures and the
spec now says so.

Leg five is re-pinned as critical. It was the sole enforcement point for the
non-reactive injection decision, and it only pinned literal form: two
type-legal variants that establish the dependency elsewhere in the computed body
pass all ten legs with vue-tsc clean, and the reviewer proved by effectScope
evaluation counting that both really do make the iframe src flip on a theme
change, reloading a running game. The narrowing was introduced by the
implementer's own fix, to avoid a trap comment that spells out the forbidden
literal; but comment masking already neutralizes that comment, so the narrowing
was unnecessary and the second line of defense created the gap. Same shape as
the P14 final review finding a hole in the first round's fix.

Also pinned: the rejected deviation five, with the corrected root cause (all four
freeze attempts used page.route, which does not intercept service worker
registration, worker-issued requests, or navigations synthesized by respondWith;
context.route holds the loading screen past 25 seconds); the missing
dark-system-times-light-hash grid that one mutation slipped past all ten source
legs and all five e2e legs simultaneously; the fourth tautology class the spec's
three warnings omitted, where emptying a loop's driving collection makes it
vacuously true; legs seven and eight, which respectively substring-searched a
hex that occurs three times in the file and matched only double-quoted style
attributes; the copyable code block's comment, which now avoids the three
literals that would false-redden a correct implementation, with the annotation
moved outside the block; and three new discipline entries covering untested
method scope, measurement units, and retaining one-off verification scripts.

Every correction was checked by a script asserting both directions — the pinned
text present and the superseded text gone — plus table column integrity, fence
pairing, and code-block cleanliness. Three earlier attempts at this re-pin failed
on my own errors and were fixed before commit; the working tree was never left in
a half-edited state.
2026-10-03 19:48:48 +08:00

13 KiB
Raw Permalink Blame History

P15-A 实现计划:拆分 Approve 169 行七阶段

  • spec(权威):docs/specs/2026-10-03-p15a-approve-phases-design.md
  • 取证账本:crearte-server/.superpowers/sdd-p15a/survey.md(控制者派发前落盘;P15 前期取证在 crearte/.superpowers/sdd-p15/survey.md §8.1)
  • 仓:crearte-server(仅此一个)· base fe810dd(0.18.0,master)· 分支 chore/p15a-approve-phases

    ⚠️ 分支前缀用 chore/:AGENTS.md 只允许 {feat|fix|docs|chore}/ 四种(refactor/ 不允许,P14 控制者曾写错)。纯结构重构归 chore/。 ⚠️ inner repo 红线:不得在 master 上提交。git commit 前先 git branch --show-current 确认。

  • 性质:纯结构重构,零行为变更(spec D-G)
  • 目标版本:crearte-server 0.19.0 · wrapper 0.3.8
  • 派发:一个实现者子代理做完 T1→T3(所有任务同动单一工作树与 git index,sdd-parallel-dispatch §1 一仓一写者)
  • 并行:P15-B 在 crearte 仓同时进行 → 不得触碰 crearte/、crearte-deploy/、wrapper 的任何文件

spec 与本计划冲突时 spec 赢。spec 全文(§0–§8)必读,尤其 §1.2(④⑥不同形,禁止合并)、§1.3(helper 形态实测)、§2 决策表 D-A…D-I、§3.2(逐阶段要求,含 6 处"逐字保留")、§5-T1(mutation a–h 及 (b) 的已知盲区警告)、§7 风险表、§8 纪律 10 条。


任务表

任务 交付物 要求 完成判据
T1 internal/service/function_length_test.go(新增文件,不改 architecture_test.go) RED 先行(spec §5-T1)。三条断言:① TestNoOversizedFunction:service/ 内非测试 .go 无任何函数 >100 行(阈值依据见 spec §0 普查:当前只有 Approve 169 超过,次大 83)② TestApproveIsSmall:Approve <60 行 ③ TestApproveHelpersExist:五个 helper 名存在——必须用源码文本匹配,不能用 reflect(spec §1.3:未导出标识符 reflect 不可见)。断言内须含"至少扫到 N 个 .go 文件"的前提检查(spec §5 mutation (e) 的教训) RED 输出逐字存证 .superpowers/sdd-p15a/impl-evidence/t1-red.txt,且红的原因是断言失败而非编译错误(与 P14 T1 的编译红形态不同:本批引用的都是既有类型)。三条各自的红须可分辨
T2 internal/service/moderation.go(改) 按 spec §3.1 骨架 + §3.2 逐阶段要求执行拆分:loadApprovePlan(①②③) / precheckApprove(④) / copyApprovalObjects(⑤,收 *approvePlan) / applyApprovalInTx(⑥,包级函数收 tx repository.ContentRepository) / cleanupApprovalUploads(⑦) + 包级私有 struct approvePlan(spec D-B 七字段)。**六处"逐字保留"**见 spec §3.2 的 ⚠️ 标注 go test ./... -count=1 11 包全绿;既有 *_test.go 修改数 = 0;architecture_test.go 修改行数 = 0;P14 七断言 -v 全 PASS
T3 字节级保真证明(加做项,P14 同款) 写脚本对 base fe810dd 的 Approve 函数体与新树的 Approve+5 helper 做语句级比对(归一化缩进与接收者前缀),产出 verbatim / changed(逐条给理由)/ missing(必须 0) 三张清单。脚本的三个已知坑见 spec §5-T3 的 ⚠️(字符串字面量里的 // 被当注释、单行 var 须提前终止、意外值先怀疑自己的模式) 三份清单存证 .superpowers/sdd-p15a/impl-evidence/t3-verbatim.txt;missing = 0;changed 每条有理由且都落在 spec §3.2 允许的范围内

验收(控制者独立复跑,不采信自报)

四门(dockerized Go,AGENTS.md 硬红线)

cd crearte-server && docker run --rm -v "$PWD/src:/src" -w /src \
  -v crearte_gomod:/go/pkg/mod -e GOCACHE=/gocache -v crearte_gocache:/gocache \
  -e GOPROXY=https://goproxy.cn,direct -e GOSUMDB=sum.golang.google.cn \
  golang:1.24-alpine sh -c 'gofmt -l . ; test -z "$(gofmt -l .)" ; echo gofmt_gate=$? ; go vet ./... ; go build ./... ; go test ./... -count=1'

基线(spec §0,已实测):test -z "$(gofmt -l .)" exit 0(⚠️ 审查者 F3:gofmt -l 的语义是「列出需要格式化的文件」,列出时仍 exit 0,只在解析失败时 exit 2 → gofmt_exit=0 不是证据;须同时存证 gofmt -l . 的原始输出为空。控制者与实现者共用旧配方,故同一盲点被复算两次而未被发现)

⚠️ host Go 1.18 不可用;必须带 GOPROXY=https://goproxy.cn,direct(proxy.golang.org 本机不可达,默认下载挂死并杀代理);冷构建 1–3 min → background: true + 耐心 poll,不要 sleep 循环、不要因没立刻返回就断定失败。 ⚠️ -count=1 强制实跑(禁缓存)。 ⚠️ 落盘证据用 setsid nohup 脱离 + 宿主侧重定向(容器只挂 /src 写不了 .superpowers/;exec session 一断容器就被杀)。 ⚠️ grep -c 输出 0 时 exit 1,放 && 链里会静默中断后续步骤(含 git commit)。 ⚠️ pkill -f <pattern> 会匹配到自己的命令行(P14 控制者把自己 SIGKILL)→ 用 [p]attern 括号技巧。

结构指标(spec §5 表,交付时须报实测数字,一律 wc -l 口径)

指标 基线 目标
Approve 行数 169 < 60
service/ 包内 >100 行的函数数 1 0
service/ 包内最大单函数行数 169 < 100(次大者 83,故落点应在 83–100)
moderation.go 行数 298 不作指标(spec D-H:helper 同文件,可能不降反升;报了即可)
既有 *_test.go 修改文件数 — 0
architecture_test.go 修改行数 — 0
Approve 签名 (ctx context.Context, adminID, submissionID, note string) error 逐字不变
字节级比对 missing — 0

⚠️ 不要用 len(text.split('\n')) 数行数:文件以换行结尾时它多算一个空段。P14 F5 就因此让控制者报出 83/299 而真值是 82/298,被任务级审查者抓到。wc -l 是本仓钉死的口径(P14 spec §5 RE-PIN)。

mutation 抽查(spec §5-T1 的 a–h,控制者独立复现,不复用实现者结果)

八条,每条测"预期红 + 其余绿 + 非编译红 + 恢复证明":

# mutation 预期
(a) helper 存在但 Approve 不调它、改为原地展开 TestApproveIsSmall 红
(b) 构造一个 >100 行的 helper(⚠️ spec §5 已警告:把⑥整体 80 行搬进 applyApprovalInTx 不会红,那是阈值 100 的已知盲区,不是守卫失效) TestNoOversizedFunction 红
(c) 删掉一个 helper(内容合并进 Approve) TestApproveHelpersExist 红
(d) helper 改名(如 loadApprovePlan→loadApproveInputs) TestApproveHelpersExist 红((c) 的对照:证明钉的是名字集合而非"存在任意 5 个函数")
(e) 扫描范围改成空目录 红(防"扫 0 文件报 0 违规")
(f) 阈值 100 → 1000 红(防阈值被放宽;断言须自证阈值字面量)
(g) 给 ModerationService 加字段(clock func() time.Time) P14 断言 6 TestLineFields 红(跨批回归)
(h) 在 ContentService 上加方法 P14 断言 5 红,且须同时测有名与匿名两种接收者形态(P14 FR-1 教训:只测有名会漏掉 func (*ContentService) M())

⚠️ restore() 只回滚 mutation 触及的文件,绝不用 git checkout -- .(P14 控制者第 24 次自伤:全量回滚抹掉了未提交的守卫编辑,导致后四轮全在测没有修复的树、汇总报"绿=3")。每轮恢复后核 git diff HEAD --exit-code -- <file> 空 + 守卫文件 func Test 计数不变。 ⚠️ mutation 若产生编译错误,不算"守卫有牙"(P14 控制者第 22 次错误:正则截断签名致编译红,却被报成"守卫拦住了遮蔽")。

行为不变的额外证据

  • 既有 13 个测试函数就是行为守卫(spec §1.6 表):TestApproveConcurrentOnlyOneWins(⑥行锁+重校)· TestApproveSameWorkIDConcurrent(④⑥的 ErrWorkIDTaken,断言文本含 want ErrWorkIDTaken or ErrSubmissionConflict)· TestApproveMetadataChangeFeaturesSemantics(⑥ 的 Features 三态语义,用 meta-absent 与 ,"features":{} 两个 payload 精确区分)· TestApproveOptionalFieldsRoundTrip · TestMetadataChangeRuntimeImmutable · TestNewVersionMetadataChangeOwnership · api 层 5 个 + 2 个真库发布链
  • 错误文案逐字比对:service: load submission: %w / service: resolve submitter: %w / service: approve precheck: %w(两处,只在④) / service: copy bundle: %w / service: copy cover: %w;以及 ErrContentNotFound(①) vs ErrSubmissionConflict(⑥ 的 GetByIDForUpdate 未找到) 这个刻意的差异(spec §3.2 ⑥)
  • 两条 slog 逐字:slog.Error("approve: pending delete failed", "key", key, "error", err)(⑦ helper 内)· slog.Info("approve", "submission", …, "work", …, "kind", …, "admin", …)(留在 Approve 本体,spec §3.2 ⑦)
  • Approve 只有一个非测试调用点 handler/admin.go:62(spec §1.7)→ helper 无须导出;签名变更会打破 moderationPort 满足性 → serve.go 编译红

边界(不做,spec §4)

  • 不合并④⑥的 switch(spec §1.2 四处实质差异 + 乐观/悲观语义 + 错误文案不同;被否方案 A/B)
  • 不动 Reject/Unpublish/Republish/UpdateWorkFeatures/ListQueue(同文件其它方法)
  • 不动 deleteAccountLocked(83)/UpdateSubmission(76)/ValidateWorkFields(75)/ImportDir(70)/Run(70)/checkSubmissionRules(66) —— 六个 >60 行函数各自独立成批(spec §4 挂账)
  • 不新增/修改任何测试(既有测试即守卫,spec §2.1 被否方案 D)
  • 不改 architecture_test.go(P14 七断言)——若某条因新 helper 变红,停下来报告,不自行改守卫
  • 不碰 docs/、go.mod/go.sum(实现者红线;记账由控制者做)
  • 不碰其它三个仓

记账(控制者做,实现者禁动 docs/)

  • crearte-server/docs/CHANGELOG.md 新增 ## [0.19.0] - 2026-10-03(插 ## [0.18.0] 前)
  • wrapper docs/CHANGELOG.md 新增 ## [0.3.8] - 2026-10-03(插 ## [0.3.7] 前),小节 ### Done / 完成;与 P15-B 合并为一条还是分两条按合并时序定
  • docs/ROADMAP.md:第三波表新增 P15-A 行 + 文档索引表新增一行(哈希引 merge commit,P12/P13/P14 惯例)
  • 挂账新增:六个 >60 行函数 · TestNoOversizedFunction 阈值 100 的已知盲区(80 行 helper 不被拦)
  • 格式:同条目英文行紧跟中文行、无空行;不同条目空一行;小节标题双语(### Changed / 变更、### Tests / 测试、### Context / 背景)
  • 纪律:显式 git add <file>,禁 git add -A;wrapper 的 IDENTITY.md/SOUL.md/USER.md 三个未跟踪文件绝不 stage
  • 合并:CHANGELOG 改动提交到特性分支 → git checkout master → git merge --no-ff → 记 merge 哈希 → 推送(禁管道)→ git ls-remote origin master 非空对账(-z 检查,防空变量假 MATCH)→ 删分支

审查阶梯(P11–P14 惯例,不可省)

  1. 实现者自报 + 证据落盘(RED 输出、四门、mutation、字节级比对)
  2. 控制者独立核实(全部自己跑,不采信自报数字;P14 控制者因此抓到实现者报告可信度高、也抓到自己 6 处错)
  3. 任务级审查者(只读,自设 mutation 不照抄 spec 的 a–h,审守卫恒真/恒假、跨任务缝隙、spec 自身问题)
    • P13 先例:10 条自设 mutation 挖出 5 条 findings,M1 抓到控制者自己写错两次的恒真守卫
    • P14 先例:抓到 spec D-J 的守卫目的无断言覆盖 + spec 里 positive control 的绕行路径 + 控制者的行数口径错
  4. 控制者逐条裁定(sdd-pre-merge-review §3:未裁定的 note 阻塞合并,"PASS with notes" 不等于门过了)
  5. 全分支终审者(只读;spec 全文自洽性 + 审控制者的裁定工作 + 独立复现 mutation)
    • P11 先例:终审裁定过「需修复后合并」→ 不是橡皮章
    • P14 先例:终审在第一轮修复自身里找到洞(匿名接收者绕过源码扫描),并推翻控制者两处已入库的全称声明
  6. spec re-pin(裁定后一次性批量改,勿在审查者读 spec 期间改=移动靶);代码改了 spec 必须同步(P14:正则字面量写进 spec,改代码后 spec 立刻不一致)