Found while verifying #27 on claude/issue-27-signable-personas at 48568f9 (memory driver and the default sqlite driver both show it). Not touched by that PR — the seed's member set is unchanged since #19 and the rule is runtime-side.
Observed
Every fresh boot logs, at info level:
[auth] membership backfill (kernel:ready) bound 82 member-less user(s) to the default organization (ADR-0093 D6) {"scanned":113,"bound":82,"skipped":31}
The 31 skipped are the 30 seeded employer staff (who hold a seeded sys_member row in their employer's organization) plus the platform owner (bound to the default organization by the default-org bootstrap). The 82 bound are the 80 job seekers and the 2 platform staff — everyone the seed deliberately leaves without a membership.
Why it matters
DESIGN.md §03, section 自助入驻的现状与到期条件, rules the membership policy explicitly:
membershipPolicy 必须是 invite-only(不自动绑组织)。⛔ 不要用 auto:那会把所有求职者绑进同一个默认组织,等于给他们一个共同的组织身份,而组织在这个平台里是隔离边界。求职者不属于任何组织是正确状态,不是待修的缺陷。
Nothing in the app declares that policy, so the platform default (auto) applies and the backfill does exactly what §03 says must not happen: all seekers now share one organization identity. Today it is inert for reads because accessible_org_ids never resolves in RLS (#18), but every readScope: 'org' grant on a seeker-side object widens the owner match to that shared organization the day #18 lifts, and the two platform staff become members of a tenant they do not belong to.
What is needed
Someone has to decide where the app states the policy — it is an auth-plugin setting (auth.membership_policy per ADR-0093 D1, read live through getMembershipPolicy()), not a defineStack() key. Options I can see:
- A. Declare it in the platform settings the deployment ships with (if
defineStack or the package manifest can carry a settings default) so a fresh boot starts at invite-only.
- B. Set it operationally (Setup → auth settings) and document it as a required step in the README next to the demo path.
- C. Ask upstream for an app-declarable default.
Recommendation: A if the surface exists, otherwise B with a loud README note, because the boot log line is the only place the deviation shows today.
Filed without a label for triage.
Found while verifying #27 on
claude/issue-27-signable-personasat48568f9(memory driver and the default sqlite driver both show it). Not touched by that PR — the seed's member set is unchanged since #19 and the rule is runtime-side.Observed
Every fresh boot logs, at info level:
The 31 skipped are the 30 seeded employer staff (who hold a seeded
sys_memberrow in their employer's organization) plus the platform owner (bound to the default organization by the default-org bootstrap). The 82 bound are the 80 job seekers and the 2 platform staff — everyone the seed deliberately leaves without a membership.Why it matters
DESIGN.md §03, section 自助入驻的现状与到期条件, rules the membership policy explicitly:
Nothing in the app declares that policy, so the platform default (
auto) applies and the backfill does exactly what §03 says must not happen: all seekers now share one organization identity. Today it is inert for reads becauseaccessible_org_idsnever resolves in RLS (#18), but everyreadScope: 'org'grant on a seeker-side object widens the owner match to that shared organization the day #18 lifts, and the two platform staff become members of a tenant they do not belong to.What is needed
Someone has to decide where the app states the policy — it is an auth-plugin setting (
auth.membership_policyper ADR-0093 D1, read live throughgetMembershipPolicy()), not adefineStack()key. Options I can see:defineStackor the package manifest can carry a settings default) so a fresh boot starts atinvite-only.Recommendation: A if the surface exists, otherwise B with a loud README note, because the boot log line is the only place the deviation shows today.
Filed without a label for triage.