Files
dsh_ai1net_server/交付物/StoryForge端到端验收-未通过-20260925.md
admin c1b5e4d966 chore(工作区): 全量入库 + 补齐 .gitignore(以工作区为准)
- 变更规模:新增 514 / 修改 62 / 重命名 155 / 删除 4(归档重组与文档轮次)
- .gitignore 修:`归档/**/db-cwd归一-备份-*/` —— 原规则写绝对层级(归档/db-cwd归一-…),
  目录搬进 归档/配置与备份/ 后**静默失效**,43 MB 的 DB 备份又变成未跟踪
- .gitignore 补:嵌套 git 内部数据(归档/内嵌git-20261008/、归档/skills-git-旧线-20261007/dotgit-原样移出/)
- .gitignore 补:运行态与部署副本(.workbuddy/collab/、.workbuddy/tools/、.workbuddy/.load-pending、.workbuddy/tmp-*)
- .gitignore 补:备份件(*.bak-*)
- 未跟踪文件从 2190 降到 890(其余为 归档/ 归档件与 .workbuddy/memory/ 知识文件,按口径入库)
2026-10-10 23:13:22 +08:00

19 KiB
Raw Permalink Blame History

StoryForge 端到端验收 · 真机读数(未通过)

验收线:接续入口_StoryForge验收线_20260925.md 第 1 棒 验收容器:平台侧(模拟 admin 操作)|对象:@dsh-local/storyforge 0.4.9 日期:2026-09-25 14:29–14:40 | 服务器:47.77.182.89(开发环境,R8) 结论口径:六步里 2 步达成、1 步部分达成、2 步未达成、1 步未达成(同因)。卡点单一且有确定根因,平台侧一行可修。


〇、一句话结论

上传 ✅ → 建库 ✅ → 发布 ❌ → 启用 ❌ → 回读 ⚠️。 卡在同一个闸门:平台数据面比对口径不一致(W7)⇒ 任何声明 bigtext 类型的插件,只要建库成功就永久 blocked,之后发布与启用都拿 409。 用户面零影响:共享层里仍是 0.3.0(0.4.9 未发布成功),已启用该插件的用户照旧工作。

步 结果 原因码
2 上传 ✅ 达成 —(回执缺 tgzSha256 ⇒ W1 复现)
3 建库 ✅ 达成(state=ready,6/6 执行成功) —
4 发布 ❌ 未达成 W7(下游闸门拒绝)
5 启用 ❌ 未达成(同因,未创建任务) W7
6 回读 ⚠️ 部分达成(对账 clean,但三面状态打架) W8

一、形态与读锁(回报 1/6)

项 读数
服务器侧 op-lock claim storyforge-e2e @ 2026-09-25 14:29(占用者 StoryForge验收线-第1棒);release @ 14:3x,归属校验通过;status 复核 = 无人占用 ✓
是否撞第 25 棒 否。开工前 handoff-guard.sh --locks = 「全局锁 空闲」;op-lock.sh status = 「无人占用 ✓」
本机域锁 StoryForge验收线-第1棒,域 3 个:aliyun-dsh-server/交付物 · aliyun-dsh-server/tmp · aliyun-dsh-server/接续入口_storyforge验收线_20260925.md
影响面声明(claim 时写入) 47 候选池上传/建库/发布 + admin 自身实例启用重装配;仅 admin 实例可能短暂重启,其他用户实例不受影响;预计 15–25 分钟

二、六步逐条:命令 + 实际输出(回报 2/6)

统一前置:SID = 47 上 node /opt/dshs/mksess.cjs admin 生成的 sid cookie(临时会话,user_agent='poc-curl2')。 全部经公网 https://ai1net.com(与门户同一入口,非回环),读数为真机。

步 1 · 取 admin 会话

ssh [email protected] "node /opt/dshs/mksess.cjs admin"      # ⇒ token(64 hex)
curl -s -b "sid=$SID" https://ai1net.com/api/plugins/business
  • token_len=64;探活 HTTP 200。
  • 收尾:DELETE FROM sessions WHERE user_agent='poc-curl2' ⇒ DELETE 1,复核残留 0。
  • ⛔ 未使用用户日常凭据;未插库以外的任何写操作。

步 2 · 上传交付件 0.4.9 ✅

