Repository navigation
fix(metadata-protocol,runtime,app-shell)!: refuse a save or draft publish naming a package that is not installed - #26920
Conversation
…lled on every /meta save and on publish-drafts The flow-only named-base rule is lifted into one helper that saveMetaItem asks for every type, draft and active; publish-drafts refuses an id the registry does not hold before anything is published. Claude-Session: https://claude.ai/code/session_01FEkQ5nhhuhjVQJ8R3qK9UB Co-authored-by: Claude <noreply@anthropic.com>
… states Claude-Session: https://claude.ai/code/session_01FEkQ5nhhuhjVQJ8R3qK9UB Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FEkQ5nhhuhjVQJ8R3qK9UB Co-authored-by: Claude <noreply@anthropic.com>
…very type Claude-Session: https://claude.ai/code/session_01FEkQ5nhhuhjVQJ8R3qK9UB Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FEkQ5nhhuhjVQJ8R3qK9UB Co-authored-by: Claude <noreply@anthropic.com>
…e saves bind A save naming an installed package is judged against that package's closure, so each fixture that now installs its package also lists the objects its views and flows bind. Claude-Session: https://claude.ai/code/session_01FEkQ5nhhuhjVQJ8R3qK9UB Co-authored-by: Claude <noreply@anthropic.com>
A save naming a package no installed package has is now refused for every type. Each fixture installs the authored or code package its saves name, as a boot does, and registers the object a grid binds where the package's closure now judges it. Claude-Session: https://claude.ai/code/session_01FEkQ5nhhuhjVQJ8R3qK9UB Co-authored-by: Claude <noreply@anthropic.com>
…pability probe; fixtures install the packages they name A host whose protocol cannot publish answers 501 whatever id it is asked about, as discard-drafts and the commit trail do. The runtime and rest fixtures that save into a package now install it first, as a boot or POST /packages does. Claude-Session: https://claude.ai/code/session_01FEkQ5nhhuhjVQJ8R3qK9UB Co-authored-by: Claude <noreply@anthropic.com>
… no entry for the package A switcher entry is an installed package, so the not-found check reads the full id list only for an id the switcher lacks; both reads still share the one package-list request. Claude-Session: https://claude.ai/code/session_01FEkQ5nhhuhjVQJ8R3qK9UB Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FEkQ5nhhuhjVQJ8R3qK9UB Co-authored-by: Claude <noreply@anthropic.com>
…t reads `designer.field.apiName` comes from the lazily fetched studio namespace, which answers from en until the zh catalogue lands and re-renders the pillar. The assertion read the title before that landing and only passed on load order. Claude-Session: https://claude.ai/code/session_01FEkQ5nhhuhjVQJ8R3qK9UB Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FEkQ5nhhuhjVQJ8R3qK9UB Co-authored-by: Claude <noreply@anthropic.com>
Four dogfood files saved into package ids no package had, three of them ids the install door refuses outright (underscores are not a reverse-domain segment). Each now installs its base through the protocol's install primitive, under a valid id with the namespace its object names carry, and the uninstall case installs the package again before it republishes. Claude-Session: https://claude.ai/code/session_01FEkQ5nhhuhjVQJ8R3qK9UB Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FEkQ5nhhuhjVQJ8R3qK9UB Co-authored-by: Claude <noreply@anthropic.com>
Brings in #26919, which renames the package draft doors' query check to refusePackageWriteDoorQuery and asks it on every package write door. On publish-drafts the query check still runs first, then the unknown-id 404. Conflicts: the Studio not-found strings re-added after desktopHint in main's i18n.ts; the engine-double ledger regenerated with --write. Claude-Session: https://claude.ai/code/session_01FEkQ5nhhuhjVQJ8R3qK9UB Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 2 package(s): 18 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 11 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. What this run could not see
Coarse fallback — 32 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 73c8c7eaedad771dfaf258d7d4438a2a9078d7de && git checkout 73c8c7eaedad771dfaf258d7d4438a2a9078d7de
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 5e99bd936c4ecd60aa82eb72709e5f7f0d810583 380f37a55aa7ce726b5452e6f724318384184675 && git checkout -B drift-repro 5e99bd936c4ecd60aa82eb72709e5f7f0d810583 && git merge --no-ff 380f37a55aa7ce726b5452e6f724318384184675
node scripts/docs-audit/affected-docs.mjs --json 5e99bd936c4ecd60aa82eb72709e5f7f0d810583
|
… save into The named-base rule now refuses a save naming a package the registry does not hold, for every type. These suites saved views and permission sets into package ids nothing installed. Each now installs the package first, as a boot or POST /packages does: the client and cli door harnesses install both packages (depending on the package that owns `task`), the kernel integration tests create theirs through protocol.installPackage under a valid hyphenated id, and plugin-security's real registry installs the runtime package while its stub registry answers getPackage for it. Claude-Session: https://claude.ai/code/session_01FEkQ5nhhuhjVQJ8R3qK9UB Co-authored-by: Claude <noreply@anthropic.com>
Brings in #26942, which moves the Studio strings into the studio i18n namespace. The two Studio not-found strings this branch adds move with them: from app-shell's metadata-admin table into @object-ui/i18n's studio namespace (en and zh), beside desktopHint. Claude-Session: https://claude.ai/code/session_01FEkQ5nhhuhjVQJ8R3qK9UB Co-authored-by: Claude <noreply@anthropic.com>
席位核对ACCEPT · The maintainer's standing authorisation for landing reviewed PRs, verbatim: 「按照 pm-dispatch 规则复核过的都授权」.
Generated by Claude Code |
Fixes #26708
Clause-②: yes (narrowing)
Metadata-protocol: none
Skills-docs: updated here — content/docs/concepts/metadata-lifecycle.mdx (no
skills/**page teaches saving into a named package:skills/objectstack-apinames?package=only as a read parameter)维护者速读
/meta保存时若?package=指向本部署未安装的包,所有类型(不只 flow)的草稿和直接保存都会被拒(422WRITABLE_PACKAGE_REQUIRED,什么都不写);publish-drafts对未知包 id 先回 404;Studio 打开不存在的包显示"未找到包",未知栏目显示"页面不存在"。publish-drafts还会把它们发布上线。discard-drafts清理。回滚即还原本 PR,无数据迁移。夹具改动量大(约 50 个测试文件改为先安装包),是规则收紧的直接后果。packages/metadata-protocol/src/protocol.ts(uninstalledNamedBaseRefusal与saveMetaItem的调用点)、packages/runtime/src/domains/packages.ts(publish-drafts 的 404 位置)、ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx(pkgMissing判定)。pnpm dev -- --fresh -p 4417后登录,PUT /api/v1/meta/dashboard/x?mode=draft&package=com.example.nosuch应回 422WRITABLE_PACKAGE_REQUIRED;再打开/_console/studio/com.example.nosuch/data应看到"未找到包"。What changes
@objectstack/metadata-protocol— the flow-only named-base rule (metadata: the/metaflow save accepts a write bound to a package id that no installed package has, and stores it active #20863) is now one private helper,uninstalledNamedBaseRefusal, andsaveMetaItemasks it for every type, draft and active, on authoring saves (not on themigrate-storedandpackage-duplicaterewrites of stored rows). A save naming a package id the registry does not hold is refused422 WRITABLE_PACKAGE_REQUIREDwithpackageIdand the ADR-0070 docs link, before anything is written or registered. The flow arm (tenantAuthoredWriteRefusal) calls the same helper, so its sentence is unchanged byte for byte and other types get the same sentence with their type name. Thesys_metadatasentinel, a save naming no package and a save naming an installed package are unchanged.@objectstack/runtime—POST /packages/:id/publish-draftson an id the registry does not hold answers404 RESOURCE_NOT_FOUND(theGET /packages/:idanswer, the existing unknown-id code) before the protocol is asked, even when drafts are stored under that id. The check sits after the capability probe, likediscard-draftsand the commit trail, so a host that cannot publish still answers 501. No error code was added. Stored rows under such an id are not migrated or deleted.@object-ui/app-shell—/studio/ID/…for an id the package list does not hold renders "Package not found" (no pillars, no Changes / Publish / create-app, one button back to the Studio landing). A pillar segment Studio has no pillar for renders a not-found state instead of falling through to Interfaces. It reads the existingGET /api/v1/packages(no new endpoint, still one request): a switcher entry counts as installed; otherwise the id is looked up among every listed id, so kernel packages the switcher hides still open. A list that failed to load leaves the builder as it was.content/docs/concepts/metadata-lifecycle.mdx; changeset26708-named-base-every-type.md(patch on the three packages, BREAKING banner, both declaration lines).Why
PUT /api/v1/meta/:type/:name?package=IDstored the item bound toIDeven when no installed package had that id, as a draft or live, and with no manifest the object namespace rule was skipped too.publish-draftson that id then put the drafts live, owned by a package that cannot be uninstalled or exported. Onlyflowrefused it.Tests
New
packages/metadata-protocol/src/protocol.named-base-every-type.test.ts(13 tests): object, dashboard and page, draft and active, refused with the envelope (code,status,packageId, message first sentence) and nothing inserted or registered; the flow arm's exact sentence; a plural type spelling; controls for an installed package, the sentinel / null / no package, and the two server-side rewrites.packages/runtime/src/domains/packages-draft-doors-unknown-package.test.ts: drafts stored under an unknown id are not published (404, the protocol is not asked, no visibility flip); controls that a known package still publishes and an unknown id's drafts are still discarded.New
ui/…/StudioDesignSurface.notFound-26708.test.tsx(10 tests): not-found on all four pillars with no pillar links or package actions and one list request, the way back, controls for a listed package, a kernel package and a failed list, and the unknown pillar segment.Red first: on the base tree the protocol pins fail 7 of 13 (the 6 controls pass), the runtime pin fails, and the Studio pins fail 6 of 10 (the 4 controls pass).
Fixtures: about 50 test files across metadata-protocol, objectql, runtime and rest saved into package ids that were never installed. Each now installs the package it names, as a boot or
POST /packagesdoes (authored, orcodewhere the file's premise is a shipped package). Where the package's closure now judges a view's object (list-view-object-outside-closure), the fixture registers the bound object under the package or declares the owner as a dependency. None of these widened the rule.Dogfood: three files saved into package ids no boot installs (
package-first-authoring,package-uninstall-revokes-named-grants,flow-shipped-name-published-door). Each now creates its base package first throughprotocol.installPackage(asPOST /api/v1/packagesdoes) under a valid reverse-domain id with its own namespace; the old ids used underscores, which the install door refuses as invalid package ids.StudioDesignSurface.flowCanvasChip-11665.test.tsx: the zh tooltip assertion now waits for the lazily fetchedstudiocatalogue it reads (it answersenuntil the zh catalogue lands and re-renders the pillar). It passed before only because of module load order; adding two keys toi18n.tschanged that order.Package suites on the final head (
48af4d05fc, after mergingmainat96e7a4335c, which holds fix(runtime)!: the package draft doors refuse a query parameter they do not read #26853's draft-door query refusal in the same handler):@objectstack/runtime7468 passed / 19 skipped,@objectstack/metadata-protocol21679 passed / 26 skipped,@objectstack/objectql8843 passed,@objectstack/rest6513 passed / 326 skipped;typecheckexit 0 on all four. UIpackages/app-shell/src/views/studio-design+metadata-admin: 835 files, 8321 passed / 1 skipped;@object-ui/app-shelltype-checkexit 0.scripts/engine-double-contract.pinned.json: the new protocol test's engine double answersdelete,findOneandupdateit never reads; recorded withpnpm check:engine-double-contract --write.Acceptance notes
main, then booted on this branch): the active row still reads (GET /meta/dashboard/NAME200) and still lists under?package=ID; its draft still reads; a save naming the id is refused 422;publish-draftsanswers 404;discard-draftsanswers 200 and removes the drafts; the id is not inGET /api/v1/packages, so Studio shows "Package not found" for it.POST /meta/:type/:name/publish?package=IDpublishes a phantom-bound draft (200). Both only reach rows written before this change.Verification
Live red/green on
pnpm dev -- --fresh, signed in as the dev admin, same probe on both trees. Base (4f05de4): sixPUT /api/v1/meta/{object,dashboard,page}/NAME[?mode=draft]&package=com.example.nosuchall answered 200 and stored the rows bound to that id;POST /packages/com.example.nosuch/publish-draftsanswered 200 whileGET /packages/com.example.nosuchanswered 404. This branch: the same six saves answer422 WRITABLE_PACKAGE_REQUIRED("Cannot save dashboard 'ghost_dashboard_a' into package 'com.example.nosuch': no package with that id is installed in this deployment, …"),publish-draftsanswers404 RESOURCE_NOT_FOUND. Controls unchanged on both: a save naming no package (200), and a package created throughPOST /api/v1/packagesthen saved into and published (201, 200, 200).node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackon48af4d05fc: 105 commands, each run with its exit code captured before any pipe; all 105 exit 0.--ranreconciliation: "105 derived, 105 run, 0 NOT-MEASURED, 0 UNRUN".Full dogfood once on the final head
48af4d05fc(pnpm --filter @objectstack/dogfood test): exit 0, 327 files passed / 3 skipped, 2683 tests passed / 20 skipped.Human Minimum
packages/specholds no runtime logic. (packages/specuntouched.)pnpm lintpasses. (Scoped to the touched files on the final head: root ESLint exit 0;uiESLint exit 0, 0 errors, 14 warnings, all on lines this PR did not write — the one inside the edited effect is the existingsetPkgWritable(undefined)call.)main, one pull request.packages/spectouched: not touched.content/docs/references/,packages/*/CHANGELOG.mdorcontent/docs/releases/.Generated by Claude Code
🤖 Generated with Claude Code
https://claude.ai/code/session_01FEkQ5nhhuhjVQJ8R3qK9UB