Architecture Decision Record · 门店营销工作台

把一个原型
拆成独立产品

36 屏交互原型、3 条业务链路、1 份三个月前刚定稿的「不要独立」选型结论——这篇记录的是它怎么被推翻的:独立到哪一层、哪些约束不可谈判、逐屏对账后发现原型的哪些效果做不出来,以及最后怎么切第一刀。

// 架构决策记录 · 含被否决的方案与判据 · 纯工程口径

36 SCREENS NEXT.JS 16 · FULL SSR SHARED SUPABASE · RLS CROSS-REPO CONTRACT GUI FLEET EXECUTION FLY / NRT · CN-REACHABLE
SCROLL / 向下滚动
01
Prototype Anatomy

先看清要实现的是什么

起点是一个 V4.1 交互原型——不是静态图,是能点的:全局状态、草稿与已应用两态、试运行门禁、跨屏联动。做架构决策前先把它拆开数清楚,否则后面所有的排期和选型都建在猜测上。

0
总屏数
22 主屏 + 14 详情屏
0
业务链路
短视频 / 企微 / 沉默客户
0
引导设置(低频)
一次性配置,可代做
0
每日任务(高频)
店长天天要打开的

MODULE 01 · 8 屏短视频获客

选业务范围 → 打标偏好 → 传门店实拍与出镜素材 → 每日选参考视频 → 结构拆解 → 看制作进度 → 质检下载成片

MODULE 02 · 9 屏企微自动接待

传门店资料与销冠对话 → 给机器人回答打分 → 定说话规则 → 每日看接待漏斗、抽检聊天、改常问答案、配自动办理的服务。

MODULE 03 · 6 屏沉默客户跟进

录入活动与名额 → 看哪种跟进方式有效 → 挑今天最该跟的客户 → 配知识海报与上门量房券的生成规则。

原型自己给的第一个架构信号

V4.1 把「每日工作」提到导航首位、把「首次设置」收成低频入口——产品定位已经从「配置工具」变成「每天要打开的工作台」。这一条后面直接决定了第一期该切哪一刀。

02
First Selection · Superseded

三个月前的结论:不要独立

第一版选型的判据很硬:原型要求的能力,现有栈里全都有——多租户 RLS 隔离、审核状态机、全链路观测、评测门禁、成本归因。于是结论是「不引入任何新框架」,把 36 屏直接长在现有代码库里。四个候选的否决理由如下。

候选判定理由
A · 复用现有栈做产品框架✓ 选定多租户 RLS、审核状态机、Langfuse 观测、评测门禁、成本归因全部已存在;模块 01 的表与接口骨架半年前就已建好。
B · n8n 做产品编排核心✗ 否决自家踩坑记录即反对票:字段静默吞、webhook 超时全灭、导出静默失败;无多租户、无审计状态机。仅保留无状态生图 fan-out 一职。
C · 媒体产线长成整个产品✗ 改任执行面定位是内部小团队工具,计费模块仍是占位;在里面重建租户 / 审核 / 资料事实源=把已有的东西再造一遍。
D · 原型自带脚手架或全新起仓✗ 否决demo 级脚手架、数据层为空;其托管方案的国内可达性同样不行;底座全部从零建,且跨仓契约是已知的静默故障源。
Why revisit

这份结论在技术上依然成立——被推翻的不是技术判断,而是它的前提:当时的问题是「怎么最省力地把功能做出来」,现在的问题是「它要能作为一个产品单独迭代、单独发布、单独卖」。同一套事实,换一个目标函数,答案就不同。

03
Spin-off Boundary

「独立」是四个不同的层级

「做成独立产品」听起来是一个决定,其实是四个可以分别取舍的层级:代码仓、数据面、部署与计费、法人主体。选错层级的代价不对称——独立得不够,两条产品线互相拖发布节奏;独立得过头,四套横切底座要整个重建。

Level 1

商业独立
技术共栈

同一个仓、同一个库,对外是独立品牌与定价。最快出成果,但两条产品线共用 CI 与高危路径门禁,发布节奏被绑死

选定 Level 2

独立仓
共享数据面

新仓、自己的域名与国内入口、自己的 CI;仍连同一个数据库、复用已有 worker 与媒体产线。发布解耦、底座不重建,代价是跨仓契约必须有单一事实源。

Level 3

完全独立
产品线

独立仓 + 独立数据库 + 独立部署与计费,原产品降级成「能力供应商」。边界最干净,但四个横切一等公民要整套重建

Level 4

独立到
主体层面

独立法人、独立备案、独立收款。主要是商务问题;但备案本来就是这个产品的最高优先级前置,可能顺带解决。

判据

选 Level 2 的理由只有一条:要独立的是「迭代节奏」,不是「数据」。两个产品服务的是同一批门店、同一批客户档案、同一套触达通道——把数据也切开,等于第一天就要建双向同步,那是比重建底座更贵的债。

04
Constraint Map · Verified

五条不可谈判的硬约束

架构选型里最贵的错误,是把「文档里写着有」当成「实际能用」。下面五条都是本轮重新实读代码与 schema 核过的——其中两条推翻了内部备忘录里的既有说法。

Constraint 01 · 最硬的一条

店长入口不能是海外 PaaS 直连。国内节点实测:前端托管平台域名超时、数据库域名连 DNS 都解析不了,只有东京机房那条链路可达。这意味着店长面必须全部服务端渲染、浏览器零直连数据库——不满足这条,换任何托管都救不回来。

CONSTRAINT 02 · 已有解国内可达已经跑通

好消息:同一条约束下的反向代理纯透传方案已经在生产用了——甲方上传页早就从海外域名换成东京机房域名。不用重新趟,但整站走反代要透传三类路径(页面 / 静态资源 / 表单),这是选择新仓直接部署到东京而不是「原站 + 反代」的直接原因。

CONSTRAINT 03企微执行层是 GUI 机队,不是 API

官方客服接口因备案主体校验冻结;平台不提供「员工个人号好友单聊」的代写接口;会话存档技术可行但会给客户端弹提示,破坏产品前提。结论:执行层只能是挂着真实登录态的云端 Windows + 读屏定位的 GUI 自动化,一店一机、逐台标定、边际成本线性。

CONSTRAINT 04 · 推翻既有说法接口骨架比记录的少

内部备忘录记的是「7 个待填接口,含审核 / 发布 / 统计」。实读文件只有 5 个,且名字完全不同——审核、发布、统计这三个根本不存在。差别不是「填实现」和「填实现」,是「填实现」和「从头写」。数据表那五张倒是真建好了。

CONSTRAINT 05 · 推翻既有说法视频表里没有互动数据

原型的「从最近三天表现较好的同行视频里选」要按播放 / 点赞 / 收藏 / 转发排序。实读 schema:视频表一个互动字段都没有,只有链接、标题、作者、发布时间与几个业务标记。这条不补,那一屏连排序都排不出来。

Method

两条「推翻既有说法」都不是靠回忆发现的,是把 schema 和路由文件重新 grep 一遍发现的。团队内部为此有一条硬纪律:任何「X 已经有了 / X 已经修了」的论断,必须当场给出命中行——误报比漏报更伤信任

05
Target Architecture

三层 · 一条共享数据面

控制面独立、数据面共享、执行面可插拔。三层之间不直接互相调用——指令向下经数据库与受控 API 下发,回执向上写回同一本账。任何一层挂掉,重启即从库里的状态续跑。

CONTROL PLANE · 新仓 · 独立 CI store-workbench Next.js 16 · 全 SSR · 东京机房 · 国内可达 店长面 token 免登录 运营托管台 回执与代做 DATA PLANE · 共享 · 单一 migration owner Supabase · schema workbench RLS tenant_scope 复用 · 不重写租户隔离 统一动作账本 五态状态机 资料事实源 引用 + 有效期 EXECUTION PLANE · 可插拔适配器 三档执行器 账本不感知执行工具 T1 Copilot 店长自发 T2 托管机队 GUI 代发 T3 官方 API 待备案 指令 回执 派单 送达
指令下行 · 机器自动 回执上行 · 人在环 执行面 · 终端

共享数据面是这套架构里最容易出事的地方。两个仓写同一个库,四条契约必须先钉死,否则会以静默故障的形式在几周后爆出来。

