os-decision-facets
⛔ This is an EXECUTION card stalled at its ruling's OWN step 4 — ⛔ not an unruled question
Ruling-ref: 5729478920 — batch #159 item 2 · letter A, maintainer 「同意」. It confirmed the 2026-09-13 ruling (batch #127 item 4, record 5651572469) and withdrew batch #149 item 1 letter B (record 5716259259). ⛔ The direction is settled and is not re-presented here; what follows is the measurement ruling A's own step 4 asked for, and the shape question that measurement returned.
⚠️ Added by the seat when the half-state patrol row H62 measured this body as carrying no os-decision-facets marker in either spelling. ⛔ Nothing already on the face was removed or reworded. The full four-axis analysis, the options and their costs live in comment 5729976367; this block is the card-face serialisation the shape requires.
Steps 1, 2, 5 and 6 are MEASURED READY. An artifact-stage body built exactly as step 2 prescribes does convert under z.toJSONSchema. Step 3 is blocked by step 4's own precondition, which fired: on the rows GET /packages serves, toRecordManifest leaves a functions residual that is neither a string nor a lowered declaration.
⭐ Two things the next reader must not mis-read. (1) The producer is packages/objectql/src/registry.ts — it is outside the three-file surface ruling A itself names, and outside packages/spec entirely, so the fix that ruling authorises does not fit the surface that ruling drew. That self-inconsistency is the seat's to have caught before dispatch, and it did not. (2) The card body's second consequence does not reproduce on today's main: packages/spec/json-schema/ is gitignored and was never version-controlled, while json-schema.manifest/api.json already carries both response schemas — ⛔ do not read their presence as 「the work is done」, nor their absence from git as 「it is not」.
The question: what shape does the REGISTRY RECORD stage take?
⛔ Not a dev's choice: it changes the payload GET /packages emits, lands in @objectstack/objectql — another published package, with its own Clause-② — and toRecordManifest's docblock states it is a structural projection and ⛔ 「not a key denylist」, so any fix that special-cases a key name overturns a written design rule.
|
option |
cost, measured |
| A |
registry records the lowered form — a dropped callable becomes a handler ref |
the only arm that literally satisfies step 4, and it fixes the under-report. ⛔ But it overturns the documented structural rule, and a ref the registry mints is not guaranteed equal to the one build mints (lower-callables.ts uses uniqueName(base, taken) and dedupes by function identity) ⇒ a record could assert a ref that resolves in no sibling module |
| B |
a dropped callable discards the whole entry |
step 3 becomes immediately doable. ⛔ But it generalises today's bare-entry behaviour to declarative entries — deliberately under-reporting at a read door — and throws away the effect declaration, the one half that survives today |
| C ⭐ |
split: file the producer under-report as a sub-issue of this card and rule the record-stage shape there; land steps 1/2/5/6 now; step 3 waits on the sub-card |
measured-ready work converts immediately and the undecided part is decided in its own package. ⛔ Cost: an exported artifact-stage body briefly has no consumer, which the PR body must declare as phase one |
| D |
same sub-card, but this whole card goes pm:blocked behind it |
nothing lands now; the four ready steps wait on an unformed producer decision |
四棱
① 项目长远合理性 — 三个声明过的阶段(authoring / 内存装配 / 落盘 artifact)之外,registry record 是事实上的第四个阶段:callable 已经没了,却没有 ref 顶上。今天没有任何声明描述它,这才是残骸能存在的根因。⇒ 偏向让 record 阶段拿到自己的声明。
② 实际业务拉动 — ⭐ 欠报是独立于本卡的真缺陷:机器可读的读门把「这个包声明了几个函数」说少了,而且踩在我们自己发布的 showcase 上(config.ts:244-249 正好一条裸的、一条声明式的,两种残骸各一)。⛔ 它不因为本卡怎么裁而消失。
③ 防 AI 犯错 — ⛔ 别再让一次派发同时裁「schema 形状」和「生产者行为」。本卡已因「裁决措辞 ↔ 代码事实」错位空转两轮(B 撤回、A 卡在自己的步骤 4)。B 的失败模式最危险:它让读门静默少报而不响亮拒绝。
④ 创业阶段不扩散 — 步骤 1/2/5/6 已实测就绪。把已就绪的收益押在一个尚未成形的生产者决定后面,是用确定换不确定。
Prior rulings read: assembledpackagebody,inert-json,z.function,lowering → 2 hits; ADR-0058 D1, ADR-0058 D3; thread: 3 ruling(s) (5651572469, 5716259259, 5729478920)
⚠️ 那 2 个 ADR 命中是噪声,不是先例。 两条都只命中 lowering 一词,而 ADR-0058 D1/D3 讲的是 CEL→FilterCondition 下推编译器 —— 与 artifact 阶段体无关。⛔ 不要把「2 hits」读成「有两条裁决适用」。
推荐:C。
自检:只看①选 C(record 阶段该有自己的声明,而那要在它自己的包里裁);②③④ 是否翻转:否 —— ② 加固它(欠报独立存在)、③ 加固它(拆开才不会再错位一轮)、④ 加固它(已就绪的不该陪跑)。
置信缺口: ⛔ NOT MEASURED —— registry 铸的 ref 与 build 铸的 ref 在真实包上到底有多常不相等(uniqueName 撞名加后缀、按函数同一性并键,两条都只从源码读出,⛔ 没有在一个真实多函数包上量过)。选 A 就等于在这个未测量上下注。
⚠️ 另有 GAMMA(保留生产者行为,单给 record 阶段一个「callable 未存活」的声明)⛔ 未给独立字母:它实质上就是 C 的子卡要裁的内容,给字母会让同一问题在两处各裁一次。⛔ 而把 GAMMA 等同于原 letter C 是不准确的 —— 原 letter C 被否的理由是「为同一个设计增加 2–3 个永久导出」,与 record 阶段该不该有声明是两回事。
Found while landing #17431. Filed rather than fixed: the remedy moves AssembledPackageBodySchema's own shape, which is #14242's ruling territory, and #17431 is bound to the read API one layer up.
The contract, verbatim
ADR-0130 D4's own docblock on ArtifactPackageEntrySchema (packages/spec/src/stack.zod.ts) states why plugins and devPlugins are envelope keys that no package body may carry:
An artifact is inert JSON: a plugin written inside packages[i].manifest could never be constructed by a loader, so a reader that resolved it there would register garbage where it used to skip in silence.
AssembledPackageBodySchema — the body half of ArtifactPackageSchema, i.e. one package as assembled into a release artifact — nevertheless carries two collections whose declarations admit values that same sentence says can never exist in an artifact:
functions, whose entry schema FlowFunctionEntrySchema (automation/flow-function.zod.ts) has z.function() as its first union branch;
hooks, whose HookSchema (data/hook.zod.ts) has a z.custom() branch.
A callable in an artifact is exactly the case the quoted sentence excludes: the artifact is JSON on disk, so the branch describes a value the surface cannot hold.
Measured consequence
Of the assembled body's 55 shape members, exactly those two have no JSON Schema form. Measured with z.toJSONSchema(member, { io: 'input' }) over every key of AssembledPackageBodySchema.shape:
functions: Function types cannot be represented in JSON Schema
hooks: Custom types cannot be represented in JSON Schema
and, on the whole body:
| probe |
result |
AssembledPackageBodySchema |
FAIL — Function types cannot be represented in JSON Schema |
.omit({ functions: true }) |
FAIL — Custom types cannot be represented in JSON Schema |
.omit({ hooks: true }) |
FAIL — Function types cannot be represented in JSON Schema |
.omit({ functions: true, hooks: true }) |
OK |
That is why ArtifactPackage and ObjectStackDefinition publish no JSON Schema at all, and it propagates: any published export that embeds the assembled body loses its own JSON Schema and its content/docs/references/** page with it. #17431 hit exactly that — binding the body into ListInstalledPackagesResponseSchema and GetInstalledPackageResponseSchema made both disappear from json-schema/api/, which build-schemas.ts's disappearance ratchet refuses.
So the cost is not local to the artifact schema. It is a standing tax on every future surface that wants to declare the assembled stage — and declaring the assembled stage is the ruled remedy for the #14242 class.
Why this is a contract violation and not a preference
The two branches are not merely unused here: the surface's own declared semantics say they are unreachable. A declaration that admits what its surface cannot hold is a claim nothing enforces, and here it has a measured price paid by unrelated schemas.
⛔ Not proposing the shape. Two obvious directions exist (narrow the two collections at the assembled body, or give artifact-stage variants of the two entry schemas) and both move a declaration #14242 ruled on, so the choice is the maintainer's.
Related: #14242 (the ruling that created AssembledPackageBodySchema) · #17431 (where this was measured) · #11072 (the sibling axis: the same tree also carries a Node-only import, which is a separate matter)
Generated by Claude Code
os-decision-facets
The question: what shape does the REGISTRY RECORD stage take?
⛔ Not a dev's choice: it changes the payload
GET /packagesemits, lands in@objectstack/objectql— another published package, with its own Clause-② — andtoRecordManifest's docblock states it is a structural projection and ⛔ 「not a key denylist」, so any fix that special-cases a key name overturns a written design rule.buildmints (lower-callables.tsusesuniqueName(base, taken)and dedupes by function identity) ⇒ a record could assert a ref that resolves in no sibling moduleeffectdeclaration, the one half that survives todaypm:blockedbehind it四棱
① 项目长远合理性 — 三个声明过的阶段(authoring / 内存装配 / 落盘 artifact)之外,registry record 是事实上的第四个阶段:callable 已经没了,却没有 ref 顶上。今天没有任何声明描述它,这才是残骸能存在的根因。⇒ 偏向让 record 阶段拿到自己的声明。
② 实际业务拉动 — ⭐ 欠报是独立于本卡的真缺陷:机器可读的读门把「这个包声明了几个函数」说少了,而且踩在我们自己发布的 showcase 上(
config.ts:244-249正好一条裸的、一条声明式的,两种残骸各一)。⛔ 它不因为本卡怎么裁而消失。③ 防 AI 犯错 — ⛔ 别再让一次派发同时裁「schema 形状」和「生产者行为」。本卡已因「裁决措辞 ↔ 代码事实」错位空转两轮(B 撤回、A 卡在自己的步骤 4)。B 的失败模式最危险:它让读门静默少报而不响亮拒绝。
④ 创业阶段不扩散 — 步骤 1/2/5/6 已实测就绪。把已就绪的收益押在一个尚未成形的生产者决定后面,是用确定换不确定。
Prior rulings read: assembledpackagebody,inert-json,z.function,lowering → 2 hits; ADR-0058 D1, ADR-0058 D3; thread: 3 ruling(s) (5651572469, 5716259259, 5729478920)lowering一词,而 ADR-0058 D1/D3 讲的是 CEL→FilterCondition下推编译器 —— 与 artifact 阶段体无关。⛔ 不要把「2 hits」读成「有两条裁决适用」。推荐:C。
自检:只看①选 C(record 阶段该有自己的声明,而那要在它自己的包里裁);②③④ 是否翻转:否 —— ② 加固它(欠报独立存在)、③ 加固它(拆开才不会再错位一轮)、④ 加固它(已就绪的不该陪跑)。
置信缺口: ⛔ NOT MEASURED —— registry 铸的 ref 与 build 铸的 ref 在真实包上到底有多常不相等(
uniqueName撞名加后缀、按函数同一性并键,两条都只从源码读出,⛔ 没有在一个真实多函数包上量过)。选 A 就等于在这个未测量上下注。Found while landing #17431. Filed rather than fixed: the remedy moves
AssembledPackageBodySchema's own shape, which is #14242's ruling territory, and #17431 is bound to the read API one layer up.The contract, verbatim
ADR-0130 D4's own docblock on
ArtifactPackageEntrySchema(packages/spec/src/stack.zod.ts) states whypluginsanddevPluginsare envelope keys that no package body may carry:AssembledPackageBodySchema— the body half ofArtifactPackageSchema, i.e. one package as assembled into a release artifact — nevertheless carries two collections whose declarations admit values that same sentence says can never exist in an artifact:functions, whose entry schemaFlowFunctionEntrySchema(automation/flow-function.zod.ts) hasz.function()as its first union branch;hooks, whoseHookSchema(data/hook.zod.ts) has az.custom()branch.A callable in an artifact is exactly the case the quoted sentence excludes: the artifact is JSON on disk, so the branch describes a value the surface cannot hold.
Measured consequence
Of the assembled body's 55 shape members, exactly those two have no JSON Schema form. Measured with
z.toJSONSchema(member, { io: 'input' })over every key ofAssembledPackageBodySchema.shape:and, on the whole body:
AssembledPackageBodySchemaFunction types cannot be represented in JSON Schema.omit({ functions: true })Custom types cannot be represented in JSON Schema.omit({ hooks: true })Function types cannot be represented in JSON Schema.omit({ functions: true, hooks: true })That is why
ArtifactPackageandObjectStackDefinitionpublish no JSON Schema at all, and it propagates: any published export that embeds the assembled body loses its own JSON Schema and itscontent/docs/references/**page with it. #17431 hit exactly that — binding the body intoListInstalledPackagesResponseSchemaandGetInstalledPackageResponseSchemamade both disappear fromjson-schema/api/, whichbuild-schemas.ts's disappearance ratchet refuses.So the cost is not local to the artifact schema. It is a standing tax on every future surface that wants to declare the assembled stage — and declaring the assembled stage is the ruled remedy for the #14242 class.
Why this is a contract violation and not a preference
The two branches are not merely unused here: the surface's own declared semantics say they are unreachable. A declaration that admits what its surface cannot hold is a claim nothing enforces, and here it has a measured price paid by unrelated schemas.
⛔ Not proposing the shape. Two obvious directions exist (narrow the two collections at the assembled body, or give artifact-stage variants of the two entry schemas) and both move a declaration #14242 ruled on, so the choice is the maintainer's.
Related: #14242 (the ruling that created
AssembledPackageBodySchema) · #17431 (where this was measured) · #11072 (the sibling axis: the same tree also carries a Node-only import, which is a separate matter)Generated by Claude Code