objectstack-upgrade

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Upgrading an ObjectStack metadata project across a protocol major

ObjectStack元数据项目跨协议大版本升级

This skill turns one session into an upgrade agent working on somebody else's metadata project. It has one job: take a project authored against protocol
N
and leave it authored against the current major, with the change proved rather than asserted.
preflight → mechanical chain → semantic residue → acceptance report
The upgrade is deliberately split into three layers, and the split is the whole design. Two of them are not yours.
LayerWho owns itWhat it is
1 · MechanicalThe CLI. You invoke it, you never re-implement it.
os migrate meta
replays the ADR-0087 conversion chain: deterministic, idempotent, fixture-tested, per-hop attributable.
2 · Semantic residueYou, with the project's owner.Everything a conversion cannot express: intent choices, custom code calling retired APIs, prose that still teaches the old shape.
3 · AcceptanceThe gates.Typed + parse-gated metadata, a green
validate
, and a report a human can read.
本技能将单次会话转化为针对他人元数据项目的升级代理。它只有一项任务:将基于协议
N
开发的项目升级为基于当前大版本开发,并验证变更有效性而非仅声明完成。
预检 → 机械转换链 → 语义残留处理 → 验收报告
升级流程被刻意拆分为三层,这种拆分是整个设计的核心。其中两层无需你手动操作。
层级负责方说明
1 · 机械层CLI工具。你只需调用它,绝不要重新实现。
os migrate meta
执行ADR-0087转换链:确定性、幂等性、经过测试用例验证,每一步变更都可追溯。
2 · 语义残留你与项目负责人所有转换链无法处理的内容:意图选择、调用已废弃API的自定义代码、仍沿用旧格式的说明文档。
3 · 验收层校验机制经过类型检查和解析校验的元数据、绿色
validate
校验结果,以及一份人类可读的报告。

⛔ The boundary — read this before the first command

⛔ 边界规则 —— 执行第一条命令前请务必阅读

Never hand-write a rewrite the chain already applies. If a key was renamed, the conversion table knows the rename; running the chain attributes each rewrite to a hop and proves the result is schema-valid. A hand-edit does neither, and it silently diverges the moment the chain gains an entry.
Never add a tolerant read to make old metadata load. No
??
alias, no "accept both spellings" branch, no coercion in the project's own code. A key was retired because nothing enforced it or because exactly one spelling survives; re-admitting the old one at the consumer is how the defect the retirement closed comes back inside the customer's repo, where no gate can see it.
Never resolve a residue item by guessing the owner's intent. The residue exists precisely because it is a business decision. The rule for when you decide alone and when you ask is in Layer 2 — it is the most important paragraph in this skill.
Never report "upgraded" without the acceptance artifacts.
validate
green plus the report is the machine criterion. Absent either, the status is in progress, whatever the diff looks like.

绝不要手动编写转换链已能自动处理的重写内容。如果某个键被重命名,转换表已记录该重命名规则;运行转换链可追溯每一处重写的来源,并验证结果符合 Schema 规范。手动编辑无法实现以上两点,且一旦转换链新增规则,手动编辑的内容会悄然偏离标准。
绝不要添加兼容逻辑来加载旧版元数据。不要使用
??
别名、“兼容两种拼写”的分支,也不要在项目代码中添加强制转换。某个键被废弃,要么是因为没有机制强制其使用,要么是因为仅有一种拼写保留;在消费端重新允许旧键使用,会让废弃本应修复的缺陷重新出现在客户仓库中,且无法被校验机制发现。
绝不要通过猜测负责人意图来处理残留项。残留项存在的原因正是因为它需要业务决策。何时自行决策、何时咨询负责人的规则请查看第2层——这是本技能中最重要的段落。
绝不要在没有验收成果的情况下报告“已升级”。绿色
validate
校验结果加报告是机器判定的标准。缺少任意一项,无论差异内容如何,状态都为进行中

Quickstart

快速开始

bash
undefined
bash
undefined

0 · preflight — what major is this project on, what major is installed?

0 · 预检 —— 项目当前使用的大版本是什么?已安装的大版本是什么?

grep -rn "protocol" objectstack.config.ts package.json | head node -p "require('@objectstack/spec/package.json').version"
grep -rn "protocol" objectstack.config.ts package.json | head node -p "require('@objectstack/spec/package.json').version"

1 · mechanical — replay the chain (reads the config, writes nothing but --out)

1 · 机械层 —— 执行转换链(读取配置,仅通过--out参数输出内容)

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
os validate > .upgrade/validate-before.txt 2>&1 || true # 保留初始校验结果作为对照 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

2 · 语义残留 —— 获取提示信息,然后逐一处理

node -e "console.log(require('fs').readFileSync(require.resolve('@objectstack/spec/package.json').replace('package.json','spec-changes.json'),'utf8'))" > .upgrade/spec-changes.json grep -rho '[REMOVED][^"]*' node_modules/@objectstack/spec/json-schema/ | sort -u > .upgrade/tombstones.txt
node -e "console.log(require('fs').readFileSync(require.resolve('@objectstack/spec/package.json').replace('package.json','spec-changes.json'),'utf8'))" > .upgrade/spec-changes.json grep -rho '[REMOVED][^"]*' node_modules/@objectstack/spec/json-schema/ | sort -u > .upgrade/tombstones.txt

3 · acceptance — all four, not three

3 · 验收层 —— 完成全部四项,缺一不可

os validate # green (compare against validate-before.txt) tsc --noEmit # tombstones type the retired keys as
never
os migrate meta --from 17 # must say "Nothing to migrate"
os validate # 绿色通过(与validate-before.txt对比) tsc --noEmit # 废弃键的类型被标记为
never
os migrate meta --from 17 # 必须提示“Nothing to migrate”

→ write .upgrade/REPORT.md (template in §3.4)

→ 编写.upgrade/REPORT.md(模板见§3.4)


Everything below is the long form of those four steps.

---

以下内容是上述四个步骤的详细说明。

---

0 · Preflight

0 · 预检

Establish the FROM major — do not guess it

确定源大版本 —— 不要猜测

--from
is the protocol major the metadata was authored against, not the one installed. Three sources, in order of authority:
  1. manifest.protocol
    in the stack config (
    '16.0.0'
    --from 16
    ). This is the declared answer and the kernel checks it at load time.
  2. The last
    @objectstack/spec
    major the project ever installed
    — read the lockfile history (
    git log -p pnpm-lock.yaml | grep -m5 '@objectstack/spec'
    ) when the manifest is absent or stale.
  3. Ask. A manifest that says 16 on a project last touched two years ago is a claim, not a measurement. If (1) and (2) disagree, the lower one is the safe
    --from
    — the chain is idempotent, so replaying a hop that has already been applied is a no-op, while skipping a hop loses its rewrites.
Arriving several majors late is the designed-for case.
os migrate meta --from 10
replays every step in order; there is no penalty for lateness and no requirement to upgrade one major at a time.
--from
参数指定的是元数据开发时使用的协议大版本,而非已安装的版本。请按以下优先级确认:
  1. 栈配置中的
    manifest.protocol
    (如
    '16.0.0'
    对应
    --from 16
    )。这是官方声明的版本,内核在加载时会校验此值。
  2. 项目曾安装的最后一个
    @objectstack/spec
    大版本
    ——当清单缺失或过时,可查看锁文件历史记录(
    git log -p pnpm-lock.yaml | grep -m5 '@objectstack/spec'
    )。
  3. 咨询负责人。如果两年未维护的项目清单显示版本为16,这只是一个声明而非实际情况。如果(1)和(2)结果不一致,选择较低的版本作为
    --from
    参数值——转换链是幂等的,重复执行已完成的步骤不会产生影响,而跳过步骤会丢失对应的重写内容。
跨多个大版本升级是设计时就支持的场景。
os migrate meta --from 10
会按顺序执行所有步骤;跨版本升级没有额外成本,也无需逐版本升级。

Make the work reviewable before you change anything

在变更前确保工作可审核

bash
git checkout -b upgrade/protocol-17
mkdir -p .upgrade         # every artifact this skill produces lands here
The
.upgrade/
directory is the deliverable's workspace: the machine outputs (
migrate.json
,
migrated.stack.json
,
spec-changes.json
,
tombstones.txt
) and the human output (
REPORT.md
). Keeping them in the repo for the review, and deleting them on merge, is the usual arrangement — decide it with the owner.

bash
git checkout -b upgrade/protocol-17
mkdir -p .upgrade         # 本技能生成的所有成果都存放在此目录
.upgrade/
目录是交付成果的工作区:包含机器输出内容(
migrate.json
migrated.stack.json
spec-changes.json
tombstones.txt
)和人类可读内容(
REPORT.md
)。通常的做法是在审核阶段将这些内容保留在仓库中,合并后删除——可与负责人协商决定。

1 · Mechanical layer — invoke the chain

1 · 机械层 —— 调用转换链

What
os migrate meta
actually does

os migrate meta
的实际作用

bash
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
It loads the stack config, normalizes it without applying the load-time conversion pass, replays each major's conversions as a chain hop, and then parses the result against the current schema to prove the output is valid. What it prints:
  • Applied N mechanical change(s)
    — one line per rewritten site, as
    path: from → to (conversionId)
    . This is the diff, already attributed.
  • N manual change(s) require your judgment
    — the chain's semantic entries for the majors you crossed, each with a
    why
    and a
    verify
    line. These are Layer 2's input, not a warning to dismiss.
  • Migrated stack is schema-valid
    — or the warning that it is not yet, which means a residue item is still blocking the parse.
  • Pending data migrations, when the chain crosses into a major with per-deployment data gates — see below.
bash
os migrate meta --from 16                       # 执行16 → 当前版本的转换
os migrate meta --from 16 --step                # 分步执行(用于排查故障)
os migrate meta --from 16 --to 17               # 升级到指定大版本后停止
os migrate meta --from 16 --json                # 输出机器可读的结果
os migrate meta --from 16 --out migrated.json   # 输出标准化后的栈配置
该命令加载栈配置,应用加载时转换流程,按顺序执行每个大版本的转换步骤,然后将结果与当前Schema进行解析校验,以验证输出内容有效。输出信息包括:
  • Applied N mechanical change(s)
    —— 每一行对应一处重写,格式为
    path: from → to (conversionId)
    。这就是差异内容,且已追溯来源。
  • N manual change(s) require your judgment
    —— 转换链针对你跨越的大版本生成的语义条目,每个条目包含
    why
    verify
    说明。这些是第2层的输入内容,而非可忽略的警告。
  • Migrated stack is schema-valid
    —— 或提示结果尚未符合规范,这意味着仍有残留项阻碍解析。
  • Pending data migrations —— 当转换链跨越到包含部署数据校验的大版本时会显示此信息——详见下文。

⚠ The one fact that surprises every operator

⚠ 让所有操作者意外的事实

os migrate meta
does not rewrite your source files.
It rewrites the loaded stack in memory and reports the diff. The only file it writes is
--out
, a JSON snapshot.
This is deliberate: rewriting a TypeScript config through an AST is lossy — it drops comments, reorders keys, and cannot see values that come from imports or expressions. So the mechanical layer gives you a provably valid target and the attributed list of edits, and porting those edits into the project's own sources is yours. Work from the printed list, one
conversionId
at a time; use
--out
as the oracle you diff against, never as the file you ship.
bash
os migrate meta --from 16 --out .upgrade/migrated.stack.json
os migrate meta
不会重写你的源文件
。它仅在内存中重写加载的栈配置并报告差异。唯一会写入的文件是通过
--out
参数指定的JSON快照。
这是刻意设计的:通过AST重写TypeScript配置会丢失注释、打乱键的顺序,且无法识别来自导入或表达式的值。因此机械层会为你提供一个经验证有效的目标配置带来源追溯的编辑列表,将这些编辑内容移植到项目源文件中是你的工作。请按照输出的列表逐一处理,每个
conversionId
对应一项;将
--out
输出的内容作为对比基准,绝不要直接作为交付文件。
bash
os migrate meta --from 16 --out .upgrade/migrated.stack.json

then, after porting the edits into the real sources:

将编辑内容移植到实际源文件后:

os migrate meta --from 17 --out .upgrade/recheck.json # should apply 0 changes

That last line is the cheapest possible proof that the port is complete: replay
the chain from the *target* major and it must find nothing to do.
os migrate meta --from 17 --out .upgrade/recheck.json # 应提示0项变更

最后一行是验证移植是否完成的最简方式:从目标大版本重新执行转换链,应无任何变更。

Stored rows: rehydration replays the same conversions

存储行:重新加载时会执行相同的转换

A deployment's
sys_metadata
rows are the other subject. They are handled for you at read time — the metadata loader and the ObjectQL plugin both pass each stored row through
applyConversionsToStoredItem
, which replays the conversion chain over a single item including entries retired from the load path. A row written under protocol 16 therefore rehydrates in its protocol-17 shape without anybody editing it.
What that does and does not mean:
  • You do not hand-edit
    sys_metadata
    .
    Ever. A row at rest has no author to ask, so the replay is unconditional and complete by design.
  • The rows on disk stay in their old shape until something rewrites them. Rehydration is a read-time projection.
  • To make it durable, run the stored pass — read-only by default:
    bash
    os migrate meta --stored                     # preview, writes nothing
    os migrate meta --stored --type view --type object   # narrow the pass
    os migrate meta --stored --apply             # rewrite the rows (prompts)
    --stored
    takes no
    --from
    : a stored row carries its own history, so the pass replays the whole chain. The authored-source flags and the stored-only flags are mutually exclusive, and mixing them is refused rather than ignored.