# 体材:JSON,字段 file=base64、filename=<原名>(端点 src/web/routes/business-plugins.ts:1194-1207;限 180 MiB)
curl -X POST https://ai1net.com/api/plugins/business \
     -H 'content-type: application/json' -b "sid=$SID" --data-binary @02-upload-body.json

HTTP 200,耗时 4.78 s。 回执关键读数:

字段 实测
plugin.version / fileSize 0.4.9 / 1797711
replaced true(同池内旧 0.3.0 被替换)
compat.level / findings ok / [](扫描了 240 个平台包,sawPlatformRef=true)
blocked [](P0 零命中,未用 trust 覆盖)
warnings 20 条;规则集合 = network-egress / vm-eval / long-base64 / sensitive-env / hidden-or-git
dataPlane.state / declared pending / true(requiresMigration=true)
dataPlane.dbName dshs_pl_storyforge
dataPlane.plan.planHash 73d1dc286b0dcf776591d32d57c93717b7c609a42a09de0c78b6dc1ef471e7c3
versionMismatch false
backupTgz /opt/dsh/backups/plugins/_dsh-local_storyforge/0.4.9.tgz
tgzSha256 ❌ 字段不存在 ⇒ W1 复现

替代对账(补 W1 缺的证据) —— 三处副本指纹逐字相同:

位置 sha256
本机原包 dsh-local-storyforge-0.4.9.tgz 71d6d10220c252205b0a768bc1ec1b434240fabcbde38da339ef67c1d0927ae1
47 池内 /var/lib/dshs/business-plugins/_dsh-local_storyforge.tgz 同上
47 备份 /opt/dsh/backups/plugins/_dsh-local_storyforge/0.4.9.tgz 同上

⇒ 落盘无损;W1 缺的只是回包字段,值本身没问题(V2 的三元组现在可人工对账)。

步 3 · 建库三段 ✅

# ① 预演
curl -X POST .../api/plugins/business/datastore/plan -d '{"id":"@dsh-local/storyforge"}'
# ② 建库
curl -X POST .../api/plugins/business/datastore      -d '{"id":"@dsh-local/storyforge"}'
# ③ 迁移
curl -X POST .../api/plugins/business/datastore/migrate -d '{"id":"...","confirm":true,"expect":"<planHash>"}'

① plan —— HTTP 200,executable=true,executabilityNote=null,7 项、条条可读:

# kind target
1 create_database dshs_pl_storyforge
2 create_table p_storyforge_sf_projects
3 create_table p_storyforge_sf_docs
4 add_index p_storyforge_sf_docs~store_project_ref
5 create_table p_storyforge_sf_chunks
6 add_index p_storyforge_sf_chunks~doc_ref
7 create_table p_storyforge_sf_prefs

② datastore —— HTTP 200,outcome="created"、state="created"、elapsedMs=73。(CREATE DATABASE dshs_pl_storyforge 成功)

③ migrate —— 第一次 HTTP 409 plan_stale,第二次 HTTP 200。

  • 第一次带的是建库前的 planHash(73d1dc28…)⇒ 409,回包给出新 hash ceac1608a1da027645badb3a44a73313cc033433b5039f3132a1a708de4b71cf。
  • 判定:这不是缺陷。 建库消耗掉 create_database 那一项 ⇒ 计划必然变 ⇒ hash 变是设计使然(planHash 就是干这个的)。 门户 UI 在建库成功后会自动二次预演(web/portal.html:711 await showPlan(id))从而规避;本棒脚本第一版未复刻该次序。 ⚠️ 但该 409 回包是 {error, message, plan},无 step 字段 ⇒ W2 复现。
  • 第二次带新 hash ⇒ HTTP 200:
字段 实测
state ready
schemaVersion 1
createdDatabase false(建库已在上一步完成)
executed 6/6 全 ok(4 表 + 2 索引;create_database 不在其中,符合预期)
elapsedMs 48
backup / backupPath taken / /opt/dsh/backups/plugin-db/_dsh-local_storyforge/v0-2026-09-25T06-32-02-823.sql

步 4 · 发布 ❌ 未达成

curl -X POST .../api/plugins/business/share -d '{"id":"@dsh-local/storyforge"}'     # 先探冲突

HTTP 409

{"error":"datastore_not_ready",
 "message":"该插件声明了数据面但尚未就绪(当前状态:blocked)⇒ 暂不能发布到用户面。请先完成建库 / 迁移。",
 "state":"blocked","dbName":"dshs_pl_storyforge"}

原因码 = W7(见 §三)。⇒ 发布腿不可达,fileRef / tgzSha256 / treeSha256 三项无从取得。

步 5 · 启用 ❌ 未达成(同因)

curl -X POST .../api/plugins/mine/apply -H 'content-type: application/json' \
     -d '{"plugins":[{"id":"@dsh-local/storyforge","enabled":true}]}'

HTTP 409(耗时 1.48 s)

{"error":"datastore_not_ready",
 "message":"插件「@dsh-local/storyforge」的数据面尚未就绪(当前状态:blocked)⇒ 暂不能启用。请联系管理员完成建库。",
 "state":"blocked"}
  • ⚠️ 未返回 taskId ⇒ /api/plugins/mine/task/:id 无对象可轮询 ⇒ 启用腿的阶段链与 detail 都拿不到。
  • 闸门在创建任务之前(前置校验),未走到 pnpm 那一步 ⇒ V1(ENABLE_LIST_NOT_APPLIED / ENABLE_TASK_FAILED)本轮既未复现、也未证伪(详见 §五)。
  • 收尾核对 /api/plugins/mine:storyforge enabled=false、dataPlaneState="blocked"、dataPlaneReady=false ⇒ admin 实例未被改动。

步 6 · 回读一致性 ⚠️ 部分达成

读面 端点 storyforge 读数
候选池(实时预演) GET /api/plugins/business 0.4.9,dataPlane.state=**blocked**,summary={tables:0,columns:0,indexes:0},declOrigin=package-json-inline,versionDrift=false,diskVersion=0.4.9
共享层 GET /api/plugins/shared 0.3.0(tgzSha256=98ff5e82…、treeSha256=1c45c841…、fileCount=117)⇒ 未被 0.4.9 替换(发布未达成,符合预期)
数据面台账 GET /api/plugins/business/datastores state=**ready**、schemaVersion=1、planHash=73d1dc28…
数据面对账 GET /api/plugins/business/datastores/reconcile ok=true、clean=true、databases == ledgered(dshs_pl_dsh_plugin_mcn_suite、dshs_pl_storyforge)、orphans=[]、missing=[]
  • ✅ 建库腿是干净落地的:PG 实体库与台账完全一致,无孤儿、无缺失。
  • ❌ 但同一插件的「数据面状态」在 admin 面有两个互相打架的值:台账说 ready,实时预演说 blocked ⇒ W8。
  • ❌ 三面对「这个包」的结论不一致(池 0.4.9 / 共享层 0.3.0),这是发布未达成的直接后果,非独立缺陷。

步 7 · 收尾 ✅

  • 临时会话:DELETE 1,复核 残留 0。
  • op-lock:已 release,status = 无人占用。
  • 异常项归位 ⇒ §四。

三、根因(W7 · 可 1:1 重放)

病灶:平台比对「声明类型」与「库内类型」时用了两套口径。

环节 位置 事实
① 插件声明 包内 package.json p_storyforge_sf_chunks.body 声明类型 = bigtext
② 类型表 src/db/plugin-data/schema.ts:42-52 9 种中性类型含 bigtext
③ PG 映射 src/db/plugin-data/schema.ts:59-69 bigtext: 'TEXT'
④ 实际建表 DDL 本棒取证(04-plan.json item 5) 生成的是 "body" TEXT ✅
⑤ 库内回读 PG data_type text ⇒ normalizeCurrentType('text') = 'text'
⑥ 比对那一行 src/db/plugin-data/diff.ts:325 if (norm !== d.type) —— 左边归一后,右边原始声明字面
⑦ 结果 diff.ts:327-329 'text' !== 'bigtext' ⇒ forbidden{kind:'alter_type', detail:'类型变化 bigtext → text ⇒ 拒绝(DB-03 §四:不允许改列类型)'}

为什么只有 storyforge 中招、dsh-plugin-mcn-suite 却是 ready:其余 8 种中性类型的声明字面恰好等于中性名(text/integer/…),左右两边天然相等;只有 bigtext 是声明别名(PG 里并没有这个类型名),两边永远不等。

