核查对象:docs/superpowers/plans/tech/phase83-batch4-bulk-open.md(v1.3,声明基线 origin/test @ f44699c2)
核查方法:一律在 origin/test 最新提交(3a75d6116d4686d02b795370241370e042b3b72d) 上用 git show origin/test:<路径> | sed -n / git grep -n ... origin/test -- <路径> 核对;sudo db-prod 仅 SELECT。
本机 grep 为 ugrep,凡 0 命中均配对照组。
| 维度 | 方案声明 | 实测 | 结论 |
|---|---|---|---|
| D1 | 基线 f44699c2,应用代码/迁移零漂移 | origin/test 领先 f44699c2 共 22 commits;方案引用的 10 个应用文件 git diff --stat 全部为空(零改动);migrations/versions/ 新增 3 个文件(phase85_002/003/004) |
偏差(局部):应用代码零漂移(所有 file:line 引用有效);migrations 目录有新增,牵动 D2 |
| D2 | 生产 head=phase83_002;test 4 个 head(34ec94466f0c / phase50_seat_wx_binding / phase79_001 / phase85_001) |
生产 SELECT version_num FROM alembic_version = phase83_002(✅ 与方案完全一致);test 实测 4 个 head = 34ec94466f0c / phase50_seat_wx_binding / phase79_001 / phase85_004(不是 phase85_001) |
偏差:test 第 4 个 head 已从 phase85_001 漂到 phase85_004(因 D1 新增的 3 个 phase85_00{2,3,4} 迁移文件接龙上去了);head 数量仍是 4,不影响方案红线①(down_revision 仍只能接生产 head phase83_002),但方案原文这行需要更新引用值 |
| D3 | 「导出明细」按钮在 page.tsx:887-893 | 按钮文本行在 :892,整个 <button> 块 885-893,与声明范围重叠/基本一致 |
成立 |
| D4 | 明细表 th :976-983;聚合表 th :815-821 | 逐行核对,两处 th 行号完全一致(976-983 共 8 个 th;815-821 共 7 个 th) | 成立(精确) |
| D5 | 开通弹窗:w-[420px] :1148 / 副标题 :1152 / 占位符 :1166 / 数量标签 :1206 / max={50} :1212 / 有效期 :1219 | 逐行核对全部精确命中同一行号 | 成立(精确) |
| D6 | 前缀提示块 :1190-1205;seat_service.py 无前缀 409 :305/:308;provSelectedOrg.subseat_prefix 来源 |
前缀提示实际块 1192-1203(在声明范围 1190-1205 内,略窄)成立;seat_service.py:305 if not org.subseat_prefix: / :308 detail 文案精确命中;数据源确认来自 allOrgs state(page.tsx:178,类型含 subseat_prefix),由 api_v1.py:260 OrgItem.subseat_prefix schema 字段 + :400 subseat_prefix=org.subseat_prefix 组装下发(机构列表接口) |
成立 |
| D7 | counter CHECK org_parent_seat_counter.py:39-43;_alloc_org_seq_batch seat_service.py:181-212 |
CHECK 语句实际在 :41(在声明区间内);函数体实际 :181-210(声明 181-212,多出 2 行空行,覆盖有效) |
成立 |
| D8 | 告警:incident_logger.py log_incident / 告警推送链路 |
log_incident 定义在 incident_logger.py:15,精确命中方案 §6 引用;另查到 app/services/alert_notifier.py:328 def push_alert(...) 存在(方案正文未直接引用该函数,仅供确认告警链路物理存在) |
成立 |
| D9 | 部分唯一索引(postgresql_where)有先例可循 |
命中 3 个文件(027a_add_vlm_columns_and_4l_switch.py、phase29_001_create_auth_tables.py ×3、phase48a_001_brand_code.py);对照组 create_index 28 处命中,证明查法有效 |
成立 |
| D10 | 新接口尚不存在(batches/resume 0 命中);router prefix :42;现有 le=50 :64 |
batches/resume 在 admin_parent_seats.py 均 0 命中(对照组 provision 3 命中,证明查法有效,非误查);router = APIRouter(prefix="/api/admin/parent-seats") 精确命中 :42;count: int = Field(1, ge=1, le=50) 精确命中 :64 |
成立(精确) |
| D11 | 状态徽章无「失败」;STATE_LABEL/DISABLED_BADGE :40-49;disabled 按钮样式 :1041 |
STATE_LABEL 定义于 :40-44(in_use/pending/expired 三态),DISABLED_BADGE 在 :49,均在声明范围内,确认无「失败」态;disabled 按钮 className 精确命中 :1041 |
成立 |
| D12 | main.py startup 事件 :326/:327 |
@app.on_event("startup") 精确在 :326,async def startup_event(): 精确在 :327 |
成立(精确) |
| D13 | Excel 依赖 openpyxl;admin_brand_code.py 存在可参考的 _build_export_workbook,新导出计划用 write_only=True |
requirements.txt:68 openpyxl==3.1.5 已装;_build_export_workbook 定义在 admin_brand_code.py:503,调用在 :767;该函数内部用的是 wb = Workbook()(:566),不是 write_only=True |
信息性发现(非方案声明矛盾):方案 §8 说的是新写的批量导出函数要用 write_only=True,并未声称 _build_export_workbook 已经这样做;实测确认现有品牌码导出走的是普通 Workbook(),write_only 是本期要新引入的写法,不是复用既有 write_only 代码 |
| D14 | dev2 有 db-/test-inspect 相关 sudo 权限 | sudo -n -l 显示 NOPASSWD:db-grant/db-prod/db-query/test-inspect 四个均有;sudo db-grant --help 正常返回用法(仅对 test 库) |
成立 |
| D15 | generate_seats 逐号 emit 隐患仍在 seat_service.py:361-363 |
import ... schedule_emit 在 :361,for sid in created_ids: 在 :362,schedule_emit(emit_lifecycle(...)) 在 :363,精确命中 |
成立(精确),F7 隐患物理确认仍未修 |
结论汇总:方案 v1.3 中对 origin/test 代码的 15 类具体 file:line / 存在性 / 0 命中声明,13 类精确或基本精确命中;2 类存在偏差——
1. D1/D2:migrations 目录自基线后新增 3 个 phase85 迁移文件,导致 test 环境第 4 个 alembic head 已从方案原文的 phase85_001 变为 phase85_004(生产 head 仍是 phase83_002,不受影响,方案红线①依然成立)。
2. D13:_build_export_workbook 现状用普通 Workbook() 非 write_only,不构成对方案声明的否定(方案说的是新函数要这样写),但如果实施时想"参考"这个函数的写法,需注意它本身不是 write_only 模式的先例。
$ git log --oneline f44699c2..origin/test | wc -l
22
$ git diff --stat f44699c2 origin/test -- app/api/admin_parent_seats.py app/services/seat_service.py app/services/password_utils.py app/api/seat_auth.py app/services/brand_code_service.py app/services/activity_log_service.py app/services/parent_seat_service.py 'web/app/(dashboard)/admin/parent-seats/page.tsx' app/main.py app/services/jwt_local.py requirements.txt
(空输出 —— 以上 10 个文件在 f44699c2..origin/test 之间零改动)
$ git diff --stat f44699c2 origin/test -- migrations/versions/
.../phase85_002_wecom_handover_settings.py | 65 +++++++++++++
.../phase85_003_wecom_group_handover_mode.py | 60 ++++++++++++
.../versions/phase85_004_wecom_learned_qa.py | 108 +++++++++++++++++++++
3 files changed, 233 insertions(+)
$ sudo db-prod "SELECT version_num FROM alembic_version"
version_num
-------------
phase83_002
(1 row)
Head 计算(解析 origin/test 上全部 108 个 migrations/versions/*.py 的 revision / down_revision,含 revision: str = '...' 注解写法和 tuple 型 down_revision,求「未被任何人 down_revision 引用」的 revision 集合):
total revisions parsed: 108
HEAD COUNT: 4
HEAD: 34ec94466f0c <- migrations/versions/34ec94466f0c_merge_4_heads_phase74_013_org_insight_.py
HEAD: phase50_seat_wx_binding <- migrations/versions/phase50_seat_wx_binding.py
HEAD: phase79_001 <- migrations/versions/phase79_001_director_studio.py
HEAD: phase85_004 <- migrations/versions/phase85_004_wecom_learned_qa.py
方案原文 F17/V7 写的第 4 个 head 是 phase85_001,链路为 phase85_001 <- phase85_002 <- phase85_003 <- phase85_004(D1 新增的 3 个文件把它顶到了 phase85_004)。
$ git grep -n "导出明细" origin/test -- 'web/app/(dashboard)/admin/parent-seats/page.tsx'
origin/test:web/app/(dashboard)/admin/parent-seats/page.tsx:892: {exporting ? "导出中…" : "导出明细"}
上下文(:870-900,button 块实际为 885-893):
885 <button
886 onClick={() => exportSeats(detail.org_id)}
887 disabled={exporting}
888 className="text-[12.5px] font-medium px-3 py-1.5 rounded-lg border border-slate-200 bg-white text-slate-600 hover:bg-slate-50 disabled:opacity-50"
889 >
890 {/* ... */}
892 {exporting ? "导出中…" : "导出明细"}
893 </button>
明细表(实际 :976-983,与方案声明完全一致):
976 <th className="px-4 py-3 font-medium">账号</th>
977 <th className="px-4 py-3 font-medium">微信</th>
978 <th className="px-4 py-3 font-medium">家长手机</th>
979 <th className="px-4 py-3 font-medium">来源</th>
980 <th className="px-4 py-3 font-medium">状态</th>
981 <th className="px-4 py-3 font-medium">开通 / 到期</th>
982 <th className="px-4 py-3 font-medium">绑定时间</th>
983 <th className="px-4 py-3 font-medium text-right">行内管控</th>
(外层 <tr className="text-left"> 在 :975,当前每个 th 没有独立对齐 class,除最后一列 text-right;方案 §12 的"账号/微信左对齐,家长手机/来源/状态/开通-到期/绑定时间居中"是待实施的目标状态,不是现状声明,故不构成偏差)
聚合表(实际 :815-821,与方案声明完全一致):
815 <th className="px-6 py-3.5 font-medium">机构</th>
816 <th className="px-6 py-3.5 font-medium">已开通</th>
817 <th className="px-6 py-3.5 font-medium">使用中</th>
818 <th className="px-6 py-3.5 font-medium">已到期</th>
819 <th className="px-6 py-3.5 font-medium">待绑定</th>
820 <th className="px-6 py-3.5 font-medium">最近开通</th>
821 <th className="px-6 py-3.5 font-medium text-right">操作</th>
1148 <div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[420px] max-w-[92vw] bg-white rounded-2xl shadow-2xl border border-slate-100">
1151 <p className="text-[12.5px] text-slate-500 mt-0.5">
1152 子席位不计入正式席位数、不进结算 · 开通后该机构的家长子席位功能同时启用
1166 placeholder="输入机构名检索"
1206 <label htmlFor="prov-count" className="text-slate-500 text-[12.5px]">数量(1-50)</label>
1212 max={50}
1219 <label htmlFor="prov-days" className="text-slate-500 text-[12.5px]">有效期(天)</label>
六处与方案声明行号一一精确对应。
前缀提示块(方案声明 :1190-1205,实际块为 1192-1203):
1190 </div>
1191 </div>
1192 {/* Phase 83.21 · 前缀提示(方案图第 13 块) */}
1193 {provSelectedOrg && (
1194 provNoPrefix ? (
1195 <p className="text-[12px] text-amber-600">
1196 该机构还没设置子席位账号前缀,请先到『机构管理 → 更多 → 子席位前缀』里设置
1197 </p>
1198 ) : (
1199 <p className="text-[12px] text-slate-400">
1200 账号前缀:{provSelectedOrg.subseat_prefix},新开的号接着往下排
1201 </p>
1202 )
1203 )}
1204 <div className="grid grid-cols-2 gap-3">
1205 <div>
后端 409(精确命中方案声明 :305/:308):
$ git show origin/test:app/services/seat_service.py | cat -n | sed -n '295,315p'
304 if seat_type == _PARENT_SEAT_KIND:
305 if not org.subseat_prefix:
306 raise HTTPException(
307 status_code=409,
308 detail="该机构还没设置子席位账号前缀,请先到『机构管理 → 更多 → 子席位前缀』里设置",
309 )
provSelectedOrg.subseat_prefix 数据来源:
$ git grep -n "allOrgs\b" origin/test -- 'web/app/(dashboard)/admin/parent-seats/page.tsx' | head -3
web/app/(dashboard)/admin/parent-seats/page.tsx:178: const [allOrgs, setAllOrgs] = useState<{ id: number; name: string; subseat_prefix?: string | null }[]>([]);
$ git show origin/test:app/api/api_v1.py | sed -n '258,261p'
brand_code_activation_count: int = 0
# Phase 83.15(批次五登录改造)· 子席位(家长号)账号前缀回显·未设置为 None
subseat_prefix: Optional[str] = None
$ git show origin/test:app/api/api_v1.py | sed -n '399,401p'
brand_code_activation_count=brand_code_activation_count,
subseat_prefix=org.subseat_prefix,
))
→ 前端 allOrgs 的 subseat_prefix 来自后端机构列表接口(app/api/api_v1.py 内 OrgItem schema + 组装处 :260/:400),确认为真实字段回显,非前端自造。
$ git grep -n "last_seq" origin/test -- app/models/org_parent_seat_counter.py
app/models/org_parent_seat_counter.py:41: "last_seq BETWEEN 100000 AND 999999",
_alloc_org_seq_batch(实际 :181-210,方案声明 181-212,多出 2 行空行,覆盖有效):
181 async def _alloc_org_seq_batch(db: AsyncSession, org_id: int, count: int) -> range:
...
203 try:
204 new_max = (await db.execute(stmt)).scalar_one()
205 except IntegrityError:
206 await db.rollback()
207 raise HTTPException(
208 status_code=409, detail="该机构家长账号编号已用完,请联系平台"
209 )
210 return range(new_max - count + 1, new_max + 1)
$ git grep -n "def log_incident" origin/test -- app/services/incident_logger.py
app/services/incident_logger.py:15:async def log_incident(
$ git grep -n "def push_alert" origin/test -- app/services/alert_notifier.py
app/services/alert_notifier.py:328:async def push_alert(
$ git grep -n "postgresql_where" origin/test -- migrations/versions/ | head -10
migrations/versions/027a_add_vlm_columns_and_4l_switch.py:54: postgresql_where=sa.text("status LIKE 'error_%'"),
migrations/versions/phase29_001_create_auth_tables.py:101: postgresql_where=sa.text("phone IS NOT NULL"),
migrations/versions/phase29_001_create_auth_tables.py:107: postgresql_where=sa.text("wechat_unionid IS NOT NULL"),
migrations/versions/phase29_001_create_auth_tables.py:113: postgresql_where=sa.text("locked_until IS NOT NULL"),
migrations/versions/phase29_001_create_auth_tables.py:119: postgresql_where=sa.text("linked_org_id IS NOT NULL"),
migrations/versions/phase48a_001_brand_code.py:113: postgresql_where=sa.text("wechat_unionid IS NULL"),
对照组(证明查法有效):
$ git grep -c "create_index" origin/test -- migrations/versions/*.py | grep -v ':0' | wc -l
28
$ git grep -c "batches" origin/test -- app/api/admin_parent_seats.py
(空 = 0 命中)
$ git grep -c "resume" origin/test -- app/api/admin_parent_seats.py
(空 = 0 命中)
对照组(证明查法有效):
$ git grep -c "provision" origin/test -- app/api/admin_parent_seats.py
app/api/admin_parent_seats.py:3
$ git show origin/test:app/api/admin_parent_seats.py | cat -n | sed -n '42p'
42 router = APIRouter(prefix="/api/admin/parent-seats")
$ git show origin/test:app/api/admin_parent_seats.py | cat -n | sed -n '64p'
64 count: int = Field(1, ge=1, le=50)
40 const STATE_LABEL: Record<string, { t: string; c: string }> = {
41 in_use: { t: "使用中", c: "bg-emerald-50 text-emerald-700" },
42 pending: { t: "待绑定", c: "bg-amber-50 text-amber-700" },
43 expired: { t: "已到期", c: "bg-slate-100 text-slate-500" },
44 };
49 const DISABLED_BADGE = { t: "已停用", c: "border border-rose-200 bg-rose-50 text-rose-700" };
(三态 + 停用徽章,确认无「失败」态)
1041 className="text-[12.5px] text-blue-600 hover:underline disabled:text-slate-300 disabled:no-underline disabled:cursor-not-allowed"
326 @app.on_event("startup")
327 async def startup_event():
328 """应用启动事件"""
$ git grep -n "openpyxl" origin/test -- requirements.txt
requirements.txt:66:# 2026-07-17 · 补漏(同上)· app/api/excel_import.py:3 顶层 import openpyxl
requirements.txt:68:openpyxl==3.1.5
$ git grep -n "_build_export_workbook\|write_only" origin/test -- app/api/admin_brand_code.py
app/api/admin_brand_code.py:503:def _build_export_workbook(
app/api/admin_brand_code.py:767: wb = _build_export_workbook(
$ git grep -n "Workbook(" origin/test -- app/api/admin_brand_code.py
app/api/admin_brand_code.py:566: wb = Workbook()
(_build_export_workbook 内部用的是普通 Workbook(),不是 write_only=True——不构成对方案的否定,方案本就是要新引入 write_only 写法)
$ sudo -n -l 2>/dev/null | grep -i 'db-\|test-inspect'
(root) NOPASSWD: /usr/local/bin/db-grant
(root) NOPASSWD: /usr/local/bin/db-prod
(root) NOPASSWD: /usr/local/bin/db-query
(root) NOPASSWD: /usr/local/bin/test-inspect
$ sudo db-grant --help 2>&1 | head -3
用法: sudo db-grant test [表名|ALL]
不填表名 = 给 public 下所有表补 SELECT(修所有漏授权)
360 # Phase 52.3a · #7 lifecycle(seat_added) 埋点(fire-and-forget · commit 成功后逐 seat emit)
361 from app.services.activity_log_service import emit_lifecycle, schedule_emit
362 for sid in created_ids:
363 schedule_emit(emit_lifecycle(org_id, "seat", str(sid), "seat_added", {}))
phase85_001)」→ 实测应为「…/ phase85_004」(基线 f44699c2 之后 origin/test 新增 3 个 phase85 迁移文件 phase85_002→003→004,接龙顶替了 phase85_001 的 head 位置)。不影响方案红线①(生产 head 仍确认为 phase83_002,新迁移 phase83_003 的 down_revision 依旧只能接 phase83_002)。其余 14 个维度均无需改写——原文行号 / 存在性 / 0命中声明与 origin/test 当前实测一致或落在声明区间内。