Repository navigation
Description
director#1 · zhuangjianguo · session_01QsR2jV5sBSzCYNEDgpYLJw
Ruled: 6573557633 · letter A · 2026-11-02T09:24Z
What happens
Measured on @objectstack/* 18.2.0 against objectstack-ai/duly at ac5b5c4, in a live pnpm demo signed in as the dev admin.
duly ships duly_member, a type: 'record' page over sys_user. It is a manager's read-only view of one person: their open, late and stalled tasks, this period's duties, standing duties and recent movement. The page is isDefault: false, because plugin-auth's sys_user_detail is already isDefault: true for the same object.
GET /api/v1/meta/pageserves both record pages forsys_user.duly_membercomes first in the list.- The 18.2 console's page assignment (
@objectstack/console18.2.0, framework chunk) resolves the page for a record as
candidates.find(p => defaultRecordPageObject(p) === object) ?? candidates[0].
TheisDefaultpage therefore always wins. - So in the Duly app, People → any person opens
sys_user_detail(Set Password / Ban User / Impersonate User; Positions, Permission Sets… tabs). duly_memberrenders only on the explicit route/apps/<app>/sys_user/record/<id>/page/duly_member.
On 17.2 the same composition opened duly_member, because the console resolved by list order (objectui#7298). That was never pinned, and 18 resolved the ambiguity in favour of isDefault (ruling A on #23015). The rule itself is right. Its effect is that an app can no longer reach its own record page over a platform object at all.
Why no declarable route reaches it
PageSchemahas no priority or override key. It is astrictObject.isDefault: trueon the app's page refuses the boot. It is a second default forsys_user, so it fails withDEFAULT_RECORD_PAGE_CONFLICT(ruling A on spec(page): no stated rule decides between two isDefault record pages for one object, so the renderer cannot honour isDefault without inventing precedence (the spec half of objectui#12195) #23015).- Overlay-by-name is refused for packages (measured on plugin-auth registers sys_organization_detail unconditionally, so a host with its own default organization record page cannot compose without it — at v18 the cloud control plane's boot is refused (DEFAULT_RECORD_PAGE_CONFLICT) #23254).
- Composition has no option for
sys_user.AuthPluginOptions.registerOrganizationRecordPage(plugin-auth registers sys_organization_detail unconditionally, so a host with its own default organization record page cannot compose without it — at v18 the cloud control plane's boot is refused (DEFAULT_RECORD_PAGE_CONFLICT) #23254) and its derivation underos serve/DevPlugin(cli/plugin-dev: an app underos servecannot leave out plugin-auth'ssys_organizationdefault record page, andDEFAULT_RECORD_PAGE_CONFLICT's remedy names a step that holder cannot take #23310,stackDeclaresOrganizationRecordPage) coversys_organizationonly.SysUserDetailPageregisters either way. - A list view cannot route its rows to a named page.
view.pageNameandnavigation.vieware retired. - What still works is an app-side stopgap: a
type: 'url'action onsys_userwhose target is the explicit/page/duly_memberroute. It is one extra click, from the record's "⋯" menu or the row menu. It also shows in Setup → Users, because actions are per object, not per app.
The caveat any fix has to answer
The console's assignment predicate ignores app, profile and form factor; its predicate for those returns true. So "the app ships the default sys_user page" is deployment-wide. If duly's page became the single default, Setup → Users would open a manager's read view. The admin page (Set Password, Ban, Impersonate, Sessions, Linked Accounts…) would disappear from the deployment. For sys_user that is not acceptable, unlike sys_organization, where cloud wanted the replacement.
The ask — one of these (lane to choose)
- An app-scoped record page. An app declares which record page its own routes open for an object, for example an app-level map
object → pageor a page-levelapps: [...]scope. Inside that app the declared page opens. Everywhere else, including Setup, the deployment's singleisDefaultpage still opens. This keeps ruling A's one-default-per-deployment and adds no precedence ladder: the choice is by the app the route is under, which the route already carries (/apps/<app>/…). - A
sys_usertwin ofregisterOrganizationRecordPage, derived underos serve/DevPluginthe way cli/plugin-dev: an app underos servecannot leave out plugin-auth'ssys_organizationdefault record page, andDEFAULT_RECORD_PAGE_CONFLICT's remedy names a step that holder cannot take #23310 derives the organization one. This is smaller, but it inherits the caveat above: Setup → Users loses its admin page. It is acceptable only if Setup gets its own page or tab set by some other route.
duly's preference is (1). Option (2) solves duly's reachability by breaking the deployment's user administration.
Pins we would expect
- With an app-scoped page declared,
/apps/<that app>/sys_user/record/<id>opens it. Control:/apps/com.objectstack.setup/sys_user/record/<id>still openssys_user_detail. - Without the declaration, behaviour is unchanged (the
isDefaultpage).
App side
duly#113 tracks it. duly pins the console's resolution rule in test/member-page.test.ts. Until this lands it ships the URL-action stopgap, labelled as such, built to be deleted.
Generated by Claude Code
Activity
Reach the member page again on 18.2 with an Open member page action, …
objectstack-fleet commentedon Nov 2, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: none
Triage: p1 ·
domain:spec·area:records·needs-user-decision· 2026-11-02T06:03Z⛔ Not a claim.
sys_userrecord page the one its routes open, because plugin-auth'ssys_user_detailholds the one deployment default. The card offers two shapes: (1) an app-scoped page choice (an app-levelobject → pagemap, or a page-levelappsscope), which adds a key and so is a protocol addition (「协议新增定 p1」); (2) a derivedsys_usertwin of the organization page, which costs Setup → Users its admin page. That is a contract-shape choice, so it goes to the decision inbox for the director seat to present. Platform first: the app keeps its labelled stopgap until it lands.Generated by Claude Code
zhuangjianguo commentedon Nov 2, 2026
director#1 · zhuangjianguo · session_01QsR2jV5sBSzCYNEDgpYLJw
Brief-claimed-by: session_01QsR2jV5sBSzCYNEDgpYLJw · batch #453 · 2026-11-02T08:57Z
Director seat (第 56 场). This card is presented to the maintainer as batch #453 item 1 in the 总监决裁 thread; the ruling comment follows the maintainer's answer. ⛔ Not a claim.
Generated by Claude Code
zhuangjianguo commentedon Nov 2, 2026
director#1 · zhuangjianguo · session_01QsR2jV5sBSzCYNEDgpYLJw
Ruling: batch #453 item 1 · letter A · maintainer 「同意」 2026-11-02T09:22Z
Director seat (第 56 场; brief-claimed above in 6573080718 and presented in the 总监决裁 thread as batch #453 item 1, recommendation A). The maintainer's word is 「同意」 for the whole batch, so this card is ruled A: a page-level app scope.
What the ruling binds (A):
PageSchemagains an optional app scope for a record page (illustrativeapps: string[]; the key's name and exact shape are the spec seat's to fix on this card, page-level preferred over an app-level map because the page and the apps it serves travel together when packaged). A record page that names apps is the page a route under one of those apps opens for its object (/apps/<app>/<object>/record/<id>). Everywhere else, Setup included, the deployment's singleisDefaultrecord page still opens, so ruling A on spec(page): no stated rule decides between two isDefault record pages for one object, so the renderer cannot honour isDefault without inventing precedence (the spec half of objectui#12195) #23015 (one default per object per deployment) stands unchanged, and no precedence ladder is added.defineStack/composeStacks, package registration, metadata save), naming both pages.sys_usertwin ofregisterOrganizationRecordPage) is not taken: it would cost the deployment its user-administration page.usePageAssignment,appObjectScope.declaredRecordPage) reads the route's app first, then the default, then declaration order; without a scoped page behaviour is unchanged. duly's URL-action stopgap is deleted once this lands (duly#113).Workload of the letter: spec key + refusal + tests (
Clause-②: yes (widening),Metadata-protocol: added,CONTRACT_REVIEW_TIER, reviewed by this seat), then the console's assignment read inui; about 300–500 lines over two lanes.Execution:
domain:specfirst (the key, the refusal doors, the describe and the reference page), thendomain:uiconsumes it; the lane seat moves the label. Pins expected: with the scope declared,/apps/<that app>/sys_user/record/<id>opens it and/apps/com.objectstack.setup/sys_user/record/<id>still openssys_user_detail; without it, unchanged. TheRuled:body line is written by this act.Generated by Claude Code
objectstack-fleet commentedon Nov 2, 2026
domain:spec#1 · os-steve · session_01ADHkmPi25XXKx92Xmg4viS
Claim: PM loop round 1 · 2026-11-02T12:37Z
Session:
session_01ADHkmPi25XXKx92Xmg4viS(seat); dev = card threadsession_01ADHkmSGMLCTCPxZfV3GqHrAccount:
os-steveBranch:
claude/project-thread-0hebo5Worktree: the card thread's own checkout of
objectstack-ai/objectstackDomain:
domain:specSeat:
domain:spec#1File surface:
packages/spec/src/ui/page.zod.ts,packages/spec/src/ui/default-record-page.ts,packages/spec/src/stack.zod.ts(duplicateDefaultRecordPageFindings) and their tests; the package-registration doorpackages/objectql/src/registry.ts(the default-record-page slot/claim/refusal code only) andpackages/objectql/src/default-record-page.ts; the metadata save doorpackages/metadata-protocol/src/protocol-save-door-refusals.ts(defaultRecordPageSaveRefusal); their tests; generated references and surfaces thatcheck:generatedmoves; root.changeset/; not the console'susePageAssignment, not duly (stop on breach; explain in the report)Container & model:
M,mode:thread,model: default — the tier is the PM's per-card call (floor sonnet · default opus · ceiling fable); one CONTRACT_REVIEW_TIER review (published schema face, a new key, three refusal doors) before landing,effort: highClause-②: yes (widening)
Metadata-protocol: added
Responsibility:
packages/spec page.zod.ts PageSchema | ruling A on #23015 allows one default record page per object per deployment, and no key scopes a record page to an app | apps (duly) that need their own sys_user record page under their own routeThread-read: 6573557633
Premise: holds (on origin/main
2f7977cd2fPageSchema carries no app scope; ruling A binds the semantics)Serial constraints cleared: none (no in-flight card of this seat holds these files; the three doors land in one PR so no door refuses what another admits)
Ruling-ref: director 6573557633, letter A, maintainer 「同意」 — an optional record-page app scope; name and shape chosen by the spec seat:
apps: string[]. Two scoped record pages for one object in one app are refused at the second-default doors, naming both. ui card filed by this seat once the key lands.objectstack-fleet commentedon Nov 2, 2026
domain:spec#1 · os-steve · session_01ADHkmSGMLCTCPxZfV3GqHr
os-dev-report
{ "issue": 28979, "status": "done", "branch": "claude/project-thread-0hebo5", "pr": "https://github.com/objectstack-ai/objectstack/pull/29437", "session": "session_01ADHkmSGMLCTCPxZfV3GqHr", "premise_still_valid": true, "summary": "PageSchema gains an optional record-page app scope `apps: string[]` (app names, min 1, unique; placed where isDefault is). defaultRecordPageObject(page, app?) is the one reading of an app's slot; the three second-default doors (defineStack/composeStacks, the objectql registry's package door + item seam + claims, the metadata-protocol save door) keep their mechanism keyed by slot (object, or object+app) and refuse a second page for one object in one app with the existing codes, naming both pages and the app. The default and an app's page coexist. No new public name; the console read is the ui follow-up.", "tests": "Red on main (6 impl files + 2 ledgers restored to 2f7977cd by blob hash, spec + metadata-protocol rebuilt, restored with empty git diff HEAD): spec stack-app-record-page.test.ts Tests 22 failed | 5 passed (27); objectql registry-app-record-page + default-record-page-save-door Tests 20 failed | 38 passed (58). Green on head 0b4d7827 (main merged), each under os-verify-lock VERDICT command-exit 0: spec test 792/792 files, 23478 passed | 1 todo; spec typecheck 0; check:generated 0; metadata-protocol test 304 passed | 4 skipped files, 22155 passed; mp typecheck 0; objectql 4 default-record-page files 93/93, typecheck 0; objectql full suite on pre-merge head 4d11203c 531/531 files, 10300 passed (the merge moved no objectql file).", "gates": "dispatch-gates --commands on 0b4d7827: 110 families, all exit 0 after a workspace build; --ran: 110 derived, 110 run, 0 NOT-MEASURED (DERIVED zero). Same 110 exit 0 on pre-merge head 4d11203c. check-changeset-no-major --event with the body lines: LEVEL AXIS ok, exit 0. Local-only: check:skill-frame-freshness 0; check:dev-prereqs 1 on machine state (35 ui packages unbuilt). CI on the PR: in_progress at report time.", "dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm", "mcp_calls": "9 — reads only: issue_read x4 (get, get_comments x3), pull_request_read x5 (get x2, get_status, get_files, get)", "api_writes": "2 — relay pr_create (#29437), relay comment (this os-dev-report on #28979); plus git push (not REST)", "files_changed": 17, "line_budget": "942 additions / 133 deletions vs main (17 files, incl. generated: authorable-surface ui.json +1, references page.mdx +1, state-counts +1/-1); under the 3000-line class (c) threshold", "deviations": [ "Changeset bumps @objectstack/objectql and @objectstack/metadata-protocol minor beside spec (delta said spec minor): the diff moves both packages' src under Metadata-protocol: added, and the level gate grades every moved package.", "Forced test edits outside the doors' own tests: metadata-protocol protocol.meta-types-degenerate-derivation.test.ts (page 27 -> 28 top-level properties) and spec metadata-form-zod-reconciliation.test.ts (planned omit row for page.apps). Both named in the PR body.", "misplacedDefaultPageRefusal (existing export) now also refuses a misplaced `apps` (the isDefault sentence first, bytes unchanged) so the parse and the registry door print one sentence per key; its name stays.", "Not subscribed to the PR: the standard order says don't wait on CI; the seat drives FAIL rounds." ], "open_questions": [ { "question": "An app-slot conflict reuses DEFAULT_RECORD_PAGE_CONFLICT (no new public name, per the seat). Keep it, or give the app slot its own code later?", "options": [ "A keep DEFAULT_RECORD_PAGE_CONFLICT; the message and err.app / conflicts[].app name the app", "B a new code such as APP_RECORD_PAGE_CONFLICT (a new public name, needs the maintainer)" ], "recommendation": "A, because the condition and the remedy are the default's one level down, clients already handle the code, and the app is carried structurally." }, { "question": "`apps` entries are app NAMES (AppSchema.name, e.g. setup), not package ids (com.objectstack.setup, which the card's example route uses). Confirm the ui card maps the route's app segment to the app name.", "options": [ "A app names (as built)", "B package ids" ], "recommendation": "A, because pages and navigation already reference apps by name, and an app name is what an author writes." }, { "question": "No cross-reference that a named app exists (a page may scope itself to another package's app). Add a validate-time warning later?", "options": [ "A leave it (as built)", "B a warning when no app in the composed stack carries the name" ], "recommendation": "A for this card; B is a small lint card if authors trip on typos." } ], "security_findings": "none", "out_of_scope_findings": [ "carrier: seat · noted, not filed — follow-ups listed in the PR body: ui usePageAssignment / appObjectScope.declaredRecordPage read the route's app first (liveness page.apps flips to live there); duly#113 stopgap removal" ] }Stamped 2026-11-02T14:57Z.
Generated by Claude Code
objectstack-fleet commentedon Nov 2, 2026
domain:spec#1 · os-steve · session_01ADHkmPi25XXKx92Xmg4viS
Landed · 2026-11-02T20:31Z
b16ee8ba06, through the merge queuecompletedbyFixes #28979pm:dispatchedis removed. The seat ACCEPTed, and the CONTRACT_REVIEW_TIER review passed on0b4d7827. The seat carried that PASS through two merge-only heads (2c39924fandb634bbb5), which were a main conflict and a dropped-refinements header regen.What changed:
PageSchema.appsis a page-level app scope for a record page, per ruling A.defaultRecordPageObject(page, app?)is the one slot reading.DEFAULT_RECORD_PAGE_CONFLICTcarryingapp.[PLANNED — not yet rendered]until the console reads it.The console half is filed as a
domain:uifollow-up. It carries review flag ③.2: resolve the route segment withmatchAppBySegment, then readdefaultRecordPageObject(p, app.name), and never compare the raw segment. Once that card lands, objectstack-ai/duly#113 can retire its URL-action stopgap. Next onstack.zod.tsis #29335, which is free for another spec seat.