为什么建库前不报、建库后才报:比对分支要求「声明列与库内列两侧都有」(diff.ts:317-319:if (c === undefined) continue)。库不存在时无从比对 ⇒ 全部放行;建库一旦成功,下一次预演立刻命中。

为什么不可自愈:blocked 状态下 /datastore/plan 直接拒绝执行(本棒实测:executable=false、executabilityNote="当前状态 blocked 不允许建库(已就绪或声明非法)"、items 为空)⇒ admin 在门户里没有任何可点的按钮能把状态救回来。

影响面(超出本案):任何声明 bigtext 的插件,建库成功后永久不可发布、不可启用。

修法(平台侧,一行):比对前把声明侧也归一到中性名,例如 const wantNeutral = d.type === 'bigtext' ? 'text' : d.type,然后比 norm !== wantNeutral;diff.ts:329 的 detail 文案同步用中性名,避免把内部别名泄漏给 admin。

复现最小步骤:任意声明了 bigtext 列的包 → 上传 → 建库 → 迁移(成功、ready)→ 再 GET /api/plugins/business ⇒ 该行 dataPlane.state 变 blocked,forbidden 出现 alter_type。


四、归位清单(回报 3/6)

平台侧

# 级别 问题 判据 / 建议落点
W7 🔴 P0 数据面类型比对口径不一致:bigtext 声明 ⇒ 建库后永久 blocked(发布 + 启用双双 409) 修 src/db/plugin-data/diff.ts:325(声明侧归一);复现判据见 §三末
W8 🟡 P1 同一插件两个读面状态相反:/datastores 台账 = ready,/business 实时预演 = blocked。admin 无从判断哪个是真的 二者取一为主并互相标注(如台账存「写路径结论」+ 门户明示「可能已被后续声明变更推翻」);W7 修好后此分歧自然消失,但建议仍加口径说明,否则同类分歧会复发
W1 已登记 上传回执缺 tgzSha256 —— 本轮复现;但三处副本指纹实测一致 ⇒ 只是字段缺失 原判据不变(回包字段与插件线 sha256.txt 逐字一致)
W2 已登记 失败回执无 step —— 本轮在 migrate 的 409 上复现({error,message,plan},无步骤名)。⚠️ 另:本轮触发的两处 409 都带可读 message,比原判据里的"只有 {error}"要好 补 step
W3/W4/W5/W6 已登记 本轮未触及(撤下按钮 / 规范文档 / 释放锁连坐 / 判据替换) —

插件侧

# 级别 问题 判据
V4 🆕 P1 package.json 里 p_storyforge_sf_chunks.body 声明为 bigtext。把声明改成 text 即可绕开 W7(语义等价:平台本来就把 bigtext 译成 TEXT),本包即可发布/启用 改后重打 0.4.10 → 上传 → /business 该行应为 ready,forbidden 为空
V1 ⚠️ — 第 2 单(启用腿)本轮无法判定 —— 被前置闸门拦在任务创建之前,未见 taskId。不是"仍失败",也不是"已修好" 解 W7(或 V4)后重跑;若届时仍失败,带 /api/plugins/mine/task/:id 的 detail 原文回来
V2 — 三元组对账(版本 / 落盘路径 / 指纹):本轮人工对账通过(0.4.9 / /var/lib/dshs/business-plugins/_dsh-local_storyforge.tgz / 71d6d102…)。W1 落地后改为回包自动对账 —
V3 — 复现信息点名账号与操作:本件已按此写(见 §二每步的 47 实例 + 端点 + 参数) —

🔴 关于 V4 的边界:本轮只报告、不代改(dsh-plugin-forge 只读)。⚠️ 同时请不要把 V4 当成 W7 的替代品 —— 平台侧 W7 必须修,否则下一个用 bigtext 的插件会掉进同一个坑。


五、第 2 单(启用腿)结论(回报 4/6)

不可判定 —— 未创建任务,ENABLE_LIST_NOT_APPLIED / ENABLE_TASK_FAILED 未被触发。

  • 拦点 = datastore_not_ready 前置校验(HTTP 409,1.48 s,无 taskId),未进入 pnpm 装配阶段。
  • 第 20 棒修的 EPERM chmod 根因、第 16/17 棒补的 task.detail,本轮都无从验证。
  • ⛔ 我没有拿另一个 dataPlane ready 的包(如 admin 实例上在用的 dsh-plugin-mcn-suite)去"替跑" —— 那会扰动一个正在被用的插件,且答不了本单的问题(问的是 storyforge 的启用腿)。

六、门禁与回滚(回报 5/6)

项 内容
动了生产哪些面 ① 47 候选池:@dsh-local/storyforge 0.3.0 → 0.4.9(上传即替换,旧包无残留);② 新建实体库 dshs_pl_storyforge(4 表 + 2 索引);③ 其余全部只读
未动 ⛔ 未重启任何服务(restart dshs 未执行);⛔ 未改共享层(仍 0.3.0);⛔ 未改任何用户 dependencies;⛔ 未碰其他用户实例;⛔ 未 DROP 任何库
影响谁 / 断多久 仅本棒的临时 admin 会话(已删)。无服务中断,已启用 storyforge 的用户零影响(其环境仍指向共享层 0.3.0)。预期受影响用户数 0
留痕/素材 池备份 /opt/dsh/backups/plugins/_dsh-local_storyforge/{0.2.0,0.3.0,0.4.9}.tgz(0.3.0 = 1758443 B,在);库备份 /opt/dsh/backups/plugin-db/_dsh-local_storyforge/v0-2026-09-25T06-32-02-823.sql
回滚 ① 池回 0.3.0:重传 /opt/dsh/backups/plugins/_dsh-local_storyforge/0.3.0.tgz(同上传端点)② 库:DROP DATABASE dshs_pl_storyforge 不可逆 ⇒ ⛔ 不擅自删,只报告(按入口 §回滚)
留还是撤(自决,可推翻) 保留池内 0.4.9。理由:它是本轮验收对象,W7(或 V4)一落地就能直接续跑发布/启用,无需重传;撤掉会把复现态一起撤没。代价如实说:W7 修好前,门户/用户会把 0.4.9 看成"最新可启用",点下去得 409(安全但提示不佳)。要撤 ⇒ 上表 ① 一条命令

七、边界自证(回报 6/6)

  • ⛔ 未改插件包:E:/ProgramData/AIProject/dsh-plugin-forge/** 全程只读(仅 tar -xzf 抽 package/package.json 到本棒 tmp/ 供读取,包本体与技能目录零写入)。
  • ⛔ 未改平台 src/**(含 web/portal.html):只做了 grep / Read 只读取证。
  • ⛔ 未碰其他用户实例:全部操作在 admin 会话与候选池面内;/mine/apply 也只对 admin 自己(且被闸门拦下,未生效)。
  • ⛔ 未使用用户日常凭据;临时会话用后即删(残留 0)。
  • ⛔ 未 DROP 任何库、未改共享层清单、未推任何仓库。

八、证据文件(留 tmp/sf-e2e-20260925/,7 天内可复核)

文件 内容
03-upload-resp.json 上传回执(含 compat / warnings 20 条 / dataPlane / backupTgz;无 tgzSha256)
04-plan.json 建库前预演(7 项、executable=true、hash 73d1dc28…、sf_chunks 的 body TEXT)
05-datastore.json 建库回执(outcome=created)
06-migrate.json 第一次迁移 = 409 plan_stale(+ 新 hash ceac1608…)
07-migrate2.json 第二次迁移 = 200 state=ready、executed 6/6、库备份路径
08-share-probe.json 发布 409 datastore_not_ready(state=blocked)
09-business2.json /business 实测(blocked + forbidden{alter_type: p_storyforge_sf_chunks.body, 'bigtext → text'})
10-datastores.json 台账实测(ready,与 09 打架 ⇒ W8)
11-plan2.json 建库后实时预演(items=0、executable=false)
12-shared.json 共享层实测(仍 0.3.0)
13-reconcile.json 对账(clean=true、无孤儿/缺失)
14-mine.json /mine 实测(enabled=false、dataPlaneReady=false)
15-apply.json 启用 409 datastore_not_ready(无 taskId)

九、下一棒建议(不代做,供拍板)

  1. 平台先修 W7(一行);顺带把 W8 的口径说明补上 —— 修完即可原样续跑:发布 → 启用 → 回读。
  2. 若插件线希望不等平台:按 V4 把 body 声明改 text、重打 0.4.10 上传,本包立即通。
  3. 两条都通后,本验收线第 2 棒 = 补跑步 4/5/6 + 顺手验 W1(回包 tgzSha256)与 W2(失败回执 step)。