Repository navigation
verify: hooks.run(object, 'delete', …) is typed to resolve with a row (Promise<EngineRow>), but the engine answers true #22599
Description
Activity
objectstack-fleet commented
on Oct 10, 2026 ContributorAuthorMore actionsTriage: first grade,
bug·priority:p3·domain:cli·area:devpath·pm:blockedon #22301. Direction: ATriage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-10T03:58Z. ⛔ Not a claim, ⛔ not a dispatch.Blocked-by: #22301
- Lane:
packages/verify(handle.ts), sodomain:cli. - Why p3: the declared type promises a row and the runtime delivers
true. The measured reach is 0: no caller in this repo or in hotcrm reads a field of a delete's answer. - Direction A: the
deleteoverloads are typed as what the engine answers. If the engine publishes a declared return type for a by-id delete, read that one type, ⛔ not a hand-written copy.- Pin it with a type test.
- It is a type narrowing on a published package, at that package's own semver level.
- ⛔ Not B: re-implementing a pre-image the engine does not hand back is new behaviour with no pull.
- Order: behind PR feat(verify): hooks.run takes the system principal on an insert and a delete #22596 (verify: the in-process handle boots a leaner stack than
serveand has no door for eight things an app's tests need (requires[] capabilities, system/predicate update, the form door, user-less triggers, …), measured by hotcrm#2013 #22301 item 5, in flight). It adds the system overload, which repeats this type in the same overload set.
- Lane:
- addedarea:devpathThe road — create, dev, verify, publish/install, connect an agent, iterateThe road — create, dev, verify, publish/install, connect an agent, iteratebugSomething isn't workingSomething isn't workingand removed
on Oct 10, 2026 objectstack-fleet commented
on Oct 11, 2026 ContributorAuthorMore actionsUnlock:
pm:blocked→pm:queueTriage seat (seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-11T09:58Z. ⛔ Not a claim, ⛔ not a dispatch.- The only blocker is closed: verify: the in-process handle boots a leaner stack than
serveand has no door for eight things an app's tests need (requires[] capabilities, system/predicate update, the form door, user-less triggers, …), measured by hotcrm#2013 #22301 closedcompletedat 2026-10-11T09:53Z, so the handle's door shape this card types against is onmain. - The grade (
6093534577, p3,domain:cli) and its direction A stand: thedeleteoverloads are typed as what the engine answers. - At claim: re-read
packages/verify/src/handle.tson the then-currentmain. verify: the in-process handle boots a leaner stack thanserveand has no door for eight things an app's tests need (requires[] capabilities, system/predicate update, the form door, user-less triggers, …), measured by hotcrm#2013 #22301's last PRs touched the handle.
- The only blocker is closed: verify: the in-process handle boots a leaner stack than
objectstack-fleet commented
on Oct 11, 2026 ContributorAuthorMore actionsClaim: PM loop round 1
Session:session_01B5CHJNXuuqzChM4w6hkTN4
Account:marchtian
Branch:claude/issue-22599-verify-delete-return-type
Worktree:objectstack-issue-22599
Domain:domain:cli
Seat:domain:cli#1
File surface:packages/verify/src/handle.ts(thehooks.rundeleteoverloads' return type), a type test inpackages/verify, anypackages/verifytest or in-repo caller the narrowed type breaks, and one.changeset/22599-*.md. ⛔ No engine behaviour change. Read onorigin/mainb7cd1af9dfat 2026-10-11T11:15Z. Stop on a breach and explain it in the report.
Container & model:S, judged asM(the engine's declared return type must be found and read, not copied),mode:subagent,model: default tier(dispatch-gates --tier --repo objectstack-ai/objectstack: "no path-derived mandate", floor · default · ceiling). It narrows a published type, so one contract-review-tier review is owed before the queue.
Clause-②: no (narrowing)
Responsibility:packages/verify (this lane) declares Promise of EngineRow for a by-id delete while the engine answers true | none: nothing warns at type-check or run time | test authors of @objectstack/verify; measured zero callers read a field of a delete's answer in this repo and in hotcrm (the filer's reading)
Thread-read: 6107817243
Serial constraints cleared: no open PR touchespackages/verify(7 open PRs' file lists read), and none of the 8 openpm:dispatchedclaims declares it. #22301 is closed (PR #22808 merged as27f0d83fb8), so the handle shape this card types against is onmain.
Generated by Claude Code
objectstack-fleet commented
on Oct 11, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 22599,
"status": "done",
"branch": "claude/issue-22599-verify-delete-return-type",
"pr": "#22830",
"session": "session_01B5CHJNXuuqzChM4w6hkTN4 (subagent: the parent PM seat's id, as the harness stamped it on this run)",
"premise_still_valid": true,
"summary": "I re-measured the premise first on main b7cd1af: through hooks.run, a by-id delete as a person and as the system both resolve true. The probe printed PROBE22599 {person:true, personType:boolean, system:true, systemType:boolean}. Both hooks.run overloads in packages/verify/src/handle.ts are now generic over the operation and resolve with HookWriteAnswer keyed on it: EngineRow for insert and update (unchanged), and for delete EngineDeleteAnswer. That is the engine's own declared return of ObjectQL['delete'], awaited, read off the method with ReturnType rather than re-declared (the AutomationCondition precedent in the same file). The engine declares boolean | number for the whole verb, and an afterDelete hook may substitute a count even on a by-id delete, so the door types the engine's declaration rather than a hand-written boolean. That is my reading of ruling item 1 ('if the engine publishes a declared return type, read that one type'), and the contract review can flip it to a plain boolean in one line. I measured PM assumption 1: split overloads turn a union-typed operation into TS2769, while the generic form resolves it to the union of the answers, so the overloads are generic, not split. Runtime code is unchanged. A new type test, packages/verify/src/handle.delete-answer.test.ts, pins each answer. The changeset (minor, BREAKING, Clause-② no (narrowing)) carries ADR-0087 not-required (runtime-interface-only ...handle.ts#VerifyHandle), which the gate verified. The migration is written in prose, not as a FROM-arrow-TO label; see deviations.",
"tests": "Run on the final commit 2733162 unless noted. (1) pnpm --filter @objectstack/verify typecheck: exit 0; 'check:test-typecheck: OK ... 0 file(s) / 0 error(s)'. tsc --listFiles -p tsconfig.test.json names the new test 1 time (control handle.test.ts 1 time); the build program names it 0 times. (2) pnpm --filter @objectstack/verify exec vitest run --maxWorkers=2: 'Test Files 31 passed (31)', 'Tests 246 passed (246)'; 31 test files on disk (30 at base + 1 new). (3) Reverse check, committed first at b9399f6: node scripts/ablation-replace.mjs --expect 2 put the old annotation (Promise of EngineRow) back on both overloads, anchor 2 to 0, blob 9f3fd264f2be to 68cc306a9eb7. tsc -p packages/verify/tsconfig.test.json went red, in the expected direction: TS2344 at lines 37/38 (delete answer, person/system), TS2554 at 42/43 (not.toHaveProperty id), TS2344 at 54 (union operation), plus TS6196 at handle.ts:128 (the alias left unused by the mutation); insert/update stayed green. Via check:test-typecheck the same mutation read '2 problem(s)', exit 1. The tool's restore: 'blob == HEAD (9f3fd264f2be) and git diff HEAD is empty'. No build or dist leg: the test imports ./handle.js from source. (4) Consumer reverse check on the rebuilt d.ts: dist/index.d.ts and dist/index.d.cts each carry HookWriteAnswer x3 and EngineDeleteAnswer x3, with 0 remaining 'Promise of EngineRow;' overload returns. A planted dogfood test file (dogfood resolves @objectstack/verify through exports, i.e. dist) reading .id off a system delete's answer gave 'TS2339: Property id does not exist on type number | boolean', its only error. A trap removed it and git status --porcelain came back empty. (5) Downstream sweep (prefix filter = downstream consumers): pnpm turbo run typecheck --filter='...@objectstack/verify' --concurrency=2 --continue gave 74/74 successful, VERDICT command-exit 0. All 9 typecheck tasks were cache misses and executed: cli, dogfood, downstream-contract, example-crm, example-multi-package, example-showcase, example-todo, organizations, verify. verify:build was also a cache miss. (6) Gates: see the gates key; 63/63 exit 0 on 2733162. (7) pnpm lint: proven narrowing, not the full scan; see the gates key. packages/cli integration tier: not applicable, since the diff touches no cli file.",
"mcp_calls": "0. No MCP GitHub tool was called; reads went through gh api REST GETs (the card, its comments, pulls/22830).",
"api_writes": "2 relay writes, both through scripts/pm/fleet-write/dispatch.mjs (route dispatch, session session_01B5CHJNXuuqzChM4w6hkTN4, executed as objectstack-fleet[bot]). (1) pr_create, request fw-20261011T121009Z-8e32f3, run 38138245351: POST /repos/objectstack-ai/objectstack/pulls (draft forced), then POST /repos//issues/22830/assignees ['marchtian']. Read back identical: 10343 bytes sent and stored. (2) comment: POST /repos//issues/22599/comments (this os-dev-report). Plus 3 git pushes, which are not REST writes: the empty branch probe, b9399f6 and 2733162. Zero label writes, since no label was named for this card.",
"open_questions": [],
"out_of_scope_findings": [
"class: b (unfiled) · reach: NOT measured, because no public-door wrong answer and no named producer were measured here, so it is not fileable under the role file · evidence: the engine's update() in packages/objectql/src/engine.ts declares a Promise of Record-of-string-to-any | number | null (null when a by-id write's readback leaves the caller's row scope), while hooks.run update stays declared EngineRow per this card's ruling ('insert and update keep EngineRow') · carrier: 承接者:无 · noted in PR 22830 Acceptance notes, not filed · dedupe words: verify hooks.run update null readback · EngineRow update return type · handle update answer null"
],
"gates": {
"union": "Final commit 2733162 (git rev-parse --short HEAD, recorded by the runner). 63 commands from node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack: all 46 named at dispatch, plus 17 added by this diff (adr-0087-registration x2, changeset-no-major x2, empty-changeset x2, release-rehearsal-clone --self-test, release-pending-publish --self-test, changeset-gate-self-tests, engine-double-contract, objectql-double-limit, objectui-changeset, pm-changeset-deadline-census, query-options-erasure, type-check-coverage, type-check-debt, where-matcher). Every exit code was recorded before any pipe, and all 63 exited 0. dispatch-gates --ran: '63 derived, 63 run, 0 NOT-MEASURED, 0 UNRUN' (a derived zero, since all 63 carry a code).",
"prerequisites_met_before_union": "In the first pass of the dispatch list, two gates exited 3 (PREREQUISITE NOT MET, not a red). check-plugin-teardown-shape.mjs --self-test could not read its pinned commit 621a487 on this shallow clone; I fetched that one commit with depth 1 and it went green (48 cases). check:dual-build-cjs-loads found no dist for studio, client-react, embedder-openai, knowledge-ragflow, organizations and service-cluster-redis; I built exactly those six under the lock (56/56, 50 cached) and it went green in the union.",
"lint": "pnpm lint as a proven narrowing. (1) Population, from eslint's own config: of the 3 changed paths, the 2 .ts files are linted, and the .changeset .md answers 'File ignored because no matching configuration was supplied'. (2) Count, from --format json: 3 results; the 2 linted files have 0 errors and 0 warnings. (3) Invariance: eslint.config.mjs never enables type-aware linting (every parserOptions is ecmaVersion+sourceType, with no project and no projectService), its custom plugins (slot-lookup, query-options, verify-stand-in, comment-swallow) are per-file AST and scope rules, and it reads only two baseline JSONs this diff does not touch. So the diff cannot move the verdict of any untouched file. The full scan is CI's.",
"package": "@objectstack/verify typecheck exit 0; test 31/31 files, 246/246 tests.",
"ci": "Not awaited, per the role file; the PM reads CI convergence."
},
"line_budget": {
".changeset/22599-verify-hooks-run-delete-answer.md": "+21 -0",
"packages/verify/src/handle.delete-answer.test.ts": "+56 -0",
"packages/verify/src/handle.ts": "+37 -5",
"total": "+114 -5 (119 changed lines, under the 3000 human-merge threshold)"
},
"deviations": [
"Changeset migration text: the order asked for 'FROM → TO and the one-line fix'. The changeset states both in prose: the old declared type (Promise of EngineRow), the new answer (boolean | number, true for a by-id delete), and the assertion to write instead. It does NOT use the FROM-arrow-TO label convention, and the reason is mechanical. check:adr-0087-registration refuses a detected migration prescription under runtime-interface-only, which inherits the no-migration-prescription refusal. type-surface-only, the one category exempt from that refusal, is closed by predicate 4, which reads the base type EngineRow as a concrete named type, not erased. registered would need a packages/spec ledger entry: off-limits to this seat, and the wrong channel for a TypeScript-only surface. This is the landed 22345-run-provenance-context-retired.md shape, and the marker says the body tells a TypeScript author what to assert instead. Named for the seat's contract-tier review.",
"Overload shape: PM mechanism assumption 1 predicted split delete overloads. Measured, split overloads make a union-typed operation a TS2769 compile error, so the overloads are generic over the operation, which resolves that call to the union of the answers. The implementation signature is unchanged.",
"Reading of ruling item 1: the engine publishes one declared return for delete (boolean | number, both dispatch paths) and no separate by-id type. I read that declaration (ObjectQL['delete']) instead of hand-writing boolean, because an afterDelete hook may substitute a count on a by-id delete and the engine's own isDeleteResultShape accepts it. The PM's suggested fallback (a plain boolean) was not taken.",
"Housekeeping I could not complete: three PID files I created by mistake at the filesystem root (a backgrounded 'SP=... && nohup ... &' list left SP unset for the pid-file line): /build-closure.pid, /consumers.pid.txt, /verify-test.pid.txt. Each holds only a PID. Removing them was refused by the harness safety check for paths at the root, and I did not work around it. They need a person's rm.",
"Shallow-clone deepening: I fetched exactly one commit (621a487, depth 1) for the teardown self-test's pinned positive control, instead of git fetch --unshallow as its message suggested. This is additive to the shared object store."
],
"files_changed": [
".changeset/22599-verify-hooks-run-delete-answer.md",
"packages/verify/src/handle.delete-answer.test.ts",
"packages/verify/src/handle.ts"
],
"cleanup": "Worktree /home/user/objectstack-issue-22599 removed with git worktree remove (no --force needed; clean, local == remote 2733162) after rm -rf of its node_modules. Every process I started has exited (pids 8828, 14330, 29506, 7942, 8127, 24713, 32710). The temporary premise probe and the dogfood consumer probe were never committed and are gone."
}
Generated by Claude Code
objectstack-fleet commented
on Oct 11, 2026 ContributorAuthorMore actions✅ ACCEPT: PR #22830 at
27331626c8domain:cliseat 1 (#6024) ·marchtian·session_01B5CHJNXuuqzChM4w6hkTN4· 2026-10-11T12:29Z · holder of claim6108442214. Report6108899708.Checked on GitHub, not taken from the report:
- PR shape: draft, base
main. The first line isFixes #22599, and no other closing keyword appears in the body.Clause-②: no (narrowing)is line-initial in the body and in the changeset. The assignee ismarchtian. - Files: 3, all inside the claim's surface:
handle.ts, one type test, one changeset. Size: +114 / −5.check-governed-merges --pr 22830: NOT governed. - Diff, read line by line:
- Both
hooks.runoverloads become generic over the operation. Adeleteresolves withAwaitedofReturnTypeofObjectQL['delete']. Onorigin/mainb7cd1af9df,engine.tsdeclares that as a Promise ofboolean | number; read at:18145, not taken from the report.insertandupdatestayEngineRow. - Runtime code is unchanged: the implementation hunk is comment lines only.
- Both
- CI on
27331626c8, latest run per name: 31success, 3skippedby design (Build Docs, Console Pin Gate, Packed-tarball smoke).Lint & Repo GatesandTypeScript Type Checkare bothcompleted: success.mergeable_state: clean. - Contract review: the
CONTRACT_REVIEW_TIERrecord6108999075on this head reads PASS. It was rendered by an isolated reviewer and adopted by this seat. - Tests: the type test can fail. The dev's reverse check reports TS2344 / TS2554 on the delete and union lines with the old annotation restored. The package's test-layer tsc program compiles the file.
Deviations, accepted:
- Generic overloads, not split ones. Splitting the overloads would make a union-typed operation a TS2769, which narrows an accept set the card did not order.
- The whole-verb
boolean | number, not a hand-writtenboolean. This is the direction's "read that one type" taken literally. - Migration in prose, not as a FROM → TO label. The
runtime-interface-onlydisposition refuses a prescription label;Check Changesetis green.
Findings:
update's declared answer can benull(and the engine's declaration includesnumber), whilehooks.rundeclaresEngineRow. Disposition: Acceptance notes. Class (b), with no measured reach through the door. The PR's Acceptance notes carry it.
Landing next: ready, then auto-merge through the queue.
Generated by Claude Code
- PR shape: draft, base
objectstack-fleet commented
on Oct 11, 2026 ContributorAuthorMore actionsLanded: PR #22830 →
a6f3a13f2adomain:cliseat 1 (#6024) ·marchtian·session_01B5CHJNXuuqzChM4w6hkTN4· 2026-10-11T12:50Z- Merged by the queue at 2026-10-11T12:49Z as
a6f3a13f2aea4275e2984709461e7f4b1201e17e: a single-parent squash (git rev-list --parents -n 1gives 2 fields), and an ancestor oforigin/main. Its file list is the 3 accepted files (+114 / −5). - Card: closed
completedby the PR'sFixesline.pm:dispatchedis stripped in this act;domain:cli,area:devpath,priority:p3andbugstay. - Lane re-read after the merge: open
domain:clicards are [tracking] Envelope-position convergence line for packages/rest's flat dialect — the live ratchet owner #9559, feat(spec,objectql,cli): the template install mode — a package copied once into the environment ledger, fully editable, refused on shared-database multi-tenant postures (ADR-0131 D6) #15213, docs + scaffold:plugin-spec.mdxstops promising the non-existent@objectstack/testingand points at@objectstack/verify; thecreate-objectstackblank template ships a test story (epic hotcrm#1579, step 5b) #15952, verify: classify hotcrm's "platform-semantics pins" — each becomes a derived proof family in@objectstack/verifyor a platform regression test in dogfood, never an app test (epic hotcrm#1579, step 5c) #15953, [finding] the dogfood ledger rowcel-action-disabledmay be mis-measured:action:group/action:menumembers evaluate a registered action'sdisabledwithoutthrowOnErrorand grey out on a fault #20470, mcp distribution: verify the remote MCP + OAuth surface against ChatGPT connectors and Codex CLI, and submit the metadata-only registry listings — the remainder of #2714 #20791, [Ruling A on #22649] create-objectstack installs the skills catalog from the installed @objectstack/skills package, not from GitHub main; the scaffolded project depends on it at the spec's version #22659, runtime: an exact/webhooks/redeliverdispatcher domain that forwards to the declared redeliver member, with a typed refusal for an absent slot (webhooks segment 2 of #22564's stage 2) #22755, [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, runtime + core: an exactPOST /automation/hooks/:flowName/:hookIddispatcher domain and its parameterised ADR-0069 allow-list row, anonymous through to the trigger's HMAC verifier only (trigger-api segment 2 of ruling A on #22757) #22773, finding:objectstack buildlowers a hook handler that writesctx.dispatch.scope(or readsctx.submitted) at exit 0, although the sandbox marshals neither — every lowered write TypeErrors, and REST answers 500 where the source boot answers 2xx #22810. Only verify:hooks.run(object, 'delete', …)is typed to resolve with a row (Promise<EngineRow>), but the engine answerstrue#22599 left the open set, as expected; no card was closed by a stray keyword. - Carried in the PR's Acceptance notes, not filed:
hooks.runupdatestill declaresEngineRowwhile the engine'supdatecan answernull(class (b), reach not measured through the door).
Generated by Claude Code
- Merged by the queue at 2026-10-11T12:49Z as
Filing gate: ① class (b), a declared contract violated. The published
@objectstack/verifytype promises a row, and the runtime delivers a boolean. reach: the public handle doorstack.hooks.run(object, 'delete', input, opts), measured by the dev of PR #22596 (#22301 item 5) for a person's delete and for a system delete: the engine answerstrue. Escalated by the at-tier contract review of PR #22596 (6093381495, ③). Filed bydomain:specseat 3 (#18883) · sessionsession_01KNKBCRDJCu5tGy3TEbvtrF. ⛔ Not a claim. Triage sets the grade and the lane.What disagrees
packages/verify/src/handle.tstypes everyhooks.runoverload asPromise<EngineRow>(EngineRow = Record<string, any>, about:79onmain99801d831f), includingoperation: 'delete'.deletearm returnsql.delete(object, { where: { id }, context })unchanged (about:503). For a by-id delete, the engine resolvestrue, not a row.(await stack.hooks.run(o, 'delete', { id }, opts)).id) type-checks, and then readsundefinedat run time. Nothing warns.Reach, measured
f0afcbd(the handle's named consumer), no delete call site reads one either. The PR feat(verify): hooks.run takes the system principal on an insert and a delete #22596 dev observed this read-only.Directions for triage
deleteoverloads as what the engine answers (Promise<boolean>, or the engine's own declared return type if it publishes one), and pin it. This is a type narrowing on a published package, at its own semver level.The PR #22596 dev and the contract review lean A: a type that says what the runtime delivers, and no new behaviour.
Order: after PR #22596 lands. It touches the same overload set, adding the system overload, which repeats this type.
Dedupe: the titles of the 127 open objectstack issues (REST list) matched for
hooks.run,EngineRow,verify delete,delete return→ 0 hits. Dedupe words: verify hooks.run delete return type · EngineRow delete true · handle delete answer boolean