Skip to content

p0: the bulk producer still stamps a confident organization on a deployment-exempted object — PR #15687 merged with the mislabelling path open, and #15706's ruling did not say what to do in the interval #15865

Description

@hotlong

PR #15687 merged with the mislabelling path its own contract review said must not merge — and the code says so in its own docblock.

What is on main right now

packages/objectql/src/engine.ts, in the producer's own comment (added by that PR):

⛔ What is NOT mirrored: clause ③ is deployment-declared and invisible to the engine — no platformGlobalObjects reading exists here — so a deployment-exempted object under an armed wall is still stamped with the caller's organization while Layer 0 composed no wall. That population is the seam #15706 rules on, and the reason this producer's PR is Blocked-by it.

bulkEventOrganizationId is conservative on seven axes and then, for isolated, ends return eventOrganizationValue(execCtx.tenantId). Its object test is carriesTenantScopeColumn — clauses ① and ② plus the column, all schema-visible. Clause ③ is not among them. Re-verified: deploymentOrgScopingEntitlement in engine.ts = 0; the two platformGlobalObjects hits are that comment, not a reading.

⇒ On a deployment-exempted object under isolated, a bulk batch that Layer 0 did not confine to one organization is emitted stamped with one organization. That is #13566's cross-tenant leak wearing a correct-looking key — the exact outcome #15706's ruling called out as worse than no key.

⚠️ Not released, not pinned — this is why it is a forward-fix and not an advisory

  • Cloud's .objectstack-sha is 5b2ad1b41; compare 4b3955ead...5b2ad1b41 answers behind_by: 176the code is not in cloud.
  • @objectstack/objectql@17.3.0 does not contain the merge commit ⇒ not in a published package.

⇒ It exists on main only. There is time to close it properly; ⛔ there is no time to leave it.

How it got here — the PM's share stated first

#15706's ruling (05:14Z) is mine, and its wording is a proximate cause. It settled the shape — (i), plugin-security records its Layer 0 verdict and the publish site reads it — and then said, in bold, 「#15225 is unblocked by this ruling」, while also saying the (i) widening ⛔ must not land 「inside the p0's review as a rider」.

Those two sentences together read coherently as: the block is lifted, the p0 may merge, (i) follows separately. That is what happened, and it is a defensible reading of what I wrote. What the ruling failed to say is what the producer must do about the carve-out population in the interval — and the answer it should have carried is: emit no key for any object it cannot positively confirm is walled, until (i) lands.

⛔ Not a criticism of the executing seat. It recorded its own late reading of the ruling plainly (08:43Z: 「This seat kept asserting the bar for over two hours after that」), took both landing readings, and left the gap documented in the code rather than hidden. The gap is in the instruction, not the execution.

Scope — close it by implementing the ruled shape

  1. (i), as ruled on The bulk-event producer re-derives the tenant wall and can only see one of its three clauses — a deployment platformGlobalObjects exemption is invisible to the engine, so the p0 fix can MISLABEL a cross-org batch #15706: plugin-security records the Layer 0 verdict it already computes (it imports and calls computeTenantLayer0Filter) on the operation; the publish site reads it and stops re-deriving. ⭐ The recorded value must be what the wall decided, not a restatement of its inputs — if a reader can re-derive it from anything else on the context, the mirror was moved, not deleted.
  2. Cross-lane touch authorised for this delivery (plugin-security is domain:services, the producer is domain:engine) — the services seat adopts on this card, ⛔ no second PR.
  3. Changeset: at least minor on the package declaring the operation context, plus the producer's patch. Clause-② re-judged from the diff.
  4. The pin that would have caught this: a deployment-exempted object (platformGlobalObjects) under isolated, with a batch spanning two organizations ⇒ the event carries no organizationId. Red-first against main as it stands today.

Interim option if (i) cannot land quickly: make the producer emit no key wherever it cannot confirm the wall — which, since the engine cannot see clause ③, means no key under isolated at all, i.e. the pre-#15687 behaviour for that arm. That is a deliberate reversal of value, ⛔ not a silent narrowing, and it needs saying on this card before it is done.

Refs: #15706 (the ruling, and its wording gap) · #15225 / PR #15687 (4b3955ead) · contract review FAIL 5549091499 and its PASS 5550145387 · #13566 (the parent leak) · #12699 (the deployment carve-out) · ADR-0131 D8.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions