fix(app-shell): the permission matrix models the artifact tier — no Save that 403s on multi-env kernels (#4518) - #4525
Merged
Conversation
…ave that 403s on multi-env kernels (#4518) The server's metadata write gate is two tiers and this editor modelled only the first. After #4446 (PR #4519) opened the type tier, an environment-scope edit of a CODE-DECLARED permission set offered live checkboxes and a Save button that failed at the end with 403 not_overridable. `saveMetaItem` refuses a second time, after the type-tier disjunction has already passed: for an item a code package ships, `allowRuntimeCreate` is not enough, because overwriting a packaged item is an OVERLAY and overlaying needs `allowOrgOverride`. `permission` sits exactly in that gap. The editor now computes the same three-way rule `ResourceEditPage:1332` has modelled all along, `sys_metadata` provenance sentinel included, read off the layered envelope it already fetches. No new probe. Scoped to the environment door via `packageId`: under a packageId the write is a package-door draft (ADR-0086 P0/P2) whose measured behaviour is 200, and a code-defined package there already arrives with the host `readOnly` prop set. So #4446's headline case stays writable, as do runtime-created sets at both scopes. The new read-only case gets its own caption. Naming the type would be the mirror image of the wording #4446 removed: the type does have a runtime write channel — what is locked is this one set, because a code package provides it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017Qqyix2QcnpUC9XeYVDzx3
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
Contributor
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
yinlianghui
marked this pull request as ready for review
August 13, 2026 04:21
Collaborator
Author
|
PM step-7 复核 — ACCEPT (session_017Qqyix2QcnpUC9XeYVDzx3)
Auto-merge armed (squash). Generated by Claude Code Generated by Claude Code |
This was referenced Aug 13, 2026
Merged
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 free
to 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.
Closes #4518
The two tiers
The server's metadata write gate is two tiers. #4446 (PR #4519) modelled the first; this adds the second. From
packages/metadata-protocol/src/protocol.ts:1. Type tier — refuse when BOTH flags are false:
2. Artifact tier — for an item a code package SHIPS,
allowRuntimeCreateis not enough:The method's own doc states the split: distinguish "overlaying a packaged item" (requires
allowOrgOverride) from "authoring a DB-only item" (requires onlyallowRuntimeCreate).permissionsits exactly in the gap —allowOrgOverride: false(ADR-0005 forbids per-org overlay of a packaged set: silent privilege drift) withallowRuntimeCreate: true.So after #4519 opened the type tier, an environment-scope edit of a code-declared set rendered live checkboxes and a Save button that failed at the end with a 403 instead of a surface that explains itself up front.
The fix
ResourceEditPage:1332has modelled both tiers all along. This is that same three-way rule, computed off the layered envelopePermissionMatrixEditoralready fetches — no new probe, per the ruling:codeIsArtifactis the sibling's predicate,sys_metadataprovenance sentinel included — a published ORG set also surfaces its active version incode, and the server'sisArtifactBackedexcludes exactly that sentinel ("lookupArtifactItemonly returns items whose_packageIdmarks a genuine code package"). Dropping it would lock every org-authored set the moment it was published.The scoping condition, and one honest deviation from the ruling's letter
The ruling says to take the scoping condition "from the sibling". Measured: the sibling has none —
ResourceEditPage'scanWriteByTypescopes only oncreateMode, so mirroring it verbatim would have re-locked exactly the case the ruling's binding constraint protects. Reported rather than silently resolved.The server's artifact tier is
environmentId !== undefined-scoped, and that key is not visible to a client: it is a server-side row-scoping property of the kernel, the console never passes one touseMetadataClient, andMetadataClientbakes it into a private base URL. The only place it surfaces isGET /discovery— the probe the ruling forbids.So the condition used is the one the filing itself names and this component already holds:
packageId. Under apackageIdthe write is a package-door draft (ADR-0086 P0/P2) whose measured behaviour is 200 — that is #4446's headline case (PUT /api/v1/meta/permission/{n}?package={pkg}on the single-kernel showcase), which stays writable. It also cannot regress there: a code-defined package arrives with the hostreadOnlyprop set, which dominates every other gate anyway.Known, deliberate residue (in the code comment, not hidden): on a single kernel the server disengages its artifact tier entirely, so an env-scope edit of a code-declared set there would be accepted (200) while this renders read-only. That is the conservative direction — an honest lock rather than a Save that 403s — and closing it needs the kernel's environment topology on the client, i.e. the forbidden probe.
Honest caption
A third reason in the same badge slot, not a borrowed one. Naming the type would be the mirror image of the wording #4446 removed: the type does have a runtime write channel — a brand-new set authored on this screen saves fine — so what is locked is this one set, because a code package provides it.
The hint carries the server's own reason and its documented remedy (
allowOrgOverride,not_overridable,OS_METADATA_WRITABLE), the same place the 403 text puts it — never in the label. New rows go through the metadata-admin defaults maps (EN + ZH), the channel this surface uses, following #4519's shape.The header hero badge is deliberately untouched and is not a re-run of the #4036 divergence:
PageShell'sWritabilityBadgerenders "create-only" for this shape, whose tooltip already reads "Code-shipped items are locked; new items can be created at runtime". That is precisely what the artifact tier says, so the two renderings agree. A case pins it.Red-first
Directions were predicted before the revert; the run matched exactly — 3 red, 9 green. Reverting only the two source files to
origin/main(172c73e) and keeping the new suite:(The two
AssertionErrorlines readexpected+ the rendered Save button element +to be nullin the real output; the element is transcribed as[ button element ]here for the sanitizer reason noted at the top.)The 9 that stay green are green on purpose — they are the must-not-change set: the #4446 headline case under a
packageId, runtime-created sets at both scopes, thesys_metadatasentinel, an overlay-allowed type, both-flags-false still naming the TYPE, a failed layered read failing open, and thereadOnlyprop still winning and still naming the PACKAGE.Verification
pnpm --workspace-concurrency=2 --filter '@object-ui/app-shell^...' build→ exit 0.pnpm exec vitest run --maxWorkers=2 packages/app-shell/src/views/metadata-admin/ packages/app-shell/src/views/studio-design/→ 166 files / 1660 passed, 1 skipped (studio-design hosts this editor in the Access pillar).PermissionMatrixEditor.*suites, verbose → 49 passed, including fix(app-shell): the permission matrix honors allowRuntimeCreate — and the read-only banner tells the truth (#4446) #4519's 16 pins (readonly7 +readonlyHeaderBadge9, four-state convergence table included) and the 12 new ones.tsc --noEmit→ exit 0;tsc -p tsconfig.test.json→ exit 0.eslinton the three changed files → 0 errors, 22 warnings. Measured as pre-existing:PermissionMatrixEditor.tsxreports 20 warnings onorigin/mainand 20 with the fix;i18n.ts0; the new suite's 2 are the fixtureas anycasts every sibling suite uses.check:control-bytesOK (4238 tracked files),check:i18n-keysOK (2888 keys, every call-site key resolves),check:i18n-driftOK (0 en values changed — it scopes topackages/i18n/src/locales, and these rows live in the app-shell defaults maps),changeset:checkOK,check:phantom-depsOK..d.tsmeasured both ways: built@object-ui/app-shellwith the fix and again with the two source files reverted, hashing every emitted.d.ts. Identical —b3d87a1ab9c3fb2df691f9050716e20a2f6cd163a86d32c35dfbac6777d1639eboth times. The new helper is module-local and the new state is internal, so there is no public type surface change: the changeset is patch.The fix was taken out and restored with
git checkout+ a patch file, nevergit stash(shared stack), and both restores were verified byte-identical by sha256.Generated by Claude Code