部署中的
sys_metadata
行是另一处理对象。它们会在读取时自动处理——元数据加载器和ObjectQL插件会将每个存储行传入
applyConversionsToStoredItem
,对单个条目执行完整的转换链包括加载路径中已废弃的条目。因此,基于协议16写入的行会自动以协议17的格式重新加载,无需手动编辑。
这意味着:
  • 绝不要手动编辑
    sys_metadata
    。存储的行没有可咨询的作者,因此重新加载时会无条件执行完整的转换。
  • 磁盘上的行仍保留旧格式,直到被重写。重新加载是读取时的投影转换。
  • 如需持久化更新,执行存储转换——默认是只读模式:
    bash
    os migrate meta --stored                     # 预览,不写入内容
    os migrate meta --stored --type view --type object   # 缩小转换范围
    os migrate meta --stored --apply             # 重写行(会提示确认)
    --stored
    无需指定
    --from
    :存储的行包含自身的历史信息,因此转换会执行完整的链。源文件标记和仅存储标记互斥,混合使用会被拒绝。

Data migrations are not metadata migrations

数据迁移≠元数据迁移

When the chain crosses into a major carrying per-deployment data gates, the command ends by naming them — for the 16 → 17 crossing, and only when the project's own metadata declares the field classes each gate is about:
CommandWhat staying un-run costs
os migrate files-to-references
Media values only warn instead of being enforced, and released files are never collected.
os migrate value-shapes
Stored reference and structured-JSON values are not checked against their field contracts; a malformed value only warns.
Both are dry-run by default;
--apply
is the only writing mode. They run against each deployment's database, once per deployment, and nothing in the metadata upgrade can run them or tell whether they have run. Not running them is safe — enforcement simply stays off. Carry them into the report as pending, by name, so a gate nobody was told about is not served by nobody.

当转换链跨越到包含部署数据校验的大版本时,命令会列出相关校验——以16 → 17为例,仅当项目元数据声明了每个校验对应的字段类时才会显示:
命令未执行的影响
os migrate files-to-references
媒体值仅发出警告而非强制校验,已发布文件不会被回收。
os migrate value-shapes
存储的引用和结构化JSON值不会按字段契约校验,格式错误的值仅发出警告。
两者默认都是试运行模式;只有
--apply
是写入模式。它们针对每个部署的数据库执行,每个部署执行一次,元数据升级无法自动执行这些命令或判断是否已执行。未执行是安全的——只是校验机制不会启用。请在报告中按名称标记为待处理,避免相关校验机制被遗漏。

2 · Semantic residue — the part that is yours

2 · 语义残留 —— 你需要处理的部分

A conversion can rename a key, drop a dead one, or lift a value onto its declared block. It cannot make a decision. Everything it cannot do lands here.
转换链可以重命名键、删除废弃键,或将值提升到指定区块,但无法做决策。所有转换链无法处理的内容都属于这一层。

2.1 Harvest the instruction sources — all of them ship

2.1 获取所有提示来源 —— 均随包发布

The prescriptions are not on a docs site you have to be online for. They ship inside the installed package. Measured against the published
@objectstack/spec
file list:
SourceWhere, in a consumer projectCarries
Chain result
os migrate meta --from N --json
.specChanges
The conversions + semantic entries for exactly the majors you cross. Start here — it is computed from the installed spec, so it can never be stale.
D4 projection
node_modules/@objectstack/spec/spec-changes.json
The same data for every major, offline:
perMajor[].converted
and
perMajor[].migrated
.
Tombstone prescriptions
node_modules/@objectstack/spec/json-schema/**
and
src/**/*.zod.ts
Every retired key's
[REMOVED] …
fix-it text, greppable.
FROM → TO tables
node_modules/@objectstack/spec/CHANGELOG.md
The per-retirement narrative, including the "what to write instead" table. This is why the package ships its changelog.
The error itselfYour parse /
tsc
output
The same prescription string, delivered at the moment you hit it.
bash
undefined
提示信息不依赖在线文档,而是随安装的包一起发布。基于已发布的
@objectstack/spec
文件列表
来源在消费项目中的位置包含内容
转换链结果
os migrate meta --from N --json
.specChanges
针对你跨越的大版本的转换规则+语义条目。从此处开始——它基于已安装的spec生成,绝不会过时。
D4投影
node_modules/@objectstack/spec/spec-changes.json
所有大版本的相同数据,支持离线使用:
perMajor[].converted
perMajor[].migrated
废弃提示
node_modules/@objectstack/spec/json-schema/**
src/**/*.zod.ts
所有废弃键的
[REMOVED] …
修复提示,可通过grep查询。
版本对照表
node_modules/@objectstack/spec/CHANGELOG.md
每个废弃项的说明,包括“替代方案”表格。这也是包中包含变更日志的原因。
错误信息本身你的解析/
tsc
输出内容
相同的提示字符串,在触发错误时直接显示。
bash
undefined

every tombstone prescription the installed spec carries, deduped

获取已安装spec中的所有废弃提示,去重

grep -rho '[REMOVED][^"]*' node_modules/@objectstack/spec/json-schema/ | sort -u
grep -rho '[REMOVED][^"]*' node_modules/@objectstack/spec/json-schema/ | sort -u

the FROM → TO table for one retired key

获取某个废弃键的版本对照表

grep -n -B4 -A20 'transform' node_modules/@objectstack/spec/CHANGELOG.md | less

> **Not reachable from a consumer project**, so do not send anyone there: the
> conversion and migration registries (`src/conversions/registry.ts`,
> `src/migrations/registry.ts`) and the platform repo's generated upgrade guide
> are **not** in the published package — only `src/**/*.zod.ts` is. Their
> consumer-facing projection is `spec-changes.json` and the chain's own `--json`
> output, which is exactly what the table above points at.
grep -n -B4 -A20 'transform' node_modules/@objectstack/spec/CHANGELOG.md | less

> **消费项目无法访问**,因此不要引导任何人前往:转换和迁移注册表(`src/conversions/registry.ts`、`src/migrations/registry.ts`)以及平台仓库生成的升级指南**未包含在发布包中**——仅`src/**/*.zod.ts`会包含。面向消费者的投影内容是`spec-changes.json`和转换链的`--json`输出,正是上表指向的内容。

