Repository navigation
Commit d65826f
fix: framework producers on tenant-column objects name the organization they can derive (ADR-0131 D9, C8 stage 8b-0b) (#24369)
Part of #24343
Part of #15212
Clause-②: yes (widening)
C8 stage 8b-0b (plan 6251466182 on #15212). Before stage 8b lifts the
system-insert exemption, each framework producer that writes
organization-less rows on a tenant-column object now names the
organization it can derive itself (ADR-0131 D9: derived under `single`,
refused otherwise). Under `single` that organization is the Default
Organization, which is the owner the engine would derive, so no reader
changes. Under a wall a row is stamped only where the producer has an
organization. A row it cannot attribute is written exactly as before,
and its fate stays the engine's (stage 8b). This PR does not lift the
exemption and does not retire any NULL reader.
**Why `Part of #24343` and not `Fixes`.** #24343's acceptance is "0 NULL
rows on the three tables after a sign-up on a walled control plane, and
the ceremony's advisory count reads 0". The walled half cannot be met by
naming a derivable organization, because a sign-up has none to derive
(measured below). Reaching it takes the fate decisions listed under "Not
built". Merging this PR must not close the card.
**Why `Clause-②: yes (widening)` when the claim said `no`.** Three
exported types gain optional members:
`AuditWriterOptions.getDefaultOrganizationId` (plugin-audit), and
`organization_id` on `SettingsSecretStore.insert`'s row and
`organizationId` on `SettingsAuditWriter.write`'s entry
(service-settings). These are additive widenings of a published surface,
so those two packages take `minor`. Nothing narrows.
`buildSettingAuditWriter`'s optional `logger` was briefly narrowed on
this branch (head `bc7e2b791`, `{ error?; warn }`), and the contract
review caught it (record 6253189824). It is now `{ warn? } | { error;
warn? }`: `main`'s accept set (no sink, `{}`, `{ warn? }`) plus a sink
that carries `error`. A tsc probe of those four shapes plus a typed `{
error?; warn? }` passed, then the probe was deleted.
`check:optional-error-sink` and `check:durability-log-level` exit 0.
## Measured first (framework at this branch, in-process sqlite,
`examples/app-crm`)
A sign-up through `POST /auth/sign-up/email` with the audience opened
(`audience_posture: open`, declared set `member_default`), then one
global encrypted settings write (`mail.api_key`). Rows written after the
snapshot:
| table | `single` (this PR) | `isolated` (`multiTenant:
'posture-only'`, this PR) |
|:--|:--|:--|
| `sys_user_permission_set` | 1 written, 0 without an organization | 1
written, **1** without (the registrant's grant) |
| `sys_email` | 1 / 0 | 1 / **1** (the verification mail) |
| `sys_activity` | all stamped with the Default Organization | 12 /
**12**: activity about `sys_user` (created, updated), `sys_email`
(created, updated), `sys_user_permission_set`, `sys_platform_setting`
×3, `sys_setting_audit` ×3, `sys_secret` |
| `sys_secret` (D4) | — | 1 / **1** (the global `mail.api_key`
ciphertext) |
| `sys_setting_audit` (D4) | — | all 6 rows in the table without an
organization, all `scope: global` |
- **A1.** No organization is derivable for any of the three sign-up
writers under a wall: the registrant belongs to no organization when its
grant, its verification mail and the activity about its user row are
written. The activity rows are about records that have no organization
(`sys_user`, `sys_platform_setting`, `sys_secret` …) or that are
org-less for the same reason. Under `single`, the verification and reset
mails and the activity about org-less records used to land NULL, and
they now carry the Default Organization. The grant already carried it.
- **A3 / D4.** The deployment-level population under a wall is **not
empty**. All 17 encrypted settings declared in this tree are `scope:
global` (ai ×10, auth, knowledge ×2, mail, sms ×2, storage), so every
`sys_secret` row the settings producer writes from the built-in catalog
is deployment-level. The datasource binder's credential rows are
deployment-level by construction. No webhook producer writes
`sys_secret`. Every global-rung write lands an org-less
`sys_setting_audit` row.
## What changed
- **plugin-auth** (`auth-manager.ts`). One reading,
`derivableWriteOrganizationId()`, which is `tenancy.defaultOrgId()`: the
Default Organization under `single`, none under a wall. It feeds the
self-registration grant (`settleSelfRegistrationGrant`) and the five
auth mails about a user: verification, password reset, account
invitation, magic link and the change-email notice.
- **plugin-audit** (`audit-writers.ts`, `audit-plugin.ts`). An activity
row whose record has no organization, written with no session
organization, takes `getDefaultOrganizationId()`. `AuditPlugin` wires
that to the `tenancy` service. The record's own organization and then
the session's still win, and the ledger's `tenant_id` attribution (D7)
is untouched.
- **service-settings** (`settings-service.ts`,
`settings-service-plugin.ts`, `settings-service.types.ts`). The
`sys_secret` ciphertext and the `sys_setting_audit` row of a `tenant` or
`user` value carry that value's organization, the same owner as its
`sys_setting` row. A `global` value's rows name none (D4, below). The
setting-audit writer's `catch` now reports at `error` when the sink
carries it (else `warn`), under the degradation log-level rule: the
settings write succeeds while the history row is lost. Without this a
refusal from stage 8b would be a silent audit loss. The first report
carries the consequence and the fix.
- **verify** (`rls.ts`). The RLS probe and position personas' grants
belong to the persona's single membership, which under `single` is the
Default Organization. They were written with an explicit
`organization_id: null`.
- **dogfood fixture** (`showcase-bu-hierarchy-sharing.dogfood.test.ts`,
the "fixture :66"). Its `findOne('sys_organization', { where: {} })` is
refused by the engine as an arbitrary pick. The `.catch(() => null)`
swallowed that refusal, so the fixture always minted a stand-in `org_bu`
beside the real Default Organization. It now reads the Default
Organization from the `tenancy` service, and it stamps its
`sys_business_unit_member` rows too.
## Not built — fates ADR-0131 D7/D9 do not settle (reported to the seat)
1. The walled self-registration grant (the registrant has no
organization yet).
2. The walled auth mails about a user (`sys_email`).
3. The walled activity about org-less records (`sys_activity`).
4. D4: the deployment-level `sys_secret` (global settings secrets,
datasource credentials) and `sys_setting_audit` (global rung) rows. No
object leaves the tenant-column set here.
The ceremony's advisory count (`packages/cli`) is not touched. It reads
0 for these tables only once 1–3 are decided.
## Tests
- New `plugin-auth/src/producers-name-organization.test.ts`: the
verification mail, the reset mail and the grant, each under `single`
(Default Organization) and under a wall (none).
- `plugin-audit/src/audit-writers.test.ts`: three cases. The Default
Organization is used under `single`; none under a wall, with no
resolver, or when the resolver throws; a known organization is never
overridden and the resolver is not asked.
- New `service-settings/src/settings-secret-organization.test.ts`:
tenant value vs global value, and the audit writer's `organization_id`.
`settings-user-reference.pin.test.ts` follows the `error` channel.
- `verify/src/rls-persona-grant.test.ts`: the persona with one
membership, none, and two.
- New `dogfood/test/signup-organization-census.dogfood.test.ts`: the
`single` half of #24343's acceptance census. After a real open-audience
sign-up, every row the sign-up wrote on the three tables names the
Default Organization. The platform admin's promotion grant, which is
stage 8b-0a's, is set aside by snapshot.
- **Ablation** (census, plugin-audit, through
`scripts/ablation-replace.mjs` with a trap, then a rebuild): the
fallback's resolver returned undefined (`return ['ABLATED'][1]`).
`ablation-dist-preflight` found the marker in `dist`, and the census
went red (`sys_activity: rows without an organization`, 2 rows).
Restored: blob equal to HEAD, `git diff HEAD` empty, `--absent`
preflight passed, 2/2 green.
- Package suites at `5c3cbdd90`: plugin-auth 161 files / 3123 passed, 10
skipped; plugin-audit 56 / 897; service-settings 50 / 874; verify 41 /
363. `typecheck` passed for all four packages and for dogfood. Dogfood
journeys passed 6 files / 28 tests: the census, the business-unit
sharing fixture, `rls-fixture`, `admin-identity-audit-trail`,
`settings-organization-isolation` and `email-template-materialization`.
## Gates
`node scripts/pm/dispatch-gates.mjs --commands` derived 77 families; all
were run and reconciled with `--ran` (76 run, 1 NOT MEASURED). The NOT
MEASURED one is `check:dual-build-cjs-loads`, which exited 3
(PREREQUISITE NOT MET: no full `pnpm build` on this box), so CI's
reading counts for it. `check:engine-double-contract` was red once, on a
`findOne` in the new grant double. It was fixed by dropping the member
the writer never calls, and the re-run passed.
`check-plugin-teardown-shape --self-test` needed its pinned commit
fetched (shallow clone) and then passed. The family list re-derived
after the last commit is identical.
## Acceptance notes
- `IEmailService`'s `SendEmailInput.organizationId` TSDoc
(`packages/spec`) still says auth verification and reset mail "genuinely
has none". That is now true only under a wall. Not edited here (outside
the file surface).
- Under `single`, the platform admin's promotion grant
(`admin_full_access`) still lands without an organization. That is stage
8b-0a's file (`bootstrap-platform-admin.ts`, #11979), measured by the
census and set aside there.
- Under a wall, `sys_audit_log.tenant_id` stays NULL on the sign-up's
rows. That is D7's attribution field, not the anchor.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_015nNHWXUVqXs5TqaLtf2aGt
---
_Generated by [Claude
Code](https://claude.ai/code/session_015nNHWXUVqXs5TqaLtf2aGt)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 5da2e88 commit d65826f
15 files changed
Lines changed: 668 additions & 29 deletions
File tree
- .changeset
- packages
- plugins
- plugin-audit/src
- plugin-auth/src
- qa/dogfood/test
- services/service-settings/src
- verify/src
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
386 | 386 | | |
387 | 387 | | |
388 | 388 | | |
389 | | - | |
| 389 | + | |
| 390 | + | |
| 391 | + | |
| 392 | + | |
| 393 | + | |
| 394 | + | |
| 395 | + | |
| 396 | + | |
| 397 | + | |
| 398 | + | |
| 399 | + | |
| 400 | + | |
| 401 | + | |
| 402 | + | |
390 | 403 | | |
391 | 404 | | |
392 | 405 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
2564 | 2564 | | |
2565 | 2565 | | |
2566 | 2566 | | |
| 2567 | + | |
| 2568 | + | |
| 2569 | + | |
| 2570 | + | |
| 2571 | + | |
| 2572 | + | |
| 2573 | + | |
| 2574 | + | |
| 2575 | + | |
| 2576 | + | |
| 2577 | + | |
| 2578 | + | |
| 2579 | + | |
| 2580 | + | |
| 2581 | + | |
| 2582 | + | |
| 2583 | + | |
| 2584 | + | |
| 2585 | + | |
| 2586 | + | |
| 2587 | + | |
| 2588 | + | |
| 2589 | + | |
| 2590 | + | |
| 2591 | + | |
| 2592 | + | |
| 2593 | + | |
| 2594 | + | |
| 2595 | + | |
| 2596 | + | |
| 2597 | + | |
| 2598 | + | |
| 2599 | + | |
| 2600 | + | |
| 2601 | + | |
| 2602 | + | |
| 2603 | + | |
| 2604 | + | |
| 2605 | + | |
| 2606 | + | |
| 2607 | + | |
| 2608 | + | |
| 2609 | + | |
| 2610 | + | |
| 2611 | + | |
| 2612 | + | |
| 2613 | + | |
| 2614 | + | |
| 2615 | + | |
| 2616 | + | |
| 2617 | + | |
| 2618 | + | |
| 2619 | + | |
| 2620 | + | |
| 2621 | + | |
| 2622 | + | |
| 2623 | + | |
| 2624 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
149 | 149 | | |
150 | 150 | | |
151 | 151 | | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
152 | 161 | | |
153 | 162 | | |
154 | 163 | | |
| |||
1445 | 1454 | | |
1446 | 1455 | | |
1447 | 1456 | | |
| 1457 | + | |
| 1458 | + | |
| 1459 | + | |
| 1460 | + | |
| 1461 | + | |
| 1462 | + | |
| 1463 | + | |
| 1464 | + | |
1448 | 1465 | | |
1449 | 1466 | | |
1450 | 1467 | | |
| |||
2204 | 2221 | | |
2205 | 2222 | | |
2206 | 2223 | | |
| 2224 | + | |
| 2225 | + | |
| 2226 | + | |
| 2227 | + | |
| 2228 | + | |
| 2229 | + | |
2207 | 2230 | | |
2208 | | - | |
| 2231 | + | |
2209 | 2232 | | |
2210 | 2233 | | |
2211 | 2234 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
2003 | 2003 | | |
2004 | 2004 | | |
2005 | 2005 | | |
| 2006 | + | |
2006 | 2007 | | |
2007 | 2008 | | |
2008 | 2009 | | |
| |||
2022 | 2023 | | |
2023 | 2024 | | |
2024 | 2025 | | |
| 2026 | + | |
2025 | 2027 | | |
2026 | 2028 | | |
2027 | 2029 | | |
| |||
2036 | 2038 | | |
2037 | 2039 | | |
2038 | 2040 | | |
| 2041 | + | |
2039 | 2042 | | |
2040 | 2043 | | |
2041 | 2044 | | |
| |||
2098 | 2101 | | |
2099 | 2102 | | |
2100 | 2103 | | |
| 2104 | + | |
| 2105 | + | |
| 2106 | + | |
2101 | 2107 | | |
2102 | 2108 | | |
2103 | 2109 | | |
| |||
4114 | 4120 | | |
4115 | 4121 | | |
4116 | 4122 | | |
| 4123 | + | |
4117 | 4124 | | |
4118 | 4125 | | |
4119 | 4126 | | |
| |||
5618 | 5625 | | |
5619 | 5626 | | |
5620 | 5627 | | |
| 5628 | + | |
| 5629 | + | |
| 5630 | + | |
| 5631 | + | |
| 5632 | + | |
| 5633 | + | |
| 5634 | + | |
| 5635 | + | |
| 5636 | + | |
| 5637 | + | |
| 5638 | + | |
| 5639 | + | |
| 5640 | + | |
| 5641 | + | |
| 5642 | + | |
| 5643 | + | |
| 5644 | + | |
| 5645 | + | |
| 5646 | + | |
| 5647 | + | |
| 5648 | + | |
| 5649 | + | |
| 5650 | + | |
| 5651 | + | |
| 5652 | + | |
| 5653 | + | |
5621 | 5654 | | |
5622 | 5655 | | |
5623 | 5656 | | |
5624 | | - | |
5625 | | - | |
5626 | | - | |
| 5657 | + | |
| 5658 | + | |
| 5659 | + | |
| 5660 | + | |
| 5661 | + | |
| 5662 | + | |
5627 | 5663 | | |
5628 | 5664 | | |
5629 | 5665 | | |
| |||
5647 | 5683 | | |
5648 | 5684 | | |
5649 | 5685 | | |
5650 | | - | |
5651 | | - | |
5652 | | - | |
5653 | | - | |
5654 | | - | |
5655 | | - | |
5656 | | - | |
| 5686 | + | |
5657 | 5687 | | |
5658 | 5688 | | |
5659 | 5689 | | |
| |||
5675 | 5705 | | |
5676 | 5706 | | |
5677 | 5707 | | |
5678 | | - | |
| 5708 | + | |
5679 | 5709 | | |
5680 | 5710 | | |
5681 | 5711 | | |
| |||
5794 | 5824 | | |
5795 | 5825 | | |
5796 | 5826 | | |
| 5827 | + | |
5797 | 5828 | | |
5798 | 5829 | | |
5799 | 5830 | | |
| |||
0 commit comments