spec: BulkDataEventSchema carries no organization term, so the bulk webhook fan-out path stays cross-tenant-leaky after #13566 lands — PR #14635's open question 1 has no reader now that it is merged #14971

Description

@os-sales

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.

Named reader: the domain:spec execution seat. The edit lands in packages/spec/src/api/events.zod.ts, which is single-owner, so this card exists rather than a rider.

Why this is filed rather than left where it was recorded

PR #14635 (#14291) added organizationId to DataEventSchema and deliberately left BulkDataEventSchema and MetadataEventSchema unchanged, recording the bulk contract's tenant term as open question 1, recommendation A in its own discussion.

⚠️That PR is merged. An open question on a merged PR has no reader, no queue membership, and no ageing — it is invisible to every sweep. This card gives it one.

The consequence, measured on origin/main

AutoEnqueuer has two fan-out match sites, and both select subscriptions by object name alone:

auto-enqueuer.ts:834-835 handleEvent → subscriptions.get(event.object) + subscriptions.get('*')
auto-enqueuer.ts:934-935 handleBulkEvent → subscriptions.get(event.object!) + subscriptions.get('*')

'*' matches every object. The #13566 repair can give :834 an organization term to discriminate on, because DataEventSchema now carries one. It cannot give :934 one:

BulkDataEventSchema block, grep "organization" → ZERO hits
CONTROL: same file, "organizationId" → 2 hits (both in the DataEventSchema region, :247 and :293)

⇒ After #14970 (producer threading) and #13566 (fan-out filter) both land, the single-record path is closed and the bulk path is still open. One organization's bulk record events still reach another organization's webhook endpoints, full payload, signed with the receiving organization's secret.

⛔ This is not a hypothetical hardening card: #13566 is a confirmedpriority:p0 cross-tenant leak — the census measured that a tenant org-admin, not a deployment administrator, can create sys_webhook rows under both walled postures. This card is the half of that leak that the current repair chain does not reach.

What is asked

Decide and apply the bulk contract's tenant term — PR #14635's recommendation A, or whatever the seat judges correct now that the single-record shape has shipped and can be copied or deliberately diverged from. Whichever way it goes, please state in the schema JSDoc whether a bulk event's organization is one organization for the whole batch or potentially per-row, because that answer decides whether the fan-out filter can be a single comparison or has to partition the batch — and the fan-out is the hot path the enqueuer exists to keep O(1).

MetadataEventSchema is named here only for completeness; this seat has not measured whether it has an equivalent exposure and is not claiming it does.

Sequencing

Independent of #14970 and #13566 — it neither blocks nor is blocked by them. It should not wait for them, because it is the reason "the leak is closed" will be false when they land.