① 迁移单一 owner

  • 迁移版本表是全库一份,两个仓各自推必然打架
  • 新仓要加表 → 提 PR 到主仓的迁移目录
  • 上线前必须先清掉远端与仓库的迁移条数漂移,否则两个仓都在猜数据库真相

② 独立 schema · 复用 RLS

  • 新表进独立 schema,不混主命名空间
  • 租户隔离直接复用已有的行级安全策略
  • 不重写一行隔离逻辑——重写=多一条越权面

③ 不给新仓超级权限

  • 读走会话绑定客户端 + RLS
  • 写操作经已有 worker 的受控 API
  • 绕过 RLS 的密钥不下发到第二个仓——散出去等于越权面翻倍且审计断链
④ 跨仓契约必须单一事实源

任务 schema、阶段枚举、回执格式的定义放数据面所属的那个仓,另一侧存副本并由 CI 比对差异。这条不是洁癖——团队踩过一次「改一边不改另一边=条件永远命中不了、且完全不报错」的事故,静默了很久才被发现。跨仓契约是已知的静默故障源,必须用 CI 而不是纪律去防。

06
Execution Tiers

「发出去」这件事分三档

同一个动作账本,三种执行器。关键设计是账本不感知执行工具——无论最后是店长自己点发送、托管机器代发、还是官方接口下发,状态机都是同一条:待办 → 已批 → 执行中 → 已执行 → 已验证

T1

COPILOT · 可立即上线AI 草拟,店长自发

系统备好话术与素材,店长在自己设备上发出去,回到工作台点一下确认。零执行基建、零边际硬件成本,定价可以是纯席位制。第一期就能跑。

T2

AUTOPILOT · 需机队基建托管机队代发

店长只在工作台批准,云端机器执行并回执。一店一机、逐台标定、成本线性且店长不可自助——所以商业上它是托管服务档而不是软件席位档。

T3

OFFICIAL API · 前置未解官方接口接待

唯一能做到真正无人值守的一档,但被备案主体校验挡着。备案同时解锁两件事:店长自定义域名 + 官方接口——因此它是整个项目优先级最高的非技术前置

「已验证」必须是业务一等状态

这是从触达通道上用真事故换来的:接口返回 200success:true 不等于送达——平台会在限频时返回一个成功外壳、把消息静默丢弃,真校验必须解包断言内层状态码。所以账本的终态不是「调用成功」,是可查证的业务 delta退出码 0 不等于业务有效

07
Reality Check · Screen-by-Screen

原型的效果,有多少真做得出来

架构定了不等于效果能落地。把 22 个主屏逐屏对账——每屏究竟依赖什么能力、那个能力现在是否存在——得到的分布比预期难看,也比预期有用。

现有能力直接覆盖13 屏 / 59%
需降级或补数据7 屏 / 32%
需要全新能力1 屏 / 4.5%
与执行现实冲突1 屏 / 4.5%

// 口径:22 个主屏,按「这一屏跑起来需要的能力是否已存在」归档。59% 能直接做不是好消息——剩下的 41% 恰好集中在原型演示效果最强的那几屏。

GAP 01 · 全新能力视频片段自动描述

「上传实拍 → 自动拆成片段 → 写出每段拍了什么」。切分用现成工具能做;逐段内容描述要视觉模型抽帧理解,现有 LLM 层是纯文本接口,这条能力不存在。降级方案:切分自动、描述由人写、店长核对——原型的操作说明里本来就写着「改正不准确的说明」,说明设计上就预期要人改。

GAP 02 · 补数据同行视频的互动指标

见约束 05:视频表里没有播放 / 点赞 / 收藏 / 转发。签名接口能带回这些统计字段,加列 + 补抓即可,是确定能做、只是没做的活。但不补,「按表现排序」和屏上那一串数字都是空的。

GAP 03 · 新逻辑资料冲突仲裁

原型里一份文件被标「有冲突 · 2 处说法不一致」,让店长选哪份为准。现有知识库解决的是检索,不是跨文档同一事实的比对。做法清楚(抽结构化条目 → 按 key 分组 → 值不一致即冲突),但是净新增逻辑。

还有一件原型完全没画

