You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Held behind the in-flight #5574+#5846 unified card (branch claude/issue-5574-bulk-before-per-row), which edits packages/objectql/src/plugin.ts (retiring sys_fetch_previous_update) and the hook-dispatch seam this card's per-object gate lives in — same-file rides in different rounds, no exception.
Known traps recorded for whoever dispatches this next; do NOT reuse the filing-time cost estimate:
The flagship establishes "engine binds previous before dispatch" on the update side and retires the update-side builtin. The delete side has bound-before-dispatch since 单记录 delete 从不绑定 hookContext.previous —— 契约声明「for update/delete」,引擎只在 update 分支赋值;#5038 之后批量 delete 反而比单记录 delete 更完整 #5272, so this card may shrink to "retire sys_fetch_previous_delete + make the per-object need-gate computation honest" — or parts may become unnecessary. The flagship dev's report carries a must-answer on exactly this question; read it before dispatching.
Claim: PM loop round 2 (engine-core seat #6019; maintainer 17:2xZ override — #5543/#6457/#5929 fly in PARALLEL, same-file serialization waived, collision cost accepted as queue rebases)
Session: session_01MwoubC3jL271FYt9rGXwxb
Branch: claude/issue-5929-retire-delete-prev-builtin
Worktree: cloud session's own clone (mode:cloud)
Domain: domain:engine-core
File surface: packages/objectql/src/plugin.ts (retire sys_fetch_previous_delete) + packages/objectql/src/engine.ts — REGION-EXCLUSIVE: the delete-path per-object need-gate ONLY; plus tests + .changeset/. ⛔ NOT resolveMasterDetailParent(s) (in-flight #6457), NOT registerObject/registry (in-flight #5543), ⛔ NOT the by-id delete REPOINT behavior (pending #6752 ruling — its pin still HONOURS a by-id beforeDelete REPOINT must stay green).
Re-pricing basis (the must-answer from #6697's report, now the premise): sys_fetch_previous_delete is measured CIRCULAR — it registers beforeDelete on '*', forcing the gate's first term true for every object, while the engine's own pre-dispatch read (since #5272) binds previous before the builtin runs, so its guard is permanently false; the measurement is recorded in plugin.ts by #6697. #6575's excludeObjects + #5860's delivered registration face mean the gate can now be honest.
Serial constraints: WAIVED by maintainer order for the three named cards; discipline replacing it — declared-disjoint regions, merge origin/main before PR and on sibling landings, merge queue arbitrates.
Container judgment: size M ⇒ mode:cloud.
{
"issue": 5929,
"status": "delivered",
"branch": "claude/issue-5929-retire-delete-prev-builtin",
"pr": 6794,
"pr_url": "https://github.com/objectstack-ai/objectstack/pull/6794",
"pr_state": "draft",
"premise_verification": {
"as_filed": "CONFIRMED — `sys_fetch_previous_delete` registered on `beforeDelete` with `object: '*'`, so `hasHooksFor('beforeDelete', object)` was true for every object on every kernel-managed engine and the per-object skip never happened outside bare-engine unit tests.",
"as_upgraded_by_6697": "CONFIRMED and independently re-measured — the builtin was circular: the engine binds `previous` before `beforeDelete` dispatches (by-id since #5272, per matched row since #6697), so its `!ctx.previous` guard was permanently false and it issued no read; its only remaining effect was holding open the gate that made it redundant.",
"measurement_cited": "packages/objectql/src/plugin.ts, pre-change lines 920-939, inside #5846's `sys_fetch_previous_update` RETIRED block — quoted verbatim in the PR body.",
"other_consumers": "NONE LIVE. Grep on the quoted exact name returned 3 hits: the declaration, its own `plugin.ts` comment, and one doc comment in plugin-auth/src/last-admin-guard.ts that names it as a co-producer of `ctx.previous` while stating in the same sentence that the guard never consumes it. That comment was corrected, not treated as a consumer. No report-instead-of-force condition."
},
"gate_terms_enumerated_from_current_main": {
"location": "packages/objectql/src/engine.ts, delete() by-id branch (`wantsPreImage`)",
"terms": [
"hasHooksFor('beforeDelete', object)",
"hasHooksFor('afterDelete', object)",
"getSummaryDescriptors(object).length > 0"
],
"no_validation_term": "`needsPriorRecord(schema)` deliberately absent — delete() evaluates no validation rules, so the term would buy a read with no reader. No summary/validation term was added post-#6697.",
"predicate_branch_twin_gate": "terms 1 and 2 only, untouched",
"terms_touched": "NONE. The gate expression is unchanged; the fix is entirely the removal of the `'*'` registration that made term 1 constant-true. engine.ts gains only the A2 enumeration recorded beside `wantsPreImage`."
},
"A1": {
"claim": "retiring the builtin changes NO observable binding anywhere",
"answer": "TRUE — no hole in the circularity premise",
"method": "reverse verification: builtin removed, NO gate change, full @objectstack/objectql suite run",
"predicted": "fully green; no test names the builtin as a live registration (grep established this beforehand)",
"actual": "Test Files 149 passed (149) · Tests 2575 passed (2575) · 0 failures"
},
"A2": {
"question": "does any remaining '*'-registered delete-phase builtin silently re-open the gate for every object?",
"answer": "No `'*'`-SPELLED one remains, but FOUR packages achieve the same thing by omitting `object` — declared rather than hidden",
"still_global_and_holding_the_gate_open": [
"plugin-auth/src/identity-write-guard.ts — beforeDelete, no `object`; filters `isManaged(ctx.object)` inside the handler",
"plugin-sharing/src/record-share-cascade.ts — before+afterDelete, no `object`; filters `targets(objectName)` inside",
"service-storage/src/file-reference-lifecycle.ts — before+afterDelete, no `object`; filters `activeFileFields(object)` inside",
"plugin-audit/src/audit-writers.ts — before+afterDelete, global MINUS `excludeObjects: AUDIT_EXCLUDED_OBJECTS` (#5860); excluded objects genuinely skip the read"
],
"object_scoped_and_therefore_harmless": [
"plugin-auth/src/last-admin-guard.ts (4 registrations)",
"plugin-sharing/src/rule-hooks.ts, primary-bu-projection.ts, sharing-plugin.ts",
"service-storage/src/attachment-lifecycle.ts, attachment-access-hooks.ts",
"plugin-audit/src/comment-access-hooks.ts"
],
"verdict": "The four global ones are real consumers with real handlers that decide applicability at dispatch time; the gate answering yes for them is the gate working, not a second #5929. What made the builtin a defect is that it consumed nothing. Narrowing any of them is that package's own card — plugin-audit's `excludeObjects` face is the worked example, and this PR pins that the engine half of it works on both delete phases. Stated in engine.ts and in the changeset so nobody expects a skip that will not appear on a full kernel.",
"sibling_of_sys_stamp_audit_update_on_the_delete_side": "none — ObjectQLPlugin's surviving builtins are `sys_stamp_audit_insert` (beforeInsert) and `sys_stamp_audit_update` (beforeUpdate) only, pinned as such in the new test file."
},
"acceptance_pins": {
"file": "packages/objectql/src/engine-delete-prior-read-scope.test.ts (new, 16 cases, delete-side counterpart of engine-update-prior-read-scope.test.ts)",
"kernel_without_delete_hooks_no_prior_read": "PINNED — real ObjectKernel + ObjectQLPlugin, per-object driver findOne AND find deltas both 0 on a single-id delete()",
"kernel_with_real_beforeDelete_previous_still_bound": "PINNED — `previous` bound, from exactly ONE read (the engine's #5272/#6697 read; the count is what proves nothing else supplied it)",
"builtin_absent_from_the_booted_engine": "PINNED — hasHooksFor beforeDelete/afterDelete false, while beforeInsert/beforeUpdate stay true (absence of one hook, not of the builtin set)",
"excludeObjects_honored": "PINNED both phases — excluded object skips the read and does not dispatch; non-excluded pays it and does. Registered via engine.registerHook because bindHooksToEngine refuses an absent `object` (#4001), so global-minus-exclusions is only expressible on the engine API — which is the API plugin-audit uses.",
"also_pinned": "term 1 alone / term 2 alone / term 3 alone; '*' still demanding the read everywhere; needsPriorRecord explicitly NOT a term; predicate-path twin gate both directions; the retired builtin's shape replayed with its guard measured short-circuiting to zero reads; the residual empty-read shape leaving `previous` UNBOUND not fabricated (#4649/#4775)."
},
"repoint_untouched": {
"pin": "bulk-write-per-row-hooks.test.ts — 'still HONOURS a by-id `beforeDelete` REPOINT — deliberately not retired here'",
"state": "GREEN",
"reasoning": "the repoint re-read is guarded by the same `wantsPreImage` value computed once before the before phase; that computation is unchanged, only what one of its inputs answers on a kernel. #6752 left entirely alone."
},
"reverse_verification_of_the_new_pins": {
"method": "builtin temporarily restored from origin/main, new test file re-run",
"predicted": "exactly 2 red — `registers NO beforeDelete hook of its own` (hasHooksFor true) and `a single-id delete on a hook-free object performs NO prior-row read` (delta 1, not 0). The `previous` still bound from ONE read case stays GREEN because the builtin's guard short-circuits. All bare-engine cases green.",
"actual": "Tests 2 failed | 14 passed (16) — 'expected true to be false' and 'expected 1 to be +0'. Nothing else moved. Signature matched exactly.",
"bonus_evidence": "with the builtin PRESENT, the hooked-object case still measured exactly ONE read — the cleanest independent confirmation that the builtin issued none, i.e. the circularity claim.",
"harness_bugs_found_and_fixed_not_papered_over": [
"the counting driver's deleteMany/updateMany/count routed through their own find(), charging the engine for reads it never issued (every predicate-path number was one too high)",
"bindHooksToEngine refuses an absent `object` (#4001), so the first excludeObjects case passed for the wrong reason — the hook had never registered at all"
]
},
"gates": {
"source": "enumerated fresh from .github/workflows/lint.yml, every check:* one by one; NODE_OPTIONS=--max-old-space-size=4096, --maxWorkers=2; full build run before check:type-check-debt",
"result": "ALL PASS",
"initially_red_then_fixed": [
"pnpm lint / check:slot-lookup — `kernel.getService('objectql') as any` erased the slot contract for every engine.* call in the kernel section; typed as getService<ObjectQL>('objectql')",
"check:query-options-erasure — three `count(obj, {} as any)` grew the test surface 263 -> 266; the empty bag is already on contract, assertion dropped"
],
"passed": ["lint", "check:slot-lookup", "check:query-options-erasure", "check:verify-stand-in", "check:nul-bytes", "check:doc-authoring", "check:docs-audit-scope", "check:role-word", "check:quick-reference-counts", "check:adr-anchors", "check:org-identifier", "check:authz-resolver", "check:service-providers", "check:route-envelope", "check:error-code-casing", "check:wildcard-fallthrough", "check:meta-type-normalized", "check:init-service-contract", "check:durability-log-level", "check:startup-registry-verdict", "check:objectui-changeset", "check:release-notes", "check:release-body", "check:node-version", "check:workflow-status-functions", "check:shard-attestation", "check:published-files", "check:engine-double-contract", "check:kernel-hook-pairs", "check:resume-authority-declared", "check:driver-memory-census", "check:merge-driver", "check:spec-parsed-alias", "check:type-check-coverage", "check:driver-conformance", "check:stall-guard", "check:skill-frame-sync", "check:skill-compatibility", "check:i18n", "check:i18n-coverage", "check:app-nav-i18n", "spec exec tsc --noEmit", "spec check:generated --reconcile-only", "spec check:skill-docs", "spec check:spec-changes", "spec check:upgrade-guide", "spec check:authorable-surface", "spec check:docs", "spec check:skill-refs", "spec check:react-blocks", "turbo build (packages + nested)", "check:type-check-debt", "turbo typecheck (packages + nested + apps)", "examples typecheck", "downstream-contract typecheck", "spec check:api-surface", "spec check:exported-any", "spec check:dual-source-exports", "spec check:skill-examples", "lint check:doc-formula-expressions"]
},
"tests": {
"objectql_final": "151 files / 2609 tests passed",
"downstream_suites_all_green": {
"plugin-audit": 122,
"plugin-auth": 974,
"plugin-sharing": 376,
"service-storage": 284,
"core": 524,
"runtime": 1733,
"trigger-record-change": 56,
"metadata-core": 124,
"service-automation": 868,
"service-messaging": 199,
"metadata-protocol": 700
}
},
"files_changed": [
"packages/objectql/src/plugin.ts — the retirement (+ the binder debug line that still advertised `previousData`, a producer that no longer exists)",
"packages/objectql/src/engine.ts — delete-gate region comment only; NO term changed",
"packages/plugins/plugin-auth/src/last-admin-guard.ts — one stale doc comment naming the retired builtin",
"packages/objectql/src/engine-delete-prior-read-scope.test.ts — new",
".changeset/retire-delete-fetch-previous-builtin.md — @objectstack/objectql patch, both directions"
],
"scope_discipline": {
"sibling_regions_untouched": "no edits in the registerObject/registry region (#5543) or resolveMasterDetailParent(s) (#6457)",
"no_spec_changes": true,
"no_plugin_audit_edits": true,
"no_releases_page_edits": true,
"new_fake_engines": "none — the tests drive the REAL ObjectQL engine behind a fake DRIVER, which check-engine-double-contract vetoes out of scan scope (DRIVER_ONLY_MEMBERS + primary-key-parameter test), so no assertEngineDeleteDispatch obligation arises; check:engine-double-contract passes",
"worktree": "dedicated worktree /home/user/objectstack-5929; no git stash used",
"merge_discipline": "origin/main merged immediately before opening the PR and again before the final push; no conflicts, and the second merge brought no objectql changes so the suite results stand"
},
"blockers": [],
"needs_user_decision": []
}
ACCEPT (engine-core seat #6019, session session_01MwoubC3jL271FYt9rGXwxb). PR #6794; CI verified directly on head 50c6478 — 24/24 concluded, zero non-green, ESLint + TypeScript Type Check + Check Changeset all success.
Both premises independently re-measured rather than inherited: the filed one ('*' registration makes the per-object gate constant-true) and #6697's upgrade (the builtin was circular — the engine binds previous before dispatch, so its guard was permanently false and it issued no read; its only remaining effect was holding open the gate that made it redundant). A1 proved by the cleanest possible experiment — builtin removed, gate untouched, full suite green (149 files / 2575 tests) — so the retirement is provably binding-neutral. The gate expression itself is unchanged: the fix is purely removing the registration that made term 1 constant, which is the minimal correct shape.
A2 is the answer that earns the acceptance. Rather than declaring victory, it enumerates the four packages that remain effectively global by omittingobject (plugin-auth identity-write-guard, plugin-sharing record-share-cascade, service-storage file-reference-lifecycle, plugin-audit minus its excludeObjects) and draws the right line: those are real consumers with real handlers deciding applicability at dispatch time — the gate answering yes for them is the gate working. What made the builtin a defect is that it consumed nothing. Narrowing each is that package's own card, with plugin-audit's excludeObjects face as the worked example (and this PR pins that the engine half honors it on both delete phases). Stating that in engine.ts and the changeset means nobody expects a skip that will not appear on a full kernel — exactly the honesty the ADR-0049 family requires.
The #6752 boundary held: the by-id repoint pin stays green and untouched. The last-admin-guard.ts doc comment naming the builtin as a co-producer was corrected rather than treated as a consumer — right call. 16 new acceptance pins as the delete-side counterpart of the update-side file. Now: ready + auto-merge; issue closes on merge.
Activity
claude commentedon Aug 6, 2026
分诊:
pm:queue(可派发)+ 维持domain:engine-core。domain:engine-core但无 pm-state 标签 ⇒ 此前对所有队列视图不可见。过时前提检查(
origin/main9e3709a)—— 两处锚点实读,均成立(行号有小幅漂移,已更新)wantsPreImage三元判据在packages/objectql/src/engine.ts:**6125**(正文记:6112,已前移;:6145/:6160两处消费点同在);sys_fetch_previous_delete的object: '*'/events: ['beforeDelete']注册在packages/objectql/src/plugin.ts:**888**(正文记:871)。⇒ 「只要 ObjectQLPlugin 在,
wantsPreImage第一项恒真、后两项永不求值」这个事实面成立。正文的计数实测(driver.findOne增量 = 1)是可复现判据。为什么入队而不是 finding:落点已钉死到两个文件的具体行,且有计数驱动的复现;后果是每一次 kernel 托管引擎上的
delete()都多一次findOne(对无 hook、无 roll-up 的对象也照读),即今天在跑的真实开销,不是休眠声明。两个候选方向都落在packages/objectql内部,不改任何对外契约 ⇒ 属车道内技术选型,⛔ 不进决策箱。方向选择交 dev,但带一条硬约束(派发令请原样传下去):
sys_fetch_previous_delete)需先实测 单记录 delete 从不绑定hookContext.previous—— 契约声明「for update/delete」,引擎只在 update 分支赋值;#5038 之后批量 delete 反而比单记录 delete 更完整 #5272 之后引擎自绑previous的分支覆盖面 —— 正文已点名单 id 有、谓词 delete 走另一条路径,这一步是前置取证不是可选项;packageId在hasHooksFor里排除内建)⛔ 一条容易被误读的依赖关系,先澄清:本单不继承 #5860 的
pm:blocked。#5860(update 侧)卡在 #5928(hook 注册契约无法表达「全局但排除」),而本单两个方向都是引擎内部改动、不需要 #5928 的表达力;正文亦已写明「两者独立,需要分别解」。⇒ 本单可独立派发,不加Blocked-by:。域锚定:落点
packages/objectql/src/{engine,plugin}.ts⇒domain:engine-core。串行约束(硬,三条):
engine.ts⇒ 排其后;plugin.ts⇒ 排其后;seedAutonumber把读故障答成return 0—— 对已有 N 行的表重新从 1 发号,自增号与既有行相撞且零日志(#4825 同族,活体) #5979 同落engine.ts⇒ 两单 ⛔ 不得同轮派发。查重:三仓 open issue / PR 各搜一遍 —— #5860(update 侧,
pm:blocked)、#5928(注册契约表达力,pm:queue/domain:spec)、#5272 / #5284 / #5038 均为关联而非重复,各自入口不同;objectui / cloud 无影子单。本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
os-zhuang commentedon Aug 8, 2026
Deferred this round — same-seam serialization + mandatory re-pricing (engine-core seat #6019, round 1, session
session_01MwoubC3jL271FYt9rGXwxb).Held behind the in-flight #5574+#5846 unified card (branch
claude/issue-5574-bulk-before-per-row), which editspackages/objectql/src/plugin.ts(retiringsys_fetch_previous_update) and the hook-dispatch seam this card's per-object gate lives in — same-file rides in different rounds, no exception.Known traps recorded for whoever dispatches this next; do NOT reuse the filing-time cost estimate:
previousbefore dispatch" on the update side and retires the update-side builtin. The delete side has bound-before-dispatch since 单记录 delete 从不绑定hookContext.previous—— 契约声明「for update/delete」,引擎只在 update 分支赋值;#5038 之后批量 delete 反而比单记录 delete 更完整 #5272, so this card may shrink to "retiresys_fetch_previous_delete+ make the per-object need-gate computation honest" — or parts may become unnecessary. The flagship dev's report carries a must-answer on exactly this question; read it before dispatching.excludeObjectsto the hook registration face — thehasHooksForgate semantics this card reasons about have changed since filing. Re-verify the always-true claim against merged main before scoping.object注册 ⇒ 引擎「按对象」需求门(#5284 单 id / #5038 批量)在 audit 启用时恒真 #5860 (identity lane) owns plugin-audit's registration-face expression; this card owns the objectql builtin's side.Generated by Claude Code
os-zhuang commentedon Aug 8, 2026
Claim: PM loop round 2 (engine-core seat #6019; maintainer 17:2xZ override — #5543/#6457/#5929 fly in PARALLEL, same-file serialization waived, collision cost accepted as queue rebases)
Session:
session_01MwoubC3jL271FYt9rGXwxbBranch:
claude/issue-5929-retire-delete-prev-builtinWorktree: cloud session's own clone (mode:cloud)
Domain:
domain:engine-coreFile surface:
packages/objectql/src/plugin.ts(retiresys_fetch_previous_delete) +packages/objectql/src/engine.ts— REGION-EXCLUSIVE: the delete-path per-object need-gate ONLY; plus tests +.changeset/. ⛔ NOTresolveMasterDetailParent(s)(in-flight #6457), NOTregisterObject/registry (in-flight #5543), ⛔ NOT the by-id delete REPOINT behavior (pending #6752 ruling — its pinstill HONOURS a by-id beforeDelete REPOINTmust stay green).Re-pricing basis (the must-answer from #6697's report, now the premise):
sys_fetch_previous_deleteis measured CIRCULAR — it registersbeforeDeleteon'*', forcing the gate's first term true for every object, while the engine's own pre-dispatch read (since #5272) bindspreviousbefore the builtin runs, so its guard is permanently false; the measurement is recorded inplugin.tsby #6697. #6575'sexcludeObjects+ #5860's delivered registration face mean the gate can now be honest.Serial constraints: WAIVED by maintainer order for the three named cards; discipline replacing it — declared-disjoint regions, merge origin/main before PR and on sibling landings, merge queue arbitrates.
Container judgment: size M ⇒
mode:cloud.Generated by Claude Code
test(objectql): keep the new delete-gate pins inside the erasure ratc…
os-zhuang commentedon Aug 8, 2026
os-dev final report (#5929)
{ "issue": 5929, "status": "delivered", "branch": "claude/issue-5929-retire-delete-prev-builtin", "pr": 6794, "pr_url": "https://github.com/objectstack-ai/objectstack/pull/6794", "pr_state": "draft", "premise_verification": { "as_filed": "CONFIRMED — `sys_fetch_previous_delete` registered on `beforeDelete` with `object: '*'`, so `hasHooksFor('beforeDelete', object)` was true for every object on every kernel-managed engine and the per-object skip never happened outside bare-engine unit tests.", "as_upgraded_by_6697": "CONFIRMED and independently re-measured — the builtin was circular: the engine binds `previous` before `beforeDelete` dispatches (by-id since #5272, per matched row since #6697), so its `!ctx.previous` guard was permanently false and it issued no read; its only remaining effect was holding open the gate that made it redundant.", "measurement_cited": "packages/objectql/src/plugin.ts, pre-change lines 920-939, inside #5846's `sys_fetch_previous_update` RETIRED block — quoted verbatim in the PR body.", "other_consumers": "NONE LIVE. Grep on the quoted exact name returned 3 hits: the declaration, its own `plugin.ts` comment, and one doc comment in plugin-auth/src/last-admin-guard.ts that names it as a co-producer of `ctx.previous` while stating in the same sentence that the guard never consumes it. That comment was corrected, not treated as a consumer. No report-instead-of-force condition." }, "gate_terms_enumerated_from_current_main": { "location": "packages/objectql/src/engine.ts, delete() by-id branch (`wantsPreImage`)", "terms": [ "hasHooksFor('beforeDelete', object)", "hasHooksFor('afterDelete', object)", "getSummaryDescriptors(object).length > 0" ], "no_validation_term": "`needsPriorRecord(schema)` deliberately absent — delete() evaluates no validation rules, so the term would buy a read with no reader. No summary/validation term was added post-#6697.", "predicate_branch_twin_gate": "terms 1 and 2 only, untouched", "terms_touched": "NONE. The gate expression is unchanged; the fix is entirely the removal of the `'*'` registration that made term 1 constant-true. engine.ts gains only the A2 enumeration recorded beside `wantsPreImage`." }, "A1": { "claim": "retiring the builtin changes NO observable binding anywhere", "answer": "TRUE — no hole in the circularity premise", "method": "reverse verification: builtin removed, NO gate change, full @objectstack/objectql suite run", "predicted": "fully green; no test names the builtin as a live registration (grep established this beforehand)", "actual": "Test Files 149 passed (149) · Tests 2575 passed (2575) · 0 failures" }, "A2": { "question": "does any remaining '*'-registered delete-phase builtin silently re-open the gate for every object?", "answer": "No `'*'`-SPELLED one remains, but FOUR packages achieve the same thing by omitting `object` — declared rather than hidden", "still_global_and_holding_the_gate_open": [ "plugin-auth/src/identity-write-guard.ts — beforeDelete, no `object`; filters `isManaged(ctx.object)` inside the handler", "plugin-sharing/src/record-share-cascade.ts — before+afterDelete, no `object`; filters `targets(objectName)` inside", "service-storage/src/file-reference-lifecycle.ts — before+afterDelete, no `object`; filters `activeFileFields(object)` inside", "plugin-audit/src/audit-writers.ts — before+afterDelete, global MINUS `excludeObjects: AUDIT_EXCLUDED_OBJECTS` (#5860); excluded objects genuinely skip the read" ], "object_scoped_and_therefore_harmless": [ "plugin-auth/src/last-admin-guard.ts (4 registrations)", "plugin-sharing/src/rule-hooks.ts, primary-bu-projection.ts, sharing-plugin.ts", "service-storage/src/attachment-lifecycle.ts, attachment-access-hooks.ts", "plugin-audit/src/comment-access-hooks.ts" ], "verdict": "The four global ones are real consumers with real handlers that decide applicability at dispatch time; the gate answering yes for them is the gate working, not a second #5929. What made the builtin a defect is that it consumed nothing. Narrowing any of them is that package's own card — plugin-audit's `excludeObjects` face is the worked example, and this PR pins that the engine half of it works on both delete phases. Stated in engine.ts and in the changeset so nobody expects a skip that will not appear on a full kernel.", "sibling_of_sys_stamp_audit_update_on_the_delete_side": "none — ObjectQLPlugin's surviving builtins are `sys_stamp_audit_insert` (beforeInsert) and `sys_stamp_audit_update` (beforeUpdate) only, pinned as such in the new test file." }, "acceptance_pins": { "file": "packages/objectql/src/engine-delete-prior-read-scope.test.ts (new, 16 cases, delete-side counterpart of engine-update-prior-read-scope.test.ts)", "kernel_without_delete_hooks_no_prior_read": "PINNED — real ObjectKernel + ObjectQLPlugin, per-object driver findOne AND find deltas both 0 on a single-id delete()", "kernel_with_real_beforeDelete_previous_still_bound": "PINNED — `previous` bound, from exactly ONE read (the engine's #5272/#6697 read; the count is what proves nothing else supplied it)", "builtin_absent_from_the_booted_engine": "PINNED — hasHooksFor beforeDelete/afterDelete false, while beforeInsert/beforeUpdate stay true (absence of one hook, not of the builtin set)", "excludeObjects_honored": "PINNED both phases — excluded object skips the read and does not dispatch; non-excluded pays it and does. Registered via engine.registerHook because bindHooksToEngine refuses an absent `object` (#4001), so global-minus-exclusions is only expressible on the engine API — which is the API plugin-audit uses.", "also_pinned": "term 1 alone / term 2 alone / term 3 alone; '*' still demanding the read everywhere; needsPriorRecord explicitly NOT a term; predicate-path twin gate both directions; the retired builtin's shape replayed with its guard measured short-circuiting to zero reads; the residual empty-read shape leaving `previous` UNBOUND not fabricated (#4649/#4775)." }, "repoint_untouched": { "pin": "bulk-write-per-row-hooks.test.ts — 'still HONOURS a by-id `beforeDelete` REPOINT — deliberately not retired here'", "state": "GREEN", "reasoning": "the repoint re-read is guarded by the same `wantsPreImage` value computed once before the before phase; that computation is unchanged, only what one of its inputs answers on a kernel. #6752 left entirely alone." }, "reverse_verification_of_the_new_pins": { "method": "builtin temporarily restored from origin/main, new test file re-run", "predicted": "exactly 2 red — `registers NO beforeDelete hook of its own` (hasHooksFor true) and `a single-id delete on a hook-free object performs NO prior-row read` (delta 1, not 0). The `previous` still bound from ONE read case stays GREEN because the builtin's guard short-circuits. All bare-engine cases green.", "actual": "Tests 2 failed | 14 passed (16) — 'expected true to be false' and 'expected 1 to be +0'. Nothing else moved. Signature matched exactly.", "bonus_evidence": "with the builtin PRESENT, the hooked-object case still measured exactly ONE read — the cleanest independent confirmation that the builtin issued none, i.e. the circularity claim.", "harness_bugs_found_and_fixed_not_papered_over": [ "the counting driver's deleteMany/updateMany/count routed through their own find(), charging the engine for reads it never issued (every predicate-path number was one too high)", "bindHooksToEngine refuses an absent `object` (#4001), so the first excludeObjects case passed for the wrong reason — the hook had never registered at all" ] }, "gates": { "source": "enumerated fresh from .github/workflows/lint.yml, every check:* one by one; NODE_OPTIONS=--max-old-space-size=4096, --maxWorkers=2; full build run before check:type-check-debt", "result": "ALL PASS", "initially_red_then_fixed": [ "pnpm lint / check:slot-lookup — `kernel.getService('objectql') as any` erased the slot contract for every engine.* call in the kernel section; typed as getService<ObjectQL>('objectql')", "check:query-options-erasure — three `count(obj, {} as any)` grew the test surface 263 -> 266; the empty bag is already on contract, assertion dropped" ], "passed": ["lint", "check:slot-lookup", "check:query-options-erasure", "check:verify-stand-in", "check:nul-bytes", "check:doc-authoring", "check:docs-audit-scope", "check:role-word", "check:quick-reference-counts", "check:adr-anchors", "check:org-identifier", "check:authz-resolver", "check:service-providers", "check:route-envelope", "check:error-code-casing", "check:wildcard-fallthrough", "check:meta-type-normalized", "check:init-service-contract", "check:durability-log-level", "check:startup-registry-verdict", "check:objectui-changeset", "check:release-notes", "check:release-body", "check:node-version", "check:workflow-status-functions", "check:shard-attestation", "check:published-files", "check:engine-double-contract", "check:kernel-hook-pairs", "check:resume-authority-declared", "check:driver-memory-census", "check:merge-driver", "check:spec-parsed-alias", "check:type-check-coverage", "check:driver-conformance", "check:stall-guard", "check:skill-frame-sync", "check:skill-compatibility", "check:i18n", "check:i18n-coverage", "check:app-nav-i18n", "spec exec tsc --noEmit", "spec check:generated --reconcile-only", "spec check:skill-docs", "spec check:spec-changes", "spec check:upgrade-guide", "spec check:authorable-surface", "spec check:docs", "spec check:skill-refs", "spec check:react-blocks", "turbo build (packages + nested)", "check:type-check-debt", "turbo typecheck (packages + nested + apps)", "examples typecheck", "downstream-contract typecheck", "spec check:api-surface", "spec check:exported-any", "spec check:dual-source-exports", "spec check:skill-examples", "lint check:doc-formula-expressions"] }, "tests": { "objectql_final": "151 files / 2609 tests passed", "downstream_suites_all_green": { "plugin-audit": 122, "plugin-auth": 974, "plugin-sharing": 376, "service-storage": 284, "core": 524, "runtime": 1733, "trigger-record-change": 56, "metadata-core": 124, "service-automation": 868, "service-messaging": 199, "metadata-protocol": 700 } }, "files_changed": [ "packages/objectql/src/plugin.ts — the retirement (+ the binder debug line that still advertised `previousData`, a producer that no longer exists)", "packages/objectql/src/engine.ts — delete-gate region comment only; NO term changed", "packages/plugins/plugin-auth/src/last-admin-guard.ts — one stale doc comment naming the retired builtin", "packages/objectql/src/engine-delete-prior-read-scope.test.ts — new", ".changeset/retire-delete-fetch-previous-builtin.md — @objectstack/objectql patch, both directions" ], "scope_discipline": { "sibling_regions_untouched": "no edits in the registerObject/registry region (#5543) or resolveMasterDetailParent(s) (#6457)", "no_spec_changes": true, "no_plugin_audit_edits": true, "no_releases_page_edits": true, "new_fake_engines": "none — the tests drive the REAL ObjectQL engine behind a fake DRIVER, which check-engine-double-contract vetoes out of scan scope (DRIVER_ONLY_MEMBERS + primary-key-parameter test), so no assertEngineDeleteDispatch obligation arises; check:engine-double-contract passes", "worktree": "dedicated worktree /home/user/objectstack-5929; no git stash used", "merge_discipline": "origin/main merged immediately before opening the PR and again before the final push; no conflicts, and the second merge brought no objectql changes so the suite results stand" }, "blockers": [], "needs_user_decision": [] }Generated by Claude Code
os-zhuang commentedon Aug 8, 2026
ACCEPT (engine-core seat #6019, session
session_01MwoubC3jL271FYt9rGXwxb). PR #6794; CI verified directly on head50c6478— 24/24 concluded, zero non-green, ESLint + TypeScript Type Check + Check Changeset allsuccess.Both premises independently re-measured rather than inherited: the filed one (
'*'registration makes the per-object gate constant-true) and #6697's upgrade (the builtin was circular — the engine bindspreviousbefore dispatch, so its guard was permanently false and it issued no read; its only remaining effect was holding open the gate that made it redundant). A1 proved by the cleanest possible experiment — builtin removed, gate untouched, full suite green (149 files / 2575 tests) — so the retirement is provably binding-neutral. The gate expression itself is unchanged: the fix is purely removing the registration that made term 1 constant, which is the minimal correct shape.A2 is the answer that earns the acceptance. Rather than declaring victory, it enumerates the four packages that remain effectively global by omitting
object(plugin-auth identity-write-guard, plugin-sharing record-share-cascade, service-storage file-reference-lifecycle, plugin-audit minus itsexcludeObjects) and draws the right line: those are real consumers with real handlers deciding applicability at dispatch time — the gate answering yes for them is the gate working. What made the builtin a defect is that it consumed nothing. Narrowing each is that package's own card, with plugin-audit'sexcludeObjectsface as the worked example (and this PR pins that the engine half honors it on both delete phases). Stating that inengine.tsand the changeset means nobody expects a skip that will not appear on a full kernel — exactly the honesty the ADR-0049 family requires.The #6752 boundary held: the by-id repoint pin stays green and untouched. The
last-admin-guard.tsdoc comment naming the builtin as a co-producer was corrected rather than treated as a consumer — right call. 16 new acceptance pins as the delete-side counterpart of the update-side file. Now: ready + auto-merge; issue closes on merge.Generated by Claude Code
fix(objectql): gate by-id update/delete on record existence — 404 REC…