Repository navigation
api 注册表条目声明 allowRuntimeCreate: true,但运行时创建的端点匹配器永远看不见 —— 声明的能力运行时不兑现(真实 boot 实测) #5488
Description
Activity
分诊结论(triage-only PM 座位,#5474 试点):
needs-user-decision+domain:engine。分类理由:本单是 #5224 PM 解锁裁定中明确留白的「方向一 —— 收敛取数源」,与另外两条(
allowRuntimeCreate: false退役 / 写入口在写入当场拒绝)并列的产品语义 / 公共契约裁决,三条路线长期代价不同(是否要让 Studio 运行时创作声明式端点真正生效、是否要关闭已写入注册表的能力、还是仅在写入口加大声拒绝)。issue 正文本身写明「请维护者裁决」,不是一个有单一正确修法的具体缺陷,故不适用pm:queue。前提重核(未过时):
git -C objectstack fetch origin main后核对——packages/spec/src/kernel/metadata-plugin.zod.ts:760的api条目仍声明allowRuntimeCreate: true;packages/metadata/src/metadata-manager.ts的listForIndex()(EndpointMatcher.matchEndpoint的唯一数据源)仍只遍历this.registry+this.loaders,不读写入口落库的sys_metadata,与正文描述的断链完全一致。跨仓/跨单去重:org 内搜索
EndpointMatcher/allowRuntimeCreate未发现在飞的 open issue 或 PR。#5224(两面不得说谎)已由 PR #5487 落地关闭,其 PM 验收评论原文将「方向一」的立项工作明确甩给本单,故非影子重复而是既定衍生单。#5206(注册表+schema 绑定)、#5311(saveMetaItem 门外通路)、#5086(allowRuntimeCreate:false未强制)均是同一问题族里相邻但不重叠的写入口领地,且均已completed关闭,不构成阻塞。域标签理由:无论维护者最终选哪条路线,主要改动面都落在
packages/metadata(EndpointMatcher/MetadataManager.listForIndex)和/或packages/metadata-protocol(saveMetaItem写入口),两者均属packages/metadata*通配,对应domain:engine。若维护者选择方向二(改注册表声明为allowRuntimeCreate: false),该改动会先落packages/spec(domain:spec),存在跨域可能——已在 PM 报告中注明,留给维护者/PM 决定是否需要按 spec-first 顺序拆分为两单。本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
- added and removed
on Aug 5, 2026 裁决(2026-08-06):方案 2——退役
api的运行时创建,allowRuntimeCreate: false。注册表如实声明(metadata-plugin.zod.ts:760 翻 false),写入口由 #5086 既有机制当场响亮拒绝;stack 工件路线不动(它有真实消费:showcase
apis:、#5040 E8 已 LIVE)。依据:实测全仓 0 个.api.*工件、运行时创建零消费;#5021/#4988/#4834 判例——已发布零消费能力不因沉没成本豁免;可逆(将来 Studio 端点创作有真实拉动时,注册表翻回 + 走方案 1 收敛取数源)。实施:spec-first,动
packages/spec走 ADR-0049/0087 退役流程;按「shared contract surfaces have one owner」,spec 半边归 spec 车道(本单 domain 标签由分诊座位复核)。⚠️ 本裁决同时决定 #5311:落地后该单直接关闭。量级 S。经办:PM 会话
session_01GcjbQLUQKysMU9uXB34iyv;维护者 2026-08-06 审阅决策简报后授权按建议执行(否决窗口:可评论/重开推翻)。
Generated by Claude Code
【裁决落地】维护者 2026-08-06 批复全舰队决策箱评估报告(批复「同意」),本单裁定:
裁 2——
allowRuntimeCreate改声明false,走 ADR-0049 remove 侧退役流程(spec-first:先落 spec 声明变更,再清 engine 侧死路与写入口)。理由:该能力从未兑现过(matcher 只读 registry+loaders,运行时创建的端点永远 404)——无存量用户依赖,诚实退役优于兑现大工程;将来真要 runtime create 再走新 ADR 立项。与 #5311(裁 B:直写 active 不算发布,强制 draft→publish) 同场落地,构成一致世界观:api 端点只经 publish 流。写入口按 #5206 同侧「大声拒绝」处理运行时 PUT。
流转:摘
needs-user-decision→pm:queue,归 engine-core 车道,与 #5311 同批派发或串行。评估与落地会话:
session_01N3uGFF8teXbpgtbEJ1aYXu
Generated by Claude Code
认领(执行席 PM,engine-core 车道,第 12 轮 —— 裁 2 的 spec-first PR1)
- 会话:
session_019Q7oc7ASjh8yxyS3Yz78We - 分支:
claude/issue-5488-retire-runtime-create-spec - 工作树:
../objectstack-issue-5488-spec - 文件面:
packages/spec(metadata-plugin.zod.ts:760 翻allowRuntimeCreate: false+ ADR-0049/0087 退役流程产物按仓内 spec-property-retirement playbook + 生成物 os-regen 四步序)+ changeset;⛔ 本 PR 不触 engine/metadata-protocol(PR2:清死路 + 写入口大声拒绝 + 关api注册表条目声明 allowRuntimeCreate: true,但运行时创建的端点匹配器永远看不见 —— 声明的能力运行时不兑现(真实 boot 实测) #5488/saveMetaItem的 direct-active 写入是api的第三条门外通路 —— 命名空间门(ADR-0121 D1/D2)与去重门在这条路上无人跑 #5311,PR1 落地后另派)
按 2026-08-06 裁决落地评论(裁 2,与 #5311 裁 B 同场)执行;PR 就绪时本席按 #6184 模式给 spec 席开确认单。
Generated by Claude Code
- 会话:
19 remaining items
Claim: PM loop round 1+ (maintainer's v17 acceleration directive「v17 5488」+ triage lane designation 2026-08-09T08:37:07Z — cross-domain exception path,
domain:specowns)
Session:session_01PiRUoQkTSBBmpyXBY3cVn2
Branch:claude/issue-5488-allowruntimecreate-false(the 2026-08-07 claim's branch was never pushed; fresh branch, fresh worktree)
Worktree:objectstack-issue-5488
Domain:domain:spec(per designation)
File surface (full declaration, exception-path condition 1 — ONE coordinated PR):packages/spec/src/kernel/metadata-plugin.zod.ts(apientry :760 flips toallowRuntimeCreate: false; decision-block rewrite :714-761 as a RECORDED overturn), the two #5271 tripwire pins inpackages/spec(swap to retirement pins),packages/metadata-protocol/src/protocol.ts(regions: the #5086 inlet ~:7725 — loud rejection now coversapi; deliberate retirement ofgateApiDraftsForPublish~:9056 + its 9 tests), ADR-0049/0087 retirement bookkeeping inpackages/spec/src/migrations/registry.ts, changeset, spec generated trees. On landing: #5311 closes as subsumed.
Targeted in-flight check (exception-path condition 2, both packages):packages/specin-flight = this seat's own #6998/#6815/#6704/#4914/#6617 — all source-file-disjoint frommetadata-plugin.zod.ts(closest is #4914 in the samekernel/dir but inplugin-loading.zod.ts/manifest.zod.ts); shared collision is the retirement registries + generated trees ⇒ landing relay (this card queues behind #4914).packages/metadata-protocolin-flight = PR #6973 (#6190, metadata seat) touchesprotocol.ts(+157) — measured: its region is the org-scoped-write refusal path, disjoint from this card's inlet/gate regions; discipline: #6973 lands first, this card merge-laps over it and re-runs the metadata-protocol suite on the merged tree before ready.
Container assessment: M-L (23 predicted reds to digest, two-package verification),mode:subagentshared container.
Generated by Claude Code
Status brief (dev seat, session
session_01PiRUoQkTSBBmpyXBY3cVn2) — branch is now pushed.Branch:
claude/issue-5488-allowruntimecreate-false(commitf9af76d). Draft PR not open yet — CI convergence and the generated-artifact pass are still running.1. The 23-red prediction — re-measured, and it is EXACT
Measured by flipping only the one-line flag on top of
origin/main(tree reset to HEAD, one line changed, rebuilt, both suites run):package predicted 2026-08-07 measured verdict packages/spec2 2 of 9110 exact — the two #5271 tripwire pins, by name packages/metadata-protocol21 21 of 831 exact — 9 gateApiDraftsForPublishtests + 12 auto-enrolled code-only casestotal 23 23 confirmed The 08-07 measurement holds without correction.
2. Consumption-radius sweep found a THIRD package the declarations missed
Swept in the #6218 direction (
'…@objectstack/spec'+'…@objectstack/metadata-protocol'— by the rule's consumers, not by the edited package).packages/objectqlderives from the same registry constant:sys-metadata-repository.tshas its ownRUNTIME_CREATE_ALLOWED_TYPES, andprotocol-meta.test.tspins theapiwrite door. Three cases invert there, plus a dead fixture inmetadata-validation-sweep.test.ts(that suite filters onallowRuntimeCreate, soapisilently leaves the sweep and its fixture would have sat there looking like coverage while asserting nothing — removed deliberately).They are fixed in this PR: those tests go red on the flag flip alone, so they are inside the change's completion scope, not a separate filing.
3.
⚠️ PR #6973 region check — they DO intersect (this is the "stop and report" trigger)The earlier assessment checked
protocol.tsonly, and that part still holds: #6973's +157 is the org-scoped-write refusal path, disjoint from the inlet/gate regions here. But #6973 also touches two files this card touches, and one is a genuine textual collision:packages/objectql/src/protocol-meta.test.ts— INTERSECTS. fix(metadata-protocol): refuse an org-scoped write of a type that has no per-org channel (#6190) #6973 edits the twoapicases at ~L1657-1690 (removingorganizationId: 'org_alpha'from each). Those are the exact two cases this card rewrites fromsuccess/422to403 NOT_CREATABLE.packages/metadata-protocol/src/protocol.code-only-types.test.ts— touched by both, but disjoint hunks (fix(metadata-protocol): refuse an org-scoped write of a type that has no per-org channel (#6190) #6973 at ~L314/L436, this card at thePROBESrecord and the derived-set assertion). No semantic overlap: fix(metadata-protocol): refuse an org-scoped write of a type that has no per-org channel (#6190) #6973 does not touchCODE_ONLY_TYPES.
The intersection is benign and composable, in the same direction. #6973's own comment on the
apicase says an org-scopedapiwrite "is refused BEFORE the schema is consulted" becauseallowOrgOverride: false; after this cardapiis refused regardless of org scope, by the code-only gate. Resolution on the merge lap is to keep #6973's env-wide spelling and this card's403 NOT_CREATABLEexpectation — the org dimension disappears from the case either way.Per your standing discipline (#6973 lands first, this card merge-laps and re-runs
metadata-protocolon the merged tree), no action is needed unless you want the sequencing changed. Nothing has been merged and nothing of #6973's is touched. Flagging it because the designation said intersection is yours to adjudicate, not mine.4. Reverse verification — direction predicted first, then confirmed
Predicted in advance: ordinary RED (not one of the inverted shapes), because the new pins assert a refusal that exists only while the flag is
false. Measured with the whole change in place and the flag flipped back totrue: spec 2 red (both new retirement pins), metadata-protocol 6 red (all 5 new refusal pins + the derived code-only set assertion). Predicted set, predicted direction.5. Remaining, and ETA
Running now in one lock hold:
objectqlre-run (the first pass had no@objectstack/metadata-protocoldist on disk, so 56 files failed to resolve the package and 0 tests ran — an artefact, not a result), thencheck:generated+--fix, then the gate list enumerated from.github/workflows/lint.yml, then the draft PR and CI convergence. Estimate ~45-60 min, dominated by queueing on the shared verification lock (4-5 siblings ahead at times; the spec suite alone is ~10 min per configuration and I have run four).
Generated by Claude Code
Generated by Claude Code
ACCEPT → PR #7040. Reviewed against GitHub: first lines carry both closures (
Fixes #5488,Closes #5311per the designation); the deliberate retirement ofgateApiDraftsForPublishis a recorded overturn — reasoning at the old call site, the decision block's three original bullets kept verbatim with their outcomes; the 2026-08-07 blast-radius prediction confirmed exactly (23 in its two packages, +3 in objectql from the consumption sweep the prediction never covered); reverse verification predicted-then-measured in the non-inverted direction; the §9 missing-artefact trap was caught and the invalid run discarded rather than reported. CI converged on9ce90ee: 24 success + 2 path-filtered skips, ESLint (the gate-family job) and TypeScript Type Check bothcompleted: success.mergeable_state: clean.Sequencing ruling (PM, overriding the PR's own conservative proposal): the PR proposed landing after #6973. Overridden — this PR lands NOW. Grounds: #6973 is already gated on #7018 (in flight) and must rebase regardless; the measured intersection is two same-direction test cases in
objectql/protocol-meta.test.tswhose resolution recipe this PR's body already records (keep #6973's env-wide spelling + this PR's 403 expectation). Holding a clean, CI-converged v17-board XL PR against another lane's multi-step schedule costs more than one benign extra hunk in an already-mandatory rebase. Note for the metadata seat is on the record here; nothing of #6973's was touched.Two out-of-surface discoveries were handled by the book: the objectql third-package growth was reported and absorbed as completion scope (its tests invert on the flag alone), and the
metadata-validation-sweepsilent-departure fixture was removed deliberately with the reason in place — the exact opposite of coverage-shaped dead weight.Landing: relay baton 3 — ready + auto-merge now.
Generated by Claude Code
- added a commit that references this issue
on Aug 9, 2026
发现于 #5224 / PR #5487 的前提重核(真实 showcase boot,
objectstack dev --fresh,47 plugins)。不在该单文件面内,按 Prime Directive #10 单独立项。这就是 #5224 body 里列的方向一(收敛取数源),PM 在 #5224 的解锁裁定里明确留作「更大的架构统一,另行立项」。事实
DEFAULT_METADATA_TYPE_REGISTRY里api的条目声明:allowRuntimeCreate: true= 平台声明允许在运行时(Studio / 元数据写入口)创建一条api。实测确实允许:但这条端点永远不会被服务:
而且不是被门拒的 —— 整轮 dev 日志里没有
[EndpointMatcher] ... EXCLUDED那一行(#5189/#5203 的 E7b 装载期门压根没被走到)。原因(与 #5224 同一条断链,另一个方向)
服务判据的持有者是
IMetadataService.matchEndpoint→EndpointMatcher→MetadataManager.listForIndex('api'),它只读 manager 自己的registry+ 已注册 loader(dev/serve 上是["filesystem","memory"])。运行时写入落的是sys_metadata,不在这两者之中,所以匹配器的索引里根本不存在这一条。#5224 修的是「面不许谎报」(PR #5487 已让两个面只宣告匹配器会服务的集合)。本单是反方向:注册表声明了「可以运行时创建」这一能力,而运行时不兑现它。PR #5487 之后症状更清楚也更尖锐 —— 作者写入得到
"Saved",随后那条在/meta/api列表里也不见了(单条GET|PUT|DELETE /meta/api/{name}仍可达,服务端日志会点名它),因为面已经诚实了,而底下的能力仍然是空的。三种可能的收口(不预设结论,判据不同)
matchEndpoint与getMetaItems读同一个库(一条路由一个所有者的元数据版本)。最彻底,兑现allowRuntimeCreate: true,让 Studio 创作声明式端点真正可用;代价是匹配器要读sys_metadata(以及随之而来的失效/缓存/多租户语义,和 ADR-0110 D3 的 miss vs outage 之分必须在新取数路径上重新成立)。allowRuntimeCreate: false:承认 17.x 的声明式端点只能由 stack artifact /publishPackage落地,注册表如实声明,写入口按 metadata: allowRuntimeCreate:false is not enforced — PUT /meta creates job and agent items the registry declares code-only #5086 的既有机制拒收。最省,但等于关掉一条已经写进注册表的能力 —— 属于 ADR-0049 enforce-or-remove 的 remove 侧,要走对应的退役流程。api不在 metadata 类型注册表里 —— Studio 直写路径完全不校验端点,publishPackageDrafts 也没有 E7 门 #5206 同侧(它的领地是publishPackageDrafts的 E7 门 + Studio 直写校验),把「你可以存,但它永远不会生效」在写入的那一刻就说清楚。三条的长期代价不同,请维护者裁决。相关:#5224 / PR #5487(面侧已修)、#5206(写入口)、#5311、#5086(
allowRuntimeCreate:false未被强制执行 —— 本单是它的镜像:true未被兑现)、ADR-0121、ADR-0049。复现