Filed unassigned and ungraded by the repo:cloud execution seat (objectstack#6026, session session_01SJreRVQqZy9s656hwafZkD) so that a ruled, three-day-old cross-repo decision has a card at the repo where its fix lands. Grading, domain:* and type are the triage seat's to mint — I have deliberately set none.
Why this exists: the ruling below was recorded on 2026-08-17, the unambiguous half landed as PR #9132 the same morning, and the write-side half has had no card since. Meanwhile cloud#1395 has been sitting labelled pm:queue, i.e. reading as dispatchable cloud work, when the only thing cloud can actually do is follow the pins after this lands. That mislabel is repaired in the same stroke as this filing; cloud#1395 now carries Blocked-by: pointing here.
The ruling being implemented (⛔ not re-litigable)
Maintainer, 2026-08-17T03:18Z, accepting the decision-inbox recommendations in full — verbatim: 「新进卡六张 同意你的建议」. Recorded on cloud#1395:
Ruled: Option A — extend the #8778 ruling: resolveRecordOrganizationField is promoted to a shared resolver used by all three platform-row writers (approvals, automation runs, audit). A platform row's organization is the SUBJECT record's organization; actor context is the fallback, never the primary.
- Option B (writer-local resolution) is rejected, on the dev's own argument, endorsed: it adds a third answer to a question the codebase already answers two ways — the exact disease the card exists to end.
- Option C (fabricating an acting org for schedule/api triggers) stays vetoed, as measured.
What is already done, so nobody redoes it
PR #9132 (MERGED 2026-08-17T05:24Z) landed only the unambiguous half and was explicit that it did so:
- removed the phantom alias
ctx.organizationId in serialize() (AutomationContext declares tenantId, and no producer ever wrote organizationId) — the one test covering that column had been feeding the phantom key, so the column's only coverage exercised a path production cannot reach; - documented the measured defect and its negative control on both declarations;
- PINNED the current behaviour with a
promote, never repair instruction.
⇒ Zero behaviour change landed. The write-side attribution repair is this card.
The blocker this card must clear first
PR #9132 named it precisely: reusing the audit resolver is blocked by an explicit scope pin in packages/spec/src/data/object.zod.ts:
Scope-pinned by the #8778 ruling: this is ONE stamp-only declaration key, not the opening move of a general field-roles mechanism — a consumer other than audit stamping needs its own ruling before reading it.
The ruling widens that pin by name, and requires the widening be declared rather than discovered:
Scope pin change: the #8778 scope pin … is widened by this ruling — the annotation next to it must name this ruling and the three sanctioned consumers, so the widening is declared, not discovered. That slice routes to the spec seat.
⇒ This is contract-first and splits in two: the spec slice (pin annotation widening) lands first, and the resolver promotion in plugin-approvals + the automation-run recorder follows it. The spec slice is not carved yet either; whichever seat takes this should carve it and link it with Blocked-by:.
⚠️ The fork PR #9132 declined to pick silently is real, not procedural, and survives into this card: tenancy.organizationField answers "which column says who this row is ABOUT"; tenantField / organization_id answers "what is this object WALLED by". They coincide for ordinary business objects and deliberately do not for sys_api_key — and an approval row and an audit row about the same record landing in different organizations would be the same two-tables-disagree pathology this work exists to end. The ruling picks the subject-record answer; the implementation still has to not break the objects where the two diverge.
Three pins lift in the same batch — this is the part most likely to be dropped
All three exist today and all three carry the same promote, never repair instruction. They must move together or they will contradict each other:
| pin | location | repo |
|---|
framework PINNED (from PR #9132) | service-automation suite | objectstack |
PINNED suite | hotcrm-multitenant.acceptance.ts | cloud (landed via cloud PR #1409) |
check a4 | scripts/dev-local/verify-hotcrm-saas.mjs | cloud |
The two cloud-side pins are this seat's follow-through and are tracked on cloud#1395. Ping objectstack#6026 when the framework side lands; ⚠️ note that cloud consumes framework at the pin (.objectstack-sha), so cloud's half additionally waits on a pin bump after this merges.
Severity basis, on record
The mechanism is live: an approval opened with no acting organization locks the record it is about while being invisible in every inbox, its owner's included — measured over HTTP, not inferred (buildRequestWhere uses strict equality under a SYSTEM context, so the owner's match fails by the same failure as a stranger's; the record lock also reads pending requests as SYSTEM, so the lock holds on a row no inbox can show). The nine measured rows sit in the platform admin's own organization only because HotCRM currently declares no schedule/api-triggered approval flows — an app that has one exposes its paying customers to it.
The ruling also fixes the tier for the framework slice: contract-behaviour change on the write path ⇒ claude-fable-5.
Dedup
Scanned the 100 most-recently-updated of the 212 open issues in this repo touched since 2026-08-16 — no card names the resolver promotion, the approvals/automation organization attribution, or the #8778 pin widening. PR #9132 is the parent and is closed. ⚠️ Honest limit: this repo's issue search returned results inconsistent with its own in:title qualifier during this check, so I used the deterministic listing instead and did not page past the first 100. If a card for this already exists further down, close this as a duplicate rather than trusting my zero.
Refs: cloud#1395 (the ruled card and the measurement) · PR #9132 (the landed half) · #8778 and #8287 (the scope pin and its ruling) · #8707 (resolveRecordOrganizationField) · cloud#1409 · cloud#1239 (the opposite arm, on the control plane) · cloud#1406 (the read-side arm — different fork, must not be merged with this one)
Filed unassigned and ungraded by the
repo:cloudexecution seat (objectstack#6026, sessionsession_01SJreRVQqZy9s656hwafZkD) so that a ruled, three-day-old cross-repo decision has a card at the repo where its fix lands. Grading,domain:*and type are the triage seat's to mint — I have deliberately set none.Why this exists: the ruling below was recorded on 2026-08-17, the unambiguous half landed as PR #9132 the same morning, and the write-side half has had no card since. Meanwhile cloud#1395 has been sitting labelled
pm:queue, i.e. reading as dispatchable cloud work, when the only thing cloud can actually do is follow the pins after this lands. That mislabel is repaired in the same stroke as this filing; cloud#1395 now carriesBlocked-by:pointing here.The ruling being implemented (⛔ not re-litigable)
Maintainer, 2026-08-17T03:18Z, accepting the decision-inbox recommendations in full — verbatim: 「新进卡六张 同意你的建议」. Recorded on cloud#1395:
What is already done, so nobody redoes it
PR #9132 (MERGED 2026-08-17T05:24Z) landed only the unambiguous half and was explicit that it did so:
ctx.organizationIdinserialize()(AutomationContextdeclarestenantId, and no producer ever wroteorganizationId) — the one test covering that column had been feeding the phantom key, so the column's only coverage exercised a path production cannot reach;promote, never repairinstruction.⇒ Zero behaviour change landed. The write-side attribution repair is this card.
The blocker this card must clear first
PR #9132 named it precisely: reusing the audit resolver is blocked by an explicit scope pin in
packages/spec/src/data/object.zod.ts:The ruling widens that pin by name, and requires the widening be declared rather than discovered:
⇒ This is contract-first and splits in two: the spec slice (pin annotation widening) lands first, and the resolver promotion in
plugin-approvals+ the automation-run recorder follows it. The spec slice is not carved yet either; whichever seat takes this should carve it and link it withBlocked-by:.tenancy.organizationFieldanswers "which column says who this row is ABOUT";tenantField/organization_idanswers "what is this object WALLED by". They coincide for ordinary business objects and deliberately do not forsys_api_key— and an approval row and an audit row about the same record landing in different organizations would be the same two-tables-disagree pathology this work exists to end. The ruling picks the subject-record answer; the implementation still has to not break the objects where the two diverge.Three pins lift in the same batch — this is the part most likely to be dropped
All three exist today and all three carry the same
promote, never repairinstruction. They must move together or they will contradict each other:PINNED(from PR #9132)service-automationsuitePINNEDsuitehotcrm-multitenant.acceptance.tsa4scripts/dev-local/verify-hotcrm-saas.mjsThe two cloud-side pins are this seat's follow-through and are tracked on cloud#1395. Ping objectstack#6026 when the framework side lands;⚠️ note that cloud consumes framework at the pin (
.objectstack-sha), so cloud's half additionally waits on a pin bump after this merges.Severity basis, on record
The mechanism is live: an approval opened with no acting organization locks the record it is about while being invisible in every inbox, its owner's included — measured over HTTP, not inferred (
buildRequestWhereuses strict equality under a SYSTEM context, so the owner's match fails by the same failure as a stranger's; the record lock also reads pending requests as SYSTEM, so the lock holds on a row no inbox can show). The nine measured rows sit in the platform admin's own organization only because HotCRM currently declares no schedule/api-triggered approval flows — an app that has one exposes its paying customers to it.The ruling also fixes the tier for the framework slice: contract-behaviour change on the write path ⇒
claude-fable-5.Dedup
Scanned the 100 most-recently-updated of the 212 open issues in this repo touched since 2026-08-16 — no card names the resolver promotion, the approvals/automation organization attribution, or the #8778 pin widening. PR #9132 is the parent and is closed.⚠️ Honest limit: this repo's issue search returned results inconsistent with its own
in:titlequalifier during this check, so I used the deterministic listing instead and did not page past the first 100. If a card for this already exists further down, close this as a duplicate rather than trusting my zero.Refs: cloud#1395 (the ruled card and the measurement) · PR #9132 (the landed half) · #8778 and #8287 (the scope pin and its ruling) · #8707 (
resolveRecordOrganizationField) · cloud#1409 · cloud#1239 (the opposite arm, on the control plane) · cloud#1406 (the read-side arm — different fork, must not be merged with this one)