Repository navigation
cli/console: the deployment's SDUI component manifest reaches the metadata save door (page.requires enforcement, stage ①) #20542
Description
Activity
- addedenhancementNew feature or requestNew feature or requestpriority:p2Medium: important, M3Medium: important, M3area:studioChanging a running app without code — authoring, publish, docs and the portalChanging a running app without code — authoring, publish, docs and the portal
on Sep 29, 2026 objectstack-fleet commented
on Sep 29, 2026 ContributorAuthorMore actionsSerial: this card is dispatched after #19922 lands (same resolver, hard serial)
domain:cliexecution PM seat #6024 · sessionlocal_1d2a197c-c20e-4e90-9be8-413d4d432289· written 2026-09-29T04:33Z · ⛔ not a claim;pm:queuestays- Why: step 1 of this card measures the console fallback of
resolveSduiManifest(packages/cli/src/utils/sdui-manifest.ts), and step 2 hasos servecall that same resolver. [finding] the CLI's@objectstack/consolemanifest fallback can never resolve:resolveSduiManifest()asks for@objectstack/console/dist/sdui.manifest.json, but the console'sexportsmap publishes only./package.json#19922 (p2bug, claimed by this seat in the same act) is the fix of exactly that fallback: resolve@objectstack/console/package.jsonfrom the CLI's location and joindist/sdui.manifest.json. Two parallel edits of one resolver would give two answers to 「where the console manifest lives」. - What that means for this card: after [finding] the CLI's
@objectstack/consolemanifest fallback can never resolve:resolveSduiManifest()asks for@objectstack/console/dist/sdui.manifest.json, but the console'sexportsmap publishes only./package.json#19922 lands, step 1's measurement reads its fix. Anexportssubpath on@objectstack/consolemay then be unnecessary, and the dispatch re-derives that. Step 2 reuses the fixed resolver. - Known pitfalls, recorded now:
- Stage ① passes the manifest into
evaluateRuntimeAuthoringGateatpackages/metadata-protocol/src/protocol.ts:5148. That is thedomain:enginepackage, so its one line is declared on the claim as a cross-domain surface. - Once [finding] the CLI's
@objectstack/consolemanifest fallback can never resolve:resolveSduiManifest()asks for@objectstack/console/dist/sdui.manifest.json, but the console'sexportsmap publishes only./package.json#19922 lands,divon an html page is refused wherever a manifest resolves (ruling A on [Decision] may a kind:'html' page author intrinsic HTML tags (div, a)? The console manifest says no, 3 of 3 shipped html pages say yes, and #19922's fallback cannot go live until one side changes #20112). A host that now hands the save door a manifest inherits that refusal: stage ① must say so, or state that it passes the manifest without judging (stages ② and ③ are metadata: refuse to save or load a page whoserequiresnames a plugin that is not loaded (1 key) #20312's).
- Stage ① passes the manifest into
- Why: step 1 of this card measures the console fallback of
objectstack-fleet commented
on Sep 29, 2026 ContributorAuthorMore actionsSerial update: #19922 has landed. Step 1 of this card is done there, and dispatch waits on one file hold
domain:cliexecution PM seat #6024 · sessionlocal_1d2a197c-c20e-4e90-9be8-413d4d432289· written 2026-09-29T14:07Z · ⛔ not a claim;pm:queuestays- The serial in
5883700298is met. [finding] the CLI's@objectstack/consolemanifest fallback can never resolve:resolveSduiManifest()asks for@objectstack/console/dist/sdui.manifest.json, but the console'sexportsmap publishes only./package.json#19922 closedcompleted(PR fix(cli)!: the JSX page gate's console manifest fallback resolves through @objectstack/console/package.json, so a project without its own manifest gets full component checking #20589 →9b402dbaed). - Premise refresh for step 1:
- PR fix(cli)!: the JSX page gate's console manifest fallback resolves through @objectstack/console/package.json, so a project without its own manifest gets full component checking #20589 made the console fallback resolve through
@objectstack/console/package.json(the one subpath itsexportspublishes), joined withdist/sdui.manifest.json(consoleSduiManifestPath,packages/cli/src/utils/sdui-manifest.ts:178onmain). - So the card's step 1 is done, and ⛔
@objectstack/console'sexportsis not widened. The dispatch re-measures it as H0 instead.
- PR fix(cli)!: the JSX page gate's console manifest fallback resolves through @objectstack/console/package.json, so a project without its own manifest gets full component checking #20589 made the console fallback resolve through
- Fold or serial with [finding] the JSX page gate looks for the project's sdui.manifest.json in the invoker's cwd, not beside the config the command was given:
os validate path/to/objectstack.config.tsfrom elsewhere never reads that project's manifest #20166: answered on [finding] the JSX page gate looks for the project's sdui.manifest.json in the invoker's cwd, not beside the config the command was given:os validate path/to/objectstack.config.tsfrom elsewhere never reads that project's manifest #20166 in this act. The two are not folded, and they run in parallel on disjoint files withresolveSduiManifest's signature frozen. This card'sos servecall passes the config's directory, the project-root rule [finding] the JSX page gate looks for the project's sdui.manifest.json in the invoker's cwd, not beside the config the command was given:os validate path/to/objectstack.config.tsfrom elsewhere never reads that project's manifest #20166 establishes. - The one hold: PR docs(cli): re-anchor the dead tracker citations in packages/cli/src to the commits that decided them, and the source-hashes header at its producer #20656 (dead tracker citations in the
domain:clipackages (689 sites, 166 numbers, 95 files): the ruling C+D stage for this lane (from #20556) #20594's stage 3, in flight) rewrites comments acrosspackages/cli/src/**, including theservetests. This card is dispatched once PR docs(cli): re-anchor the dead tracker citations in packages/cli/src to the commits that decided them, and the source-hashes header at its producer #20656 lands. - For the dispatch, not a ruling: the card says
Clause-②: nobecause nothing narrows until stage ②. Handing the save door a manifest could change a save verdict ifevaluateRuntimeAuthoringGatealready judges page source with one. The dev measures that first, and a changed verdict is reported, not shipped.
- The serial in
objectstack-fleet commented
on Sep 29, 2026 ContributorAuthorMore actionsClaim: PM loop round 6 of the
domain:cliseat's sessionlocal_1d2a197c(batch3):priority:p2, stage ① ofpage.requiresenforcement (ruling5881821895on #20312), after #19922 landed; run in parallel with #20166 on disjoint files
Session:local_1d2a197c-c20e-4e90-9be8-413d4d432289
Account:hotlong
Branch:claude/issue-20542-serve-sdui-manifest
Worktree:objectstack-issue-20542
Domain:domain:cli
Seat:domain:cli#1
File surface:packages/cli/src/commands/serve.ts: resolve the manifest throughresolveSduiManifest(configDir), hand it to the save door, and print one boot line when none resolves. It includes the standalone host path it shares, if that path is inpackages/cliorpackages/runtime, named in the report;packages/metadata-protocol/src/protocol.ts: the one-line pass-through ofsduiManifestintoevaluateRuntimeAuthoringGateat:5148, plus the smallest wiring that carries the host's manifest to it. This is a cross-lane touch (packages/metadata*isdomain:engine), declared here per the card's lane assignment;- tests in the touched packages;
- one
.changeset/20542-*.md.
Read-only:
packages/cli/src/utils/sdui-manifest.ts(#20166's; its signature is frozen),packages/console/**(⛔exportsstays closed, since PR #20589 already made the fallback resolve),packages/spec/**. Stop on breach and explain in the report
Container & model:M,mode:subagent,model: default tier(dispatch-gates --tier: no path-derived mandate).
Clause-②: no
Thread-read: 5891952823
Serial constraints cleared:PR #20589 (#19922) landed at 9b402dbaed, and PR #20656 (#20594 stage 3, comments across packages/cli/src) landed at 6bff748bbd. The file lists of all 6 open PRs were read in this act, and none touches serve.ts or metadata-protocol/src/protocol.ts. #20166, dispatched in the same act, touches the validate / compile / lint callers onlyTaken 2026-09-29T15:27Z,
main6bff748bbd. Executes the card's steps 2 to 4. Step 1 is done by PR #20589 and is re-measured as H0.Clause-②: noholds only if handing the save door a manifest changes no save verdict (the card: nothing narrows until stage ②). The dev measures that first, and a changed verdict is reported, not shipped.objectstack-fleet commented
on Sep 29, 2026 ContributorAuthorMore actionsos-dev-report
{ "issue": 20542, "status": "needs_decision", "branch": "claude/issue-20542-serve-sdui-manifest", "pr": null, "session": "local_1d2a197c-c20e-4e90-9be8-413d4d432289", "premise_still_valid": true, "summary": "Stopped before building, per the dispatch's fork clause: no existing seam carries a host fact from `os serve` to the save door, so every wiring needs a new public option on the protocol or a new service contract (options in open_questions). H0 holds: the console fallback resolves to the workspace copy's path, `os serve` resolves no manifest, and the save door is handed none. H1 holds, measured at the real save door with a trap-restored mutation: handing the gate the tracked manifest changes no verdict for a `div`, an `avatar` or a `box` html page (all 200, no issues, no advisories), because the one rule that reads the manifest, `validateJsxPages`, is CLI_ONLY, so `Clause-②: no` stands. The ruling's cloud confidence gap is now read: cloud's per-env kernels mount `createMetadataProtocolPlugin({ environmentId })` and never pass through `os serve`, so a CLI-only wiring would not reach cloud.", "tests": "All readings on BASE 6bff748bbd (origin/main at worktree creation), Node v26.7.0, macOS. Build: `pnpm turbo run build --filter=@objectstack/cli... --concurrency=2` under os-verify-lock (declared UNLOCKED mode, no flock on this host): 'Tasks: 59 successful, 59 total / Cached: 58 cached', VERDICT command-exit 0. H0: from packages/cli/dist, `consoleSduiManifestPath()` = packages/console/dist/sdui.manifest.json (the workspace copy's path; exists=false in this worktree because no console build ran); `resolveSduiManifest(examples/app-showcase)` = absent, lookedAt [the project path, that console path]; `git grep -c 'resolveSduiManifest|sdui' packages/cli/src/commands/serve.ts` exits 1 with zero hits, and the control `resolveConsolePath` in the same file hits 2; packages/runtime/src and packages/objectql/src hold zero `sduiManifest` hits; protocol.ts:5148 is the only non-test call of `evaluateRuntimeAuthoringGate`, and its argument object has 0 `sduiManifest` keys. H1 static: the gate forwards `sduiManifest` (:697) into `runRuntimeAuthoringRules` and on into the rule context, whose one reader is `validateJsxPages` (lint authoring-rules.ts:1060, `surfaces: CLI_ONLY`); `runtimeAuthoringRulesFor('page')` = [validatePresetComparands]. H1 at rule level (lint dist): three html pages whose source roots in a div, in an avatar (inside flex), or in a box, each run through runRuntimeAuthoringRules type page, with and without the tracked repo-root sdui.manifest.json (107 components; div absent, avatar absent, box present), give errors=[] advisories=[] rulesRun=1 in all 6 runs. The control leg: `validateJsxPages` with the same manifest refuses the div and the avatar pages (jsx-forbidden-tag and jsx-unknown-component on each), and without it refuses nothing. So the manifest is live, and only the runtime surface ignores it. H1 at the real save door: `os dev --fresh --seed-admin` on examples/app-crm on a random port, sign in as the seeded dev admin, then PUT /api/v1/meta/page/NAME (active) for h1_div, h1_avatar, h1_box. Without a manifest (main): 200, 200, 200 (state=active, seq=1), no issues, no advisories, GET readback 200 with the source stored. With the manifest: the mutation went through `node scripts/ablation-replace.mjs` in WRAP mode, anchor 'orgWallEnforced: this.orgWallEnforced(),' (protocol.ts:5162) 1 → 0, blob 10802c1003d9 → 4f73b2159ade. It hands the gate the manifest from an env var and logs a probe. `pnpm --filter @objectstack/metadata-protocol build` exit 0; `ablation-dist-preflight.mjs metadata-protocol H1-PROBE` marker present in 2 built files; the server log printed '[H1-PROBE] sduiManifest handed to the gate for page/h1_div: 107 components' once per save (3 of 3). Verdicts: 200, 200, 200, no issues, no advisories, identical to the leg without it. Restore: blob after restore == HEAD blob 10802c1003d9, `git diff HEAD` empty; rebuilt; preflight --absent: 'marker absent from all 24 built files', 'working tree clean against HEAD'; `git status --porcelain` 0 lines. Both servers were torn down by the script's own trap, and 0 listeners were left on either port. H2 / H3: NOT MEASURED, reason: nothing was built (fork clause).", "mcp_calls": "0", "api_writes": "1 -- POST /repos/objectstack-ai/objectstack/issues/20542/comments (this os-dev-report, via scripts/pm/post-stamped.mjs). Not REST: one `git push` of the empty branch (the write-route probe, through with-fleet.sh). No PR create and no label-write, because there is no PR.", "open_questions": [ { "question": "How does the host's SDUI manifest reach `assertRuntimeAuthoringRules` (protocol.ts:5148)? Measured: the protocol is assembled inside ObjectQLPlugin's built-in mode (standalone stack, and the artifact fallback through it) or inside createMetadataProtocolPlugin (cloud per-env kernels, cloud main 3efda046, artifact-kernel-factory.ts:662/667). Its only host inputs today are the constructor arguments, the kernel service map read lazily (the resolveFlowCanonicalizer pattern, protocol.ts:4827), and process env (orgWallEnforced). Cloud's host HTTP layer serves the same @objectstack/console (objectos-runtime node-server.ts:42/74) and holds zero sdui.manifest / resolveSduiManifest references. So no route exists without a new public surface.", "options": [ "A: a kernel service. `os serve` resolves `resolveSduiManifest(path.dirname(configPath))` once and registers the result under ONE service key exported as a constant from @objectstack/metadata-protocol (not a CoreServiceName slot, so no spec edit). The protocol reads that key per publish, the way it reads `automation`, and passes `sduiManifest` at :5148. Absent or unusable: the one boot line, and the boot continues. Reach: every `os serve` composition (library/standalone, artifact fallback, host config, OS_MODE=off), since all share one kernel, plus cloud with one registerService line per per-env kernel. Cost: 2 source files (serve.ts, protocol.ts) plus tests, and one stringly-keyed service contract.", "B: a declared assembly option. `sduiManifest` goes on AssembleMetadataProtocolOptions (plugin.ts:120), MetadataProtocolPluginOptions, ObjectQLPluginOptions, the protocol constructor (protocol.ts:4842) and StandaloneStackConfigSchema (runtime), is forwarded by createDefaultHostConfig, and is passed by serve.ts. Reach: library-mode boots, plus cloud via createMetadataProtocolPlugin({ environmentId, sduiManifest }). It misses host-config boots whose config instantiates its own ObjectQLPlugin. Cost: about 6 source files in 4 packages (two in domain:engine), 4 to 5 new typed public options.", "C: a public setter on ObjectStackProtocolImplementation, called by a small CLI plugin once `protocol` is registered. The reach is A's. Cost: 2 files, a new public method and mutable state after construction, plus an ordering dependency on when `protocol` registers.", "D: an OS_ env var holding a manifest path, read by the protocol per publish. Reach: any host that sets it. Cost: a new deployment config key and file I/O inside the protocol. It also bypasses the ruled resolver (the ruling names resolveSduiManifest)." ], "recommendation": "A. Real business need: the pull is the REST/MCP save door on every `os serve` composition and on cloud's per-env kernels. A reaches all of them with one line per host. B misses host-config boots, and cloud needs a new option either way. Long-term soundness: A follows the protocol's own pattern for facts another layer owns: read lazily per call, where resolveFlowCanonicalizer's header says 'Inside a server there is nothing to thread'. Reading per publish records no startup verdict. B copies the declared-at-assembly precedent (authoringChannel, runPlatformMigrations), but those record what the kernel IS, while the manifest belongs to the UI host layer. Preventing AI authoring errors: stage 1 is neutral, since H1 measured that no verdict moves. B's typed options beat A's string key here, so A must export the key as one constant used by both producer and consumer. Startup scope: A is 2 files and adds no public option. B is about 6 files across 4 packages. Whichever route is chosen: until stage 2 lands, a host that DOES hand over a manifest boots silently while page source is still unvalidated at save. Only the no-manifest boot line is a true statement at stage 1." } ], "out_of_scope_findings": [], "gates": "dispatch-gates --repo objectstack-ai/objectstack --commands exit 2: 'this branch changes nothing against origin/main (merge base 6bff748bb) -- nothing to derive'. pnpm lint, check-issue-citations and package test/typecheck were not run, reason: empty diff, no PR. The one heavy command (the build above) ran in os-verify-lock's declared UNLOCKED mode.", "line_budget": "n/a -- no skills/** and no governed surface touched; zero lines changed", "deviations": [ "No code, no PR, no label-write: the dispatch's clause 'if the wiring needs more than that (a new public option on the protocol, a new service contract), stop and report it as a fork before building it' fired", "First ablation-replace attempt was a no-op: the replacement contained the anchor text (count 1 → 1). The tool refused it and auto-restored (blob == HEAD, git diff HEAD empty), and no reading was taken. Re-run with the anchor spelled distinctly, in the replacement shown in tests", "The real-door leg first tried examples/app-showcase: compile failed on the unbuilt @objectstack/connector-slack dist, outside the cli build closure. Switched to examples/app-crm, whose deps sit inside the closure. The page bodies were PUT, not taken from the app", "Cloud was read at its origin/main (3efda046) through a fetch into an own ref (refs/os-dev/issue-20542-cloud-main), deleted after the read. Nothing in cloud was checked out or edited", "Verification ran in os-verify-lock's declared UNLOCKED mode (no flock on this macOS host)" ], "files_changed": [] }
Generated by Claude Code
objectstack-fleet commented
on Sep 29, 2026 ContributorAuthorMore actions回决策箱:阶段 ① 的前提「A 不新增契约」被实测证伪,需要裁定 manifest 走哪条通道到达保存入口
domain:cli执行席 #6024 · sessionlocal_1d2a197c-c20e-4e90-9be8-413d4d432289· 写于 2026-09-29T15:48Z · 依据 os-dev-report5893633630(dev 在派发令的分叉条款处停下,未写代码、未开 PR)Release: the claim
5893325271is released in this stroke (premise falsified). The assignee comes off;pm:dispatched→needs-user-decision. The empty branchclaude/issue-20542-serve-sdui-manifeststays as a marker, and #20312 stayspm:blockedon this card.一句话问题: 裁决让「部署自带的组件清单」送达元数据保存入口,但实测发现,任何一条送达路径都要新增一个公开接口。而阶段 ① 单独落地时,不会改变任何一次保存的结果。
背景与 Governing text:
- 裁决
5881821895(metadata: refuse to save or load a page whoserequiresnames a plugin that is not loaded (1 key) #20312,维护者「同意」,letter A 分三阶段):①「os serveresolves the manifest through the CLI's existingresolveSduiManifestand passes it intoevaluateRuntimeAuthoringGate」;其理由写的是「A adds no contract」。 - ADR-0080 §5(ruled):「Authoritative parse + sanitize + validate + compile runs server-side at save」。
- 协议既有惯例:别层拥有的事实按需从内核服务表读取,不在装配时穿线(
protocol.ts:4827resolveFlowCanonicalizer,读automation服务)。 - ⛔ 本卡不改协议:不改 spec,也不改 ADR-0080。
前提(dev 实测,附复查命令):
- ① 保存入口今天拿不到 manifest:
git grep -n "evaluateRuntimeAuthoringGate(" origin/main -- packages/metadata-protocol/src/protocol.ts只有:5148一处,参数里没有sduiManifest;os serve不解析 manifest(git grep -c "resolveSduiManifest" origin/main -- packages/cli/src/commands/serve.ts为 0)。 - ② 没有现成通道:协议的宿主输入只有三种:构造参数、内核服务表(按需读)、进程环境变量。云端的 per-env 内核经
createMetadataProtocolPlugin({ environmentId })装配,完全不经过os serve(cloud main3efda046,artifact-kernel-factory.ts:662/667)。 - ③ 阶段 ① 不改变任何保存结果:唯一读 manifest 的规则
validateJsxPages是CLI_ONLY,运行时页面规则只有validatePresetComparands。dev 在真实保存入口(os dev+ PUT/api/v1/meta/page/…)用临时改动把 manifest 交进去,div/avatar/box三个 html 页面有无 manifest 都是 200,无问题、无提示。改动已还原,并经 blob 核对。
选项 × 真实代价:
选项 做什么 客户可感知的后果 A 内核服务 os serve解析一次 manifest,注册到一个由@objectstack/metadata-protocol导出的常量服务键下;协议每次发布时按需读取(同automation的读法)所有 os serve形态都覆盖:库模式、独立栈、artifact 回退、宿主配置。云端每个 per-env 内核多一行注册即可。约 2 个源文件,新增一个「字符串键」服务契约B 装配参数 把 sduiManifest加进 4 个包的装配选项与构造参数(其中两个属于domain:engine),由serve.ts传入库模式和云端(传参)覆盖;宿主配置里自行实例化 ObjectQLPlugin 的启动会漏掉。约 6 个文件,新增 4–5 个类型化公开选项 C 公开 setter 协议实现类加一个公开方法,由一个小 CLI 插件在协议注册后调用 覆盖面同 A;代价是构造后可变状态,外加一个注册时序依赖 D 环境变量 新增 OS_变量存 manifest 路径,协议每次发布时读文件谁设了变量就覆盖谁;多一个部署配置键,协议里多了文件 I/O,并且绕过了裁决点名的 resolveSduiManifestE 并入 ② 阶段 ① 不单独落地,通道随阶段 ②(保存时编译并拒绝)一起建;通道形状仍须在 A–D 中选一 今天零变化;② 的 PR 变大,但通道和它唯一的读者同时出现 业务含义直译:
- A 像给整栋楼装一条公共管道,各户按需接水;
- B 像给每户单独拉一根专线,漏掉了自己装修的那几户;
- C 像在门口挂个钥匙,谁先来谁开;
- D 像把地址写在门口的便条上;
- E 是先不接管,等要用水的那天再一起装。
四轴(业务立场):
- 长远合理性(权重最高): 两年后的样子,是任何宿主(本地
os serve、云端 per-env 内核)的保存入口都用本部署的组件清单判页面,正如 Salesforce 部署校验和 Power Apps 解决方案依赖检查都在服务端、按部署判定。A 用协议自己「按需读别层事实」的惯例,一条通道覆盖全部宿主;B 把 UI 宿主层的事实写进内核装配面,还漏掉宿主配置启动。 - 实际业务拉动: 今天拉动为零,阶段 ① 实测不改变任何保存结果。真正的拉动在 ②:AI 或 MCP 经 REST 写入带未知组件的页面,今天能保存成功。
- 防 AI 犯错: 出错时谁看到什么?阶段 ① 无论选哪项都不改变结果,错误照旧要到终端用户打开页面时才暴露;只有 ② 能把错误变成「保存时 422,点名组件」。另一个事实:① 规定的「没有 manifest」启动提示,在 ② 落地前是一句半真的话,因为有 manifest 时页面同样没有被校验。把这句话留到 ② 再说才诚实。
- 创业阶段不扩散: A 只新增一个导出常量,不新增类型化公开选项;B 新增 4–5 个跨包的公开选项,每个都是永久义务。E 不在没有读者时先建通道。
os-decision-facets
- ① 项目长远合理性:A 沿用协议「按需读别层事实」的惯例,一条通道覆盖所有宿主(含云端),不扩大装配契约;B 在 4 个包增生公开选项,且漏掉宿主配置启动。
- ② 实际业务拉动:阶段 ① 实测零结果变化,拉动全在 ②;无读者先建通道属于投机,宜与 ② 同期。
- ③ 防 AI 犯错:① 任何选项都不能让错误响亮;唯一响亮的是 ② 的 422。① 的「无 manifest」启动提示在 ② 之前半真,应随 ② 一起落。
- ④ 创业阶段不扩散:A 是一个导出常量;B 是 4–5 个永久公开选项;E 不提前声明。
- Prior rulings read:
5881821895(metadata: refuse to save or load a page whoserequiresnames a plugin that is not loaded (1 key) #20312, A staged) → 1 hit; ADR-0080 §5 (ruled); thread: cli/console: the deployment's SDUI component manifest reaches the metadata save door (page.requires enforcement, stage ①) #205425883700298,5891952823,5893633630
推荐:A(内核服务通道),时序按 E:通道与 ② 同期落地。
- 只看① 选 A;②③④ 是否翻转:否。②④ 只调时序(通道不先于读者建),③ 只调启动提示的落地时点。字母不变。
- 回退: B(若维护者更看重类型化选项,胜过一个字符串服务键)。
- 置信缺口: 云端 per-env 内核拿不拿得到同一份 console manifest,只读了代码,没有端到端实测;A 对云端的那一行注册在 cloud 仓,不在本仓车道内。
裁后执行:
- 裁 A + E:本卡关闭
not_planned,理由写「并入 metadata: refuse to save or load a page whoserequiresnames a plugin that is not loaded (1 key) #20312 阶段 ②」。metadata: refuse to save or load a page whoserequiresnames a plugin that is not loaded (1 key) #20312 摘掉Blocked-by回队,由派发 ② 的席位在同一个 PR 里建 A 通道,加上保存时编译和启动提示。云端那一行注册另立 cloud 卡。 - 裁 A 且 ① 单独先落:本卡以 A 重派,只建通道 + 「保存入口收到 manifest」的钉子,启动提示随 ② 落。
- 裁 B:本卡以 B 重派,跨 engine 包的那几个文件按跨域例外路径在认领里申报。
- 裁决
- added a commit that references this issue
on Sep 29, 2026 objectstack-fleet commented
on Sep 30, 2026 ContributorAuthorMore actionsRuling: batch #247 item 2 · letter A + E · maintainer 「20542 同意」 2026-09-30T01:39Z
Director seat (objectstack#12708, summon #30 续 2,
session_01AsCNgFBs8HCjwhyHQsFbx3). Provenance: maintainer, live PM chat with the director seat, 2026-09-30, replying 「11111 同意;20665 业务上需要支持,ENFORCE;20542 同意;5930 v18 启动;20513 A」 to batches #246–#249 as presented; 「同意」 is to the director's recommendation in chat (A + E), which followed thedomain:cliseat's escalation5893682204and the dev's measurements5893633630.Ruled: A + E. The deployment's SDUI component manifest reaches the metadata save door through a kernel service:
os serveresolves it once through the CLI's existingresolveSduiManifest(path.dirname(configPath))and registers the result under one constant service key exported by@objectstack/metadata-protocol(not aCoreServiceNameslot, no spec edit); the protocol reads that key per publish, the way it readsautomation(theresolveFlowCanonicalizerpattern), and passessduiManifestintoevaluateRuntimeAuthoringGateatprotocol.ts:5148. Absent or unusable: the one boot line, and the boot continues. Timing E: the channel is built together with stage ② (save-time compile and refusal) in one PR — stage ① alone changes no save verdict (measured: the only rule reading the manifest,validateJsxPages, isCLI_ONLY), and the "no manifest" boot line is a half-truth until stage ② exists. ⛔ Not B (4–5 typed public options across four packages, and host-config boots that instantiate their ownObjectQLPluginare missed), ⛔ not C (mutable state after construction plus an ordering dependency), ⛔ not D (bypasses the ruled resolver).Readings that decided it:
- Long-term: one channel covers every
os servecomposition and, with one registration line, the cloud's per-env kernels; the protocol's own convention for facts another layer owns is to read them lazily from the kernel service table, not to thread them through assembly. - Real use: zero at stage ①; all of it at stage ② — an AI or MCP client writing a page with an unknown component through REST saves successfully today.
- AI-safety: only stage ② makes the error loud (422 naming the component); the boot line is true only once stage ② is in.
- Startup scope: A adds one exported constant, no typed public option; E builds no channel without a reader.
Execution parameters:
- This card closes
not_planned— merged into metadata: refuse to save or load a page whoserequiresnames a plugin that is not loaded (1 key) #20312 stage ②; the empty marker branchclaude/issue-20542-serve-sdui-manifestmay be deleted by its seat. - metadata: refuse to save or load a page whose
requiresnames a plugin that is not loaded (1 key) #20312 returns topm:queue(itsBlocked-byremoved) with the dispatch rewritten: one PR in thedomain:clilane builds the A channel (the constant key in@objectstack/metadata-protocol,os serve's registration, the per-publish read and the:5148pass-through — the metadata-protocol lines declared as a cross-lane touch), the save-time compile and refusal of stage ②, and the boot line; stage ③ stays metadata: refuse to save or load a page whoserequiresnames a plugin that is not loaded (1 key) #20312's own follow-on. - cloud#2482 (filed in the same stroke,
pm:blockedon metadata: refuse to save or load a page whoserequiresnames a plugin that is not loaded (1 key) #20312): each per-env kernel registers the console manifest under the same key. - Confidence gap recorded: whether a cloud per-env kernel can read the same console manifest the host serves is read from code, not measured.
State:
needs-user-decisionremoved; closednot_plannedwith this record; the Ruled line goes on the body in the same stroke; #20312 re-labelled and its dispatch pointer posted in the same act.- Long-term: one channel covers every
- added a commit that references this issue
on Oct 7, 2026
Ruled: 5902378057 · letter A + E — the channel is a kernel service under one constant key exported by
@objectstack/metadata-protocol, read per publish; built together with #20312 stage ② in onedomain:cliPR; this card closes not_planned, merged into #20312 stage ② (cloud#2482 follows) · 2026-09-30T01:47ZFiled by the director seat (objectstack#12708,
session_01AsCNgFBs8HCjwhyHQsFbx3) as the predecessor of #20312, per ruling5881821895on that card (batch #241 item 1, letter A, maintainer 「同意」):page.requiresis enforced per ADR-0080 §5 in three stages, and this card is stage ①. ⛔ Not a claim.What
No host hands the metadata save door a component manifest today, so the door cannot judge an html page's source against the deployment's components:
evaluateRuntimeAuthoringGateaccepts an optionalsduiManifest(packages/metadata-protocol/src/runtime-authoring-gate.ts:625, passed through at:697); its one call site,packages/metadata-protocol/src/protocol.ts:5148, does not pass it.os validate/os build/os lint(packages/cli/src/utils/sdui-manifest.ts,resolveSduiManifest): the project'ssdui.manifest.json, else@objectstack/console/dist/sdui.manifest.json.@objectstack/console'spackage.jsonexportsmap exposes only./package.json, so that fallback specifier is expected to fail Node's exports check and resolveabsent— NOT MEASURED; measure it first.os servedoes not resolve a manifest at all.Do
createRequire(...).resolve('@objectstack/console/dist/sdui.manifest.json')succeed under the package'sexports? If not, add the subpath to@objectstack/console'sexports(one line) so the CLI's existing fallback and this card's reader both work.os serve(and the standalone host path it shares) resolves the manifest throughresolveSduiManifestand passes it intoevaluateRuntimeAuthoringGateassduiManifest.requiresare not validated at save: no SDUI manifest」 — instead of degrading silently (the [finding] os validate / compile / lint validate JSX pages at parse level only when no SDUI manifest resolves, and say nothing: the author-time JSX gate silently degrades #20113 posture on the CLI side).Out of scope here, stages ② and ③ on #20312: compiling the page source at save, stamping
requires, the load-time report.Clause-②: no(nothing narrows until stage ②). Lane:domain:cli(the host and the console package); the save-door pass-through is a one-line change in metadata-protocol.