交付出件箱

← 返回出件箱

Phase83 批次四 tech v1.3 物理核查报告(2026-09-28)

核查对象: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 模式的先例。


逐维原始命令输出

D1 基线漂移

$ 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(+)

D2 迁移 head

$ 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)。

D3 「导出明细」按钮位置

$ 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>

D4 两表表头(逐 th 对齐 class)

明细表(实际 :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>

D5 开通弹窗六处行号(cat -n 精确核对)

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>

六处与方案声明行号一一精确对应。

D6 前缀提示 / seat_service.py 409 / 数据来源

前缀提示块(方案声明 :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),确认为真实字段回显,非前端自造。

D7 编号容量 CHECK / 批量取号函数

$ 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)

D8 告警链路

$ 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(

D9 部分唯一索引先例

$ 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

D10 新接口尚不存在 / router prefix / le=50

$ 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)

D11 状态徽章 / disabled 按钮样式

   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"

D12 startup_event

   326  @app.on_event("startup")
   327  async def startup_event():
   328      """应用启动事件"""

D13 Excel 依赖 / 品牌码导出函数

$ 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 写法)

D14 sudo 权限

$ 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(修所有漏授权)

D15 generate_seats 逐号 emit 隐患(F7)物理确认仍在

   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", {}))

偏差清单(方案原写 → 实测应为)

  1. F17 / V7(§1 现状表 / §16a 证据表):「test 有 4 个 head(34ec94466f0c / phase50_seat_wx_binding / phase79_001 / 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 当前实测一致或落在声明区间内。