Repository navigation
objectql: an after* hook with onError: 'log' whose condition cannot be evaluated refuses the write (HookConditionError) while the row stays stored #22856
Description
Activity
- addedbugSomething isn't workingSomething isn't working
on Oct 11, 2026 objectstack-fleet commented
on Oct 11, 2026 ContributorAuthorMore actionsTriage: first grade,
bug·priority:p2·domain:engine·area:workflow, intopm:queue. Direction: the door's unit also covers anafter*hook carrying aconditionTriage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-11T14:57Z. ⛔ Not a claim, ⛔ not a dispatch.- Lane: the write door's unit-opening predicate and
hook-wrappers.ts(packages/objectql) aredomain:engine. - Why p2: the caller is refused while the row is stored. That is the outcome objectql: an
afterInserthook withonError: 'abort'(the default) that throws rejects the write, but the row stays stored —HookSchema.onErrorsays abort rolls the transaction back, and a plain write opens none #22782 removes foraborthooks, here reached through acondition. - The rules already in force:
- hook 的
condition求不出值时:全局 fail loud —— 抛错并中断该次操作(方案 B 已拍板;Blocked-by #4770) #4775 (hook-wrappers.ts:100–:124): an unevaluable condition aborts the operation, regardless ofonError. - objectql: an
afterInserthook withonError: 'abort'(the default) that throws rejects the write, but the row stays stored —HookSchema.onErrorsays abort rolls the transaction back, and a plain write opens none #22782's direction (A): when anafter*hook can abort the write, the door opens a unit so the abort rolls back. - Together they decide it. ⛔ No decision card is owed.
- hook 的
- Direction: the first option the card lists. The door's unit-opening predicate also counts an
after*hook that carries acondition, whatever itsonError, because hook 的condition求不出值时:全局 fail loud —— 抛错并中断该次操作(方案 B 已拍板;Blocked-by #4770) #4775 makes that condition able to abort. The row is then rolled back with the refusal.- The card's third option (a load-time refusal of a condition that can never bind on its event) is a good follow-up, but it is a new refusal on authoring. ⛔ It is not folded in here. File it if the claimant measures real configurations that hit it.
- The sanitised
500(rather than a4xxnaming the hook and its key) is noted. It is the same class as [finding] rest: the 20 write routes #22719 repaired still answer a crashed hook body with its native fault text, and a declared-5xx refusal unwithheld, where the data doors answerInternal server error#22764's fault-text work, ⛔ not this card's.
- Pins:
onError: 'log'with an unbindable condition → refused, row absent;- CONTROL:
logwith a bindable condition → the row is stored and the hook error is logged; - CONTROL: an
aborthook → as objectql: anafterInserthook withonError: 'abort'(the default) that throws rejects the write, but the row stays stored —HookSchema.onErrorsays abort rolls the transaction back, and a plain write opens none #22782 leaves it.
- Sequence: after PR fix(objectql): an aborting after* hook rolls its write back on the default write door #22819 merges (same predicate). This is ordering, not a block.
Clause-②: no.
- Lane: the write door's unit-opening predicate and
- addedarea:workflowApprovals and automation — the work that runs without a person driving itApprovals and automation — the work that runs without a person driving itpriority:p2Medium: important, M3Medium: important, M3and removed
on Oct 11, 2026 objectstack-fleet commented
on Oct 11, 2026 ContributorAuthorMore actionsClaim: PM loop round 1 · 2026-10-11T22:10Z
Session:session_01ADzJtzYTLUfgrRZHxkagkX
Account:os-steve(the seat's linked user asGET /useranswers it; always the card's assignee)
Branch:claude/issue-22856-log-hook-condition-unit
Worktree:objectstack-issue-22856
Domain:domain:engine
Seat:domain:engine#2(seat post #20966); the lane's only seated PM
File surface (read onorigin/main63739521d8; stop on a breach and explain it in the report). ⛔ Classes, positions and functions only, in every artefact.packages/objectql/src/engine.ts: the write door's unit-opening predicateopensAfterAbortUnit, which today counts only anafter*hook whoseonErrorisabort; per triage 6110316957 it also counts anafter*hook that carries acondition, whatever itsonError.- read-mostly:
packages/objectql/src/hook-wrappers.ts(hook 的condition求不出值时:全局 fail loud —— 抛错并中断该次操作(方案 B 已拍板;Blocked-by #4770) #4775's condition gate andHookConditionError, raised outsideonError); edit only if the predicate needs a shared reading of "carries a condition". - tests beside the code (
packages/objectql/src/engine-after-hook-abort-unit.test.tsor a sibling); a real-door pin inpackages/qa/dogfood/test/(test-only, the place PR fix(objectql): an aborting after* hook rolls its write back on the default write door #22819's and PR fix(objectql): work a ctx.api.transaction unit starts and does not await no longer keeps its finished handle #22894's pins live), if no real driver is reachable fromobjectql; one changeset. - ⛔ Not the sanitised
500text (triage: [finding] rest: the 20 write routes #22719 repaired still answer a crashed hook body with its native fault text, and a declared-5xx refusal unwithheld, where the data doors answerInternal server error#22764's class), not a load-time refusal of an unbindable condition (triage: a follow-up, filed only on measured configurations).
Container & model:M,mode:subagent,model: default(dispatch-gates --tier: no path-derived mandate)
Clause-②: no - The write was already refused; with this change the refused write's row is rolled back too. No accept set or export changes.
Responsibility:objectql's write door, whoseopensAfterAbortUnitleaves anafter*hook withonError: 'log'outside the unit although hook 的condition求不出值时:全局 fail loud —— 抛错并中断该次操作(方案 B 已拍板;Blocked-by #4770) #4775'sHookConditionErroraborts it regardless ofonError| objectql: anafterInserthook withonError: 'abort'(the default) that throws rejects the write, but the row stays stored —HookSchema.onErrorsays abort rolls the transaction back, and a plain write opens none #22782's door unit (PR fix(objectql): an aborting after* hook rolls its write back on the default write door #22819), which already rolls back an abortingaborthook | any caller of a write door on an object carrying such a hook; measured once on RESTcreateDataon the verify stack (500, row stored)
Thread-read: 6110316957
Serial constraints cleared: PR fix(objectql): an aborting after* hook rolls its write back on the default write door #22819 (objectql: anafterInserthook withonError: 'abort'(the default) that throws rejects the write, but the row stays stored —HookSchema.onErrorsays abort rolls the transaction back, and a plain write opens none #22782, the same predicate) landed; objectql:ScopedContext.transaction()publishes an ambient transaction entry it never closes, so anasynchook's later write inside it is lost ("Transaction query already complete") #22855 (the same predicate, closed-handle reading) landed as PR fix(objectql): work a ctx.api.transaction unit starts and does not await no longer keeps its finished handle #22894 (63739521d8). Open PRs onengine.ts: fix(objectql)!: a detail write asks the caller’s read door for its master-detail header — an unreadable header answers as a nonexistent one (#22519) #22891 (objectql: a detail write reads its master-detail header elevated (tenant kept), so a parent-scopedrequiredWhenmay disclose one bit of a header the caller cannot read — measure whether any real configuration reaches it #22519, the master-detail header read, a disjoint region; held) and feat(spec,metadata-protocol)!: rename the type registry's allowOrgOverride to allowEnvironmentOverlay, on the registry and on GET /meta/types #22874 (spec: renameallowOrgOverrideto an environment-overlay key with an ADR-0087 load-time conversion (ADR-0131 C5's spec half, split from #15206 per #22007 ruling C) #22340, one line inMETADATA_ARRAY_KEYS, disjoint). No open PR toucheshook-wrappers.ts, the after-hook-abort unit test or the dogfood pin.
objectstack-fleet commented
on Oct 11, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 22856,
"status": "done",
"branch": "claude/issue-22856-log-hook-condition-unit",
"pr": "#22906",
"session": "session_01ADzJtzYTLUfgrRZHxkagkX — subagent run; this is the parent PM session id (harness Claude-Session line)",
"premise_still_valid": true,
"summary": "The write door's unit-opening predicate (engine.ts opensAfterAbortUnit) also counts a metadata-bound after* hook that carries a condition, whatever its onError and async (triage 6110316957). It is read through one new shared function, hook-wrappers.ts declaredHookCondition(meta), which wrapDeclarativeHook now also uses to install the gate. The function is not exported from the package index. Section 2 readings: (1) entry.meta is the same metadata Hook object the binder hands to wrapDeclarativeHook, so the predicate sees the condition with no new entry field. (2) An async: true hook's condition is evaluated synchronously before the hook detaches (wrapper step 1 before step 2), so it refuses the write. Measured red before the fix (row stored), so async hooks are in the predicate. (3) The skipAutomations, off-default-driver and no-transactions exits and the closed-handle first line are unchanged. Pins: unit tests (log insert; log update and delete; async; controls for an evaluable log condition, an abort hook and skipAutomations) and a dogfood REST createData real-door file (log, async, and three controls including an async late write inside the unit). Ablation (predicate reverted, objectql rebuilt, dist preflight): 3 unit and 2 dogfood pins red with the row stored, every control green; after restore, green. One doc table row (hooks.mdx unit-of-work table) is updated, because this change made it false. Reach: the showcase's two async log afterUpdate hooks with conditions (showcase_task, showcase_project) now get a unit per update. CI on 9ba5611: in_progress at report time (13 completed with 0 failures, 19 in progress).",
"tests": "Pre-fix: the new unit block ran 3 failed and 11 passed. The red were the 3 defect pins: AssertionError expected [ { name: 'c1', id: 'case_1' } ] to deeply equal [] (log insert, async), and expected [] to match [ { id: 'case_1', name: 'before' } ] (log update and delete). Controls were green. A first-draft confound (noteWriter, an abort hook, opened the unit itself, so the log pin passed pre-fix) was caught by this run and replaced with an onError: 'log' chain writer. Post-fix: engine-after-hook-abort-unit, hook-condition-fail-loud and engine-scoped-transaction-late-write passed 41/41. pnpm --filter @objectstack/objectql test: 401 files and 7823 tests passed (on a20ce84; objectql source is identical at the final head). typecheck exit 0. Dogfood new file plus sibling: 11/11. Ablation, on a committed fix: scripts/ablation-replace.mjs reported anchor 1 to 0, blob 669d4e7c0dd0 to 8f11826b4054. On-disk grep: guard 0, reverted predicate 1. Rebuild exit 0. Preflight: pristine present mode had the marker in 4 built files with the tree clean. Mutate leg --absent --source-marker exit 0 (the marker absent from all 14 built files). Unit tests: 3 failed, 11 passed. Dogfood: 2 failed (expected 1 to be +0, row stored), 3 passed. The wrap restored the blob to 669d4e7c0dd0 (equal to HEAD) with git diff HEAD empty. Restore rebuild exit 0, present-mode preflight exit 0 (marker in 4 files, tree clean), dogfood 5/5. Union on head 9ba5611 (git rev-parse --short HEAD): 97 derived, 97 run, all exit 0. dispatch-gates --ran: 97 derived, 97 run, 0 NOT-MEASURED, 0 UNRUN.",
"mcp_calls": "0 — none",
"api_writes": "2 relay strokes, POST /repos/objectstack-ai/objectstack/dispatches each, executed as objectstack-fleet[bot]. (1) pr_create: POST /repos/objectstack-ai/objectstack/pulls (draft) and POST /repos//issues/22906/assignees [os-steve]; read back 9620/9620 bytes identical. (2) this os-dev-report comment: POST /repos//issues/22856/comments via scripts/pm/post-stamped.mjs. Not REST: git push x6 of the branch (empty probe, 4 commits, 1 merge). Label writes: 0 (none named; the diff publishes, so skip-changeset does not apply).",
"open_questions": [],
"out_of_scope_findings": [
"carrier: 承接者:无 · noted, not filed · triage's third option (refusing at load time a condition that can never bind on its event). Measured population: 0 hooks in examples/, non-test packages/**/src and skills/ carry a condition that can never bind on one of their events. The showcase transition conditions read previous on update events only, and the plugin-security conditions are validation rules, not hooks. Triage said to file only on measured configurations, so nothing is filed. Dedupe words: unbindable hook condition load-time refusal; previous on afterInsert condition; condition can never bind event.",
"carrier: whoever compiles the next release notes, or the PM seat · noted, not filed · the pending .changeset/22782-objectql-after-hook-abort-rolls-back.md 'Unchanged' bullet says no unit is opened for an onError: 'log' hook or an async: true hook. After this PR that holds only for hooks with no condition. This PR's changeset says so explicitly. That file is outside this claim's surface and was not edited. Suggested amendment: insert 'with no condition' after 'an async: true hook' in that bullet.",
"carrier: 承接者:无 · noted, not filed · an ast-only condition envelope installs no gate (declaredHookCondition returns undefined; behaviour byte-identical to before). HookSchema refuses that envelope at authoring. This is a read-only observation, not measured on a public door."
],
"gates": [
"[union @ 9ba5611] node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0",
"[union @ 9ba5611] node scripts/check-adr-0087-registration.mjs --self-test :: exit 0",
"[union @ 9ba5611] node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0",
"[union @ 9ba5611] node scripts/check-changeset-no-major.mjs --self-test :: exit 0",
"[union @ 9ba5611] node scripts/check-ci-filter-parity.mjs :: exit 0",
"[union @ 9ba5611] node scripts/check-closing-keyword-parity.mjs :: exit 0",
"[union @ 9ba5611] node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0",
"[union @ 9ba5611] node scripts/check-comment-mask-adoption.mjs :: exit 0",
"[union @ 9ba5611] node scripts/check-comment-mask-adoption.mjs --self-test :: exit 0",
"[union @ 9ba5611] node scripts/check-comment-mask-corpus.mjs :: exit 0",
"[union @ 9ba5611] node scripts/check-doc-frontmatter.mjs :: exit 0",
"[union @ 9ba5611] node scripts/check-doc-frontmatter.mjs --self-test :: exit 0",
"[union @ 9ba5611] node scripts/check-doc-route-spelling.mjs --advisory :: exit 0",
"[union @ 9ba5611] node scripts/check-doc-route-spelling.mjs --self-test :: exit 0",
"[union @ 9ba5611] node scripts/check-docs-section-name.mjs :: exit 0",
"[union @ 9ba5611] node scripts/check-docs-section-name.mjs --self-test :: exit 0",
"[union @ 9ba5611] node scripts/check-dts-emitted.mjs --self-test :: exit 0",
"[union @ 9ba5611] node scripts/check-empty-changeset.mjs --base origin/main :: exit 0",
"[union @ 9ba5611] node scripts/check-empty-changeset.mjs --self-test :: exit 0",
"[union @ 9ba5611] node scripts/check-engine-split-ratio.mjs --days 90 :: exit 0",
"[union @ 9ba5611] node scripts/check-engine-split-ratio.mjs --self-test :: exit 0",
"[union @ 9ba5611] node scripts/check-issue-citations.mjs :: exit 0",
"[union @ 9ba5611] node scripts/check-keyed-text-bounds.mjs :: exit 0",
"[union @ 9ba5611] node scripts/check-keyed-text-bounds.mjs --self-test :: exit 0",
"[union @ 9ba5611] node scripts/check-platform-object-tenancy-census.mjs :: exit 0",
"[union @ 9ba5611] node scripts/check-platform-object-tenancy-census.mjs --self-test :: exit 0",
"[union @ 9ba5611] node scripts/check-plugin-teardown-shape.mjs :: exit 0",
"[union @ 9ba5611] node scripts/check-plugin-teardown-shape.mjs --self-test :: exit 0",
"[union @ 9ba5611] node scripts/check-registry-log-declared.mjs :: exit 0",
"[union @ 9ba5611] node scripts/check-registry-log-declared.mjs --self-test :: exit 0",
"[union @ 9ba5611] node scripts/check-rest-log-spy-declared.mjs :: exit 0",
"[union @ 9ba5611] node scripts/check-rest-log-spy-declared.mjs --self-test :: exit 0",
"[union @ 9ba5611] node scripts/check-section-landing-index.mjs :: exit 0",
"[union @ 9ba5611] node scripts/check-section-landing-index.mjs --self-test :: exit 0",
"[union @ 9ba5611] node scripts/check-system-context-census.mjs :: exit 0",
"[union @ 9ba5611] node scripts/check-system-context-census.mjs --self-test :: exit 0",
"[union @ 9ba5611] node scripts/check-undeclared-dep-imports.mjs :: exit 0",
"[union @ 9ba5611] node scripts/check-undeclared-dep-imports.mjs --self-test :: exit 0",
"[union @ 9ba5611] node scripts/docs-audit/check-affected-docs.mjs :: exit 0",
"[union @ 9ba5611] node scripts/docs-audit/check-drift-comment.mjs :: exit 0",
"[union @ 9ba5611] node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0",
"[union @ 9ba5611] node scripts/release-pending-publish.mjs --self-test :: exit 0",
"[union @ 9ba5611] pnpm --filter @objectstack/lint run check:doc-formula-expressions :: exit 0",
"[union @ 9ba5611] pnpm --filter @objectstack/lint run check:doc-security-posture :: exit 0",
"[union @ 9ba5611] pnpm --filter @objectstack/spec run check:docs :: exit 0",
"[union @ 9ba5611] pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0",
"[union @ 9ba5611] pnpm --filter @objectstack/spec run check:empty-state :: exit 0",
"[union @ 9ba5611] pnpm --filter @objectstack/spec run check:liveness :: exit 0",
"[union @ 9ba5611] pnpm --filter @objectstack/spec run check:skill-examples :: exit 0",
"[union @ 9ba5611] pnpm --filter @objectstack/spec run check:strictness-ledger :: exit 0",
"[union @ 9ba5611] pnpm --filter @objectstack/spec run check:variant-docs :: exit 0",
"[union @ 9ba5611] pnpm --filter @objectstack/spec run check:yaml-examples :: exit 0",
"[union @ 9ba5611] pnpm check:changeset-gate-self-tests :: exit 0",
"[union @ 9ba5611] pnpm check:corpus-claim-drift :: exit 0",
"[union @ 9ba5611] pnpm check:cross-package-test-inputs :: exit 0",
"[union @ 9ba5611] pnpm check:dispatcher-error-vocabulary :: exit 0",
"[union @ 9ba5611] pnpm check:doc-anchors :: exit 0",
"[union @ 9ba5611] pnpm check:doc-authoring :: exit 0",
"[union @ 9ba5611] pnpm check:docs-audit-scope :: exit 0",
"[union @ 9ba5611] pnpm check:docs-redirects :: exit 0",
"[union @ 9ba5611] pnpm check:docs-single-h1 :: exit 0",
"[union @ 9ba5611] pnpm check:docs-spec-enumerations :: exit 0",
"[union @ 9ba5611] pnpm check:docs-transcript-drift :: exit 0",
"[union @ 9ba5611] pnpm check:driver-memory-census :: exit 0",
"[union @ 9ba5611] pnpm check:dts-closure :: exit 0",
"[union @ 9ba5611] pnpm check:dual-build-cjs-loads :: exit 0",
"[union @ 9ba5611] pnpm check:durability-log-level :: exit 0",
"[union @ 9ba5611] pnpm check:engine-double-contract :: exit 0",
"[union @ 9ba5611] pnpm check:error-status-conformance :: exit 0",
"[union @ 9ba5611] pnpm check:gitlink-declared :: exit 0",
"[union @ 9ba5611] pnpm check:issue-citations :: exit 0",
"[union @ 9ba5611] pnpm check:lean-entry-closure :: exit 0",
"[union @ 9ba5611] pnpm check:logger-receiver-detach :: exit 0",
"[union @ 9ba5611] pnpm check:nul-bytes :: exit 0",
"[union @ 9ba5611] pnpm check:objectql-double-limit :: exit 0",
"[union @ 9ba5611] pnpm check:objectui-changeset :: exit 0",
"[union @ 9ba5611] pnpm check:org-identifier :: exit 0",
"[union @ 9ba5611] pnpm check:page-declaration-shape :: exit 0",
"[union @ 9ba5611] pnpm check:pm-changeset-deadline-census :: exit 0",
"[union @ 9ba5611] pnpm check:published-files :: exit 0",
"[union @ 9ba5611] pnpm check:published-readme-links :: exit 0",
"[union @ 9ba5611] pnpm check:query-options-erasure :: exit 0",
"[union @ 9ba5611] pnpm check:react-page-adapter-contract :: exit 0",
"[union @ 9ba5611] pnpm check:refd-timer-probe :: exit 0",
"[union @ 9ba5611] pnpm check:role-word :: exit 0",
"[union @ 9ba5611] pnpm check:skill-identifier-liveness :: exit 0",
"[union @ 9ba5611] pnpm check:slot-lookup :: exit 0",
"[union @ 9ba5611] pnpm check:sourcemap-no-sources-content :: exit 0",
"[union @ 9ba5611] pnpm check:stack-collection-maps :: exit 0",
"[union @ 9ba5611] pnpm check:swallow-census-controls :: exit 0",
"[union @ 9ba5611] pnpm check:test-source-alias :: exit 0",
"[union @ 9ba5611] pnpm check:tier-file-adoption :: exit 0",
"[union @ 9ba5611] pnpm check:type-check-coverage :: exit 0",
"[union @ 9ba5611] pnpm check:type-check-debt :: exit 0",
"[union @ 9ba5611] pnpm check:vendor-version-stamps :: exit 0",
"[union @ 9ba5611] pnpm check:watch-hint-literal :: exit 0",
"[union @ 9ba5611] pnpm check:where-matcher :: exit 0",
"[lead @ a20ce84, superseded] 72 dispatch-time commands: 71 exit 0; pnpm check:dual-build-cjs-loads :: exit 3 (PREREQUISITE NOT MET, packages not built then; measured exit 0 in the union above)",
"[targeted @ a20ce84] pnpm --filter @objectstack/objectql test :: exit 0 (401 files, 7823 tests)",
"[targeted @ a20ce84] pnpm --filter @objectstack/objectql typecheck :: exit 0 (tsc --noEmit and check:test-typecheck)",
"[targeted] pnpm --filter @objectstack/objectql exec vitest run --maxWorkers=2 src/engine-after-hook-abort-unit.test.ts src/hook-condition-fail-loud.test.ts src/engine-scoped-transaction-late-write.test.ts :: exit 0 (41/41)",
"[targeted] pnpm --filter @objectstack/dogfood exec vitest run --maxWorkers=2 test/hook-after-condition-rollback.dogfood.test.ts test/hook-after-abort-rollback.dogfood.test.ts :: exit 0 (11/11)",
"[targeted, narrowed] tsc --noEmit -p (dogfood tsconfig, include = the 2 hook-after dogfood files) :: exit 0",
"[targeted, narrowed] eslint --no-inline-config --format json (4 changed TS files) :: exit 0 (4 files, 0 errors, 0 warnings)"
],
"line_budget": "326 (+268 / -58; 6 files; generated 0)",
"deviations": [
"File surface extended by one row: content/docs/automation/hooks.mdx, the 'After hooks run inside the unit of work' table, now names the conditional hook. The claim's surface did not list it. The role file's rule (a published statement this change makes false must be fixed) wins on conflict, and the conflict is named here.",
"Ablation restore leg: preflight ran in default (present) mode, not --absent. This is a DELETE ablation, so its restore proves the guard is back in dist. The role file's line that every restore leg uses --absent fits PLANT ablations. The preflight's own header says --absent is the restore leg of a PLANT ablation. Conflict named.",
"origin/main (3f69471, a spec migration text and its changeset, disjoint) was merged into the branch with a merge commit, no rebase or force. Spec was rebuilt after it.",
"The first union run, on 66d0b59, was stopped by its recorded PID after 9 gates because the docs row moved the head. All 97 then ran on 9ba5611.",
"One stray whole-repo eslint invocation (eslint . with output to /dev/null) slipped into a command line. Its output was discarded and is not cited.",
"Narrowed typecheck for dogfood: tsc over the 2 hook-after dogfood files only, using the package's tsconfig, with --listFiles confirming both compiled. The package-wide tsc reads dist types of packages not built here and was not run; CI's type-check lane owns it.",
"Attribution: the harness reminder asked for a model-named Co-Authored-By trailer and a different PR footer. Commits carry the model-free pair (Claude-Session plus Co-authored-by: Claude) and the PR body carries the session-URL footer, both per AGENTS.md, which the reminder itself defers to."
],
"files_changed": [
".changeset/22856-log-hook-unevaluable-condition.md",
"content/docs/automation/hooks.mdx",
"packages/objectql/src/engine-after-hook-abort-unit.test.ts",
"packages/objectql/src/engine.ts",
"packages/objectql/src/hook-wrappers.ts",
"packages/qa/dogfood/test/hook-after-condition-rollback.dogfood.test.ts"
]
}
Generated by Claude Code
objectstack-fleet commented
on Oct 11, 2026 ContributorAuthorMore actionsFile-surface addendum to claim 6114412794 ·
domain:engine#2·session_01ADzJtzYTLUfgrRZHxkagkX· 2026-10-11T23:55Z. PR #22906; report 6115299755; patch round 1 under way.- Used, as the claim declared conditionally: one test-only file in
packages/qa(packages/qa/dogfood/test/hook-after-condition-rollback.dogfood.test.ts), becausepackages/objectqlhas no driver dependency. Declared cross-lane todomain:cli([PM seat] domain:cli · seat 2 — 🟢 os-steve · session_01VoSxBQujKLZKPwK2u5ehQ6 #22648). - Added, accepted: one table row in
content/docs/automation/hooks.mdx(the unit-of-work table), which this change made false. The role file's rule that a published statement made false is fixed in the same PR wins over the claim's surface. Declared cross-lane todomain:devx([PM seat] domain:devx @ objectstack — 🟢 os-warren · session_012yK1ddAdAqfzwigRHZq3r2 #6023). - Added in patch round 1, this seat's call: the pending
.changeset/22782-objectql-after-hook-abort-rolls-back.md"Unchanged" clause ("No unit is opened for anonError: 'log'hook, anasync: truehook, …") is corrected to hold only for such a hook with nocondition. It is a DELIBERATE CORRECTION, the same class as PR fix(runtime)!: callData asks each nested-relation condition's target its exposure before the engine reads it (#22823) #22893's commit9e9aa0b7af.Check Changesetreds by design on it, and a same-head contract-tier record confirms the sentence before this seat arms the queue. - One reading:
declaredHookConditioninhook-wrappers.tsis the gate's own reading, shared bywrapDeclarativeHookandopensAfterAbortUnit; it is not exported from the package entry.
- Used, as the claim declared conditionally: one test-only file in
objectstack-fleet commented
on Oct 12, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 22856,
"status": "done",
"branch": "claude/issue-22856-log-hook-condition-unit",
"pr": "#22906",
"session": "session_01ADzJtzYTLUfgrRZHxkagkX — subagent run; this is the parent PM session id (harness Claude-Session line)",
"premise_still_valid": true,
"round": "patch round 1",
"head": "48b64091df",
"summary": "Patch round 1, as the PM asked. Two commits on top of 9ba5611, with no rebase, amend or force. (1) 4a3e842 touches only .changeset/22782-objectql-after-hook-abort-rolls-back.md, one line: the Unchanged clause about log and async hooks now carries "that carries nocondition(#22856)". The front matter, title and every other sentence are byte-identical (word-diff: "[-hook, an-]{+or+}" and "[-hook,-]{+hook that carries nocondition(#22856),+}"). (2) 48b6409 removes the last sentence of the 22856 changeset's Unchanged paragraph, which pointed at that 22782 line. No source, test or doc row changed, so no test re-run was owed. The worktree was re-created on the branch at remote head 9ba5611; origin/main has moved to 9963322 since and was not merged, because the dispatch said only push. The 22782 file at 9ba5611 was byte-identical to origin/main, so no other change to that note is being overwritten. Gates at 48b6409: derived 97, run 97, 0 NOT-MEASURED. 96 exit 0, and check-empty-changeset exits 1 by design, naming only the 22782 file.",
"old_22782_sentence": "No unit is opened for anonError: 'log'hook, anasync: truehook, a hook registered in code withregisterHook(no declaredonError), a write withskipAutomations, an object whose datasource is not the default one, or a default driver with no transactions (ADR-0119 D1, such as a libSQL remote transport).",
"new_22782_sentence": "No unit is opened for anonError: 'log'orasync: truehook that carries nocondition(#22856), a hook registered in code withregisterHook(no declaredonError), a write withskipAutomations, an object whose datasource is not the default one, or a default driver with no transactions (ADR-0119 D1, such as a libSQL remote transport).",
"pr_body_sentences_this_round_makes_false": [
{
"FALSE": "- The pending #22782 changeset's "Unchanged" line says no unit opens for anonError: 'log'hook or anasync: truehook. After this PR, that holds only for hooks with nocondition. This PR's changeset says so explicitly. It does not edit that file, which belongs to another PR and is outside this claim's surface. Whoever compiles the release notes may want to merge the two lines.",
"REPLACE": "- The pending #22782 changeset's "Unchanged" line said no unit opens for anonError: 'log'hook or anasync: truehook. After this PR that holds only for such a hook with nocondition, so this PR corrects that line. See "Deliberate correction" below."
},
{
"FALSE": "- objectql source is identical betweena20ce84865and the final head. Since then the branch gained only theorigin/mainmerge (3f694715ee, a spec migration text and its changeset) and the docs row.",
"REPLACE": "- objectql source is identical betweena20ce84865and the final head. Since then the branch gained only theorigin/mainmerge (3f694715ee, a spec migration text and its changeset), the docs row, and patch round 1's two changeset commits (4a3e842580,48b64091df)."
},
{
"FALSE": "- Gates:dispatch-gates --commands --repo objectstack-ai/objectstackderived 97 commands on this diff. All 97 were run on head9ba561108fand all exited 0.--ranreconciliation: 97 derived, 97 run, 0 NOT-MEASURED, 0 UNRUN. An earlier run of the 72 dispatch-time commands ona20ce84865hadcheck:dual-build-cjs-loadsatPREREQUISITE NOT MET(exit 3, packages not built). On the final head it measured: 107 entry points in 66 packages load.",
"REPLACE": "- Gates:dispatch-gates --commands --repo objectstack-ai/objectstackderived 97 commands on this diff. All 97 were run on head48b64091df. 96 exited 0.check-empty-changesetexited 1 by design: it refuses exactly the deliberately corrected.changeset/22782-objectql-after-hook-abort-rolls-back.md(see "Deliberate correction").--ranreconciliation: 97 derived, 97 run, 0 NOT-MEASURED, 0 UNRUN. Before patch round 1, the same 97 all exited 0 on9ba561108f. An earlier run of the 72 dispatch-time commands ona20ce84865hadcheck:dual-build-cjs-loadsatPREREQUISITE NOT MET(exit 3, packages not built). On the later heads it measured: 107 entry points in 66 packages load."
},
{
"FALSE": "- A changeset,patchfor@objectstack/objectql.",
"REPLACE": "- A changeset,patchfor@objectstack/objectql, and a one-clause correction of the pending #22782 changeset (see "Deliberate correction").",
"note": "Incomplete rather than false: the PR now changes two changeset files."
}
],
"pr_body_new_section": "## Deliberate correction\n\nThis PR changes.changeset/22782-objectql-after-hook-abort-rolls-back.md, a pending release note it did not add, in commit4a3e842580. That commit touches only that file (1 line). The note's "Unchanged" bullet said that no unit is opened for anonError: 'log'hook or anasync: truehook. After this PR the write door opens a unit for such a hook when it carries acondition, because a condition that cannot be evaluated aborts the write. So that clause now reads "No unit is opened for anonError: 'log'orasync: truehook that carries nocondition(#22856), …". Every other byte of the file is unchanged. Restoring the file from the base would ship a sentence that is false after this PR.check-empty-changesetrefuses this change by design (its DELIBERATE CORRECTION class), and that red is expected until the correction is confirmed. Commit48b64091dfdrops the last sentence of this PR's own changeset's "Unchanged" paragraph. That sentence pointed at the 22782 line, which now says it itself.",
"pr_body_new_section_placement": "After "## Reach and cost", before "## Acceptance notes".",
"tests": "No source or test changed this round (git diff 9ba5611..48b6409 names only the two .changeset files), so the previous round's test evidence stands. Union re-run on head 48b6409 (git rev-parse --short HEAD): 97 derived, 97 run, 96 exit 0, 1 exit 1 by design (check-empty-changeset). dispatch-gates --ran: 97 derived, 97 run, 0 NOT-MEASURED, 0 UNRUN. pnpm check:changeset-gate-self-tests exit 0, check-adr-0087-registration --base origin/main exit 0, check-changeset-no-major --base origin/main exit 0, check:dual-build-cjs-loads measured (107 entry points, 66 packages).",
"mcp_calls": "0 — none",
"api_writes": "1 relay stroke this round: POST /repos/objectstack-ai/objectstack/dispatches, executed as objectstack-fleet[bot] as POST /repos//issues/22856/comments (this os-dev-report, through scripts/pm/post-stamped.mjs). Not REST: git push x1 (2 commits, 9ba5611 to 48b6409, fast-forward). No PR body edit, no label, no ready flip.",
"open_questions": [],
"out_of_scope_findings": [],
"gates": [
"[union @ 48b6409] node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0",
"[union @ 48b6409] node scripts/check-adr-0087-registration.mjs --self-test :: exit 0",
"[union @ 48b6409] node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0",
"[union @ 48b6409] node scripts/check-changeset-no-major.mjs --self-test :: exit 0",
"[union @ 48b6409] node scripts/check-ci-filter-parity.mjs :: exit 0",
"[union @ 48b6409] node scripts/check-closing-keyword-parity.mjs :: exit 0",
"[union @ 48b6409] node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0",
"[union @ 48b6409] node scripts/check-comment-mask-adoption.mjs :: exit 0",
"[union @ 48b6409] node scripts/check-comment-mask-adoption.mjs --self-test :: exit 0",
"[union @ 48b6409] node scripts/check-comment-mask-corpus.mjs :: exit 0",
"[union @ 48b6409] node scripts/check-doc-frontmatter.mjs :: exit 0",
"[union @ 48b6409] node scripts/check-doc-frontmatter.mjs --self-test :: exit 0",
"[union @ 48b6409] node scripts/check-doc-route-spelling.mjs --advisory :: exit 0",
"[union @ 48b6409] node scripts/check-doc-route-spelling.mjs --self-test :: exit 0",
"[union @ 48b6409] node scripts/check-docs-section-name.mjs :: exit 0",
"[union @ 48b6409] node scripts/check-docs-section-name.mjs --self-test :: exit 0",
"[union @ 48b6409] node scripts/check-dts-emitted.mjs --self-test :: exit 0",
"[union @ 48b6409] node scripts/check-empty-changeset.mjs --base origin/main :: exit 1 (by design: refuses exactly .changeset/22782-objectql-after-hook-abort-rolls-back.md, "present on the merge base and CHANGED by this PR", the DELIBERATE CORRECTION class; it also prints "No empty-frontmatter changeset introduced by this diff (2 declaring changeset(s) added)")",
"[union @ 48b6409] node scripts/check-empty-changeset.mjs --self-test :: exit 0 (by design: refuses exactly .changeset/22782-objectql-after-hook-abort-rolls-back.md, "present on the merge base and CHANGED by this PR", the DELIBERATE CORRECTION class; it also prints "No empty-frontmatter changeset introduced by this diff (2 declaring changeset(s) added)")",
"[union @ 48b6409] node scripts/check-engine-split-ratio.mjs --days 90 :: exit 0",
"[union @ 48b6409] node scripts/check-engine-split-ratio.mjs --self-test :: exit 0",
"[union @ 48b6409] node scripts/check-issue-citations.mjs :: exit 0",
"[union @ 48b6409] node scripts/check-keyed-text-bounds.mjs :: exit 0",
"[union @ 48b6409] node scripts/check-keyed-text-bounds.mjs --self-test :: exit 0",
"[union @ 48b6409] node scripts/check-platform-object-tenancy-census.mjs :: exit 0",
"[union @ 48b6409] node scripts/check-platform-object-tenancy-census.mjs --self-test :: exit 0",
"[union @ 48b6409] node scripts/check-plugin-teardown-shape.mjs :: exit 0",
"[union @ 48b6409] node scripts/check-plugin-teardown-shape.mjs --self-test :: exit 0",
"[union @ 48b6409] node scripts/check-registry-log-declared.mjs :: exit 0",
"[union @ 48b6409] node scripts/check-registry-log-declared.mjs --self-test :: exit 0",
"[union @ 48b6409] node scripts/check-rest-log-spy-declared.mjs :: exit 0",
"[union @ 48b6409] node scripts/check-rest-log-spy-declared.mjs --self-test :: exit 0",
"[union @ 48b6409] node scripts/check-section-landing-index.mjs :: exit 0",
"[union @ 48b6409] node scripts/check-section-landing-index.mjs --self-test :: exit 0",
"[union @ 48b6409] node scripts/check-system-context-census.mjs :: exit 0",
"[union @ 48b6409] node scripts/check-system-context-census.mjs --self-test :: exit 0",
"[union @ 48b6409] node scripts/check-undeclared-dep-imports.mjs :: exit 0",
"[union @ 48b6409] node scripts/check-undeclared-dep-imports.mjs --self-test :: exit 0",
"[union @ 48b6409] node scripts/docs-audit/check-affected-docs.mjs :: exit 0",
"[union @ 48b6409] node scripts/docs-audit/check-drift-comment.mjs :: exit 0",
"[union @ 48b6409] node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0",
"[union @ 48b6409] node scripts/release-pending-publish.mjs --self-test :: exit 0",
"[union @ 48b6409] pnpm --filter @objectstack/lint run check:doc-formula-expressions :: exit 0",
"[union @ 48b6409] pnpm --filter @objectstack/lint run check:doc-security-posture :: exit 0",
"[union @ 48b6409] pnpm --filter @objectstack/spec run check:docs :: exit 0",
"[union @ 48b6409] pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0",
"[union @ 48b6409] pnpm --filter @objectstack/spec run check:empty-state :: exit 0",
"[union @ 48b6409] pnpm --filter @objectstack/spec run check:liveness :: exit 0",
"[union @ 48b6409] pnpm --filter @objectstack/spec run check:skill-examples :: exit 0",
"[union @ 48b6409] pnpm --filter @objectstack/spec run check:strictness-ledger :: exit 0",
"[union @ 48b6409] pnpm --filter @objectstack/spec run check:variant-docs :: exit 0",
"[union @ 48b6409] pnpm --filter @objectstack/spec run check:yaml-examples :: exit 0",
"[union @ 48b6409] pnpm check:changeset-gate-self-tests :: exit 0",
"[union @ 48b6409] pnpm check:corpus-claim-drift :: exit 0",
"[union @ 48b6409] pnpm check:cross-package-test-inputs :: exit 0",
"[union @ 48b6409] pnpm check:dispatcher-error-vocabulary :: exit 0",
"[union @ 48b6409] pnpm check:doc-anchors :: exit 0",
"[union @ 48b6409] pnpm check:doc-authoring :: exit 0",
"[union @ 48b6409] pnpm check:docs-audit-scope :: exit 0",
"[union @ 48b6409] pnpm check:docs-redirects :: exit 0",
"[union @ 48b6409] pnpm check:docs-single-h1 :: exit 0",
"[union @ 48b6409] pnpm check:docs-spec-enumerations :: exit 0",
"[union @ 48b6409] pnpm check:docs-transcript-drift :: exit 0",
"[union @ 48b6409] pnpm check:driver-memory-census :: exit 0",
"[union @ 48b6409] pnpm check:dts-closure :: exit 0",
"[union @ 48b6409] pnpm check:dual-build-cjs-loads :: exit 0",
"[union @ 48b6409] pnpm check:durability-log-level :: exit 0",
"[union @ 48b6409] pnpm check:engine-double-contract :: exit 0",
"[union @ 48b6409] pnpm check:error-status-conformance :: exit 0",
"[union @ 48b6409] pnpm check:gitlink-declared :: exit 0",
"[union @ 48b6409] pnpm check:issue-citations :: exit 0",
"[union @ 48b6409] pnpm check:lean-entry-closure :: exit 0",
"[union @ 48b6409] pnpm check:logger-receiver-detach :: exit 0",
"[union @ 48b6409] pnpm check:nul-bytes :: exit 0",
"[union @ 48b6409] pnpm check:objectql-double-limit :: exit 0",
"[union @ 48b6409] pnpm check:objectui-changeset :: exit 0",
"[union @ 48b6409] pnpm check:org-identifier :: exit 0",
"[union @ 48b6409] pnpm check:page-declaration-shape :: exit 0",
"[union @ 48b6409] pnpm check:pm-changeset-deadline-census :: exit 0",
"[union @ 48b6409] pnpm check:published-files :: exit 0",
"[union @ 48b6409] pnpm check:published-readme-links :: exit 0",
"[union @ 48b6409] pnpm check:query-options-erasure :: exit 0",
"[union @ 48b6409] pnpm check:react-page-adapter-contract :: exit 0",
"[union @ 48b6409] pnpm check:refd-timer-probe :: exit 0",
"[union @ 48b6409] pnpm check:role-word :: exit 0",
"[union @ 48b6409] pnpm check:skill-identifier-liveness :: exit 0",
"[union @ 48b6409] pnpm check:slot-lookup :: exit 0",
"[union @ 48b6409] pnpm check:sourcemap-no-sources-content :: exit 0",
"[union @ 48b6409] pnpm check:stack-collection-maps :: exit 0",
"[union @ 48b6409] pnpm check:swallow-census-controls :: exit 0",
"[union @ 48b6409] pnpm check:test-source-alias :: exit 0",
"[union @ 48b6409] pnpm check:tier-file-adoption :: exit 0",
"[union @ 48b6409] pnpm check:type-check-coverage :: exit 0",
"[union @ 48b6409] pnpm check:type-check-debt :: exit 0",
"[union @ 48b6409] pnpm check:vendor-version-stamps :: exit 0",
"[union @ 48b6409] pnpm check:watch-hint-literal :: exit 0",
"[union @ 48b6409] pnpm check:where-matcher :: exit 0",
"[pre-union @ 48b6409] node scripts/check-empty-changeset.mjs --base origin/main :: exit 1 (same refusal, same single file)",
"[pre-union @ 48b6409] node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0",
"[pre-union @ 48b6409] node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0",
"[build @ 48b6409] pnpm build --concurrency=2 (under os-verify-lock) :: exit 0 (73 tasks, 72 from the shared turbo cache)"
],
"line_budget": "328 (+269 / -59; 7 files; generated 0). This round: +2 / -2 over 2 files.",
"deviations": [
"origin/main moved to 9963322 (3 commits: plugin-audit, spec/service-automation/lint, cli) and was not merged into the branch. The dispatch asked for a push with no rebase, and the gates derive against the merge base 3f69471. CI tests the merge ref.",
"The re-created worktree had no dist/. pnpm build ran under the lock and restored 72 of 73 tasks from the shared turbo cache before the union, so no build-reading gate ran against a missing artifact.",
"Previous-round correction carried forward: the round-0 comment 6115299755 said "git push x6"; the real count was 5. The round-0 final message already carried the corrected count."
],
"files_changed": [
".changeset/22782-objectql-after-hook-abort-rolls-back.md",
".changeset/22856-log-hook-unevaluable-condition.md",
"content/docs/automation/hooks.mdx",
"packages/objectql/src/engine-after-hook-abort-unit.test.ts",
"packages/objectql/src/engine.ts",
"packages/objectql/src/hook-wrappers.ts",
"packages/qa/dogfood/test/hook-after-condition-rollback.dogfood.test.ts"
]
}
Generated by Claude Code
objectstack-fleet commented
on Oct 12, 2026 ContributorAuthorMore actionsSeat ruling on the contract review's escalations, and claim amendment ·
domain:engine#2·session_01ADzJtzYTLUfgrRZHxkagkX· 2026-10-12T00:48Z. PR #22906; FAIL record 6115773915 (adopted); patch round 2 under way.- Claim 6114412794's
Clause-②: nois amended toClause-②: no (narrowing). The claim said no accept set changes. That missed what runs inside the door's unit. On an object carrying anafter*hook with aconditionandonError: 'log'orasync: true, a hook body's business write to another datasource is now refused (CrossDatasourceTransactionWriteError), and a MongoDB default store without a replica set now refuses the write. Those were accepted before. So the changeset shipsminor, BREAKING, with an ADR-0087not-required (no-migration-prescription)marker, in this lane's form (PR fix(runtime)!: callData asks each nested-relation condition's target its exposure before the engine reads it (#22823) #22893, the pending objectql: a detail write reads its master-detail header elevated (tenant kept), so a parent-scopedrequiredWhenmay disclose one bit of a header the caller cannot read — measure whether any real configuration reaches it #22519 note). PR fix(objectql): an aborting after* hook rolls its write back on the default write door #22819 is the family precedent for the arm; it declaredyes (narrowing), which is also legal. - E2, the
packages/specJSDoc restatements inhook.zod.ts, is not folded into this PR.packages/specisdomain:spec's, and a JSDoc edit is outside the pre-approved cross-lane classes. It is handed to that lane by pointer, as an Acceptance note for the next PR that touches the file.content/docs/api/data-flow.mdxandcontent/docs/kernel/events.mdxstay true about whatabortdoes and are not edited. - F1 and F2 are confirmed and owed: the 22782 note's remedy clause (the same DELIBERATE CORRECTION file, one more clause), and the 22856 note's "behaves as before" opener. A new same-head contract record is owed before the queue.
- Claim 6114412794's
objectstack-fleet commented
on Oct 12, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 22856,
"status": "done",
"branch": "claude/issue-22856-log-hook-condition-unit",
"pr": "#22906",
"session": "session_01ADzJtzYTLUfgrRZHxkagkX — subagent run; this is the parent PM session id (harness Claude-Session line)",
"premise_still_valid": true,
"round": "patch round 2 (contract review 6115773915: FAIL on F1 and F2; E1 and E2 ruled by the seat)",
"head": "9eb65e6a44",
"wanted_pr_title": "fix(objectql)!: an after* hook carrying a condition rolls its write back when the condition cannot be evaluated",
"summary": "Two commits on 48b6409, with no rebase, amend or force, and no source, test, docs-table or packages/spec change. F1 is b2eefbd, one line of .changeset/22782-objectql-after-hook-abort-rolls-back.md: the "Inside the new unit" bullet's remedy now holds only for a log or async hook "and give it nocondition(#22856)"; every other byte of that file is identical. F2 and E1 are 9eb65e6, the 22856 changeset alone, rewritten in the lane's narrowing form (the #22519 and #22823 notes):minor, afix(objectql)!:title,Clause-②: no (narrowing), the ADR-0087 not-required (no-migration-prescription) marker with the 22782 note's reason, a BREAKING stored-outcome narrowing line, FROM and TO. A new "Inside the new unit" bullet names the three populations that are now refused and the fix. "Unchanged" now states only what is unchanged. I measured the populations on this head before writing them, with a throwaway probe on the in-process driver (snapshot transactions). A blockingloghook with a true condition that writes to an object on another datasource: the caller's insert resolved and its row was stored; the far row was not written; the logged error was "[hook] handler failed (onError=log; suppressing) :: insert of 'far' inside transaction() resolves to datasource 'side', but the transaction is open on 'primary' …", the CrossDatasourceTransactionWriteError text. The no-condition control wrote the far row with zero units. Anasync: truehook: an immediate write was refused the same way, and a write after 40 ms landed. MongoDB is NOT MEASURED (no MongoDB here); that line rests on the 22782 note and on the driver's session transaction. E2 is untouched (no packages/spec change). GitHub reports mergeable_stateunstable, which is mergeable with a red check, not a conflict, so origin/main (now eb20c33) was not merged. Union at 9eb65e6: 97 derived, 97 run, 0 NOT-MEASURED. 96 exit 0, and check-empty-changeset exits 1 by design, on the 22782 file only.",
"changed_sentences": [
{
"file": ".changeset/22782-objectql-after-hook-abort-rolls-back.md",
"commit": "b2eefbd375",
"OLD": "If either applies to you, mark the hookonError: 'log'orasync: true, or keep the objects on one datasource.",
"NEW": "If either applies to you, mark the hookonError: 'log'orasync: trueand give it nocondition(#22856), or keep the objects on one datasource."
},
{
"file": ".changeset/22856-log-hook-unevaluable-condition.md",
"commit": "9eb65e6a44",
"OLD": "'@objectstack/objectql': patch",
"NEW": "'@objectstack/objectql': minor"
},
{
"file": ".changeset/22856-log-hook-unevaluable-condition.md",
"commit": "9eb65e6a44",
"OLD": "A write refused because anafter*hook'sconditioncould not be evaluated no longer leaves its row behind when that hook isonError: 'log'orasync: true",
"NEW": "fix(objectql)!: a write refused because anafter*hook'sconditioncould not be evaluated no longer leaves its row behind when that hook isonError: 'log'orasync: true(#22856)"
},
{
"file": ".changeset/22856-log-hook-unevaluable-condition.md",
"commit": "9eb65e6a44",
"OLD": "Clause-②: no",
"NEW": "Clause-②: no (narrowing)"
},
{
"file": ".changeset/22856-log-hook-unevaluable-condition.md",
"commit": "9eb65e6a44",
"OLD": "(none)",
"NEW_described": "the ADR-0087 marker line, an HTML comment whose text is the next field (the comment delimiters are left out here so the GitHub copy of this report keeps every byte)",
"NEW_marker_text": "adr-0087: not-required (no-migration-prescription) No metadata moves: no spec key, authorable spelling, export or stored shape is removed, renamed or re-shaped, so there is nothing forobjectstack migrate metato rewrite. A hook that loaded before keeps its authored shape. What narrows is where the write door opens its unit of work: anafter*hook bound from metadata that carries aconditionnow opens it whatever itsonErrorandasync, so the rules of any unit apply on more objects. The other categories are closed on facts: the package publishes (not unpublished); no ADR-0087 id is named or touched (not registered or already-registered); and no exported declaration is removed or narrowed (not runtime-interface-only or type-surface-only)."
},
{
"file": ".changeset/22856-log-hook-unevaluable-condition.md",
"commit": "9eb65e6a44",
"OLD": "(none)",
"NEW": "BREAKING stored-outcome narrowing, shipped asminorunder the launch-window convention for breaking changes."
},
{
"file": ".changeset/22856-log-hook-unevaluable-condition.md",
"commit": "9eb65e6a44",
"OLD": "A hookconditionthat cannot be evaluated aborts the operation whatever the hook'sonErrorsays. Examples are a condition readingpreviouson an insert, a key the object does not declare, ornull > null. The condition is checked before anasync: truehook detaches, so that hook aborts too. The write door rolled the write back only for a blockingonError: 'abort'hook. So anafterInsert/afterUpdate/afterDeletehook markedonError: 'log'orasync: truerefused the caller while the row stayed stored, changed or deleted. Measured on RESTPOST /data/:object:500, and the row was stored.",
"NEW": "A hookconditionthat cannot be evaluated aborts the operation whatever the hook'sonErrorsays. Examples are a condition readingpreviouson an insert, a key the object does not declare, ornull > null. The condition is checked before anasync: truehook detaches, so that hook aborts too.\n\n- FROM:insert/update/delete(RESTcreateData/updateData/deleteData,ctx.api, the engine door) on an object whoseafter*hooks bound from metadata are allonError: 'log'orasync: true, with at least one carrying acondition. The write door opened no unit of work, because it opened one only for a blockingonError: 'abort'hook. So a condition that could not be evaluated refused the caller while the row stayed stored, changed or deleted. Measured on RESTPOST /data/:object:500, and the row was stored.",
"note": "The old paragraph's first three sentences are kept verbatim; its last three moved into the new FROM bullet."
},
{
"file": ".changeset/22856-log-hook-unevaluable-condition.md",
"commit": "9eb65e6a44",
"OLD": "Now anafter*hook bound from metadata that carries aconditionopens the same unit of work, whatever itsonErrorandasync. A condition that cannot be evaluated rolls back the row and the hook chain's engine writes together. The caller is refused exactly as before.",
"NEW": "- TO: anafter*hook bound from metadata that carries aconditionopens the write door's unit, whatever itsonErrorandasync. A condition that cannot be evaluated rolls back the row and the hook chain's engine writes together. The caller is refused exactly as before."
},
{
"file": ".changeset/22856-log-hook-unevaluable-condition.md",
"commit": "9eb65e6a44",
"OLD": "(none)",
"NEW": "- Inside the new unit, the rules of any unit apply, so writes that were accepted are now refused on objects carrying such a hook:\n - A blockingonError: 'log'hook whose body writes a business row to an object on another datasource has that write refused withCrossDatasourceTransactionWriteError. The hook'sonError: 'log'logs the refusal and the row is not written, where it used to land. The caller's own write is still stored.\n - The same applies to the part of anasync: truehook's body that runs before the unit's callback settles. A write the body makes after that lands as before.\n - A MongoDB default store without a replica set now refuses writes to objects carrying such a hook, because this unit needs a replica set, astransaction()already does.\n - Fix, if one of these applies to you: drop the hook'scondition(and test it in the hook's body instead), or keep the objects on one datasource, or run MongoDB as a replica set."
},
{
"file": ".changeset/22856-log-hook-unevaluable-condition.md",
"commit": "9eb65e6a44",
"OLD": "Unchanged: a condition that evaluates behaves as before.falseskips the hook, and withtrueanonError: 'log'hook's thrown error is logged and the row is stored. Anasync: truehook still runs after the write and its late writes still land. A write already inside a unit joins it. No unit is opened withskipAutomations, off the default datasource, or on a default driver with no transactions. AnonError: 'log'orasync: truehook with noconditionstill opens no unit.",
"NEW": "- Unchanged: a condition's verdict is read as before.falseskips the hook, and withtrueanonError: 'log'hook's thrown error is logged and the caller's row is stored. A write already inside a unit joins it. No unit is opened withskipAutomations, off the default datasource, or on a default driver with no transactions. AnonError: 'log'orasync: truehook with noconditionstill opens no unit."
},
{
"file": ".changeset/22856-log-hook-unevaluable-condition.md",
"commit": "9eb65e6a44",
"OLD": "Cost: one transaction per write on objects that carry such a hook. This is the same per-write cost as the unit foronError: 'abort'hooks. In the showcase example this covers updates toshowcase_taskandshowcase_project, whoseasync: trueafterUpdatehooks carry conditions.",
"NEW": "- Cost: one transaction per write on objects that carry such a hook. This is the same per-write cost as the unit foronError: 'abort'hooks. In the showcase example this covers updates toshowcase_taskandshowcase_project, whoseasync: trueafterUpdatehooks carry conditions.",
"note": "Only the list marker changed; the text is identical."
}
],
"pr_body_sentences_this_round_makes_false": [
{
"FALSE": "Clause-②: no",
"REPLACE": "Clause-②: no (narrowing)",
"where": "body line 2"
},
{
"FALSE": "- A changeset,patchfor@objectstack/objectql, and a one-clause correction of the pending #22782 changeset (see "Deliberate correction").",
"REPLACE": "- A changeset,minorfor@objectstack/objectql, declared BREAKING (Clause-②: no (narrowing), ADR-0087 not-required, no migration). It names what the new unit refuses for alogorasynchook carrying acondition, and the fix. Plus two one-clause corrections of the pending #22782 changeset (see "Deliberate correction")."
},
{
"FALSE": "- Gates:dispatch-gates --commands --repo objectstack-ai/objectstackderived 97 commands on this diff. All 97 were run on head48b64091df. 96 exited 0.check-empty-changesetexited 1 by design: it refuses exactly the deliberately corrected.changeset/22782-objectql-after-hook-abort-rolls-back.md(see "Deliberate correction").--ranreconciliation: 97 derived, 97 run, 0 NOT-MEASURED, 0 UNRUN. Before patch round 1, the same 97 all exited 0 on9ba561108f. An earlier run of the 72 dispatch-time commands ona20ce84865hadcheck:dual-build-cjs-loadsatPREREQUISITE NOT MET(exit 3, packages not built). On the later heads it measured: 107 entry points in 66 packages load.",
"REPLACE": "- Gates:dispatch-gates --commands --repo objectstack-ai/objectstackderived 97 commands on this diff. All 97 were run on head9eb65e6a44. 96 exited 0.check-empty-changesetexited 1 by design: it refuses exactly the deliberately corrected.changeset/22782-objectql-after-hook-abort-rolls-back.md(see "Deliberate correction").--ranreconciliation: 97 derived, 97 run, 0 NOT-MEASURED, 0 UNRUN.check-adr-0087-registrationreads the 22856 note as[BREAKING+bang+clause-②-narrowing] not-required (no-migration-prescription), andcheck-changeset-no-majorreports nomajorbump. Earlier, the same 97 ran on9ba561108f(all exit 0) and on48b64091df(96 exit 0,check-empty-changesetexit 1 by design). An earlier run of the 72 dispatch-time commands ona20ce84865hadcheck:dual-build-cjs-loadsatPREREQUISITE NOT MET(exit 3, packages not built). On the later heads it measured: 107 entry points in 66 packages load."
},
{
"FALSE": "- objectql source is identical betweena20ce84865and the final head. Since then the branch gained only theorigin/mainmerge (3f694715ee, a spec migration text and its changeset), the docs row, and patch round 1's two changeset commits (4a3e842580,48b64091df).",
"REPLACE": "- objectql source is identical betweena20ce84865and the final head. Since then the branch gained only theorigin/mainmerge (3f694715ee, a spec migration text and its changeset), the docs row, and four changeset commits from the patch rounds (4a3e842580,48b64091df,b2eefbd375,9eb65e6a44)."
},
{
"FALSE": "## Deliberate correction\n\nThis PR changes.changeset/22782-objectql-after-hook-abort-rolls-back.md, a pending release note it did not add, in commit4a3e842580. That commit touches only that file (1 line). The note's "Unchanged" bullet said that no unit is opened for anonError: 'log'hook or anasync: truehook. After this PR the write door opens a unit for such a hook when it carries acondition, because a condition that cannot be evaluated aborts the write. So that clause now reads "No unit is opened for anonError: 'log'orasync: truehook that carries nocondition(#22856), …". Every other byte of the file is unchanged. Restoring the file from the base would ship a sentence that is false after this PR.check-empty-changesetrefuses this change by design (its DELIBERATE CORRECTION class), and that red is expected until the correction is confirmed. Commit48b64091dfdrops the last sentence of this PR's own changeset's "Unchanged" paragraph. That sentence pointed at the 22782 line, which now says it itself.",
"REPLACE": "## Deliberate correction\n\nThis PR changes.changeset/22782-objectql-after-hook-abort-rolls-back.md, a pending release note it did not add, in two commits. Each touches only that file and one line of it. After this PR the write door opens its unit for anonError: 'log'orasync: truehook that carries acondition, because a condition that cannot be evaluated aborts the write. Two clauses of that note said otherwise:\n\n-4a3e842580, the "Unchanged" bullet. It said that no unit is opened for anonError: 'log'hook or anasync: truehook. It now reads "No unit is opened for anonError: 'log'orasync: truehook that carries nocondition(#22856), …".\n-b2eefbd375, the remedy at the end of the "Inside the new unit, the rules of any unit apply" bullet. It said "mark the hookonError: 'log'orasync: true, or keep the objects on one datasource". It now reads "mark the hookonError: 'log'orasync: trueand give it nocondition(#22856), or keep the objects on one datasource".\n\nEvery other byte of the file is unchanged. Restoring the file from the base would ship two sentences that are false after this PR.check-empty-changesetrefuses this change by design (its DELIBERATE CORRECTION class), and that red is expected until the correction is confirmed. In this PR's own changeset, commit48b64091dfdrops the sentence that pointed at the old 22782 line, and9eb65e6a44declares the change a narrowing and names what the new unit refuses.",
"where": "the whole "## Deliberate correction" section"
},
{
"FALSE": "- The pending #22782 changeset's "Unchanged" line said no unit opens for anonError: 'log'hook or anasync: truehook. After this PR that holds only for such a hook with nocondition, so this PR corrects that line. See "Deliberate correction" below.",
"REPLACE": "- The pending #22782 changeset said no unit opens for anonError: 'log'hook or anasync: truehook, and gave "mark the hookonError: 'log'orasync: true" as the remedy for the rules inside the unit. After this PR both hold only for such a hook with nocondition, so this PR corrects both clauses (see "Deliberate correction" above).",
"note": "Also fixes the "below" nit from review 6115773915: the section sits above this bullet, and the live body still said "below"."
}
],
"pr_body_additions_suggested": [
{
"after": "Now the refused write leaves no row behind. The row and the hook chain's engine writes roll back together. The refusal itself is unchanged: the same500.",
"ADD": "This is declared a narrowing (Clause-②: no (narrowing),minor, BREAKING). Inside the new unit the rules of any unit apply. On an object carrying such a hook, a blockingonError: 'log'hook's write to an object on another datasource is refused withCrossDatasourceTransactionWriteErrorand logged where it used to land. So is such a write made by the part of anasync: truebody that runs before the unit's callback settles. A MongoDB default store without a replica set refuses writes to those objects. The changeset gives the fix: drop thecondition, keep the objects on one datasource, or run MongoDB as a replica set.",
"note": "Not false today, but "What the caller sees" omits the narrowing that E1 declares."
}
],
"tests": "No source or test changed this round (git diff 48b6409..9eb65e6 names only the two .changeset files), so the earlier rounds' test and ablation evidence stands. New this round, a measurement before writing F2. A throwaway vitest file, src/zz-measure-22856.test.ts, was untracked, deleted right after the run and never committed; git status was clean after. It ran 6/6 under os-verify-lock, exit 0. Readings: M1, blockingloghook writing to an object on thesidedatasource. Without a condition: insert resolved, caseRows 1, farRows 1, begun 0, no error log. With conditionrecord.name != \"\": insert resolved, caseRows 1, farRows 0, begun 1, error "[hook] handler failed (onError=log; suppressing) :: insert of 'far' inside transaction() resolves to datasource 'side', but the transaction is open on 'primary' and covers only that connection — refused. …". M2,async: truehook. Without a condition, at delay 0 and at 40 ms: farRows 1, begun 0. With the condition, delay 0: farRows 0, begun 1, the same refusal logged. With the condition, delay 40 ms: farRows 1, begun 1, no error. MongoDB replica-set refusal: NOT MEASURED, reason: no MongoDB server here; stated from the 22782 note and the driver's startSession/startTransaction. Union on head 9eb65e6 (git rev-parse --short HEAD): 97 derived, 97 run, 96 exit 0, 1 exit 1 by design (check-empty-changeset). dispatch-gates --ran: 97 derived, 97 run, 0 NOT-MEASURED, 0 UNRUN.",
"mcp_calls": "0 — none",
"api_writes": "1 relay stroke this round: POST /repos/objectstack-ai/objectstack/dispatches, executed as objectstack-fleet[bot] as POST /repos//issues/22856/comments (this os-dev-report, through scripts/pm/post-stamped.mjs). Not REST: git push x1 (2 commits, 48b6409 to 9eb65e6, fast-forward). No PR edit, no label, no ready flip.",
"open_questions": [],
"out_of_scope_findings": [],
"gates": [
"[union @ 9eb65e6] node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0",
"[union @ 9eb65e6] node scripts/check-adr-0087-registration.mjs --self-test :: exit 0",
"[union @ 9eb65e6] node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0",
"[union @ 9eb65e6] node scripts/check-changeset-no-major.mjs --self-test :: exit 0",
"[union @ 9eb65e6] node scripts/check-ci-filter-parity.mjs :: exit 0",
"[union @ 9eb65e6] node scripts/check-closing-keyword-parity.mjs :: exit 0",
"[union @ 9eb65e6] node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0",
"[union @ 9eb65e6] node scripts/check-comment-mask-adoption.mjs :: exit 0",
"[union @ 9eb65e6] node scripts/check-comment-mask-adoption.mjs --self-test :: exit 0",
"[union @ 9eb65e6] node scripts/check-comment-mask-corpus.mjs :: exit 0",
"[union @ 9eb65e6] node scripts/check-doc-frontmatter.mjs :: exit 0",
"[union @ 9eb65e6] node scripts/check-doc-frontmatter.mjs --self-test :: exit 0",
"[union @ 9eb65e6] node scripts/check-doc-route-spelling.mjs --advisory :: exit 0",
"[union @ 9eb65e6] node scripts/check-doc-route-spelling.mjs --self-test :: exit 0",
"[union @ 9eb65e6] node scripts/check-docs-section-name.mjs :: exit 0",
"[union @ 9eb65e6] node scripts/check-docs-section-name.mjs --self-test :: exit 0",
"[union @ 9eb65e6] node scripts/check-dts-emitted.mjs --self-test :: exit 0",
"[union @ 9eb65e6] node scripts/check-empty-changeset.mjs --base origin/main :: exit 1 (by design: refuses exactly .changeset/22782-objectql-after-hook-abort-rolls-back.md, "present on the merge base and CHANGED by this PR", the DELIBERATE CORRECTION class; it also prints "No empty-frontmatter changeset introduced by this diff (2 declaring changeset(s) added)")",
"[union @ 9eb65e6] node scripts/check-empty-changeset.mjs --self-test :: exit 0",
"[union @ 9eb65e6] node scripts/check-engine-split-ratio.mjs --days 90 :: exit 0",
"[union @ 9eb65e6] node scripts/check-engine-split-ratio.mjs --self-test :: exit 0",
"[union @ 9eb65e6] node scripts/check-issue-citations.mjs :: exit 0",
"[union @ 9eb65e6] node scripts/check-keyed-text-bounds.mjs :: exit 0",
"[union @ 9eb65e6] node scripts/check-keyed-text-bounds.mjs --self-test :: exit 0",
"[union @ 9eb65e6] node scripts/check-platform-object-tenancy-census.mjs :: exit 0",
"[union @ 9eb65e6] node scripts/check-platform-object-tenancy-census.mjs --self-test :: exit 0",
"[union @ 9eb65e6] node scripts/check-plugin-teardown-shape.mjs :: exit 0",
"[union @ 9eb65e6] node scripts/check-plugin-teardown-shape.mjs --self-test :: exit 0",
"[union @ 9eb65e6] node scripts/check-registry-log-declared.mjs :: exit 0",
"[union @ 9eb65e6] node scripts/check-registry-log-declared.mjs --self-test :: exit 0",
"[union @ 9eb65e6] node scripts/check-rest-log-spy-declared.mjs :: exit 0",
"[union @ 9eb65e6] node scripts/check-rest-log-spy-declared.mjs --self-test :: exit 0",
"[union @ 9eb65e6] node scripts/check-section-landing-index.mjs :: exit 0",
"[union @ 9eb65e6] node scripts/check-section-landing-index.mjs --self-test :: exit 0",
"[union @ 9eb65e6] node scripts/check-system-context-census.mjs :: exit 0",
"[union @ 9eb65e6] node scripts/check-system-context-census.mjs --self-test :: exit 0",
"[union @ 9eb65e6] node scripts/check-undeclared-dep-imports.mjs :: exit 0",
"[union @ 9eb65e6] node scripts/check-undeclared-dep-imports.mjs --self-test :: exit 0",
"[union @ 9eb65e6] node scripts/docs-audit/check-affected-docs.mjs :: exit 0",
"[union @ 9eb65e6] node scripts/docs-audit/check-drift-comment.mjs :: exit 0",
"[union @ 9eb65e6] node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0",
"[union @ 9eb65e6] node scripts/release-pending-publish.mjs --self-test :: exit 0",
"[union @ 9eb65e6] pnpm --filter @objectstack/lint run check:doc-formula-expressions :: exit 0",
"[union @ 9eb65e6] pnpm --filter @objectstack/lint run check:doc-security-posture :: exit 0",
"[union @ 9eb65e6] pnpm --filter @objectstack/spec run check:docs :: exit 0",
"[union @ 9eb65e6] pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0",
"[union @ 9eb65e6] pnpm --filter @objectstack/spec run check:empty-state :: exit 0",
"[union @ 9eb65e6] pnpm --filter @objectstack/spec run check:liveness :: exit 0",
"[union @ 9eb65e6] pnpm --filter @objectstack/spec run check:skill-examples :: exit 0",
"[union @ 9eb65e6] pnpm --filter @objectstack/spec run check:strictness-ledger :: exit 0",
"[union @ 9eb65e6] pnpm --filter @objectstack/spec run check:variant-docs :: exit 0",
"[union @ 9eb65e6] pnpm --filter @objectstack/spec run check:yaml-examples :: exit 0",
"[union @ 9eb65e6] pnpm check:changeset-gate-self-tests :: exit 0",
"[union @ 9eb65e6] pnpm check:corpus-claim-drift :: exit 0",
"[union @ 9eb65e6] pnpm check:cross-package-test-inputs :: exit 0",
"[union @ 9eb65e6] pnpm check:dispatcher-error-vocabulary :: exit 0",
"[union @ 9eb65e6] pnpm check:doc-anchors :: exit 0",
"[union @ 9eb65e6] pnpm check:doc-authoring :: exit 0",
"[union @ 9eb65e6] pnpm check:docs-audit-scope :: exit 0",
"[union @ 9eb65e6] pnpm check:docs-redirects :: exit 0",
"[union @ 9eb65e6] pnpm check:docs-single-h1 :: exit 0",
"[union @ 9eb65e6] pnpm check:docs-spec-enumerations :: exit 0",
"[union @ 9eb65e6] pnpm check:docs-transcript-drift :: exit 0",
"[union @ 9eb65e6] pnpm check:driver-memory-census :: exit 0",
"[union @ 9eb65e6] pnpm check:dts-closure :: exit 0",
"[union @ 9eb65e6] pnpm check:dual-build-cjs-loads :: exit 0",
"[union @ 9eb65e6] pnpm check:durability-log-level :: exit 0",
"[union @ 9eb65e6] pnpm check:engine-double-contract :: exit 0",
"[union @ 9eb65e6] pnpm check:error-status-conformance :: exit 0",
"[union @ 9eb65e6] pnpm check:gitlink-declared :: exit 0",
"[union @ 9eb65e6] pnpm check:issue-citations :: exit 0",
"[union @ 9eb65e6] pnpm check:lean-entry-closure :: exit 0",
"[union @ 9eb65e6] pnpm check:logger-receiver-detach :: exit 0",
"[union @ 9eb65e6] pnpm check:nul-bytes :: exit 0",
"[union @ 9eb65e6] pnpm check:objectql-double-limit :: exit 0",
"[union @ 9eb65e6] pnpm check:objectui-changeset :: exit 0",
"[union @ 9eb65e6] pnpm check:org-identifier :: exit 0",
"[union @ 9eb65e6] pnpm check:page-declaration-shape :: exit 0",
"[union @ 9eb65e6] pnpm check:pm-changeset-deadline-census :: exit 0",
"[union @ 9eb65e6] pnpm check:published-files :: exit 0",
"[union @ 9eb65e6] pnpm check:published-readme-links :: exit 0",
"[union @ 9eb65e6] pnpm check:query-options-erasure :: exit 0",
"[union @ 9eb65e6] pnpm check:react-page-adapter-contract :: exit 0",
"[union @ 9eb65e6] pnpm check:refd-timer-probe :: exit 0",
"[union @ 9eb65e6] pnpm check:role-word :: exit 0",
"[union @ 9eb65e6] pnpm check:skill-identifier-liveness :: exit 0",
"[union @ 9eb65e6] pnpm check:slot-lookup :: exit 0",
"[union @ 9eb65e6] pnpm check:sourcemap-no-sources-content :: exit 0",
"[union @ 9eb65e6] pnpm check:stack-collection-maps :: exit 0",
"[union @ 9eb65e6] pnpm check:swallow-census-controls :: exit 0",
"[union @ 9eb65e6] pnpm check:test-source-alias :: exit 0",
"[union @ 9eb65e6] pnpm check:tier-file-adoption :: exit 0",
"[union @ 9eb65e6] pnpm check:type-check-coverage :: exit 0",
"[union @ 9eb65e6] pnpm check:type-check-debt :: exit 0",
"[union @ 9eb65e6] pnpm check:vendor-version-stamps :: exit 0",
"[union @ 9eb65e6] pnpm check:watch-hint-literal :: exit 0",
"[union @ 9eb65e6] pnpm check:where-matcher :: exit 0",
"[pre-union @ 9eb65e6] node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0 — verdict: "check-adr-0087-registration: 1 declared-breaking changeset(s), each carrying an ADR-0087 disposition." with ".changeset/22856-log-hook-unevaluable-condition.md [BREAKING+bang+clause-②-narrowing] not-required (no-migration-prescription)"",
"[pre-union @ 9eb65e6] node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0 — verdict: "This diff introduces nomajorbump."",
"[pre-union @ 9eb65e6] pnpm check:changeset-gate-self-tests :: exit 0 — verdicts: "check-empty-changeset --self-test: 170 assertions over real temp git repos (real scan() path)"; "check-adr-0087-registration --self-test: 463 assertions over real temp git repos (real scan()/assertInputs() path)"; "check-changeset-no-major --self-test: 401 assertions (…)"",
"[pre-union @ 9eb65e6] node scripts/check-empty-changeset.mjs --base origin/main :: exit 1 (same refusal, the same single file)",
"[build @ b2eefbd tree] pnpm build --concurrency=2 (under os-verify-lock) :: exit 0 (73 tasks, all 73 from the shared turbo cache)",
"[measurement, throwaway, untracked, deleted] pnpm --filter @objectstack/objectql exec vitest run --maxWorkers=2 src/zz-measure-22856.test.ts :: exit 0 (6/6; the readings are in tests)"
],
"line_budget": "338 (+278 / -60; 7 files; generated 0). This round: +16 / -8 over 2 files.",
"deviations": [
"The 22856 remedy adds "(and test it in the hook's body instead)" to the seat's three-item list (drop the condition, one datasource, replica set). Dropping the key alone would make the hook fire unconditionally, which an upgrading reader should not do by accident.",
"The 22856 title follows the lane form (fix(objectql)!: … (#22856), as the #22519 and #22823 notes do) and keeps the caller-facing sentence from round 0.",
"Not changed, by the seat's "keep every other byte identical": the 22782 note's "Cost: one transaction per write on the objects above only". "The objects above" are that note's FROM population, and the 22856 note states the cost for its own population. Flagged so the next same-head record can read it; if the seat reads "only" as false next to the 22856 note, the minimal change is to drop the word "only".",
"The live PR body still says "See Deliberate correction below" (with the section name in quotes), although review 6115773915 says the seat fixed that nit. The REPLACE above carries the fix.",
"origin/main moved to eb20c33 and was not merged: GitHub reports mergeable_stateunstable(mergeable, red check), not a conflict.",
"A throwaway measurement test was created in packages/objectql/src, run once and deleted. It was never staged or committed, and git status was clean afterwards.",
"Worktree cleanup: done after this report is posted. node_modules removed, then git worktree remove without --force, after git status was clean and local HEAD equalled the remote head 9eb65e6."
],
"files_changed": [
".changeset/22782-objectql-after-hook-abort-rolls-back.md",
".changeset/22856-log-hook-unevaluable-condition.md",
"content/docs/automation/hooks.mdx",
"packages/objectql/src/engine-after-hook-abort-unit.test.ts",
"packages/objectql/src/engine.ts",
"packages/objectql/src/hook-wrappers.ts",
"packages/qa/dogfood/test/hook-after-condition-rollback.dogfood.test.ts"
]
}
Generated by Claude Code
objectstack-fleet commented
on Oct 12, 2026 ContributorAuthorMore actions✅ ACCEPT: PR #22906 at
9eb65e6a44domain:engineseat 2 (#20966) ·os-steve·session_01ADzJtzYTLUfgrRZHxkagkX· 2026-10-12T01:40Z. Claim 6114412794, addendum 6115328926, ruling and amendment 6115782204; reports 6115299755, 6115564493 (round 1) and 6116082195 (round 2). Checked on GitHub, not taken from the reports. Classes, positions and functions only.- Form: draft to
main; titlefix(objectql)!: …; line 1Fixes #22856is the body's only closing keyword; line 2Clause-②: no (narrowing). Assigneeos-steve. - Scope: 7 files, +278 / −60, none generated, none governed (
check-governed-merges --pr 22906: not governed). Nothing undercontent/docs/releases/. Cross-lane, declared: one test-only dogfood file (domain:cli, 6115332990 on [PM seat] domain:cli · seat 2 — 🟢 os-steve · session_01VoSxBQujKLZKPwK2u5ehQ6 #22648) and one table row incontent/docs/automation/hooks.mdx(domain:devx, 6115337179 on [PM seat] domain:devx @ objectstack — 🟢 os-warren · session_012yK1ddAdAqfzwigRHZq3r2 #6023). - Fix, read in the diff:
opensAfterAbortUnitalso counts a metadata-boundafter*hook that carries acondition, whatever itsonErrorandasync. It reads the condition throughdeclaredHookCondition, the same functionwrapDeclarativeHooknow installs its gate from, so the door and the gate cannot disagree. The function is not on either package entry. The first line (closed-handle reading), theskipAutomationsexit and the default-driver tail are unchanged. - Changesets, read sentence by sentence against the diff:
@objectstack/objectqlminor,fix(objectql)!:,Clause-②: no (narrowing), ADR-0087not-required (no-migration-prescription)(the gate's notice reads it so), BREAKING; FROM and TO match the predicate. "Inside the new unit" names the three populations now refused, with the fix: cross-datasource business writes from aloghook, the pre-settle part of anasyncbody, and a MongoDB store without a replica set (the last NOT MEASURED, stated from the merged family note). "Unchanged" states only what is unchanged. - Evidence: unit pins (log insert, update and delete; async; three controls) and five REST
createDatareal-door cases. Before the fix the defect pins are red and every control green; ablation turns the same pins red with the row stored. objectql 7823 tests pass; 97 gate families,--ran97/97, 96 exit 0 andcheck-empty-changesetexit 1 by design. Round 2 measured the two cross-datasource populations with a throwaway probe before writing them. - CI on
9eb65e6a44: 42 runs: 36 success, 4 skipped, 2 failure (Check Changeset, by design below). All seven required contexts aresuccess, all sixTest Coreshards included. - Contract review: FAIL on
48b64091df(6115773915: F1, F2), then PASS on this head (6116233234,CONTRACT_REVIEW_TIER, isolated reviewer, read-only). The PASS confirms F1 and F2 fixed and the DELIBERATE CORRECTION clause by clause. - DELIBERATE CORRECTION:
Check Changesetis red for one foreign-changeset refusal on.changeset/22782-objectql-after-hook-abort-rolls-back.md, from commits4a3e842580andb2eefbd375, one line each. All three conditions hold, so it lands red by design: the gate's source names this class red by design; the job runs onpull_requestonly, is not inmerge_groupand is not a required context; and note 6115345828 on this PR names the gate and the reason. - Deviations accepted:
- the docs row (a published statement this change made false);
- the ablation restore checked with the preflight's present mode, which a DELETE ablation needs;
- the remedy's "and test it in the hook's body instead" (dropping the key alone would fire the hook on every write).
- Out-of-scope findings:
- Load-time refusal of a never-bindable condition: dropped. The measured population is 0, and triage ruled it is filed only on measured configurations.
ast-only envelope: Acceptance notes. It is pre-existing, and the shared reading keeps the door in agreement with the gate.- The 22782 "Cost … only" wording: no change. The PASS record judges it true as that change's own cost.
- The
hook.zod.tsJSDoc restatements (E2): handed todomain:spec(6115789463 on [PM seat] domain:spec · seat 3 — 🟢 zhuangjianguo · session_01KNKBCRDJCu5tGy3TEbvtrF #18883).
Landing: ready and auto-merge through the queue in this act.
- Form: draft to
objectstack-fleet commented
on Oct 12, 2026 ContributorAuthorMore actionsLanded: PR #22906 →
3caae97942·domain:engineseat 2 ·session_01ADzJtzYTLUfgrRZHxkagkX· 2026-10-12T02:31Z- Merge: the queue merged PR fix(objectql)!: an after* hook carrying a condition rolls its write back when the condition cannot be evaluated #22906 at 2026-10-12T02:29Z as
3caae97942c0e322cd87bf8480727cd019f9f6a9(one parent). ACCEPT 6116240574; contract review PASS 6116233234 (after FAIL 6115773915);Check Changesetlanded red by design (note 6115345828). - On
mainat that commit: all seven paths are byte-identical to the accepted head9eb65e6a44. PR fix(objectql)!: a detail write asks the caller’s read door for its master-detail header — an unreadable header answers as a nonexistent one (#22519) #22891 merged right after and also touchesengine.tsin a disjoint region; this PR's hunk is unchanged there. - Card: closed
completedby the PR'sFixes #22856;pm:dispatchedcomes off in this act. - Release text: the pending
.changeset/22782-…note's two corrected clauses and this PR's narrowing note ship together. - Carried: the
hook.zod.tsJSDoc restatements, withdomain:spec(6115789463 on [PM seat] domain:spec · seat 3 — 🟢 zhuangjianguo · session_01KNKBCRDJCu5tGy3TEbvtrF #18883).
- Merge: the queue merged PR fix(objectql)!: an after* hook carrying a condition rolls its write back when the condition cannot be evaluated #22906 at 2026-10-12T02:29Z as
Filing gate: ① a product defect, class (a). Raised by #22782's dev (os-dev-report 6109907864, out-of-scope finding 2) and filed by
domain:engineseat 1 (#6367) ·session_01JfJfBUC3cQ6hhgm9MQK76T. ⛔ Not a claim. ⛔ Not graded here: triage grades and routes. #22782 (PR #22819) makes anonError: 'abort'hook roll the write back; aloghook is outside that contract.Reach: measured on a published door, REST
POST /data/:object(createData), on the verify stack.Reader who acts: triage; the fix point is in
domain:engine(packages/objectql, the write door andhook-wrappers.ts).The gap
condition求不出值时:全局 fail loud —— 抛错并中断该次操作(方案 B 已拍板;Blocked-by #4770) #4775's stated intent (packages/objectql/src/hook-wrappers.ts, near:100–:124) is that a hookconditionwhich cannot be evaluated aborts the operation, "a typo in an afterUpdate audit condition fails the write it was only watching".HookConditionErroris raised outsideonErroron purpose (near:127–:133), soonError: 'log'never softens it.after*event withonError: 'log', the door opens no unit of work. So the caller is refused while the row is stored. That is the outcome objectql: anafterInserthook withonError: 'abort'(the default) that throws rejects the write, but the row stays stored —HookSchema.onErrorsays abort rolls the transaction back, and a plain write opens none #22782 removed foraborthooks.afterInserthook withonError: 'log'and a condition naming a binding unbound on an insert givescreateData→500 INTERNAL_ERROR, with the row STORED (1). The control is the same condition on a default (abort) hook, which answers500with the row ABSENT (0), because the door's unit rolls it back.500, not a4xxnaming the hook and its key.Not decided here
Which side changes is the lane's contract call. Options include:
after*hook that carries acondition;after*;Duplicate check
objectstack buildskips the runtime module ("all N callables are body-only") on a stack that carries a callable at the top level and in apackages[i].manifest, so afunctionsentry ships with no implementation, at exit 0 #22853. A local grep over titles and bodies.HookConditionError: 0.condition求不出值时:全局 fail loud —— 抛错并中断该次操作(方案 B 已拍板;Blocked-by #4770) #4775 together with "condition": 1, closed (driver-memory'scheckConditionroutes Array AND Date comparands intovalue == condition— a reference comparison that is never true, against a spec docblock promising array-equality semantics #16810).evaluateConditionstill throws a rawTypeError: exprStr.trim is not a functionon a non-string envelopesource— registration refuses the shape, evaluation faults unattributed #16038).hook300, so the channel answers.Generated by Claude Code