Skip to content

[finding] an app author's draft baseline is read through the pruned plain read (GET /meta/app/:name?state=draft), and the designers merge it over the whole stored app, so a draft save drops the navigation entries withheld from that author #20290

Closed

Description

@objectstack-fleet

Ruled: 5861438413 · letter A · 2026-09-28T00:56Z

Filing gate: ① a defect with a named landing site. The server half is packages/rest/src/rest-server.ts, the plain read's ?state=draft branch. The client half is objectui's two app editors. Finding class (a), with reach: measured at a public door: an authoring save silently loses data.

Found by the os-dev round on #20156 (PR #20284, the ruled author exemption on the stored-version doors). The server half was confirmed at source by that PR's at-tier contract review (record 5858783654). Filed by the domain:cli execution seat (#6024, session_01UYBdGBzWSrAMzpW8ah3GbP). ⛔ Filed bare: routing, grading and the choice of carrier are triage's. ⛔ Not a claim.

What happens

⚠️ The objectui readings below are the dev's, relayed. They were read from objectui source at the pinned .objectui-sha f8a9d0fb. The seat did not re-read objectui.

  • Producers (objectui):
    • app-shell StudioDesignSurface.tsx builds its app baseline as { ...layered.effective, ...getDraft } (about :1760-:1767) and saves navigation with client.save('app', …, { mode: 'draft' }) (about :1960);
    • ResourceEditPage.tsx, the generic editor (no bespoke app edit page is registered), merges the same way (about :1016-:1056) and saves at about :1500.
  • The door: GET /meta/app/:name?state=draft, the plain read.
    • It sets state: 'draft' on getMetaItem (rest-server.ts about :6988-:7002).
    • It then runs metaItemReadGate(..., { arms: 'all', app: 'gate' }) and serves verdict.document (about :7060-:7068).
    • It attaches no author exemption, so it prunes the navigation entries the caller's requiredPermissions or documentation audience withhold, authors included.
  • Measured by the dev on PR fix(rest): the stored-version doors serve an app whole to whoever may save it, and pruned to everyone else #20284's head 9d955a36: an author holding manage_metadata but not finance.access read ?state=draft as navigation: [nav_leads], while /layers served [nav_leads, nav_finance_ledger].
  • Result: when an app has a pending draft, the draft's PRUNED navigation overrides the author's whole effective navigation in the editor. The next draft save writes the pruned list back, and the withheld entries are deleted.
  • Not caused by PR fix(rest): the stored-version doors serve an app whole to whoever may save it, and pruned to everyone else #20284. On main, the plain read already prunes ?state=draft for every caller. PR fix(rest): the stored-version doors serve an app whole to whoever may save it, and pruned to everyone else #20284 makes /layers whole for authors (ruling B), which is what exposes the gap between the two reads.

The contract it contradicts

Ruling 5856774816 on #20156 (letter B), in its rationale: 「whoever can save it must see it whole, or a save drops entries silently」. The same ruling: 「Read-to-display is pruned per user; read-to-edit is whole for the editor.」 The designers' draft read is a read-to-edit, but it goes through a read-to-display door.

Candidate carriers (for triage, ⛔ not presumed here)

  1. Server: the plain read's ?state=draft branch honours the author exemption, since a draft is a stored version, not a rendered one. Or the draft is served on a stored-version door (/layers with a draft layer).
  2. Client: objectui reads its edit baseline, draft included, through a stored-version door rather than the plain read.

If choosing between them changes what a non-author may read of a draft, that is a question for the decision box, not for a dev. Ruling B covers the three stored-version doors only.

Who acts

Triage decides the landing repository and lane. The server half is domain:cli (packages/rest); the client half is repo:objectui.

Dedupe

MCP issue search, run 2026-09-27:

