Skip to content

PermissionMatrixEditor does not model the artifact-backed write tier that ResourceEditPage does — an env-scope edit of a code-declared permission set can offer Save and get 403 #4518

Description

@yinlianghui

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):

  1. Type tier — refuse only when BOTH flags are false:

    if (!overlayAllowed && !runtimeCreateAllowed) { ... }
  2. 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.

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions