Repository navigation
feat(plugin-auth): the single posture's Default Organization takes its first owner's default currency - #25706
Conversation
…s first owner's default currency Claude-Session: https://claude.ai/code/session_01JVTd3PprStGa26aez5omLB Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JVTd3PprStGa26aez5omLB Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): ⛔ 1 release-owned page(s) name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 21 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin c7e9ab0b27c110e489d6111940734154eb24cf43 && git checkout c7e9ab0b27c110e489d6111940734154eb24cf43
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin b1790841bb0bf2c7c696e858de51e8105704471a 1ce6154c182a70cf6d12dec7748b0a58bf331e88 && git checkout -B drift-repro b1790841bb0bf2c7c696e858de51e8105704471a && git merge --no-ff 1ce6154c182a70cf6d12dec7748b0a58bf331e88
node scripts/docs-audit/affected-docs.mjs --json b1790841bb0bf2c7c696e858de51e8105704471a
|
席位核对ACCEPT ·
Landing: Generated by Claude Code |
Fixes #25651
Clause-②: no
Metadata-protocol: none
What
The single posture's Default Organization now takes its first owner's default currency (
localization.currency, onetenantrow), by the same rule and the same write a new organization's creator gets (the #25495 helper,organization-default-currency.ts, reused, no second copy of the rule): the owner's storedsys_user.locale, else the binding request'sAccept-Language, through the CLDR region table. Nothing derivable means nothing is written; a value already set on any rung is kept. No new setting, key or door.ensure-default-organization.ts:EnsureDefaultOrganizationResultgains an optionalownerUserId, set exactly when the call bound (memberCreated) or promoted (ownerPromoted) the owner.auth-plugin.ts: the single-posturerunEnsure(the once-gated default-organization bootstrap) callsseedCreatedOrganizationCurrencyfor the Default Organization and that owner when, and only when, the gate reports the owner first bound. Outside theorganizationplugin factory region ofauth-manager.ts.Where the owner is first bound, and what is visible there (measured)
ensureDefaultOrganizationExistscreates the Default Organization at boot with no creator, so it never reachesonOrganizationCreated.POST /sign-up/emailinserts thesys_user; plugin-security's first-user promotion writes thesys_user_permission_setgrant from its write middleware; plugin-auth's bootstrap middleware fires on that grant insert and binds the owner. A temporary probe on the composed kernel (case 10 below) printed the gate's answers in order:no_admin,no_admin,admin_already_in_org(the re-entrant call while the decision is in flight), thenmemberCreated: truewithownerUserIdset. The probe was mutation-and-restore throughscripts/ablation-replace.mjs(blob restored to HEAD8c554309), not committed.Accept-Languageis read. Case 10 asserts CNY with no stored locale, which is only possible from the request.sys_user.locale(it stays unset; only an IdPlocaleclaim writes it,idp-locale-claim.ts). So on a first email sign-up the sign-up request'sAccept-Languageis the input. A bind atkernel:ready(no request) reads the stored locale only.Tests
organization-created-default-currency.test.ts, a new describe block on the real composition (ObjectKernel, ObjectQL on in-memory SQLite, AuthPlugin, SecurityPlugin, SettingsServicePlugin) under the defaultsingleposture, the first sign-up throughAuthManager.handleRequest:localization.currency{ value: 'CNY', source: 'tenant' }, exactly one settings write.Accept-Language:source: 'default', no value, zero writes.Each case first asserts the sign-up was bound as the owner, so none passes vacuously.
Ablation (one leg): the new call's guard replaced by
if (false as boolean)viascripts/ablation-replace.mjs⇒ case 10 red (expected ... to match object { value: 'CNY', source: 'tenant' }), 11 and 12 green; restored, blob == HEAD8c554309andgit diff HEADempty. AuthPlugin is imported from source, so no build was in the loop.Runs at head 1ce6154:
pnpm --filter @objectstack/plugin-auth exec vitest run --maxWorkers=2: Test Files 193 passed (193), Tests 3670 passed, 10 skipped.pnpm --filter @objectstack/plugin-auth typecheck: exit 0 (tsc, examples,check:test-typecheckOK).--no-inline-configon the three touched source files: exit 0.dispatch-gates --commands: 74 derived, 73 run exit 0, 1 NOT MEASURED:check:dual-build-cjs-loadsexit 3, PREREQUISITE NOT MET (it needs a whole-workspace build; CI runs it).Acceptance notes
seedCreatedOrganizationCurrencyprints name the "creator" (for example "its creator's language"); for the Default Organization the first owner stands in for the creator. Wording only, left as is to keep one copy of the rule.@objectstack/organizations) is not wired here; the card and ruling cover the single posture.Generated by Claude Code