2.2 The three residue classes

2.2 三类残留项

R1 · Intent choice — a key was retired with no single lossless target. The conversion drops the key (so the project parses) and, where it matters, emits a notice naming the site. What the key was for still has to go somewhere, and where is a business statement.
R2 · Custom code calling a retired API. The chain's semantic entries name these: a service slot that no longer exists, an engine method that was removed, a context field that was renamed. Metadata parses fine; the project's own TypeScript is what breaks — or worse, keeps compiling while reading
undefined
.
R3 · Prose that still teaches the old shape. READMEs, comments, ADRs, seed fixtures, and the project's own AI conventions file. Nothing fails, and the next agent to read the repo re-authors the retired shape from it.
R1 · 意图选择 —— 某个键被废弃且无唯一无损替代方案。转换链会删除该键(确保项目可解析),并在重要位置发出提示,标记该位置。该键的用途仍需找到替代方案,而选择是业务决策。
R2 · 调用已废弃API的自定义代码。转换链的语义条目会列出这些内容:不存在的服务插槽、已移除的引擎方法、已重命名的上下文字段。元数据可正常解析,但项目自身的TypeScript代码会报错——更糟的是,代码可能仍能编译但读取
undefined
值。
R3 · 沿用旧格式的说明文档。README、注释、ADR、种子测试数据以及项目自身的AI约定文件。不会导致报错,但后续读取仓库的代理会根据这些内容重新编写废弃格式的代码。

2.3 A worked R1 — the retired field-mapping
transform

2.3 R1示例 —— 已废弃的字段映射
transform

The shape in a protocol-16 project:
jsonc
{
  "connectors": [{
    "name": "sap_erp",
    "fieldMappings": [
      { "source": "order_value", "target": "order_total",
        "transform": { "type": "javascript", "expression": "value / 100" } }
    ]
  }]
}
The chain deletes the key (
field-mapping-transform-removed
) and the schema tombstones it, so the parse error is the prescription: the union had five members and no runtime ever executed any of them, so nothing is lost by deleting the key — but the customer wrote it because they wanted a transformation, and that need is real even though the key never served it.
The prescription names two live targets, and choosing between them is the business decision:
If the intent was…The v17 home is…
per-row value shaping on an importImport mapping
mapping.fieldMapping[].transform
— a flat string enum (
none
/
constant
/
map
/
split
/
join
/
lookup
) with settings in
params
, executed row by row by the REST import path.
multi-source, multi-stage transformationan ETL transformation step.
nothing — the value was already correctdelete the key and record that the transformation never ran.
That third row is not a joke and it is frequently the truth: the member never executed, so the connector has been landing raw values for as long as it has been running. Whether the downstream data is therefore wrong is a question only the owner can answer, and it is exactly the kind of finding the report exists to surface.
<a id="decide-alone-or-ask"></a>
协议16项目中的格式:
jsonc
{
  "connectors": [{
    "name": "sap_erp",
    "fieldMappings": [
      { "source": "order_value", "target": "order_total",
        "transform": { "type": "javascript", "expression": "value / 100" } }
    ]
  }]
}
转换链会删除该键(
field-mapping-transform-removed
),Schema会标记其为废弃,因此解析错误本身就是提示:该联合类型曾有五个成员,但没有运行时执行过其中任何一个,因此删除该键不会丢失任何内容——但客户编写该键是因为需要转换功能,这个需求是真实存在的,尽管该键从未发挥作用。
提示信息列出了两个可用的替代方案,选择哪一个是业务决策:
如果意图是…v17中的对应位置是…
导入时逐行值转换导入映射
mapping.fieldMapping[].transform
—— 扁平字符串枚举(
none
/
constant
/
map
/
split
/
join
/
lookup
),配置在
params
中,由REST导入路径逐行执行。
多源多阶段转换ETL转换步骤
无需求——值已正确删除该键,并记录该转换从未执行过。
第三点并非玩笑,且经常是实际情况:该成员从未执行过,因此连接器一直以来都在传输原始值。下游数据是否因此错误,只有负责人能回答,这正是报告需要呈现的发现之一。
<a id="decide-alone-or-ask"></a>

2.4 Decide alone, or ask the owner

2.4 自行决策或咨询负责人

Decide it yourself when all three hold:
  1. The prescription names exactly one target. The tombstone or conversion summary gives a single FROM → TO, with the value unchanged.
  2. The evidence is in the project. A grep in the repo settles it — the skill that already owns the tool, the import that already exists, the field the predicate already references.
  3. Being wrong fails a gate. A mistaken choice breaks
    tsc
    or
    validate
    rather than changing behaviour quietly.
Ask the owner when any one of these holds:
  1. Two or more real targets, and the choice is a business statement — the
    transform
    case above.
  2. The change is observable without a test failing — security posture (an authentication default), row visibility (an access predicate), retry counts, retention. A wrong call here ships silently and is discovered by an auditor.
  3. Capability has to be re-declared somewhere new, so choosing wrong removes a capability instead of breaking a build. Agent tooling that has to move inside a specific skill is the canonical shape.
  4. The source is dead or undocumented in their repo — nothing to decide from. Say so; do not invent a rationale.
How to ask. One message, per item, carrying: the site (file and path), the prescription verbatim, the options with what each costs, your recommendation and why, and what you will verify once they choose. Never a bare "how should I handle
transform
?" — that hands the reading work back to the person with the least context about the diff.
While you wait, do not stall the upgrade. Park the item in the report as
AWAITING DECISION
, keep the mechanical layer complete, and keep going. A project can be schema-valid with open residue items; it just is not done.
满足以下全部三点时可自行决策
  1. 提示信息指定唯一替代方案。废弃提示或转换摘要给出唯一的FROM → TO映射,且值保持不变。
  2. 项目中有明确证据。通过grep查询仓库可确认——工具所属的技能、已存在的导入、谓词已引用的字段。
  3. 决策错误会触发校验失败。错误选择会导致
    tsc
    validate
    失败,而非悄然改变行为。
满足以下任意一点时需咨询负责人
  1. 存在多个有效替代方案,选择属于业务决策——如上述
    transform
    案例。
  2. 变更可被观察但不会触发测试失败——安全策略(认证默认值)、行可见性(访问谓词)、重试次数、保留期限。错误决策会悄然生效,直到被审计发现。
  3. 能力需要在新位置重新声明,错误选择会移除某项功能而非导致构建失败。例如,代理工具必须迁移到特定技能内部的典型场景。
  4. 项目中该来源已失效或无文档——无决策依据。如实说明,不要编造理由。
