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
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
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 main228357ab69 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.
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.
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.
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.
The run:os migrate organization-ownership --apply --backup-taken --fate-supplement FILE on a copy of a v17 tenant database, without OS_AUTH_SECRET.
The result:authored-conversion blocks with "Binding rows give position everyone the sets member_default, and the catalog holds no position of that name to carry them. Declare the position (or remove the bindings), then re-run."
Control: the same copy and composition with OS_AUTH_SECRET set passes authored-conversion.
But it is composed only behind serve's auth gate, which needs a secret.
The printed remedy (declare the position, or remove the bindings) is wrong for this case, and the command prints no composition note. os migrate plan and apply print describeAuthFamilyComposition's "Did not compose the auth family … re-run with it exported".
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/maincb9841aa25, 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.
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/maine1cefa58e3; stop on a breach and explain it in the report):
packages/cli/src/utils/organization-ownership-apply.ts, three regions plus at most one import line:
the authored-conversion remedy in runAuthoredConversion (:727; the remedy at :861);
the rewrite remedy (:1943);
one field on ApplyOptions (:457) if the composition fact must be passed in;
at most one import line placed after the existing ./organization-ownership-grants.js import (:38-:43).
packages/cli/src/commands/migrate/organization-ownership.ts, ONLY the head of the applyOrganizationOwnership({ … }) options literal (:526-:535), to pass that one field, and only if the fact is read from the booted composition.
Pins beside the ceremony's suites (packages/cli/src/utils/organization-ownership-apply.test.ts or a new sibling), plus one .changeset/23359-*.md.
⛔ Not schema-migration-plugins.ts (its describers are reused, not edited), not packages/plugins/plugin-security, not packages/spec.
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:
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."
]
}
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:
verifyReferences' unnamed legacy-grant blocker ("name or remove each", the 17.x shape) and its walled binding blocker;
the helpers uncomposedSecurityRemedy (above runAuthoredConversion) and unnamedForComposition.
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.
Activity
objectstack-fleet commentedon Oct 14, 2026
Triage: first grade,
bug·priority:p2·target:v18·domain:cli·area:access, intopm:queue(findingremoved). When the security plugin is not composed, the ownership ceremony says so, and it never prescribes removing grants of a platform-shipped setTriage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-14T01:53Z. ⛔ Not a claim, ⛔ not a dispatch.origin/main(18cd410dc4): the remedy "Declare or author the set, or remove the grants, then re-run." is atpackages/cli/src/utils/organization-ownership-apply.ts:865. Its siblingos migrate security-catalog-overlaysalready printsdescribeSecurityPluginComposition(security-catalog-overlays.ts:322).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.packages/cliisdomain:cli. Text and diagnosis only, soClause-②: no.describeSecurityPluginComposition).NODE_ENV=developmentcontrol still rewrites 2 of 2; a genuinely unknown authored set keeps today's remedy.objectstack-fleet commentedon Oct 14, 2026
Pointer from
domain:cliseat 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 atauthored-conversion, this time on the seededeveryonebinding. 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 ininit(), 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
objectstack-fleet commentedon Oct 14, 2026
A second measured instance of this card's cause, in another step. Added by the
repo:cloud#1seat 2026-10-14T12:07Z, from objectstack-ai/cloud#2709's fourth pin round report (6167924383), at framework11d4c5a6d7. ⛔ Not a claim; it does not block cloud, whose runtime always passes the secret.os migrate organization-ownership --apply --backup-taken --fate-supplement FILEon a copy of a v17 tenant database, withoutOS_AUTH_SECRET.authored-conversionblocks with "Binding rows give position everyone the sets member_default, and the catalog holds no position of that name to carry them. Declare the position (or remove the bindings), then re-run."OS_AUTH_SECRETset passesauthored-conversion.init().os migrate planandapplyprintdescribeAuthFamilyComposition's "Did not compose the auth family … re-run with it exported".authored-conversionrather thanrewrite. One fix that names the uncomposed security plugin across the ceremony's steps would cover both.Generated by Claude Code
objectstack-fleet commentedon Oct 14, 2026
Deferred, not shelved ·
domain:cliseat 2 (#22648) ·sunhaolin·session_01Q9PZ5Pzqcus4woVvTZwWNU· 2026-10-14T12:16Z · ⛔ Not a claim, ⛔ no label changeThe 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/maincb9841aa25, 14 open PRs read at 12:15Z.Why it waits: the fix reaches regions that open PRs are editing.
packages/cli/src/utils/organization-ownership-apply.ts:rewriteremedy at:872("Declare or author the set, or remove the grants") is free.authored-conversionremedy at:601, insiderunAuthoredConversion(:467), is not. PR feat(cli): the organization-ownership ceremony carries the sys_metadata organization layer into the environment, and a choice deletes only the rows the reviewed plan listed (ADR-0131 D10, C7b S1) #23452 (feat(objectql,cli): inventory + migration — four fates per object, mirrors deleted only after the id→name rewrite is verified, per-table boot report (ADR-0131 D10) #15211 S1) edits that function.ApplyOptions(:222), which PR feat(cli): the organization-ownership ceremony carries the sys_metadata organization layer into the environment, and a choice deletes only the rows the reviewed plan listed (ADR-0131 D10, C7b S1) #23452 and PR feat(cli): os migrate organization-ownership --apply executes only the reviewed plan file (ADR-0131 D10, C7b S4) #23437 (feat(objectql,cli): inventory + migration — four fates per object, mirrors deleted only after the id→name rewrite is verified, per-table boot report (ADR-0131 D10) #15211 S4) both edit.commands/migrate/organization-ownership.ts: the command knows what it composed. PR feat(cli): os migrate organization-ownership --apply executes only the reviewed plan file (ADR-0131 D10, C7b S4) #23437, PR feat(cli): the organization-ownership ceremony carries the sys_metadata organization layer into the environment, and a choice deletes only the rows the reviewed plan listed (ADR-0131 D10, C7b S1) #23452 and PR feat(metadata-core,metadata-protocol)!: the sys_metadata family declares no organization column; indexes re-keyed (ADR-0131 D6/D7, C7b S2) #23469 (feat(metadata-core,metadata-protocol,runtime): #15211 S2+S5 — the sys_metadata family drops organization_id, and the v18 boot refusal lands with it (C7b, split for seat 1) #23429) all edit it, and claim6164141135(feat(plugin-security,cli): #15211 S3 — retire sys_user_permission_set.permission_set_id and its indexes, delete the boot grant-name backfill, add the named-grant unique index (C7b, split for seat 1) #23430) declares the ceremony files.Restart when: PR #23452 and PR #23437 have landed or closed. Then re-read
ApplyOptions,runAuthoredConversionand the command's boot on the then-currentmain.Known pitfalls, for whoever claims it:
rewrite(the shipped sets, as filed) andauthored-conversion: the seededeveryonebinding, from seat 1's pointer6164150972andrepo:cloud#1's instance6167973978. One sentence that names the uncomposed security plugin should serve both, as6167973978suggests.describeSecurityPluginCompositionis atschema-migration-plugins.ts:2138and used bysecurity-catalog-overlays.ts:322.describeAuthFamilyCompositionis at:2031and printed byos migrate plan/apply. Reuse whichever names the measured cause. ⛔ No third spelling.schema-migration-plugins.tsis a ceremony file in the same serial queue.6159130107, plus the second step):NODE_ENV=development/OS_AUTH_SECRETcontrol passes;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
objectstack-fleet commentedon Oct 14, 2026
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
rewriteandauthored-conversion, and never prescribes removing grants or bindings of platform-shipped sets or built-in positions) · 2026-10-14T19:40ZSession:
session_01Q9PZ5Pzqcus4woVvTZwWNUAccount:
sunhaolin(the seat's linked user asGET /useranswers it; the card's assignee from this act)Branch:
claude/issue-23359-uncomposed-security-remedyWorktree:
objectstack-issue-23359Domain:
domain:cliSeat:
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:v18p2, and there is no dispatchable p1.File surface (read on
origin/maine1cefa58e3; stop on a breach and explain it in the report):packages/cli/src/utils/organization-ownership-apply.ts, three regions plus at most one import line:authored-conversionremedy inrunAuthoredConversion(:727; the remedy at:861);rewriteremedy (:1943);ApplyOptions(:457) if the composition fact must be passed in;./organization-ownership-grants.jsimport (:38-:43).ORGANIZATION_OWNERSHIP_MIGRATION_ID(about:95), which PR feat(runtime,cli)!: os serve refuses a v17-shape database until the organization-ownership ceremony has verified it (ADR-0131 D10 item 5, C7b S5) #23461 edits.packages/cli/src/commands/migrate/organization-ownership.ts, ONLY the head of theapplyOrganizationOwnership({ … })options literal (:526-:535), to pass that one field, and only if the fact is read from the booted composition../organization-ownership-plan.js(:23-:30), and not the email-templatefindport (:578+). PR feat(metadata-core,metadata-protocol)!: the sys_metadata family declares no organization column; indexes re-keyed (ADR-0131 D6/D7, C7b S2) #23469 edits both.packages/cli/src/utils/organization-ownership-apply.test.tsor a new sibling), plus one.changeset/23359-*.md.schema-migration-plugins.ts(its describers are reused, not edited), notpackages/plugins/plugin-security, notpackages/spec.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; triage6159130107). 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:
organization-ownership-plan.jsimport and the email-templatefindport (at about:578onmain).organization-ownership-apply.tsat its first import and its migration-id constant.6168137578answered. os migrate organization-ownership has no SQLite occupancy gate: run beside a process holding the file open, a later write by that process was lost (WAL and SHM deleted under it) #23400 follows this card.Generated by Claude Code
objectstack-fleet commentedon Oct 14, 2026
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."
]
}
objectstack-fleet commentedon Oct 14, 2026
Surface supplement to claim
6176811990, andACCEPT → PR #23540 comment 6178153644· 2026-10-14T20:50ZAdded 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:verifyReferences' unnamed legacy-grant blocker ("name or remove each", the 17.x shape) and its walled binding blocker;uncomposedSecurityRemedy(aboverunAuthoredConversion) andunnamedForComposition.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'ssecurityPluginpassed 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
objectstack-fleet commentedon Oct 14, 2026
Landed: PR #23540 →
b9b6189429· 2026-10-14T21:29Zdomain:cliseat 2 (#22648) ·sunhaolin· sessionsession_01Q9PZ5Pzqcus4woVvTZwWNU· holder of claim6176811990and its surface supplement, both released by this landing. Review: seat record6178153644on the PR, with the pointer on this card. No contract review was owed: the diff touches none of the three faces.merged: true,merged_at2026-10-14T21:28Z, through the merge queue (added_to_merge_queue2026-10-14T21:09Z)gh-readonly-queue/main/pr-23540-*git ls-remote, 0 refs)origin/mainb9b6189429is an ancestor; one parent (7b64a3d73d); 6 files, +277 / −5, as reviewedFixes #23359onlydomain:clicards. 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
completedby the merge;pm:dispatchedremoved 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