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
feat(cli): the ownership ceremony's fate for walled organization-less self-registration grants (sys_user_permission_set) — attribute through the user's first membership, or record that ruling C's manual fix governs #25206
Filing gate ③: owed item A3 of #24343, carried to 18.x (6260319465). Amendment 6258289325 gave it to 8b-0de "if owed", and 8b-0f's report said it was owed. The epic seat's reconciliation at main2919ddb875 found it still open.
packages/cli/src/utils/organization-ownership-inventory.ts:652 gives sys_user_permission_set the fate attribution with anchors: [].
Under single, D10 gives such a row the Default Organization.
Under a wall, every organization-less self-registration grant is therefore unattributable. Under ruling C, it is reported and blocks certification until it is fixed by hand.
Measure: on a walled 17.x or pre-release store, how many such grants does a store hold, and does each user have a membership that names one organization?
Then either:
A: add an anchor, the user's first sys_member row's organization, when that is unambiguous; a user in several organizations stays unattributable and is reported;
Claim: epic seat round · 2026-10-22T16:29Z · item A3 of #24343, carried to 18.x (6260319465), from the reconciliation note 6339774838
Session: session_01Y5583bs2RDd53MH6kM1ca3
Account: os-steve
Branch: claude/issue-25206-walled-self-registration-grant-fate
Worktree: the cloud session's own checkout (mode:cloud)
Domain: domain:cli
Seat: epic:#15194
File surface:
Measure first, and report before building beyond a probe.
If A (an anchor through the user's sole membership): packages/cli/src/utils/organization-ownership-inventory.ts (the sys_user_permission_set row's anchors), the plan and apply code that reads anchors, if it must learn the anchor kind, their tests, and one .changeset/*.md. The PR takes needs:upgrade-harness.
Measurement · os-dev for the epic seat (session_01CpKhP8iqtGJKZHZ5PhdNq2, dispatched by session_01Y5583bs2RDd53MH6kM1ca3) · 2026-10-22T17:13Z · branch claude/issue-25206-walled-self-registration-grant-fate. Step 1 of the claim 6345859138. Readings are taken against main at 6bc8cc6a.
1. What the harness fixtures hold
fixture
posture it was born under
sys_user_permission_set rows
organization-less
sys_member rows
organizations
fixture-18.0 (ref 5b14b7f3ad8e)
single
2
0
3
1 (Default)
fixture-18.0.0
single
2
0
3
1 (Default)
Neither fixture is walled, and both were generated after #24453 (95246668d). The harness has no walled store and no v17 walled store, so the population cannot be counted from it.
2. The walled store used instead (hand-made, described exactly)
I took a copy of fixture-18.0's bytes. Under isolated and group I added the walled trait, then booted HEAD over it (refused by the ownership guard), then ran the real os migrate organization-ownership plan and --apply --plan-file … --backup-taken from examples/app-showcase, with OS_TENANCY_POSTURE set.
The legacy row shape:settleSelfRegistrationGrant at 69f342b924 (the parent of 95246668d), auth-manager.ts:5699, wrote { id, user_id, permission_set } and no organization_id, because under a wall derivableWriteOrganizationId() answers nothing. Before fix(plugin-auth): the self-registration grant settles on the registrant's first membership (ADR-0131 D3, C8 8b-0f) #24453 that was one row per walled self-registrant, written at sign-up, before any membership. The runtime's dedupe meant no second one.
The trait:
a second organization, org_beta;
four self-registrants, each holding one NULL member_default grant;
one: 1 membership (org_beta);
two: 2 memberships (Default and org_beta);
none: 0 memberships;
dup: 1 membership (org_beta). An administrator also granted member_default to dup in org_beta.
One unrelated walled blocker is resolved as its remedy says: the fixture's customized copy of the declared webhook is renamed. Without that, declared-webhooks blocks every walled run, including the baseline.
Baseline: the same store and trait without the four NULL grants.
user
memberships
distinct organizations
one
1
1
two
2
2
none
0
0
dup
1
1 (and the set is already held there)
3. What today's ceremony does (main6bc8cc6a)
run
plan exit
sys_user_permission_set in the plan
apply exit
verdict
isolated + trait
0
0 attributed, 4 unattributable, each "no anchor names the owner"
So under a wall, every legacy self-registration grant blocks certification, including the grant of a user whose only membership names exactly one organization. The operator has to write each one's organization by hand: ruling C, as organization-ownership-apply.ts states it, says "assign it an organization, or delete it by hand".
4. The same store with an anchor through the sole membership (the branch's WIP, 05b4e397)
user usr_selfreg_two is a member of 2 organizations, so the grant's is not derivable
user usr_selfreg_none is a member of no organization
user usr_selfreg_dup's sole membership names org_beta, where row ups_usr_selfreg_dup_admin already holds this key
The dup case matters for safety. The grant table carries a NULL-safe unique index on (user_id, permission_set, organization). Attributing that row would make the apply's UPDATE throw and stop the ceremony, so the anchor reports it instead.
a user in several organizations: which membership came first is not recorded, so the row is never guessed;
a user in none;
a user whose key is already held.
Each is reported with its reason and its remedy, and none is deleted.
Shape (within the claim's File surface): a platform-only inventory key, soleMembership: { userKey, keyColumns }, on the sys_user_permission_set row. It follows the precedent of underivableDeparture. It is read only under a wall and tried after the anchors.
It is not a spec anchor kind, because a host's objects have no membership semantics. So no packages/spec byte moves, and Metadata-protocol: none is expected.
No apply change is needed: the apply writes the plan's attributions.
Not pursued: the 8b-0f report's alternative, deleting the grant of a user with no membership. That is a deletion outside fate 2, so it would need its own ruling. It stays reported.
How large is the population on a real deployment? I cannot measure that here. It is one row per walled self-registrant admitted before 95246668d (2026-10-19). Cloud's control plane is the walled deployment on file (cloud#3051, 6258022252). There, that grant duplicates the isDefault baseline, so attributing it changes no one's access.
I proceed with A on the branch: pins, ablation, changeset and proofs. The draft PR follows.
Landed · epic PM session_01Y5583bs2RDd53MH6kM1ca3 (os-steve) · 2026-10-22T20:10Z. PR #25272 merged into main as 1eb1afae07. It closes this card: #24343's A3, recommendation A of the measurement 6346714622.
What changed: under a wall, the ownership ceremony gives an organization-less self-registration grant (sys_user_permission_set) the organization of its user's sole membership.
Three cases are reported, never guessed: a user in several organizations, a user in none, and a key the grant's unique index would refuse.
single is unchanged.
Acceptance: seat read plus CI, since no contract face is touched (ACCEPT 6349853985). The labelled upgrade-harness lane passed on the head. R1 and R4 were measured by the dev, including the hand-fix path to a certified walled boot.
Activity
objectstack-fleet commentedon Oct 22, 2026
Claim: epic seat round · 2026-10-22T16:29Z · item A3 of #24343, carried to 18.x (6260319465), from the reconciliation note 6339774838
Session:
session_01Y5583bs2RDd53MH6kM1ca3Account:
os-steveBranch:
claude/issue-25206-walled-self-registration-grant-fateWorktree: the cloud session's own checkout (
mode:cloud)Domain:
domain:cliSeat:
epic:#15194File surface:
packages/cli/src/utils/organization-ownership-inventory.ts(thesys_user_permission_setrow's anchors), the plan and apply code that reads anchors, if it must learn the anchor kind, their tests, and one.changeset/*.md. The PR takesneeds:upgrade-harness.docs/adr/**.ORGANIZATION_OWNERSHIP_SHAPE_GENERATIONchange.organization-ownership-apply.test.tsandorganization-ownership-plan.ts. Keep your edits there minimal, and mergemainonce it lands.Container & model:
M,mode:cloud,model: TIER_DEFAULT. The review is at the tier the diff's declared face picks.Clause-②: no — expected: a new attribution anchor widens what the ceremony certifies and moves no accepted input; the dev re-declares from the diff
Responsibility: under a wall, every organization-less self-registration grant (
sys_user_permission_set, inventory anchors[]) is unattributable and blocks certification until it is fixed by hand | none | reached by every walled 17.x or pre-release store with self-registered users at its ceremonyThread-read: none
Serial constraints cleared: fix(organizations)!: retire claimOrphanOrgRows — it gives organization-less rows to the first organization, an owner by guess that ADR-0131 D9 and D10 forbid (measured 6342754850) #25204 landed as
e3190b9494and fix(service-settings): the tenant and user rungs stop reading organization-less sys_setting rows as every organization's fallback (ADR-0131 D8) — measure first #25202 as279fdcf469. fix(core,plugin-security): ADR-0131 8c-2 review carriers — guard a sys_organization insert that moves the platform-admin anchor, pin the ceremony's NULL admin_full_access attribution, one Default Organization definition #25208 is the one other session in flight; the ceremony-file overlap is noted above.objectstack-fleet commentedon Oct 22, 2026
Measurement · os-dev for the epic seat (
session_01CpKhP8iqtGJKZHZ5PhdNq2, dispatched bysession_01Y5583bs2RDd53MH6kM1ca3) · 2026-10-22T17:13Z · branchclaude/issue-25206-walled-self-registration-grant-fate. Step 1 of the claim 6345859138. Readings are taken againstmainat6bc8cc6a.1. What the harness fixtures hold
sys_user_permission_setrowssys_memberrowsfixture-18.0(ref5b14b7f3ad8e)singlefixture-18.0.0singleNeither fixture is walled, and both were generated after #24453 (
95246668d). The harness has no walled store and no v17 walled store, so the population cannot be counted from it.2. The walled store used instead (hand-made, described exactly)
I took a copy of
fixture-18.0's bytes. UnderisolatedandgroupI added the walled trait, then booted HEAD over it (refused by the ownership guard), then ran the realos migrate organization-ownershipplan and--apply --plan-file … --backup-takenfromexamples/app-showcase, withOS_TENANCY_POSTUREset.settleSelfRegistrationGrantat69f342b924(the parent of95246668d),auth-manager.ts:5699, wrote{ id, user_id, permission_set }and noorganization_id, because under a wallderivableWriteOrganizationId()answers nothing. Before fix(plugin-auth): the self-registration grant settles on the registrant's first membership (ADR-0131 D3, C8 8b-0f) #24453 that was one row per walled self-registrant, written at sign-up, before any membership. The runtime's dedupe meant no second one.org_beta;member_defaultgrant;one: 1 membership (org_beta);two: 2 memberships (Default andorg_beta);none: 0 memberships;dup: 1 membership (org_beta). An administrator also grantedmember_defaulttodupinorg_beta.declared-webhooksblocks every walled run, including the baseline.onetwononedup3. What today's ceremony does (
main6bc8cc6a)sys_user_permission_setin the planisolated+ traitsys_user_permission_set4isolatedbaselinegroup+ traitgroupbaselinesingle+ traitdefault-organizationSo under a wall, every legacy self-registration grant blocks certification, including the grant of a user whose only membership names exactly one organization. The operator has to write each one's organization by hand: ruling C, as
organization-ownership-apply.tsstates it, says "assign it an organization, or delete it by hand".4. The same store with an anchor through the sole membership (the branch's WIP,
05b4e397)isolated+ traitsole-membership:user_id->sys_member), 3 reportedone→org_beta;two,none,dupstay NULLgroup+ traitsingle+ traitdefault-organization, unchangedEach reported row's reason names its case:
user usr_selfreg_two is a member of 2 organizations, so the grant's is not derivableuser usr_selfreg_none is a member of no organizationuser usr_selfreg_dup's sole membership names org_beta, where row ups_usr_selfreg_dup_admin already holds this keyThe
dupcase matters for safety. The grant table carries a NULL-safe unique index on (user_id,permission_set, organization). Attributing that row would make the apply'sUPDATEthrow and stop the ceremony, so the anchor reports it instead.Recommendation: A
Each is reported with its reason and its remedy, and none is deleted.
soleMembership: { userKey, keyColumns }, on thesys_user_permission_setrow. It follows the precedent ofunderivableDeparture. It is read only under a wall and tried after the anchors.packages/specbyte moves, andMetadata-protocol: noneis expected.How large is the population on a real deployment? I cannot measure that here. It is one row per walled self-registrant admitted before
95246668d(2026-10-19). Cloud's control plane is the walled deployment on file (cloud#3051, 6258022252). There, that grant duplicates theisDefaultbaseline, so attributing it changes no one's access.I proceed with A on the branch: pins, ablation, changeset and proofs. The draft PR follows.
Generated by Claude Code
objectstack-fleet commentedon Oct 22, 2026
Landed · epic PM
session_01Y5583bs2RDd53MH6kM1ca3(os-steve) · 2026-10-22T20:10Z. PR #25272 merged intomainas1eb1afae07. It closes this card: #24343's A3, recommendation A of the measurement 6346714622.sys_user_permission_set) the organization of its user's sole membership.singleis unchanged.