咨询方式。针对每个残留项发送一条消息,包含:位置(文件和路径)、完整提示信息、各选项的影响、你的推荐及理由,以及选择后你将如何验证。绝不要只发送“我该如何处理
transform
?”——这会把阅读工作推给最不了解差异上下文的人。
等待回复期间不要暂停升级。在报告中将该项目标记为
AWAITING DECISION
,保持机械层完成,继续处理其他内容。项目可在存在未处理残留项的情况下符合Schema规范,但不算完成

2.5 Working an R2 — retired APIs in the project's own code

2.5 处理R2 —— 项目代码中已废弃的API

The chain's semantic entries are the search list. For each one, the surface it names is a string you grep for in the project's own source:
bash
undefined
转换链的语义条目是搜索列表。针对每个条目,搜索项目自身源代码中对应的字符串:
bash
undefined

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); })"
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)

针对每个内容,搜索项目源代码(排除node_modules)

grep -rn "<surface-token>" src/ app/ --include='.ts' --include='.tsx'

Two traps that have cost real upgrades a lap:

- **A renamed context field keeps compiling.** When a read moves from one key to
  another and the old key is simply absent afterwards, the code reads
  `undefined` and every branch quietly takes its false path. Verify against a
  real dispatch, not a fixture — invoke the path and assert the value observed
  under the canonical key.
- **A rename is not always the fix.** If the old read was itself wrong, renaming
  it migrates the defect rather than the code. Read the semantic entry's
  `reason` before applying its `replacement`.
grep -rn "<surface-token>" src/ app/ --include='.ts' --include='.tsx'

两个曾导致升级返工的陷阱:

- **已重命名的上下文字段仍能编译**。当读取从旧键迁移到新键后,旧键不存在,代码会读取`undefined`,所有分支会悄然走错误路径。请针对实际调度进行验证,而非测试用例——触发该路径并断言规范键下的观测值。
- **重命名并非总是解决方案**。如果旧读取本身就错误,重命名会迁移缺陷而非代码。在应用替代方案前,请阅读语义条目的`reason`说明。

2.6 Working an R3 — the prose sweep

2.6 处理R3 —— 文档扫描

Run it last, once the shapes are settled, and run it over the whole repo:
bash
undefined
在格式确定后最后执行,扫描整个仓库:
bash
undefined

every retired key name the installed spec knows, as a search list

获取已安装spec中所有废弃键名,作为搜索列表

grep -rho '[REMOVED]
[^
]
' node_modules/@objectstack/spec/json-schema/ \   | sed 's/.*
(.
)`.*/\1/' | sort -u > .upgrade/retired-names.txt

Then sweep the project's `*.md`, comments, seed fixtures, and its AI conventions
file. The conventions file matters most: it is what the next agent loads before
it writes anything, so a retired shape left there re-enters the codebase on the
next feature, long after the upgrade closed.

---
grep -rho '[REMOVED]
[^
]
' node_modules/@objectstack/spec/json-schema/ \   | sed 's/.*
(.
)`.*/\1/' | sort -u > .upgrade/retired-names.txt

然后扫描项目中的`*.md`文件、注释、种子测试数据以及AI约定文件。约定文件最为重要:它是后续代理编写代码前加载的内容,因此残留的废弃格式会在后续功能开发时重新进入代码库,远在升级完成之后。

---

3 · Acceptance — what "upgraded" means

3 · 验收层 —— “已升级”的定义

Four artifacts. Three are machine-checked; the fourth is the one a human reads.
四项成果。三项由机器校验;第四项供人类阅读。

3.1 Typed

3.1 类型检查通过

bash
tsc --noEmit
A retired key is not merely absent from the schema — it is declared as a tombstone whose input type is
never
. Assigning anything to it fails to compile, at the authoring site, before anything runs. A green
tsc
is therefore positive evidence that no retired key survives in typed sources.
bash
tsc --noEmit
废弃键不仅从Schema中移除,还被声明为类型
never
的废弃项。为其赋值会在编写阶段编译失败,无需运行。绿色
tsc
结果是类型源文件中无废弃键存活的明确证据。

3.2 Parse-gated

3.2 解析校验通过

The same tombstone rejects at parse time, and the rejection carries the prescription rather than a generic "unrecognized key". This is the channel that catches metadata
tsc
cannot see: JSON files, database rows, anything built at runtime. You do not have to do anything to enable it — but you do have to prove it is live for this project, because a schema that silently strips is indistinguishable from one that accepts. See the reverse check.
相同的废弃项会在解析时被拒绝,且拒绝信息包含提示内容而非通用的“无法识别的键”。这一机制会捕获
tsc
无法检测的元数据:JSON文件、数据库行、任何运行时构建的内容。你无需手动启用——但必须验证该机制对当前项目生效,因为静默剥离的Schema与接受旧键的Schema无法区分。详见反向校验

3.3 Validate

3.3 Validate校验通过

bash
os validate            # green is the criterion
os validate --strict   # warnings become errors — agree with the owner whether this is the bar
os validate
runs two passes: the protocol schema (where tombstones reject) and the author-time rule set. Read the two separately — a rule finding about a missing sharing model or an options-less choice field is a pre-existing project-quality issue, not upgrade residue. Establish which is which by running
os validate
once before you start, on the un-upgraded source, and keeping that output as the control. Fixing the project's standing lint debt may be a welcome side-effect, but it is not this upgrade, and it must not be reported as part of it.

⚠ A green
validate
does NOT mean the chain has nothing left to do

Some conversions are migration-chain-only: the loader deliberately does not apply them and no tombstone rejects the old shape, because the change is a default flip rather than a rename — auto-applying it would stamp a constraint onto sources that deliberately omit it. The 16 → 17 crossing has one:
field-required-notnull-explicit
, which writes the physical
storage.notNull
that
required
used to imply on its own.
A project carrying only that shape validates green while the chain still has work. So
validate
green is necessary and not sufficient, and the criterion that closes the gap is the replay:
bash
os migrate meta --from <target-major>    # must report "Nothing to migrate"
Run both. A report that cites only
validate
cannot see this class at all.
bash
os validate            # 绿色通过是判定标准
os validate --strict   # 警告转为错误——与负责人协商是否以此为标准
os validate
执行两次校验:协议Schema(废弃项会被拒绝)和编写时规则集。请分别查看结果——关于缺失共享模型或无选项选择字段的规则发现是项目已存在的质量问题,而非升级残留项。通过在升级前执行一次
os validate
并保留输出作为对照,可区分两者。修复项目已存在的规则债务可能是升级的意外收获,但不属于本次升级的范围,不应作为升级成果报告。

