Skip to content

Studio editors still read a refused (403) draft as "no draft" and can save the published body over it: Interfaces app nav, ResourceEditPage, list view inspector, bundle item, permission matrix, hooks #29148

Closed

Description

@objectstack-fleet

domain:ui#1 · zhuangjianguo · session_01QsR2jUJ19MstFvFhjgiYTu

Filing gate: ② out-of-scope finding, class a, from #29015 (PR #29132; report /mnt/project-files/issue-29015/os-dev-report.json) · filed by domain:ui#1 · ⛔ Not a claim. No grade: triage sets it.

Business reason. A pending draft can carry an audience gate (requiredPermissions) that its author does not hold, for example one written over the API or MCP. The server refuses to read that draft (403), by design. #29015 fixes the Interfaces pillar's dashboard leaves, but every other Studio editor still takes the refusal for "no draft". It opens the published body instead, and its next save sends that body as the draft. That silently drops the gate and every other unpublished change, and publishing then serves the item to everyone.

Measured (code reading at origin/main; not run). After PR #29132, readDraftForSave (ui/packages/app-shell/src/views/metadata-admin/DraftConflictDialog.tsx) returns refused for a 403. Only the Interfaces leaf load reads it. These callers still treat a refusal as absence:

  • the Interfaces app nav load in StudioDesignSurface.tsx (the app arm refuses an app draft carrying requiredPermissions, the nav buffer seeds from the published layer, and the nav save goes unpinned);
  • ResourceEditPage;
  • ObjectListViewInspector (list views carry the same audience gate);
  • StudioBundleItemEditor;
  • PermissionMatrixEditor;
  • ObjectHooksPanel.

Wanted.

  1. Each caller above checks refused. On a refusal it installs no buffer and shows the engine.studio.refusal.draftUnreadable strip that Studio: a dashboard draft whose requiredPermissions the author does not hold reads 403, Studio treats that as "no draft", and the next autosave overwrites the draft without the key #29015 adds, with the server's sentence behind Details.
  2. With no buffer, the autosave has nothing to send.
  3. A leaf with no draft, or a readable draft, behaves as before.

Tests. Per caller: a refused draft read installs no buffer and sends no save, while a missing draft still opens the published body. One ablation per caller.

Reader. domain:ui (area:studio). Size M; the callers are disjoint, so triage may split it. Serialize after PR #29132 (#29015).

Dedupe. Searched readDraftForSave, refused 403 draft overwrite. #29113 is the Interfaces pillar's refused-save guard, a different door. No card for these callers.

Links back: #29015, PR #29132.

Activity

  1. added
    bugSomething isn't working
    pm:queueReady for the PM dispatch loop
    domain:uiDomain lane — seat card indexed by label:pm:seat
    on Nov 2, 2026
  2. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: none

    Triage: p2 (priority added) · 2026-11-02T08:34Z

    ⛔ Not a claim.


    Generated by Claude Code

  3. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Nov 2, 2026
  4. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#5 · zhuangjianguo · session_01QsR2jQjvWkZ7is6GXbC6DB

    Claim: PM loop round 2 · 2026-11-02T09:59Z
    Session: session_01QsR2jTCMoEXh1SQzWaNp2v (card thread, os-dev); dispatched by seat session session_01QsR2jQjvWkZ7is6GXbC6DB
    Account: zhuangjianguo
    Branch: claude/project-thread-jr58yz (platform-assigned to the card thread; it can push only this branch)
    Worktree: objectstack-issue-29148
    Domain: domain:ui
    Seat: domain:ui#5
    File surface: the readDraftForSave callers in ui/packages/app-shell/src/views/: studio-design/StudioDesignSurface.tsx (Interfaces app nav load only), metadata-admin/ResourceEditPage.tsx, studio-design/ObjectListViewInspector.tsx, studio-design/StudioBundleItemEditor.tsx, metadata-admin/PermissionMatrixEditor.tsx, studio-design/ObjectHooksPanel.tsx; their new tests; a changeset. metadata-admin/DraftConflictDialog.tsx only for a shared helper, with the reason in the report (stop on breach; a split beyond one reviewable PR is proposed to the seat first)
    Container & model: M, mode:thread, model: default tier (dispatch-gates --tier: no path-derived mandate; no Fable per the maintainer's standing rule), effort: high
    Clause-②: no
    Metadata-protocol: none
    Responsibility: six Studio editors take readDraftForSave's refused (403) read for "no draft" and seed the published body | the platform covers the read (the server refuses it, by design) but not the write: their next save sends the published body as the draft | every author editing an item whose pending draft carries requiredPermissions they lack (API- or MCP-written); publishing then drops the audience gate
    Thread-read: 6572677492
    Premise: holds (at origin/main 2f29c84d: readDraftForSave returns refused since PR #29132 / 3d2e8fd, and only the Interfaces leaf load reads it; callers at ResourceEditPage.tsx:1344,2250, ObjectListViewInspector.tsx:550, PermissionMatrixEditor.tsx:933,1492,1540, ObjectHooksPanel.tsx:238, StudioBundleItemEditor.tsx, and the nav load in StudioDesignSurface.tsx). Triage 6572677492 graded p2. Handed from seat 3 to seat 5 in the project, 09:55Z.
    Serial constraints cleared: StudioDesignSurface.tsx is also in open PR #29224 (#29139, seat 3), seat 5's #29113 and seat 2's #28984. This PR opens after #29224 lands and merges main before it opens. ResourceEditPage.tsx is in open PR #29203 (#29079, seat 1, landing queued): no edit there until it lands and main is merged.

  5. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#5 · zhuangjianguo · session_01QsR2jQjvWkZ7is6GXbC6DB

    Claim amended · 2026-11-02T10:01Z · on the os-dev's measurement (/mnt/project-files/issue-29148/split-proposal.md), before code. This amends claim 6574269114: the split and the file surface. Branch, session, Clause-② and Metadata-protocol are unchanged.

    Clause-②: no
    Metadata-protocol: none

  6. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#5 · zhuangjianguo · session_01QsR2jTCMoEXh1SQzWaNp2v
    os-dev-report

    {
      "issue": 29148,
      "status": "done",
      "branch": "claude/project-thread-jr58yz",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/29275",
      "pr_part": "PR 1 of 2 (Part of #29148), per claim amendment 6574316242",
      "head": "fc165c69",
      "session": "session_01QsR2jTCMoEXh1SQzWaNp2v",
      "premise_still_valid": true,
      "summary": "ObjectListViewInspector, StudioBundleItemEditor, PermissionMatrixEditor (package door load at :933) and ObjectHooksPanel now act on readDraftForSave's refused (403) answer: no buffer is installed, the existing engine.studio.refusal.draftUnreadable key shows with formatMetadataError's text behind Details in each editor's own refusal host, and the autosave has nothing to send. ObjectHooksPanel holds only the refused hook (it stays listed by its published row and pins nothing); a hook known only by its refused draft stays unlisted, as before. PermissionMatrixEditor :1492 and :1540 unchanged per the amendment. No new i18n key, no DraftConflictDialog edit.",
      "files_changed": [
        ".changeset/29148-refused-draft-four-editors.md",
        "ui/packages/app-shell/src/views/metadata-admin/PermissionMatrixEditor.tsx",
        "ui/packages/app-shell/src/views/metadata-admin/PermissionMatrixEditor.draftUnreadable-29148.test.tsx",
        "ui/packages/app-shell/src/views/studio-design/ObjectHooksPanel.tsx",
        "ui/packages/app-shell/src/views/studio-design/ObjectHooksPanel.draftUnreadable-29148.test.tsx",
        "ui/packages/app-shell/src/views/studio-design/ObjectListViewInspector.tsx",
        "ui/packages/app-shell/src/views/studio-design/ObjectListViewInspector.draftUnreadable-29148.test.tsx",
        "ui/packages/app-shell/src/views/studio-design/StudioBundleItemEditor.tsx",
        "ui/packages/app-shell/src/views/studio-design/StudioBundleItemEditor.draftUnreadable-29148.test.tsx"
      ],
      "line_budget": "843 changed lines (+826/-17): 162 in the four editors, 669 in four new test files, 12 changeset. Seat's PR 1 estimate was ~600-700; the overage is tests.",
      "tests": "4 new files, 10 tests: green at b944048d and again at merged head fc165c69 (Test Files 4 passed (4), Tests 10 passed (10)). Ablation per editor via scripts/ablation-replace.mjs (guard to false and ...): bundle 1 failed/1 passed, listview 1 failed/1 passed, perm 2 failed/1 passed, hooks 1 failed/2 passed; every refused case red, every control green; each restore blob == HEAD, git diff HEAD empty. Nearby sweep: the 80 test files naming the four editors, 2 locked batches at maxWorkers 2: 40/40 files (279 tests) and 40/40 (236 tests). ui vitest run scripts/: 169 passed, 2 skipped files; 4900 passed, 2 skipped tests. app-shell type-check (tsc --noEmit and tsconfig.test.json): 65/65 turbo tasks exit 0 at b944048d and at fc165c69.",
      "gates": "dispatch-gates --commands at b944048d: 34 families, all exit 0; --ran reconciliation: 34 derived, 34 run, 0 NOT-MEASURED, 0 UNRUN. check-sdui-manifest exit 0 (and --self-test exit 0). PR body gates on the final body: check-changeset-no-major exit 0, check-adr-0087-registration exit 0, check-closing-keyword-parity exit 0, check-partof-closing-keyword exit 0. CI check-runs: in_progress at report time (re-runs are the seat's).",
      "dogfood_battery": "n/a: no ! in the PR title and no (narrowing) arm",
      "deviations": [
        "Line count 843 is over the ~600 single-PR guide; tests are 669 of it.",
        "Main 47ee57fe merged at fc165c69 before pr_create; incoming commits touch none of the four editors. Re-ran on fc165c69 only app-shell type-check and the 4 new test files; sweep, ui scripts and derived gates are b944048d readings."
      ],
      "mcp_calls": "5 reads: issue_read get x1, issue_read get_comments x3 on #29148; get_session x1. No MCP write.",
      "api_writes": "2: fleet-write relay pr_create (run 41432081875, read-back identical, 8106 bytes); this os-dev-report comment via post-stamped. git push is not a REST write.",
      "open_questions": [],
      "out_of_scope_findings": [
        "carrier: PR 2 of this card (seat amendment 6574316242) · StudioDesignSurface.tsx object load, object-extension load and flow load seed the published body on a 403; already added to PR 2's surface, not filed"
      ],
      "next": "PR 2 on this branch after PR 1 lands and #29224 lands (#29203 already landed at 47ee57fe): StudioDesignSurface app nav, object, object-extension and flow loads, ResourceEditPage :1344 and :2250, and optionally StudioRefusalStrip moved to a shared module."
    }
  7. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#5 · zhuangjianguo · session_01QsR2jTCMoEXh1SQzWaNp2v
    os-dev-report

    {
      "issue": 29148,
      "status": "done",
      "branch": "claude/project-thread-jr58yz",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/29380",
      "pr_part": "PR 2 of 2 (Fixes #29148), per claim amendment 6574316242; PR 1 #29275 landed at 192c15cf",
      "head": "816939aa",
      "session": "session_01QsR2jTCMoEXh1SQzWaNp2v",
      "premise_still_valid": true,
      "summary": "StudioDesignSurface's Interfaces app nav load, Data object load, Data object-extension load (base pair) and Automations flow load, and ResourceEditPage's load (:1344) and post-publish re-read (:2250) now act on readDraftForSave's refused (403) answer: no buffer and no pin are installed, the existing engine.studio.refusal.draftUnreadable key shows with formatMetadataError's text behind Details, and nothing is sent. ResourceEditPage holds a draftRefused state that makes canWrite false (every write withheld) and renders StudioRefusalStrip in its shell in place of the editor. StudioRefusalStrip moved unchanged into its own module (StudioDesignSurface re-exports it). Verdict-only re-reads unchanged. No new i18n key.",
      "files_changed": [
        ".changeset/29148-refused-draft-studio-pillars.md",
        "ui/packages/app-shell/src/views/metadata-admin/ResourceEditPage.tsx",
        "ui/packages/app-shell/src/views/metadata-admin/ResourceEditPage.draftUnreadable-29148.test.tsx",
        "ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx",
        "ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.draftUnreadable-29148.test.tsx",
        "ui/packages/app-shell/src/views/studio-design/DataPillar.draftUnreadable-29148.test.tsx",
        "ui/packages/app-shell/src/views/studio-design/StudioRefusalStrip.tsx"
      ],
      "line_budget": "972 changed lines (+907/-65): 239 source (SDS 118, mostly the strip move; StudioRefusalStrip.tsx 72 moved; ResourceEditPage 49), 721 in three new test files, 12 changeset.",
      "tests": "3 new files, 11 tests (6 refused cases, 5 controls): green at bb4c4af9, ee8cf851 and 816939aa. Ablation per site via scripts/ablation-replace.mjs (guard to false and ...): app-nav, object, flow 1 failed/5 passed each; object-extension 1 failed/1 passed; rep-load and rep-publish 1 failed/2 passed each; each site's refused case red, all else green; every restore blob == HEAD. Nearby sweep: the 331 app-shell test files naming StudioDesignSurface, ResourceEditPage, StudioRefusalStrip or a pillar, 9 locked batches at bb4c4af9: 331/331 files, 2533 tests. At ee8cf851: app-shell type-check 65/65, 6 related files 20 tests, ui vitest run scripts/ 169 passed 2 skipped files, 4903 passed 2 skipped tests. eslint clean on new files.",
      "gates": "dispatch-gates --commands at ee8cf851: 34 families, all exit 0; --ran: 34 derived, 34 run, 0 NOT-MEASURED, 0 UNRUN. check-sdui-manifest exit 0 (and --self-test). PR body gates on the final body: check-changeset-no-major, check-adr-0087-registration, check-closing-keyword-parity, check-partof-closing-keyword all exit 0. CI: not yet reported at report time (re-runs are the seat's).",
      "dogfood_battery": "n/a: no ! in the PR title and no (narrowing) arm",
      "deviations": [
        "Line count 972 is over the ~600 single-PR guide; tests are 721 of it.",
        "Final merge 816939aa brought 11 main commits touching none of the changed files; only the 3 new test files were re-run on it. Sweep is a bb4c4af9 reading; type-check, gates and ui scripts are ee8cf851 readings.",
        "The four PR 1 editors keep their own refusal hosts; only ResourceEditPage was moved onto the shared StudioRefusalStrip."
      ],
      "mcp_calls": "1 read: pull_request_read get on #29380. No MCP write.",
      "api_writes": "2: fleet-write relay pr_create (run 41453377449, read-back identical, 9292 bytes); this os-dev-report comment via post-stamped. git push is not a REST write.",
      "open_questions": [],
      "out_of_scope_findings": [],
      "serial_note": "Seat note on #29311 (claude/project-thread-oritte): this PR's StudioDesignSurface hunks are the strip move (~L700), the app nav load effect (~L4831), the object/extension loads (~L7897, ~L7991) and the flow load (~L10294); none in the InterfacesPillar leaf load or the Preview element.",
      "next": "Seat lands #29380; card closes on merge."
    }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingdomain:uiDomain lane — seat card indexed by label:pm:seatpm:dispatchedDispatched to a dev agent by /pm-dispatchpriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions