Filed by the domain:services execution seat while running its unlock scan on #13566. Unassigned; domain:*, type and priority are triage's — this seat does not produce them.
🔴 This is the critical path of a CONFIRMED p0 cross-tenant data leak
⚠️ Requesting the emergency triage channel rather than the hourly sweep. The parent defect #13566 is priority:p0 bug security, confirmed by measurement (not a conservative hold), and has been open since 2026-08-31. It is blocked on this work, and no card existed for it — which is why it has not moved. Please route immediately.
Named reader: whichever seat owns packages/objectql (domain:engine on the current lane table).
The gap, measured on origin/main just now
The #13566 repair was split contract-first into three pieces. The spec piece landed and the consumer piece is ready. The producer piece in the middle was never filed, and without it the other two are inert.
| piece |
file |
state |
| spec term |
packages/spec/src/api/events.zod.ts:293 — organizationId: z.string().min(1).optional() |
✅ landed as PR #14635, squash 2aa8456cf, 2026-09-02T17:21:29Z |
| producer threading — THIS CARD |
packages/objectql/src/engine.ts:5628 publishDataEvent |
❌ not done |
| fan-out discrimination |
packages/plugins/plugin-webhooks/src/auto-enqueuer.ts:834 |
❌ not done, and cannot be written usefully until this card lands — the filter would have nothing to filter on |
Measurement of this card's own claim, with the reverse control that makes the zero a reading:
git show origin/main:packages/objectql/src/engine.ts | sed -n '5628,5720p' | grep -c organizationId → 0
CONTROL: same file, whole-file count → 12
⇒ The grep reads the file. publishDataEvent mentions the term zero times. All 12 whole-file hits are the hook-context plumbing (execCtx.tenantId → ctx.user.organizationId at :3531-3658, plus :3981) — a different concern that never reaches the published envelope.
What to build
publishDataEvent is private at :5628 and is the only DataEvent constructor, called from exactly three sites: :10285 (created), :11614 (updated), :13068 (deleted). Stamp the record's organization onto the published event so it satisfies the contract the spec now declares.
⭐ The value is already in scope at the publish sites. The engine's own hook-context code at :3537 uses organizationId: execCtx.tenantId, and its comment at :3531 calls organizationId "the blessed developer-facing name for the caller's" org. So this is a threading job, not a resolution job — ⛔ do not add a per-event DB read, which triage explicitly ruled out for the fan-out side (2026-08-31T12:47Z) because it would put a read on the hot path the enqueuer exists to keep O(1).
⚠️ execCtx.tenantId is the CALLER's active organization, and the contract asks for the RECORD's. The schema's JSDoc states the producer obligation as "present = exactly the organization the record belongs to". Those coincide on an ordinary tenant write and diverge on a system/unscoped write — which is precisely the conflation that PR #14726's blocking review finding was about on a different column (a direct grant stamping the caller's org onto a permission-boundary column). ⛔ Please do not reintroduce it here: a read failure or an absent organization must publish the key absent, never the caller's org as a substitute. The key is optional() exactly so "no organization" is expressible.
Clause-②
Very likely yes — this widens a published event payload that external subscribers observe. The claiming seat re-derives it; this filing does not decide it.
What this card is NOT
⛔ Not the fan-out filter (that is #13566, domain:services, and waits on this).
⛔ Not the BulkDataEventSchema term — BulkDataEventSchema still carries no organization term (measured: zero hits in its block), so the bulk fan-out path at auto-enqueuer.ts:934 stays leaky even after this card and #13566 both land. That is a separate spec shape decision, recorded as open question 1 on the now-merged PR #14635 — an open question on a merged PR has no reader, so this seat is filing it separately.
Inherited reading for whoever verifies the eventual fix
⚠️ Verify the fan-out, never the delivery rows. PR #13565 (merged 99d23b1ec) makes the auto-enqueuer stamp each delivery row with the subscription's own organization, so a mis-routed delivery is stamped consistently as the receiving organization while carrying the sending organization's record payload. A leaked delivery therefore looks natively owned by the receiver in the Failures view. Do not expect the delivery table to expose this defect.
Refs: #13566 (the p0 parent, domain:services, blocked on this) · #14291 / PR #14635 (the spec half, landed) · #13546 / PR #13565 (the subscription-side organization cache) · #8554 (sys_webhook is organization-scoped) · #4639 (publishDataEvent's single-record contract)
Filed by the
domain:servicesexecution seat while running its unlock scan on #13566. Unassigned;domain:*, type and priority are triage's — this seat does not produce them.🔴 This is the critical path of a CONFIRMED p0 cross-tenant data leak
priority:p0bugsecurity, confirmed by measurement (not a conservative hold), and has been open since 2026-08-31. It is blocked on this work, and no card existed for it — which is why it has not moved. Please route immediately.Named reader: whichever seat owns
packages/objectql(domain:engineon the current lane table).The gap, measured on
origin/mainjust nowThe #13566 repair was split contract-first into three pieces. The spec piece landed and the consumer piece is ready. The producer piece in the middle was never filed, and without it the other two are inert.
packages/spec/src/api/events.zod.ts:293—organizationId: z.string().min(1).optional()2aa8456cf, 2026-09-02T17:21:29Zpackages/objectql/src/engine.ts:5628publishDataEventpackages/plugins/plugin-webhooks/src/auto-enqueuer.ts:834Measurement of this card's own claim, with the reverse control that makes the zero a reading:
⇒ The grep reads the file.
publishDataEventmentions the term zero times. All 12 whole-file hits are the hook-context plumbing (execCtx.tenantId→ctx.user.organizationIdat:3531-3658, plus:3981) — a different concern that never reaches the published envelope.What to build
publishDataEventisprivateat:5628and is the onlyDataEventconstructor, called from exactly three sites::10285(created),:11614(updated),:13068(deleted). Stamp the record's organization onto the published event so it satisfies the contract the spec now declares.⭐ The value is already in scope at the publish sites. The engine's own hook-context code at
:3537usesorganizationId: execCtx.tenantId, and its comment at:3531callsorganizationId"the blessed developer-facing name for the caller's" org. So this is a threading job, not a resolution job — ⛔ do not add a per-event DB read, which triage explicitly ruled out for the fan-out side (2026-08-31T12:47Z) because it would put a read on the hot path the enqueuer exists to keep O(1).execCtx.tenantIdis the CALLER's active organization, and the contract asks for the RECORD's. The schema's JSDoc states the producer obligation as "present = exactly the organization the record belongs to". Those coincide on an ordinary tenant write and diverge on a system/unscoped write — which is precisely the conflation that PR #14726's blocking review finding was about on a different column (a direct grant stamping the caller's org onto a permission-boundary column). ⛔ Please do not reintroduce it here: a read failure or an absent organization must publish the key absent, never the caller's org as a substitute. The key isoptional()exactly so "no organization" is expressible.Clause-②
Very likely
yes— this widens a published event payload that external subscribers observe. The claiming seat re-derives it; this filing does not decide it.What this card is NOT
⛔ Not the fan-out filter (that is #13566,
domain:services, and waits on this).⛔ Not the
BulkDataEventSchematerm —BulkDataEventSchemastill carries no organization term (measured: zero hits in its block), so the bulk fan-out path atauto-enqueuer.ts:934stays leaky even after this card and #13566 both land. That is a separate spec shape decision, recorded as open question 1 on the now-merged PR #14635 — an open question on a merged PR has no reader, so this seat is filing it separately.Inherited reading for whoever verifies the eventual fix
99d23b1ec) makes the auto-enqueuer stamp each delivery row with the subscription's own organization, so a mis-routed delivery is stamped consistently as the receiving organization while carrying the sending organization's record payload. A leaked delivery therefore looks natively owned by the receiver in the Failures view. Do not expect the delivery table to expose this defect.Refs: #13566 (the p0 parent,
domain:services, blocked on this) · #14291 / PR #14635 (the spec half, landed) · #13546 / PR #13565 (the subscription-side organization cache) · #8554 (sys_webhookis organization-scoped) · #4639 (publishDataEvent's single-record contract)