Repository navigation
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
Conversation
…r retired; webhook activation door Claude-Session: https://claude.ai/code/session_01PDSxBZE561TFe1N2iEjKXZ Co-authored-by: Claude <noreply@anthropic.com>
…he 18.0 dual read; door pins for webhook Claude-Session: https://claude.ai/code/session_01PDSxBZE561TFe1N2iEjKXZ Co-authored-by: Claude <noreply@anthropic.com>
…asserts no row and a dispatch Claude-Session: https://claude.ai/code/session_01PDSxBZE561TFe1N2iEjKXZ Co-authored-by: Claude <noreply@anthropic.com>
…the checklist, the webhooks guide and the showcase; changeset Claude-Session: https://claude.ai/code/session_01PDSxBZE561TFe1N2iEjKXZ Co-authored-by: Claude <noreply@anthropic.com>
…tcher's switch read Claude-Session: https://claude.ai/code/session_01PDSxBZE561TFe1N2iEjKXZ Co-authored-by: Claude <noreply@anthropic.com>
…posture builds no wall, as its 18.0 seeded copy stamped with the Default Organization was Claude-Session: https://claude.ai/code/session_01PDSxBZE561TFe1N2iEjKXZ Co-authored-by: Claude <noreply@anthropic.com>
…red writes; the declared-webhook double is drivable by the limit gate Claude-Session: https://claude.ai/code/session_01PDSxBZE561TFe1N2iEjKXZ Co-authored-by: Claude <noreply@anthropic.com>
…does not implement Claude-Session: https://claude.ai/code/session_01PDSxBZE561TFe1N2iEjKXZ Co-authored-by: Claude <noreply@anthropic.com>
…authors; checklist and admitsOrganization docblock say a declared webhook is deployment-wide under single Claude-Session: https://claude.ai/code/session_01PDSxBZE561TFe1N2iEjKXZ Co-authored-by: Claude <noreply@anthropic.com>
…0g-webhooks-registry
…the fixture's customized declared-webhook mirror Claude-Session: https://claude.ai/code/session_01PDSxBZE561TFe1N2iEjKXZ Co-authored-by: Claude <noreply@anthropic.com>
…n fail on a double delivery Claude-Session: https://claude.ai/code/session_01PDSxBZE561TFe1N2iEjKXZ Co-authored-by: Claude <noreply@anthropic.com>
…0g-webhooks-registry # Conflicts: # content/docs/permissions/tenant-audit-census.mdx # docs/audits/2026-08-tenant-audit-write-call-sites.counts.md
…0g-webhooks-registry
…0g-webhooks-registry # Conflicts: # content/docs/permissions/tenant-audit-census.mdx # docs/audits/2026-08-tenant-audit-write-call-sites.counts.md
…-registry' into claude/issue-24580-walled-lift
…er the walled postures (ADR-0131 C8 stage 8b, walled half) Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XA1XHtvWd8Q1Q1gyWvhZZz
…declared webhook's name with 409 RESOURCE_CONFLICT Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XA1XHtvWd8Q1Q1gyWvhZZz
… webhook name guard Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XA1XHtvWd8Q1Q1gyWvhZZz
…d system insert needs Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XA1XHtvWd8Q1Q1gyWvhZZz
… the guard test's fake refuses combinators Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XA1XHtvWd8Q1Q1gyWvhZZz
📓 Docs Drift CheckThis PR changes 4 package(s): 46 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 9 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. What this run could not see
Coarse fallback — 42 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 c88cfe0a952b16db210bc37f276fc8f6f03bf4c0 && git checkout c88cfe0a952b16db210bc37f276fc8f6f03bf4c0
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin f1298b3487fa8a93fdc63fe715e3d1d9f977f33a 1e8338d3b669027350d477b694a2ae7c1b42623c && git checkout -B drift-repro f1298b3487fa8a93fdc63fe715e3d1d9f977f33a && git merge --no-ff 1e8338d3b669027350d477b694a2ae7c1b42623c
node scripts/docs-audit/affected-docs.mjs --json f1298b3487fa8a93fdc63fe715e3d1d9f977f33a
|
…very posture (ruling A on the walled shape) Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XA1XHtvWd8Q1Q1gyWvhZZz
… posture rule refuses it Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XA1XHtvWd8Q1Q1gyWvhZZz
…led postures Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XA1XHtvWd8Q1Q1gyWvhZZz
…edential organization-less system inserts landing until the steps 5+6 flips Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XA1XHtvWd8Q1Q1gyWvhZZz
…ables and spells the column opt-out systemFields Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XA1XHtvWd8Q1Q1gyWvhZZz
…ent facts to the restated R4 — a walled os serve boots once provisioned Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XA1XHtvWd8Q1Q1gyWvhZZz
…to source for the walled credential-bind pin Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XA1XHtvWd8Q1Q1gyWvhZZz
Contract review (re-review after FAIL at
|
…lled-lift # Conflicts: # docs/qa/platform-checklist/areas/access-security.json
Conflicts and restatements, the ceremony being open on this branch: - runtime boot guard, its unit, kernel and dogfood pins: #24682's 18.0 "fresh installs only" refusal gives way to the marker-based refusal that names the ceremony's plan and apply. - cli.mdx: the open-ceremony text kept; #24678 and step 4's "until then the ceremony refuses" sentences restated; #24722's audit-mail-bodies row and section kept beside it. - B2: #24661's seven 18.0 upgrade-guide entries prescribe the ceremony again (the ups entry keeps the full apply), pinned in organization-ownership-upgrade-entries.test.ts. - metadata-lifecycle, metadata-service, capabilities, permission-sets: the 18.0 deferral restated. - platform-core.v18-boot-refusals revision 5. - objectql: WALLED_ORGANIZATION_LESS_BRIDGE, isWalledOrganizationLessBridgeObject and the engine reader are deleted (sys_secret and sys_flow_credential are column-less here); the refusal names systemFields: { tenant: false }. The tripwire is deleted; the flow-credential walled pin is kept, renamed walled-flow-credential-write.test.ts. - #24688's principal x door matrix restated over a ceremonied fixture. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014UATXWqAXqkaZkbG92ox6V
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_credentialbridge, contract review B1), #24489 items 2 and 3 (theos servelift, R4 restated), the declared-name guard, and item 4 under ruling A.Base. #24588 (step 1) has merged into
main(191e0430e), andorigin/main(3a931fca1) is merged into this branch atfdfc84238with 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 againstb4ae8e68, byte for byte. No regenerable artifact conflicted. The diff is now this card's own files only.What changes
packages/objectql). The platform-object exemption inEngine.resolveSystemInsertOrganizationis gone undergroup/isolatedtoo. A system insert into asys_/cloud_/ai_object with an organization column that names no organization is now refused under a wall withERR_SYSTEM_WRITE_ORGANIZATION_REQUIRED(500, reasonwalled-posture), instead of landingorganization_idNULL.PLATFORM_OBJECT_TENANCYkeeps its every-posture reader, thebypassTenantAuditmute; only the walled write-path reader retired. The docblocks inplatform-object-tenancy.ts,system-write-organization.tsandengine.tsnow say this.sys_webhook(packages/plugins/plugin-webhooks, seat note 6271514301 item 1).bindDeclaredWebhookNameGuardhas the shape ofbindDeclaredRuleNameGuard. A non-system insert whosenameis declared (switched on or off) is refused, and so is an update that changesnameonto a declared one. The refusal isDeclaredWebhookNameConflictError: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.WebhookOutboxPluginbinds the guard beside the reader and unbinds it ondestroy(). The system-context census page gains the anchor.plugin-security/src/builtin-positions.boot.test.ts: the census positive-control insert.plugin-security/src/grant-holder-membership-refusal.test.ts: thesys_positionseed.system-write-organization.test.tstenancy-by-object-classification.test.ts15212-8b-walled-platform-insert-refused.md(objectql,minor, breaking) and15212-webhook-declared-name-guard.md(plugin-webhooks,minor, breaking).#24489 items 2 and 3: the
os servelift (R4 restated)The session owner granted the permission the classifier had held, and these went in at
45898dbf6:walledPostureRefusaland its call inserve.tsare deleted, together withserve-walled-posture-refusal.test.ts. The diff is the exact inverse ofae6bd1a6'sserve.tshunks.The seven walled e2e cases are restored whole from
ae6bd1a6^. Neither file had changed since. There are 3 cases inserve-organizations-host-resolution.e2e.test.tsand 4 inserve-organizations-mount-failure.e2e.test.ts.walledPostureFinding(doctor) andWALLED_POSTURE_RELEASE_NOTE(the fix-list note) are removed. Their four test files are restored from5b14b7f^; none had changed since, and5b14b7f'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: theOS_TENANCY_POSTURErow;self-hosting.mdx: byte-identical to beforeae6bd1a6;cli.mdx, three sites.Each sentence's upgrade half stays, because it belongs to steps 5 and 6. Nothing under
content/docs/releases/ordocs/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
groupandisolateda declared webhook is deployment-wide, exactly as undersinglesince step 1:sys_http_deliveryrow is stamped with its event's organization.What changed:
AutoEnqueuermarks every declared subscriptiondeploymentWide. The posture-reading optiondeclaredWebhooksDeploymentWideis removed (added by feat(plugin-webhooks,runtime)!: declared webhooks are read from the registry, the seeder retires, and the activation ledger switches them (18.x step 1, 8b-0g′) #24588 and never released).WebhookOutboxPlugindropspostureBuildsNoWalland no longer reads the posture for declared webhooks.CachedSubscription.deploymentWide,admitsOrganization, thedeclared-webhooks.tsmodule doc) cite the ruling.15212-declared-webhook-walled-deployment-wide.md(minor,Clause-②: yes (widening)).The pins:
packages/qa/dogfood/test/wall-declared-webhook-deployment-wide.dogfood.test.ts. It runs a real walled boot, under bothisolatedandgroup, with two organizations created through the auth API and the engine's own record events reaching the plugin's dispatcher. It pins three things:manage_metadatain its own organization is refused403 PERMISSION_DENIEDat the activation door, nothing is written, and the platform operator switches the webhook on;GET /data/sys_http_deliverywith an organization-scoped read grant.declared-webhooks.test.ts, the walled-refusal case becomes the two-organization delivery case, and the posture-read case becomes "deployment-wide undersingle/group/isolated/ no tenancy service".Reverse-verified, each through
ablation-replace.mjs, with the package rebuilt so the mutation reacheddistand the source restored togit diff HEADempty:sub.deploymentWide = truechanged tofalse: 3 unit cases and 4 dogfood cases red (stamping and reading, under both postures).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.manage_metadata, so the door refused it on authority, not on posture, and the ablation stayed green. The fixture now grantsmanage_metadatainside org A, so only the posture rule can refuse it.Contract review B1 (comment 6280853341): the walled bridge for
sys_secretandsys_flow_credentialThe walled lift reached three production writers that are organization-less by ruling until step 6:
settings-service.ts, inserted bysettings-service-plugin.ts);datasource-secret-binder.ts);flow-credential-channel.ts).Under a wall each became a 500
ERR_SYSTEM_WRITE_ORGANIZATION_REQUIRED. The fix is the seat's choice (i):A cited bridge.
WALLED_ORGANIZATION_LESS_BRIDGE(objectql/src/tenancy/system-write-organization.ts) names exactlysys_secretandsys_flow_credential.Engine.resolveSystemInsertOrganizationlets their organization-less system inserts land undergroup/isolated, as before the lift; undersinglethey take the Default Organization as before. It cites ruling 6270035914 item 2 and feat(cli,runtime)!: 18.x step 6: open the upgrade ceremony for v17 and 18.0 databases (and the deferred flips) #24582.A tripwire.
service-automation/src/walled-organization-less-bridge.test.tsasserts the list is exactly those two tables, and that each still carries its tenant column. Note for the feat(cli,runtime)!: the upgrade ceremony opens — v17 and 18.0 databases take it, and a server serves only a database it created or one the ceremony certified (18.x steps 5 and 6) #24681 dev: the PR that flips either table tosystemFields: { tenant: false }turns this red. Its message says to delete the list, its reader inEngine.resolveSystemInsertOrganizationand the tripwire itself.Three real-engine walled pins, each under
isolatedandgroup:service-settings/src/settings-global-secret-walled.test.ts: the realSettingsServiceplus the plugin's real secret store;service-datasource/src/datasource-secret-binder.walled.test.ts: the real binder on SQLite. The package gains@objectstack/objectqland@objectstack/platform-objectsas devDependencies (6 lockfile lines, no build-graph cycle), aliased to source;service-automation/src/walled-organization-less-bridge.test.ts: the realFlowCredentialChannelon SQLite, which writes both tables.Reverse-verified through
ablation-replace.mjs:systemFields: { tenant: false }added tosys_flow_credential, then tosys_secret(platform-objects rebuilt): the tripwire red, each time with its remedy message.Each restored to
git diff HEADempty.The changeset
15212-8b-walled-platform-insert-refused.md:systemFields: { tenant: false };The QA checklist.
origin/main(with docs(qa): 18.0 sweep anchor round 5 — the walled-posture and second-organization themes, runner environment facts, and two carried notes #24687) is merged again, at3f839a98c, 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;access-security.json,platform-core.settings-organization-isolation-and-platform-rung, andattachments-storage.chunked-upload-integrity. An unprovisioned walled leg is nowblocked(fixture);check-platform-checklist, full and--changed-from: OK.Re-verified on
f73d8891a(after a fresh install and a fullpnpm build):singlebootwebhook-materializationand the walled declared-webhook pinisolatedos servewith the real Organizations runtimeOrganizationsmounted, 0 refusalstypecheck(all nine packages)dispatch-gates --commandscheck:test-source-aliasasked for the new service-datasource imports to be aliased, and they aref73d8891aRe-verified after merging
main(headfdfc84238)The install was refreshed and
pnpm buildrerun in full (exit 0). Then:pnpm dev -- --fresh(showcase,single)upgrade-18x-harness(withwebhook-materializationand the walled declared-webhook pin)--project unit+ the 7 restored e2e casestypecheck: objectql, plugin-webhooks, plugin-security, clidispatch-gates --commands: every derived command, run in fullCI red after the merge, fixed at
1412586b4(fixtures only, no assertion changed).fdfc84238was 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_idnull) 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 unscopedadmin_full_accessgrant and its catalog row are seeded through the driver in the L5 block. The fixture'ssys_metadata_activationnow declaressystemFields: { tenant: false }, as the platform object does (no organization column).1412586b4: 36 success, 5 skipped, 0 failures, all six Test Core shards included.The readings below were taken before the merge.
Proof (head
45898dbf6)typecheck+ whole packageupgrade-18x-harness.dogfood.test.tswebhook-materializationand the new walled pin: 3 files / 15 passedtypecheck+ whole packagepnpm dev -- --fresh(showcase,single) after a fullpnpm buildGET /api/v1/discovery200, no system-write refusal in the logos servebootsbin/run-dev.js serve) underOS_TENANCY_POSTURE=isolated(plus the walled prerequisitesOS_PLATFORM_OWNER_EMAILandOS_AUTH_MEMBERSHIP_POLICY=invite-only), on a throwaway app that declares the real@objectstack/organizations, reachedPress Ctrl+C to stop.Organizationswas mounted,GET /api/v1/discoveryanswered 200, and the log holds 0 system-write refusalstypecheck+--project unitOS_TEST_TIERS=nightly)Ablations, each through
scripts/ablation-replace.mjs, with mutation landed and restored togit diff HEADempty:sys_audit_entryandsys_permission_set, ×isolated/group, in both objectql files).throwreplaced byreturn: 4 cases red (insert, switched-off, rename, plugin wiring).Ablation of the lift: a walled refusal re-inserted at
serve's posture gate turns all 7 restored e2e cases red; restored togit diff HEADempty.Local gates for the
os servehalf, 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: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-censusandwhere-matcherwere red on first run, on this card's own new code; both are fixed in the branch.Acceptance notes
git diff --shortstat origin/main...HEADon headf73d8891a: 45 files, +1431 / -602 (2033 changed lines), under the 3000 human-merge line, with 0 governed paths.Generated by Claude Code