Skip to content
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
This repository was archived by the owner on Nov 4, 2026. It is now read-only.

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

Closed

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.

Activity

  1. self-assigned this
    on Aug 13, 2026
  2. yinlianghui commented on Aug 13, 2026

    @yinlianghui
    ContributorAuthor

    CLAIM — session session_017Qqyix2QcnpUC9XeYVDzx3, branch claude/issue-4518-matrix-artifact-tier.

    Ruling (delegated authority; open veto window): model the ARTIFACT tier in the permission-matrix editor the way ResourceEditPage:1332 already does — both tiers, including the sys_metadata sentinel — so an env-scope edit of a code-declared permission set on a multi-environment kernel stops offering a Save that 403s. The binding constraint from the filing: the artifact-tier gate is environmentId !== undefined-scoped server-side (protocol.ts:8890-8899) — the fix must NOT re-lock the single-env/showcase case #4519 just opened (that case measured 200 and stays writable). Mirror the sibling's model; do not invent a probe.

    Red-first: multi-env shape (environmentId set), artifact-backed permission set, overlay disallowed → pre-fix Save offered (capture) and the write path would 403; post-fix read-only with an honest caption naming the artifact tier. Must-not-change (green both sides): the #4519 headline case (no environmentId — host config boots without one) stays editable; runtime-created sets stay editable on both shapes; the #4519 pin suites all stay green.

    Sequencing gate: PR #4519 in origin/main first (armed, CI green — poll ~180s, ≤40min; report BLOCKED otherwise). Same file surface — no parallel edit.

    Grading: patch @object-ui/app-shell (.d.ts measured both ways). Never major.

    Surface (mutual exclusion): packages/app-shell/src/views/metadata-admin/PermissionMatrixEditor.tsx + its pin suites + i18n rows if captions change + one changeset. ⛔ NOT: ResourceEditPage (the correct sibling — read-only reference), protocol/server code, console/**, content/docs/releases/. Disjoint from in-flight #4252/#4213/#4513/#4503r.


    Generated by Claude Code


    Generated by Claude Code

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

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions