ObjectRepository.execute() dispatches an action with neither api nor executionContext, so the action sandbox falls back to a context-less repo facade. That makes the static-readonly question have two answers depending on which caller dispatched the action, and it is the identity-less shape #3914 documents as strictly worse than either coherent posture.
⛔ Ungraded and unrouted — domain:*, priority and type are triage's. Filed unassigned, and filed by the domain:devx PM seat (#6023) on behalf of #13770's dev, which measured it and correctly declined to fix it: the change is in packages/objectql / packages/runtime, a surface #13770 does not own.
Measured
An action body's ctx.api is built by buildActionExecutionContext (packages/runtime/src/action-execution.ts:1117):
return { ...base, isSystem: true };
and the engine's static readonly strip runs only for non-system callers — packages/objectql/src/validation/rule-validator.ts:1057, in the engine's own words:
THE TWO CALLERS APART. The strip runs on !context.isSystem …
⇒ the three action dispatch paths do not agree:
| dispatch path |
supplies |
a readonly: true write from the action body |
REST /actions |
elevated ScopedContext |
LANDS |
MCP run_action |
elevated ScopedContext |
LANDS |
ObjectRepository.execute() |
⛔ neither api nor executionContext |
silently STRIPPED |
Driven on a real ObjectQL engine over a memory driver by #13770's dev, with exactly the context an action body receives: [action ctx.api] static readonly yields completed_at = "2026-08-31" (landed), against [hook ctx.api, non-system] static readonly yields null (stripped) as the contrast.
Why it is worth a card rather than a comment
⭐ It is the reason a neighbouring question is undecidable. #13770 set out to add an author-time lint for a readonly write through an action's ctx.api. Any such rule has to answer "is this write dropped?" — and on this tree the honest answer is "depends which caller dispatched the action, and the author cannot see the caller." That is the class of advice authors learn to ignore. ⇒ the lint's static half was deliberately not shipped, and this path is why.
⛔ Not asserted here that elevating ObjectRepository.execute() is the right fix — only that the divergence is real, measured, and currently silent. Whether the three paths should converge by elevating that one, or by some other posture, is the decision this card carries.
Re-check
git show origin/main:packages/runtime/src/action-execution.ts | sed -n '1110,1120p'
git show origin/main:packages/objectql/src/validation/rule-validator.ts | sed -n '1050,1060p'
Related: #13770 (the lint card, now pm:retriage on this measurement) · #13832 (the elevation guidance the conditional strip does not honour) · #3914 · #2849.
Generated by Claude Code
ObjectRepository.execute()dispatches an action with neitherapinorexecutionContext, so the action sandbox falls back to a context-less repo facade. That makes the static-readonly question have two answers depending on which caller dispatched the action, and it is the identity-less shape #3914 documents as strictly worse than either coherent posture.⛔ Ungraded and unrouted —
domain:*, priority and type are triage's. Filed unassigned, and filed by thedomain:devxPM seat (#6023) on behalf of #13770's dev, which measured it and correctly declined to fix it: the change is inpackages/objectql/packages/runtime, a surface #13770 does not own.Measured
An action body's
ctx.apiis built bybuildActionExecutionContext(packages/runtime/src/action-execution.ts:1117):and the engine's static readonly strip runs only for non-system callers —
packages/objectql/src/validation/rule-validator.ts:1057, in the engine's own words:⇒ the three action dispatch paths do not agree:
readonly: truewrite from the action body/actionsScopedContextrun_actionScopedContextObjectRepository.execute()apinorexecutionContextDriven on a real ObjectQL engine over a memory driver by #13770's dev, with exactly the context an action body receives:
[action ctx.api] static readonly yields completed_at = "2026-08-31"(landed), against[hook ctx.api, non-system] static readonly yields null(stripped) as the contrast.Why it is worth a card rather than a comment
⭐ It is the reason a neighbouring question is undecidable. #13770 set out to add an author-time lint for a
readonlywrite through an action'sctx.api. Any such rule has to answer "is this write dropped?" — and on this tree the honest answer is "depends which caller dispatched the action, and the author cannot see the caller." That is the class of advice authors learn to ignore. ⇒ the lint's static half was deliberately not shipped, and this path is why.⛔ Not asserted here that elevating
ObjectRepository.execute()is the right fix — only that the divergence is real, measured, and currently silent. Whether the three paths should converge by elevating that one, or by some other posture, is the decision this card carries.Re-check
Related: #13770 (the lint card, now
pm:retriageon this measurement) · #13832 (the elevation guidance the conditional strip does not honour) · #3914 · #2849.Generated by Claude Code