Uh oh!
There was an error while loading. Please reload this page.
Promote resolveRecordOrganizationField to the shared platform-row resolver: approvals + automation runs stamp the SUBJECT record's organization (cloud#1395 Option A) - #11311
Conversation
…d platform-row resolver (#10101) Implements the cloud#1395 Option A ruling: a platform row's organization is the SUBJECT record's organization; actor context is the fallback, never the primary. The resolver moves from plugin-audit to @objectstack/metadata-core (the {spec, zod}-only common home all three sanctioned writers can import with no new cycle); plugin-audit re-exports from its original paths, the approval-request writer stamps subject-first in openNodeRequest, and the automation-run store resolves the trigger record's organization on both its write paths (paused serialize() and terminal recordTerminal()). WIP: tests, i18n bundle regeneration and changeset follow. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01APWX2AwT3a4xDcjPCe8bk4
#10101) - metadata-core: unit pins for the four-limb precedence, the sys_api_key divergence, and the memoized resolver factory (12 tests) - service-automation: the PINNED defect test is PROMOTED per its own instruction; subject-beats-actor, fallback directions, Option-C veto, sys_api_key divergence, paused/terminal agreement, and an engine-to-store end-to-end handoff pin - plugin-approvals: openNodeRequest attribution pins (row + action + approver index move together, both fallback directions, sys_api_key divergence and the ADR-0066 limb-1 non-stamp) - i18n: reworded organization_id help extracted to the en bundle; zh-CN / ja-JP / es-ES hand-translated in the same pass (check-i18n-bundles OK, 9/9) - changeset for the four packages Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01APWX2AwT3a4xDcjPCe8bk4
…est programs (#10101) check:test-source-alias and check:type-source-resolution both red on the new @objectstack/metadata-core edges: without these entries plugin-audit's and plugin-approvals' typecheck and test verdicts would be about metadata-core's dist build state rather than about the source in the checkout. paths + anchored vitest aliases per the gates' own prescription; rootDir widened as the packages/rest #9960 precedent records. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01APWX2AwT3a4xDcjPCe8bk4
📓 Docs Drift CheckThis PR changes 4 package(s): 6 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 2 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 13 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 6ec455c44a76551ed430013f2cc690bd3f02d3c6 && git checkout 6ec455c44a76551ed430013f2cc690bd3f02d3c6
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin ab23c67abc2c99db9b58e70d46c438cd2c47151c 45393d212b1207df73f026a0ff3c0e8c6257e0cc && git checkout -B drift-repro ab23c67abc2c99db9b58e70d46c438cd2c47151c && git merge --no-ff 45393d212b1207df73f026a0ff3c0e8c6257e0cc
node scripts/docs-audit/affected-docs.mjs --json ab23c67abc2c99db9b58e70d46c438cd2c47151c
|
合同审核已通过(PASS),判定记录见卡 #10101。 Generated by Claude Code |
Uh oh!
There was an error while loading. Please reload this page.
Refresh the #8778 scope-pin annotation and the .describe() beside tenancy.organizationField in packages/spec/src/data/object.zod.ts. The annotation (landed by #10999) said consumer 1 (audit stamping) was "still the only one wired up" and consumers 2/3 (approvals, automation runs) were "sanctioned but not yet implemented". #10101's PR #11311 (merged 2026-08-23) landed both: resolveRecordOrganizationField was promoted to a shared resolver in @objectstack/metadata-core and all three sanctioned platform-row writers now call it. Also corrects the resolver-home naming ("plugin-audit's resolveRecordOrganizationField" -> @objectstack/metadata-core's, plugin-audit re-exports). Prose accuracy only: the pin's load-bearing "exactly THREE consumers, and no others" sentence and the fourth-consumer refusal posture are unchanged verbatim. No accept/reject behaviour change, no schema shape change, no new keys.
Fixes#10101
Implements the maintainer ruling recorded on cloud#1395 (2026-08-17T03:18Z, 「新进卡六张 同意你的建议」), transcribed into the spec annotation by #10999:
yes(fixed by the ruling): this PR is a draft and STAYS a draft;needs:contract-reviewstays on the card. No ready-flip, no auto-merge.Where the shared resolver lives, and why
@objectstack/metadata-core, new modulerecord-organization.ts—resolveRecordOrganizationFieldmoved verbatim from plugin-audit, pluscreateFieldPresenceProbe(its companion) and a new memoized factorycreateRecordOrganizationResolver(itsorganizationOfmethod takes an object name plus candidate records and returns the subject record's organization as a string, or null when unresolvable). This is the package's documented criterion for exactly this situation — the #5619 engine dispatch predicates, the ADR-0106 FLS projection and the #6190 org-scope predicate were each "sunk here" because the consumers span packages with no other common home, and metadata-core depends on@objectstack/specplus zod only, soplugin-audit,plugin-approvalsandservice-automationall import it with no new cycle. Placing it in@objectstack/objectqlinstead would have forced two writer packages to take the whole query engine as a dependency for one pure function;packages/specis barred (no business logic, and zero spec ownership in this lane). ⛔ There is ONE implementation; plugin-audit re-exports from its original paths (public surface unchanged), so this is not Option B by any door.The three writers
plugin-audit/src/audit-writers.ts): imports the shared resolver; local definitions deleted; behavior identical (its 313-test suite is untouched and green).plugin-approvals/src/approval-service.ts,openNodeRequest— the onlysys_approval_requestinsert site): resolvesrequestOrgas the subject record's organization (live record first, trigger snapshot second) with the previousctxOrgas fallback.requestOrgfeeds the request row, bothsys_approval_actioninserts, thesys_approval_approverindex AND the approver-slate expansion — all the places that mean "this request's organization" move together; a slate resolved in a different organization than the wall the request lands behind would be approvers who cannot see the request they are asked to decide.service-automation): both write paths ofObjectStoreSuspendedRunStoreresolve from the trigger-record snapshot with the acting tenant as fallback —serialize()for paused rows,recordTerminal()for terminal rows (which previously carried NO organization at all: nothing ever setRunRecord.organizationId). The engine now threads the run context intorecordLogso the terminal record carries the two attribution inputs (acting tenant + trigger-record snapshot;RunRecord.triggerRecordis a write-time input, not a column). Same inputs, same precedence on both paths, so a run's paused row and its terminal row agree by construction — pinned by a test.The
sys_api_keydivergence — explicitly NOT flattenedtenancy.organizationFieldanswers who a row is ABOUT;tenantField/organization_idanswers what an object is WALLED by. They deliberately diverge for credential tables (#8287). Pinned three times: at the resolver (metadata-core unit tests — limb 0 wins over the ADR-0066 opt-out, and the missing-column guard falls through), at the approval writer (stampsactive_organization_id; and an ADR-0066 platform-global object with an org FK but noorganizationFielddoes NOT stamp from the FK), and at the automation store. Nothing here touchesapplyTenantScopeor any wall.Both directions pinned (the fallback survives)
Every writer's suite pins: subject beats actor when both resolve; actor answers when the subject is unresolvable (no org column, no record, no schema access on a test double); tenant-less AND subject-less stays NULL — fabricating an acting organization for schedule/api triggers stays vetoed (Option C).
The three
promote, never repairpinsThe framework pin is PROMOTED in this PR per its own instruction:
suspended-run-store.test.ts's pinned-defect test now asserts the organization resolved from the trigger record (reverse-verified — see below). The two cloud-side pins (hotcrm-multitenant.acceptance.ts, checka4inverify-hotcrm-saas.mjs) are tracked on cloud#1395 and follow at the next.objectstack-shabump — the framework side has landed once this merges; ping the repo:cloud seat (objectstack#6026). This session cannot reach the cloud repo (measured constraint).The #10131 backfill question — answered: DECLINED, with reasons and a filed card
The measured stranded population (9/9
sys_approval_request, 12/12sys_automation_runorg-less rows naming subjects that have organizations) is NOT repaired by this PR. A backfill writes to existing production rows — its own blast radius, which should not ride a contract-review-tier behavior change. Cost of declining, stated: pending org-less approval rows keep locking their records invisibly until repaired (in-product recovery stays the #3424 admin override); terminal automation rows age out anyway under the object's declared 30-day retention; paused rows stay unattributed until resumed. Filed as #11308 (blocked by this PR: a backfill must use this same shared resolver, never a second one).Out-of-scope finding, filed: the spec annotation's prose is now stale by its own design
The #10999 annotation says the
.describe()"still speaks of audit rows … and #10101 updates it as the readers actually land" — but this lane has zeropackages/specownership (its spec slice was exactly #10999), so that prose refresh is filed as #11307 for the spec seat rather than ridden here. The annotation's load-bearing property — exactly THREE sanctioned consumers, "and no others", a fourth needs its own ruling — is untouched, and the moved resolver's documentation restates it.Also in this diff
organization_idfield descriptions (ADR-0120 terminology; the old text claimed a propagation that measurably did not happen). i18n bundles regenerated (check:i18nOK, 9/9 packages); zh-CN / ja-JP / es-ES hand-translated in the same pass.plugin-auditandplugin-approvalsnow resolve@objectstack/metadata-coreto SOURCE in their tsc and vitest programs (tsconfigpaths+ anchored vitest aliases;rootDirwidened per thepackages/rest[finding]protocol.deletePackagehas no declared spec shape — three hand-rolled types that disagree, and the runtime twin reaches it throughas any#9960 precedent) —check:test-source-aliasandcheck:type-source-resolutionboth demanded it for the new dependency edges and are green.Verification (all at the final commit
45393d212b, clean tree, exit codes captured before any pipe)os-verify-lock: VERDICT command-exit 0.serialize()resolution to actor-only turned exactly the 4 subject-first automation tests red (28 fallback pins stayed green); revertingrequestOrgtoctxOrginopenNodeRequestturned exactly the 4 subject-first approval tests red (14 stayed green). Both mutation scripts carried trap-based restore; both suites re-ran green after restore. No dist leg applies: these suites resolve the mutated files from source (vitest in-package relative imports).node scripts/pm/dispatch-gates.mjs(no hand-supplied paths) at45393d212b; every named family run to a real verdict, all green: changeset-gate-self-tests · objectui-changeset · override-consistency · published-files · slot-lookup · test-source-alias · type-source-resolution · adr-0087-registration · changeset-fixed · changeset-no-major · ci-filter-parity · empty-changeset · osv-exemptions · plugin-teardown-shape · affected-docs · query-options-erasure · where-matcher · engine-double-contract · cross-package-test-inputs · i18n · nul-bytes · type-check-coverage · type-check-debt (re-measure OK after rebuilding the 7 packages its preflight named). The two type-check ratchets ran on the byte-identical tree the final commit captured (zero dirty files at commit time); everything else re-ran after the commit.check:engine-double-contractOK, no ledger change).Generated by Claude Code
Generated by Claude Code