⚠ 绿色
validate
不代表转换链已完成所有工作

部分转换是仅迁移链执行:加载器刻意不应用这些转换,且废弃项不会拒绝旧格式,因为变更是默认值翻转而非重命名——自动应用会给刻意省略该约束的源文件添加限制。16 → 17升级包含一项此类转换:
field-required-notnull-explicit
,它会写入
required
曾隐含的物理约束
storage.notNull
仅包含此类格式的项目会通过
validate
校验绿色,但转换链仍有工作要做。因此绿色
validate
是必要非充分条件,填补缺口的判定标准是重新执行转换链:
bash
os migrate meta --from <target-major>    # 必须提示“Nothing to migrate”
请同时执行两者。仅引用
validate
结果的报告无法覆盖此类情况。

3.4 The report — the human half

3.4 报告 —— 人类可读部分

The upgrade is not finished by a passing command; it is finished by a document a maintainer can read in five minutes and a year from now. Write
.upgrade/REPORT.md
:
markdown
undefined
升级并非通过某个命令完成,而是通过一份维护者可在5分钟内读完、且一年后仍能理解的文档完成。编写
.upgrade/REPORT.md
markdown
undefined

Protocol 16 → 17 upgrade — <project>

协议16 → 17升级 —— <项目名称>

Status: complete | complete with N open decisions Spec: <installed @objectstack/spec version> · Chain: 16 → 17 Verified:
os validate
green ·
tsc --noEmit
green · replay-from-17 applies 0 changes
状态: 已完成 | 已完成,存在N项待决策内容 Spec版本: <已安装的@objectstack/spec版本> · 转换链: 16 → 17 验证结果:
os validate
绿色 ·
tsc --noEmit
绿色 · 从17版本重新执行转换链无变更

1 · Mechanical (applied by the chain)

1 · 机械层变更(由转换链执行)

SiteChangeConversion
objects[crm_lead].fields.name
required: true
+ storage.notNull: true
field-required-notnull-explicit
N sites, M conversions. Ported into sources from
os migrate meta --out
.
位置变更内容转换ID
objects[crm_lead].fields.name
required: true
+ storage.notNull: true
field-required-notnull-explicit
共N处位置,M项转换。已从
os migrate meta --out
输出内容移植到源文件。

2 · Semantic residue (decided)

2 · 语义残留处理(已决策)

connector.fieldMappings[].transform
— RESOLVED

connector.fieldMappings[].transform
—— 已解决

  • Site:
    src/connectors/sap.ts:24
  • Prescription: <verbatim from the tombstone>
  • Options: import-mapping
    transform
    · ETL step · delete
  • Decision: delete — owner confirmed the values arrive pre-scaled. Decided by: <who>, <date>.
  • Verified:
    os validate
    green; connector sync run against staging, 200 rows, values unchanged.
  • 位置:
    src/connectors/sap.ts:24
  • 提示信息: <废弃提示原文>
  • 选项: 导入映射
    transform
    · ETL步骤 · 删除
  • 决策: 删除 —— 负责人确认值已预先缩放。 决策人: <姓名>, <日期>.
  • 验证:
    os validate
    绿色;连接器同步运行于预发布环境,200行数据,值无变化。

3 · Open decisions

3 · 待决策内容

ItemSiteOptionsRecommendationBlocking?
agent.tools
→ which skill
src/ai/support-bot.ts:12
case_management
· new skill
case_management
no — parses without it
位置选项推荐方案是否阻塞?
agent.tools
→ 迁移至哪个技能
src/ai/support-bot.ts:12
case_management
· 新技能
case_management
否 —— 可正常解析

4 · Pending, per deployment

4 · 待执行的部署级任务

  • os migrate files-to-references
    — media values only warn until it passes.
  • os migrate value-shapes
    — stored reference/JSON values unchecked until it passes.
  • os migrate meta --stored --apply
    — rows rehydrate correctly today; this makes it durable.
  • os migrate files-to-references
    —— 媒体值仅发出警告,直到执行完成。
  • os migrate value-shapes
    —— 存储的引用/JSON值未校验,直到执行完成。
  • os migrate meta --stored --apply
    —— 当前行可正确重新加载;此操作会持久化更新。

5 · Not changed, and why

5 · 未变更项及原因

  • <retired surface the project never used> — no occurrences.

Section 5 earns its place: "we looked and it was not there" is a finding, and
without it the next reader cannot tell a surface that was checked from one that
was missed.

<a id="reverse-check"></a>
  • <项目从未使用的废弃内容> —— 无匹配项。

第5部分至关重要:“我们已检查且未发现匹配项”是一项发现,没有这部分内容,后续读者无法区分已检查的内容和遗漏的内容。

<a id="reverse-check"></a>

3.5 Prove the gate is real, do not assume it

3.5 验证校验机制有效,不要假设

Before you report the parse gate as acceptance evidence, make it fire once. Take a value the chain would have converted, put it back after migrating, and parse:
bash
undefined
在报告中将解析校验作为验收证据前,请先触发一次校验失败。将转换链本应转换的值在迁移后恢复,然后执行解析:
bash
undefined

a stack that still carries a retired key, fed straight to the schema

包含废弃键的栈配置,直接传入Schema校验

os validate .upgrade/residue-probe.config.mjs

Predict the direction **before** you run it. There are three real outcomes and
you must say which you expect:

1. **Rejected with the prescription** — a tombstoned key. This is what a
   tombstone looks like when it works:
