成稿链接存进库没人读 · 写入方不等于展示方
入档:2026-07-30 一句话:字段有人写、有库存、接口也返回了,但没有任何页面读它——发布方在管理台填完成稿链接,任务书详情页一个字都不变;缺口跨了两轮迭代没被发现,因为在此之前人一直在正文里手写「交付情况」替系统把这件事做了。
事实记录(不可修改区)
- 项目:蛛网之上(tiaozhuxiansheng.com)任务书系统,2026-07-30 单会话
- 起点:发布方把 OJO 测评任务在管理台切成「完工待打款」并填了成稿链接,详情页上什么都没变
- 提交(above-the-web):
5571bad修复渲染缺口 + 同步回填 →6eb6023OJO 收录为评测类标杆 - 改动规模:8 文件,+118 / -5 行;后端回归 37/37 通过(含本轮新增 2 条)
- 定位证据(
git log -S):deliverable_url列、管理台输入框、接口deliverableUrl全部诞生于a179002(账号系统那轮);前端渲染deliverableUrl的第一次提交就是本轮的5571bad - 交付:详情页正文前加一行「成稿」callout(两张详情页共用,md 那份构建期就位、运行时以库为准);
syncTasks的UPDATE分支补上「库里空才回填」 - 数据来源:本地 chrome-headless-shell 造三种库内状态实测 + 线上两条流水线绿后对生产页面与
/api/tasks/ojo-review复验 - 已知尾巴:库里那条链接带着飞书
?from=from_copylink尾巴(能打开,未清);OJO 仍是done,打款后需人工切closed
缺口长什么样
任务系统的成稿链接有三条写入路径:承接人在站内点「提交交付」、发布方在管理台手填、md 里的 deliverable 首次入库时当初值。
读路径零条。详情页上那条「交付:链接」读的是 deliveries 流水表——只有第一条路径才会产生行。线下交付、发布方代填这两类,成稿链接就只躺在 tasks.deliverable_url 里,页面上不存在。
顺带查出同族第二个洞:syncTasks 的 INSERT 分支写 deliverable_url,UPDATE 分支不写。md 里补了链接、而库里那行已存在时,链接永远进不来——表现得像「同步不工作」,实际是两条分支的字段集不对称。
为什么跨了两轮迭代没被发现
- 之前三份任务书的成稿链接,是手写进正文的「## 交付情况」小节的——页面看起来一直是对的;
- frontmatter 的
deliverable确实有一条读路径(任务书首页的「教程类标杆」板块),所以「这字段没人用」的直觉不成立; - 直到发布方第一次真的走「只在管理台填、正文一个字不改」这条从没跑过的路径,缺口才现形。
四条可复用 insight
1. 写入方不等于展示方(同族第三形态)
一个字段有人写、有库存、接口也返回,不等于有人读它。链路上任一环没接,功能就等于不存在。
- 加运行时字段时对照三件套:存库 → 接口返回 → 页面渲染,缺一条就是隐形字段;
- 读路径要挂在收敛后的那个字段(
tasks.deliverable_url)上,不是某一条写入口的流水表(deliveries)——流水表只记「谁在什么时候提交过」,不回答「现在的成稿是哪一份」; - 验收问句从「数据存进去了吗」改成「页面上哪一行会因此变化」。
边界:只写不读也可能是对的(审计、留痕字段本就不上页面)。判据是「这个字段存在的目的是给人看吗」。
2. 手工兜底会掩盖缺失的系统路径
人用手写内容替系统把事做了,缺口就永远暴露不出来。发现某个功能「靠人手写补上」时要记一笔——那是系统缺口的坐标,不是工作习惯。
新增字段后至少走一遍不靠手写的路径。本轮的判据就是「只在管理台填,正文一个字不改,页面变不变」。
边界:手工兜底本身没错,它是上线速度的合理代价;错的是把兜底当常态而不记账。
3. upsert 两条分支的字段集要对齐
INSERT 写了某列、UPDATE 没写,表现为「只有第一次同步生效」,最像「同步不工作」。
- 改 upsert 时把两条分支的列清单并排列出来比对,不逐条读代码;
- 真相源字段与运行时字段混在一张表时,「只在空时回填」写成
deliverable_url = CASE WHEN deliverable_url = '' THEN ? ELSE deliverable_url END,比在应用层判断更难写错——库里已有的(承接人提交的、管理台填的)都比 md 新,不许覆盖;md 补了而库里空着,也不该让链接永远进不来。
4. 运行时字段的验收要造出「真相源与库不一致」的那一格
md 与库一致时,看不出到底是谁在生效。本轮自测造了三格:
| 场景 | md | 库 | 期望 |
|---|---|---|---|
| 用户踩到的那个 | 无链接 | 管理台手填 | 显示(修复前:不显示) |
| 证明「库为准」 | 有链接 | 空 | 隐藏 |
| 常态 | 有链接 | 同一条 | 显示 |
只有第二格能证明「数据库为准」这条设计真的成立。双源字段的验收矩阵至少这三格,造状态用临时库 + 管理台接口,不许动生产数据。
本机验证姿势(可直接复用)
要验的是浏览器侧注水逻辑,构建产物证明不了。本机没装 playwright 包,但 playwright 的浏览器二进制在 %LOCALAPPDATA%\ms-playwright\:
- 用
chromium_headless_shell-*/chrome-headless-shell-win64/chrome-headless-shell.exe,参数--disable-gpu --no-sandbox --no-first-run --user-data-dir=<临时目录> --no-proxy-server --virtual-time-budget=5000 --dump-dom <url>; - 别用
chromium-*/chrome-win64/chrome.exe --headless=new --dump-dom:同参数会挂住不返回(实测超 3 分钟)。目录名是chrome-win64不是chrome-win; --no-proxy-server与 curl 的--noproxy 127.0.0.1,localhost都是必须的,本机代理会把回环请求也劫走;- 本地环境:平台服务指向临时 sqlite +
dist/tasks/index.json当清单,另起astro preview(本地 base 是/above-the-web/,CORS 白名单已含127.0.0.1:4321),用管理员令牌 PATCH/api/admin/tasks/<slug>造各种库内状态; - 测动效路径还要加
--blink-settings=prefersReducedMotion=false,见 reduced-motion本机陷阱_动效降级纯淡入而非跳过_v1。
关联文档
- 任务系统演进_站内新建管理台拆分首页瘦身_v1 —— 直接前情:那篇给
fee_override配齐了「存库 + 接口 +renderFee()」三件套,本篇是同一张表上另一个字段只做了前两件的后果 - ⭐ 静态站接账号系统_内容归git状态归库的双源切分_v1 —— 地基:
deliverable_url列与「正文归 git、状态归库」的双源框架都出自那轮;本篇是该框架的第一个渲染缺口 - ⚠️ 零作品的比赛_界面演完不等于链路存在_v1 —— 同族第二形态(界面全在、
submissions表 0 行);本篇是它的镜像:数据在库里、页面不问它。两篇合起来是一句话的两头——验收既要问「跑完该多出什么数据」,也要问「库里有了页面读了吗」 - ⚠️ 赛事状态机到点不切换_写入方不等于推进方_v1 —— 同族第一形态:字段有人写、有人显示,没人在到点推进它
- ⚠️ 云端定时内容生产连环坑复盘_全绿不等于已发_v1 —— insight 2 的前例:「手动跑通会掩盖自动路径」,本篇是它在内容层的形态(手写正文掩盖缺失的渲染路径)
- ⭐ 交付前实测证伪律_v1 —— insight 4 的上位律:验收要造出能证伪的那一格,不是跑一遍看着对
- ⚠️ Claude预览环境不派发滚动事件_滚动类功能无法行为验证_v1 —— 无头 Chrome 三层退级的来源,本篇的自测姿势是它在本机的又一次落地
- 复盘事实先行原则 —— 本篇顶部事实冻结区按它执行
- 项目内复盘全文:
E:\above-the-web\docs\postmortem-2026-07-30-deliverable-link.md - 09_平台工程索引 —— 平台工程区入口
方法论验证等级
| 本轮 insight | 等级 |
|---|---|
| 写入方不等于展示方(存进库不等于看得见) | ⭐⭐ 二次验证(同族第三形态) |
| 手工兜底会掩盖缺失的系统路径 | ⭐⭐ 二次验证 |
| upsert 两条分支的字段集要对齐 | ⚠️ 首次发现 |
| 双源字段验收要造出「只有库里有」那一格 | ⚠️ 首次发现 |
没有三次验证的方法论,不用于指导下一次决策——只作为「值得继续观察的假设」。前两条已二次成立,下次做同类迭代时按它们先自查再动手。