Repository navigation
fix(objectql): judge an authored action artifact-shipped by each holder's owner key, not by bare name - #33271
Conversation
…gement per holder on a booted kernel Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016WV4cyWLSMyXZGc6dwhCAu
…er's owner key, not by bare name Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016WV4cyWLSMyXZGc6dwhCAu
…judgement Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016WV4cyWLSMyXZGc6dwhCAu
📓 Docs Drift CheckThis PR changes 1 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 — 21 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 699b186e50e529a73081207e5a948d63fe60838f && git checkout 699b186e50e529a73081207e5a948d63fe60838f
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 24f2f4014d0c9bbd43ec6928942fa44b21c1dff6 efef4782e2d3383e25e732b74136739929681d0e && git checkout -B drift-repro 24f2f4014d0c9bbd43ec6928942fa44b21c1dff6 && git merge --no-ff efef4782e2d3383e25e732b74136739929681d0e
node scripts/docs-audit/affected-docs.mjs --json 24f2f4014d0c9bbd43ec6928942fa44b21c1dff6
|
Contract reviewServed-tier: ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: FAIL S1 (≤30 lines + pin) → delta review by review#3. |
…mes among holders at its owner key Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016WV4cyWLSMyXZGc6dwhCAu
… find bound Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016WV4cyWLSMyXZGc6dwhCAu
|
Contract-review-claim: session_016WV4d1PGptBW3QRQNmy5e6 · head efef478 · 2026-11-14T16:56:44Z review#3. |
Contract reviewServed-tier: Delta over my FAIL at ① Derived judgments
② Semver levelUnchanged from the first record: ③ Boundary flags
Implemented-by: VERDICT: PASS |
|
domain:server#5 · os-elon-musk · session_016WV4cyWLSMyXZGc6dwhCAu
|
Requested by Elon · project thread
Fixes #32990
Clause-②: yes (widening)
Metadata-protocol: none
维护者速读
ObjectQLPlugin绑定环境自写的脚本 action 时,原来只要有任何代码包发布了同名 action 就跳过;现在逐个读每个持有者的同名 action,按它所属的对象判断:只有某个包的同名 action 拥有该行所在对象(或该 action 不绑定对象)时才跳过。声明式.customoverride 也按自己对象上的那个包匹配,不再按名字取第一个包。account、contact上各有approve,环境在lead上自写approve时,fix(runtime): the bare action route serves the environment's own action on an object no holder of a refused name binds #32987 已让/actions/lead/approve服务环境自己的声明,但它的脚本体从未被绑定,调用答 not found。metadata-service)上,不进入任何包的坐标。不改存储、schema、spec。回滚即 revert。packages/objectql/src/plugin.ts(isArtifactShippedAction、新的artifactActionsNamed、isDeclaredArtifactActionOverride中artifacts.find一行)。pnpm --filter @objectstack/objectql exec vitest run src/plugin-authored-actions.test.ts -t 32990,预期 6 条全部通过。What changes
ObjectQLPlugin.resyncAuthoredActionsbinds runtime-authored script actions through the engine's default action runner, and skips a row a code package ships (the app plugin binds that package's handler underapp:PACKAGE). The skip,isArtifactShippedAction, askedregistry.getArtifactItem('action', NAME): any code package's artifact of the bare name, on any object, skipped the row.isArtifactShippedActionnow reads every code package's artifact of the name (artifactActionsNamed) and judges each by the object it owns, with the router's own predicatestandaloneActionOwnsRoute: the row is artifact-shipped when an artifact owns the row's object, an object-less artifact owning every object. The embedded-in-a-packaged-object branch is unchanged.artifactActionsNamed(new, private): the registry's first match by bare name (what one holder always answered) plus each holder's own artifact (packageScopedHoldersOf('action', NAME, null, { includeDisabledPackages: true }), thengetArtifactItemprefer-local per holder), deduplicated. Disabled packages count: a reversible disable never moves a binding.isDeclaredArtifactActionOverride: the base is the artifact on the row's own object among every holder's, not the first artifact by name. With one holder the answer is the same; with two, a declared override of the holder registered second is now recognised.action-handler-coordinate.ts: one docblock sentence follows the new judgement. No code change there.No change to
packages/runtime,packages/metadata-protocol,packages/objectql/src/registry.tsorpackages/spec.FROM → TO
Composition: package A ships a script action
approveon its object, package B one of the same name on its own, and the environment stores its own package-less script actionapproveon a third object. Readings are the engine dispatch on a booted kernel (ObjectKernel+ObjectQLPlugin, itskernel:readyresync).maine540baf8)Action 'approve' on object 'OBJECT' not foundB.customdeclared override of B's action, A registered firstUnchanged, pinned as CONTROLs: a package-less row on an object a holder's action owns stays that holder's customization (the holder's body runs), both orders; an object-less package action keeps a row on any other object unbound; a DISABLED holder registered after another still owns its object.
Row 2 is the one-holder case of the same defect: the declaration the route serves there is unchanged (the environment's own item, as
action-package-addressing.test.ts#32365case 1 pins for the plain read), and what moves is that its body now has a handler. Named here so the seat can confirm it reads within the epic's "one-holder behaviour unchanged" red line (resolution untouched; the dispatch gains a handler).Tests
packages/objectql/src/plugin-authored-actions.test.ts: a new top-level describe[#32990] …, on a booted kernel(the file is this resync's harness). It bootsObjectKernelwithObjectQLPlugin, a row-store driver double and a test plugin standing in for the runtime's AppPlugin (installs the default action runner, registers each package through the manifest service during the cold boot and binds its handler underapp:PACKAGE). Six cases: rows 1, 2 and 3/4 above (both registration orders where order matters), and the three CONTROLs.Premise measured on the unmodified source (the pins committed first,
d2848f11): case 1 answeredAction 'approve' on object 'env_lead' not found; the override case answeredran betawith A registered first.Ablations (each through
scripts/ablation-replace.mjs: mutation landed by anchor count and blob hash, restore verified as blob equal toHEADwith an empty diff; the suite imports./plugin.jsfrom source, so no rebuild is involved):Five ablations, each red
artifactActionsNamed(name).length > 0): 2 failed (two holders; one holder).some(() => false)): 2 failed (the disabled-holder and the object-less CONTROLs).standaloneActionOwnsRoute: 1 failed (the object-less CONTROL).artifacts[0]): 1 failed (the override of the second holder).return falsebefore both branches): 3 failed (the three CONTROLs, the same-object customization among them; that CONTROL is also held by the embedded-object branch, which is why ablation 2 leaves it green).A first attempt at ablation 5 used a replacement that contained its own anchor;
ablation-replacerefused it (anchor count 1 to 1) and restored, so it ran nothing; it was re-run with a different anchor.Acceptance notes
ObjectQL'sarrivesAsDeclaredBase(packages/objectql/src/engine-actions.ts) readsgetArtifactItem('action', NAME)by bare name the same way when a declared base's handler arrives under an armed override. Not measured, not changed here; noted for the seat.isArtifactShippedHookstays name-only; ADR-0150 D4 names it as gaining the package, a separate card.Verification
Head at open:
ab806df2. Local readings so far: the dependency closure built (pnpm --filter '@objectstack/objectql^...' build, lock VERDICT command-exit 0);vitest run src/plugin-authored-actions.test.ts src/action-activation.test.ts: Test Files 2 passed (2), Tests 50 passed (50). The package's full test, its typecheck and the derived gate families follow in theos-dev-reportcomment on #32990; CI: see the check runs.🤖 Generated with Claude Code