Measurement first
AutoEnqueuer.handleEvent / handleBulkEvent (packages/plugins/plugin-webhooks/src/auto-enqueuer.ts) select the subscriptions to deliver to as:
const subs = [
...(this.subscriptions.get(event.object) ?? []),
...(this.subscriptions.get('*') ?? []),
];
Object name and trigger are the ONLY match terms. There is no organization term anywhere in the match, and there cannot be one today from the event side:
RealtimeEventPayload (packages/spec/src/contracts/realtime-service.ts) has no organization member;- the
DataEvent payload (packages/spec/src/api/events.zod.ts) has no organization member either (grep for organization over that file: zero hits); - the delivered body embeds the full record under
after for create/update events.
sys_webhook itself IS organization-scoped (#8554 — org-unique name, kernel-provisioned organization_id).
Consequence, if the walled-deployment posture is in scope for webhooks
On OS_TENANCY_POSTURE=isolated|group, organization A's webhook on contact receives organization B's contact creations — full record data, delivered to A's URL, signed with A's secret. That would be the same cross-organization family as the redeliver() wall (the stamping card, currently in repair), but on the fan-out side and arguably wider: it is not a replay of an existing row, it is first delivery of another tenant's record content.
Not graded here — it is possible sys_webhook authoring is deliberately global/admin-only on walled deployments and this is an accepted shape; that is a triage question, and the answer should be recorded either way (declared ≠ enforced cuts both directions).
What already exists toward a repair
The stamping card's repair (#13546, PR #13565) caches the subscription's own organization on CachedSubscription.organizationId — the subscription half of any future filter. The event half is the missing piece: either an organization on the published DataEvent (producer-side threading at the engine's publish site), or a fan-out-side resolution of the record's organization. Producer-side threading is the contract-first direction; a fan-out-side lookup per event would add a read to the hot path the enqueuer exists to keep O(1).
Measured while implementing the stamping repair; filed separately so it does not live only in that PR's margins. Unassigned, no labels — for triage.
Generated by Claude Code
Measurement first
AutoEnqueuer.handleEvent/handleBulkEvent(packages/plugins/plugin-webhooks/src/auto-enqueuer.ts) select the subscriptions to deliver to as:Object name and trigger are the ONLY match terms. There is no organization term anywhere in the match, and there cannot be one today from the event side:
RealtimeEventPayload(packages/spec/src/contracts/realtime-service.ts) has no organization member;DataEventpayload (packages/spec/src/api/events.zod.ts) has no organization member either (grep fororganizationover that file: zero hits);afterfor create/update events.sys_webhookitself IS organization-scoped (#8554 — org-uniquename, kernel-provisionedorganization_id).Consequence, if the walled-deployment posture is in scope for webhooks
On
OS_TENANCY_POSTURE=isolated|group, organization A's webhook oncontactreceives organization B's contact creations — full record data, delivered to A's URL, signed with A's secret. That would be the same cross-organization family as theredeliver()wall (the stamping card, currently in repair), but on the fan-out side and arguably wider: it is not a replay of an existing row, it is first delivery of another tenant's record content.Not graded here — it is possible
sys_webhookauthoring is deliberately global/admin-only on walled deployments and this is an accepted shape; that is a triage question, and the answer should be recorded either way (declared ≠ enforced cuts both directions).What already exists toward a repair
The stamping card's repair (#13546, PR #13565) caches the subscription's own organization on
CachedSubscription.organizationId— the subscription half of any future filter. The event half is the missing piece: either an organization on the publishedDataEvent(producer-side threading at the engine's publish site), or a fan-out-side resolution of the record's organization. Producer-side threading is the contract-first direction; a fan-out-side lookup per event would add a read to the hot path the enqueuer exists to keep O(1).Measured while implementing the stamping repair; filed separately so it does not live only in that PR's margins. Unassigned, no labels — for triage.
Generated by Claude Code