Uh oh!
There was an error while loading. Please reload this page.
Studio's 新建对象 asks for the record-sharing baseline, and an unauthored one is reported before Publish instead of by it - #5480
Merged
os-support-ai merged 4 commits intoAug 21, 2026
Conversation
…created Studio's 新建对象 collected exactly two fields (label, identifier) and the skeleton it saved declared no `sharingModel`. The draft saved happily, the form editor worked, and the object was then refused at 发布 → 全部发布 by `security-owd-unset` — a required decision the surface never asked for, delivered only by failing. - `buildObjectSkeleton` now takes `sharingModel` as a REQUIRED parameter, so no create path can omit the baseline without failing to type-check. - The create dialog gains the choice, pre-selected to `private` and glossed with the Settings tab's own strings. `controlled_by_parent` is deliberately not offered: a just-created object has no master-detail relation for it to derive access from. - The Settings tab stops describing an unset OWD as safe. It is styled as the publish-blocking problem it is, matching the D11 external-wider warning beside it. Part of #5418 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RV6yuVCxymHYE16PL9vQkE
…ot by it The pending-changes sheet — the surface behind 发布 → 全部发布 — now runs the framework's own `validateSecurityPosture` over the pending object drafts and names any blocking finding, with its fix-it hint, beside the confirm button. It mirrors the producer's rule rather than re-deriving it: a console-local "is sharingModel missing" check would be a fork of a security gate, free to drift from the door it claims to predict. It reports without blocking — the server door stays the authority, and the installed lint can legitimately differ from the version the server enforces. The `import()` stays dynamic on purpose: `@objectstack/lint` is the one `@objectstack/*` package the console's vendor chunk group does not claim, so a static import would pull the lint bundle onto the eager console graph. Copy lands in all ten locale packs — this surface reads the `@object-ui/i18n` catalog, unlike the Studio pillars, whose `engine.studio.*` catalog is a two-locale table. Part of #5418 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RV6yuVCxymHYE16PL9vQkE
…tems as type/name
Adds the positive coverage the previous commit lacked: the sheet names an
OWD-less object draft, keeps Publish enabled while doing so, and goes quiet
once the baseline is authored.
The block addresses an item as `object/crmext_visit` — the way the server's own
publish refusal addresses it, and, unlike a bare name, text that cannot collide
with the same draft's row in the list above. That collision was a live flake:
the existing suite's `getByText('ticket')` matched both the row and the block,
resolving on whichever the async lint won the race to. Fixed by construction, so
the existing suite is left untouched.
Part of #5418
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RV6yuVCxymHYE16PL9vQkE…effect The effect spelling re-rendered the dialog a second time on every open purely to undo a previous session's pick, and tripped react-hooks/set-state-in-effect — one warning over this file's origin/main baseline of 16. Routing both openers through one `openCreateDialog` callback returns the file to 16. Part of #5418 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RV6yuVCxymHYE16PL9vQkE
Contributor
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
os-support-ai
marked this pull request as ready for review
August 21, 2026 02:13
Uh oh!
There was an error while loading. Please reload this page.
os-support-ai
deleted the
claude/issue-5418-studio-new-object-sharingmodel
branch
August 21, 2026 02:14
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes#5418
Studio's
新建对象asked for exactly two things and produced an object the publishdoor refuses. The gate is correct and is unchanged — an org-wide default has to
be an authored decision. What changes is when the console asks, and when it answers.
The walk, re-verified on current
main(9bd7536)The card was measured on 17.1.0. Every step still reproduces:
main新建对象asks for exactly two fieldsCreateItemDialog.tsxrenders twoInputs plus an unusedextraslot; the object call site passed noextrabuildObjectSkeletonreturned{ name, label, fields }— zerosharingModel, counter-probed against 11 hits inObjectSettingsPanel.tsxsecurity-owd-unsetstill errors for any non-system object withowd == nullin the framework'spackages/lint/src/validate-security-posture.tsdoPublish→toast.error(formatPublishFailures(failed))app-shell— the 8 near-hits are the flow simulator, an approval preflight and an i18n column label; counter-probed against 147validatehits in the same treeTwo things the card did not measure, both of which pin the fix:
@objectstack/spec'skernel/metadata-create-seeds.tsseedssharingModel: 'private'forobject,reasoning that the runtime already resolves an absent value to
private(fail-closed, ADR-0090 D1) so making it explicit changes no tenant's effective
sharing. Studio's inline skeleton — which
skeletons.tsdocuments as bypassing theregistry — is the divergence, not the baseline.
"Not set — the platform defaults to Private (ADR-0090)… Pick an explicit model to
widen visibility", and its header comment said "leaving it unset is safe". That
answers what the runtime does and not whether the object can ship.
Surface — located by measurement, as instructed
The dispatch warned not to trust
views/metadata-admin/**. Confirmed: that directoryholds the
engine.studio.*i18n table and nothing else relevant. The create andpublish paths live in the sibling
views/studio-design/, and the review sheet behind发布 → 全部发布 is
preview/DraftChangesPanel.tsx. No package-creation form surfacewas touched, so #5416 stays held out.
The shape chosen, and the ones rejected
The card offered three fixes and said any one closes it. This lands 1 and 2, plus
the wording half of 3.
1 — ask in the create dialog. A third control collects the baseline, pre-selected
to
private, glossed with the Settings tab's own strings so two surfaces cannotdescribe one security baseline two ways.
buildObjectSkeletontakes the value as arequired parameter: a future create path cannot omit the baseline without failing
to type-check.
controlled_by_parentis deliberately not offered at creation — it derives accessfrom a master relation a brand-new object does not have, so offering it would trade
the
security-owd-unsetwall for thesecurity-controlled-by-parent-no-relationone.The framework's own hint draws the same line: "If the object has no master, its
baseline is its own decision — use
sharingModel: 'private'(owner + shares),'public_read', or'public_read_write'." The Settings tab keeps all four, where theobject may since have gained the master-detail field.
This is asking, not defaulting around the gate: a visible, labeled, glossed control
pre-set to the value the rule's own hint calls "recommended default". An author who
reads it and accepts it has authored the baseline.
2 — fail early. The pending-changes sheet now runs the framework's own
validateSecurityPostureover the pending object drafts and names any blockingfinding, with its fix-it hint, beside the Publish button. It mirrors the producer's
rule rather than re-deriving it — a console-local "is
sharingModelmissing" checkwould be a fork of a security gate, free to drift from the door it predicts. It
reports without blocking: the installed lint can legitimately differ from the
version the server enforces, and a console that refuses a publish the server would
accept is the worse failure, because it has no override.
3 — navigable message: partially. The block names the item the way the server's own
refusal does (
object/crmext_visit) and says where the control is. It is not a livelink, because
useSurfaceDeepLinkcaptures its target only at mount, so a?surface=write from an already-mounted pillar moves nothing. Filed as #5476 with thethree candidate routes rather than bolted on here.
Rejected: shortening the duplicated ADR prose in the toast. Tempting — the card
names it — but the duplication is producer-side: the failure's
errorstring alreadycontains the text
issues[]carries. De-duplicating in the consumer is the aliastolerance Prime Directive #12 forbids. Filed against the producer as
objectstack-ai/objectstack#10524.
Rejected: seeding
sharingModelin the skeleton with no UI. It would make theerror go away and leave the decision unauthored — the manual floor, not this card's.
Clause ② — yes
This changes what the console produces: a
新建对象draft now carriessharingModel. It does not change what the platform accepts, and no gate wasweakened.
Reverse-verification
Both legs are source-level within
packages/app-shell— the tests import the ablatedmodules by relative path, not across a package
exportsboundary — so nodistrebuild is involved; the only cross-package resolution in play is the unmodified
published
@objectstack/lint. Direction was predicted before each run.buildObjectSkeletondropssharingModel{ name: 'visit', label: 'Visit', fields }— the card's original defect shapeBoth restored;
git statusclean at each step.Verification
Run on the final commit
f89e0a053:pnpm exec vitest run packages/app-shell/src/preview/ packages/app-shell/src/views/studio-design/ packages/app-shell/src/views/metadata-admin/ packages/i18n --maxWorkers=2→ 273 files, 3056 passed, 1 skipped. The skip is a pre-existing
it.skipIfversion-conditional in
flow-node-config.spec-reconciliation.test.ts, a file this PRdoes not touch. No test skipped, disabled or quarantined here.
type-checkon@object-ui/app-shell+@object-ui/i18n→ both scripts echoed, exit 0.It caught a real widening:
OWD_DEFAULTwas typed over all four models while thecreate set has three.
lint→ 0 errors.StudioDesignSurface.tsxis back at itsorigin/mainbaseline of16 warnings: the first draft's reset-effect added one
(
react-hooks/set-state-in-effect), so the reset moved to anopenCreateDialogcallback — better React and no cascading render.
check:control-bytes,check:i18n-keys,check:i18n-drift,check:phantom-deps,check:self-import,changeset:check— all pass.check:i18n-keysconfirms both newdefaultValuestrings match theirenpackvalues and pass exactly the holes those values have;
check:i18n-driftreports"2 keys added, 0 removed, 0 en values changed".
i18n — a note on "all ten locale packs"
This repo has two catalogs, and the dispatch's instruction applies to one of them:
preview/DraftChangesPanel.tsxreads@object-ui/i18n, so its two new keys went intoall ten packs (ar, de, en, es, fr, ja, ko, pt, ru, zh).
views/metadata-admin/i18n.ts, which is a deliberatetwo-locale table (
en-US|zh-CN).engine.studio.*appears 0 times acrossall ten packs — counter-probed, the packs are real and populated — so the
create-dialog copy follows the existing convention there. Adding it to the ten-pack
would have been dead weight.
Not flaky
Adding the block surfaced a latent race: it rendered the bare object name, which
collided with the existing suite's
getByText('ticket'), resolving on whichever theasync lint won. Fixed by construction — the block addresses items as
type/name—so the existing
DraftChangesPanel.test.tsxis byte-for-byte untouched.Out-of-scope findings
?surface=deep-link captures only at mount #5476 — the block cannot navigate to the object's Settings tab (deep-link is mount-time only)owd-sharing.ts'sisExternalWider— the module that exists to be the single home for exactly that comparison #5477 —[finding]ObjectSettingsPanelre-declaresOWD_WIDTHinstead of callingisExternalWidererrorstring inlines the same validation prose thatissues[]already carries, so every console renders each finding twice objectstack#10524 — the publish failure'serrorstring inlines the proseissues[]already carriesGenerated by Claude Code