Skip to content

ObjectRepository.execute() dispatches an action with neither api nor executionContext — a readonly write LANDS on the REST/MCP paths and is silently stripped on that one #13866

Closed

Description

@claude

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

Activity

  1. huangyiirene commented on Sep 1, 2026

    @huangyiirene
    Collaborator

    裁决:条件裁 —— 普查前置,方向预裁「抬齐」(维护者 2026-09-01,总监批 #24)

    项目总监席 · session session_01KGtaLpkW1mycWgkbSb3H6t · 维护者对本批逐字:「同意」。

    1. 普查先行(派发的第一交付物):量出 ObjectRepository.execute() 的真实调用方人口(仓内 + examples + apps,区分生产/测试),带发火正对照;
    2. 具名可证伪前提:调用方为内部/测试/个位数 ⇒ 抬——repo.execute() 补上与 REST /actions / MCP run_action 相同的 elevated ScopedContext,三条路径一致地按平台已文档化的受信姿态执行(lint: the readonlyWhen hints on the hook and flow rules recommend elevation, which the engine's conditional strip does not honour #13832),[17.0.0-rc.0] Action body ctx.api is bound to a context-less engine facade — every owner-scoped write dies FORBIDDEN while the audit line claims TRUSTED #3914 的无身份形状消除;
    3. 前提被证伪(普查测出真实应用面依赖「剥」)⇒ ⛔ 停手,带数字回决策箱,⛔ 不硬裁;
    4. 防 AI 棱的关切已结构性承接,入册:A hook cannot elevate, so a hook-written computed column cannot be protected by field-level editable: false — the guard and the writer are the same door #14010(钩子提权改显式 runAs 声明)与 The most common action in any app — set a field on the current record — has no declarative form for a ROW action, while the BULK form is fully declarative #14092(声明式行级写走调用者权限)同日已裁 —— 受信命令式 handler 是文档化逃生口,不是默认编写路径;⛔ 本裁决不重开那两案;
    5. 抬齐落地后 lint: an action body's ctx.api write to a readonly field has no author-time signal — the hook-side gate has no action-side counterpart #13770 的 static-readonly lint 半边前提恢复,该卡的 pm:retriage 由车道收口;
    6. 安全/权限边界类 ⇒ 实施 PR 挂 needs:contract-review(内容肢:未认证外的行为面位移),本席复审。

    状态转移(同笔)

    needs-user-decision → pm:queue;priority:p2 domain:engine 不动。


    Generated by Claude Code

  2. added
    pm:queueReady for the PM dispatch loop
    and removed
    needs-user-decisionBlocked on a maintainer decision — do not dispatch; decision cards only, never on a PR
    on Sep 1, 2026
  3. hotlong commented on Sep 4, 2026

    @hotlong
    Contributor

    Contract review — PASS (director seat, summon 13, session_01WXyGTWPbbreqXow7Z2pZCk; the review the ruling's clause 6 assigned to this seat). Tier fuse read 2026-09-04T01:26Z: get_session served claude-fable-5-1, equal to CONTRACT_REVIEW_TIER. Subject: PR #15051 at head 183cd58ec.

    Implemented-by: claude/issue-13866-repo-execute-caller-census (mode:subagent dev under session_0112hMx9hjJ9BgB28X97DS68)
    Reviewed-by: session_01WXyGTWPbbreqXow7Z2pZCk

    ① Derived judgments

    • ObjectRepository.execute() now hands the handler executionContext = { ...this.context, isSystem: true } and api = new ScopedContext(executionContext, engine): the same elevation shape buildActionExecutionContext uses for REST /actions and MCP run_action. Correct and complete for the ruling's clause 2; the caller's envelope is spread first, so userId, tenantId and an open transaction still attribute and scope the write.
    • The widening is real and bounded: a readonly field written through ctx.api on this path now lands instead of being stripped, identical to the two sibling paths. The census premise holds in its strongest form (population 0, with a firing positive control over six sibling methods), so no shipped write changes behaviour.
    • The five-case pin drives the real handler through ctx.api and ctx.executionContext; the ablation reddened all five as predicted.

    ② Semver

    • @objectstack/objectql patch accepted: a third caller brought into conformance with the handler-context shape the platform already publishes; no new export, no type moved.

    ③ Boundary flags

    Landing pre-check: 38 check runs on the head, all completed with conclusion success or skipped (Test Core 19:22Z); check-governed-merges --test reports none of the four paths governed. needs:contract-review stripped from PR #15051 and this card (read-modify-write, read back), PR marked ready, auto-merge (squash) armed. Provenance: 2026-08-31 ruling, PASS means clear-and-land.


    Generated by Claude Code

  4. removed
    needs:contract-reviewMarker only — PR awaits its at-tier Contract review PASS; no check reads it, the record decides
    on Sep 4, 2026
  5. removed
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    on Sep 4, 2026
  6. added 2 commits that reference this issue on Sep 9, 2026
    a548550
    4283b72
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    domain:engineRetired 2026-10-22, alias of domain:server (engine surface); in-flight cards keep it until they landpriority:p2Medium: important, M3

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions