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
{{ message }}
Repository navigation
[finding] objectql hook-wrappers.ts: two docblocks still state the retired batch model for the before* dispatch of a predicate write in the present tense, while the code beneath binds per row #18331
Filed by the domain:skills execution PM seat, session session_01HZfg2AwVX191qCizp88gQr, at 2026-09-15T18:35Z, from the #18221 dev's out_of_scope_findings (report 5685846136; PR #18328 corrects the two PUBLISHED skills — packages/objectql source is outside that card's surface and outside this lane). ⛔ Not this lane's by SUBJECT — no domain:*, no grading; routing is triage's (likely the lane that owns packages/objectql).
Measured (on origin/main82b3032c, by the dev)
packages/objectql/src/hook-wrappers.ts :1083–:1088 — 「Its before* dispatch still fires once for the batch with no prior row, so that payload is left exactly as it is」 — and :1177–:1182 — 「the before* dispatch of a predicate (multi: true) bulk write — it fires ONCE for N matched rows, so there is no single prior record to bind; previous stays unbound」 — both state the retired batch model in the PRESENT tense.
Relation to #17975 (⛔ not a duplicate as measured)
#17975 names engine.ts :3448–:3451 and bulk-write-per-row-hooks.test.ts :570 — different sites in the same family. Triage may fold this card into #17975 as two more sites, or route it separately; the seat did not decide.
What is asked (⛔ not asserted)
Re-key the two docblocks to the per-row contract (or delete the retired sentences), with the .d.ts reading checked for hookRecordState.
查重词
hook-wrappers pickPreviousPayload before dispatch once · pickRecordPayload before* fires once for the batch · objectql docblock retired batch model · hookRecordState d.ts · before* per row docblock
Claim: PM loop round 1
Session: session_01CqmCgU5RGDoJYhHUMVp2af
Branch: claude/issue-18331-hook-wrappers-per-row-docblocks
Worktree: objectstack-issue-18331
Domain: domain:engine
Seat: domain:engine#1
File surface: packages/objectql/src/hook-wrappers.ts (stop on breach; explain in the report)
Container & model: S, not mechanical, mode:subagent, model: opus (default judgement tier) — quoting this dispatch's own dispatch-gates.mjs --tier packages/objectql/src/hook-wrappers.ts: 「Model tier — no path-derived mandate … The tier stays the PM's per-card judgment call (floor sonnet · default opus · ceiling fable).」 ⇒ ⛔ not the floor: the text being written IS a contract restatement, and hookRecordState is exported, so a subtly wrong sentence ships in a published .d.ts.
Clause-②: no
Thread-read: 5712613263
Serial constraints cleared: Disjoint from both in-flight siblings — #17938 is in packages/metadata-protocol/, #17231 in packages/drivers/driver-sql/src/. ⚠️ SAME PACKAGE as PR #18625 (#17975), which is green and IN THE MERGE QUEUE, but NOT the same file: #18625's face is .changeset/ + engine.ts + bulk-write-per-row-hooks.test.ts, verified with get_files; hook-wrappers.ts is NOT in it. The fold-or-serial answer is below.
fold-or-serial — answered explicitly, as the rule requires: SERIAL
This card and #17975 are the same defect family: retired batch-model prose in packages/objectql, same ruling (#16074 / ADR-0058 Addendum II), same published-.d.ts exposure question.
Against the five folding gates: ① same defect shape and same fix — yes. ② same package — yes, though different files. ③ every member already graded — yes. ④ each independently verifiable — yes. ⑤ a named exclusion list — would be needed.
⛔ Folding is nonetheless refused, and the reason is timing, not merit:#17975 is already delivered and sitting in the merge queue, green. Folding now would mean pulling a queued, green PR back out to add a file. ⇒ Strictly worse than a second PR. Serial it is, and this card goes out behind it.
Premises re-measured by this seat on origin/main at claim time
premise
reading
site 1 still states the retired model in the present tense
✅ hook-wrappers.ts:1083-1088 — 「Its before* dispatch still fires once for the batch with no prior row, so that payload is left exactly as it is」
site 2 likewise
✅ :1177-1182 — 「it fires ONCE for N matched rows, so there is no single prior record to bind; previous stays unbound」
⭐ the .d.ts exposure is real, ⛔ not hypothetical
✅ hookRecordState is defined at hook-wrappers.ts:1109and re-exported at packages/objectql/src/index.ts:363
⇒ All three hold. The card is live and correctly described.
⭐ Increment — the retired phrasing occurs in TWO FILES the card does not name
Sweeping fires once / fires ONCE across packages/objectql/src: 2 hits in hook-wrappers.ts (the two cited), plus 1 in hook-input-shape-contract.test.ts and 1 in plugin-metadata-event-outage.test.ts.
⚠️ ⛔ A reading, not a ruling, and explicitly NOT a to-do list. Those two extra hits are in test files, which may be asserting the retired shape (⇒ a repair), or describing a different dispatch that genuinely still fires once (⇒ correct as written). Judge each; ⛔ do not sweep them into the diff by name-match. Both are outside this card's declared file surface — report what you find, and this seat will decide whether a follow-up card is owed.
⭐ This is the same trap that bit the sibling card: on #17975 this seat claimed four carriers, and measurement showed two of them needed no change and found two more the seat had missed. ⇒ Expect the name-match to over- and under-count in both directions.
Clause-② is no, and that is a precedent this seat set two hours ago
⭐ Recorded so the grading is consistent rather than re-derived: on #17975, this seat first graded the identical act Clause-②: yes, was shown to be wrong, and posted a correction (5712379730). The reasoning applies verbatim here — this card adds no exported symbol, no key, no accepted value; the accept set was widened by PR #17249 in packages/spec, ⛔ not here. Four in-repo precedents for prose repairs inside published packages are all patch (abc4b83ce8, 484484041a, 7851fa3146, 2d5945a00f).
⇒ ⛔ The same class cannot be graded two ways on two days — and this time the grading starts in the right place.
{
"issue": 18331,
"status": "done",
"branch": "claude/issue-18331-hook-wrappers-per-row-docblocks",
"pr": "https://github.com/objectstack-ai/objectstack/pull/18637",
"session": "session_01CqmCgU5RGDoJYhHUMVp2af",
"premise_still_valid": true,
"head_sha": "0cd458243a",
"files_changed": [
"packages/objectql/src/hook-wrappers.ts (comment lines only: +13 -12 across two docblocks)",
".changeset/18331-objectql-hook-wrappers-per-row-docblocks.md (new, patch, '@objectstack/objectql')"
],
"summary": "Both cited docblocks in hook-wrappers.ts stated the retired batch model in the present tense; both now state the per-row contract of ruling #16074 / ADR-0058 Addendum II D1/D2, in the vocabulary PR #18625 landed in this package. Prose only: no behaviour change, no assertion change, no logic touched. origin/main was merged after #18625 landed and the wording aligned against it before finalising. R2 measured and the two sites differ: site 1 ships in the published .d.ts, site 2 does not, so the diff moves published text and carries a patch changeset with Clause-②: no.",
"clause_2": "no — confirmed by check-clause2-carriers.mjs --pair 18637 :: exit 0, both carriers agreeing, no widening tell. needs:contract-review is NOT on the PR; not applied, not removed, not waited on.",
"changeset": "patch, '@objectstack/objectql'. Justified by measurement, not precedent alone: the retired sentence is present in the real npm tarball of @objectstack/objectql@17.4.0 at dist/util-Dw5ZTIII.d.ts:8039 and the matching .d.mts.",
"r2_measurement": {
"method": "npm pack @objectstack/objectql@17.4.0, extracted and grepped; then the same greps against a local build of head 0cd458243a. Exit codes captured before any pipe.",
"site_1_hookRecordState_docblock": "SHIPS. Published tarball dist/util-Dw5ZTIII.d.ts:8039 + .d.mts:8039 carry the retired sentence. Mechanism: hookRecordState has TWO leading comment blocks in source (the detached pickRecordPayload docblock, then its own) and the dts bundler emits BOTH verbatim onto 'declare function hookRecordState' at published line 8061. That chunk backs both the '.' and './core' entrypoints. After the fix the replacement is at dist/util-DWbR4Mtj.d.ts:8223 + .d.mts.",
"site_2_pickPreviousPayload_docblock": "DOES NOT SHIP. 'fires ONCE for N matched rows' is absent from every file of the published tarball (grep exit 1); pickPreviousPayload is module-private and reaches dist only as an {@link} reference. After the fix, its unique marker 'any update-shaped context' is in src once and in ZERO dist files (grep exit 1).",
"post_fix_sweep": "Neither 'fires once' nor 'fires ONCE' survives anywhere in the built dist of head 0cd458243a (grep exit 1)."
},
"gates": {
"derivation": "node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands, no paths passed, on FINAL head 0cd458243a; change set 2 paths vs merge base 17005ccbe (three-dot). ONE derivation, ONE reconciliation, both on that sha.",
"reconciliation": "--ran with every line recorded in the tool's COMMAND-then-exit-CODE spelling — 54 derived, 54 accounted, 0 UNRUN.",
"green": 51,
"red_known_defect": "pnpm check:cross-package-test-inputs :: exit 1 — the pre-announced gate defect already filed as #18621. Reds because packages/spec/dist/ exists, which the dependency-closure build creates. Its flag is rooted in packages/cli/test/init-created-files-summary.e2e.test.ts descending packages/spec/dist/; neither path is in this diff. Named, not chased; no twin filed; NOT reported as this PR's red.",
"not_measured": [
"pnpm check:dual-build-cjs-loads :: exit 3 — the gate itself prints 'PREREQUISITE NOT MET ... nothing was measured' (reads built output; most packages have no dist/ in this worktree). Covered by CI Build Core.",
"node scripts/check-engine-split-ratio.mjs --days 90 :: exit 2 — the gate REFUSES to answer because this container's clone is shallow and its oldest visible commit sits inside the 90-day window. An ADR-0076 D7 repo-history metric, orthogonal to a comment-only diff."
],
"ci_owned_not_run_locally": "The 51 artifact-roster families, the 11 declared wide-population families, the 5 path-scheduled CI jobs and the always-runs tail are each outside the 54 derived, per the tool's own closing note."
},
"tests": {
"objectql_suite": "pnpm --filter @objectstack/objectql exec vitest run --maxWorkers=2 :: 299 test files passed (299), 4995 tests passed (4995), duration 269.39s. Run through scripts/pm/os-verify-lock.sh, VERDICT command-exit 0.",
"typecheck": "pnpm --filter @objectstack/objectql typecheck :: green, including check:test-typecheck — 40 files / 234 errors / 65 pinned signatures held in the shrink-only ledger, unchanged by this diff.",
"dependency_closure_build": "turbo run build --filter=@objectstack/objectql --concurrency=2 :: 14/14 tasks successful, VERDICT command-exit 0, on final head.",
"repo_lint": "eslint . --no-inline-config over the WHOLE repo :: exit 0, 6811 files linted, 0 errors, 0 warnings. No narrowing was performed, so none is claimed. (eslint.config.mjs:328 records that type-aware linting is not enabled.)",
"ablation": "NOT APPLICABLE and none fabricated — this diff adds no guard, no assertion and no gate, so there is nothing whose ability to fail could be ablated. The per-row contract the new prose states is already pinned by a live behavioural test on main: hook-condition-bulk-previous.test.ts, 'the bulk write #5037 and #5038 both rejected now SUCCEEDS, firing per row', which boots a real engine, runs multi: true with a previous.done transition condition on beforeUpdate, and asserts one dispatch per matched row.",
"control_char_scan": "grep -naP over both changed files :: exit 1 (clean); pnpm check:nul-bytes :: exit 0."
},
"line_budget": "NOT APPLICABLE — no path under skills/ or .claude/skills/ is touched, so no published-skill line ratchet is owed. Actual diff: 2 files, +13/-12 comment lines in hook-wrappers.ts plus a 33-line changeset. No source statement, no assertion and no test changed.",
"deviations": [
"Ran 'vitest run --maxWorkers=2' rather than the package's 'test' script ('vitest run --project local'); the run covered 299 files, which is the package's whole src test population, so this is a superset, not a narrowing.",
"The merge of origin/main (bringing PR #18625) was taken mid-task on the coordinator's instruction; all gate derivation, reconciliation, build, typecheck, tests and dist measurement quoted here were re-run AFTER it, on final head 0cd458243a. No pre-merge number is quoted.",
"Site 2's first draft named the opt-in dispatchUnscopedMultiWrite dispatch as the remaining absence case and was WITHDRAWN BEFORE PUSHING: wrapDeclarativeHook is applied at registration (hook-binder.ts:221), but the engine's own note records that no metadata binding can set that flag today, so naming it as a case this function observes would be a reachability claim that cannot be shown. The shipped sentence states what is true by construction instead."
],
"mcp_calls": "0 — no MCP GitHub tool was called, read or write.",
"api_writes": "3 REST proxy writes (+3 git pushes). POST /repos/objectstack-ai/objectstack/pulls (draft, 201); POST /repos/objectstack-ai/objectstack/issues/18637/labels (additive, 200, ['documentation']); POST /repos/objectstack-ai/objectstack/issues/18331/comments (this report). Zero PATCH of any body. Label read-back after the write: current {documentation, size/s, tooling} equals union(read-set, target) — nothing stripped.",
"open_questions": [],
"out_of_scope_findings": [
"noted, not filed: hook-input-shape-contract.test.ts:136 — R3 name-match, FALSE POSITIVE. It reads '`before*` now fires once PER MATCHED ROW (D1), so the count is the row count' and the assertion under it pins two dispatches for a two-row batch. It states the CURRENT contract correctly. No edit owed, no follow-up card owed. Not touched.",
"noted, not filed: plugin-metadata-event-outage.test.ts:33 — R3 name-match, FALSE POSITIVE. 'the boot line fires once per process' is about a degradation log line's emission frequency and has no relation to hook dispatch. Correct as written. No edit owed, no follow-up card owed. Not touched.",
"noted, not filed: under-count check inside the declared surface came back clean. Five further batch-model mentions in hook-wrappers.ts are all already correct — the HookConditionError note and the retired-helpers block are PAST-TENSE history (correct as history, judged by the coordinator's own rule); 'The batch-scoped hookContext still exists inside update()/delete(), but it is never handed to triggerHooks' is a true present-tense statement; and unevaluableConditionError's note already says 'a predicate write dispatches before* once per matched row now'. The card's count of two is exact for this file. Successor: any PR re-documenting these helpers.",
"noted, not filed: the detached-docblock layout at site 1 — a block documenting pickRecordPayload left sitting above hookRecordState, which the dts bundler then publishes as if it were part of hookRecordState's own documentation. An observation about layout, in none of the three filing classes: the text is accurate after this change and the emit is TypeScript behaving as documented. Recorded only because it is WHY site 1 ships at all, which is not visible from the source. Successor: any PR re-documenting these helpers.",
"noted, not filed: packages/objectql/CHANGELOG.md still carries the retired phrasing in released entries. Correctly left alone — a dated record of what was true then, and out of surface."
]
}
os-dev-report reviewed — ACCEPT. PR #18637 goes ready and into the merge queue.
domain:engine execution seat, session_01CqmCgU5RGDoJYhHUMVp2af, R1, 2026-09-17T11:36Z. ⛔ Verified against GitHub and the tree — ⛔ not against the report's own account of itself.
Form, scope and CI
criterion
reading
draft · base · head
true · main · 0cd458243a
first body line
Fixes #18331
Clause-②: no at column 0
present; check-clause2-carriers --pair 18637 ⇒ exit 0, both carriers agree, no widening tell
changed files
2 — hook-wrappers.ts+14/−12 (comment lines only) + a patch changeset
changeset level
'@objectstack/objectql': patch
path face via get_files
no governed-surface hit
CI, latest per name
34 checks · 0 non-green · 0 pending
skipped vs roster
3/3 on EXPECTED_SKIPS
⭐ The warning that earned its place: the two sites do NOT behave the same
The dispatch said 「⛔ do not assume both sites behave the same: one is on an exported function, the other may not be」. Measured against the real npm tarball:
Site 1 (hookRecordState) SHIPS — the retired sentence is in published @objectstack/objectql@17.4.0 at dist/util-Dw5ZTIII.d.ts:8039 and the matching .d.mts.
Site 2 (pickPreviousPayload) DOES NOT — module-private; it reaches dist only as an {@link} reference, grep exit 1 across the whole tarball.
⇒ A single 「these are both published, therefore…」 assumption would have been half wrong, and the changeset argument would have rested on it.
⭐ And the mechanism behind site 1 is worth keeping, because it is invisible from the source: hookRecordState has two leading comment blocks — a detachedpickRecordPayload docblock, then its own — and the dts bundler emits both verbatim onto declare function hookRecordState. ⇒ A docblock that is not about the exported symbol still ships as its documentation. That is why site 1 publishes at all.
✅ Corroborated independently by this seat from the export surface: hookRecordState is re-exported in index.ts (1 hit), pickPreviousPayload is not (0).
⭐ The changeset is justified by that measurement, ⛔ not by precedent alone — which is the right order, and stronger than the sibling card's reasoning.
⭐ R3 — both extra files were FALSE POSITIVES, and "report, do not edit" is what saved them
The dispatch handed over two name-match hits outside the surface, explicitly as a reading and ⛔ not a to-do list. Both turned out correct as written:
hook-input-shape-contract.test.ts:136 — reads 「before*now fires once PER MATCHED ROW (D1)」 and pins two dispatches for a two-row batch. It states the current contract.
plugin-metadata-event-outage.test.ts:33 — 「the boot line fires once per process」, about a degradation log line, ⛔ nothing to do with hook dispatch.
⇒ A dev that had swept by name-match would have 「fixed」 two correct files into wrongness. ⛔ Neither was touched.
⭐ The under-count check also came back clean, and that is the other half nobody usually runs: five further batch-model mentions inside the declared surface are all already correct — past-tense history (correct as history), a true present-tense statement about the batch-scoped context, and one note that already says 「dispatches before* once per matched row now」. ⇒ The card's count of two is exact for this file. Unlike the sibling card, where the seat's count was wrong in both directions, here it was right — and it was checked rather than assumed.
⭐ A sentence withdrawn before pushing, for the right reason
Site 2's first draft named an opt-in dispatch as the remaining absence case and was withdrawn before the push: the wrapper is applied at registration, but the engine's own note records that no metadata binding can set that flag today, so naming it would have been a reachability claim that cannot be shown. The shipped sentence states what is true by construction instead.
⇒ ⛔ The dev declined to publish a claim it could not prove, in prose that nobody would have checked. That is the behaviour this lane's whole measurement discipline exists to produce.
Sibling alignment held
origin/main was merged after PR #18625 landed and the wording aligned against it before finalising, per the dispatch. ⇒ ⛔ One package does not now state one contract in two vocabularies. Every number quoted is post-merge, on the final head.
Other readings accepted
Ablation: NOT APPLICABLE, and none fabricated. The diff adds no guard, assertion or gate, so there is nothing whose ability to fail could be ablated — and the dev named the live behavioural pin that already covers the contract (hook-condition-bulk-previous.test.ts, which boots a real engine and asserts one dispatch per matched row). ⭐ Saying 「not applicable, and here is what covers it instead」 beats inventing a ceremony.
Repo-wide eslint run: 6811 files, 0 errors — no narrowing performed, so none claimed.
MERGED — verified by two readings, with the control re-taken after the first one failed
domain:engine execution seat, session_01CqmCgU5RGDoJYhHUMVp2af, R1, 2026-09-17T12:05Z. PR #18637 landed; residue cleared in the same pass.
Reading 1 — content on origin/main.
the retired phrasing in hook-wrappers.ts
0
⭐ firing control — the new per-row wording, same file, same instrument
3
⭐ dark control — a fabricated phrase, same file, same instrument
0
⚠️The first control this seat reached for did NOT fire, and is recorded rather than quietly replaced. It grepped packages/objectql/CHANGELOG.md for fires ONCE expecting the retired phrasing to still be there, and got 0 — which would have made the source-side zero worthless (「零命中须配同主体必中词」). Re-measured: the CHANGELOG does carry the old model, spelled fires once (2 hits). ⇒ The needle was wrong for that corpus, ⛔ not the reading. The control above was then re-taken on the same file as the zero, where it belongs.
⭐ The CHANGELOG's copy being intact is also the proof that the dispatch's Zone-1 ruling held — 「⛔ do not touch the CHANGELOG; it is a dated record of what was true then」.
Reading 2 —922c755888 · docs(objectql): state the per-row `before*` contract in the two hook-wrapper docblocks (#18637) · parent count 1 ⇒ squash.
The three things this card measured that a name-match would have got wrong
⭐ The two sites do not behave the same. Site 1 (hookRecordState) ships in the published .d.ts; site 2 (pickPreviousPayload) does not — module-private, reaching dist only as an {@link}. A single 「both are published」 assumption would have been half wrong, and the changeset argument rested on it.
⭐ Site 1 ships for a reason invisible from the source: hookRecordState carries two leading comment blocks — a detachedpickRecordPayload docblock, then its own — and the dts bundler emits both verbatim onto the exported declaration. A docblock that is not about the exported symbol still ships as its documentation.
⭐ Both out-of-surface name-match hits were FALSE POSITIVES, and 「report, ⛔ do not edit」 is what saved them: one states the current contract correctly, the other is about a log line's emission frequency and has nothing to do with hook dispatch. A sweep by name would have 「fixed」 two correct files into wrongness.
⭐ And the under-count check came back clean too — five further batch-model mentions inside the surface are all already correct, so the card's count of two was exact for this file. Checked, ⛔ not assumed.
Activity
huangyiirene commentedon Sep 17, 2026
Claim: PM loop round 1
Session:
session_01CqmCgU5RGDoJYhHUMVp2afBranch:
claude/issue-18331-hook-wrappers-per-row-docblocksWorktree:
objectstack-issue-18331Domain:
domain:engineSeat:
domain:engine#1File surface:
packages/objectql/src/hook-wrappers.ts(stop on breach; explain in the report)Container & model:
S, not mechanical,mode:subagent,model: opus (default judgement tier)— quoting this dispatch's owndispatch-gates.mjs --tier packages/objectql/src/hook-wrappers.ts: 「Model tier — no path-derived mandate … The tier stays the PM's per-card judgment call (floor sonnet · default opus · ceiling fable).」 ⇒ ⛔ not the floor: the text being written IS a contract restatement, andhookRecordStateis exported, so a subtly wrong sentence ships in a published.d.ts.Clause-②: no
Thread-read: 5712613263
Serial constraints cleared:
Disjoint from both in-flight siblings — #17938 is in packages/metadata-protocol/, #17231 in packages/drivers/driver-sql/src/. ⚠️ SAME PACKAGE as PR #18625 (#17975), which is green and IN THE MERGE QUEUE, but NOT the same file: #18625's face is .changeset/ + engine.ts + bulk-write-per-row-hooks.test.ts, verified with get_files; hook-wrappers.ts is NOT in it. The fold-or-serial answer is below.fold-or-serial — answered explicitly, as the rule requires: SERIAL
This card and #17975 are the same defect family: retired batch-model prose in
packages/objectql, same ruling (#16074 / ADR-0058 Addendum II), same published-.d.tsexposure question.Against the five folding gates: ① same defect shape and same fix — yes. ② same package — yes, though different files. ③ every member already graded — yes. ④ each independently verifiable — yes. ⑤ a named exclusion list — would be needed.
⛔ Folding is nonetheless refused, and the reason is timing, not merit: #17975 is already delivered and sitting in the merge queue, green. Folding now would mean pulling a queued, green PR back out to add a file. ⇒ Strictly worse than a second PR. Serial it is, and this card goes out behind it.
Premises re-measured by this seat on
origin/mainat claim timehook-wrappers.ts:1083-1088— 「Itsbefore*dispatch still fires once for the batch with no prior row, so that payload is left exactly as it is」:1177-1182— 「it fires ONCE for N matched rows, so there is no single prior record to bind;previousstays unbound」.d.tsexposure is real, ⛔ not hypotheticalhookRecordStateis defined athook-wrappers.ts:1109and re-exported atpackages/objectql/src/index.ts:363⇒ All three hold. The card is live and correctly described.
⭐ Increment — the retired phrasing occurs in TWO FILES the card does not name
Sweeping
fires once/fires ONCEacrosspackages/objectql/src: 2 hits inhook-wrappers.ts(the two cited), plus 1 inhook-input-shape-contract.test.tsand 1 inplugin-metadata-event-outage.test.ts.⭐ This is the same trap that bit the sibling card: on #17975 this seat claimed four carriers, and measurement showed two of them needed no change and found two more the seat had missed. ⇒ Expect the name-match to over- and under-count in both directions.
Clause-② is
no, and that is a precedent this seat set two hours ago⭐ Recorded so the grading is consistent rather than re-derived: on #17975, this seat first graded the identical act
Clause-②: yes, was shown to be wrong, and posted a correction (5712379730). The reasoning applies verbatim here — this card adds no exported symbol, no key, no accepted value; the accept set was widened by PR #17249 inpackages/spec, ⛔ not here. Four in-repo precedents for prose repairs inside published packages are allpatch(abc4b83ce8,484484041a,7851fa3146,2d5945a00f).⇒ ⛔ The same class cannot be graded two ways on two days — and this time the grading starts in the right place.
Generated by Claude Code
huangyiirene commentedon Sep 17, 2026
os-dev-report
{ "issue": 18331, "status": "done", "branch": "claude/issue-18331-hook-wrappers-per-row-docblocks", "pr": "https://github.com/objectstack-ai/objectstack/pull/18637", "session": "session_01CqmCgU5RGDoJYhHUMVp2af", "premise_still_valid": true, "head_sha": "0cd458243a", "files_changed": [ "packages/objectql/src/hook-wrappers.ts (comment lines only: +13 -12 across two docblocks)", ".changeset/18331-objectql-hook-wrappers-per-row-docblocks.md (new, patch, '@objectstack/objectql')" ], "summary": "Both cited docblocks in hook-wrappers.ts stated the retired batch model in the present tense; both now state the per-row contract of ruling #16074 / ADR-0058 Addendum II D1/D2, in the vocabulary PR #18625 landed in this package. Prose only: no behaviour change, no assertion change, no logic touched. origin/main was merged after #18625 landed and the wording aligned against it before finalising. R2 measured and the two sites differ: site 1 ships in the published .d.ts, site 2 does not, so the diff moves published text and carries a patch changeset with Clause-②: no.", "clause_2": "no — confirmed by check-clause2-carriers.mjs --pair 18637 :: exit 0, both carriers agreeing, no widening tell. needs:contract-review is NOT on the PR; not applied, not removed, not waited on.", "changeset": "patch, '@objectstack/objectql'. Justified by measurement, not precedent alone: the retired sentence is present in the real npm tarball of @objectstack/objectql@17.4.0 at dist/util-Dw5ZTIII.d.ts:8039 and the matching .d.mts.", "r2_measurement": { "method": "npm pack @objectstack/objectql@17.4.0, extracted and grepped; then the same greps against a local build of head 0cd458243a. Exit codes captured before any pipe.", "site_1_hookRecordState_docblock": "SHIPS. Published tarball dist/util-Dw5ZTIII.d.ts:8039 + .d.mts:8039 carry the retired sentence. Mechanism: hookRecordState has TWO leading comment blocks in source (the detached pickRecordPayload docblock, then its own) and the dts bundler emits BOTH verbatim onto 'declare function hookRecordState' at published line 8061. That chunk backs both the '.' and './core' entrypoints. After the fix the replacement is at dist/util-DWbR4Mtj.d.ts:8223 + .d.mts.", "site_2_pickPreviousPayload_docblock": "DOES NOT SHIP. 'fires ONCE for N matched rows' is absent from every file of the published tarball (grep exit 1); pickPreviousPayload is module-private and reaches dist only as an {@link} reference. After the fix, its unique marker 'any update-shaped context' is in src once and in ZERO dist files (grep exit 1).", "post_fix_sweep": "Neither 'fires once' nor 'fires ONCE' survives anywhere in the built dist of head 0cd458243a (grep exit 1)." }, "gates": { "derivation": "node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands, no paths passed, on FINAL head 0cd458243a; change set 2 paths vs merge base 17005ccbe (three-dot). ONE derivation, ONE reconciliation, both on that sha.", "reconciliation": "--ran with every line recorded in the tool's COMMAND-then-exit-CODE spelling — 54 derived, 54 accounted, 0 UNRUN.", "green": 51, "red_known_defect": "pnpm check:cross-package-test-inputs :: exit 1 — the pre-announced gate defect already filed as #18621. Reds because packages/spec/dist/ exists, which the dependency-closure build creates. Its flag is rooted in packages/cli/test/init-created-files-summary.e2e.test.ts descending packages/spec/dist/; neither path is in this diff. Named, not chased; no twin filed; NOT reported as this PR's red.", "not_measured": [ "pnpm check:dual-build-cjs-loads :: exit 3 — the gate itself prints 'PREREQUISITE NOT MET ... nothing was measured' (reads built output; most packages have no dist/ in this worktree). Covered by CI Build Core.", "node scripts/check-engine-split-ratio.mjs --days 90 :: exit 2 — the gate REFUSES to answer because this container's clone is shallow and its oldest visible commit sits inside the 90-day window. An ADR-0076 D7 repo-history metric, orthogonal to a comment-only diff." ], "ci_owned_not_run_locally": "The 51 artifact-roster families, the 11 declared wide-population families, the 5 path-scheduled CI jobs and the always-runs tail are each outside the 54 derived, per the tool's own closing note." }, "tests": { "objectql_suite": "pnpm --filter @objectstack/objectql exec vitest run --maxWorkers=2 :: 299 test files passed (299), 4995 tests passed (4995), duration 269.39s. Run through scripts/pm/os-verify-lock.sh, VERDICT command-exit 0.", "typecheck": "pnpm --filter @objectstack/objectql typecheck :: green, including check:test-typecheck — 40 files / 234 errors / 65 pinned signatures held in the shrink-only ledger, unchanged by this diff.", "dependency_closure_build": "turbo run build --filter=@objectstack/objectql --concurrency=2 :: 14/14 tasks successful, VERDICT command-exit 0, on final head.", "repo_lint": "eslint . --no-inline-config over the WHOLE repo :: exit 0, 6811 files linted, 0 errors, 0 warnings. No narrowing was performed, so none is claimed. (eslint.config.mjs:328 records that type-aware linting is not enabled.)", "ablation": "NOT APPLICABLE and none fabricated — this diff adds no guard, no assertion and no gate, so there is nothing whose ability to fail could be ablated. The per-row contract the new prose states is already pinned by a live behavioural test on main: hook-condition-bulk-previous.test.ts, 'the bulk write #5037 and #5038 both rejected now SUCCEEDS, firing per row', which boots a real engine, runs multi: true with a previous.done transition condition on beforeUpdate, and asserts one dispatch per matched row.", "control_char_scan": "grep -naP over both changed files :: exit 1 (clean); pnpm check:nul-bytes :: exit 0." }, "line_budget": "NOT APPLICABLE — no path under skills/ or .claude/skills/ is touched, so no published-skill line ratchet is owed. Actual diff: 2 files, +13/-12 comment lines in hook-wrappers.ts plus a 33-line changeset. No source statement, no assertion and no test changed.", "deviations": [ "Ran 'vitest run --maxWorkers=2' rather than the package's 'test' script ('vitest run --project local'); the run covered 299 files, which is the package's whole src test population, so this is a superset, not a narrowing.", "The merge of origin/main (bringing PR #18625) was taken mid-task on the coordinator's instruction; all gate derivation, reconciliation, build, typecheck, tests and dist measurement quoted here were re-run AFTER it, on final head 0cd458243a. No pre-merge number is quoted.", "Site 2's first draft named the opt-in dispatchUnscopedMultiWrite dispatch as the remaining absence case and was WITHDRAWN BEFORE PUSHING: wrapDeclarativeHook is applied at registration (hook-binder.ts:221), but the engine's own note records that no metadata binding can set that flag today, so naming it as a case this function observes would be a reachability claim that cannot be shown. The shipped sentence states what is true by construction instead." ], "mcp_calls": "0 — no MCP GitHub tool was called, read or write.", "api_writes": "3 REST proxy writes (+3 git pushes). POST /repos/objectstack-ai/objectstack/pulls (draft, 201); POST /repos/objectstack-ai/objectstack/issues/18637/labels (additive, 200, ['documentation']); POST /repos/objectstack-ai/objectstack/issues/18331/comments (this report). Zero PATCH of any body. Label read-back after the write: current {documentation, size/s, tooling} equals union(read-set, target) — nothing stripped.", "open_questions": [], "out_of_scope_findings": [ "noted, not filed: hook-input-shape-contract.test.ts:136 — R3 name-match, FALSE POSITIVE. It reads '`before*` now fires once PER MATCHED ROW (D1), so the count is the row count' and the assertion under it pins two dispatches for a two-row batch. It states the CURRENT contract correctly. No edit owed, no follow-up card owed. Not touched.", "noted, not filed: plugin-metadata-event-outage.test.ts:33 — R3 name-match, FALSE POSITIVE. 'the boot line fires once per process' is about a degradation log line's emission frequency and has no relation to hook dispatch. Correct as written. No edit owed, no follow-up card owed. Not touched.", "noted, not filed: under-count check inside the declared surface came back clean. Five further batch-model mentions in hook-wrappers.ts are all already correct — the HookConditionError note and the retired-helpers block are PAST-TENSE history (correct as history, judged by the coordinator's own rule); 'The batch-scoped hookContext still exists inside update()/delete(), but it is never handed to triggerHooks' is a true present-tense statement; and unevaluableConditionError's note already says 'a predicate write dispatches before* once per matched row now'. The card's count of two is exact for this file. Successor: any PR re-documenting these helpers.", "noted, not filed: the detached-docblock layout at site 1 — a block documenting pickRecordPayload left sitting above hookRecordState, which the dts bundler then publishes as if it were part of hookRecordState's own documentation. An observation about layout, in none of the three filing classes: the text is accurate after this change and the emit is TypeScript behaving as documented. Recorded only because it is WHY site 1 ships at all, which is not visible from the source. Successor: any PR re-documenting these helpers.", "noted, not filed: packages/objectql/CHANGELOG.md still carries the retired phrasing in released entries. Correctly left alone — a dated record of what was true then, and out of surface." ] }Generated by Claude Code
huangyiirene commentedon Sep 17, 2026
os-dev-report reviewed — ACCEPT. PR #18637 goes ready and into the merge queue.
domain:engineexecution seat,session_01CqmCgU5RGDoJYhHUMVp2af, R1, 2026-09-17T11:36Z. ⛔ Verified against GitHub and the tree — ⛔ not against the report's own account of itself.Form, scope and CI
true·main·0cd458243aFixes #18331Clause-②: noat column 0check-clause2-carriers --pair 18637⇒ exit 0, both carriers agree, no widening tellhook-wrappers.ts+14/−12(comment lines only) + apatchchangeset'@objectstack/objectql': patchget_filesEXPECTED_SKIPS⭐ The warning that earned its place: the two sites do NOT behave the same
The dispatch said 「⛔ do not assume both sites behave the same: one is on an exported function, the other may not be」. Measured against the real npm tarball:
hookRecordState) SHIPS — the retired sentence is in published@objectstack/objectql@17.4.0atdist/util-Dw5ZTIII.d.ts:8039and the matching.d.mts.pickPreviousPayload) DOES NOT — module-private; it reachesdistonly as an{@link}reference, grep exit 1 across the whole tarball.⇒ A single 「these are both published, therefore…」 assumption would have been half wrong, and the changeset argument would have rested on it.
⭐ And the mechanism behind site 1 is worth keeping, because it is invisible from the source:
hookRecordStatehas two leading comment blocks — a detachedpickRecordPayloaddocblock, then its own — and the dts bundler emits both verbatim ontodeclare function hookRecordState. ⇒ A docblock that is not about the exported symbol still ships as its documentation. That is why site 1 publishes at all.✅ Corroborated independently by this seat from the export surface:
hookRecordStateis re-exported inindex.ts(1 hit),pickPreviousPayloadis not (0).⭐ The changeset is justified by that measurement, ⛔ not by precedent alone — which is the right order, and stronger than the sibling card's reasoning.
⭐ R3 — both extra files were FALSE POSITIVES, and "report, do not edit" is what saved them
The dispatch handed over two name-match hits outside the surface, explicitly as a reading and ⛔ not a to-do list. Both turned out correct as written:
hook-input-shape-contract.test.ts:136— reads 「before*now fires once PER MATCHED ROW (D1)」 and pins two dispatches for a two-row batch. It states the current contract.plugin-metadata-event-outage.test.ts:33— 「the boot line fires once per process」, about a degradation log line, ⛔ nothing to do with hook dispatch.⇒ A dev that had swept by name-match would have 「fixed」 two correct files into wrongness. ⛔ Neither was touched.
⭐ The under-count check also came back clean, and that is the other half nobody usually runs: five further batch-model mentions inside the declared surface are all already correct — past-tense history (correct as history), a true present-tense statement about the batch-scoped context, and one note that already says 「dispatches
before*once per matched row now」. ⇒ The card's count of two is exact for this file. Unlike the sibling card, where the seat's count was wrong in both directions, here it was right — and it was checked rather than assumed.⭐ A sentence withdrawn before pushing, for the right reason
Site 2's first draft named an opt-in dispatch as the remaining absence case and was withdrawn before the push: the wrapper is applied at registration, but the engine's own note records that no metadata binding can set that flag today, so naming it would have been a reachability claim that cannot be shown. The shipped sentence states what is true by construction instead.
⇒ ⛔ The dev declined to publish a claim it could not prove, in prose that nobody would have checked. That is the behaviour this lane's whole measurement discipline exists to produce.
Sibling alignment held
origin/mainwas merged after PR #18625 landed and the wording aligned against it before finalising, per the dispatch. ⇒ ⛔ One package does not now state one contract in two vocabularies. Every number quoted is post-merge, on the final head.Other readings accepted
hook-condition-bulk-previous.test.ts, which boots a real engine and asserts one dispatch per matched row). ⭐ Saying 「not applicable, and here is what covers it instead」 beats inventing a ceremony.eslintrun: 6811 files, 0 errors — no narrowing performed, so none claimed.check:cross-package-test-inputsred ([finding]check:cross-package-test-inputsexits 0 or 1 on the same commit depending on whether adist/exists — and lint.yml runs it with no build ahead of it, so CI only ever sees the blind leg #18621) named and ⛔ not chased, ⛔ not twinned, ⛔ not reported as this PR's red. TwoNOT MEASUREDfamilies each with the gate's own refusal reason.CHANGELOG.md's retired phrasing correctly left alone.mcp_calls0 · REST writes 3, label write read back. ⛔ No denied-roster tool was called.⇒ Ready + auto-merge (SQUASH). This seat follows it to MERGED.
Generated by Claude Code
huangyiirene commentedon Sep 17, 2026
MERGED — verified by two readings, with the control re-taken after the first one failed
domain:engineexecution seat,session_01CqmCgU5RGDoJYhHUMVp2af, R1, 2026-09-17T12:05Z. PR #18637 landed; residue cleared in the same pass.Reading 1 — content on
origin/main.hook-wrappers.tspackages/objectql/CHANGELOG.mdforfires ONCEexpecting the retired phrasing to still be there, and got 0 — which would have made the source-side zero worthless (「零命中须配同主体必中词」). Re-measured: the CHANGELOG does carry the old model, spelledfires once(2 hits). ⇒ The needle was wrong for that corpus, ⛔ not the reading. The control above was then re-taken on the same file as the zero, where it belongs.⭐ The CHANGELOG's copy being intact is also the proof that the dispatch's Zone-1 ruling held — 「⛔ do not touch the CHANGELOG; it is a dated record of what was true then」.
Reading 2 —
922c755888·docs(objectql): state the per-row `before*` contract in the two hook-wrapper docblocks (#18637)· parent count 1 ⇒ squash.Residue cleared:
pm:dispatcheddropped, assignee released, read back. Card auto-closedcompleted.The three things this card measured that a name-match would have got wrong
hookRecordState) ships in the published.d.ts; site 2 (pickPreviousPayload) does not — module-private, reachingdistonly as an{@link}. A single 「both are published」 assumption would have been half wrong, and the changeset argument rested on it.hookRecordStatecarries two leading comment blocks — a detachedpickRecordPayloaddocblock, then its own — and the dts bundler emits both verbatim onto the exported declaration. A docblock that is not about the exported symbol still ships as its documentation.⭐ And the under-count check came back clean too — five further batch-model mentions inside the surface are all already correct, so the card's count of two was exact for this file. Checked, ⛔ not assumed.
Generated by Claude Code
docs(objectql): state the per-row `before*` contract in the two hook-…