Refs: #13566 (the p0 parent) · #14970 (the producer-threading half, packages/objectql) · #14291 / PR #14635 (the single-record term, landed 2aa8456cf, and where open question 1 was recorded) · #13546 / PR #13565 (subscription-side organization cache)

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

    , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all \u003cpre\u003e\u003ccode\u003e blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks"); } } catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); } })(); (function(){ try { var __m = "github.com"; var __re = new RegExp('^' + "github\\.com" + '
    Skip to content

    spec: BulkDataEventSchema carries no organization term, so the bulk webhook fan-out path stays cross-tenant-leaky after #13566 lands — PR #14635's open question 1 has no reader now that it is merged #14971

    Description

    @os-sales

    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.

    Named reader: the domain:spec execution seat. The edit lands in packages/spec/src/api/events.zod.ts, which is single-owner, so this card exists rather than a rider.

    Why this is filed rather than left where it was recorded

    PR #14635 (#14291) added organizationId to DataEventSchema and deliberately left BulkDataEventSchema and MetadataEventSchema unchanged, recording the bulk contract's tenant term as open question 1, recommendation A in its own discussion.

    ⚠️That PR is merged. An open question on a merged PR has no reader, no queue membership, and no ageing — it is invisible to every sweep. This card gives it one.

    The consequence, measured on origin/main

    AutoEnqueuer has two fan-out match sites, and both select subscriptions by object name alone:

    auto-enqueuer.ts:834-835 handleEvent → subscriptions.get(event.object) + subscriptions.get('*')
    auto-enqueuer.ts:934-935 handleBulkEvent → subscriptions.get(event.object!) + subscriptions.get('*')
    

    '*' matches every object. The #13566 repair can give :834 an organization term to discriminate on, because DataEventSchema now carries one. It cannot give :934 one:

    BulkDataEventSchema block, grep "organization" → ZERO hits
    CONTROL: same file, "organizationId" → 2 hits (both in the DataEventSchema region, :247 and :293)
    

    ⇒ After #14970 (producer threading) and #13566 (fan-out filter) both land, the single-record path is closed and the bulk path is still open. One organization's bulk record events still reach another organization's webhook endpoints, full payload, signed with the receiving organization's secret.

    ⛔ This is not a hypothetical hardening card: #13566 is a confirmedpriority:p0 cross-tenant leak — the census measured that a tenant org-admin, not a deployment administrator, can create sys_webhook rows under both walled postures. This card is the half of that leak that the current repair chain does not reach.

    What is asked

    Decide and apply the bulk contract's tenant term — PR #14635's recommendation A, or whatever the seat judges correct now that the single-record shape has shipped and can be copied or deliberately diverged from. Whichever way it goes, please state in the schema JSDoc whether a bulk event's organization is one organization for the whole batch or potentially per-row, because that answer decides whether the fan-out filter can be a single comparison or has to partition the batch — and the fan-out is the hot path the enqueuer exists to keep O(1).

    MetadataEventSchema is named here only for completeness; this seat has not measured whether it has an equivalent exposure and is not claiming it does.

    Sequencing

    Independent of #14970 and #13566 — it neither blocks nor is blocked by them. It should not wait for them, because it is the reason "the leak is closed" will be false when they land.

    Refs: #13566 (the p0 parent) · #14970 (the producer-threading half, packages/objectql) · #14291 / PR #14635 (the single-record term, landed 2aa8456cf, and where open question 1 was recorded) · #13546 / PR #13565 (subscription-side organization cache)

    Activity

    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

    Metadata

    Metadata

    Assignees

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
      Skip to content

      spec: BulkDataEventSchema carries no organization term, so the bulk webhook fan-out path stays cross-tenant-leaky after #13566 lands — PR #14635's open question 1 has no reader now that it is merged #14971

      Description

      @os-sales

      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.

      Named reader: the domain:spec execution seat. The edit lands in packages/spec/src/api/events.zod.ts, which is single-owner, so this card exists rather than a rider.

      Why this is filed rather than left where it was recorded

      PR #14635 (#14291) added organizationId to DataEventSchema and deliberately left BulkDataEventSchema and MetadataEventSchema unchanged, recording the bulk contract's tenant term as open question 1, recommendation A in its own discussion.

      ⚠️That PR is merged. An open question on a merged PR has no reader, no queue membership, and no ageing — it is invisible to every sweep. This card gives it one.

      The consequence, measured on origin/main

      AutoEnqueuer has two fan-out match sites, and both select subscriptions by object name alone:

      auto-enqueuer.ts:834-835 handleEvent → subscriptions.get(event.object) + subscriptions.get('*')
      auto-enqueuer.ts:934-935 handleBulkEvent → subscriptions.get(event.object!) + subscriptions.get('*')
      

      '*' matches every object. The #13566 repair can give :834 an organization term to discriminate on, because DataEventSchema now carries one. It cannot give :934 one:

      BulkDataEventSchema block, grep "organization" → ZERO hits
      CONTROL: same file, "organizationId" → 2 hits (both in the DataEventSchema region, :247 and :293)
      

      ⇒ After #14970 (producer threading) and #13566 (fan-out filter) both land, the single-record path is closed and the bulk path is still open. One organization's bulk record events still reach another organization's webhook endpoints, full payload, signed with the receiving organization's secret.

      ⛔ This is not a hypothetical hardening card: #13566 is a confirmedpriority:p0 cross-tenant leak — the census measured that a tenant org-admin, not a deployment administrator, can create sys_webhook rows under both walled postures. This card is the half of that leak that the current repair chain does not reach.

      What is asked

      Decide and apply the bulk contract's tenant term — PR #14635's recommendation A, or whatever the seat judges correct now that the single-record shape has shipped and can be copied or deliberately diverged from. Whichever way it goes, please state in the schema JSDoc whether a bulk event's organization is one organization for the whole batch or potentially per-row, because that answer decides whether the fan-out filter can be a single comparison or has to partition the batch — and the fan-out is the hot path the enqueuer exists to keep O(1).

      MetadataEventSchema is named here only for completeness; this seat has not measured whether it has an equivalent exposure and is not claiming it does.

      Sequencing

      Independent of #14970 and #13566 — it neither blocks nor is blocked by them. It should not wait for them, because it is the reason "the leak is closed" will be false when they land.

      Refs: #13566 (the p0 parent) · #14970 (the producer-threading half, packages/objectql) · #14291 / PR #14635 (the single-record term, landed 2aa8456cf, and where open question 1 was recorded) · #13546 / PR #13565 (subscription-side organization cache)

      Activity

      Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

      Metadata

      Metadata

      Assignees

      Labels

      Type

      No type

      Projects

      No projects

        Milestone

        No milestone

        Relationships

        None yet

        Development

        No branches or pull requests

        Issue actions

        , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length \u003e 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
        Skip to content

        spec: BulkDataEventSchema carries no organization term, so the bulk webhook fan-out path stays cross-tenant-leaky after #13566 lands — PR #14635's open question 1 has no reader now that it is merged #14971

        Description

        @os-sales

        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.

        Named reader: the domain:spec execution seat. The edit lands in packages/spec/src/api/events.zod.ts, which is single-owner, so this card exists rather than a rider.

        Why this is filed rather than left where it was recorded

        PR #14635 (#14291) added organizationId to DataEventSchema and deliberately left BulkDataEventSchema and MetadataEventSchema unchanged, recording the bulk contract's tenant term as open question 1, recommendation A in its own discussion.

        ⚠️That PR is merged. An open question on a merged PR has no reader, no queue membership, and no ageing — it is invisible to every sweep. This card gives it one.

        The consequence, measured on origin/main

        AutoEnqueuer has two fan-out match sites, and both select subscriptions by object name alone:

        auto-enqueuer.ts:834-835 handleEvent → subscriptions.get(event.object) + subscriptions.get('*')
        auto-enqueuer.ts:934-935 handleBulkEvent → subscriptions.get(event.object!) + subscriptions.get('*')
        

        '*' matches every object. The #13566 repair can give :834 an organization term to discriminate on, because DataEventSchema now carries one. It cannot give :934 one:

        BulkDataEventSchema block, grep "organization" → ZERO hits
        CONTROL: same file, "organizationId" → 2 hits (both in the DataEventSchema region, :247 and :293)
        

        ⇒ After #14970 (producer threading) and #13566 (fan-out filter) both land, the single-record path is closed and the bulk path is still open. One organization's bulk record events still reach another organization's webhook endpoints, full payload, signed with the receiving organization's secret.

        ⛔ This is not a hypothetical hardening card: #13566 is a confirmedpriority:p0 cross-tenant leak — the census measured that a tenant org-admin, not a deployment administrator, can create sys_webhook rows under both walled postures. This card is the half of that leak that the current repair chain does not reach.

        What is asked

        Decide and apply the bulk contract's tenant term — PR #14635's recommendation A, or whatever the seat judges correct now that the single-record shape has shipped and can be copied or deliberately diverged from. Whichever way it goes, please state in the schema JSDoc whether a bulk event's organization is one organization for the whole batch or potentially per-row, because that answer decides whether the fan-out filter can be a single comparison or has to partition the batch — and the fan-out is the hot path the enqueuer exists to keep O(1).

        MetadataEventSchema is named here only for completeness; this seat has not measured whether it has an equivalent exposure and is not claiming it does.

        Sequencing

        Independent of #14970 and #13566 — it neither blocks nor is blocked by them. It should not wait for them, because it is the reason "the leak is closed" will be false when they land.

        Refs: #13566 (the p0 parent) · #14970 (the producer-threading half, packages/objectql) · #14291 / PR #14635 (the single-record term, landed 2aa8456cf, and where open question 1 was recorded) · #13546 / PR #13565 (subscription-side organization cache)

        Activity

        Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

        Metadata

        Metadata

        Assignees

        Labels

        Type

        No type

        Projects

        No projects

          Milestone

          No milestone

          Relationships

          None yet

          Development

          No branches or pull requests

          Issue actions

          , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
          Skip to content

          spec: BulkDataEventSchema carries no organization term, so the bulk webhook fan-out path stays cross-tenant-leaky after #13566 lands — PR #14635's open question 1 has no reader now that it is merged #14971

          Description

          @os-sales

          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.

          Named reader: the domain:spec execution seat. The edit lands in packages/spec/src/api/events.zod.ts, which is single-owner, so this card exists rather than a rider.

          Why this is filed rather than left where it was recorded

          PR #14635 (#14291) added organizationId to DataEventSchema and deliberately left BulkDataEventSchema and MetadataEventSchema unchanged, recording the bulk contract's tenant term as open question 1, recommendation A in its own discussion.

          ⚠️That PR is merged. An open question on a merged PR has no reader, no queue membership, and no ageing — it is invisible to every sweep. This card gives it one.

          The consequence, measured on origin/main

          AutoEnqueuer has two fan-out match sites, and both select subscriptions by object name alone:

          auto-enqueuer.ts:834-835 handleEvent → subscriptions.get(event.object) + subscriptions.get('*')
          auto-enqueuer.ts:934-935 handleBulkEvent → subscriptions.get(event.object!) + subscriptions.get('*')
          

          '*' matches every object. The #13566 repair can give :834 an organization term to discriminate on, because DataEventSchema now carries one. It cannot give :934 one:

          BulkDataEventSchema block, grep "organization" → ZERO hits
          CONTROL: same file, "organizationId" → 2 hits (both in the DataEventSchema region, :247 and :293)
          

          ⇒ After #14970 (producer threading) and #13566 (fan-out filter) both land, the single-record path is closed and the bulk path is still open. One organization's bulk record events still reach another organization's webhook endpoints, full payload, signed with the receiving organization's secret.

          ⛔ This is not a hypothetical hardening card: #13566 is a confirmedpriority:p0 cross-tenant leak — the census measured that a tenant org-admin, not a deployment administrator, can create sys_webhook rows under both walled postures. This card is the half of that leak that the current repair chain does not reach.

          What is asked

          Decide and apply the bulk contract's tenant term — PR #14635's recommendation A, or whatever the seat judges correct now that the single-record shape has shipped and can be copied or deliberately diverged from. Whichever way it goes, please state in the schema JSDoc whether a bulk event's organization is one organization for the whole batch or potentially per-row, because that answer decides whether the fan-out filter can be a single comparison or has to partition the batch — and the fan-out is the hot path the enqueuer exists to keep O(1).

          MetadataEventSchema is named here only for completeness; this seat has not measured whether it has an equivalent exposure and is not claiming it does.

          Sequencing

          Independent of #14970 and #13566 — it neither blocks nor is blocked by them. It should not wait for them, because it is the reason "the leak is closed" will be false when they land.

          Refs: #13566 (the p0 parent) · #14970 (the producer-threading half, packages/objectql) · #14291 / PR #14635 (the single-record term, landed 2aa8456cf, and where open question 1 was recorded) · #13546 / PR #13565 (subscription-side organization cache)

          Activity

          Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

          Metadata

          Metadata

          Assignees

          Labels

          Type

          No type

          Projects

          No projects

            Milestone

            No milestone

            Relationships

            None yet

            Development

            No branches or pull requests

            Issue actions

            , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
            Skip to content

            spec: BulkDataEventSchema carries no organization term, so the bulk webhook fan-out path stays cross-tenant-leaky after #13566 lands — PR #14635's open question 1 has no reader now that it is merged #14971

            Description

            @os-sales

            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.

            Named reader: the domain:spec execution seat. The edit lands in packages/spec/src/api/events.zod.ts, which is single-owner, so this card exists rather than a rider.

            Why this is filed rather than left where it was recorded

            PR #14635 (#14291) added organizationId to DataEventSchema and deliberately left BulkDataEventSchema and MetadataEventSchema unchanged, recording the bulk contract's tenant term as open question 1, recommendation A in its own discussion.

            ⚠️That PR is merged. An open question on a merged PR has no reader, no queue membership, and no ageing — it is invisible to every sweep. This card gives it one.

            The consequence, measured on origin/main

            AutoEnqueuer has two fan-out match sites, and both select subscriptions by object name alone:

            auto-enqueuer.ts:834-835 handleEvent → subscriptions.get(event.object) + subscriptions.get('*')
            auto-enqueuer.ts:934-935 handleBulkEvent → subscriptions.get(event.object!) + subscriptions.get('*')
            

            '*' matches every object. The #13566 repair can give :834 an organization term to discriminate on, because DataEventSchema now carries one. It cannot give :934 one:

            BulkDataEventSchema block, grep "organization" → ZERO hits
            CONTROL: same file, "organizationId" → 2 hits (both in the DataEventSchema region, :247 and :293)
            

            ⇒ After #14970 (producer threading) and #13566 (fan-out filter) both land, the single-record path is closed and the bulk path is still open. One organization's bulk record events still reach another organization's webhook endpoints, full payload, signed with the receiving organization's secret.

            ⛔ This is not a hypothetical hardening card: #13566 is a confirmedpriority:p0 cross-tenant leak — the census measured that a tenant org-admin, not a deployment administrator, can create sys_webhook rows under both walled postures. This card is the half of that leak that the current repair chain does not reach.

            What is asked

            Decide and apply the bulk contract's tenant term — PR #14635's recommendation A, or whatever the seat judges correct now that the single-record shape has shipped and can be copied or deliberately diverged from. Whichever way it goes, please state in the schema JSDoc whether a bulk event's organization is one organization for the whole batch or potentially per-row, because that answer decides whether the fan-out filter can be a single comparison or has to partition the batch — and the fan-out is the hot path the enqueuer exists to keep O(1).

            MetadataEventSchema is named here only for completeness; this seat has not measured whether it has an equivalent exposure and is not claiming it does.

            Sequencing

            Independent of #14970 and #13566 — it neither blocks nor is blocked by them. It should not wait for them, because it is the reason "the leak is closed" will be false when they land.

            Refs: #13566 (the p0 parent) · #14970 (the producer-threading half, packages/objectql) · #14291 / PR #14635 (the single-record term, landed 2aa8456cf, and where open question 1 was recorded) · #13546 / PR #13565 (subscription-side organization cache)

            Activity

            Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

            Metadata

            Metadata

            Assignees

            Labels

            Type

            No type

            Projects

            No projects

              Milestone

              No milestone

              Relationships

              None yet

              Development

              No branches or pull requests

              Issue actions

              , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
              Skip to content

              spec: BulkDataEventSchema carries no organization term, so the bulk webhook fan-out path stays cross-tenant-leaky after #13566 lands — PR #14635's open question 1 has no reader now that it is merged #14971

              Description

              @os-sales

              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.

              Named reader: the domain:spec execution seat. The edit lands in packages/spec/src/api/events.zod.ts, which is single-owner, so this card exists rather than a rider.

              Why this is filed rather than left where it was recorded

              PR #14635 (#14291) added organizationId to DataEventSchema and deliberately left BulkDataEventSchema and MetadataEventSchema unchanged, recording the bulk contract's tenant term as open question 1, recommendation A in its own discussion.

              ⚠️That PR is merged. An open question on a merged PR has no reader, no queue membership, and no ageing — it is invisible to every sweep. This card gives it one.

              The consequence, measured on origin/main

              AutoEnqueuer has two fan-out match sites, and both select subscriptions by object name alone:

              auto-enqueuer.ts:834-835 handleEvent → subscriptions.get(event.object) + subscriptions.get('*')
              auto-enqueuer.ts:934-935 handleBulkEvent → subscriptions.get(event.object!) + subscriptions.get('*')
              

              '*' matches every object. The #13566 repair can give :834 an organization term to discriminate on, because DataEventSchema now carries one. It cannot give :934 one:

              BulkDataEventSchema block, grep "organization" → ZERO hits
              CONTROL: same file, "organizationId" → 2 hits (both in the DataEventSchema region, :247 and :293)
              

              ⇒ After #14970 (producer threading) and #13566 (fan-out filter) both land, the single-record path is closed and the bulk path is still open. One organization's bulk record events still reach another organization's webhook endpoints, full payload, signed with the receiving organization's secret.

              ⛔ This is not a hypothetical hardening card: #13566 is a confirmedpriority:p0 cross-tenant leak — the census measured that a tenant org-admin, not a deployment administrator, can create sys_webhook rows under both walled postures. This card is the half of that leak that the current repair chain does not reach.

              What is asked

              Decide and apply the bulk contract's tenant term — PR #14635's recommendation A, or whatever the seat judges correct now that the single-record shape has shipped and can be copied or deliberately diverged from. Whichever way it goes, please state in the schema JSDoc whether a bulk event's organization is one organization for the whole batch or potentially per-row, because that answer decides whether the fan-out filter can be a single comparison or has to partition the batch — and the fan-out is the hot path the enqueuer exists to keep O(1).

              MetadataEventSchema is named here only for completeness; this seat has not measured whether it has an equivalent exposure and is not claiming it does.

              Sequencing

              Independent of #14970 and #13566 — it neither blocks nor is blocked by them. It should not wait for them, because it is the reason "the leak is closed" will be false when they land.

              Refs: #13566 (the p0 parent) · #14970 (the producer-threading half, packages/objectql) · #14291 / PR #14635 (the single-record term, landed 2aa8456cf, and where open question 1 was recorded) · #13546 / PR #13565 (subscription-side organization cache)

              Activity

              Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

              Metadata

              Metadata

              Assignees

              Labels

              Type

              No type

              Projects

              No projects

                Milestone

                No milestone

                Relationships

                None yet

                Development

                No branches or pull requests

                Issue actions

                , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
                Skip to content

                spec: BulkDataEventSchema carries no organization term, so the bulk webhook fan-out path stays cross-tenant-leaky after #13566 lands — PR #14635's open question 1 has no reader now that it is merged #14971

                Description

                @os-sales

                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.

                Named reader: the domain:spec execution seat. The edit lands in packages/spec/src/api/events.zod.ts, which is single-owner, so this card exists rather than a rider.

                Why this is filed rather than left where it was recorded

                PR #14635 (#14291) added organizationId to DataEventSchema and deliberately left BulkDataEventSchema and MetadataEventSchema unchanged, recording the bulk contract's tenant term as open question 1, recommendation A in its own discussion.

                ⚠️That PR is merged. An open question on a merged PR has no reader, no queue membership, and no ageing — it is invisible to every sweep. This card gives it one.

                The consequence, measured on origin/main

                AutoEnqueuer has two fan-out match sites, and both select subscriptions by object name alone:

                auto-enqueuer.ts:834-835 handleEvent → subscriptions.get(event.object) + subscriptions.get('*')
                auto-enqueuer.ts:934-935 handleBulkEvent → subscriptions.get(event.object!) + subscriptions.get('*')
                

                '*' matches every object. The #13566 repair can give :834 an organization term to discriminate on, because DataEventSchema now carries one. It cannot give :934 one:

                BulkDataEventSchema block, grep "organization" → ZERO hits
                CONTROL: same file, "organizationId" → 2 hits (both in the DataEventSchema region, :247 and :293)
                

                ⇒ After #14970 (producer threading) and #13566 (fan-out filter) both land, the single-record path is closed and the bulk path is still open. One organization's bulk record events still reach another organization's webhook endpoints, full payload, signed with the receiving organization's secret.

                ⛔ This is not a hypothetical hardening card: #13566 is a confirmedpriority:p0 cross-tenant leak — the census measured that a tenant org-admin, not a deployment administrator, can create sys_webhook rows under both walled postures. This card is the half of that leak that the current repair chain does not reach.

                What is asked

                Decide and apply the bulk contract's tenant term — PR #14635's recommendation A, or whatever the seat judges correct now that the single-record shape has shipped and can be copied or deliberately diverged from. Whichever way it goes, please state in the schema JSDoc whether a bulk event's organization is one organization for the whole batch or potentially per-row, because that answer decides whether the fan-out filter can be a single comparison or has to partition the batch — and the fan-out is the hot path the enqueuer exists to keep O(1).

                MetadataEventSchema is named here only for completeness; this seat has not measured whether it has an equivalent exposure and is not claiming it does.

                Sequencing

                Independent of #14970 and #13566 — it neither blocks nor is blocked by them. It should not wait for them, because it is the reason "the leak is closed" will be false when they land.

                Refs: #13566 (the p0 parent) · #14970 (the producer-threading half, packages/objectql) · #14291 / PR #14635 (the single-record term, landed 2aa8456cf, and where open question 1 was recorded) · #13546 / PR #13565 (subscription-side organization cache)

                Activity

                Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

                Metadata

                Metadata

                Assignees

                Labels

                Type

                No type

                Projects

                No projects

                  Milestone

                  No milestone

                  Relationships

                  None yet

                  Development

                  No branches or pull requests

                  Issue actions