Repository navigation
fix(objectql, runtime): a converted stale-roll-up refusal keeps its lead and cause through a sandboxed hook, and is hedged under a failed rollback - #29721
Conversation
…ead and cause through a sandboxed hook, and is hedged under a failed rollback Claude-Session: https://claude.ai/code/session_01JQwxbZhgmEnK3HMysTCHGU Co-authored-by: Claude <noreply@anthropic.com>
…body lets a converted refusal leave Claude-Session: https://claude.ai/code/session_01JQwxbZhgmEnK3HMysTCHGU Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift Check5 anchor(s) derived from 2 changed package(s); no hand-written page names any of them. What this run could not see
Coarse fallback — 37 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 ad6897b978a04701da719106f14c88d4d0cc10ec && git checkout ad6897b978a04701da719106f14c88d4d0cc10ec
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 9b4fa6cc9d8295e891d5f8d2264aee1cca36c203 ac992d4d011a18e3c4195a219469e63285a67a64 && git checkout -B drift-repro 9b4fa6cc9d8295e891d5f8d2264aee1cca36c203 && git merge --no-ff ac992d4d011a18e3c4195a219469e63285a67a64
node scripts/docs-audit/affected-docs.mjs --json 9b4fa6cc9d8295e891d5f8d2264aee1cca36c203 |
|
domain:server#4 · os-warren · session_01JQwxbacSXJQqAztgt4UMrS 席位核对ACCEPT ·
|
Requested by Warren · project thread
Fixes #29527
Clause-②: no
Metadata-protocol: none
维护者速读
ctx.api写入时,若那次写入已被转换成"未落地"拒绝(级联被汇总重算失败卡住、事务已回滚),钩子体原样抛出时,运行器现在交还引擎自己的错误对象,而不是一个SandboxError;于是钩子派发处能照常改写首句,点名外层写入,cause保留原始ERR_SUMMARY_RECOMPUTE。②ObjectQL.transaction回滚本身也失败时,已经转换过的拒绝也改用"回滚也失败、存储状态未知"的首句,不再说"在会回滚的事务里什么都没写"。insert('hcase')的调用方读到的是钩子自己那次删除("The delete of "hacct" …"),cause丢失;回滚失败时级联拒绝仍声称"nothing was written"。500 INTERNAL_ERROR,数据入口与动作入口实测)。钩子体自己捕获后抛自定义错误、或改写消息后再抛,照旧走SandboxError中继。@objectstack/objectql新增一个导出isUnlandedWriteRefusal。回滚即 revert。packages/runtime/src/sandbox/quickjs-runner.ts(holdEngineRefusal的谓词一行),packages/objectql/src/summary-errors.ts(品牌符号与rollbackFailedRefusal的 re-lead),再看packages/runtime/src/sandbox-hook-unlanded-refusal-lead.test.ts。pnpm --filter @objectstack/runtime exec vitest run --maxWorkers=2 src/sandbox-hook-unlanded-refusal-lead.test.ts,预期 6 个通过。What changes
holdEngineRefusal, matched by identity on the host-side error). It now does the same for objectql's converted stale-roll-up refusal. The hook-dispatch seam (triggerHooks, and the whole-operation dispatch beside it) then recognises it and re-leads it as it does a host-code hook's, so the outer write's caller readsThe insert of "hcase" did not complete: its beforeInsert hook "purge_before" stopped. …andcauseis the line's ownERR_SUMMARY_RECOMPUTErefusal with itsfailures.beforeFind/afterFindand the other read events are covered;beforeFindis pinned for them.SandboxErrorrelay (two CONTROL pins).500,INTERNAL_ERROR, the delete's own sentence,success: false(pinned; the pin is green with and without the fix).rollbackFailedRefusalhedges every converted refusal. It now re-leads an earlier conversion (relead = true), so a cascade-stopped or hook-stopped refusal escaping anObjectQL.transactionwhose rollback threw leads withThe transaction did not complete, and its rollback failed too, so what was written inside it may or may not be stored.,causekept. A transaction whose rollback ran still leaves the converted refusal exactly as it escaped (CONTROL).isUnlandedWriteRefusal(e), exported from@objectstack/objectql, is the predicate the runner asks. The class stays module-private. The predicate reads a non-enumerableSymbol.forbrand rather thaninstanceof, because this package ships two realms (ESM and CJS) and a reader holding the other realm's class would getfalse. The re-lead insidesummary-errors.tsasks the same predicate.Status codes and
codeare unchanged everywhere.packages/specandprotocol.tsare untouched. Changeset:patchfor@objectstack/objectqland@objectstack/runtime.Tests
Reproduced red first (one-time mutation through scripts/ablation-replace.mjs, restored by blob hash)
rollbackFailedRefusal's re-lead flipped back to the base behaviour (truetofalse).engine-unlanded-refusal-lead.test.ts: Tests 2 failed, 9 passed. The red message was the base answer:The delete of "line" did not complete: its cascade stopped before the record was removed. Under a transaction that rolls back nothing was written; …. Blob restored6cdd4cf1= HEAD.holdEngineRefusal's predicate reduced to the duplicate-record class alone (the base).sandbox-hook-unlanded-refusal-lead.test.ts: Tests 3 failed, 3 passed. The three re-lead pins (beforeInsert, afterInsert, beforeFind) red withhook 'purge_before' threw: Error: The delete of "hacct" did not complete: …;status500 andcodeINTERNAL_ERRORalready held there, so the status does not move. The two CONTROL pins and the action-door pin stayed green. Blob restored5e56e6d1= HEAD.Green (head ac992d4)
src/engine-unlanded-refusal-lead.test.ts(objectql): Tests 11 passed.src/sandbox-hook-unlanded-refusal-lead.test.ts(runtime, realObjectQLover better-sqlite3, realQuickJSScriptRunner, realHttpDispatcher): Tests 6 passed.pnpm --filter @objectstack/objectql test: Test Files 543 passed, Tests 10605 passed.typecheck: exit 0.pnpm --filter @objectstack/runtime typecheck: exit 0.pnpm --filter @objectstack/runtime test: Test Files 530 passed, Tests 8518 passed, 19 skipped.Verification
Gates (head ac992d4)
node scripts/pm/dispatch-gates.mjs --commandsderived 60 commands; all run locally with exit codes captured before any pipe. All 60 exited 0.check:dual-build-cjs-loadsfirst answered PREREQUISITE NOT MET (exit 3, nodist/for 37 packages); after a fullpnpm build(exit 0) it exited 0: 115 require entry points across 70 packages load.--ranreconciliation: 60 derived, 60 run, 0 NOT-MEASURED, 0 UNRUN.pnpm lintexited 0.origin/mainmoved 24 commits after the branch point; none touchessummary-errors.ts,index.ts,engine.tsorquickjs-runner.ts, so no merge was made.Acceptance notes
500 INTERNAL_ERROR; the re-lead and thecausereach in-process callers, the action door's message and the server log.ScopedContext.transaction(the repository surface's twin) does not swallow a failing rollback, so the rollback's own error leaves it and no "rolled back" claim is made there. Not touched.Human Minimum
packages/specholds no runtime logic. (No schema touched.)pnpm lintpasses.main, one pull request.packages/spectouched: its generated artifacts are regenerated and committed. (Not touched.)content/docs/references/,packages/*/CHANGELOG.mdorcontent/docs/releases/.Card thread session:
session_01JQwxbZhgmEnK3HMysTCHGU, seatdomain:server#4, claim 6592672964.🤖 Generated with Claude Code
https://claude.ai/code/session_01JQwxbZhgmEnK3HMysTCHGU
Generated by Claude Code