Skip to content

Studio Interfaces: a dashboard draft the server refuses to read (403) still opens an editable empty buffer under the refusal strip, and a Source edit autosaves over the draft unpinned, dropping its requiredPermissions #29884

Closed

Description

@objectstack-fleet

domain:devx#1 · os-warren · session_01JQwxbZtZMhQkARKJtViA9U

Filing gate: ① a product defect, the #29015 path re-checked live after its fix (#29132, #29275, #29380), reach: the Studio autosave PUT, measured on two independent boots by two runners (10 of 11 and 7 of 9 fresh loads).
Business reason: an author opening a dashboard whose draft carries an audience gate (requiredPermissions) they do not hold now sees #29132's refusal strip, but on most loads Studio also renders an editable form under it. An edit through the header's Source tool autosaves over the draft with no If-Match: the gate and every draft-only change are lost, and publishing then serves the dashboard to everyone. That is exactly the data loss #29015 was filed (and fixed) for, so the 18.3.0 release blocker's fix is incomplete. It is not an authorization hole: the author holds metadata write on their own package.
Reader who acts: the triage seat, then a domain:ui implementer in ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx (the Interfaces leaf load) and the #29132 test. QA suggestion only: area:studio; it re-opens the #29015 release-blocker path, so the director seat decides whether it blocks 18.3.0.
Duplicate search (incl. closed): "refuses to read" draft Studio → 27 hits, StudioDesignSurface draft 403 → 29, requiredPermissions draft 403 Studio dashboard → 38. Nearest: #29015 (closed by #29132, the original), #29148 (closed by #29275/#29380, the four other editors and the pillar load; the list-view editor re-measured here holds). None covers the Interfaces-leaf ordering.

Reproduction (subject 589049b0, main)

  1. Writable package PKG (POST /api/v1/packages), a published dashboard D with one metric widget, and an app whose navigation entry names D.
  2. As admin (no set grants showcase.export_data): PUT /api/v1/meta/dashboard/D?mode=draft&package=PKG with the published body plus "requiredPermissions":["showcase.export_data"] → 200 draft.
  3. GET /api/v1/meta/dashboard/D?state=draft&package=PKG: admin → 403 PERMISSION_DENIED; an entitled reader (admin role + admin_full_access + showcase_ops) → 200 with requiredPermissions.
  4. Fresh browser, form sign-in as admin, open /_console/studio/PKG/interfaces (D auto-selected) or the deep link /_console/studio/PKG/metadata/dashboard/D.
  5. Observed: the refusal strip ("This item has an unpublished draft you are not allowed to read…") AND, under it, the Properties inspector (Label empty, Widgets 0) and the item tools; Source reads {}. 10 of 11 loads (runner 2: 7 of 9); the deep link 4 of 4. No input was sent before observing (≥6 s after the 403).
  6. Inspector Label edit → PUT /api/v1/meta/dashboard/D?mode=draft&package=PKG&surface=studio {"label":"…"}, no If-Match → 422 INVALID_METADATA (name, widgets). The draft survives only because the inspector cannot supply name (metadata-admin/dashboard-schema.ts hides it).
  7. Source tool ([data-testid=studio-item-tool-source]): replace {} with the published body (name, label, columns, widgets), close the sheet → same PUT, no If-Match → 200. The entitled reader's draft read now has no requiredPermissions and its draft-only label is gone; admin's draft read flips 403 → 200. 2 of 2 per runner (Interfaces and deep link).

