Skip to content

fix(studio): object pickers offer only the objects the edited package may reference - #28329

Merged
objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/project-thread-rbfgwx
Oct 31, 2026
Merged

objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/project-thread-rbfgwx

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 31, 2026 •

Copy link
Copy Markdown
Contributor

domain:ui#3 · os-tesla · session_01L9qibvSmNNn2cV2emnvGXn

Requested by Tesla · project thread

Fixes #28240

Clause-②: no
Metadata-protocol: none

维护者速读

  • 改了什么:Studio 的对象选择器(流程触发对象、流程节点对象、查找字段的关联对象、新建数据集的对象)现在只能选当前软件包可用的对象:自己的、已声明依赖包的、系统对象。范围外的对象放在最后一个默认折叠的分组里,灰显不可选,并说明原因,附「声明依赖」入口,直接打开软件包编辑框;保存后不用刷新即可选。
  • 为什么改:发布闸门本来就拒绝范围外的对象,但选择器什么都列,作者在对象上把流程做完,保存后才看到拒绝。
  • 风险与回滚:依赖遍历从服务端搬进 @objectstack/lint 的 resolvePackageClosure,服务端改为调用它,接纳范围不变(服务端原有闭包测试未改、全绿)。软件包列表读不到时不做任何收窄,由服务端判定,与闸门的失败方向一致。回滚即 revert 本 PR。
  • 先看:ui/packages/app-shell/src/views/metadata-admin/inspectors/objectPickerClosure.ts、ObjectPicker.tsx 的分组与禁用逻辑、packages/metadata-protocol/src/protocol-write-package-scope.ts。
  • 两分钟验证:pnpm --dir ui exec vitest run packages/app-shell/src/views/metadata-admin/inspectors/ObjectPicker.closure-28240.test.tsx,预期 6/6 通过。
  • 需要你做的:无,席位落地。

What changes

  • @objectstack/lint: new export resolvePackageClosure(packageId, getPackage), on both the root and the ./runtime entry. It is the dependency walk resolveWritePackageScope ran inline, moved verbatim: same guards (no id, the sys_metadata sentinel, an unknown package answer undefined), same DFS, a declared-but-unresolvable dependency still in the closure, own id skipped. A throwing getPackage propagates; the host decides.
  • @objectstack/metadata-protocol: resolveWritePackageScope keeps its two host guards (registry without getPackage, the silent catch) and calls the shared walk. What the gate admits is unchanged.
  • Studio object picker (ObjectPicker.tsx, shared by the flow New dialog's record presets, every flow object reference, a lookup's related object and the new-dataset dialog): when a package is being edited, it reads GET /api/v1/packages (the request packages-io.ts already shares) and lazily imports @objectstack/lint, then calls resolvePackageClosure and narrowObjectsToPackageClosure over the served object list. Neither rule is restated in ui.
    • Objects the closure does not admit are listed last, in a group that starts collapsed ("Show objects outside this package's dependencies (N)"), each aria-disabled with "From PACKAGE". A search reaches them without opening the group.
    • The group's first row carries the reason and, when the package is writable, Declare a dependency, which opens the package's edit dialog (PackageFormDialog mode edit). The picker re-reads the closure on objectui:packages-changed, so a declared dependency's objects become pickable without a reload.
    • Choosing a disabled row commits nothing. Free-text entry is unchanged (Enter or blur keeps what was typed; the server judges it).
    • No package in scope, the package list unreadable, or the package not listed: nothing is narrowed (the gate's own failure direction).
  • i18n: six engine.inspector.objectPicker.* keys, one contiguous block in each studio pack (en, zh).
  • Changeset: .changeset/28240-studio-object-picker-closure.md (patch: app-shell, i18n, lint, metadata-protocol).

Presentation choice (card item 1, "absent or disabled"): disabled with the reason and the declare door, as the seat ruled. A missing object reads as "the platform has no such object"; a disabled one tells the author what to do.

Tests

  • packages/lint/src/runtime-gate.resolve-package-closure.test.ts (new): the guards, own-only closure, transitive walk through a cycle, an unresolvable declared dependency kept, a throwing registry propagating, and that the narrow ./runtime entry names the function.
  • ui/.../inspectors/ObjectPicker.closure-28240.test.tsx (new), with the real lint functions and only the two reads doubled:
    • the card repro as a pin: a package with no dependencies is not offered showcase_task; searching finds it disabled, with the reason, and clicking it commits nothing;
    • CONTROL: with com.example.showcase declared, showcase_task is offered and commits;
    • a dependency declared after mount makes the object pickable once the package list changes;
    • platform objects stay pickable with no dependency;
    • the declare row opens the package edit dialog;
    • an unreadable package list narrows nothing.
  • ObjectPicker.test.tsx: a module-scope fetch double for GET /api/v1/packages (empty list, so nothing narrows and its expectations are unchanged). The picker now reads that list on mount, and the network-escape guard reds a real socket.
Readings
# lint + server closure tests (server behaviour unchanged; existing server tests untouched)
packages/lint: runtime-gate.resolve-package-closure, runtime-gate.package-closure, rule-id-barrel-exports, authoring-rule-wiring
  Test Files  4 passed (4) · Tests  75 passed (75)
packages/metadata-protocol: protocol.package-closure-gate, protocol-publish-drafts-closure, protocol.draft-publish-verdict,
  protocol.runtime-authoring-gate, sys-metadata-repository.package-writability, protocol.packageless-save-address
  Test Files  6 passed (6) · Tests  382 passed (382)

# picker
ObjectPicker.closure-28240.test.tsx + ObjectPicker.test.tsx :: Test Files 2 passed · Tests 16 passed

# broad sweep (every test under views/metadata-admin/, views/studio-design/, console/ai/)
pnpm --dir ui exec vitest run --maxWorkers=3 packages/app-shell/src/views/metadata-admin/ packages/app-shell/src/views/studio-design/ packages/app-shell/src/console/ai/
  Test Files  1010 passed (1010) · Tests  9500 passed | 1 skipped (9501)

# ablation (source; the test imports ObjectPicker relatively, no dist in the path)
node scripts/ablation-replace.mjs --anchor "admitted && !admitted.has(e.name)\n        ? 'outside'" --replacement "false\n        ? 'outside'"
  mutation landed: anchor 1 -> 0, blob df4f2d4b -> 92c0e63c
  ObjectPicker.closure-28240.test.tsx :: Tests 4 failed | 2 passed (repro, re-read, platform, declare red; CONTROL and unreadable stay green, as they must)
  restore: blob after restore df4f2d4b = HEAD, git diff HEAD empty

# type-check
pnpm --filter @objectstack/lint run typecheck · @objectstack/metadata-protocol typecheck · @object-ui/app-shell type-check · @object-ui/i18n type-check :: exit 0

Verification

Head 3c5e468c (branch merged with origin/main, full rebuild pnpm turbo run build --filter='!@objectstack/docs' 115/115).

  • Derived gates: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derives 64 families; each was run with </dev/null and every one exits 0. Reconciliation --ran: 64 derived, 64 run, 0 NOT-MEASURED, 0 UNRUN.
  • node scripts/check-sdui-manifest.mjs is among the 64 (exit 0). ui check:new-line-citations: 0 new. pnpm --dir ui exec vitest run scripts/ (pre-merge head): 169 files, 4901 tests passed.
  • Rerun on this head: picker 2 files / 16 tests, lint 4 files / 75 tests, metadata-protocol closure tests 6 files / 382 tests, type-checks (lint, metadata-protocol, i18n, app-shell) exit 0.
Live measure (card item 2, and the picker)

Own stack: showcase backend objectstack dev --seed-admin --fresh -p 41873; the Console production build (vite build) served by vite preview --port 51874 proxied to it. (The Vite dev server cannot load @objectstack/lint in the browser, see Acceptance notes, so the picker was measured on the production bundle.)

Item 2, the dependencies editor is reachable from Studio, no wiring needed:

  • New package (Studio landing, New package (writable base)): package-form-dependencies visible before Advanced = false, after expanding Advanced = true. It sits in the collapsed Advanced section, which is likely why the walkthrough missed it.
  • Package settings (package chip, Package info & settings, Edit): package-form-dependencies visible = true, one row, com.example.showcase (declared in the step below).

Item 1, the card's repro on com.default.acme-custom (no dependencies), Automations, New, When a record is created, Object:

  • pickable before search: sys_inbox_message, sys_file, sys_upload_session (platform objects); toggle "Show objects outside this package's dependencies (24)";
  • typing task: showcase_task aria-disabled="true", "From com.example.showcase", reason row "Acme Custom can use only its own objects, …"; clicking it left the input at the typed task (nothing committed);
  • Declare a dependency opened the package edit dialog; adding com.example.showcase / * and saving closed it; the stored manifest reads {"com.example.showcase":"*"}, and the same picker then offered showcase_task enabled.

Acceptance notes

  • Vite dev Console cannot import @objectstack/lint in the browser (measured on this branch: Module "module" has been externalized for browser compatibility. Cannot access "module.createRequire" in client code). The lint graph imports node:module in six rule files. In dev the picker therefore narrows nothing and the server judges, the documented failure direction. The production bundle loads it (measured above). The existing in-browser lint callers (preview/capabilityLint.ts, preview/securityPostureLint.ts, metadata-admin/celAuthoring.ts) take the same dynamic import, so they degrade the same way in dev. Not changed here; Refs dev: the Vite dev Console cannot import @objectstack/lint in the browser (node:module createRequire named import), so every in-browser lint caller silently degrades to no lint under pnpm dev #28333.
  • Report data source: a new report binds a dataset, not an object, so its picker is the dataset list and is not narrowed here. A new dataset's object picker is narrowed.
  • Free text stays free: a name typed and committed with Enter is still saved as typed (flow references may hold expressions), and the server refuses an out-of-closure name at publish as before.
  • Card item 3 "the server refusal no longer reachable through the picker" holds for every pick from the list; the free-text path above is the remaining way to reach it, by design.
  • File surface: ObjectPicker.tsx and the new objectPickerClosure.ts live in views/metadata-admin/inspectors/ (the shared picker every Studio object picker uses), and packages-io.ts gains a manifest reader. StudioDesignSurface.tsx is not touched. packages/lint and protocol-write-package-scope.ts per the seat's claim amendment 6538025413.

🤖 Generated with Claude Code

https://claude.ai/code/session_01L9qibvSmNNn2cV2emnvGXn


Generated by Claude Code

…h gate

resolvePackageClosure is the walk resolveWritePackageScope ran inline,
moved into @objectstack/lint unchanged so Studio's object pickers can
call the same function over the served package list.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L9qibvSmNNn2cV2emnvGXn
Objects outside the package's closure are listed last, disabled, with
the reason and a door to declare the dependency. The closure is the
publish gate's own, computed by the shared lint functions.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L9qibvSmNNn2cV2emnvGXn
@github-actions github-actions Bot added size/l documentation Improvements or additions to documentation tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface labels Oct 31, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/lint, @objectstack/metadata-protocol, touching 3 documentable anchor(s). ⚠️ 2 changed file(s) yielded no anchor (packages/lint/src/index.ts, packages/lint/src/runtime.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

20 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: node scripts/docs-audit/affected-docs.mjs --json 5055b102d40bde2873f29ee7385fad219e2e8e20.

⛔ 9 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.

What this run could not see
  • 2 changed file(s) yielded no anchor (packages/lint/src/index.ts, packages/lint/src/runtime.ts) — pages documenting those are invisible to this run
  • the SDK route bridge reached 59 of 211 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 14 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 5055b102d40bde2873f29ee7385fad219e2e8e20 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 876c8d5a51d854cb05255973c30a15520ae70af5 — the merge of head 3c5e468cf21923d3f4f6cc2be346c8b82007f967 into base 5055b102d40bde2873f29ee7385fad219e2e8e20, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 876c8d5a51d854cb05255973c30a15520ae70af5 && git checkout 876c8d5a51d854cb05255973c30a15520ae70af5
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 5055b102d40bde2873f29ee7385fad219e2e8e20 3c5e468cf21923d3f4f6cc2be346c8b82007f967 && git checkout -B drift-repro 5055b102d40bde2873f29ee7385fad219e2e8e20 && git merge --no-ff 3c5e468cf21923d3f4f6cc2be346c8b82007f967

node scripts/docs-audit/affected-docs.mjs --json 5055b102d40bde2873f29ee7385fad219e2e8e20

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 5055b102d40bde2873f29ee7385fad219e2e8e20 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#3 · os-tesla · session_01L9qibz8HEj53Wx2uphTo2o

维护者速读

席位意见:选择器和发布闸门共用同一个遍历函数,两边不会再对不上;手动输入仍保留,由服务端判定。可以直接落地。

席位核对

ACCEPT · seat domain:ui#3 · head 3c5e468c · 2026-10-31T05:34Z

  1. Shape: Fixes #28240, draft, base main; 13 files, +644/−44. resolvePackageClosure is new in packages/lint/src/runtime-gate.ts and exported from both . and ./runtime. resolveWritePackageScope keeps the registry guard and the silent catch and delegates the walk to it. The seat read the diff: the guards moved inside the shared function (id / sys_metadata / unknown package → undefined), the DFS is verbatim, and the only re-ordering (registry check before id check) answers undefined either way. @objectstack/lint was already a dependency of metadata-protocol. ObjectPicker.tsx adds a collapsed out-of-closure group (aria-disabled, reason row, "Declare a dependency" → PackageFormDialog edit) and re-reads on objectui:packages-changed. Also new: objectPickerClosure.ts, a manifest reader in packages-io.ts, six i18n keys in en and zh, and a changeset.
  2. Premise measured: the live measure on the dev's own stack, using the Console production build, shows the card repro on com.default.acme-custom: showcase_task is offered today and refused only at publish. After the change it is disabled with a reason, and becomes pickable once the dependency is declared. Card item 2: the dependencies editor is already reachable (Advanced section, and Package settings → Edit), so no wiring was needed.
  3. Gates / check-runs: on head 3c5e468c every latest check-run is success or skipped (57 / 10, 0 failure) and mergeable_state is clean. Dev: 64/64 derived gates exit 0, --ran 0 NOT-MEASURED. Lint 75 tests, metadata-protocol closure 382, picker 16, and a broad sweep of 1010 files / 9500 tests all pass. The ablation reds 4 of 6 as predicted, and the CONTROL and unreadable-list tests stay green (os-dev-report 6539426760).
  4. Accepted deviations: none. The Test UI shards ran on this diff because it touches ui/**, so the ci: a diff confined to a package ui tests import (packages/lint, spec, client, core, formula, sdui-parser, types) skips the Test UI shards on the PR and in the queue #28220 gap does not apply.
  5. Out of scope: the Vite dev Console cannot load @objectstack/lint in the browser, so the picker narrows nothing in dev (the server still judges). This is filed as dev: the Vite dev Console cannot import @objectstack/lint in the browser (node:module createRequire named import), so every in-browser lint caller silently degrades to no lint under pnpm dev #28333. Report data sources bind datasets and are not narrowed. Free text stays free by design. Skills-docs: none — Metadata-protocol: none.
  6. Landing: check-governed-merges --pr 28329 says NOT governed, 688 changed lines ≤ 3000. No contract review is owed (Clause-②: no, no packages/spec path). A changeset is present. The seat readies the PR and arms auto-merge under the maintainer's 「按 pm-dispatch 审过的pr你可以自主落地」.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/l tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Studio: object pickers offer only objects inside the package's dependency closure; the dependencies editor is reachable from Studio

1 participant