✗ connectors.0.fieldMappings.0.transform invalid_type:
FieldMapping.transform
… was removed in @objectstack/spec 17.0.0 (#5552, ADR-0049) … Delete the key. The transform pipeline that IS enforced is the import mapping's … Run
os migrate meta --from 16
to rewrite it automatically. expected: never

Note what the error is not: not "unrecognized key", not a deprecation label.
The fix-it text *is* the error.

2. **Accepted, and the chain rewrites it** — a conversion with a live load-path
acceptance window. Your evidence for that key is the chain's diff, not the
parse gate.

3. **Accepted, and the chain still rewrites it** — a migration-chain-only
conversion (§3.3). `validate` cannot see this class at all; only the replay
can.

Record which one you actually got. A check whose expected direction you did not
state in advance proves nothing, and "it passed" is a different fact in each of
the three cases.

---
os validate .upgrade/residue-probe.config.mjs

在执行前**预测结果方向**。存在三种实际结果,你必须说明预期的结果:

1. **被拒绝并显示提示信息**——废弃键。这是废弃机制生效的表现:
✗ connectors.0.fieldMappings.0.transform invalid_type:
FieldMapping.transform
… was removed in @objectstack/spec 17.0.0 (#5552, ADR-0049) … Delete the key. The transform pipeline that IS enforced is the import mapping's … Run
os migrate meta --from 16
to rewrite it automatically. expected: never

注意错误信息的形式:不是“无法识别的键”,也不是弃用标签。修复提示本身就是错误信息。

2. **被接受,且转换链会重写它**——转换存在加载路径接受窗口期。该键的证据是转换链的差异,而非解析校验。

3. **被接受,且转换链仍会重写它**——仅迁移链执行的转换(§3.3)。`validate`无法检测此类情况;只有重新执行转换链可以。

记录实际得到的结果。未预先说明预期方向的校验无法证明任何内容,“通过”在三种情况下代表不同的事实。

---

The v17 prescription set, as of
17.0.0-rc.5

v17提示集(基于
17.0.0-rc.5

This section is a pinned reading, not a live list. It was measured from the spec sources at the
17.0.0-rc.5
publish and is deliberately bounded so that entries registered after that publish are a visible delta rather than a silent contradiction.
ReadingValue at
17.0.0-rc.5
@objectstack/spec
version
17.0.0-rc.5
(protocol
17.0.0
)
Chain support floorprotocol 10
D2 conversions for major 1745
D3 semantic entries for major 1729
retiredKey()
tombstones in shipped
*.zod.ts
113, across 32 files
Distinct
[REMOVED]
prescriptions in shipped
json-schema/
96
RETIRED_KEYS_BY_MAJOR[17]
— every authorable key formally tombstoned under the exact-key registry at this publish (3 entries, one retirement: the property is declared once and inherited by two extending schemas, so it is registered three times):
  • data/ExternalFieldMapping:transform
  • integration/ConnectorFieldMapping:transform
  • shared/FieldMapping:transform
RETIRED_DEFS_BY_MAJOR[17]
— every whole schema def unpublished at this publish (1 entry):
  • shared/FieldMappingTransform
Why these two tables are short and the counts above are not. They record retirements registered under the exact-key gates that created them, which are newer than most of protocol 17's work; they are explicitly not a backfill of every retirement ever. The 45 conversions and 113 tombstones are the real size of the v17 surface. Use the tables to answer "was this retirement formally registered", and the conversions/tombstones to answer "what do I have to change" — the second question is the upgrade's question.
本部分是固定快照,而非实时列表。它基于
17.0.0-rc.5
发布时的spec源文件统计,刻意限定范围,以便该版本之后新增的条目可作为可见的增量而非静默矛盾。
统计项
17.0.0-rc.5
时的值
@objectstack/spec
版本
17.0.0-rc.5
(协议
17.0.0
转换链支持最低版本协议10
大版本17的D2转换数量45
大版本17的D3语义条目数量29
已发布
*.zod.ts
中的
retiredKey()
废弃项数量
113,分布在32个文件中
已发布
json-schema/
中不同的
[REMOVED]
提示数量
96
RETIRED_KEYS_BY_MAJOR[17]
——在此次发布时,精确键注册表中正式标记为废弃的所有可编写键(3项,对应一次废弃:属性被声明一次,被两个继承Schema复用,因此注册三次):
  • data/ExternalFieldMapping:transform
  • integration/ConnectorFieldMapping:transform
  • shared/FieldMapping:transform
RETIRED_DEFS_BY_MAJOR[17]
——此次发布时未发布的完整Schema定义(1项):
  • shared/FieldMappingTransform
为何这两个表格内容少而上述统计数量多。它们记录的是通过精确键校验机制注册的废弃项,这些机制比协议17的大部分工作更新;它们并未回溯所有历史废弃项。45项转换和113个废弃项是v17实际涉及的范围。使用这两个表格回答“该废弃项是否已正式注册”,使用转换/废弃项回答“我需要变更什么”——第二个问题才是升级的核心问题。

How this table is refreshed

如何更新此表格

Never hand-edit the numbers above from memory. Re-measure against whatever spec the project has installed — one command per row:
bash
undefined
绝不要凭记忆手动编辑上述数字。针对项目已安装的spec重新统计——每行对应一条命令:
bash
undefined

protocol version, support floor, and the per-major conversion / semantic counts

协议版本、支持最低版本、以及每个大版本的转换/语义条目数量

node -e " const p = require.resolve('@objectstack/spec/package.json'); const j = require(p.replace('package.json','spec-changes.json')); const e = j.perMajor.find(x => x.to === 17); console.log(j.protocolVersion, 'floor', j.supportFloor, '| 16→17:', e.converted.length, 'converted,', e.migrated.length, 'semantic'); "
node -e " const p = require.resolve('@objectstack/spec/package.json'); const j = require(p.replace('package.json','spec-changes.json')); const e = j.perMajor.find(x => x.to === 17); console.log(j.protocolVersion, 'floor', j.supportFloor, '| 16→17:', e.converted.length, 'converted,', e.migrated.length, 'semantic'); "

tombstone prescriptions actually present in this install

当前安装包中实际存在的废弃提示数量

grep -rho '[REMOVED][^"]*' node_modules/@objectstack/spec/json-schema/ | sort -u | wc -l

If a reading disagrees with the table, **the install wins** — the table is a
snapshot of one publish, and post-`rc.5` registrations are expected to add
entries. Record the delta in the upgrade report rather than editing this
section's pinned numbers; the pin is what makes a later disagreement legible
instead of invisible.

---
grep -rho '[REMOVED][^"]*' node_modules/@objectstack/spec/json-schema/ | sort -u | wc -l

如果统计结果与表格不一致,**以安装包为准**——表格是某个发布版本的快照,`rc.5`之后新增的条目是预期的。在升级报告中记录差异,而非编辑本部分的固定数字;固定快照可让后续的差异清晰可见而非被隐藏。

---

The v17-canonical shapes, compiled

v17标准格式示例

What the protocol-16 shapes in this skill's examples look like after the upgrade. This block is type-checked against the published spec, so it cannot rot into teaching a shape that no longer compiles:
<!-- os:check -->
typescript
import { ObjectSchema } from '@objectstack/spec/data';
import { defineAgent } from '@objectstack/spec/ai';

// `conditionalRequired` → `requiredWhen`; `required` now also states the
// physical constraint explicitly via `storage.notNull`.
export const Lead = ObjectSchema.create({
  name: 'crm_lead',
  label: 'Lead',
  fields: {
    name: { type: 'text', required: true, storage: { notNull: true } },
    status: { type: 'select', required: true, storage: { notNull: true } },
    due_date: { type: 'date', requiredWhen: 'record.stage == "closed"' },
    notes: { type: 'textarea' },
  },
});

// Agent capability is reached through skills — there is no inline tool list.
export const SupportBot = defineAgent({
  name: 'support_bot',
  label: 'Support Bot',
  role: 'Front-line support triage',
  instructions: 'Answer support questions and open cases when needed.',
  skills: ['case_management'],
});

本技能示例中的协议16格式升级后的样子。此代码块已针对已发布的spec进行类型检查,因此不会过时为无效格式:
<!-- os:check -->
typescript
import { ObjectSchema } from '@objectstack/spec/data';
import { defineAgent } from '@objectstack/spec/ai';

// `conditionalRequired` → `requiredWhen`; `required`现在通过`storage.notNull`明确声明物理约束。
export const Lead = ObjectSchema.create({
  name: 'crm_lead',
  label: 'Lead',
  fields: {
    name: { type: 'text', required: true, storage: { notNull: true } },
    status: { type: 'select', required: true, storage: { notNull: true } },
    due_date: { type: 'date', requiredWhen: 'record.stage == "closed"' },
    notes: { type: 'textarea' },
  },
});

// 代理能力通过技能调用——不再支持内联工具列表。
export const SupportBot = defineAgent({
  name: 'support_bot',
  label: 'Support Bot',
  role: 'Front-line support triage',
  instructions: 'Answer support questions and open cases when needed.',
  skills: ['case_management'],
});

Failure modes

故障排查

SymptomWhat it actually isFix
migrate meta
reports changes, but the files are unchanged
Working as designed — the command writes nothing but
--out
.
Port the printed edits into the sources, then replay from the target major to confirm 0 changes.
Replay from the target major still applies changesThe port is incomplete, or a source builds metadata at runtime from a shape the chain never saw.Diff against
--out
; grep for the
conversionId
's surface in code that constructs metadata dynamically.
validate
green, but a feature silently stopped working
An R2 residue item: code reading a renamed key now reads
undefined
.
Exercise the path for real. A green parse says nothing about a
??
chain in the project's own code.
validate
green from the start, so "there was nothing to upgrade"
A migration-chain-only conversion — no tombstone rejects it, so nothing complains.Replay the chain anyway.
validate
green is necessary, not sufficient; see 3.3.
validate
reports findings that have nothing to do with retired keys
The author-time rule pass, not the schema pass.Diff against the pre-upgrade
validate
control. Pre-existing findings are not this upgrade's scope.
A retired key round-trips without errorThe schema carrying it is not strict and the key is being stripped, or the key still has a live load-path window.Determine which — the two need different acceptance evidence. See the reverse check.
--apply
refused / stored-only flag rejected
--apply
,
--yes
,
--force
,
--type
,
--database-url
mean something only with
--stored
.
Add
--stored
, or drop the flag; the authored-source chain has nothing to write to.
MigrationFloorError
--from
is older than the chain's support floor.
Upgrade to the floor by an older route first; the floor is a release-policy boundary, not an oversight.
症状实际原因解决方案
migrate meta
报告有变更,但文件未修改
设计如此——命令仅通过
--out
输出内容。
将输出的编辑内容移植到源文件,然后从目标大版本重新执行转换链确认无变更。
从目标大版本重新执行转换链仍有变更移植不完整,或某个源文件通过转换链未检测到的格式在运行时构建元数据。
--out
输出内容对比差异;grep查询代码中动态构建元数据的
conversionId
对应内容。
validate
绿色,但某个功能悄然失效
R2残留项:代码读取已重命名的键,现在读取
undefined
实际触发该路径。绿色解析结果无法反映项目代码中的
??
链。
初始
validate
绿色,因此“无内容可升级”
仅迁移链执行的转换——无废弃项拒绝旧格式,因此无报错。仍需执行转换链。绿色
validate
是必要非充分条件;详见3.3
validate
报告与废弃键无关的发现
编写时规则校验,而非Schema校验。与升级前的
validate
对照结果对比。已存在的发现不属于本次升级范围。
废弃键可正常往返对应的Schema非严格模式,键被静默剥离,或该键仍处于加载路径窗口期。确定属于哪种情况——两者需要不同的验收证据。详见反向校验
--apply
被拒绝 / 仅存储标记被拒绝
--apply
--yes
--force
--type
--database-url
仅在搭配
--stored
时有效。
添加
--stored
,或移除该标记;源文件转换链无写入目标。
MigrationFloorError
--from
版本早于转换链支持的最低版本。
通过旧路径先升级到最低支持版本;最低版本是发布策略边界,而非疏忽。

Guardrails (binding)

约束规则(强制性)

  1. Run the chain first, always. It is the only source of an attributed, schema-proved diff.
  2. One conversion id per commit, where the project's review culture allows it. The
    conversionId
    is the commit's subject line and its justification.
  3. No tolerant reads, ever — not in the metadata, not in the project's code. A retirement that gets re-admitted at the consumer is a defect that moved into a repo with no gate over it.
  4. Ask about intent, decide about mechanics. The split in 2.4 is the contract with the project's owner.
  5. The report is a deliverable, not a summary. No report, not done.
  6. Never leave a pending per-deployment data migration unnamed. A gate nobody was told about is served by nobody.
  1. 始终先执行转换链。它是唯一可追溯来源、经Schema验证的差异来源。
  2. 每个转换ID对应一次提交(如果项目的审核流程允许)。
    conversionId
    作为提交主题和依据。
  3. 绝不要添加兼容读取逻辑——无论在元数据还是项目代码中。在消费端重新允许废弃项使用,会让缺陷转移到无校验机制的仓库中。
  4. 意图问题咨询负责人,机制问题自行决策2.4中的拆分是与项目负责人的约定。
  5. 报告是交付成果,而非摘要。无报告则不算完成。
  6. 绝不要遗漏待执行的部署级数据迁移。未告知的校验机制等于不存在。

Cross-skill routing

跨技能路由

  • Authoring the corrected metadata — load the domain skill for the shape you are fixing (data, ui, automation, ai, api, i18n).
  • Any CEL predicate you rewrite while resolving a residue item — load formula.
  • Runtime, plugin, and CLI questions the upgrade turns up — load platform.
  • This skill covers the consumption side of a retirement. Designing one inside the ObjectStack platform repo is a different job with its own internal playbook, and it is not this one.
  • 编写修正后的元数据——加载对应格式的领域技能(datauiautomationaiapii18n)。
  • 处理残留项时重写的CEL谓词——加载formula技能。
  • 升级过程中出现的运行时、插件和CLI问题——加载platform技能。
  • 本技能覆盖废弃的消费端处理。在ObjectStack平台仓库中设计废弃逻辑是不同的工作,有其内部操作手册,不属于本技能范畴。