Filed unassigned and ungraded while implementing #4446 — routing/grading is the triage seat's call, not the filer's. Independent of #4446 (which is correct as it stands); no Blocked-by:.
Placeholders below are written {set} / {pkg} rather than in angle brackets: GitHub's body sanitizer strips an angle bracket followed by a letter as an HTML tag at rest, which silently ate them on the first save of this issue.
What was measured
The server's metadata write gate is two tiers, not one. From packages/metadata-protocol/src/protocol.ts (objectstack, 5d24f4b94):
-
Type tier — refuse only when BOTH flags are false:
if (!overlayAllowed && !runtimeCreateAllowed) { ... }
-
Artifact tier — for an item that IS artifact-backed, allowRuntimeCreate is not enough:
if (this.environmentId !== undefined) {
const artifactBacked = this.isArtifactBacked(request.type, request.name);
if (artifactBacked && !overlayAllowed) {
// [not_overridable] ... provided by a code package
// and the type has not opted into per-org overlay writes
// status 403
}
}
The method doc states the split directly: distinguish "overlaying a packaged item" (requires allowOrgOverride) from "authoring a DB-only item" (requires only allowRuntimeCreate).
ResourceEditPage.tsx:1332 models both tiers, including the sys_metadata provenance sentinel that keeps a published org item editable:
const isArtifactItem =
!createMode
&& layered?.code != null
&& (layered.code as { _packageId?: string } | null)?._packageId !== 'sys_metadata';
const canWriteByType = createMode
? !!(entry?.allowOrgOverride || entry?.allowRuntimeCreate)
: isArtifactItem
? !!entry?.allowOrgOverride
: !!(entry?.allowOrgOverride || entry?.allowRuntimeCreate);
PermissionMatrixEditor.tsx models only the type tier. It already loads the layered envelope (client.layered() returns code / overlay / overlayScope) but never reads code for a writability decision.
Why this is not a defect in #4446, and where it could bite
Two of the three surfaces are already covered by something else:
The uncovered one is the metadata-admin route at environment scope on a multi-environment kernel: no readOnly prop is passed there, and after #4446 the type tier alone says writable for permission (allowOrgOverride: false, allowRuntimeCreate: true). For a code-declared, artifact-backed set the artifact tier then refuses the save with 403 not_overridable. Result: live checkboxes and a Save button that fails at the end instead of a surface that explains itself up front.
Pre-#4446 this state was unreachable only by accident — the surface was locked for the wrong reason (the overlay flag), so the artifact tier never got a chance to matter.
Not fixed in #4446 on purpose
The #4446 ruling scoped that card to the type-tier disjunction and said explicitly: trust the directory entry's flags exactly as the sibling sites do, do not invent a new server probe. Adding artifact awareness there would also have made the card's own headline case (a code-declared set on the showcase, measured 200) render read-only again — the opposite of what was asked for. So it is recorded here rather than folded in silently.
Suggested direction
Mirror ResourceEditPage's three-way rule in PermissionMatrixEditor, including the sys_metadata sentinel, but keep it scoped to the environment-scope path — under a packageId the write is a package-door draft (ADR-0086 P0/P2) and the measured behaviour there is 200. Worth confirming first whether an artifact-backed permission set is reachable at env scope in the metadata-admin route in practice, since that decides whether this is a real user path or a latent one.
Source
Found while implementing #4446 (PR #4519). Server behaviour read on objectstack 5d24f4b94; client sites on objectui 5cc847c31.
Filed unassigned and ungraded while implementing #4446 — routing/grading is the triage seat's call, not the filer's. Independent of #4446 (which is correct as it stands); no
Blocked-by:.What was measured
The server's metadata write gate is two tiers, not one. From
packages/metadata-protocol/src/protocol.ts(objectstack,5d24f4b94):Type tier — refuse only when BOTH flags are false:
Artifact tier — for an item that IS artifact-backed,
allowRuntimeCreateis not enough:The method doc states the split directly: distinguish "overlaying a packaged item" (requires
allowOrgOverride) from "authoring a DB-only item" (requires onlyallowRuntimeCreate).ResourceEditPage.tsx:1332models both tiers, including thesys_metadataprovenance sentinel that keeps a published org item editable:PermissionMatrixEditor.tsxmodels only the type tier. It already loads the layered envelope (client.layered()returnscode/overlay/overlayScope) but never readscodefor a writability decision.Why this is not a defect in #4446, and where it could bite
Two of the three surfaces are already covered by something else:
readOnlyprop for a read-only (code-defined) package, which dominates every type flag. Locked, correctly.environmentId) — the artifact tier is deliberately disengaged there, which is exactly why the Permission-matrix editor is read-only for a writable package —writablegates onallowOrgOverridealone while the server accepts the package-door write #4446 QA run measuredPUT /api/v1/meta/permission/{set}?package={pkg}returning 200 for a code-declared set. Offering the edit is correct.The uncovered one is the metadata-admin route at environment scope on a multi-environment kernel: no
readOnlyprop is passed there, and after #4446 the type tier alone says writable forpermission(allowOrgOverride: false,allowRuntimeCreate: true). For a code-declared, artifact-backed set the artifact tier then refuses the save with 403not_overridable. Result: live checkboxes and a Save button that fails at the end instead of a surface that explains itself up front.Pre-#4446 this state was unreachable only by accident — the surface was locked for the wrong reason (the overlay flag), so the artifact tier never got a chance to matter.
Not fixed in #4446 on purpose
The #4446 ruling scoped that card to the type-tier disjunction and said explicitly: trust the directory entry's flags exactly as the sibling sites do, do not invent a new server probe. Adding artifact awareness there would also have made the card's own headline case (a code-declared set on the showcase, measured 200) render read-only again — the opposite of what was asked for. So it is recorded here rather than folded in silently.
Suggested direction
Mirror
ResourceEditPage's three-way rule inPermissionMatrixEditor, including thesys_metadatasentinel, but keep it scoped to the environment-scope path — under apackageIdthe write is a package-door draft (ADR-0086 P0/P2) and the measured behaviour there is 200. Worth confirming first whether an artifact-backed permission set is reachable at env scope in the metadata-admin route in practice, since that decides whether this is a real user path or a latent one.Source
Found while implementing #4446 (PR #4519). Server behaviour read on objectstack
5d24f4b94; client sites on objectui5cc847c31.