Dedupe words: app draft state=draft pruned author · designer getDraft merge effective navigation save drops entries · StudioDesignSurface draft baseline withheld nav · ResourceEditPage draft baseline pruned app


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Sep 28, 2026

    @objectstack-fleet
    ContributorAuthor

    Ruling: batch #229 item 2 · letter A · maintainer 「同意」 2026-09-28T00:52Z

    Director seat, summon #30 续 2, session_01AsCNgFBs8HCjwhyHQsFbx3. Batch #229 was presented in the seat chat with the full analysis of each item (the domain:cli seat's analysis 5860841580 re-read, four axes not flipped); the maintainer answered 「同意」 (verbatim, recorded here per the charter).

    Ruled: A — PR #20337 lands as delivered: the plain read's ?state=draft branch serves an app whole to whoever may save it (metaWriteCapabilityVerdict, the predicate ruling B on #20156 fixed), and pruned to everyone else — draft-only, never-published apps included.

    Readings on origin/main ab820016b3 and the PR head 8055937f5e: the Studio design surface and the generic editor read their edit baseline through ?state=draft, which runs metaItemReadGate(..., { app: 'gate' }) and prunes per caller, then merge it over the whole /layers version and save; an author holding manage_metadata but not finance.access read [nav_leads] where /layers served [nav_leads, nav_finance_ledger], and the next draft save deleted the withheld entry (the dev's measurement on PR #20284's head, and PR #20337's real-stack test: after the fix all four entries survive a save). For a published app /diff?from=0&to=N already served the author the draft whole, so nothing new is disclosed; for a draft-only app no door did, so ?state=draft becomes the first — the one cell the seat escalated. The contract review 5860832731 is PASS at head; Check Changeset is red there by design (two pending release notes corrected in place, the #20228 class), which the seat's ACCEPT records.

    Mainstream reading the ruling rests on: Salesforce's Lightning App Builder and ServiceNow's application designer give an editor the complete definition whether or not the app has been published; ruling B's own rationale (「read-to-display is pruned per user; read-to-edit is whole for the editor」) and ADR-0106 D4 (「the exemption is a caller property, not a route property」) say the same. B would fork the rule on publication state and leave the most-edited case, a newly created app, still losing entries; C needs a stored-version door for drafts that does not exist.

    Execution parameters, ruled here so the dispatch needs no second decision:

    1. The domain:cli seat writes its ACCEPT on head 8055937f5e (naming the by-design Check Changeset red and the corrected pending notes), then pr_ready + automerge_enable through the relay; the card closes by Fixes with the PR.
    2. Scope stays the ?state=draft branch and the exemption constant fix(rest): the stored-version doors serve an app whole to whoever may save it, and pruned to everyone else #20284 introduced; non-authors keep the pruned plain read; ADR-0045 §3 (unpublished apps invisible to non-builders) is untouched.
    3. The two further objectui editors that save a read-to-display baseline (useNavigationSync saveApp, AppManagementPage toggles; 5860747208) are the objectui seat's, filed from that note; not this ruling's scope.
    4. The seat's separate card on drafts readable by non-builder members (ADR-0037 risk line) proceeds on its own.
    5. Clause-②: no; changeset as the PR carries it.

    Confidence gap carried: whether manage_metadata always accompanies build permission is unmeasured; only an author with both reaches the draft-only edge.

    State: needs-user-decision → pm:queue in this act (domain:cli · p1 · security kept), for the cli seat to re-claim and land per its own comment; body first line set to this ruling.


    Generated by Claude Code

  2. objectstack-fleet commented on Sep 28, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 3, re-claim to land a ruled PR (batch 3; executing ruling 5861438413, letter A; no new dev: PR #20337 is complete at head 8055937)
    Session: session_01UYBdGBzWSrAMzpW8ah3GbP
    Account: os-litant
    Branch: claude/issue-20290-draft-read-author-exemption
    Worktree: objectstack-issue-20290
    Domain: domain:cli
    Seat: domain:cli#1
    File surface: unchanged from the claim of record 5860036163, as amended in place, and exactly the 8 files of PR #20337 at 8055937f. No new edit is made in this act.
    Container & model: M, mode:subagent, model: default tier (the delivering round). This act is the seat's landing only.
    Clause-②: no
    Thread-read: 5861438413
    Serial constraints cleared: PR #20337's two rest-server.ts hunks (the STORED_VERSION_DOOR_POLICY docblock and the plain read's ?state=draft branch) are disjoint from PR #20345's (#20139) hunks, which the #20139 review measured (5861415643). PR #20345 and PR #20329 are in the merge queue; the later lander merges main. #20320 waits behind #20139

    Taken 2026-09-28T00:56Z. Ruling 5861438413 (batch #229 item 2, letter A, maintainer 「同意」): 「PR #20337 lands as delivered」. Its execution parameter 1 tells this seat to write its ACCEPT on head 8055937f5e, then relay pr_ready + automerge_enable.

    ⚠️ Half-state repaired in this act. The ruling's 「State: needs-user-decision → pm:queue in this act」 and its body first line had not landed at 2026-09-28T00:56Z: the labels still read needs-user-decision. This seat makes the transition directly, needs-user-decision → pm:dispatched + assignee os-litant, because the card is claimed in the same act. The body's first line is left to the director seat, which wrote it.


    Generated by Claude Code

  3. objectstack-fleet commented on Sep 28, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT: PR #20337 at 8055937f, R3 of the domain:cli seat (executing ruling 5861438413, letter A)

    domain:cli execution PM seat #6024 · session session_01UYBdGBzWSrAMzpW8ah3GbP · review of record, written 2026-09-28T00:59Z

    Reviewed against GitHub and origin/main, not against the report.

    Checklist:

    • PR shape: draft, base main. The first line is Fixes #20290, followed by a bare line-initial Clause-②: no.
    • Scope: 8 files, none governed (check-governed-merges --pr 20337: NOT governed; 470 changed lines):
      • rest-server.ts;
      • the gate module (docblock only);
      • the census and a new real-stack test;
      • apps.mdx;
      • the two corrected pending notes and one new changeset.
        All are inside claim 5860036163 as amended. The re-claim of record is 5861471550.
    • Fix: the plain read's ?state=draft branch runs STORED_VERSION_DOOR_POLICY. Whoever may save the app reads the draft whole, through the same metaSaveVerdict predicate PR fix(rest): the stored-version doors serve an app whole to whoever may save it, and pruned to everyone else #20284 introduced, and everyone else reads it pruned per caller. ?preview=draft and the rendered read are unchanged, whole refusals stay refused, and NO_DRAFT is unchanged.
    • Tests:
      • 36 census cells on the draft door;
      • a real-stack file: the author reads the draft whole; the member control equals /layers; the draft-save round trip keeps all four navigation entries;
      • ablations A (exemption off), B (on for everyone) and C (arms: 'all') are each red as predicted.
    • CI on head 8055937f: 32 checks success and 2 rostered expected skips (Console Pin Gate, Packed-tarball smoke (opt-in)). 1 is red: Check Changeset, for the DELIBERATE CORRECTION class, recorded on the PR as 5861486067.
    • Contract review: at-tier, independent, PASS (5860832731).

    Decisions of record:

    • Ruling 5861438413 (batch 🔗 Broken links detected in documentation #229 item 2, letter A, maintainer 「同意」): 「PR fix(rest): the draft read serves an app whole to whoever may save it, and pruned to everyone else #20337 lands as delivered … draft-only, never-published apps included.」 That answers the one cell the seat escalated (5860841580): for a draft-only app, ?state=draft is the first door that serves the draft whole to an author. The seat had paused landing at needs-user-decision because its own order named that case a stop condition.
    • The DELIBERATE CORRECTION of both pending notes is confirmed by the same-head review record, which judges every rewritten sentence (1 + 2). The PR lands under the three conditions for a red-by-design check:
      • the gate's source says it stays red for this class;
      • pr-automation.yml has no merge_group trigger, and Check Changeset is not required;
      • the gate and its reason are written on the PR.
    • patch + Clause-②: no, per ruling item 5.

    Out of scope:

    Next: landing through the relay ops pr_ready + automerge_enable.


    Generated by Claude Code

  4. objectstack-fleet commented on Sep 28, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed: PR #20337 → a36a691bcff40959c5f278be68ab784faed6b597

    domain:cli execution PM seat #6024 · session session_01UYBdGBzWSrAMzpW8ah3GbP · landing record, written 2026-09-28T01:19Z

    • Merged through the merge queue: enqueued 2026-09-28T01:01Z, merged 2026-09-28T01:17Z. The landing is a squash: git rev-list --parents -n 1 gives 2 fields.
    • Content reading on a36a691b:
      • the real-stack pin packages/rest/src/meta-draft-read-author-exemption.test.ts is in the tree;
      • .changeset/20290-draft-read-author-exemption.md is in the tree;
      • the corrected pending note .changeset/20156-app-author-exemption.md carries its new 「?state=draft is the exception」 sentence (1 hit).
    • Negative control: the parent 329ea2e6 has 0 / absent / absent, so the instrument can fire.
    • Card: closed completed by the PR's Fixes, executing ruling 5861438413 (letter A) in full. pm:dispatched and the assignee were cleared in this act.
    • Family status:

    Generated by Claude Code

  5. added 2 commits that reference this issue on Sep 28, 2026
    a36a691
    5049a3c
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guardsbugSomething isn't workingdomain:cliRetired 2026-10-22, alias of domain:server (cli surface); in-flight cards keep it until they landpriority:p1High: required for production / M2security

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions