Found while building the update_record half of the scheduled-sweep organization guard (#1363). Out of scope for that card, whose file surface is the guard test only — the fix belongs in the flow, not in the guard. Filing so the class does not go unrecorded. Unassigned; severity is for triage.
What the new guard reported
The #1363 guard resolves each interpolation token in a scheduled update_record's config.fields back to its source. On forecast_snapshot it names this chain for all four amount columns:
forecast_snapshot . write_snapshot . fields.pipeline_amount = {pipelineTotal}
`pipelineTotal` from `add_pipeline` : `current_pipeline` from `sum_pipeline` :
`pipelineOpps` is an independently-fetched crm_opportunity collection
(`find_pipeline`) — its filter pins no `organization_id`
…and identically for best_case_amount, commit_amount and closed_amount.
Why that is not just a guard artefact
The sweep runs runAs: 'system', so its reads are unconstrained by the driver's organization predicate — measured on a real engine in test/saas-composition.test.ts. Its per-owner loop iterates sys_user, and sys_user declares no organization_id: identity is global, which test/flow-scheduled-org-partition.test.ts already states in its header and which is exactly why the sweep's create_record has to bind {ownerAnyDeal.organization_id} rather than {currentOwner.organization_id}.
The four bucket queries scope by owner_id: '{currentOwner.id}' and the target row's own period window. Neither is an organization predicate. So for an owner holding opportunities in more than one organization:
OWNED_PERIOD_FILTER carries no organization_id either, so the owner gets one crm_forecast row, not one per organization;
- that row's
organization_id is whichever organization ownerAnyDeal happened to return at create time;
- and its four amounts are the sum across every organization the owner has deals in.
The result is one tenant's forecast row reporting another tenant's pipeline. No NULL partition, no index violation, no error — the numbers are just wrong, and wrong in the direction that looks plausible.
Why this was not visible before
#1363's six-row source table records forecast_snapshot's source as "the swept row's own aggregates", which is the right reading in the single-organization shape the sweep was written for, and the reason the card treated it as safe. That reading depends on "the swept row" being the owner and the owner being inside one organization. sys_user being global is what breaks it, and it only became reachable when PR #1362 made the multi-organization composition a published shape. forecast_snapshot is registered in that composition — the SaaS shape drops demo_bootstrap and exactly nothing else.
Current state
Not silently passed over: #1363's guard flags all four columns, and they are held in that file's ORGANIZATION_NEUTRALITY_EXEMPTIONS register with this residual written out and pointing here. The register is checked from both sides, so the entry cannot rot — but an exemption is a note, not a fix.
A possible shape, if triage wants one
Pin the four bucket fetches to the target row's organization — organization_id: '{currentForecast.organization_id}' in inPeriod, the same "prove the source carries the right organization" mechanism {ownerAnyDeal.organization_id} already establishes. That alone would make the guard clear the node with no exemption. It leaves the prior question open: whether a cross-organization owner should get one snapshot row per organization, which is a change to CURRENT_PERIOD_FILTER and to what crm_forecast is keyed by, and is a decision rather than a fix.
Generated by Claude Code
Generated by Claude Code
Found while building the
update_recordhalf of the scheduled-sweep organization guard (#1363). Out of scope for that card, whose file surface is the guard test only — the fix belongs in the flow, not in the guard. Filing so the class does not go unrecorded. Unassigned; severity is for triage.What the new guard reported
The #1363 guard resolves each interpolation token in a scheduled
update_record'sconfig.fieldsback to its source. Onforecast_snapshotit names this chain for all four amount columns:…and identically for
best_case_amount,commit_amountandclosed_amount.Why that is not just a guard artefact
The sweep runs
runAs: 'system', so its reads are unconstrained by the driver's organization predicate — measured on a real engine intest/saas-composition.test.ts. Its per-owner loop iteratessys_user, andsys_userdeclares noorganization_id: identity is global, whichtest/flow-scheduled-org-partition.test.tsalready states in its header and which is exactly why the sweep'screate_recordhas to bind{ownerAnyDeal.organization_id}rather than{currentOwner.organization_id}.The four bucket queries scope by
owner_id: '{currentOwner.id}'and the target row's own period window. Neither is an organization predicate. So for an owner holding opportunities in more than one organization:OWNED_PERIOD_FILTERcarries noorganization_ideither, so the owner gets onecrm_forecastrow, not one per organization;organization_idis whichever organizationownerAnyDealhappened to return at create time;The result is one tenant's forecast row reporting another tenant's pipeline. No NULL partition, no index violation, no error — the numbers are just wrong, and wrong in the direction that looks plausible.
Why this was not visible before
#1363's six-row source table records
forecast_snapshot's source as "the swept row's own aggregates", which is the right reading in the single-organization shape the sweep was written for, and the reason the card treated it as safe. That reading depends on "the swept row" being the owner and the owner being inside one organization.sys_userbeing global is what breaks it, and it only became reachable when PR #1362 made the multi-organization composition a published shape.forecast_snapshotis registered in that composition — the SaaS shape dropsdemo_bootstrapand exactly nothing else.Current state
Not silently passed over: #1363's guard flags all four columns, and they are held in that file's
ORGANIZATION_NEUTRALITY_EXEMPTIONSregister with this residual written out and pointing here. The register is checked from both sides, so the entry cannot rot — but an exemption is a note, not a fix.A possible shape, if triage wants one
Pin the four bucket fetches to the target row's organization —
organization_id: '{currentForecast.organization_id}'ininPeriod, the same "prove the source carries the right organization" mechanism{ownerAnyDeal.organization_id}already establishes. That alone would make the guard clear the node with no exemption. It leaves the prior question open: whether a cross-organization owner should get one snapshot row per organization, which is a change toCURRENT_PERIOD_FILTERand to whatcrm_forecastis keyed by, and is a decision rather than a fix.Generated by Claude Code
Generated by Claude Code