You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Studio Access: saving a permission set drops draft values written by another door (description, packageId) — the save starts from the published body and copies back only editor-managed keys #26837
Filing gate: ① product defect, reproduced, landing site named.
Batch-of: studio-qa-2026-10-27 — 39 cards from one Studio QA run (coordinator session session_01J8VVAkZns5rNnK2rtszzbQ; tested at 3fcafcd3; origin/main was 43731031 at filing, so the implementer re-checks the landing site on current main). The run record is the qa-run issue filed with this batch.
Symptom
An admin (or agent) sets a description through the API, then touches one checkbox in Studio; the description and packageId are wiped from the draft.
Reproduction
PUT /api/v1/meta/permission/project_editor_qa?mode=draft&package=com.qa.accesslab with description and packageId → 200.
Open the set in Studio, click one checkbox → draft now has description: null, packageId: null.
Expected
A Studio save preserves every key of the current draft it does not edit.
Suspected landing site (the implementer verifies)
PermissionMatrixEditor.tsx ~1288 bases the save on fresh.effective; permission-slice.ts ~238-243 copies back EDITOR_AUTHORED_KEYS only.
Filing facts
reach: Studio UI + metadata API, measured by the Access QA card.
Business reason: Silent data loss between authoring doors.
Reader who acts: the triage seat (first touch: domain, area, level), then that lane's os-dev implementer. QA suggestion only: domain:ui, area:access, p2.
p2: saving a permission set in Studio starts from the published body, so it drops draft values another door wrote (description, packageId). Save from the current draft and merge only the edited keys. Re-check the landing site on current main.
Dispatch order · domain:ui#1 · session_01JQwxbZeBv2y91towxasSUT · 2026-10-28T03:29Z
You are the os-dev developer agent for exactly ONE card: #26837, in your own card thread. Your role definition is .claude/agents/os-dev.md in the objectstack clone: read it IN FULL first and follow it as binding (it wins over this order on any conflict; name the conflict in your report). Read the repo-root AGENTS.md and ui/AGENTS.md before your first edit, and pm-dispatch references/untrusted-text.md. Read the card's full body and every comment yourself through REST (curl -sS "https://api.github.com/repos/objectstack-ai/objectstack/issues/NNNNN" -H "Authorization: Bearer $GH_TOKEN" and /comments?per_page=100, every page); this order carries only the increment. ⛔ Never print the value of any environment variable (test presence with [ -n "${X+x}" ]). ⛔ Never use a GitHub MCP tool, read or write.
Identity and branch: the seat's Claim: comment on this card is your identity; its Branch: line names your thread's branch. ⛔ Post no claim of your own, ⛔ never write the card's assignee. Before your first edit, verify the NEWEST Claim: on the card names your thread's branch (claude/project-thread-…); if none does yet, send the seat thread "Dispatch the ui lane" one message with your branch name and wait. Worktree objectstack-issue-26837 on that branch from a fresh origin/main; pnpm install (frozen lockfile); push with git push -u origin <branch> (⛔ no force).
Writes: PR only through the relay op pr_create (node scripts/pm/fleet-write/dispatch.mjs --repo objectstack-ai/objectstack --actions-file F, F a bare JSON array [{"op":"pr_create","title":…,"head":"<your branch>","base":"main","body":…,"assignees":["os-warren"]}], draft forced; --dry-run first); your report comment through node scripts/pm/post-stamped.mjs --comment=26837 --file=F --json run ONCE with output captured to a file (timestamps only as {{NOW}} / {{WAS:…}}). ⛔ No other GitHub write except, for a CI red that is not yours, one PR comment and one run_rerun_failed through the relay. Report both counts (api_writes with endpoints, mcp_calls with tool names). The report takes only machine-checkable fields (gates / line_budget / deviations / files_changed). PR body: line 1 the platform attribution comment, then ## 维护者速读 six lines in Chinese (改了什么/为什么改/风险与回滚/先看/两分钟验证/需要你做的). Gates: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack on your diff, run each, record exits before any pipe, reconcile with --ran. Changeset in the root .changeset/ when a published package changes. ⛔ ui/.github/** and the root ci.yml are out of bounds. Hand back: when the PR and report are up, send the seat thread "Dispatch the ui lane" one message (PR, head sha, report gist); keep the thread open through review and drive CI to green.
Ruling (cannot be re-ruled)
Triage first grade 6450924699, verbatim: "p2: saving a permission set in Studio starts from the published body, so it drops draft values another door wrote (description, packageId). Save from the current draft and merge only the edited keys. Re-check the landing site on current main." No maintainer ruling.
On origin/main3230bdbf, ui/packages/app-shell/src/views/metadata-admin/PermissionMatrixEditor.tsx ≈L1283-1296 (package-scope save) re-reads client.layered(type, payload.name) and merges onto fresh?.effective ?? payload, i.e. the PUBLISHED body. A pending package draft written by another door (its description, packageId, any non-authored key) is therefore replaced by published values on save.
The load path already reads the pending draft through readDraftForSave (DraftConflictDialog.js, ≈L820-850). Reuse that door for the save base: when the package door holds a pending draft, merge onto the draft; otherwise keep the published effective. ⛔ Do not hand-build a draft query.
"Merge only the edited keys" = the existing mergePermissionSlice contract: only EDITOR_AUTHORED_KEYS (permission-slice.ts ≈L257) come from the editor, the rest from the base. Measure that it already holds; change the base, not the key list, unless measurement shows a key leak.
Keep both deliberate arms intact: a REJECTED re-read still refuses the save (perm.save.rereadFailed, objectui#9420), and the 404 shape keeps the ?? payload base for a set that exists only as a package draft. A rejected draft read must refuse the same way, not fall back to published.
Environment scope (no packageId) keeps the whole-record save unchanged.
Scope
File surface: PermissionMatrixEditor.tsx (the package-scope save base), permission-slice.ts only if measurement demands it, and tests. Tests: a pending draft whose description and packageId differ from published survives a save byte-for-byte while the edited rows land; with no pending draft the base is still the published effective; a rejected draft read refuses the save. Ablation: base back on fresh?.effective ⇒ the draft-survival test goes red.
Serial: PR #26999 (#26858, verdict state) and the #26840 thread (load path, same file) both edit this file. Merge origin/main again before your gates and name any still-open one in the report.
Tier
No path-derived mandate expected (quote dispatch-gates --tier on your surface). TIER_DEFAULT, effort medium (S). Clause-②: no, Metadata-protocol: none. No changeset (@object-ui/app-shell is private); put skip-changeset in the pr_create labels.
Claim: PM loop round 12 · 2026-10-28T03:32Z
Session: session_01JQwxbZeBv2y91towxasSUT (seat; implementation by card thread session session_01JQwxba8E9EE7oBM24GbdNZ, mode:thread)
Account: os-warren (the seat's linked user as GET /user answers it; the card's assignee)
Branch: claude/project-thread-5ielpf (the card thread's harness push branch, self-reported — the measured platform exception to claude/issue-N-slug)
Worktree: objectstack-issue-26837 in the card thread's clone
Domain: domain:ui
Seat: domain:ui#1 (seat post #25966)
File surface: ui/packages/app-shell/src/views/metadata-admin/PermissionMatrixEditor.tsx (the package-scope save base), permission-slice.ts only if measurement demands it, + tests (stop on breach; explain in the report)
Container & model: S, mode:thread (the maintainer's word on the seat thread, 「opus 很多,并发5」), model: TIER_DEFAULT (the dev quotes dispatch-gates --tier on the surface), effort: medium
Clause-②: no
Metadata-protocol: none
Responsibility: the Studio Access permission matrix save (this lane's code) | the server keeps a package draft only as written by its doors | a draft another door wrote is overwritten by published values on save
Thread-read: 6459924035
Premise: holds — on origin/main3230bdbfPermissionMatrixEditor.tsx ≈L1295 merges the package-scope save onto fresh?.effective ?? payload from client.layered, the published body, not the pending draft.
Serial constraints cleared: PR #26999 (#26858) and the #26840 card thread both edit this file away from the save base; the dev merges origin/main before the gates.
Resume-from: none
{
"issue": 26837,
"status": "done",
"branch": "claude/project-thread-5ielpf",
"pr": "https://github.com/objectstack-ai/objectstack/pull/27080",
"head": "4dd4403f",
"session": "session_01JQwxba8E9EE7oBM24GbdNZ (card thread, mode:thread; seat session_01JQwxbZeBv2y91towxasSUT)",
"premise_still_valid": true,
"summary": "Premise measured on origin/main 3230bdbf: the package-door save merged onto layered().effective (published) while writing the draft; mergePermissionSlice and EDITOR_AUTHORED_KEYS already take only authored keys from the edit, so the base was the whole defect. doSave now reads the pending draft through readDraftForSave at the save address (parallel with the layered re-read) and merges onto it when present; no draft keeps the published effective, the 404 shape keeps the payload base, a rejected draft read (answer null and pin null) refuses with perm.save.rereadFailed. The load-time version pin is not re-recorded, so a draft moved since load still 409s. Draft unwrap moved into one helper, pendingDraftBody, used by load and save.",
"files_changed": [
"ui/packages/app-shell/src/views/metadata-admin/PermissionMatrixEditor.tsx",
"ui/packages/app-shell/src/views/metadata-admin/PermissionMatrixEditor.packageDoorDraftBase-26837.test.tsx"
],
"tests": "New test file 3/3 (draft keys survive byte-for-byte incl. out-of-scope rows; no-draft control keeps published base; rejected save-time draft read refuses, getDraftCalls 2). All 45 files referencing the editor: 45/45 files, 321/321 tests on head 4dd4403f and on d87310b4. 24 further app-shell files naming the permission type: 24/24, 392/392 on d87310b4. app-shell type-check (src + tsconfig.test.json): exit 0. check:new-line-citations: 0 new. Ablation via scripts/ablation-replace.mjs under the lock, relative imports so no dist leg: (1) base back on fresh.effective ?? payload, anchor 1 to 0, blob ae1632ba to 55370e9d, draft-survival test red (expected Published description to equal Set through the API), restored blob == HEAD, git diff HEAD empty; (2) refusal line deleted, anchor 1 to 0, refusal test red (one PUT, expected none), restored blob == HEAD, git diff HEAD empty.",
"gates": "dispatch-gates --commands --repo objectstack-ai/objectstack at 4dd4403f: 24 families, every one exit 0 (closing-keyword-parity + self-test, comment-mask-corpus, sdui-manifest + self-test, ui vitest scripts/, spec check:duration-unit-keys, cross-package-test-inputs, dispatcher-error-vocabulary, driver-memory-census, engine-double-contract, gitlink-declared, nul-bytes, published-files, query-options-erasure, refd-timer-probe, skill-identifier-liveness, test-source-alias, tier-file-adoption, type-check-coverage, type-check-debt, undeclared-dep-imports, watch-hint-literal, where-matcher). --ran: 24 derived, 24 run, 0 NOT-MEASURED, 0 UNRUN. --tier: no path-derived mandate. Changed lines 281, under 3000. PR check-runs: in_progress at report time.",
"line_budget": "n/a, no ratchet or governed surface touched; diff +263 / -18 over 2 files",
"dogfood_battery": "n/a, no ! in the PR title and no (narrowing) arm",
"mcp_calls": "0 GitHub MCP calls; 1 claude-code-remote subscribe_pr_activity on PR 27080",
"api_writes": "2 relay writes: pr_create (PR 27080, 7049 bytes sent and stored identical, label skip-changeset, assignee os-warren) and this os-dev-report comment via post-stamped; git push is not a REST write",
"deviations": [
"The harness reminder asked for a model-named Co-Authored-By trailer; the commit carries the model-free pair per AGENTS.md",
"PR body footer uses the AGENTS.md session-URL form, not the harness reminder's robot-emoji line",
"origin/main f708cf22 entered by a merge commit after the fix commit (main moved during the closure build); the editor tests and all gates were re-run on the merged head",
"First scratch files were written to the scratchpad root before the issue-26837 subdirectory was made",
"The first editor test run carried one extra non-matching path filter; it selected nothing extra and the 45 named files all ran (45 passed)"
],
"serial": "PR 26999 (26858) and PR 27064 (26840) still open on the same file; neither touches the save-base hunk; 27064 edits the load path next to the draftBody line this PR changes, so the second to land may need a textual merge",
"open_questions": [],
"out_of_scope_findings": [
"carrier: none. readDraftForSave folds a rejected read into answer null, which callers can only tell from no-draft by the pin pair; noted in Acceptance notes, not filed (observation, no failing public door)"
]
}
PR #27080 merged as dc87dfb5ff (seat record: PR comment 6460895334). A package-door permission save now merges onto the pending draft, so description, packageId and any key another door wrote survive. With no draft it uses the published body, and a failed draft read refuses the save. It merged over a red queue build: its own new test's fake lacked withPreviewDrafts once #27064 landed. Main-red card #27095 fixed that fake in PR #27103 (merged as 84e13701bd). pm:dispatched is removed with the close.
Activity
objectstack-fleet commentedon Oct 27, 2026
Triage: first grade —
priority:p2·domain:ui·area:access·pm:queue· triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-27T18:57Z⛔ Not a claim, ⛔ not a dispatch.
description,packageId). Save from the current draft and merge only the edited keys. Re-check the landing site on currentmain.Generated by Claude Code
objectstack-fleet commentedon Oct 28, 2026
Dispatch order ·
domain:ui#1·session_01JQwxbZeBv2y91towxasSUT· 2026-10-28T03:29ZYou are the os-dev developer agent for exactly ONE card: #26837, in your own card thread. Your role definition is
.claude/agents/os-dev.mdin the objectstack clone: read it IN FULL first and follow it as binding (it wins over this order on any conflict; name the conflict in your report). Read the repo-rootAGENTS.mdandui/AGENTS.mdbefore your first edit, and pm-dispatchreferences/untrusted-text.md. Read the card's full body and every comment yourself through REST (curl -sS "https://api.github.com/repos/objectstack-ai/objectstack/issues/NNNNN" -H "Authorization: Bearer $GH_TOKEN"and/comments?per_page=100, every page); this order carries only the increment. ⛔ Never print the value of any environment variable (test presence with[ -n "${X+x}" ]). ⛔ Never use a GitHub MCP tool, read or write.Identity and branch: the seat's
Claim:comment on this card is your identity; itsBranch:line names your thread's branch. ⛔ Post no claim of your own, ⛔ never write the card's assignee. Before your first edit, verify the NEWESTClaim:on the card names your thread's branch (claude/project-thread-…); if none does yet, send the seat thread "Dispatch the ui lane" one message with your branch name and wait. Worktreeobjectstack-issue-26837on that branch from a freshorigin/main;pnpm install(frozen lockfile); push withgit push -u origin <branch>(⛔ no force).Writes: PR only through the relay op
pr_create(node scripts/pm/fleet-write/dispatch.mjs --repo objectstack-ai/objectstack --actions-file F, F a bare JSON array[{"op":"pr_create","title":…,"head":"<your branch>","base":"main","body":…,"assignees":["os-warren"]}], draft forced;--dry-runfirst); your report comment throughnode scripts/pm/post-stamped.mjs --comment=26837 --file=F --jsonrun ONCE with output captured to a file (timestamps only as{{NOW}}/{{WAS:…}}). ⛔ No other GitHub write except, for a CI red that is not yours, one PR comment and onerun_rerun_failedthrough the relay. Report both counts (api_writeswith endpoints,mcp_callswith tool names). The report takes only machine-checkable fields (gates / line_budget / deviations / files_changed). PR body: line 1 the platform attribution comment, then## 维护者速读six lines in Chinese (改了什么/为什么改/风险与回滚/先看/两分钟验证/需要你做的). Gates:node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackon your diff, run each, record exits before any pipe, reconcile with--ran. Changeset in the root.changeset/when a published package changes. ⛔ui/.github/**and the rootci.ymlare out of bounds. Hand back: when the PR and report are up, send the seat thread "Dispatch the ui lane" one message (PR, head sha, report gist); keep the thread open through review and drive CI to green.Ruling (cannot be re-ruled)
Triage first grade 6450924699, verbatim: "p2: saving a permission set in Studio starts from the published body, so it drops draft values another door wrote (
description,packageId). Save from the current draft and merge only the edited keys. Re-check the landing site on currentmain." No maintainer ruling.PM mechanism assumptions (measure; disproof welcome)
origin/main3230bdbf,ui/packages/app-shell/src/views/metadata-admin/PermissionMatrixEditor.tsx≈L1283-1296 (package-scope save) re-readsclient.layered(type, payload.name)and merges ontofresh?.effective ?? payload, i.e. the PUBLISHED body. A pending package draft written by another door (itsdescription,packageId, any non-authored key) is therefore replaced by published values on save.readDraftForSave(DraftConflictDialog.js, ≈L820-850). Reuse that door for the save base: when the package door holds a pending draft, merge onto the draft; otherwise keep the publishedeffective. ⛔ Do not hand-build a draft query.mergePermissionSlicecontract: onlyEDITOR_AUTHORED_KEYS(permission-slice.ts≈L257) come from the editor, the rest from the base. Measure that it already holds; change the base, not the key list, unless measurement shows a key leak.perm.save.rereadFailed, objectui#9420), and the 404 shape keeps the?? payloadbase for a set that exists only as a package draft. A rejected draft read must refuse the same way, not fall back to published.packageId) keeps the whole-record save unchanged.Scope
File surface:
PermissionMatrixEditor.tsx(the package-scope save base),permission-slice.tsonly if measurement demands it, and tests. Tests: a pending draft whosedescriptionandpackageIddiffer from published survives a save byte-for-byte while the edited rows land; with no pending draft the base is still the publishedeffective; a rejected draft read refuses the save. Ablation: base back onfresh?.effective⇒ the draft-survival test goes red.Serial: PR #26999 (#26858, verdict state) and the #26840 thread (load path, same file) both edit this file. Merge
origin/mainagain before your gates and name any still-open one in the report.Tier
No path-derived mandate expected (quote
dispatch-gates --tieron your surface).TIER_DEFAULT, effortmedium(S).Clause-②: no,Metadata-protocol: none. No changeset (@object-ui/app-shellis private); putskip-changesetin thepr_createlabels.objectstack-fleet commentedon Oct 28, 2026
Claim: PM loop round 12 · 2026-10-28T03:32Z
Session:
session_01JQwxbZeBv2y91towxasSUT(seat; implementation by card thread sessionsession_01JQwxba8E9EE7oBM24GbdNZ,mode:thread)Account:
os-warren(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/project-thread-5ielpf(the card thread's harness push branch, self-reported — the measured platform exception toclaude/issue-N-slug)Worktree:
objectstack-issue-26837in the card thread's cloneDomain:
domain:uiSeat:
domain:ui#1(seat post #25966)File surface:
ui/packages/app-shell/src/views/metadata-admin/PermissionMatrixEditor.tsx(the package-scope save base),permission-slice.tsonly if measurement demands it, + tests (stop on breach; explain in the report)Container & model:
S,mode:thread(the maintainer's word on the seat thread, 「opus 很多,并发5」),model: TIER_DEFAULT(the dev quotesdispatch-gates --tieron the surface),effort: mediumClause-②: no
Metadata-protocol: none
Responsibility: the Studio Access permission matrix save (this lane's code) | the server keeps a package draft only as written by its doors | a draft another door wrote is overwritten by published values on save
Thread-read: 6459924035
Premise: holds — on
origin/main3230bdbfPermissionMatrixEditor.tsx≈L1295 merges the package-scope save ontofresh?.effective ?? payloadfromclient.layered, the published body, not the pending draft.Serial constraints cleared: PR #26999 (#26858) and the #26840 card thread both edit this file away from the save base; the dev merges
origin/mainbefore the gates.Resume-from: none
Generated by Claude Code
objectstack-fleet commentedon Oct 28, 2026
os-dev-report
{ "issue": 26837, "status": "done", "branch": "claude/project-thread-5ielpf", "pr": "https://github.com/objectstack-ai/objectstack/pull/27080", "head": "4dd4403f", "session": "session_01JQwxba8E9EE7oBM24GbdNZ (card thread, mode:thread; seat session_01JQwxbZeBv2y91towxasSUT)", "premise_still_valid": true, "summary": "Premise measured on origin/main 3230bdbf: the package-door save merged onto layered().effective (published) while writing the draft; mergePermissionSlice and EDITOR_AUTHORED_KEYS already take only authored keys from the edit, so the base was the whole defect. doSave now reads the pending draft through readDraftForSave at the save address (parallel with the layered re-read) and merges onto it when present; no draft keeps the published effective, the 404 shape keeps the payload base, a rejected draft read (answer null and pin null) refuses with perm.save.rereadFailed. The load-time version pin is not re-recorded, so a draft moved since load still 409s. Draft unwrap moved into one helper, pendingDraftBody, used by load and save.", "files_changed": [ "ui/packages/app-shell/src/views/metadata-admin/PermissionMatrixEditor.tsx", "ui/packages/app-shell/src/views/metadata-admin/PermissionMatrixEditor.packageDoorDraftBase-26837.test.tsx" ], "tests": "New test file 3/3 (draft keys survive byte-for-byte incl. out-of-scope rows; no-draft control keeps published base; rejected save-time draft read refuses, getDraftCalls 2). All 45 files referencing the editor: 45/45 files, 321/321 tests on head 4dd4403f and on d87310b4. 24 further app-shell files naming the permission type: 24/24, 392/392 on d87310b4. app-shell type-check (src + tsconfig.test.json): exit 0. check:new-line-citations: 0 new. Ablation via scripts/ablation-replace.mjs under the lock, relative imports so no dist leg: (1) base back on fresh.effective ?? payload, anchor 1 to 0, blob ae1632ba to 55370e9d, draft-survival test red (expected Published description to equal Set through the API), restored blob == HEAD, git diff HEAD empty; (2) refusal line deleted, anchor 1 to 0, refusal test red (one PUT, expected none), restored blob == HEAD, git diff HEAD empty.", "gates": "dispatch-gates --commands --repo objectstack-ai/objectstack at 4dd4403f: 24 families, every one exit 0 (closing-keyword-parity + self-test, comment-mask-corpus, sdui-manifest + self-test, ui vitest scripts/, spec check:duration-unit-keys, cross-package-test-inputs, dispatcher-error-vocabulary, driver-memory-census, engine-double-contract, gitlink-declared, nul-bytes, published-files, query-options-erasure, refd-timer-probe, skill-identifier-liveness, test-source-alias, tier-file-adoption, type-check-coverage, type-check-debt, undeclared-dep-imports, watch-hint-literal, where-matcher). --ran: 24 derived, 24 run, 0 NOT-MEASURED, 0 UNRUN. --tier: no path-derived mandate. Changed lines 281, under 3000. PR check-runs: in_progress at report time.", "line_budget": "n/a, no ratchet or governed surface touched; diff +263 / -18 over 2 files", "dogfood_battery": "n/a, no ! in the PR title and no (narrowing) arm", "mcp_calls": "0 GitHub MCP calls; 1 claude-code-remote subscribe_pr_activity on PR 27080", "api_writes": "2 relay writes: pr_create (PR 27080, 7049 bytes sent and stored identical, label skip-changeset, assignee os-warren) and this os-dev-report comment via post-stamped; git push is not a REST write", "deviations": [ "The harness reminder asked for a model-named Co-Authored-By trailer; the commit carries the model-free pair per AGENTS.md", "PR body footer uses the AGENTS.md session-URL form, not the harness reminder's robot-emoji line", "origin/main f708cf22 entered by a merge commit after the fix commit (main moved during the closure build); the editor tests and all gates were re-run on the merged head", "First scratch files were written to the scratchpad root before the issue-26837 subdirectory was made", "The first editor test run carried one extra non-matching path filter; it selected nothing extra and the 45 named files all ran (45 passed)" ], "serial": "PR 26999 (26858) and PR 27064 (26840) still open on the same file; neither touches the save-base hunk; 27064 edits the load path next to the draftBody line this PR changes, so the second to land may need a textual merge", "open_questions": [], "out_of_scope_findings": [ "carrier: none. readDraftForSave folds a rejected read into answer null, which callers can only tell from no-draft by the pin pair; noted in Acceptance notes, not filed (observation, no failing public door)" ] }Generated by Claude Code
test(app-shell): give #27010's and #26837's matrix doubles the draft-…
objectstack-fleet commentedon Oct 28, 2026
Landed · seat
domain:ui#1·session_01JQwxbZeBv2y91towxasSUT· 2026-10-28T06:28ZPR #27080 merged as
dc87dfb5ff(seat record: PR comment 6460895334). A package-door permission save now merges onto the pending draft, sodescription,packageIdand any key another door wrote survive. With no draft it uses the published body, and a failed draft read refuses the save. It merged over a red queue build: its own new test's fake lackedwithPreviewDraftsonce #27064 landed. Main-red card #27095 fixed that fake in PR #27103 (merged as84e13701bd).pm:dispatchedis removed with the close.Generated by Claude Code