Repository navigation
Setup Positions: a new position saved as a draft does not appear in the list, with no unpublished badge and no Publish action #26192
Description
Filing gate ②: a maintainer ruling, carried out as child 6 of the nine that #26059's ruling (Q3) names. ⛔ Not a claim.
Premise to settle first: which packageId did Setup → Positions → New carry when it saved? The permission editor writes mode: 'draft' only when the item sits under a packageId (PermissionMatrixEditor.tsx:1104, ADR-0086 P0/P2: authoring inside an installed package is a draft until publish). So the measured path was probably a position created inside a package's scope, and the gap is the list. If the save carried no packageId, the draft write is itself the defect, and the fix is the save, not the list. Reproduce, answer this on the card, then build.
Setup → Positions → New → Save sends PUT /api/v1/meta/position/:name?mode=draft and gets 200. The new position does not appear in the Positions list, and GET of it answers not found until the admin opens its detail page and clicks Publish. A permission-set save takes effect immediately, so admins reasonably conclude the position was not saved.
Part of #26059
Ruling-ref: 6416233738 (#26059 (comment))
Ruling: batch #396 item 1, letter Q1 是 / Q2 不做 / Q3 分拆 / Q4 框架. This card is Q3 child 6 (ui, p2): item 4. Brief 6415795834, item-by-item reading 6415971852 (item 4).
Filed by domain:ui seat 2 (domain:ui#2) · os-steve · session session_01SMps7wq7rBTk53e8KTSEup, first claimant of #26059. Domain, grade and route are triage's on first touch.
Business reason. An admin creating positions for an org chart saves one, sees nothing in the list, and creates it again or gives up. The two access objects they author side by side (permission sets and positions) behave differently on Save, with no visible cue.
Reader who acts. The domain:ui execution seat, dispatch step: one os-dev, one PR in ui/packages/app-shell/src/views/metadata-admin/ (the metadata list, or the save path if the premise says so).
Spec contract. None changes. The read uses the existing drafts ledger.
Duplicate check. A local scan of the 1,962 newest issues (all states, #22168–#26181) for (position).{0,120}draft.{0,120}(list|not shown|unpublished)|unpublished.{0,40}badge found 0 hits. #26056, the drafts-ledger read for a member, is the neighbour, not a duplicate (see below).
Ruled scope (ruling Q3 item 6)
- The metadata list shows the caller's drafts with an "unpublished" badge and a Publish action, beside fix(app-shell): no drafts-ledger read for a member, no published read for a new draft #26056's drafts-ledger read.
- Premise first: which
packageIdthe Setup "New" carried (PermissionMatrixEditor.tsx:1104, ADR-0086 P0/P2). A draft written with none is itself the defect.
Anchors (re-read on origin/main 83a26c38)
ui/packages/app-shell/src/views/metadata-admin/PermissionMatrixEditor.tsx::1104holds:...(packageId ? { mode: 'draft' as const, packageId } : {}).- The comment at
:1092–1101says the environment door (nopackageId) "stays live", and that metadata-admin mounts this page "with neitherpackageIdnorembedded".
ui/packages/app-shell/src/views/metadata-admin/EmbeddedItemEditor.tsx:167holds. It writesmode: 'draft'only when the parent already has a pending draft (:117, objectui#12027).- fix(app-shell): no drafts-ledger read for a member, no published read for a new draft #26056 was merged 2026-10-26T12:51Z, after the ruling. It was the PR "fix(app-shell): no drafts-ledger read for a member, no published read for a new draft", so the drafts-ledger read the ruling leans on is on
mainnow. Build on what it landed. docs/adr/0086-authz-metadata-config-boundary-and-cross-package-composition.md: P0/P2, the package door's draft semantics.
Expected / acceptance
- The premise is answered on this card before code: the
packageIdthe Setup "New" carried, with a reproduction. - If it carried a
packageId, the Positions list shows the caller's draft positions, each with an "unpublished" badge and a Publish action. Publishing moves the position to the published list and clears the badge. - If it carried none, a Setup-created position saves live, as a permission set does, and no draft is written. The PR fixes the save path instead.
- Either way, a position saved from Setup → Positions → New is visible in the list right after Save. Tests pin the chosen path.
Activity
objectstack-fleet commentedon Oct 26, 2026
Triage: first grade — as filed ·
pm:queue· triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-26T16:01Z⛔ Not a claim, ⛔ not a dispatch.
Generated by Claude Code
objectstack-fleet commentedon Oct 26, 2026
Claim: PM loop round 3 · 2026-10-26T16:38Z
Session:
session_01SMps7wq7rBTk53e8KTSEupAccount:
os-steveBranch:
claude/issue-26192-position-create-liveWorktree:
objectstack-issue-26192Domain:
domain:uiSeat:
domain:ui#2File surface: the
positionresource registration —ui/packages/app-shell/src/services/builtinComponents.tsx(:216–235, theregisterMetadataResourceforposition) orui/packages/app-shell/src/views/metadata-admin/anchors.ts(beside thepermissionentry'screateLive: true, ~:401), whichever is the registration's home; the testResourceEditPage.liveCreate-7611.test.tsx(add a position case) or a newResourceEditPage.positionCreateLive-26192.test.tsx; root.changeset/. ⛔ResourceEditPage.tsxsave rules,ResourceListPage.tsx,PermissionMatrixEditor.tsx, i18n packs,ui/.github/**, rootci.yml. No sdui record moves.Container & model:
S,mode:subagent,model: default tier,effort: mediumClause-②: no
Metadata-protocol: none
Responsibility:
Setup → Positions → New, which writes a draft the list never shows|the existing createLive key (registry.ts:250–260, objectui#7611) and the server's allowRuntimeCreate for position (metadata-plugin.zod.ts:1198)|every Setup admin creating a positionThread-read: 6420729090
Premise: corrected 2026-10-26T16:26Z — answering the card's premise question on
origin/maindf8aac41f2: Setup → Positions → New carried nopackageId. Positions have noEditPage(builtinComponents.tsx:216–235), so New opensMetadataResourceEditPage; its save (ResourceEditPage.tsx:1736–1752) sendsmode: 'draft'with apackageIdonly under?package=, and Setup's scope is?scope=environment. By the ruling's own condition the draft write is the defect. The fix is in the save path, not a drafts list.permissionalready solves this withcreateLive: true(anchors.ts:401, pinned byliveCreate-7611.test.tsx:124–134);positionlacks it, and the server admits a live position create (metadata-plugin.zod.ts:1198, pinned at objectqlengine-security-catalog-package-door.test.ts:260).Serial constraints cleared: none — no open PR or claim holds these files.
Writes: PR only via the relay op
pr_create; comments only via the relay write tools; ⛔ no GitHub MCP writes.createLive: trueforposition, the same keypermissioncarries. Pin it: one live PUT (mode: null, packageId: null, ifNoneMatch: '*') for a New outside a package, and a draft under?package=as the control. Out of scope, stated in the PR: later edits of a position in Setup still save as drafts (the generic editor's rule for every type). Changing that is a product choice and is not made here.Fixes #26192.objectstack-fleet commentedon Oct 26, 2026
os-dev-report
{ "issue": 26192, "status": "done", "branch": "claude/issue-26192-position-create-live", "pr": "https://github.com/objectstack-ai/objectstack/pull/26269", "session": "session_01SMps7wq7rBTk53e8KTSEup (subagent; parent's harness-stamped id)", "premise_still_valid": true, "summary": "Premise answered as the claim corrected it: Setup -> Positions -> New carried no packageId, so the draft write was the defect. The position resource now registers createLive: true, the permission set's key, in ui/packages/app-shell/src/services/builtinComponents.tsx: that registerMetadataResource call is the position's only registration (the permission set's createLive sits in anchors.ts registerBuiltinAnchors beside its object anchor, which position does not have; the registry merges per type, so the single home was chosen). ResourceEditPage.tsx is unchanged: its existing rule writes a createLive type live outside ?package= and a package draft under it. Out of scope, stated in the PR: later Setup edits of a position still save as drafts.", "tests": "New ui/packages/app-shell/src/views/metadata-admin/ResourceEditPage.positionCreateLive-26192.test.tsx (real page, real MetadataClient, real builtinComponents registration, in-memory server recording PUT mode/package/If-None-Match). Red first at test commit 739f7ba6 on base df8aac41f2: VERDICT command-exit 1, Tests 1 failed | 3 passed (4), received mode draft vs expected null (packageId null); 7611 permission control green in the same run. Green at head 41bbd17a: new test + liveCreate-7611 + positionGrants-25333 + permissionSetsFeed-12126: VERDICT command-exit 0, Test Files 4 passed (4), Tests 14 passed (14). @object-ui/app-shell type-check (tsconfig.json + tsconfig.test.json) at 41bbd17a: VERDICT command-exit 0, 0 error TS. Wider 41-file app-shell set (files loading builtinComponents or naming position): NOT MEASURED locally, three lock queue-timeouts (exit 99) behind holder pid 23611 (whole app-shell package run, held 2229s at the last read); CI Test UI 1..16/16 all success at 41bbd17a covers it. No ablation: the diff adds one registration key, the red-first run is the before/after.", "gates": "Local: dispatch-gates --commands --repo objectstack-ai/objectstack at 41bbd17a derived 35 commands, each run with exit captured before any pipe: 34 :: exit 0; pnpm check:type-check-debt :: exit 3 (PREREQUISITE NOT MET, its closure build got no turn on the verify lock) = NOT MEASURED locally. --ran reconciliation: 35 derived, 35 run, 0 NOT-MEASURED claimed, 0 UNRUN. check-sdui-manifest :: exit 0, scripts/sdui-manifest.* untouched. CI at head 41bbd17a (read at report time, not waited on): Lint & Repo Gates success, TypeScript Type Check success, Type Check - debt ledger success, Test Core success, Build Core skipped, Governed Surface Queue Guard success, Type Check UI success, Build & E2E UI success, Test UI 1/16..16/16 success, Check Changeset success, Dogfood Regression Gate success, Lint UI in_progress.", "dogfood_battery": "n/a: no ! in the PR title and Clause-② is no without a (narrowing) arm", "mcp_calls": "0", "api_writes": "2 relay strokes: (1) fleet-write pr_create, one repository_dispatch carrying POST /repos/objectstack-ai/objectstack/pulls (draft #26269) + POST /repos/objectstack-ai/objectstack/issues/26269/assignees (os-steve), read back byte-identical; (2) this os-dev-report comment, POST /repos/objectstack-ai/objectstack/issues/26192/comments via post-stamped.mjs. git push is not a REST write. Zero label writes (changeset present, no labels named).", "open_questions": [], "out_of_scope_findings": [ "noted, not filed: the createLive JSDoc in registry.ts still describes the key only for a type whose EditPage edits the live item; position now uses it with the generic editor (registry.ts was outside the claim's file surface). carrier: none, noted in this report only", "noted, not filed: later edits of an existing position in Setup still save as drafts (the generic editor's rule for every type); the claim names this a product choice not made here, and the PR states it. carrier: the maintainer, if that product choice is wanted" ] }Generated by Claude Code
objectstack-fleet commentedon Oct 26, 2026
Landed · seat 2 · 2026-10-26T19:28Z
d613fdcd, head41bbd17a(+170/−0, ACCEPT on #26269)origin/main366e4aa6, contains it (git merge-base --is-ancestorexit 0)gh-readonly-queue/main/pr-26269-*: none left (consumed)Fixes #26192on #26269Status: Setup → Positions → New now writes the position live outside a package binding, so it shows in the list right after Save; under
?package=it stays a package draft. Later edits of a position still save as drafts (the generic editor's rule, a product choice outside this card).pm:dispatchedremoved.