Repository navigation
Commit fc34d00
feat(objectql,cli,plugin-webhooks)!: 18.x step 4: the walled lift (8b walled half, the os serve lift, the declared-webhook name guard, declared webhooks deployment-wide) (#24667)
Clause-②: yes (narrowing)
Closes #24580 (18.x step 4, the walled lift). Every item of the card is
in: 8b's walled half (with the cited `sys_secret` /
`sys_flow_credential` bridge, contract review B1), #24489 items 2 and 3
(the `os serve` lift, R4 restated), the declared-name guard, and item 4
under ruling A.
**Base.** #24588 (step 1) has merged into `main` (`191e0430e`), and
`origin/main` (`3a931fca1`) is merged into this branch at `fdfc84238`
with a merge commit. The four plugin-webhooks files conflicted only
because #24588 was squash-merged: main's copy of each equals the step-1
head this branch had merged (`b4ae8e68`), and the resolution keeps this
branch's side. That is proven per file: the branch's delta against main
equals its own delta against `b4ae8e68`, byte for byte. No regenerable
artifact conflicted. The diff is now this card's own files only.
## What changes
1. **8b's walled half (`packages/objectql`).** The platform-object
exemption in `Engine.resolveSystemInsertOrganization` is gone under
`group` / `isolated` too. A system insert into a `sys_` / `cloud_` /
`ai_` object with an organization column that names no organization is
now refused under a wall with `ERR_SYSTEM_WRITE_ORGANIZATION_REQUIRED`
(500, reason `walled-posture`), instead of landing `organization_id`
NULL. `PLATFORM_OBJECT_TENANCY` keeps its every-posture reader, the
`bypassTenantAudit` mute; only the walled write-path reader retired. The
docblocks in `platform-object-tenancy.ts`,
`system-write-organization.ts` and `engine.ts` now say this.
2. **The declared-name guard on `sys_webhook`
(`packages/plugins/plugin-webhooks`, seat note 6271514301 item 1).**
`bindDeclaredWebhookNameGuard` has the shape of
`bindDeclaredRuleNameGuard`. A non-system insert whose `name` is
declared (switched on or off) is refused, and so is an update that
changes `name` onto a declared one. The refusal is
`DeclaredWebhookNameConflictError`: `RESOURCE_CONFLICT`, 409, naming the
webhook and the remedy (clone under another name, switch the declaration
off through the activation door). System writes pass. An update that
keeps its name passes, so the 18.0 mirror rows and the rows customised
there stay editable. `WebhookOutboxPlugin` binds the guard beside the
reader and unbinds it on `destroy()`. The system-context census page
gains the anchor.
3. **Walled test rigs.** 164 walled-posture test files, measured by a
posture grep. Two failed after the lift, both fixtures, and both are
fixed by carrying the organization a walled system write needs. No
assertion moved.
- `plugin-security/src/builtin-positions.boot.test.ts`: the census
positive-control insert.
- `plugin-security/src/grant-holder-membership-refusal.test.ts`: the
`sys_position` seed.
4. **The objectql pins of the exemption itself flip.** They pinned the
behaviour this card retires. They now pin the walled refusal and its
discriminating control (an object with no tenant column is still not
refused):
- `system-write-organization.test.ts`
- `tenancy-by-object-classification.test.ts`
5. Two changesets: `15212-8b-walled-platform-insert-refused.md`
(objectql, `minor`, breaking) and `15212-webhook-declared-name-guard.md`
(plugin-webhooks, `minor`, breaking).
## #24489 items 2 and 3: the `os serve` lift (R4 restated)
The session owner granted the permission the classifier had held, and
these went in at `45898dbf6`:
- `walledPostureRefusal` and its call in `serve.ts` are deleted,
together with `serve-walled-posture-refusal.test.ts`. The diff is the
exact inverse of `ae6bd1a6`'s `serve.ts` hunks.
- The seven walled e2e cases are restored whole from `ae6bd1a6^`.
Neither file had changed since. There are 3 cases in
`serve-organizations-host-resolution.e2e.test.ts` and 4 in
`serve-organizations-mount-failure.e2e.test.ts`.
- `walledPostureFinding` (doctor) and `WALLED_POSTURE_RELEASE_NOTE` (the
fix-list note) are removed. Their four test files are restored from
`5b14b7f^`; none had changed since, and `5b14b7f`'s hunks in them were
all walled.
- The 18.0 walled docs sentences are removed:
- `tenancy-modes.mdx`: the callout now says only that the upgrade path
arrives in a later 18.x release;
- `environment-variables.mdx`: the `OS_TENANCY_POSTURE` row;
- `self-hosting.mdx`: byte-identical to before `ae6bd1a6`;
- `cli.mdx`, three sites.
Each sentence's upgrade half stays, because it belongs to steps 5 and 6.
Nothing under `content/docs/releases/` or `docs/adr/` is touched.
- One changeset: `15212-walled-posture-boots.md` (`@objectstack/cli`,
`minor`, `Clause-②: yes (widening)`).
## Item 4 (seat note 6271514301 item 2): ruling A, implemented
The maintainer ruled **A** (ruling record 6278609968 on #24580). Under
`group` and `isolated` a declared webhook is deployment-wide, exactly as
under `single` since step 1:
- It receives every organization's events, and each `sys_http_delivery`
row is stamped with its event's organization.
- The switch stays the deployment-level activation-ledger row,
operator-gated under a wall.
- Today's organization-less refusal under a wall is removed.
- No ADR change.
What changed:
- `AutoEnqueuer` marks every declared subscription `deploymentWide`. The
posture-reading option `declaredWebhooksDeploymentWide` is removed
(added by #24588 and never released).
- `WebhookOutboxPlugin` drops `postureBuildsNoWall` and no longer reads
the posture for declared webhooks.
- Docblocks (`CachedSubscription.deploymentWide`, `admitsOrganization`,
the `declared-webhooks.ts` module doc) cite the ruling.
- One changeset: `15212-declared-webhook-walled-deployment-wide.md`
(`minor`, `Clause-②: yes (widening)`).
The pins:
- **New:**
`packages/qa/dogfood/test/wall-declared-webhook-deployment-wide.dogfood.test.ts`.
It runs a real walled boot, under both `isolated` and `group`, with two
organizations created through the auth API and the engine's own record
events reaching the plugin's dispatcher. It pins three things:
1. an organization administrator holding `manage_metadata` in its own
organization is refused `403 PERMISSION_DENIED` at the activation door,
nothing is written, and the platform operator switches the webhook on;
2. one delivery per event, each stamped with that event's organization;
3. each organization reads only its own deliveries, through `GET
/data/sys_http_delivery` with an organization-scoped read grant.
- **Flipped (unit):** in `declared-webhooks.test.ts`, the walled-refusal
case becomes the two-organization delivery case, and the posture-read
case becomes "deployment-wide under `single` / `group` / `isolated` / no
tenancy service".
Reverse-verified, each through `ablation-replace.mjs`, with the package
rebuilt so the mutation reached `dist` and the source restored to `git
diff HEAD` empty:
- `sub.deploymentWide = true` changed to `false`: 3 unit cases and 4
dogfood cases red (stamping and reading, under both postures).
- The event-organization stamp replaced by the subscription's: the same
3 + 4 red.
- The door's posture refusal removed in `catalog-activation.ts` (runtime
dist confirmed mutated, 2 → 1 occurrences): the dogfood door case red
under both postures, and the runtime door unit test 12 red.
- A first run of this pin had org A without `manage_metadata`, so the
door refused it on authority, not on posture, and the ablation stayed
green. The fixture now grants `manage_metadata` inside org A, so only
the posture rule can refuse it.
## Contract review B1 (comment 6280853341): the walled bridge for
`sys_secret` and `sys_flow_credential`
The walled lift reached three production writers that are
organization-less by ruling until step 6:
- a global-scope encrypted setting (`settings-service.ts`, inserted by
`settings-service-plugin.ts`);
- a datasource credential bind (`datasource-secret-binder.ts`);
- a flow credential save (`flow-credential-channel.ts`).
Under a wall each became a 500 `ERR_SYSTEM_WRITE_ORGANIZATION_REQUIRED`.
The fix is the seat's choice (i):
1. **A cited bridge.** `WALLED_ORGANIZATION_LESS_BRIDGE`
(`objectql/src/tenancy/system-write-organization.ts`) names exactly
`sys_secret` and `sys_flow_credential`.
`Engine.resolveSystemInsertOrganization` lets their organization-less
system inserts land under `group` / `isolated`, as before the lift;
under `single` they take the Default Organization as before. It cites
ruling 6270035914 item 2 and #24582.
2. **A tripwire.**
`service-automation/src/walled-organization-less-bridge.test.ts` asserts
the list is exactly those two tables, and that each still carries its
tenant column. **Note for the #24681 dev:** the PR that flips either
table to `systemFields: { tenant: false }` turns this red. Its message
says to delete the list, its reader in
`Engine.resolveSystemInsertOrganization` and the tripwire itself.
3. **Three real-engine walled pins**, each under `isolated` and `group`:
- `service-settings/src/settings-global-secret-walled.test.ts`: the real
`SettingsService` plus the plugin's real secret store;
- `service-datasource/src/datasource-secret-binder.walled.test.ts`: the
real binder on SQLite. The package gains `@objectstack/objectql` and
`@objectstack/platform-objects` as devDependencies (6 lockfile lines, no
build-graph cycle), aliased to source;
- `service-automation/src/walled-organization-less-bridge.test.ts`: the
real `FlowCredentialChannel` on SQLite, which writes both tables.
Reverse-verified through `ablation-replace.mjs`:
- bridge disabled, objectql rebuilt: all 6 walled cases red across the
three packages;
- `systemFields: { tenant: false }` added to `sys_flow_credential`, then
to `sys_secret` (platform-objects rebuilt): the tripwire red, each time
with its remedy message.
Each restored to `git diff HEAD` empty.
4. **The changeset** `15212-8b-walled-platform-insert-refused.md`:
- its "Unchanged" list names the two bridged tables;
- the remedy spells `systemFields: { tenant: false }`;
- the ADR-0087 marker records the two added exports.
5. **The QA checklist.** `origin/main` (with #24687) is merged again, at
`3f839a98c`, cleanly. Then:
- `RUNNER.md`'s two environment facts are rewritten: "A walled boot is a
fixture of its own" (the three prerequisites, measured), and the
one-organization entry, which now names the walled boot's organization
create;
- seven items re-pointed, each with a revision bump and a history entry:
the five in `access-security.json`,
`platform-core.settings-organization-isolation-and-platform-rung`, and
`attachments-storage.chunked-upload-integrity`. An unprovisioned walled
leg is now `blocked(fixture)`;
- `check-platform-checklist`, full and `--changed-from`: OK.
Re-verified on `f73d8891a` (after a fresh install and a full `pnpm
build`):
| reading | result |
|:--|:--|
| R1, fresh `single` boot | discovery 200, 0 refusals |
| R2 + R3, the harness with `webhook-materialization` and the walled
declared-webhook pin | 3 files / 15 passed |
| R4, `isolated` `os serve` with the real Organizations runtime |
discovery 200, `Organizations` mounted, 0 refusals |
| service-settings | 53 files / 894 passed |
| service-datasource | 46 files / 840 passed |
| service-automation | 196 files / 2600 passed |
| plugin-security | 189 files / 3786 passed, 45 skipped |
| plugin-webhooks | 221 passed |
| plugin-sharing | 1163 passed |
| plugin-auth | 3168 passed, 10 skipped |
| objectql walled pins | 68 passed |
| CLI unit + the 7 walled e2e cases | 4639 passed; 7/7 |
| `typecheck` (all nine packages) | exit 0 |
| `dispatch-gates --commands` | 124 / 124 exit 0, after one fix:
`check:test-source-alias` asked for the new service-datasource imports
to be aliased, and they are |
| CI on `f73d8891a` | 37 success, 5 skipped, 0 failures |
## Re-verified after merging `main` (head `fdfc84238`)
The install was refreshed and `pnpm build` rerun in full (exit 0). Then:
| reading | result |
|:--|:--|
| R1: `pnpm dev -- --fresh` (showcase, `single`) | ready, discovery 200,
0 refusals |
| R2 + R3: `upgrade-18x-harness` (with `webhook-materialization` and the
walled declared-webhook pin) | 3 files / 15 passed |
| plugin-webhooks, whole package | 20 files / 221 passed |
| objectql walled pins + plugin-security walled fixtures | 68 + 76
passed |
| CLI `--project unit` + the 7 restored e2e cases | 314 files / 4639
passed; 7/7 |
| `typecheck`: objectql, plugin-webhooks, plugin-security, cli | exit 0
|
| `dispatch-gates --commands`: every derived command, run in full | 110
/ 110 exit 0 |
**CI red after the merge, fixed at `1412586b4` (fixtures only, no
assertion changed).** `fdfc84238` was red in Test Core 2/6
(plugin-sharing) and 3/6 (plugin-auth). The 164-file walled grep had
missed both files, because they set the posture another way. Both
fixtures wrote organization-less legacy rows under a wall through the
engine, which the walled lift now refuses:
- `plugin-sharing/src/reconcile-refused-grant-continues.test.ts` (9
cases): the "platform-global rule" (`organization_id` null) is now
seeded through the driver, as the fixture already seeds its
organization-less records. That is the row an older database holds.
- `plugin-auth/src/last-admin-guard.config-anchor.test.ts` (3 cases):
the legacy unscoped `admin_full_access` grant and its catalog row are
seeded through the driver in the L5 block. The fixture's
`sys_metadata_activation` now declares `systemFields: { tenant: false
}`, as the platform object does (no organization column).
- Full suites after the fix: plugin-sharing 49 files / 1161 passed;
plugin-auth 165 files / 3168 passed, 10 skipped; typecheck exit 0 for
both.
- CI on `1412586b4`: 36 success, 5 skipped, 0 failures, all six Test
Core shards included.
The readings below were taken before the merge.
## Proof (head `45898dbf6`)
| reading | result |
|:--|:--|
| objectql, whole package | 449 files / 8454 tests passed |
| plugin-webhooks build closure + `typecheck` + whole package | 20 files
/ 221 tests passed |
| plugin-security, whole package | 188 files / 3754 passed, 45 skipped |
| walled rigs (164 files over 17 packages that depend on objectql) | all
green after the two fixture fixes; dogfood 12 passed, 1 skipped |
| R2: `upgrade-18x-harness.dogfood.test.ts` | 6/6, rerun after ruling A
together with `webhook-materialization` and the new walled pin: 3 files
/ 15 passed |
| plugin-webhooks after ruling A: `typecheck` + whole package | exit 0;
20 files / 221 passed |
| R3 | covered by the harness's v17 case (the ceremony still refuses) |
| R1: `pnpm dev -- --fresh` (showcase, `single`) after a full `pnpm
build` | ready, `GET /api/v1/discovery` 200, no system-write refusal in
the log |
| R4 as restated: a walled `os serve` boots | MET. The built CLI
(`bin/run-dev.js serve`) under `OS_TENANCY_POSTURE=isolated` (plus the
walled prerequisites `OS_PLATFORM_OWNER_EMAIL` and
`OS_AUTH_MEMBERSHIP_POLICY=invite-only`), on a throwaway app that
declares the real `@objectstack/organizations`, reached `Press Ctrl+C to
stop`. `Organizations` was mounted, `GET /api/v1/discovery` answered
200, and the log holds 0 system-write refusals |
| CLI after the lift: build + `typecheck` + `--project unit` | exit 0;
313 files / 4633 passed |
| the seven restored e2e cases (`OS_TEST_TIERS=nightly`) | 7/7 |
**Ablations,** each through `scripts/ablation-replace.mjs`, with
mutation landed and restored to `git diff HEAD` empty:
- The walled exemption re-inserted in the engine: 6 walled cases red
(`sys_audit_entry` and `sys_permission_set`, × `isolated` / `group`, in
both objectql files).
- The guard's `throw` replaced by `return`: 4 cases red (insert,
switched-off, rename, plugin wiring).
- The guard's system pass and rename check removed: 3 control cases red.
**Ablation of the lift:** a walled refusal re-inserted at `serve`'s
posture gate turns all 7 restored e2e cases red; restored to `git diff
HEAD` empty.
**Local gates for the `os serve` half**, each exit 0:
`check-platform-checklist --changed-from origin/main` (for the removed
exports), `adr-0087-registration`, `changeset-no-major`,
`empty-changeset`, `closing-keyword-parity`, `check-affected-docs`,
`check-drift-comment`, `check-doc-frontmatter`, `doc-anchors`,
`doc-authoring`, `docs-spec-enumerations`, `docs-transcript-drift`,
`doc-security-posture`, `cli-command-ids`, `issue-citations`,
`nul-bytes`, `cross-package-test-inputs`, `tier-file-adoption`,
`pm-prior-rulings`, `platform-checklist`. Targeted eslint exit 0.
**Local gates** (`dispatch-gates --commands`, narrowed to the families
reading this card's files; CI runs the farm), all exit 0 at the end:
- the three census gates (`system-context`, `tenant-audit`,
`platform-object-tenancy`);
- `adr-0087-registration`, `changeset-no-major`, `empty-changeset`,
`closing-keyword-parity`, `issue-citations`, `doc-authoring`;
- `engine-double-contract`, `objectql-double-limit`,
`query-options-erasure`, `where-matcher`;
- `nul-bytes`, `cross-package-test-inputs`, `test-source-alias`,
`durability-log-level`, `error-status-conformance`, `route-envelope`,
`platform-checklist`, `engine-split-ratio`.
`check-system-context-census` and `where-matcher` were red on first run,
on this card's own new code; both are fixed in the branch.
## Acceptance notes
- **Size.** `git diff --shortstat origin/main...HEAD` on head
`f73d8891a`: 45 files, +1431 / -602 (2033 changed lines), under the 3000
human-merge line, with 0 governed paths.
- **Walled 18.0 databases from embedded hosts.** These were never
refused. Their NULL rows are step 5's (ceremony attribution and report
fates), as the card records. Nothing here moves or rewrites stored rows.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01XA1XHtvWd8Q1Q1gyWvhZZz)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent f7005cb commit fc34d00
45 files changed
Lines changed: 1439 additions & 604 deletions
File tree
- .changeset
- content/docs
- deployment
- permissions
- docs/qa/platform-checklist
- areas
- packages
- cli
- src
- commands
- utils
- test
- objectql/src
- tenancy
- plugins
- plugin-auth/src
- plugin-security/src
- plugin-sharing/src
- plugin-webhooks/src
- qa/dogfood/test
- services
- service-automation/src
- service-datasource
- src
- service-settings/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 | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 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 | + | |
| 18 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
203 | 203 | | |
204 | 204 | | |
205 | 205 | | |
206 | | - | |
| 206 | + | |
207 | 207 | | |
208 | 208 | | |
209 | 209 | | |
| |||
1216 | 1216 | | |
1217 | 1217 | | |
1218 | 1218 | | |
1219 | | - | |
| 1219 | + | |
1220 | 1220 | | |
1221 | 1221 | | |
1222 | 1222 | | |
| |||
1527 | 1527 | | |
1528 | 1528 | | |
1529 | 1529 | | |
1530 | | - | |
| 1530 | + | |
1531 | 1531 | | |
1532 | 1532 | | |
1533 | 1533 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
98 | 98 | | |
99 | 99 | | |
100 | 100 | | |
101 | | - | |
| 101 | + | |
102 | 102 | | |
103 | 103 | | |
104 | 104 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
466 | 466 | | |
467 | 467 | | |
468 | 468 | | |
469 | | - | |
470 | | - | |
| 469 | + | |
471 | 470 | | |
472 | 471 | | |
473 | 472 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
16 | 16 | | |
17 | 17 | | |
18 | 18 | | |
19 | | - | |
20 | | - | |
21 | | - | |
22 | | - | |
23 | | - | |
24 | | - | |
25 | | - | |
26 | | - | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
27 | 23 | | |
28 | 24 | | |
29 | 25 | | |
| |||
0 commit comments