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
console: a member refused the Setup app still gets /apps/setup/OBJECT routes rendered inside another app's shell — the alias spelling skips the app verdict #27507 added #28988
/_console/apps/setup/sys_user, /apps/setup/sys_organization and /apps/setup/showcase_task render the object list inside the Showcase app's header and sidebar, under a /apps/setup/… URL;
the legacy /_console/apps/setup/system/users redirects there, so the member reaches it from a documented URL.
Same class under an app the member can open: /_console/apps/com.example.showcase/system/settings shows the settings hub with "No settings registered. Plugins can register settings manifests via the SettingsService." (the server returns 0 manifests to a member), #27507's first symptom on a route its fix did not cover.
Server side is by design: GET /api/v1/data/sys_user → 200 under the sys_user_org_members policy; GET /api/v1/meta/app/setup → 403. Presentation only.
Measured at framework 7fa9873f (main, 2026-11-02), stock showcase boot from a scratch copy, Console built from the same tree.
Reproduction
Boot the showcase; mint a plain member (invite then sign-up, or admin create-user then change-password); form sign-in as that member.
Open /_console/apps/setup/sys_user.
Expected: the "You don't have access to this app" page, as for /_console/apps/com.objectstack.setup/sys_user.
Actual: the Showcase shell with the sys_user list (4 users). Reproduced twice on fresh browser contexts.
Claim: PM loop round 17 · 2026-11-02T06:21Z
Session: session_01QsR2jTJvw3sfvZA89BdmSR (card thread, os-dev); dispatched by seat session session_01QsR2jUJ19MstFvFhjgiYTu
Account: zhuangjianguo
Branch: claude/project-thread-jsplg6 (platform-assigned to the card thread; it can push only this branch)
Worktree: objectstack-issue-28988
Domain: domain:ui
Seat: domain:ui#1
File surface: ui/packages/app-shell/src/console/AppContent.tsx (the Setup app verdict on alias-spelled object routes), its tests (ui/apps/console/src/__tests__/AppContent.* included), and a root .changeset (stop on breach; explain in the report)
Container & model: S, mode:thread, model: default tier (dispatch-gates --tier: no path-derived mandate), effort: medium
Clause-②: no
Metadata-protocol: none
Responsibility: AppContent asks the Setup app verdict for the metadata, root, settings and marketplace routes (appSurfaceAwaitsVerdict) but not for alias-spelled object routes
Thread-read: 6570295530
Premise: holds (at origin/mainab085a04: AppContent.tsx:556-557appSurfaceAwaitsVerdict covers isMetadataRoute, isSetupRootRoute and isSettingsOrMarketplaceRoute only. Boundary: AppContent.tsx:537-543 leaves /apps/setup/OBJECT/record/ID open as a product decision (objectui#12706), and members reach it from ApprovalsInboxPage.tsx:613,727; record routes stay as they are unless that decision is made)
Serial constraints cleared: none known.
{
"issue": 28988,
"status": "done",
"branch": "claude/project-thread-jsplg6",
"pr": "https://github.com/objectstack-ai/objectstack/pull/29082",
"head": "0259ffcd",
"session": "session_01QsR2jTJvw3sfvZA89BdmSR",
"premise_still_valid": true,
"summary": "Premise held at main ab085a04: the /apps/setup subtree is a pseudo-route (isSetupRoute in isSpecialRoute), so an alias-spelled object list fell back to the default app instead of asking the verdict, while /apps/com.objectstack.setup/OBJECT (an ordinary unmatched app) drew app-access-denied. AppContent.tsx gains isObjectListAppRoute (matchRoutes over APP_ROUTE_METADATA_READS plus the host's route paths, the same ranking preloadAppRouteChunk uses) and an isSetupObjectListRoute arm in appSurfaceAwaitsVerdict: under the setup alias, :objectName, :objectName/view/:viewId and :objectName/data wait on the Setup verdict. Record routes, the OBJECT/ID shorthand, OBJECT/new and host routes (system/approvals, system/users) stay as they were, per the claim's boundary; the legacy system/users forwards onto the list and is refused there. Admins and a server without the app are unchanged.",
"files_changed": [
"ui/packages/app-shell/src/console/AppContent.tsx",
"ui/packages/app-shell/src/console/__tests__/AppContent.setupObjectListVerdict-28988.test.tsx",
".changeset/28988-setup-alias-object-list-verdict.md"
],
"line_budget": "391 changed lines (+390 / -1, 3 files) vs merge base ab085a04, under the 3000 threshold; inside the claim's file surface",
"tests": "From ui/, under os-verify-lock, --maxWorkers=2, at 0259ffcd. New AppContent.setupObjectListVerdict-28988.test.tsx (real ObjectStackAdapter over stubbed fetch) with the 27507 and 12706 verdict files: Test Files 3 passed, Tests 34 passed, VERDICT command-exit 0. Red on main: AppContent.tsx at ab085a04 with the new test: Tests 6 failed / 6 passed (the five list routes and the legacy system/users redirect; package-id case and controls pass), restored via git checkout HEAD, git diff HEAD empty. Ablation via scripts/ablation-replace.mjs: 'isSetupRoute && isObjectListAppRoute(appSubPath, hostRoutePaths);' -> 'isSetupRoute && appSubPath !== '';' (anchor 1->0, blob 9430666f -> 04b61c84) reds 5 across the new file and the 12706 file (both record-page controls, both approvals-inbox controls, legacy redirect); restored blob == HEAD 9430666f, git diff HEAD empty. No dist step: the tests import AppContent from src. Narrowed sweep: every ui test file importing AppContent (git grep -l: 53 app-shell, 16 apps/console, 1 i18n): Test Files 70 passed, Tests 663 passed, VERDICT 0. turbo run type-check --filter=@object-ui/app-shell (script includes tsc -p tsconfig.test.json): 65/65 tasks, VERDICT 0.",
"gates": "node scripts/pm/dispatch-gates.mjs --commands derived 34 at 0259ffcd; all 34 ran, each exit 0 captured before any pipe, incl. check-sdui-manifest.mjs, check-changeset-no-major --base origin/main, check:type-check-debt, check:query-options-erasure, check:published-files, check:nul-bytes, pnpm --dir ui exec vitest run scripts/ (169 passed / 2 skipped files, 4900 tests). --ran: 34 derived, 34 run, 0 NOT-MEASURED, 0 UNRUN. PR-body gates on the exact body before pr_create: check-changeset-no-major (GITHUB_EVENT_PATH) exit 0, check-adr-0087-registration exit 0, check-closing-keyword-parity exit 0, check-partof-closing-keyword exit 0. main gained 9 commits since the base, none under ui/packages/app-shell/src/console or ui/apps/console/src; no merge. CI check-runs on #29082: in_progress at report time (Check Changeset, Type Check, Lint legs, Governed Surface Queue Guard); re-runs and landing are the seat's.",
"dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm",
"mcp_calls": "3 — mcp__github__issue_read x2 (card body, comments), mcp__github__pull_request_read get_check_runs x1; no write tool",
"api_writes": "2 — (1) fleet-write relay dispatch POST /repos/objectstack-ai/objectstack/dispatches carrying one pr_create (no labels, no assignees; read-back 8059 bytes sent = stored, #29082); (2) this os-dev-report comment through post-stamped. git push x1 is not a REST write.",
"deviations": [
"The PR body opens with the harness-required attribution comment line and the Requested-by line, then Fixes #28988; kept as the harness requires.",
"The shared checkout held the platform-assigned branch, so it was detached (git checkout --detach, no file change) for the worktree objectstack-issue-28988.",
"Not subscribed to PR activity and no assignee/reviewer set on the PR: os-dev.md puts landing and re-runs with the seat and forbids PR assignees."
],
"open_questions": [],
"out_of_scope_findings": [
"class: a · reach: the card's own measurement (界面, a plain member opens /_console/apps/com.example.showcase/system/settings and gets 'No settings registered. Plugins can register settings manifests via the SettingsService.'; the server returns 0 manifests to a member) · evidence: not the same verdict: the member may open Showcase, so the app verdict is granted and #27507 pins that route as rendered unprobed ('the settings route under an app the caller may open renders the page, unprobed' in AppContent.settingsMarketplaceVerdict-27507.test.tsx); the fix site is the host settings hub's empty state (apps/console), not AppContent · dedupe words: settings hub member empty, No settings registered member, system/settings non-admin",
"carrier: seat · noted, not filed — /apps/setup/OBJECT/new (the create form) still renders under the default app for a refused caller; it is not a list route, so the claim's boundary left it. Same wrong-shell class; joins the objectui#12706 product decision on the rest of the Setup subtree."
]
}
Landed · 2026-11-02T08:57Z · PR #29082 merged through the merge queue as 692a4e4a at 2026-11-02T08:37Z (GitHub's clock). ACCEPT 6571491824 at head 0259ffcd.
probe
before (692a4e4a~1)
now (origin/main)
isSetupObjectListRoute in ui/packages/app-shell/src/console/AppContent.tsx
Fixes #28988 closed this card. pm:dispatched comes off in the same stroke as this note. /apps/setup/OBJECT/new under the alias is #29102 (seat 2), and the member settings-hub copy is #29085 (seat 2).
Activity
objectstack-fleet commentedon Nov 2, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: none
Triage: p2 ·
domain:ui·area:access·pm:queue· 2026-11-02T06:02Z⛔ Not a claim.
/apps/setup/OBJECTrendered inside another app's shell, because thesetupalias spelling skips the app verdict Setup routes refuse a plain member three different ways: the settings hub says "No settings registered", a settings page shows a raw capability error inside the Showcase shell, while the Setup app root shows the proper no-access page #27507 added for the canonical id. The server's read is allowed by design here (sys_user_org_members), so this is a UI verdict inconsistency, not an authorization hole. Named location:AppContent.tsx.domain:ui, p2.Generated by Claude Code
objectstack-fleet commentedon Nov 2, 2026
domain:ui#1 · zhuangjianguo · session_01QsR2jUJ19MstFvFhjgiYTu
Claim: PM loop round 17 · 2026-11-02T06:21Z
Session:
session_01QsR2jTJvw3sfvZA89BdmSR(card thread, os-dev); dispatched by seat sessionsession_01QsR2jUJ19MstFvFhjgiYTuAccount:
zhuangjianguoBranch:
claude/project-thread-jsplg6(platform-assigned to the card thread; it can push only this branch)Worktree:
objectstack-issue-28988Domain:
domain:uiSeat:
domain:ui#1File surface:
ui/packages/app-shell/src/console/AppContent.tsx(the Setup app verdict on alias-spelled object routes), its tests (ui/apps/console/src/__tests__/AppContent.*included), and a root.changeset(stop on breach; explain in the report)Container & model: S,
mode:thread,model: default tier (dispatch-gates --tier: no path-derived mandate),effort: mediumClause-②: no
Metadata-protocol: none
Responsibility:
AppContentasks the Setup app verdict for the metadata, root, settings and marketplace routes (appSurfaceAwaitsVerdict) but not for alias-spelled object routesThread-read: 6570295530
Premise: holds (at
origin/mainab085a04:AppContent.tsx:556-557appSurfaceAwaitsVerdictcoversisMetadataRoute,isSetupRootRouteandisSettingsOrMarketplaceRouteonly. Boundary:AppContent.tsx:537-543leaves/apps/setup/OBJECT/record/IDopen as a product decision (objectui#12706), and members reach it fromApprovalsInboxPage.tsx:613,727; record routes stay as they are unless that decision is made)Serial constraints cleared: none known.
objectstack-fleet commentedon Nov 2, 2026
domain:ui#1 · zhuangjianguo · session_01QsR2jTJvw3sfvZA89BdmSR
os-dev-report
{ "issue": 28988, "status": "done", "branch": "claude/project-thread-jsplg6", "pr": "https://github.com/objectstack-ai/objectstack/pull/29082", "head": "0259ffcd", "session": "session_01QsR2jTJvw3sfvZA89BdmSR", "premise_still_valid": true, "summary": "Premise held at main ab085a04: the /apps/setup subtree is a pseudo-route (isSetupRoute in isSpecialRoute), so an alias-spelled object list fell back to the default app instead of asking the verdict, while /apps/com.objectstack.setup/OBJECT (an ordinary unmatched app) drew app-access-denied. AppContent.tsx gains isObjectListAppRoute (matchRoutes over APP_ROUTE_METADATA_READS plus the host's route paths, the same ranking preloadAppRouteChunk uses) and an isSetupObjectListRoute arm in appSurfaceAwaitsVerdict: under the setup alias, :objectName, :objectName/view/:viewId and :objectName/data wait on the Setup verdict. Record routes, the OBJECT/ID shorthand, OBJECT/new and host routes (system/approvals, system/users) stay as they were, per the claim's boundary; the legacy system/users forwards onto the list and is refused there. Admins and a server without the app are unchanged.", "files_changed": [ "ui/packages/app-shell/src/console/AppContent.tsx", "ui/packages/app-shell/src/console/__tests__/AppContent.setupObjectListVerdict-28988.test.tsx", ".changeset/28988-setup-alias-object-list-verdict.md" ], "line_budget": "391 changed lines (+390 / -1, 3 files) vs merge base ab085a04, under the 3000 threshold; inside the claim's file surface", "tests": "From ui/, under os-verify-lock, --maxWorkers=2, at 0259ffcd. New AppContent.setupObjectListVerdict-28988.test.tsx (real ObjectStackAdapter over stubbed fetch) with the 27507 and 12706 verdict files: Test Files 3 passed, Tests 34 passed, VERDICT command-exit 0. Red on main: AppContent.tsx at ab085a04 with the new test: Tests 6 failed / 6 passed (the five list routes and the legacy system/users redirect; package-id case and controls pass), restored via git checkout HEAD, git diff HEAD empty. Ablation via scripts/ablation-replace.mjs: 'isSetupRoute && isObjectListAppRoute(appSubPath, hostRoutePaths);' -> 'isSetupRoute && appSubPath !== '';' (anchor 1->0, blob 9430666f -> 04b61c84) reds 5 across the new file and the 12706 file (both record-page controls, both approvals-inbox controls, legacy redirect); restored blob == HEAD 9430666f, git diff HEAD empty. No dist step: the tests import AppContent from src. Narrowed sweep: every ui test file importing AppContent (git grep -l: 53 app-shell, 16 apps/console, 1 i18n): Test Files 70 passed, Tests 663 passed, VERDICT 0. turbo run type-check --filter=@object-ui/app-shell (script includes tsc -p tsconfig.test.json): 65/65 tasks, VERDICT 0.", "gates": "node scripts/pm/dispatch-gates.mjs --commands derived 34 at 0259ffcd; all 34 ran, each exit 0 captured before any pipe, incl. check-sdui-manifest.mjs, check-changeset-no-major --base origin/main, check:type-check-debt, check:query-options-erasure, check:published-files, check:nul-bytes, pnpm --dir ui exec vitest run scripts/ (169 passed / 2 skipped files, 4900 tests). --ran: 34 derived, 34 run, 0 NOT-MEASURED, 0 UNRUN. PR-body gates on the exact body before pr_create: check-changeset-no-major (GITHUB_EVENT_PATH) exit 0, check-adr-0087-registration exit 0, check-closing-keyword-parity exit 0, check-partof-closing-keyword exit 0. main gained 9 commits since the base, none under ui/packages/app-shell/src/console or ui/apps/console/src; no merge. CI check-runs on #29082: in_progress at report time (Check Changeset, Type Check, Lint legs, Governed Surface Queue Guard); re-runs and landing are the seat's.", "dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm", "mcp_calls": "3 — mcp__github__issue_read x2 (card body, comments), mcp__github__pull_request_read get_check_runs x1; no write tool", "api_writes": "2 — (1) fleet-write relay dispatch POST /repos/objectstack-ai/objectstack/dispatches carrying one pr_create (no labels, no assignees; read-back 8059 bytes sent = stored, #29082); (2) this os-dev-report comment through post-stamped. git push x1 is not a REST write.", "deviations": [ "The PR body opens with the harness-required attribution comment line and the Requested-by line, then Fixes #28988; kept as the harness requires.", "The shared checkout held the platform-assigned branch, so it was detached (git checkout --detach, no file change) for the worktree objectstack-issue-28988.", "Not subscribed to PR activity and no assignee/reviewer set on the PR: os-dev.md puts landing and re-runs with the seat and forbids PR assignees." ], "open_questions": [], "out_of_scope_findings": [ "class: a · reach: the card's own measurement (界面, a plain member opens /_console/apps/com.example.showcase/system/settings and gets 'No settings registered. Plugins can register settings manifests via the SettingsService.'; the server returns 0 manifests to a member) · evidence: not the same verdict: the member may open Showcase, so the app verdict is granted and #27507 pins that route as rendered unprobed ('the settings route under an app the caller may open renders the page, unprobed' in AppContent.settingsMarketplaceVerdict-27507.test.tsx); the fix site is the host settings hub's empty state (apps/console), not AppContent · dedupe words: settings hub member empty, No settings registered member, system/settings non-admin", "carrier: seat · noted, not filed — /apps/setup/OBJECT/new (the create form) still renders under the default app for a refused caller; it is not a list route, so the claim's boundary left it. Same wrong-shell class; joins the objectui#12706 product decision on the rest of the Setup subtree." ] }objectstack-fleet commentedon Nov 2, 2026
domain:ui#1 · zhuangjianguo · session_01QsR2jUJ19MstFvFhjgiYTu
Landed · 2026-11-02T08:57Z · PR #29082 merged through the merge queue as
692a4e4aat 2026-11-02T08:37Z (GitHub's clock). ACCEPT 6571491824 at head0259ffcd.692a4e4a~1)origin/main)isSetupObjectListRouteinui/packages/app-shell/src/console/AppContent.tsxAppContent.setupObjectListVerdict-28988.test.tsxexistsFixes #28988closed this card.pm:dispatchedcomes off in the same stroke as this note./apps/setup/OBJECT/newunder the alias is #29102 (seat 2), and the member settings-hub copy is #29085 (seat 2).