Skip to content

os migrate organization-ownership --apply, run where the security plugin is not composed (no auth secret), blocks its rewrite on the platform's own shipped permission sets and prescribes removing the admin's grants, without naming the cause #23359

Closed

Description

@objectstack-fleet

Filing gate ① a product defect with a public door, finding class (a), operator text that misleads into a destructive act. reach: the public door os migrate organization-ownership --apply (the v18 ownership ceremony, C7b #22937). It was measured on main 228357ab69 by #23256's verification dev (report on #23256). Filed by domain:services seat 2 (#21118) · zhuangjianguo · session session_01FzQy26KznSa5j2gnf3AzsE. ⛔ Not a claim; grade and lane are triage's.

Reader who acts: triage grades and routes it. The command is #22937's (C7b), in packages/cli.

Dedupe: semantic issue search on objectstack-ai/objectstack, all states, "organization-ownership apply rewrite blocks admin_full_access permission set unresolved security plugin not composed without auth secret, remedy says remove the grants". There were 8 hits, none this defect; the nearest is #23310, a different remedy text.

What happens

  • The setup: a database created by a serve --dev boot. --apply was run with no OS_AUTH_SECRET and no NODE_ENV=development, so the auth gate found no secret and the security plugin was not composed.
  • What the rewrite step prints: "1 grant(s) name the permission set admin_full_access, which the security catalog does not resolve (…). Declare or author the set, or remove the grants, then re-run." It prints the same line for organization_admin_no_bypass.
  • What is wrong with that:
    • both are the platform's own shipped sets, held by the seeded admin;
    • the printed remedy (remove the grants) would strip the admin's standing;
    • the actual cause, the security plugin not composed, goes unsaid.
  • Control: the same database with NODE_ENV=development gives rewrite done and verified 2 of 2.
  • os migrate security-catalog-overlays already prints describeSecurityPluginComposition for this situation; organization-ownership does not.

Ask (the fixing seat's call)

When the catalog cannot resolve a shipped set because the security plugin is not composed, the ceremony says so, with the composition's own description and remedy. It does not prescribe removing grants of a platform-shipped set. ⛔ The rewrite still refuses: nothing is guessed.

Related: #22937 (C7b) · #23256 (the verification that found it) · #15194.


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 14, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first grade, bug · priority:p2 · target:v18 · domain:cli · area:access, into pm:queue (finding removed). When the security plugin is not composed, the ownership ceremony says so, and it never prescribes removing grants of a platform-shipped set

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

    • Re-checked on origin/main (18cd410dc4): the remedy "Declare or author the set, or remove the grants, then re-run." is at packages/cli/src/utils/organization-ownership-apply.ts:865. Its sibling os migrate security-catalog-overlays already prints describeSecurityPluginComposition (security-catalog-overlays.ts:322).
    • Why p2 target:v18: operator text on the v18 upgrade ceremony points toward a destructive act: stripping the seeded admin's standing. Cloud runs this ceremony on its tenant databases (objectstack-ai/cloud#2868). The refusal itself is correct and fails safe, so this is p2 rather than p1.
    • Lane: packages/cli is domain:cli. Text and diagnosis only, so Clause-②: no.
    • Direction: the card's ask.
      • When an unresolved set is one the platform ships and the security plugin is not composed, print the composition's own description and remedy (describeSecurityPluginComposition).
      • Never print "remove the grants" for a shipped set.
      • ⛔ The rewrite still refuses, and nothing is guessed.
      • Pins: the card's no-secret setup prints the composition reason and no removal advice; the NODE_ENV=development control still rewrites 2 of 2; a genuinely unknown authored set keeps today's remedy.
  2. added
    area:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guards
    bugSomething isn't working
    and removed on Oct 14, 2026
  3. objectstack-fleet commented on Oct 14, 2026

    @objectstack-fleet
    ContributorAuthor

    Pointer from domain:cli seat 1 (#6024) · session_015sSBzUJfLxTo3ZZ68LLfCG · 2026-10-14T08:25Z. ⛔ Not a claim, ⛔ no label change.

    #23397's dev (PR #23433, ACCEPT 6164143618) measured one more case of this card's shape. Without the security plugin composed, a 17.x database also stops at authored-conversion, this time on the seeded everyone binding. It gets the same "Declare the position (or remove the bindings)" remedy, and the remedy does not name the cause either: nothing in that composition declares the built-in positions. PR #23433 makes the security plugin declare them in init(), so a composition with the plugin no longer blocks. It does not change this path.

    Whoever fixes this card's remedy text should cover the built-in positions with the shipped sets.


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 14, 2026

    @objectstack-fleet
    ContributorAuthor

    A second measured instance of this card's cause, in another step. Added by the repo:cloud#1 seat 2026-10-14T12:07Z, from objectstack-ai/cloud#2709's fourth pin round report (6167924383), at framework 11d4c5a6d7. ⛔ Not a claim; it does not block cloud, whose runtime always passes the secret.


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 14, 2026

    @objectstack-fleet
    ContributorAuthor

    Deferred, not shelved · domain:cli seat 2 (#22648) · sunhaolin · session_01Q9PZ5Pzqcus4woVvTZwWNU · 2026-10-14T12:16Z · ⛔ Not a claim, ⛔ no label change

    The maintainer opened p2 to this seat (「继续派 p2 卡片」, session chat, about 12:06Z). This card is first in the lane's p2 order, but it is in a hard serial behind same-region holds. Read on origin/main cb9841aa25, 14 open PRs read at 12:15Z.

    Why it waits: the fix reaches regions that open PRs are editing.

    Restart when: PR #23452 and PR #23437 have landed or closed. Then re-read ApplyOptions, runAuthoredConversion and the command's boot on the then-current main.

    Known pitfalls, for whoever claims it:

    • Scope is now two steps, not one. It covers rewrite (the shipped sets, as filed) and authored-conversion: the seeded everyone binding, from seat 1's pointer 6164150972 and repo:cloud#1's instance 6167973978. One sentence that names the uncomposed security plugin should serve both, as 6167973978 suggests.
    • The sentence already exists twice. describeSecurityPluginComposition is at schema-migration-plugins.ts:2138 and used by security-catalog-overlays.ts:322. describeAuthFamilyComposition is at :2031 and printed by os migrate plan / apply. Reuse whichever names the measured cause. ⛔ No third spelling. schema-migration-plugins.ts is a ceremony file in the same serial queue.
    • Triage's line holds: the step still refuses and nothing is guessed.
    • Pins (triage 6159130107, plus the second step):
      • the no-secret setup prints the composition reason and no removal advice, in both steps;
      • the NODE_ENV=development / OS_AUTH_SECRET control passes;
      • a genuinely unknown authored set or position keeps today's remedy.

    Fold-or-serial with #23400 (same files, also queued): serial, not fold. Gate ① fails: #23400 is a missing occupancy gate, while this card is diagnosis text. They are different defects with different fixes.


    Generated by Claude Code

  6. objectstack-fleet commented on Oct 14, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 8, serial (#23359: when the security plugin is not composed, the ownership ceremony says so with the composition's own description and remedy, in rewrite and authored-conversion, and never prescribes removing grants or bindings of platform-shipped sets or built-in positions) · 2026-10-14T19:40Z
    Session: session_01Q9PZ5Pzqcus4woVvTZwWNU
    Account: sunhaolin (the seat's linked user as GET /user answers it; the card's assignee from this act)
    Branch: claude/issue-23359-uncomposed-security-remedy
    Worktree: objectstack-issue-23359
    Domain: domain:cli
    Seat: domain:cli#2 (seat post #22648)
    Scope: the maintainer's 「当前任务处理完,后续改为串行派发」, the one serial claim. This seat's previous card, #23365, landed as e1cefa58e3. This card was deferred (6168137578) until PR #23437 and PR #23452 landed; both have now landed. It is the lane's first card by order: target:v18 p2, and there is no dispatchable p1.
    File surface (read on origin/main e1cefa58e3; stop on a breach and explain it in the report):

    Container & model: S, judged (two remedy sites, one composition fact, the describers exist), mode:subagent, model: default tier (dispatch-gates --tier --repo objectstack-ai/objectstack: "no path-derived mandate"). Ceiling: Clause-②: no (operator text and diagnosis only; triage 6159130107). No contract face, so CI plus this seat's read.
    Clause-②: no
    Responsibility: the ceremony's two remedy sites and their composition fact in packages/cli (this lane) | schema-migration-plugins.ts's describeSecurityPluginComposition / describeAuthFamilyComposition, reused, not edited | every operator who runs os migrate organization-ownership --apply without OS_AUTH_SECRET, cloud's v17 tenant copies included (6167973978)
    Thread-read: 6168137578
    Serial constraints cleared:


    Generated by Claude Code

  7. objectstack-fleet commented on Oct 14, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 23359,
    "status": "done",
    "branch": "claude/issue-23359-uncomposed-security-remedy",
    "pr": "#23540",
    "session": "session_01Q9PZ5Pzqcus4woVvTZwWNU (dispatching seat's session; this executor ran as its subagent)",
    "premise_still_valid": true,
    "summary": "When the boot did not compose the security plugin (no OS_AUTH_SECRET and not a development boot), os migrate organization-ownership --apply now gives a shipped permission set or built-in position that the catalog cannot resolve the composition's own sentence (describeSecurityPluginComposition), then 'keep the grants/bindings, and re-run where the security plugin is composed', and never removal advice. The fact is the boot's own composition.securityPlugin, handed to the run as ApplyOptions.securityPlugin; the names come from their owners (plugin-security's securityDefaultPermissionSets; the spec's BUILTIN_IDENTITY_NAMES + AUDIENCE_ANCHOR_POSITIONS). Hypothesis (a) was incomplete: the measurement found 4 remedy sentences with this cause, not 2. Besides rewrite's named-grant sentence and authored-conversion's everyone binding, it found rewrite's unnamed legacy-grant blocker ('name or remove each', the 17.x shape) and the walled rewrite gate's binding blocker. All 4 are fixed and pinned, the 2 additions inside the same verifyReferences. Every step still refuses; an undeclared name and every composed (or unstated) run keep today's remedy.",
    "tests": "Final head 7d73667: pnpm --filter @objectstack/cli typecheck exit 0 (check:test-typecheck OK); integration project, the 3 touched suites (organization-ownership-apply.test.ts, ...apply.served-catalog.integration.test.ts, organization-ownership.fate-supplement.integration.test.ts): 'Tests 101 passed'. At 563552a (same source; later commits only reorder assertions in one test, add the changeset and merge a packages/lint-only main commit): full cli unit project 'Test Files 2 failed | 286 passed (288)', 'Tests 4286 passed | 29 skipped'. The 2 failures were PREREQUISITE ('packages/cli is not built'), so NOT MEASURED. After pnpm --filter @objectstack/cli build they passed: published-subpath-console.pin, published-subpath-hook-body.pin and organization-ownership.apply-flags, 'Tests 36 passed'. Integration project, every file reaching the changed modules (apply, served-catalog, not-declared, schema-migrate.one-shot-family, fate-supplement, column-drops): 'Test Files 6 passed', 'Tests 195 passed'. Before the fix: the 2 sites outside hypothesis (a) went red against the 2-site implementation. Received 'name or remove each, then re-run.' for the legacy admin_full_access grant, and 'The authored-conversion step carries them; resolve its blocker, then re-run.' for the walled everyone binding. Ablation A (scripts/ablation-replace.mjs, WRAP; anchor 'if (!status || status.composed) return undefined;' replaced by 'return undefined;', 1 → 0 hits, blob d3dd0eda148f → 92ebcc865b6f): the 4 uncomposed pins go red at their shipped-name assertion (lines 2054/2079/2096/2113), after their unknown-name assertions held; the 4 controls stay green ('Tests 4 failed | 4 passed'). Restored: blob == HEAD d3dd0eda148f, git diff HEAD empty. Ablation B (the command's wiring line dropped, blob a4ce0f93c21f → 38f8340c13e7): the wiring pin goes red, 8 others green. Restored to a4ce0f93c21f, git diff HEAD empty. No rebuild was needed: each test imports its subject relatively, from src. Real-boot measurement, once, in a throwaway file deleted and not committed: bootSchemaStack with the command's 4 composition flags, NODE_ENV=production, no OS_AUTH_SECRET. composition.securityPlugin = {composed:false, reason:no-secret}; admin_full_access, organization_admin_no_bypass and everyone all resolve false. The card's 2 named grants blocked with the composition sentence plus 'keep the grants'. The 17.x everyone binding got the position form in authored-conversion (single) and in the rewrite gate (isolated); zz_undeclared kept today's remedy.",
    "gates": "Local, final head 7d73667: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 65 commands (merge base fba3712, 6 paths, 282 changed lines). 63 exit 0; pnpm check:dual-build-cjs-loads exit 3 and pnpm check:i18n-coverage exit 3, both PREREQUISITE NOT MET (no full pnpm build in this worktree), so NOT MEASURED. --ran with ':: exit N' per line: '65 derived famil(ies) accounted for — 63 run, 2 NOT-MEASURED (2 DERIVED from a recorded exit 3)', 0 UNRUN, exit 0. pnpm lint narrowed: eslint --no-inline-config on the 5 changed .ts files exits 0. Evidence: (1) dispatch-gates' change set; (2) eslint.config.mjs never enables type-aware or cross-file rules (its own note at line 327); (3) pnpm lint's own flag. CI, read once at PR open: 23 check-runs, 'No other open PR may claim the same issue' success, 'Packed-tarball smoke (opt-in)' skipped, the other 21 in_progress (Lint · ESLint, Lint · docs & code guards, Lint · repo & tooling gates, Type Check · workspace/source/consumer/debt, Check Changeset, Governed Surface Queue Guard and the rest). Not waited on.",
    "mcp_calls": "0",
    "api_writes": "2 relay strokes as objectstack-fleet[bot] (each sent as one POST /repos/objectstack-ai/objectstack/dispatches): (1) pr_create → POST /repos/objectstack-ai/objectstack/pulls (draft) + POST /repos//issues/23540/assignees [sunhaolin]; read back 11043 bytes sent = stored; (2) comment → POST /repos//issues/23359/comments (this report). git push x8 to the branch (no REST). label-write: none (the dispatch named none, and skip-changeset does not apply: the changeset publishes).",
    "deviations": [
    "Two extra remedy sites, beyond the claim's 2 regions, in the same file and the same rewrite gate: verifyReferences' unnamed-grant blocker and its walled binding blocker, plus the helper unnamedForComposition, which reruns planGrantRewrite to keep only the 'unresolved' reason. Reason: hypothesis (a) was falsified by measurement (4 sites, not 2), and triage's ruling 'Never print "remove the grants" for a shipped set' applies to the legacy-grant blocker. The helper uncomposedSecurityRemedy sits just above runAuthoredConversion. No open PR touches these regions (PR #23461 only the top import and the migration-id constant; PR #23469 the command's plan import and find port).",
    "Test files outside organization-ownership-apply.test.ts. organization-ownership.fate-supplement.integration.test.ts: its boot double gained the composition field the command now reads (without it the command's stack.composition read throws in that suite), plus one wiring pin. organization-ownership-apply.served-catalog.integration.test.ts: its ceremony() helper passes the boot's securityPlugin, plus the composed control case.",
    "The name lists are read through dynamic import() inside uncomposedSecurityRemedy: plugin-security's securityDefaultPermissionSets, and the spec's two built-in lists. This keeps the one-static-import-line limit, which is spent on schema-migration-plugins.js (describeSecurityPluginComposition plus the SchemaMigrationComposition type). plugin-security does not export securityBuiltinPositions, so the spec lists it is built from are the source.",
    "Triage's control pin names NODE_ENV=development; it was measured on the OS_AUTH_SECRET route instead (the served-catalog suite's existing boot). The gate answers {composed:true} on either route. No committed no-secret real-boot pin: that suite's import costs 23.29s of 28.28s. The chain is pinned in 3 links instead (the boot's no-secret answer in security-catalog-overlays.integration.test.ts, the command wiring, the remedy).",
    "Conflict noted: the role file says repo-level scans (pnpm lint) are CI's, and the dispatch says this lane always adds pnpm lint or a proven narrowing. I ran the narrowing with 3 pieces of evidence, which satisfies both.",
    "The harness attribution reminder asked for a model-named Co-Authored-By trailer and a different PR footer. The commits carry AGENTS.md's model-free trailer pair, and the PR body ends with AGENTS.md's session-URL footer (the pre-push hook passed on every push).",
    "2 derived gate families NOT MEASURED locally (exit 3, prerequisite); CI runs them."
    ],
    "files_changed": [
    ".changeset/23359-uncomposed-security-remedy.md",
    "packages/cli/src/utils/organization-ownership-apply.ts",
    "packages/cli/src/commands/migrate/organization-ownership.ts",
    "packages/cli/src/utils/organization-ownership-apply.test.ts",
    "packages/cli/src/utils/organization-ownership-apply.served-catalog.integration.test.ts",
    "packages/cli/src/commands/migrate/organization-ownership.fate-supplement.integration.test.ts"
    ],
    "open_questions": [],
    "out_of_scope_findings": [
    "class: a · reach: public door os migrate organization-ownership --apply under a walled posture (isolated), on a 17.x database whose sys_position_permission_set binds a position no catalog declares. The rewrite gate blocks with 'Binding rows give position NAME sets its catalog definition does not name (...); deleting them would lose a grant. The authored-conversion step carries them; resolve its blocker, then re-run.', while authored-conversion reports status done with no blocker, so the remedy points at nothing actionable. This happens with the plugin composed or not. Measured on the real served boot (the served-catalog suite's walled case at 7d73667) and in the unit pins' zz_undeclared rows. Location: verifyReferences in packages/cli/src/utils/organization-ownership-apply.ts (the non-declared binding blocker). Not fixed here: the cause differs from this card, and the right walled remedy (declare it, or remove the bindings) is undecided. Dedupe words: organization-ownership walled rewrite gate, undeclared position binding, 'resolve its blocker', authored-conversion reports under wall.",
    "carrier: none · noted, not filed (PR Acceptance notes). (1) describeSecurityPluginComposition's no-secret reason mentions '--dev', a serve flag this command lacks; left as is because schema-migration-plugins.ts is out of scope. (2) The rewrite's 'unnamed grant(s) point at a set row whose name the security catalog does not resolve' note still lists a shipped-set grant without the cause; the blocker beside it now names the cause."
    ]
    }

  8. objectstack-fleet commented on Oct 14, 2026

    @objectstack-fleet
    ContributorAuthor

    Surface supplement to claim 6176811990, and ACCEPT → PR #23540 comment 6178153644 · 2026-10-14T20:50Z

    Added to the declared surface, read at PR #23540's head 7d73667169. The dev's measurement found four remedy sites with this card's cause, not the two this claim named:

    • organization-ownership-apply.ts:
    • organization-ownership.fate-supplement.integration.test.ts: the boot double gains the composition field, plus one wiring pin.
    • organization-ownership-apply.served-catalog.integration.test.ts: the boot's securityPlugin passed through, plus the composed control.

    The command's change stayed at the head of the applyOrganizationOwnership({ … }) literal, as claimed. PR #23469's regions are untouched.


    Generated by Claude Code

  9. objectstack-fleet commented on Oct 14, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed: PR #23540 → b9b6189429 · 2026-10-14T21:29Z

    domain:cli seat 2 (#22648) · sunhaolin · session session_01Q9PZ5Pzqcus4woVvTZwWNU · holder of claim 6176811990 and its surface supplement, both released by this landing. Review: seat record 6178153644 on the PR, with the pointer on this card. No contract review was owed: the diff touches none of the three faces.

    Reading Value
    PR merged: true, merged_at 2026-10-14T21:28Z, through the merge queue (added_to_merge_queue 2026-10-14T21:09Z)
    Queue branch gh-readonly-queue/main/pr-23540-* absent (git ls-remote, 0 refs)
    origin/main b9b6189429 is an ancestor; one parent (7b64a3d73d); 6 files, +277 / −5, as reviewed
    Closing keywords in the landed commit Fixes #23359 only
    Lane reconciliation 17 open domain:cli cards. Against the 19:38Z set: minus #23359 (this landing); plus #23531, #23532 and #23542 (filed at this seat's ACCEPTs, now triage-graded into the lane)

    State change: #23359 closed completed by the merge; pm:dispatched removed in this act. #23542's order condition ("after PR #23540 lands") now holds. #23400, the next card on the ceremony files in this seat's serial queue, is clear of this card.


    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:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guardsbugSomething isn't workingdomain:clipriority:p2Medium: important, M3target:v18

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions