Uh oh!
There was an error while loading. Please reload this page.
fix(messaging): classify the delivery dispatchers' updateMany sweeps as global environment sweeps - #10725
Conversation
📓 Docs Drift CheckThis PR changes 1 package(s): 1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
What this run could not see
Coarse fallback — 4 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 38981505bfa2c4b518660e0e3b17667e57c1ca54 && git checkout 38981505bfa2c4b518660e0e3b17667e57c1ca54
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 8f04d9a4ff8d50f84783a20c70b73a291be9c0c7 72607c032fcfddec9eac469e984ed01964455e11 && git checkout -B drift-repro 8f04d9a4ff8d50f84783a20c70b73a291be9c0c7 && git merge --no-ff 72607c032fcfddec9eac469e984ed01964455e11
node scripts/docs-audit/affected-docs.mjs --json 8f04d9a4ff8d50f84783a20c70b73a291be9c0c7
|
Uh oh!
There was an error while loading. Please reload this page.
Fixes#10673
On a walled deployment (
OS_TENANCY_POSTURE=isolated|group) the SQL driver's tenant-audit gate reported both delivery objects:The card is explicit that silencing the line is not a fix — the audit exists to force a per-call-site classification. This PR does that classification and nothing else.
Call-site census
Six
multi: true(⇒driver.updateMany) writes exist on these two objects. All six are on the dispatcher claim path:sql-outbox.tsclaim()step 1sql-outbox.tsclaim()step 3sql-outbox.tsclaimDigest()step 1sql-outbox.tsclaimDigest()step 3sql-http-outbox.tsclaim()step 1sql-http-outbox.tsclaim()step 3The census is not a bare
updateManygrep:driver.updateManyis reached throughengine.update(..., { multi: true }), so the search term ismulti: truescoped to the two outbox implementations, cross-checked against everyengine.updatein both files (the remaining three aremulti: falsesingle-record writes —ackin both outboxes andredeliver— which the driver audits under theupdateop, notupdateMany, and which this card does not cover).Classification: all six are global dispatcher sweeps
Not a judgement of taste — three structural facts, all checked against the tree:
NotificationDispatcher.runPartition()andHttpDispatcher.runPartition(), driven by asetIntervaltick under a cluster lock keyednotify.dispatcher.partition.N/http.dispatcher.partition.N. No HTTP request, no session, no active organization.ClaimOptions/HttpClaimOptionsare{ nodeId, limit, partition, claimTtlMs, now }. Partitioning ishash(refId | notificationId | digestKey) mod N— a load-spreading key, deliberately not an organization key — so a partition holds rows from every organization by construction.messaging-service-plugin.ts) and drain the whole environment's queue. Anorganization_id =predicate naming a single org on the claim would strand every other organization's deliveries forever; the same predicate on the reap would leave rows a crashed node abandoned permanentlyin_flightfor every other organization.So each site now passes
bypassTenantAuditthrough one documented helper,outbox-dispatcher-scope.ts, which carries that warrant in full, with a one-line pointer at each site. Per its spec the flag is diagnostics-only and "never changes what the write touches" — and the tests below pin that it changed nothing.Verification
The card's repro (EE 4.1.0 image + composed boot +
docker compose logs app) is not runnable in this container. Declared narrowing: it was not run and no composed log is claimed. What stands in its place is the instrument's own criterion —SqlDriver.auditMissingTenant— exercised end to end indelivery-claim-tenant-audit.integration.test.ts: a realSqlDriveron better-sqlite3, realsyncSchemas(), the real liveOS_TENANCY_POSTUREread, the production outboxes, and the driver's own logger as the assertion surface. Before the change that harness reproduces both card lines verbatim.Each test pins three directions:
[tenant-audit] updateManyline for its object;organization_idsurvives, so an implementation that touched nothing, or one that scoped per organization, goes red;multi: truewrite on the same object through the same driver must still produce the audit line. Without it, a silent run would prove nothing about the code under test — it would equally match an object that stopped being tenant-scoped or a posture that stopped resolving. The control runs after the assertion it guards because the gate throttles one warning per object-and-op pair.Ablation (signature predicted before mutating): flipping
bypassTenantAudittofalseinsrc/, with no rebuild, turns exactly the four audit assertions red withexpected true to be false, and zero "positive control failed" — matching the prediction, and proving the tests readsrc/rather than a built artifact.git checkout HEAD --restored the file byte-identically (git hash-object=e8e59054e802ecd60344682d6f46d20cf8bea6c8, equal to the HEAD blob) and the suite returned to 4 passed.Package suite at final commit
72607c0:Test Files 24 passed (24),Tests 248 passed (248);tsc --noEmitclean.Gate union derived with
node scripts/pm/dispatch-gates.mjs(no path arguments) after the final commit on a clean tree, all green at72607c0— includingcheck:i18n("OK — 9 package(s) — all bundles in sync"),check:type-check-debt --re-measure("33 ledger entr(ies) re-measured ... none above its recorded number"),check:engine-double-contract,check:where-matcher,check:test-source-aliasandcheck:nul-bytes. The derivation did not namecheck:route-envelopeorcheck:dispatcher-error-vocabulary; both were run explicitly with--self-testand both pass.check:query-options-erasureis green, and its self-test printspackages/spec/src/migrations/registry.ts parses at --stack-size=4000 through this gate's own channel— the reported parser-stack red does not reproduce here.Out of scope
The
crm_contracthalf of the upstream report stays onobjectstack-ai/cloud#1512and is not addressed here; that card remains open.