关于 objectstack-upgrade
objectstack-upgrade 是面向 ObjectStack 项目的技能,用于跨协议主版本迁移元数据。它将自动转换与需人工判断的业务决策分开,并通过验收产物界定升级完成状态。
主要功能
- 01预检协议版本
- 02重放并检查迁移步骤
- 03验证迁移元数据的 Schema
- 04预览并应用存储行迁移
- 05归类语义迁移遗留项
- 06跟踪验收报告与数据门控
适用场景
- 01规划跨多个协议主版本的升级
- 02追踪并审查自动元数据改写
- 03验证后转换存储元数据行
- 04与项目所有者处理弃用字段和 API
MCP 相关
这是 Agent 技能,并非 MCP 服务或客户端;输入未说明其与 MCP 的直接关系。
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ objectstack-ai/ objectstack --skill objectstack-upgrade安装后在 Claude Code 中输入/objectstack-upgrade确认已加载。
附带文件evals/protocol-major-upgrade.jsonreferences/examples-upgrade.md
详细介绍
跨协议主版本升级 ObjectStack 元数据项目
预检 → 机械迁移链 → 语义遗留项 → 验收报告
⛔ 边界 — 首次执行命令前请阅读
绝不要手动编写迁移链已经会应用的重写。 如果某个键已重命名,转换表就知道该如何重命名;运行迁移链会将每项重写归属到对应的迁移步骤,并证明结果符合架构。手动编辑两者都做不到,而且迁移链一旦新增条目,就会悄然产生偏差。
绝不要添加宽容读取逻辑来让旧元数据加载。 不要使用 ?? 别名,不要添加“接受两种拼写”的分支,也不要在项目自身代码中进行强制转换。某个键之所以被废弃,是因为没有任何机制强制使用它,或者因为最终只保留一种拼写;在使用方重新接纳旧写法,就会让废弃操作刚刚消除的缺陷重新出现在客户的仓库中,且没有任何关卡能发现它。
绝不要猜测所有者的意图来解决遗留项。 这些遗留项之所以存在,正是因为它们涉及业务决策。何时自行决定、何时询问,请参阅第 2 层——这是本指南中最重要的一段。
没有验收材料,绝不要报告“已升级”。 validate 通过并附上报告,才是机器判定标准。缺少其中任一项,状态都是进行中,无论差异看起来如何。
每次提交只包含一个 conversionId,前提是项目的评审惯例允许如此。该 ID 就是提交主题行,也是提交的理由。
快速开始
# 0 · preflight — what major is this project on, what major is installed?
grep -rn "protocol" objectstack.config.ts package.json | head
node -p "require('@objectstack/spec/package.json').version"
# 1 · mechanical — replay the chain (writes only --out; --write also rewrites the sites it can prove)
os validate > .upgrade/validate-before.txt 2>&1 || true # the control, kept
os migrate meta --from 16 --step
os migrate meta --from 16 --json > .upgrade/migrate.json
os migrate meta --from 16 --out .upgrade/migrated.stack.json
# 2 · semantic residue — harvest the prescriptions, then work each item
node -e "console.log(require('fs').readFileSync(require.resolve('@objectstack/spec/package.json').replace('package.json','spec-changes.json'),'utf8'))" > .upgrade/spec-changes.json
# 3 · acceptance — all four, not three
os validate # green (compare against validate-before.txt)
tsc --noEmit # tombstones type the retired keys as `never`
os migrate meta --from 17 --json # `applied` must be [] (see §3.3)
# → write .upgrade/REPORT.md (template in §3.4)
0 · 预检
确认 FROM 主版本 — 不要猜
--from 指元数据编写时所依据的协议主版本,而不是已安装的版本。以下三个来源按权威性排序:
- 栈配置中的
manifest.engines.protocol— 声明的答案,会在启动握手时检查。它是一个范围,而非版本:每个脚手架和示例都使用插入符形式(engines: { protocol: '^17' }),而精确的'16.0.0'只是仅含一个版本的范围。取它指向的主版本——下限:'^17'→--from 17,'>=15 <18'→--from 15,绝不要取上限。 - 项目曾安装过的最后一个
@objectstack/spec主版本 — 如果清单缺失或过时,请查看锁文件历史(git log -p pnpm-lock.yaml | grep -m5 '@objectstack/spec')。 - 询问。 一个两年未更新的项目,其清单写着 16,只是一种说法,并非实际测量。如果 (1) 和 (2) 不一致,就先查明情况 — ⛔ 绝不要默认采用较低版本:默认值翻转(3.3)会将其约束写入一个已经超出该主版本的源文件。
一次跨越多个主版本才进行升级,正是这套流程要支持的场景:os migrate meta --from 16 会按顺序重放每一步,不要求每次只升级一个主版本——最低可降至迁移链支持的下限,目前是 16。
修改任何内容前,先确保工作成果便于评审
git checkout -b upgrade/protocol-17
mkdir -p .upgrade
.upgrade/ 目录是交付成果的工作区:其中包含机器输出(migrate.json、migrated.stack.json、spec-changes.json、retired-names.txt)和人工输出(REPORT.md)。通常会将这些文件保留在仓库中供评审,并在合并时删除——请与所有者商定。
从迁移后的目录重新安装技能
17.x 项目的技能通过 skills add 安装;其 skills-lock.json 条目中的 sourceType: github 会导致同步跳过这些技能(退出码为 0),而 Agent 仍会读取 17.x 的技能文本。请移除这些技能(不存在的名称会跳过),使用安装项目时所用的包管理器,按已安装 spec 的版本添加 @objectstack/skills,然后复制它:
npx skills remove objectstack-ai objectstack-api objectstack-automation \
objectstack-data objectstack-formula objectstack-i18n objectstack-platform \
objectstack-pm-dispatch objectstack-query objectstack-ui objectstack-upgrade -y
pnpm add -D "@objectstack/skills@$(node -p "require('@objectstack/spec/package.json').version")"
# npm: npm install --save-dev "@objectstack/skills@…" · yarn: yarn add -D "@objectstack/skills@…"
npx [email protected] experimental_sync --agent claude-code --copy -y
1 · 机械层 — 调用迁移链
os migrate meta 的实际作用
os migrate meta --from 16 # replay 16 → current
os migrate meta --from 16 --step # per-hop checkpoint (bisect a failure)
os migrate meta --from 16 --to 17 # stop at a specific major
os migrate meta --from 16 --json # machine-readable result
os migrate meta --from 16 --out migrated.json # write the canonicalized stack
os migrate meta --from 16 --write # rewrite the proven sites in place
os migrate meta --from 16 apps/crm/objectstack.config.ts # pick the stack explicitly
它会加载堆栈配置,在不应用加载时转换流程的情况下对其进行规范化,将每个主要版本的转换依次作为链式步骤重放,然后根据当前架构解析结果,以确认输出有效。它会输出:
Applied N mechanical change(s)— 每个重写的位置占一行,格式为path: from → to (conversionId)。这是已标注来源的差异。N manual change(s) require your judgment— 你跨越的主要版本对应的转换链语义条目,每项都包含一行why和一行verify。这是第二层的输入,不是可以忽略的警告。Migrated stack is schema-valid— 或者提示当前尚未通过架构校验,这意味着仍有残留项阻止解析。- 待处理的数据迁移,当转换链跨入包含按部署设置的数据门槛的主要版本时会显示 — 详见下文。
⚠ 每位运维人员都会感到意外的一件事
默认情况下,os migrate meta 不会重写任何源文件。 它会列出机械性编辑,并且只写入 --out JSON 快照。--write 会原地重写它能追溯到某个项目文件中某个字面值的每一处编辑,列出其他未写入编辑及其原因,绝不写入语义变更;如果对写入后的文件重新运行转换链时结果不一致,它会还原所有文件并以状态码 1 退出。
已存储行:重新水合时会重放相同的转换
部署的 sys_metadata 行是另一个处理对象。读取时会自动处理它们 — 元数据加载器和 ObjectQL 插件都会通过 applyConversionsToStoredItem 处理每个已存储行,在单个条目上重放转换链,包括已从加载路径中移除的条目。因此,在协议 16 下写入的行会以协议 17 的结构重新水合,无需任何人手动编辑。
重新水合是在读取时进行的投影:磁盘上的行会保留旧结构,你绝不应手动编辑 sys_metadata。若要将其永久化,请运行已存储数据转换流程 — 默认只读:
os migrate meta --stored # preview, writes nothing
os migrate meta --stored --type view --type object # narrow the pass
os migrate meta --stored --apply --yes # rewrite the rows
⛔ 对你而言,--yes 不是可选项。 不加它时,--apply 会请求确认;而在任何非 TTY 环境中 — 即每个 Agent 会话 — 它都会拒绝执行,并以状态码 1 和 confirmation_required 退出。
--stored 不接受 --from:已存储行携带自身的历史记录,因此该流程会重放整个转换链。
数据迁移不是元数据迁移
当转换链跨入包含按部署设置的数据门槛的主要版本时,运行结束时会逐项列出这些门槛、未运行会造成的后果,以及 --apply 是唯一写入模式这一事实。请阅读该输出,而不是自行推断。它无法告知你这些迁移是否已运行:这需要在每个部署自己的数据库上执行,元数据升级不会检查这一点。
⛔ 请将运行输出中的每个门槛都按名称标记为 pending,并纳入报告。 没人获知的门槛,就没人会处理。
2 · 语义残留 — 需要你处理的部分
转换可以重命名键、删除失效键,或将值提升到其声明所在的区块。它无法替你做决定。所有无法自动处理的内容都会落在这里。
2.1 收集所有指引来源——它们都随包提供
这些迁移指引不在必须联网才能访问的文档网站上,而是随已安装的软件包一起提供。根据已发布的 @objectstack/spec 文件列表进行实测:
| 来源 | 在使用方项目中的位置 | 包含内容 |
|---|---|---|
| 迁移链结果 | os migrate meta --from N --json → .specChanges | 你跨越的每个主版本对应的转换和语义条目。从这里开始——它根据已安装的规范计算,因此永远不会过时。 |
| D4 投影 | node_modules/@objectstack/spec/spec-changes.json | 每个主版本的相同数据,离线可用:perMajor[].converted 和 perMajor[].migrated。 |
| 墓碑指引 | node_modules/@objectstack/spec/json-schema/** 和 src/**/*.zod.ts | 每个已弃用键的 [REMOVED] … 修复说明,可通过 grep 搜索。 |
| FROM → TO 表格 | node_modules/@objectstack/spec/CHANGELOG.md | 每项弃用的说明,包括“应改写为何种内容”表格。这就是软件包会附带变更日志的原因。 |
| 错误本身 | 你的解析输出 / tsc 输出 | 同一条修复说明字符串会在你遇到问题时显示。 |
# every tombstone prescription the installed spec carries, deduped (`| wc -l` sizes it)
grep -rho '\[REMOVED\][^"]*' node_modules/@objectstack/spec/json-schema/ | sort -u
# protocol version, support floor, and this crossing's conversion / semantic counts
node -e "
const p = require.resolve('@objectstack/spec/package.json');
const j = require(p.replace('package.json','spec-changes.json'));
const to = parseInt(j.protocolVersion, 10);
const e = j.perMajor.find(x => x.to === to);
console.log(j.protocolVersion, 'floor', j.supportFloor, '| →', to + ':',
e.converted.length, 'converted,', e.migrated.length, 'semantic');
"
# the FROM → TO table for one retired key
grep -n -B4 -A20 'transform' node_modules/@objectstack/spec/CHANGELOG.md | less
⛔ 绝不要在报告中沿用记忆中的数量或固定表格。 此处的每个数字都取自当前安装版本;请在升级开始时测量一次,并在报告中再次测量。
使用方项目无法访问以下内容,因此不要指引任何人去那里:转换和迁移注册表(
src/conversions/registry.ts、src/migrations/registry.ts)以及平台仓库生成的升级指南都不包含在已发布的软件包中——其中只有src/**/*.zod.ts。
2.2 三类残留问题
R1 · 意图选择——某个键已弃用,但没有唯一且无损的目标。 转换会删除该键(以便项目能够解析),并在需要时发出注明位置的提示。该键原本的用途仍须有所归属,而归属何处属于业务决策。
R2 · 自定义代码调用了已弃用的 API。 迁移链的语义条目会列出这些情况:不再存在的服务槽位、已移除的引擎方法、已重命名的上下文字段。元数据解析正常,但项目自身的 TypeScript 会出错——更糟的是,代码可能继续编译,却读取到 undefined。
R3 · 仍在讲解旧结构的文字。 README、注释、ADR、种子数据夹具,以及项目自己的 AI 约定文件。不会有任何报错,而下一个阅读代码仓库的 Agent 会照此重新编写已弃用的结构。
→ 原文移至 references/examples-upgrade.md § 2.3 一个 R1 实例——已弃用的字段映射 transform。
2.4 自行决定,还是询问项目负责人
以下三点全部满足时,自行决定:
- 迁移说明明确指定了一个目标。 墓碑或转换摘要给出了唯一的 FROM → TO,且值保持不变。
- 项目中有证据。 在仓库中运行 grep 就能确认——已有的工具归属技能、已有的导入,或谓词已经引用的字段。
- 选错会导致检查失败。 错误选择会导致
tsc或validate失败,而不是悄无声息地改变行为。
以下任意一点成立时,询问项目负责人:
- 存在两个或更多真实目标,且选择代表业务声明——如上文的
transform情况。 - 改动可在测试未失败的情况下被观察到——安全态势(身份验证默认值)、行可见性(访问谓词)、重试次数、保留期限。此处判断错误会悄无声息地发布,并被审计人员发现。
- 必须在新的位置重新声明能力,因此选错会移除能力,而不是导致构建失败。必须将 Agent 工具移入特定技能,就是典型情况。
- 源内容在他们的仓库中已失效或没有文档记录——无从判断。请如实说明;不要凭空编造理由。
如何询问。 每个事项发一条消息,包含:位置(文件和路径)、原文迁移说明、各选项及其代价、你的建议和理由,以及对方做出选择后你会验证什么。不要只问“transform 应该怎么处理?”——那等于把阅读工作推回给对比变更内容最缺乏上下文的人。
等待期间,不要停下升级工作。 在报告中将该事项标为 AWAITING DECISION,完成机械性工作,并继续推进。有未决事项的项目仍可通过架构校验;只是还没有完成。
2.5 处理 R2——项目自身代码中的已弃用 API
语义条目列表就是搜索清单。对于每个条目,请在项目自身的源代码中 grep 其指定的字符串:
# from the chain's own output — the surfaces it says it cannot fix for you
os migrate meta --from 16 --json | node -e "
let s=''; process.stdin.on('data',d=>s+=d).on('end',()=>{
for (const t of JSON.parse(s).todos) console.log(t.surface);
})"
# then, for each surface, search the project (not node_modules)
grep -rn "<surface-token>" src/ app/ --include='*.ts' --include='*.tsx'
一个曾让实际升级多绕一圈的陷阱:重命名不一定就是修复。 如果旧的读取操作本身就有误,重命名只会迁移缺陷,而不是迁移代码。应用 replacement 之前,请先阅读语义条目的 reason。
2.6 处理 R3——清扫文档说明
等结构调整妥当后,最后再运行,并覆盖整个仓库:
# every retired key name the installed spec knows, as a search list
grep -rho '\[REMOVED\] `[^`]*`' node_modules/@objectstack/spec/json-schema/ \
| sed 's/.*`\(.*\)`.*/\1/' | sort -u > .upgrade/retired-names.txt
然后检查项目的 *.md、注释、种子测试数据和 AI 规范文件。规范文件最重要:下一个 Agent 会在编写任何内容之前加载它,因此如果其中仍留有已弃用的结构,这些结构就会在升级结束很久之后的下一项功能开发中重新进入代码库。
3 · 验收——“已升级”的含义
四项交付物。其中三项由机器检查;第四项供人阅读。
3.1 类型检查通过
tsc --noEmit
已弃用的键不只是从架构中移除,还会声明为输入类型为 never 的墓碑。给它赋任何值都会在编译时失败,错误会出现在编写代码的位置,在任何代码运行之前。因此,tsc 通过就能有力证明,类型化源代码中已不再残留已弃用的键。
3.2 解析门禁生效
同一个墓碑也会在解析时拒绝该键,并附带迁移说明,而不是泛泛提示“无法识别的键”。这是捕获元数据的途径,因为 tsc 看不到 JSON 文件、数据库行以及任何在运行时构造的内容。你无需进行任何操作来启用它——但必须证明它在此项目中生效,因为会静默剥除字段的架构与会接受这些字段的架构无法区分。请参阅反向检查。
3.3 验证
os validate # green is the criterion
os validate --strict # warnings become errors — agree with the owner whether this is the bar
os validate 会执行两轮检查:协议架构检查(墓碑规则会在此拒绝)和编写时规则集。请分开解读两者——有关缺少共享模型或选择字段没有选项的规则检查结果,属于既有项目质量问题,而非升级残留。要确认各自属于哪类,请在开始前对未升级的源代码运行一次 os validate,并将输出保留为对照。
⚠
validate结果为绿色,不表示这条链已无待办事项有些转换仅存在于迁移链中:加载器有意不应用这些转换,且没有墓碑规则拒绝旧结构,因为这类变更是默认值翻转,而非重命名——自动应用会给有意省略该约束的源代码强加约束。跨越 16 → 17 时就有一个这样的转换:
field-required-notnull-explicit,它会写入原先由required单独隐含的物理storage.notNull。仅包含这种结构的项目,验证结果仍为绿色,但迁移链还有待办事项。因此,
validate结果为绿色是必要条件,但并非充分条件;通过回放来补上这一缺口:os migrate meta --from <target-major> --json # `applied` 必须为 []请查看
applied,而非标题:对于大多数发布线,--to默认为本次构建包含迁移步骤的最高主版本,也就是比已安装主版本高一个,因此todos会包含下一个主版本的语义残留——它确实存在,但不属于本次升级的范围。
→ 原文移至 references/examples-upgrade.md § 3.4 报告——人工检查部分。
3.5 证明门禁确实有效,不要想当然
在将解析门禁作为证据报告之前,先让它触发一次:取一个迁移链本来会转换的值,在迁移后将其恢复,再进行解析。
# a stack that still carries a retired key, fed straight to the schema
os validate .upgrade/residue-probe.config.mjs
请将探测数据写成纯数据字面量——⛔ 不要调用 define*。os validate 会在非源代码编写模式下加载配置,而每个 define* 辅助函数都会执行 Schema.parse(),因此,若按真实配置的写法编写探测数据,就会在加载过程中抛出错误,根本无法触发你要证明的门禁。
请在运行前预测结果,然后记录实际结果——每一行中,“通过了”代表的情况都不同:
| 结果 | 含义 | 你的验收证据 |
|---|---|---|
| 被拒绝,错误中包含修复提示文本 | 键已被墓碑规则标记。不是“无法识别的键”,也不是弃用标签——修复建议本身就是错误。 | 引述拒绝结果本身。 |
| 被接受,且迁移链会重写它 | 转换具有有效的加载路径接受窗口。 | 迁移链的差异——而非解析门禁。 |
被接受,且 validate 仍为绿色 | 仅存在于迁移链中的转换(3.3)。 | 只有从目标主版本开始回放,才能发现这一类。 |
3.6 已存储的行:验证它们,而不是轻信
以上四项工件覆盖了编写的源代码。对于部署,已存储内容的检查本身就是断言——以只读模式运行,并根据退出代码分支处理:
os migrate meta --stored --json # 0 = every row canonical, 1 = work left
这样,“此部署使用协议 N”才是 CI 检查,而非主观判断。没有任何门禁要求必须运行此检查;读取时的重新生成仍是保障。
→ 原文移至 references/examples-upgrade.md § v17 规范结构,已编译。
故障模式
| 症状 | 实际情况 | 修复方法 |
|---|---|---|
migrate meta 报告有更改,但文件没有变化 | 这是预期行为——默认运行只会列出更改。 | 传入 --write,或手动应用列出的编辑;然后从目标主版本重放,以确认更改数为 0。 |
| 从目标主版本重放仍会应用更改 | 移植不完整,或者某个源在运行时根据迁移链从未见过的结构生成元数据。 | 对照 --out 生成的结果进行差异比较;在动态构造元数据的代码中搜索 conversionId 对应的部分。 |
validate 通过,但某项功能悄然失效 | 这是一个 R2 残留项:读取重命名键的代码现在读到的是 undefined。 | 实际运行这条路径。解析通过并不能说明项目自身代码中的 ?? 链没有问题。 |
validate 一开始就通过,所以“没有需要升级的内容” | 这是仅由迁移链完成的转换——没有 tombstone 会拒绝它,因此不会有任何报错。 | 仍然要重放迁移链。validate 通过是必要条件,而非充分条件;参见 3.3。 |
validate 报告的发现项与已弃用键无关 | 这是作者时规则检查,而不是架构检查。 | 对照升级前的 validate 基准结果进行差异比较。升级前已有的发现项不属于本次升级的范围。 |
| 已弃用的键往返时没有报错 | 承载该键的架构不是严格模式,且该键正被剥离;或者该键仍处于有效加载路径的时间窗口内。 | 确定是哪种情况——两者需要不同的验收证据。参见反向检查。 |
--apply 被拒绝/仅存储数据的标志被拒绝 | --apply、--yes、--force、--type、--database-url 仅在使用 --stored 时有意义。 | 添加 --stored,或移除该标志;作者源文件迁移链只会写入 --out,并且仅在使用 --write 时写入源文件。 |
MigrationFloorError | --from 早于迁移链的支持下限。 | 先通过较早的迁移途径升级到该下限;该下限是发布策略边界,并非疏忽。 |
要用脚本运行而不是阅读结果?上面每个 --json 错误都带有稳定的 error 代码,可用于分支处理——stored_only_flag、missing_from_major、unsupported_from_major、confirmation_required、database_busy——表格中的说明对人类读者表达的是同一事实。
跨技能路由
- 编写修正后的元数据——加载与你要修正的结构对应的领域技能(data、ui、automation、ai、api、**i18n);每个技能都会将你引导至 formula,以处理你重写的任何 CEL。
- 升级过程中遇到的运行时、插件和 CLI 问题——加载 platform。