原型里的数字全是为评审模拟的——「近 30 天已发 1,842 张」「送达 98.7%」「读出 126 条内容」。第一家店上线第一天,这些全是 0,而 36 屏里一屏空状态都没画。店长打开看到一片 0 会认为产品是坏的。空状态与首日引导是净增量工作,且没有任何设计稿可抄。

08
Design Conflicts · Must Change

三处不是「难做」,是「照做会坏」

前面的缺口都是工作量问题。这三处不同——它们按原型实现出来会破坏系统本身,或者依赖一个平台根本不提供的数据。这类问题必须在动工前改设计,不能留到联调时发现。

CONFLICT 01
功能性冲突
企微「扫码登录即开始接待」卡片。原型画的是店长看二维码、员工手机扫码、系统随即开始接待。但现实的执行层是常驻登录的托管机队,而企微是单端互斥的——店长这一扫,会把机队的登录态踢掉。照做不是「实现了」,是打断了执行通道
→ 改成只读的接待状态卡(在线 / 离线 / 最近更新 / 今日已接待),登录与掉线由运营侧处理。
CONFLICT 02
数据源不存在
「发布时间建议 · 依据本账号近 30 天粉丝在线时间」。这个数据平台不向第三方开放,只有创作者本人后台能看到。
→ 改成用本店历史发布数据 + 行业经验值给建议,并且删掉「依据粉丝在线时间」这句话——留着它,店长第一次追问就会发现产品在说自己拿不到的东西。
CONFLICT 03
精度不可兑现
成片自动质检里的「人物说话与字幕基本一致 99%」。语音转写有现成能力,唇动检测没有,那个 99% 兑现不了。
→ 八项检查里留六项真自动(清晰度 / 字幕安全区 / 门店标识 / 价格与活动核对 / 敏感词 / 完整性),口型一致与背景音乐版权改成人工勾选项。宁可少两项,不要假装有。
这一节的通用教训

交互原型天然会画出「看起来该有」的能力——它的职责是把产品意图说清楚,不是核实每个数据源存不存在。所以原型评审必须有一道「这一屏的数据从哪来」的逐项核实:不是问「能不能做」,是问「这个数字此刻在哪张表里」。答不上来的,要么补数据、要么改设计,不能留到实现阶段再发现

09
Delivery Slice · First Cut

第一刀切在哪

36 屏一次交付不现实。切法的判据不是「哪些屏简单」,而是「哪些屏店长非自己做不可」——一次性的配置动作由服务方代做,体验反而更好;每天要做的决策才必须产品化。

每日任务屏 · 第一期9
引导设置屏 · 代做13
必需上传页 · 极简版4

// 上传页是唯一的例外:素材在店长手机里,代做不了,但第一期只做上传本身,不做完整的「自动拆解 + 逐项核对」界面。

第一期 · 真交付

  • 店长面独立入口(国内可达 · 全 SSR)
  • 三条链路的每日任务屏与详情页
  • 统一动作账本 + 审核闸
  • 运营侧托管台与回执打通
  • 空状态与首日引导

第二期 · 自动化补位

  • 媒体产线可自动化的环节服务化
  • 互动数据抓取补齐
  • 资料冲突仲裁
  • 注册登录与付费闭环
  • 备案落地 → 自定义域名

第三期 · 前置解锁后

  • 官方接口接待(T3)
  • 出站队列账本收敛
  • 机队管理面(注册表 / 自助配置 / 标定向导)
  • 双指标监控:心跳 + 业务 delta
最长的那根杆不是代码

备案是全盘工期最长的前置,而且它同时挡着两件事:店长自定义域名、官方客服接口。它不启动,第三期就永远在等;它今天启动,前两期照常推进——这类非技术前置必须和第一行代码同时开始,不能排在「等产品做出来再说」

一句话总括:被推翻的不是三个月前的技术判断,而是它的目标函数——从「最省力地做出功能」变成「能作为产品单独迭代」。于是边界从「不独立」挪到「独立仓 + 共享数据面」:切开发布节奏,不切开数据。而逐屏对账的价值在于,它把「原型看起来能做」变成了三类可决策的东西——补数据、降精度、改设计;剩下那 59% 才是真正可以立刻动手的部分。