You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Approval/automation writers stamp the ACTING org, so flow-opened rows land org-less: records locked behind requests no inbox can surface (write-side repair #9132 deferred) #10131
On the walled single-database SaaS shape (OS_TENANCY_POSTURE=isolated, HotCRM as composed artifact), sys_approval_request and sys_automation_run rows created by flow/trigger writers carry organization_id = NULL while describing records owned by specific customers. The approvals inbox scopes with strict equality against the caller's organization (ApprovalService.buildRequestWhere, packages/plugins/plugin-approvals/src/approval-service.ts:3752-3753), which NULL satisfies for nobody — so the requests are invisible in every inbox, including the owning customer's, while the record lock they hold (org-blind, reads as SYSTEM) stays fully enforced.
This is the write-side repair that PR #9132 explicitly deferred pending a maintainer ruling. #9132 landed the pins and the analysis; nothing tracks the repair itself — this issue does.
Operational impact, over HTTP only: the 9 seeded high-value opportunities read approval_status='pending' and refuse edits with 409 RECORD_LOCKED, yet GET /api/v1/approvals/requests returns 0 rows for the record owner, the other customer, and the platform admin alike. The records are locked behind requests that have no inbox entry, no approver, and no in-product rescue path (the record lock has no admin exemption by design; the sanctioned rescue starts from a request you can find).
Not a leak: the org-less rows are absent from strangers' inboxes too. Availability, not disclosure — but the data is wrong at rest, and which surfaces hide vs. show such rows is decided per-filter instead of by the data.
Root cause (from #9132's own analysis, confirmed at head)
ApprovalService.openNodeRequest stamps ctxOrg = context.organizationId ?? context.tenantId ?? input.organizationId ?? null (approval-service.ts:1563); the automation run store stamps ctx.tenantId. Schedule, time-relative and api triggers set no acting tenant — correctly, a scheduled sweep has no one acting org — so the NULL is every run those triggers produce, not an edge case. Two writers read the actor; the audit writer reads the subject. The disagreement is the defect.
Summary
On the walled single-database SaaS shape (
OS_TENANCY_POSTURE=isolated, HotCRM as composed artifact),sys_approval_requestandsys_automation_runrows created by flow/trigger writers carryorganization_id = NULLwhile describing records owned by specific customers. The approvals inbox scopes with strict equality against the caller's organization (ApprovalService.buildRequestWhere,packages/plugins/plugin-approvals/src/approval-service.ts:3752-3753), whichNULLsatisfies for nobody — so the requests are invisible in every inbox, including the owning customer's, while the record lock they hold (org-blind, reads as SYSTEM) stays fully enforced.This is the write-side repair that PR #9132 explicitly deferred pending a maintainer ruling. #9132 landed the pins and the analysis; nothing tracks the repair itself — this issue does.
Measured (2026-08-19/20, fresh composed boot, framework @
8798cd2, HotCRM @d4ddee0)sys_approval_request: 9/9 rowsorganization_id = NULL, each about acrm_opportunityowned by a real org.sys_automation_run: 12/12 rowsorganization_id = NULL, each about a customer-owned record.sys_audit_log— 1469 rows, all correctly attributed (its writer resolves the org from the subject record:resolveRecordOrganizationField, Audit rows are stamped from the ACTOR's active organization in preference to the record's own — and the record-side fallback cannot seesys_api_key.active_organization_id#8707).approval_status='pending'and refuse edits with409 RECORD_LOCKED, yetGET /api/v1/approvals/requestsreturns 0 rows for the record owner, the other customer, and the platform admin alike. The records are locked behind requests that have no inbox entry, no approver, and no in-product rescue path (the record lock has no admin exemption by design; the sanctioned rescue starts from a request you can find).Root cause (from #9132's own analysis, confirmed at head)
ApprovalService.openNodeRequeststampsctxOrg = context.organizationId ?? context.tenantId ?? input.organizationId ?? null(approval-service.ts:1563); the automation run store stampsctx.tenantId. Schedule, time-relative and api triggers set no acting tenant — correctly, a scheduled sweep has no one acting org — so the NULL is every run those triggers produce, not an edge case. Two writers read the actor; the audit writer reads the subject. The disagreement is the defect.Requested ruling + fix
tenancy.tenantFieldcannot servesys_api_keywithout walling the credential table (#8707 remainder) #8778 scope pin ontenancy.organizationField("one stamp-only declaration key … a consumer other than audit stamping needs its own ruling"). Thesys_api_keydivergence (organizationField≠ wall column) is the case to decide explicitly.organization_id IS NULLrows that name a subject record with an organization.verify-hotcrm-saas.mjschecka4, andapps/objectos-ee/test/hotcrm-multitenant.acceptance.ts("an approval opened with NO acting organization is invisible in every inbox").Cross-refs: objectstack-ai/cloud#1395 (storage fact), objectstack-ai/cloud#1404 (HTTP half), PR #9132 (pins + deferral).