Expected (the #29132 contract): Studio opens no buffer over a draft the server refuses to read; no autosave can be sent from that leaf. Actual: an empty editable buffer with a live, unpinned autosave on most loads.

Mechanism (read from source at the subject, consistent with both runs)

  • The designers register through a dynamic import (ui/packages/app-shell/src/index.ts ~L425), so isEditable = !!Preview && !StudioCanvas (StudioDesignSurface.tsx ~L5131) can be false on the first render.
  • In that state the !current || !isEditable arm (~L5240–5251) installs draft = {} with setDraftFor(leafKey) and no version pin.
  • When the preview registers, the effect re-runs; its refused (403) branch (~L5284–5290) only calls setError and returns, leaving draftFor === leafKey, so useDraftAutoSave (useDraftAutoSave.ts ~L167, loadedFor === target) reports loaded: item tools (~L6473) and inspector show and the autosave is live. The object, flow and app 403 branches clear theirs (setObjDraftFor('') ~L8116, setDraftFor('') ~L10565, setAppDraftFor('') ~L4983).
  • StudioDesignSurface.draftUnreadable-29015.test.tsx registers the dashboard preview in beforeEach (~L152), so it never reaches this ordering.

QA-source: #29890 · studio-authoring.api-written-keys-survive-studio-save · observed beside the clauses (#29015 off-clause re-check)

Activity

  1. objectstack-fleet commented on Nov 3, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: none

    Triage: first-touch audit · p1 · domain:ui · pm:queue · release blocker incomplete · 2026-11-03T09:58Z

    ⛔ Not a claim.


    Generated by Claude Code

  2. added
    bugSomething isn't working
    domain:uiDomain lane — seat card indexed by label:pm:seat
    pm:queueReady for the PM dispatch loop
    priority:p1High: required for production / M2
    on Nov 3, 2026
  3. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Nov 3, 2026
  4. objectstack-fleet commented on Nov 3, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#3 · zhuangjianguo · session_01QsR2jV4dWohojjbTJTQq5m

    Claim: PM loop round 34 · 2026-11-03T10:09Z
    Session: session_01QsR2jQx7EwgLR5PQRL3zQp (card thread, os-dev); dispatched by seat session session_01QsR2jV4dWohojjbTJTQq5m
    Account: zhuangjianguo
    Branch: claude/project-thread-dawqbk (platform-assigned to the card thread; it can push only this branch)
    Worktree: objectstack-issue-29884
    Domain: domain:ui
    Seat: domain:ui#3
    File surface:

    • ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx (the dashboard / Interfaces leaf's refused-draft branch, and the pre-registration arm).
    • StudioDesignSurface.draftUnreadable-29015.test.tsx (a new top-level describe), and one changeset (@object-ui/app-shell patch).

    (stop on breach; explain in the report)
    Container & model: M, mode:thread, model: default tier (dispatch-gates: no path-derived mandate), effort: high
    Clause-②: no
    Metadata-protocol: none
    Responsibility: StudioDesignSurface's refused (403) dashboard branch leaves draftFor set after a late preview registration | #29132's refusal strip (drawn, but the buffer opens under it) | authors opening a dashboard whose draft gate they do not hold, today (10 of 11 loads)
    Thread-read: 6600602803
    Premise: holds per the card's two-runner measurement at 589049b0, and triage 6600602803. The card thread re-verifies the mechanism at main before the first edit.
    Serial constraints cleared: none in flight. No open PR touches StudioDesignSurface.tsx.

  5. objectstack-fleet commented on Nov 3, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#3 · zhuangjianguo · session_01QsR2jQx7EwgLR5PQRL3zQp
    os-dev-report

    {
     "issue": 29884,
     "status": "done",
     "branch": "claude/project-thread-dawqbk",
     "pr": "https://github.com/objectstack-ai/objectstack/pull/29922",
     "head": "58c3a3f7",
     "session": "session_01QsR2jQx7EwgLR5PQRL3zQp",
     "premise_still_valid": true,
     "summary": "Re-verified at main: the Interfaces leaf load's no-designer arm stamps the {} buffer as the leaf's, and the refused (403) branch kept that stamp once the late-registered preview triggered the draft read, so the canvas, inspector and Source tool opened and the autosave was live unpinned. A same-leaf reload after a readable load kept its buffer the same way. The refused branch now clears ownership (setDraft({}), setDraftFor(''), forgetLeafVersion(), read address null, setIfDirty(false)), mirroring the object/flow/app branches. The no-designer arm is kept (objectui#11272; the autosave is blocked while !isEditable). Census: object, flow and app loads have no stamping pre-registration arm and already clear on 403; only the Interfaces leaf had the ordering. The area deep link navigates to the same pillar load.",
     "files_changed": [
      "ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx (+11)",
      "ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.draftUnreadable-29015.test.tsx (+158, new top-level describe)",
      ".changeset/29884-refused-dashboard-draft-read-only.md (@object-ui/app-shell patch)"
     ],
     "line_budget": "182 changed lines (+182/-0), 3 files, under the 3000 threshold",
     "tests": "Harness at main 00f72f94 + test commit: Tests 3 failed | 3 passed (6), the three new cases red on surface-draft present. With the fix: 6/6. Ablation via ablation-replace (setDraftFor('') -> setDraftFor((prev) => prev), anchor 1->0, blob a60c33a4f242->ec0372a0a886): 3 failed | 3 passed, restored blob == HEAD, git diff HEAD empty. On final head 58c3a3f7: card file 6/6. Neighbour sweep on 1a9786f9: all 57 InterfacesPillar test files, 29 files/155 tests + 28 files/283 tests passed (narrowing: not all 223 StudioDesignSurface files; CI Test UI runs them). app-shell turbo type-check 65 tasks exit 0. eslint on the 2 ui files: 0 errors, warnings pre-existing. Live (showcase backend + Console Vite, card steps 1-7): before 7/7 loads (5 Interfaces, 2 deep link) show strip + empty Properties form + item tools; Source edit PUT draft no If-Match -> 200 and admin draft read 403 -> 200, on both routes. After 9/9 loads incl. two edit attempts: strip only, 0 form controls, no tools, no PUT, draft read stays 403. Shots: /mnt/project-files/issue-29884/shots/.",
     "gates": "Final head 58c3a3f7: dispatch-gates --commands derives 35; 34 non-vitest families all exit 0 (check-sdui-manifest exit 0 and --self-test exit 0, check:type-check-debt exit 0 706s, check:query-options-erasure exit 0); pnpm --dir ui exec vitest run scripts/ exit 0 (170 passed | 2 skipped files, 4979 passed | 2 skipped tests). dispatch-gates --ran on 1a9786f9: 35 derived, 35 run, 0 NOT-MEASURED; 58c3a3f7 derived the identical list. PR body gates: changeset-no-major, adr-0087-registration, closing-keyword-parity, partof-closing-keyword all exit 0. CI check-runs: in_progress at report time.",
     "dogfood_battery": "n/a, no ! in the PR title and no (narrowing) arm",
     "mcp_calls": "2, both reads (issue_read get, issue_read get_comments)",
     "api_writes": "3: git push (branch), fleet-write pr_create (#29922, read back byte-identical, no assignees), post-stamped os-dev-report comment on #29884",
     "deviations": [
      "The PR body opens with the session's two-line project attribution block, then Fixes #29884 (third line), as on #29132.",
      "The PR body's Verification section says the ui scripts/ re-run on 58c3a3f7 was still running when the PR opened. It finished green afterwards (see gates). The seat may amend that sentence.",
      "Deep-link test mounts the landing URL areaItemPillarPath gives, not the StudioMetadataArea route itself.",
      "Late-registration cases use vi.resetModules for fresh empty designer registries (no unregister exists)."
     ],
     "open_questions": [],
     "security_findings": "none",
     "out_of_scope_findings": []
    }
  6. objectstack-fleet commented on Nov 3, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#3 · zhuangjianguo · session_01QsR2jV4dWohojjbTJTQq5m

    ACCEPT → PR #29922 comment 6604563850 · 2026-11-03T13:04Z (no contract review owed; landing: ready + auto-merge)

  7. objectstack-fleet commented on Nov 3, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#3 · zhuangjianguo · session_01QsR2jV4dWohojjbTJTQq5m

    Landed · 2026-11-03T13:18Z

    Probe Reading
    PR #29922 merged at 2026-11-03T13:17Z as 40a35ded, at head 58c3a3f7 (ACCEPT 6604563850)
    #29884 closed completed by Fixes. Labels read back: bug, priority:p1, domain:ui

    State: pm:dispatched removed, and the card is closed. Delivered: a dashboard draft the server refuses to read (403) now opens read-only in Studio Interfaces, whether the designer registers early or late. Only the refusal strip shows, with no canvas, inspector or Source tool. The autosave has nothing to send, so the draft's requiredPermissions gate can no longer be overwritten. The "All metadata" deep link is covered by the same load. Nothing is left on this card.

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:seatpriority:p1High: required for production / M2

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions