Repository navigation
Commit f8ef144
feat(objectql)!: under
Part of #15212
Clause-②: no (narrowing)
C8 stage 8b, with stage 8c-T-α folded in: its commit `5d1b91e9b` (5
grant rows in 4 test files stamped with the organization their context
reads under) is this branch's first commit. Shaped by epic ruling record
6259899486 on #15194: 18.0 is fresh installs, `single` only, and 8b
lifts the exemption under `single` only.
## What changes
`Engine.resolveSystemInsertOrganization`
(`packages/objectql/src/engine.ts`) used to return before it read the
posture for every in-reach platform object outside the 9-entry admit
list. Those system inserts landed with `organization_id` NULL under
every posture.
- **Under `single`** there is no platform-object exemption now. A system
insert into a platform object with a tenant column that names no
organization goes through the same ADR-0131 D9 decision every
application object gets: one organization (the Default Organization)
stamps it; zero or several refuse it with
`ERR_SYSTEM_WRITE_ORGANIZATION_REQUIRED`. The census puts 35 objects in
this newly reached set (44 in reach, minus the 9 admitted).
- **Under a walled posture (`group` / `isolated`)** the #13491 exemption
stands, as a named posture condition with a comment citing 6259899486
(item 4). It is not a per-table list.
- **`PLATFORM_OBJECT_TENANCY` does not retire here.** Two readers
remain: the walled exemption above, and the engine's diagnostics-only
`bypassTenantAudit` isSystem mute (`buildDriverOptions`, every posture).
The ledger header now says this. It retires with its walled reader in
18.x (D13). The census tooling and its JSON are unchanged and green, and
`lint.yml` is untouched.
- Docblocks are updated in `tenancy/platform-object-tenancy.ts` and
`tenancy/system-write-organization.ts`.
`packages/core/src/security/system-write-organization.ts` needed no
change: its decision function is unchanged, and editing its JSDoc would
change published `.d.ts` text for no behavioural reason.
#14570 (`sys_business_unit_member` unadjudicated): under `single` its
system-insert row is now stamped (pinned on the real showcase below).
Under a wall it still lands NULL, because the exemption stands there in
18.0. So this is **Part of #14570**; the walled half rides 18.x.
## Boot order (the main risk), measured
On a fresh `single` showcase boot (`pnpm dev -- --fresh`, SQLite) the
boot is clean and the admin is seeded. Then:
- the admin signs in;
- the admin sends an invitation through `POST
/auth/organization/invite-member`, which writes the invitation row and
the identity mail;
- the admin creates a record.
A direct DB census after these steps found **0 rows with
`organization_id` NULL in every platform table that has the column**,
across `sys_setting`, `sys_setting_audit`, `sys_member`,
`sys_invitation`, `sys_email`, `sys_user_permission_set`,
`sys_user_position`, `sys_audience_binding_suggestion`,
`sys_inbox_message`, `sys_notification_receipt`, `sys_webhook`,
`sys_business_unit`, the approval family and `sys_record_share`. Every
one was stamped with the Default Organization. No producer writes before
the Default Organization exists, so no producer needed changing.
(`AuthPlugin.start()` creates the Default Organization before the
application plugins start, and the seed loader already stamps.)
## Tests
**New pins**
- `packages/objectql/src/tenancy-by-object-classification.test.ts`:
- under `single`, un-admitted platform objects derive the sole
organization;
- with zero or two organizations they are refused, with the envelope
`code`, `status` and `organizationCount`;
- walled controls (`isolated`, `group`) still land unstamped.
- `packages/objectql/src/system-write-organization.test.ts`: the same
split, single against walled, on the #8844 fixture.
-
`packages/qa/dogfood/test/showcase-single-platform-insert-default-organization.dogfood.test.ts`,
on the real showcase composition:
- boot, sign-in and an invitation leave no organization-less row in any
platform object with a tenant column (with a non-vacuity floor);
- a bare `isSystem` insert into `sys_business_unit` /
`sys_business_unit_member` takes the Default Organization;
- with a second organization the same insert is refused
(`ambiguous-organization`) and nothing is written.
**Tests 8b broke, and how each was fixed.** No pin of a NULL reader was
rewritten, and no test was deleted or skipped.
- **Fixtures that hand-registered `sys_organization` / `sys_user` as
bare schemas** now declare them as the platform does (`managedBy:
'better-auth'`, so no injected column). Otherwise the first
organization's own insert was refused for want of an organization.
-
`runtime/src/system-write-tenancy-autonumber-split.integration.test.ts`
- `runtime/src/seed-tenancy-autonumber-split.integration.test.ts`
-
`service-messaging/src/notification-organization-follows-event.integration.test.ts`
- `plugin-approvals/src/status-mirror-cascade.integration.test.ts`
- `plugin-security/src/sys-audit-log-row-scope.test.ts`
- `plugin-security/src/granted-by-writer-provenance.test.ts` (plus the
Default Organization row a `single` boot holds)
- **PIN fixtures that model organization-less legacy rows** now land
those rows through the driver, as the residue an older release left.
These are global grants, the retired catalogue bucket, unscoped
platform-admin holders and the pre-change ledger. The readers they pin
still read NULL rows until 8c (18.x), so each assertion still pins what
it pinned.
- `plugin-security/src/__tests__/legacy-catalog-rows.testkit.ts` and its
plugin-auth copy
- `plugin-security/src/`:
- `explain-enforce-parity`
- `explain-removed-member-principal`
- `explain-principal-person`
- `explain-asker-gate`
- `orgless-position-name-fold`
- `catalog-reference-report`
- `bootstrap-platform-admin-existing-holder-scan`
- `controlled-by-parent-chain-middle-master`
- `sys-audit-log-row-scope` (`LEDGER_BEFORE`)
- `builtin-positions.boot` (legacy `sys_position` rows)
- `plugin-auth/src/`:
- `signup-existing-address-refusal`
- `sso-register-platform-admin-gate`
- `driver-fault-auth-log-carriers`
- `scim-deactivation-reconcile-user`
- `runtime/src/seed-tenancy-autonumber-split.integration.test.ts`
(`[GUARD]` platform residue)
- **One positive control rewritten to the ruled behaviour.** In
`plugin-security/src/builtin-positions.boot.test.ts`, the census
control's organization-less `sys_permission_set` insert now reads back
with the sole organization under `single`, and unstamped under the wall.
- **Incidental fixtures that only needed an owner** now carry one:
- `plugin-audit/src/identity-objects-feeds-off.test.ts`;
- the SCIM rigs `credential-at-rest-posture`, `scim-transaction-scope`,
`scim-deactivation-reconcile-user` and
`principal-less-producers-system-context`. Their mint carries an
organization; the verifier reads the digest alone. The plugin-auth
engine pool refuses a database holding an organization row, so adding a
Default Organization there was not an option.
- **Dogfood fixtures.** The ablation below proved each of these red only
because of 8b.
- **The Default Organization, asked of the `tenancy` service.** This
replaces an empty-`where` `findOne` whose swallowed refusal minted a
second organization (the trap that #14570's remainder named):
- `showcase-scope-depth`
- `showcase-scope-depth-fallback`
- `showcase-scope-depth-write`
- **A grant that carries the fixture organization its sessions are
active in:**
- `activity-withheld-update`
- `identity-admin-fields-org-peer`
- **Global grants that an organization-less principal must hold, landed
beneath the engine:**
- `write-door-unreadable-is-not-found`
- `predicate-write-unreadable-not-matched`
- `parent-derived-write-refusal-not-visible`
- `sharing-rule-org-less-caller`
- **Prechecks that resolved a principal with no organization** now
resolve in the organization a `single` session carries. The HTTP doors
in the same files use real sessions, and they were green throughout.
- `wall-package-refusal`
- `wall-package-doors-refusal`
- `wall-authoring-trail-doors`
- `wall-environment-metadata-refusal`
- `catalog-activation-door`
- `permission-set-clone-door`
- `showcase-permission-zoo` (explain is asked in that organization)
- **Pin rewritten to the ruled behaviour:**
`single-tenant-identity-create`. A system `sys_team` insert now takes
the Default Organization; the pin used to assert NULL.
The plugin-auth edits are test-only. No plugin-auth source changed.
8b-0f (#24453) had merged into this branch's base before they were made.
## Verification
Everything below is at head `d1838bf96`. Heavy runs went through
`os-verify-lock.sh`.
**Build and types**
- **Build:** `pnpm build --concurrency=2` passed 76/76 tasks, after the
second `origin/main` merge.
- **Typecheck:** exit 0 in each of objectql, core, runtime,
plugin-security, plugin-auth, plugin-audit, service-messaging,
plugin-approvals and dogfood.
**Tests**
- **Unit and integration, in the downstream consumers of
`@objectstack/objectql`, plus core.** All 48 packages are green after
the fixes (vitest per package). Among them:
- core 2646
- runtime 7455 (19 skipped)
- plugin-security 3783
- plugin-auth 3168
- rest 6707
- metadata-protocol 20674
- service-analytics 4759
- cli `unit` 4610
One cli file (`boot-preparation-parity`) timed out at 5 s under parallel
load; it passed alone, 37/37. The 18 originally red files re-ran green.
- **objectql:** 446 files, 8369 tests. The only 4 reds were the pins
this PR rewrites; those two files now pass 66/66.
- **Dogfood (whole suite, single and walled lanes):** 2291 passed, 18
skipped, 0 failed (`pnpm --filter @objectstack/dogfood exec vitest
run`).
- **Fresh showcase boot:** clean, with the admin seeded and 0
NULL-organization platform rows (see above).
**A1: the walled branch is unchanged**
- The engine's walled branch is the same expression as before.
- The walled controls in both objectql pin files pass, unchanged in
shape.
- Every walled dogfood test passes. The ones that broke were rigs whose
engine ran at the default `single` and simulated a wall only in plugin
services.
**Ablation**
- Tools: `scripts/ablation-replace.mjs` and
`ablation-dist-preflight.mjs`, with a trap restore.
- The mutation restored the exemption in every posture (`!== 'single'`
became `!== '8b-ablated'`). Results:
- **objectql pins:** 5 new cases went red.
- **dogfood pin:** after a rebuild, with the marker proven present in
`dist`, 3/3 went red.
- **restore:** `git diff HEAD` empty, blob hash matched, rebuild exit 0,
marker `--absent` exit 0.
- The same ablation re-ran the 17 other dogfood files that 8b had turned
red. All were green with the exemption back, so 8b is their only cause.
**Gates**
- `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` derived 80 commands, plus a 51-family
artifact-roster block. All 128 ran, with exit codes captured before any
pipe.
- **Green:** every family not listed below. That includes:
- `check-platform-object-tenancy-census`, `check-tenant-audit-census`
and `check-system-context-census`;
- `check-adr-0087-registration` (`not-required
(no-migration-prescription)`) and `check-changeset-no-major`;
- `check:cross-package-test-inputs`, `check:test-source-alias`,
`check:engine-double-contract`, `check:nul-bytes` and
`check:type-check-debt`.
The families that read tests were re-run on the final head.
- **Shallow clone:** `check-engine-split-ratio --days 90` and
`check-plugin-teardown-shape --self-test` refused on the shallow clone
at first. Both are green after `git fetch --shallow-since=2026-07-14`.
- **NOT MEASURED locally:** `check-closing-target-claim`,
`check-single-claim-paths` and `check-governed-queue-guard` need PR
context that only CI supplies. `check-partof-closing-keyword` was run
against this body and is green.
## Acceptance notes
- `mintScimConnectionCredential` (plugin-auth) has no production caller.
Under `single` an org-less mint is now stamped with the Default
Organization. That is behaviour-neutral, because the verifier never
reads the organization.
- A showcase boot logs `[sharing-rule] … expands to NO recipients` for
`share_new_inquiries_with_field_ops` with `organization: null` (the rule
itself, under regime C). It is not a NULL membership row, and this stage
does not touch it.
- Under `single`, a non-system write with no `tenantId` also reaches the
decision. The resolver runs for any context without an organization, not
only `isSystem`, so such a write into a platform object is now stamped
or refused as well. That is consistent with D9 ("every writer").
---
_Generated by [Claude
Code](https://claude.ai/code/session_01AZMXDVkiaxSpJkGXACuVwr)_
---------
Co-authored-by: Claude <noreply@anthropic.com>single, a platform object's organization-less system insert is stamped or refused (C8 stage 8b) (#24513)1 parent 246aba0 commit f8ef144
53 files changed
Lines changed: 625 additions & 167 deletions
File tree
- .changeset
- packages
- core/src/security
- objectql/src
- tenancy
- plugins
- plugin-approvals/src
- plugin-audit/src
- plugin-auth/src
- __tests__
- plugin-hono-server/src
- plugin-security/src
- __tests__
- qa/dogfood/test
- runtime/src
- security
- services/service-messaging/src
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
| 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 | |
|---|---|---|---|
| |||
1090 | 1090 | | |
1091 | 1091 | | |
1092 | 1092 | | |
1093 | | - | |
1094 | | - | |
| 1093 | + | |
| 1094 | + | |
1095 | 1095 | | |
1096 | 1096 | | |
1097 | 1097 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
7237 | 7237 | | |
7238 | 7238 | | |
7239 | 7239 | | |
7240 | | - | |
7241 | | - | |
7242 | | - | |
7243 | | - | |
7244 | | - | |
7245 | | - | |
7246 | | - | |
7247 | | - | |
7248 | | - | |
7249 | | - | |
| 7240 | + | |
| 7241 | + | |
| 7242 | + | |
| 7243 | + | |
| 7244 | + | |
| 7245 | + | |
| 7246 | + | |
| 7247 | + | |
| 7248 | + | |
| 7249 | + | |
| 7250 | + | |
| 7251 | + | |
| 7252 | + | |
| 7253 | + | |
7250 | 7254 | | |
7251 | 7255 | | |
7252 | 7256 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
124 | 124 | | |
125 | 125 | | |
126 | 126 | | |
127 | | - | |
128 | | - | |
129 | | - | |
130 | | - | |
131 | | - | |
132 | | - | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
133 | 132 | | |
134 | 133 | | |
135 | 134 | | |
| |||
389 | 388 | | |
390 | 389 | | |
391 | 390 | | |
392 | | - | |
393 | | - | |
| 391 | + | |
| 392 | + | |
394 | 393 | | |
395 | | - | |
396 | | - | |
397 | | - | |
398 | | - | |
399 | | - | |
| 394 | + | |
| 395 | + | |
| 396 | + | |
| 397 | + | |
400 | 398 | | |
401 | 399 | | |
402 | 400 | | |
403 | 401 | | |
404 | 402 | | |
405 | 403 | | |
| 404 | + | |
| 405 | + | |
| 406 | + | |
| 407 | + | |
| 408 | + | |
| 409 | + | |
| 410 | + | |
| 411 | + | |
| 412 | + | |
406 | 413 | | |
407 | 414 | | |
408 | 415 | | |
| |||
418 | 425 | | |
419 | 426 | | |
420 | 427 | | |
| 428 | + | |
| 429 | + | |
| 430 | + | |
421 | 431 | | |
422 | | - | |
423 | 432 | | |
424 | 433 | | |
425 | 434 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
196 | 196 | | |
197 | 197 | | |
198 | 198 | | |
199 | | - | |
200 | | - | |
201 | | - | |
202 | | - | |
203 | | - | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
204 | 214 | | |
205 | | - | |
206 | | - | |
207 | | - | |
208 | | - | |
209 | | - | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
210 | 241 | | |
211 | 242 | | |
212 | 243 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
3 | 3 | | |
4 | 4 | | |
5 | 5 | | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
6 | 26 | | |
7 | 27 | | |
8 | 28 | | |
| |||
78 | 98 | | |
79 | 99 | | |
80 | 100 | | |
81 | | - | |
82 | | - | |
83 | | - | |
84 | | - | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
85 | 106 | | |
86 | 107 | | |
87 | 108 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
71 | 71 | | |
72 | 72 | | |
73 | 73 | | |
74 | | - | |
75 | | - | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
76 | 79 | | |
77 | 80 | | |
78 | 81 | | |
| |||
84 | 87 | | |
85 | 88 | | |
86 | 89 | | |
87 | | - | |
88 | | - | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
89 | 93 | | |
90 | 94 | | |
91 | 95 | | |
| |||
156 | 160 | | |
157 | 161 | | |
158 | 162 | | |
159 | | - | |
| 163 | + | |
| 164 | + | |
160 | 165 | | |
161 | 166 | | |
162 | 167 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
327 | 327 | | |
328 | 328 | | |
329 | 329 | | |
| 330 | + | |
| 331 | + | |
| 332 | + | |
| 333 | + | |
330 | 334 | | |
331 | 335 | | |
332 | 336 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
236 | 236 | | |
237 | 237 | | |
238 | 238 | | |
239 | | - | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
240 | 244 | | |
241 | 245 | | |
242 | 246 | | |
| |||
0 commit comments