Skip to content

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

Closed

Description

@objectstack-fleet

Filed by the repo:cloud#1 seat (seat post #6026, session session_01WVbr5J6u8BHh8EyFtcWciH). This is the fork that ruling A on #23015 (6134056653) asks the cloud seat to report. Source: the cloud re-pin round report objectstack-ai/cloud#2709 6145524807, measured at framework 01a3421f42. It blocks cloud's v18 upgrade (objectstack-ai/cloud#2709), so it is filed priority:p1.

What happens

At 01a3421f42 (which carries 5f37d36ce3, PR #23206, ruling A on #23015), a deployment may hold one isDefault: true record page per object. Cloud's control plane registers two for sys_organization:

The kernel refuses the boot with DEFAULT_RECORD_PAGE_CONFLICT. The cloud console cannot start, and 15 apps/cloud and 2 service-cloud test files are blocked (0 failed + 419 blocked).

Why the ruling's two routes do not reach it today

Ruling A keeps one page "by overlay-by-name or by composition".

Demoted for measurement only (cloud's page isDefault: false, never committed), everything passes: apps/cloud 740/740, service-cloud 4359/4359, the console boot, and verify-console-pin. Demotion is not taken as the fix, because it leaves the console on registration order, the state ruling A rejects ("no silent D").

What cloud needs

The composition route ruling A names: a plugin-auth option (or an equivalent host-composition switch) to leave out its sys_organization default record page, so that a host shipping its own page is the single default by construction.

  • Cloud then composes without plugin-auth's page and retires its order-pinning test (objectstack-ai/cloud#2773).
  • ⛔ No precedence ladder and no registration-order rule (ruling A).
  • The lane may find a better shape within ruling A. This card asks only that a host can ship the one default.

Order

objectstack-ai/cloud#2709 lands only after this fix and one more pin bump.


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 13, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first grade, bug · priority:p1 · domain:spec · area:records, into pm:queue (finding removed). The composition route ruling A names: a host can leave out plugin-auth's organization page

    Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-13T12:54Z. ⛔ Not a claim, ⛔ not a dispatch.

  2. objectstack-fleet commented on Oct 13, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1, rolling slot (#23254: a host can leave out plugin-auth's sys_organization default record page, the composition route of ruling A on #23015) · 2026-10-13T14:08Z
    Session: session_012uFegdfR6K6uqvLzt7XsdZ
    Account: os-support-ai (the seat's linked user as GET /user answers it; always the card's assignee)
    Branch: claude/issue-23254-auth-org-page-option
    Worktree: objectstack-issue-23254
    Domain: domain:spec
    Seat: domain:spec#3 (seat post #18883)
    Ruling-ref: ruling A on #23015 (6134056653, batch #331 item 2, maintainer 「同意」), and triage 6146018242. Both read in this act.
    File surface (at origin/main 6c181625f or later; stop on a breach and explain it in the report):

    • packages/plugins/plugin-auth/src/auth-plugin.ts:
      • one declared option on AuthPluginOptions (:151) by which a host says it ships its own sys_organization default record page;
      • the conditional on the page list at :674. With the option set, SysOrganizationDetailPage is not registered. SysUserDetailPage is unchanged.
      • A cross-lane touch, declared here as triage directs.
    • packages/plugins/plugin-auth/src/manifest.ts, only if it declares the same page list.
    • The option's tests in packages/plugins/plugin-auth/, with the three pins triage names:
      • with the option, a host's own default is the single default and the boot succeeds;
      • without it, plugin-auth's page registers as today;
      • with both pages present and no option, DEFAULT_RECORD_PAGE_CONFLICT still refuses.
    • The page that documents AuthPlugin's options (README or content/docs/**).
    • One .changeset/23254-*.md.
    • ⛔ No precedence ladder and ⛔ no registration-order rule (ruling A). ⛔ The default is unchanged. ⛔ No change to the one-default refusal itself (PR feat(spec,objectql,metadata-protocol)!: one default record page per object per deployment, a second refused at os validate, registration and save (#23015) #23206). ⛔ Cloud's half (setting the option, retiring its order-pinning test) is objectstack-ai/cloud#2773's, not this card's.
      Container & model: S (one option, one conditional, three pins, docs), mode:subagent, model: default tier. It widens plugin-auth's published options, so it owes a review at CONTRACT_REVIEW_TIER before the queue, by an isolated at-tier subagent.
      Clause-②: yes (a new declared AuthPluginOptions key; the default is unchanged)
      Responsibility: the option is plugin-auth's, routed to the spec lane by triage under 强制条款② | no host can compose without the page today: overlay-by-name is refused at registration (measured by the cloud seat) | reached by any host that ships its own sys_organization default record page, the cloud control plane first (objectstack-ai/cloud#2709 waits on it)
      Thread-read: 6146018242
      Serial constraints cleared (the file lists of all 6 open PRs and all open pm:dispatched claims, read in this act):
    • No open PR or in-flight claim touches packages/plugins/plugin-auth/**.

    Generated by Claude Code

  3. objectstack-fleet commented on Oct 13, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 23254,
    "status": "done",
    "branch": "claude/issue-23254-auth-org-page-option",
    "pr": "#23309",
    "session": "session_012uFegdfR6K6uqvLzt7XsdZ",
    "premise_still_valid": true,
    "summary": "AuthPluginOptions gains registerOrganizationRecordPage (boolean, @default true): only an explicit false leaves SysOrganizationDetailPage (sys_organization_detail) out of plugin-auth's manifest pages, so a host that ships its own sys_organization default record page is the single default by composition (ruling A on #23015). SysUserDetailPage registers either way, the default is unchanged, and PR #23206's one-default refusal is untouched; with false, init() logs one info line naming the option. Premise verified at 6c18162: auth-plugin.ts registered [SysOrganizationDetailPage, SysUserDetailPage] unconditionally, manifest.ts and identity-objects-plugin.ts declare no page list. H1: a mechanism-named boolean whose true keeps today's behaviour, after registerRoutes, chosen over a host-declaration name (hostOrganizationRecordPage) because it states what the runtime does and the pins prove it; read as !== false so an explicit undefined cannot drop the page. H2: nothing else dangles: no nav item or app names the page (record pages resolve by object + isDefault), the pages.sys_organization_detail.label bundle entries in platform-objects translations are read only when the page is registered, and check-app-nav-i18n / platform-page-i18n-parity / i18n-extract.config / check-i18n-coverage are static over the platform-objects/pages barrel, which still ships the page; objectui 7578323 names it only in useNavigationSync tests. H3: harness = real ObjectKernel + ObjectQLPlugin on in-memory SQLite + AuthPlugin from source + a host plugin registering its page through the manifest service (the identity-objects-plugin.test.ts kernel shape); the refusal is the registry's own.",
    "tests": "All on a shared box (os-verify-lock). (1) New pins src/organization-record-page-option.test.ts: pin 1 option false: host page is the only sys_organization default and the boot succeeds in both plugin orders, sys_user_detail unchanged (control = pin 3); pin 2 no option: sys_organization_detail of com.objectstack.plugin-auth is the default (control: option with no host page leaves zero defaults); pin 3 both pages, no option: refused code DEFAULT_RECORD_PAGE_CONFLICT status 422 with incoming/existing named, in both orders (control: explicit true refused the same). Run at a162f1f: 'Test Files 1 passed (1) Tests 3 passed (3)'; on merged head 3f7b183 with identity-objects-plugin.test.ts: 'Test Files 2 passed (2) Tests 18 passed (18)'. (2) Ablation via scripts/ablation-replace.mjs (wrap mode, under the lock): anchor 'pages: [...(registerOrganizationRecordPage ? [SysOrganizationDetailPage] : []), SysUserDetailPage],' replaced with 'pages: [SysOrganizationDetailPage, SysUserDetailPage],'; on disk anchor x1 -> x0, replacement x0 -> x1, blob 72c5e5482b2a -> 8195f6d48184; result pin 1 RED ('auth first: the boot succeeds: expected DefaultRecordPageConflictError ... to be undefined'), pin 2 control RED ('expected [ {...} ] to deeply equal []'), pin 3 green, 'Tests 2 failed | 1 passed (3)'; restore: blob after restore 72c5e5482b2a == blob at HEAD, git diff HEAD empty, git status clean. Subject imported from source (relative), so no dist leg. (3) @objectstack/plugin-auth full suite (vitest run --maxWorkers=2): at 859822b and again on merged head 3f7b183 'Test Files 138 passed (138) Tests 2794 passed | 10 skipped (2804)', SUITE_EXIT=0. (4) pnpm --filter @objectstack/plugin-auth typecheck on 3f7b183: TC_EXIT=0, 'check:test-typecheck: OK ... 94 error(s) / 23 pinned signature(s) held' (new test file adds none). (5) pnpm --filter @objectstack/spec test:repo at 859822b: 'Test Files 58 passed (58) Tests 1007 passed (1007)' (VERDICT command-exit 0). (6) Derived gates: see gates.",
    "gates": "dispatch-gates --commands --repo objectstack-ai/objectstack on merged head 3f7b183 (origin/main 543b33d merged via scripts/pm/os-regen-merge.sh, merge commit): 68 commands derived from 4 paths, all 68 run with exit captured before any pipe: 68 x exit 0. --ran reconciliation: '68 derived, 68 run, 0 NOT-MEASURED, 0 UNRUN'. Pre-merge run at 859822b: 67 x 0 and check:dual-build-cjs-loads exit 3 (PREREQUISITE NOT MET, other packages had no dist), not counted; after a full turbo build (74/74 tasks) it reads '108 published require entry point(s) across 67 package(s) load' on 3f7b183. Key verdict lines: check-adr-0087-registration 'this PR adds no declared-breaking changeset (1 non-breaking changeset(s) seen)'; check-empty-changeset '1 declaring changeset(s) added'; check:type-check-debt 'none above its recorded number'; check:cross-package-test-inputs OK; check:test-source-alias exit 0 (plugin-auth's unaliased set unchanged). NOT MEASURED: the 6 workflow-valued families (check-shard-attestation x3, check-test-completeness x2, check-issue-citations --census), whose argv only exists in a CI run, and the CI job lanes; those are CI's. CI on PR #23309: not read (report delivered at local verification).",
    "line_budget": "4 files, +306 / -1 (dispatch-gates: 307 changed lines, under the 3000 human-merge threshold). auth-plugin.ts +47/-1, organization-record-page-option.test.ts +218, README.md +27, changeset +14. No skills/** or .claude/** paths, so no line ratchet applies and the PR is not governed.",
    "files_changed": [
    ".changeset/23254-auth-org-record-page-option.md",
    "packages/plugins/plugin-auth/README.md",
    "packages/plugins/plugin-auth/src/auth-plugin.ts",
    "packages/plugins/plugin-auth/src/organization-record-page-option.test.ts"
    ],
    "deviations": [
    "File surface: inside the claim. manifest.ts not touched (it declares no page list). The docs page is the package README (Configuration section) rather than content/docs: it is the page that lists AuthPlugin's options and ships in the tarball.",
    "auth-plugin.ts carries one info log line at init when the option is false (AGENTS.md route and surface rule 3: absence must be loud). Not pinned (prose).",
    "spec test:repo measured at 859822b (pre-merge) and not re-run on 3f7b183: 28 min on the shared box, and the diff touches nothing under packages/spec; declared narrowing, CI runs it.",
    "Changeset: '@objectstack/plugin-auth': minor with 'Clause-②: yes (widening)' (the fixed spelling); the PR body line 2 is the claim's Clause-② line verbatim, as dispatched.",
    "Labels: zero writes. The dispatch named none, and skip-changeset does not apply (the package publishes).",
    "Attribution: commits end with the model-free AGENTS.md trailer pair (Claude-Session + Co-authored-by: Claude), and the PR body ends with the AGENTS.md session-URL footer, not the harness reminder's model-named Co-Authored-By trailer or its two-line footer, because AGENTS.md takes precedence and pre-push refuses a model identifier in the pair.",
    "Worktree ../objectstack-issue-23254 removed after the PR opened (clean, fully pushed at 3f7b183)."
    ],
    "mcp_calls": "0 — no MCP GitHub tools used (reads were gh api GETs).",
    "api_writes": "2 relay dispatches (POST /repos/objectstack-ai/objectstack/dispatches via scripts/pm/with-fleet.sh --via dispatch), executed as objectstack-fleet[bot]: (1) pr_create = POST /repos/objectstack-ai/objectstack/pulls (draft) + POST /repos//issues/23309/assignees [os-support-ai], read back 8467 bytes sent = stored; (2) this os-dev-report comment = POST /repos//issues/23254/comments. Plus git push (not REST): 5 pushes of the branch (empty probe, 3 work commits, merge commit).",
    "open_questions": [],
    "out_of_scope_findings": [
    "carrier: none (承接者:无) · noted, not filed · DefaultRecordPageConflictError's message names the remedy as 'compose the deployment without the package that ships the other'; for sys_organization the narrower remedy is now registerOrganizationRecordPage: false. Left unchanged on purpose (dispatch: no change to PR #23206's refusal); in PR #23309 Acceptance notes."
    ]
    }


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 13, 2026

    @objectstack-fleet
    ContributorAuthor

    ✅ ACCEPT (seat review): PR #23309 at 3f7b18399. The contract-tier review is owed before the queue · domain:spec seat 3 (seat post #18883) · session_012uFegdfR6K6uqvLzt7XsdZ · 2026-10-13T17:24Z

    PR shape. Draft against main. Line 1 is Fixes #23254; line 2 is the claim's Clause-②: line, verbatim. 4 files, +306 / −1. It touches no governed path and sits far under the human-merge threshold. main (543b33dee) was merged in with a merge commit.

    The change, read in the diff.

    • The option. AuthPluginOptions.registerOrganizationRecordPage?: boolean, @default true.
      • It is read as !== false, so only an explicit false leaves out SysOrganizationDetailPage, and an explicit undefined keeps the page.
      • SysUserDetailPage registers either way.
      • Its docblock names what false removes and what stays, and what a host without its own page then sees: the synthesized default layout, without the Members / Invitations / Teams tabs.
    • Within ruling A (6134056653): the option uses composition only. It adds no precedence ladder and no registration-order rule, and PR feat(spec,objectql,metadata-protocol)!: one default record page per object per deployment, a second refused at os validate, registration and save (#23015) #23206's one-default refusal is untouched.
    • The info line. With false, init() logs one info line naming the option. That is the loud-absence rule; it is not pinned, because it is prose.

    Triage's three pins (6146018242), each with a control, in a real ObjectKernel with ObjectQLPlugin, run in both plugin orders:

    1. With false, the host's page is the only sys_organization default and the boot succeeds. Its control is pin 3.
    2. With no option, plugin-auth's page is the default. Its control: false with no host page gives zero defaults.
    3. With both pages and no option, the boot is refused with DEFAULT_RECORD_PAGE_CONFLICT (422), naming both holders. Its control: an explicit true is refused the same way.

    The ablation. Unconditional registration turns pins 1 and 2 red. The file was restored to blob == HEAD.

    The changeset and docs.

    • The changeset is '@objectstack/plugin-auth': minor, Clause-②: yes (widening), and states the adoption in one line.
    • The option is documented in the package README's Configuration section, which ships in the tarball. That is the page that lists AuthPlugin's options.

    H2, nothing dangles. No nav item or app names the page: record pages resolve by object plus isDefault. The pages.sys_organization_detail.label translations are read only when the page is registered.

    Measurements.

    • On the merged head:
      • plugin-auth: 138 files and 2794 tests passed (10 skipped); typecheck passed, with the ledger unchanged.
      • 68 derived gates, each exit 0.
      • check:dual-build-cjs-loads: 108 entry points load, after a full build.
    • Before the merge: spec test:repo, 58 files and 1007 tests passed. It was not re-run after the merge, because the diff touches nothing under packages/spec, and CI runs it.

    The out-of-scope note. DefaultRecordPageConflictError's remedy text names the generic remedy ("compose the deployment without the package that ships the other"). It was left unchanged on purpose, and it stays in the PR's Acceptance notes.

    Landing to-do, recorded now:

    1. needs:contract-review goes on the PR in this act. The review runs on 3f7b18399: Clause-②: yes, an added published option.
    2. When the review is PASS and every check is green: ready, then auto-merge, then the queue, confirmed by added_to_merge_queue.
    3. When it merges: a landing record here, and pm:dispatched comes off. Cloud then sets the option under objectstack-ai/cloud#2773.

    Generated by Claude Code

  5. objectstack-fleet commented on Oct 13, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed: PR #23309 → 404fc477e (Fixes #23254). registerOrganizationRecordPage: false lets a host that ships its own sys_organization default record page be the single default

    domain:spec seat 3 (#18883) · os-support-ai · session session_012uFegdfR6K6uqvLzt7XsdZ · 2026-10-13T18:10Z · holder of claim 6147450905.

    • Path:

      • ACCEPT 6151254191.
      • The contract-tier review PASSed on 3f7b18399 (6151463116).
      • Every check was green or an expected skip (11 of 11 in the roster), with mergeable: clean. check-governed-merges reported it NOT governed, at 307 lines.
      • Ready and auto-merge went on at 2026-10-13T17:43Z through the relay. The PR entered the merge queue at 2026-10-13T17:47Z, and the queue merged it at 2026-10-13T18:09Z.
    • On main at 404fc477e: the squash is the PR's 4 files, +306 / −1. Each blob equals the reviewed head's, and the patch-id --stable equals that of 543b33dee..3f7b18399: byte-identical to the reviewed head.

    • Closing keywords: this card closed as completed, and the PR names no other issue. pm:dispatched comes off in this act.

    • Unblocked: objectstack-ai/cloud#2773. Cloud sets registerOrganizationRecordPage: false beside its own cloud_organization_detail and retires its order-pinning test, then takes the pin bump that objectstack-ai/cloud#2709 waits on.

    • Carried: cli/plugin-dev: an app under os serve cannot leave out plugin-auth's sys_organization default record page, and DEFAULT_RECORD_PAGE_CONFLICT's remedy names a step that holder cannot take #23310, filed from review flags 2 and 3. It covers two gaps:

      • os serve and plugin-dev's DevPlugin do not forward the option, so an app under os serve that ships its own sys_organization default page is still refused;
      • DEFAULT_RECORD_PAGE_CONFLICT's remedy clause names a step this holder cannot take.

      Neither touches cloud's p1 chain.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:recordsBusiness objects, records, the views that show data, usable forms, searchbugSomething isn't workingdomain:specpriority: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