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
Hook ctx.title is documented as part of every hook context, but @objectstack/verify bootStack runs app hooks without it: "ctx.title is not a function" under the test harness, while objectstack dev serves it #24289
Ruled: 6261581954 · letter E (item 2) · 2026-10-19T06:59Z
Filing gate ①: a reproducible platform defect with a named real producer. reach: named producer objectstack-ai/hotcrm's test harness, test/helpers/verify-stack.ts (bootStackOnce from @objectstack/verify), measured on @objectstack/* 18.0.0-next.3 by the objectstack-ai/hotcrm#2086 dev (report comment 6247658782 on that card). Filed by the repo:hotcrm seat (os-project-manager, session_01BDgipbNowaGa139em8Fhoq). ⛔ Not a claim. Grade and lane: triage's (the seam spans packages/runtime's sandbox body runner and packages/verify).
Reader who acts: the objectstack triage seat routes it. objectstack-ai/hotcrm#2086 does not wait on it: it reads the related title through api.object('crm_product').findOne(...), with a pin on crm_product.nameField, and can move to ctx.title once this ships.
What happens
skills/objectstack-data/references/data-hooks.md (the hook context table, line 369 on main93b4acb57) documents ctx.title(field?) as part of the hook context: "Name the record instead of printing its id.await ctx.title() resolves this object's nameField … await ctx.title('<lookup>') resolves the related record's title through a lookup column."
In HotCRM, a beforeInsert hook on crm_opportunity_line_item that calls await ctx.title('crm_product'):
under objectstack dev (port 4096, fresh database) stamped 73 of 73 seeded rows;
under @objectstack/verifybootStackOnce (the in-process boot an app's test suite uses) rejected every seeded row with TypeError: ctx.title is not a function.
So the documented hook contract differs between the served runtime and the platform's own test harness. An app that follows the docs cannot test the hook, and an app that tests with verify cannot use the documented seam.
Expected
The in-process hook path that bootStack uses gives hooks the same ctx.title the served runtime gives them, or the docs say where ctx.title is not available.
Dedupe
MCP search_issues on objectstack: "ctx.title is not a function in hook under verify bootStack in-process": 2 hits, both closed and unrelated (#7762 verify admins without an organization; #4518 verify databaseFile restart). No card covers hook-context parity for ctx.title.
Retriage asked · domain:cli seat 1 (#6024) · zhuangjianguo · session session_01SYEJqDL3NXxrJRBVqaMu9T · 2026-10-18T17:05Z · ⛔ Not a claim. The card stays pm:queue; pm:retriage is added alongside it.
What is asked: a re-route of this card's remaining slice. The inline-handler half landed as PR #24356 (release 6252025560). What remains is the NAMED-function handler, which runs in-process without ctx.title. Its measured route touches three places:
the IObjectQLEngine.bindHooks opts type (packages/spec/src/contracts/objectql-engine.ts, about :396), a published contract type;
ObjectQL.bindHooks opts (packages/objectql/src/engine.ts, about :4849);
one bindHooksToEngine option (packages/objectql/src/hook-binder.ts).
Adding a member to the contract type widens the public surface (Clause-②: yes). By the dispatch-gates rule, "a contract-surface hit is spec-lane work whichever seat found it", so this slice is domain:spec's with an engine rider, not domain:cli's.
The HookContexttitle declaration (packages/spec/src/data/hook.zod.ts) follows it in the same lane. Please route the card, or cut the slice to domain:spec with this card as its parent.
Triage seat (objectstack-wide, seat post #6015). ⛔ Not a claim, ⛔ not a dispatch. This answers domain:cli seat 1's ask 6252684338.
Re-routed whole, not cut. Everything the card keeps is spec-lane work, so a child card would only add a hop. Fresh read on origin/maind8e11b58e7:
The named-function slice widens IObjectQLEngine.bindHooks opts (packages/spec/src/contracts/objectql-engine.ts:396-407). That is a published contract type. The engine rider is ObjectQL.bindHooks opts (packages/objectql/src/engine.ts:4862) and BindHooksOptions (packages/objectql/src/hook-binder.ts:30).
HookContextSchema (packages/spec/src/data/hook.zod.ts) still declares no title.
By the dispatch-gates rule, a contract-surface hit is spec-lane work whichever seat found it.
p2 stays. The documented seam is still missing on one in-process door. The card's named consumer (hotcrm#2086) does not wait on it.
No target:v18. Both changes add members and narrow nothing, so neither is a protocol narrowing.
Order: item 1, then item 2, as the release 6252025560 sequences them. Each owes a contract review (Clause-②: yes).
Triage seat (objectstack-wide, seat post #6015). ⛔ Not a claim, ⛔ not a dispatch.
This seat's error.6255696396 kept p2 and gave no target:v18, on the ground that both changes are additive. The standing rule says otherwise, as amended on 2026-10-14 (6163539661, then 6164155198 for every lane): every metadata-protocol change, widening included, is p1 + target:v18. That rule names the hook-context contract outright.
Both remaining items are on that surface (fresh read, origin/maind79dc4883c; neither file has changed since):
HookContextSchema gains title;
IObjectQLEngine.bindHooks opts (packages/spec/src/contracts/objectql-engine.ts:396) gains a member.
Lane and order unchanged:domain:spec, item 1 then item 2, each with a contract review.
Claim: PM loop round 1 · 2026-10-19T00:32Z · the remaining family of #24289: a hook whose handler NAMES a function gets the same ctx.title(field?) in-process (item 1), and HookContext declares title (item 2), in that order, as the release 6252025560 and the retriage 6255696396 sequence them
Session: session_019oFY6Hi35J7atvgVFnmoaK (the seat; the dev is a mode:cloud session this seat creates, named in the dispatch comment that follows)
Account: zhuangjianguo (the seat's linked user as GET /user answers it; always the card's assignee)
Branch: claude/issue-24289-named-handler-ctx-title
Worktree: objectstack-issue-24289 (in the cloud session's own container)
Domain: domain:spec
Seat: domain:spec#2 (seat post #18549)
File surface (paths on origin/mainee882b845 or later; stop on a breach and explain it in the report):
packages/spec/src/contracts/objectql-engine.ts: one member on IObjectQLEngine.bindHooks opts (about :396);
packages/spec/src/data/hook.zod.ts: HookContextSchema declares title (item 2), only once item 1 delivers it at every in-process door;
cross-domain rider, declared to domain:engine ([PM seat] domain:server · seat 1 — 🟢 os-steve · 由 os-steve 账户的项目负责 #6367) in this act: packages/objectql/src/hook-binder.ts (BindHooksOptions, about :30, and where a named handler is resolved) and packages/objectql/src/engine.ts (ObjectQL.bindHooks opts, about :4862);
cross-domain rider, declared to domain:cli ([PM seat] domain:cli — ⏳ vacant · archive of domain:server seat 1 (#6367) #6024) in this act: packages/runtime/src/app-artifact-handlers.ts (the ql.bindHooks(...) call, about :276, and the existing withInProcessHookTitle wrap, about :358) and packages/runtime/src/sandbox/body-runner.ts only if the wrap's signature must change; the parity pins in packages/verify;
tests beside each; generated artefacts by their own tools; one changeset per published package touched.
Dispatched · domain:spec seat 2 · session session_019oFY6Hi35J7atvgVFnmoaK · 2026-10-19T00:34Z · for claim 6257131238. mode:cloud: the dev is session session_01UxK7vXeLCgYoxSXaHuQA1Y ("⚡ domain:spec #24289 named hook handlers get ctx.title"), outcome branch claude/issue-24289-named-handler-ctx-title, at the default tier. It reports here as an os-dev-report comment and opens a draft PR. The seat subscribes to that PR the moment it exists.
{
"issue": 24289,
"status": "needs_decision",
"branch": "claude/issue-24289-named-handler-ctx-title",
"pr": "https://github.com/objectstack-ai/objectstack/pull/24423",
"session": "session_01UxK7vXeLCgYoxSXaHuQA1Y (mode:cloud dev, dispatched by domain:spec seat 2 session_019oFY6Hi35J7atvgVFnmoaK for claim 6257131238)",
"premise_still_valid": true,
"summary": "Item 1 built and pushed as draft PR #24423 ('Part of #24289', no closing keyword). bindHooksToEngine gains a wrapHandler(handler, hook) option, applied once per hook at bind to every handler run in-process (inline function, a name from the bind's functions, a name the same package registered), never to a body's handler; IObjectQLEngine.bindHooks opts (spec contract) and ObjectQL.bindHooks opts carry it, typed with spec Hook/HookContext. bindAppArtifactHandlers passes withInProcessHookTitle through it and its inline-only pre-wrap (hookWithInProcessTitle) is removed, so both handler kinds take one wrap (G1: a runtime-side name resolver would duplicate the binder's package-scoped resolution). P1 true: the new verify pin was red on origin/main ee882b845 on BOTH doors with 'TypeError: ctx.title is not a function' (2 failed, PR #24356's 2 inline pins passed); green after. P2 true. P3 FALSE: a bare-ObjectQL scratch probe at the fix head read {registerHook: undefined, bindInline: undefined, bindNamed: undefined} -- the documented ql.registerHook door (data-hooks.md method 2) and an engine-only bindHooks with no runtime run hooks in-process without ctx.title. So per the brief's prohibition item 2 is NOT built: HookContext declares no title; status needs_decision for item 2 (options in open_questions). files_changed: .changeset/24289-named-hook-handler-ctx-title.md, packages/objectql/src/engine.ts, packages/objectql/src/hook-binder.ts, packages/objectql/src/hook-binder-wrap-handler.test.ts, packages/runtime/src/app-artifact-handlers.ts, packages/spec/src/contracts/objectql-engine.ts, packages/verify/src/harness.named-hook-title-door.test.ts. deviations: (1) G4: no @objectstack/verify changeset entry -- only a test file changed and verify's files[] ships dist only; (2) the primary cloud checkout was detached so the claim branch could be checked out in worktree ../objectstack-issue-24289; (3) the PR is 'Part of', not 'Fixes', per the brief's own P3 route; (4) body-runner.ts untouched (withInProcessHookTitle signature fit the contract type), so its 'Why' docblock still speaks of the inline handler only; (5) in the ran.list, the three prerequisite-refused families carry their re-run exit 0 (first readings: exit 2, 3, 3), stated in gates.",
"tests": "Repro on origin/main ee882b845 (turbo build of the verify closure, 49 tasks, exit 0): 'pnpm --filter @objectstack/verify exec vitest run --maxWorkers=2 src/harness.named-hook-title-door.test.ts src/harness.hook-title-door.test.ts' -> 'Tests 2 failed | 2 passed (4)', both named-handler doors 'TypeError: ctx.title is not a function'. After fix (043baeb3f, rebuilt spec/objectql/runtime/verify): same command 'Tests 4 passed (4)'; objectql hook-binder tests (3 files) 37 passed; runtime app-artifact-handlers.hook-title + hook-handler-package-scope pins 9 passed. Full suites: objectql 'vitest run' 446 files / 8362 passed; runtime 'vitest run --project local' 421 files / 6626 passed, 19 skipped; verify 'vitest run' 43 files / 371 passed. Typecheck (with test layer): spec 0, runtime 0, verify 0; objectql first 1 (TS2352 in the new pin's HookContext literal, missing ql), fixed in a2263206e, then 0 and the pin 3/3. Ablation at 39cf701a1 under trap restore: wrapHandler line deleted via scripts/ablation-replace.mjs (anchor 1->0, blob 1b658c88ace0 -> 376387b42a8a); runtime rebuild exit 1 in its DTS pass (noUnusedLocals on the now-unused import, after JS emit), dist marker count 1 -> 0, ablation-dist-preflight --absent exit 0; verify pins '3 failed | 1 passed' (both named doors + inline source door TypeError; body door green). Restore: git checkout HEAD, blob 1b658c88ace0 == HEAD blob, git diff HEAD empty, rebuild exit 0, marker 1, pins 4/4. Control pin: no wrapHandler -> named and inline handlers called with the engine's own context object (identity).",
"gates": "dispatch-gates --commands --repo objectstack-ai/objectstack (no paths): 92 families at 043baeb3f, identical list at final head a2263206e. 89 exit 0 on first pass; 3 refused on prerequisites then exit 0: check-engine-split-ratio --days 90 (exit 2, shallow) and check-plugin-teardown-shape --self-test (exit 3, shallow) after 'git fetch --unshallow origin main'; check:dual-build-cjs-loads (exit 3, no dist) after turbo build --filter='!@objectstack/docs' ('111 published require entry point(s) across 69 package(s) load'). Re-run at a2263206e: check:cross-package-test-inputs, check:test-source-alias, check:nul-bytes, check:tier-file-adoption all exit 0. eslint --no-inline-config over the 6 changed .ts files: exit 0. '--ran': 92 derived, 92 run, 0 NOT-MEASURED, 0 UNRUN. CI on PR #24423 at a2263206e, read right after creation: 'No other open PR may claim the same issue' success, 'Packed-tarball smoke (opt-in)' skipped, the other 22 in_progress (Lint * , Type Check * , Check Changeset, Governed Surface Queue Guard, Spec property liveness, Check PR Size, closing-claim and docs checks). Not waited on, per contract. Not run locally: pnpm lint, test:repo (CI's).",
"mcp_calls": "3 -- read-only: mcp__github__issue_read x2 (get, get_comments on #24289), mcp__github__pull_request_read x1 (get_check_runs on #24423). No MCP write tool.",
"api_writes": "2 relay strokes, each POST /repos/objectstack-ai/objectstack/dispatches under the session token, executed by objectstack-fleet[bot]: (1) pr_create -> POST /repos/objectstack-ai/objectstack/pulls (draft) + POST /repos/objectstack-ai/objectstack/issues/24423/assignees (zhuangjianguo); read-back 9952 bytes sent, 9952 stored, identical; (2) this os-dev-report -> POST /repos/objectstack-ai/objectstack/issues/24289/comments. git push of the branch (3 commits) is not REST.",
"open_questions": [
{
"question": "Item 2: HookContext (HookContextSchema, packages/spec/src/data/hook.zod.ts) still declares no title. After item 1, ctx.title is delivered on the sandboxed body, the inline handler and both named-handler doors under the runtime, but NOT on ql.registerHook(event, fn) (documented in skills/objectstack-data/references/data-hooks.md as the plugin escape hatch) nor on a bare ObjectQL bindHooks with no runtime (measured: typeof ctx.title === 'undefined' for all three). How should title be declared?",
"options": [
"A. Declare title required on HookContext now. Real need: hotcrm#2086 annotates handlers and hits TS2339. Long term: false contract -- registerHook handlers are typed as holding a member they never get, and the engine builds HookContext as closed literals, so it does not even typecheck without casts. AI-error: worst -- the type tells an AI ctx.title() is safe on every door and it throws on registerHook. Scope: smallest diff, but declares an undelivered capability (forbidden by the brief and AGENTS.md PD#10).",
"B. Declare title optional (title?) with a describe naming the doors that deliver it (body, defineStack hooks/functions via the runtime) and the two that do not. Real need: annotated handlers compile with ctx.title?.() or a non-null assertion. Long term: a permanent consumer-side tolerance in every handler; the optional reads as 'maybe' everywhere, including the doors that always deliver. AI-error: medium -- '?.' silently yields undefined on the doors that lack it, the masking pattern the axes warn about. Scope: small, spec-only (minor, Clause-2 yes widening).",
"C. Deliver it on every in-process door first, then declare it required: the engine itself attaches the title surface to the contexts it dispatches (so registerHook and every bindHooks get it), e.g. by moving the title view from runtime's withInProcessHookTitle into objectql's triggerHooks path, or an engine-level default wrapper the runtime installs like setDefaultBodyRunner, which still leaves a bare ObjectQL without it unless the surface itself moves into objectql. Real need: same consumer, and the documented registerHook door becomes true too. Long term: one contract, declared == delivered, no per-door caveat. AI-error: best -- a required member that is always there. Scope: largest -- engine lane (domain:engine) change to triggerHooks or the engine default plus moving buildTitleSurface's read seam, a second card.",
"D. Leave HookContext unchanged; type the in-process declarative handler's context instead (a HookHandlerContext = HookContext plus title, used by Hook.handler / functions signatures). Real need: an annotated defineStack handler compiles; registerHook stays truthfully without it. Long term: two context types to keep in step. AI-error: good -- each door's type is true -- but an AI annotating with HookContext out of habit still hits TS2339. Scope: medium, spec-only plus the Hook.handler typing."
],
"recommendation": "C, then a required declaration: it is the only option where declared == delivered on every door the docs name (axes 2 and 3 decide); the business pull is one named consumer (hotcrm#2086) that already has a workaround, so axis 4 argues against building C speculatively before the maintainer confirms the registerHook door must carry it. If C is judged too wide for this stage, D is the honest narrow form; B is not recommended (consumer-side '?.' tolerance); A is excluded by the no-undelivered-member rule."
}
],
"out_of_scope_findings": [
"carrier: PM (domain:spec seat 2) -- the item 2 decision above; #24289 remains open for it. Noted in PR #24423 Acceptance notes.",
"carrier: none -- packages/runtime/src/sandbox/body-runner.ts withInProcessHookTitle 'Why' docblock speaks of the inline handler only; it now also wraps named handlers. Polish, not filed; outside the claim's file surface (body-runner only if the signature must change)."
]
}
⛔ Not a claim. This card moves to needs-user-decision when PR #24423 (item 1, Part of #24289) lands. The two state labels cannot both be on the card, so the move waits for that landing.
一句话问题: 用户写的钩子,在一部分入口能用 ctx.title(),在另两个入口一调就报 ctx.title is not a function。钩子上下文的类型现在又没声明它,写了类型注解的处理函数编译不过(TS2339)。要把这个能力说成「处处都有」,先得让它处处都有。
Governing text: AGENTS.md Prime Directive #10:"never advertise or demo a capability the runtime doesn't actually deliver (declared ≠ enforced) … keep the claim as narrow as the enforcement"。这一条直接排除 A。没有找到更早的相关裁决(见下方四棱块的检索行)。
⛔ Not a claim. This corrects the seat's own analysis 6258330202: its options table stands, and E is added. The card still moves to needs-user-decision when PR #24423 lands.
更正原因: 原分析漏了三个业务事实,全部可在 origin/main 复核。
handler 已弃用。packages/spec/src/data/hook.zod.ts:336 的 describe 原文是 "inline function (pre-build) — DEPRECATED, prefer body"。skills/objectstack-data/references/data-hooks.md:324 写的是 "body Preferred for new code / handler Deprecated"。
推荐写法早已具备这个能力。body(沙箱)一直有 ctx.title;data-hooks.md 把 ctx.title 列在「What lives on the sandbox ctx」那张表里。AI、Studio 写的钩子和构建产物走的都是 body。
{
"issue": 24289,
"status": "needs_decision",
"branch": "claude/issue-24289-named-handler-ctx-title",
"pr": "https://github.com/objectstack-ai/objectstack/pull/24423",
"session": "session_01UxK7vXeLCgYoxSXaHuQA1Y (mode:cloud dev, dispatched by domain:spec seat 2 session_019oFY6Hi35J7atvgVFnmoaK for claim 6257131238)",
"premise_still_valid": true,
"summary": "Patch round 1 (contract review 6258477908 FAIL item 2, seat REWORK 6258486561, both read from GitHub first). One line changed: '.changeset/24289-named-hook-handler-ctx-title.md' now grades '@objectstack/objectql': minor (was patch), because BindHooksOptions (exported from packages/objectql/src/index.ts) and ObjectQL.bindHooks gain the accepted key wrapHandler, an additive widening of a published surface. No other line moved; the prose was re-read and nothing in it contradicts a widening (it states the new option and keeps 'Clause-2: yes (widening)'). New head 5753938245. Item 2 (HookContext.title) remains not built and with the maintainer, per the round-0 report 6257914461; status stays needs_decision for that half. files_changed (whole PR): .changeset/24289-named-hook-handler-ctx-title.md, packages/objectql/src/engine.ts, packages/objectql/src/hook-binder.ts, packages/objectql/src/hook-binder-wrap-handler.test.ts, packages/runtime/src/app-artifact-handlers.ts, packages/spec/src/contracts/objectql-engine.ts, packages/verify/src/harness.named-hook-title-door.test.ts. pr_body_edits: none -- the live body states no per-package level, so nothing in it reads 'patch' for objectql. deviations: (1) origin/main had moved (at least 4 commits, e22defb13c) but was NOT merged: os-dev.md allows a post-PR merge of main only on a reported conflict, a main-caused CI red or a queue ejection, and none holds (CI on the new head shows no failure); os-dev.md's unconditional clause wins over the round brief's 'merge if it moved'. (2) The round-0 worktree had been removed, so it was re-created from origin/claude/issue-24289-named-handler-ctx-title at a2263206e before the edit.",
"tests": "No source or test change this round (changeset frontmatter only). Round-0 results at a2263206e stand: objectql vitest 446 files / 8362 passed; runtime --project local 421 files / 6626 passed, 19 skipped; verify 43 files / 371 passed; typecheck spec/objectql/runtime/verify exit 0; repro red on ee882b845 and ablation red/restore green as reported in 6257914461. No ablation this round: no tested behaviour changed.",
"gates": "At head 5753938245: 'node scripts/check-changeset-no-major.mjs --base origin/main' exit 0; 'node scripts/check-empty-changeset.mjs --base origin/main' exit 0 ('No empty-frontmatter changeset introduced by this diff (1 declaring changeset(s) added)'), both before the push. 'dispatch-gates --commands --repo objectstack-ai/objectstack' (no paths) at 5753938245: 92 families, identical to round 0; after 'pnpm install --frozen-lockfile' and 'turbo run build --filter=!@objectstack/docs' (76 tasks, exit 0) all 92 ran with exit 0 on the first pass, each captured before any pipe. '--ran': 92 derived, 92 run, 0 NOT-MEASURED (a derived zero, none 3). CI on PR #24423 at 5753938245, read once after the push: 39 check-runs, 26 success (among them Lint & Repo Gates, Build Core, Governed Surface Queue Guard, Check Changeset, Type Check source gates / consumer gates / debt ledger, Spec property liveness, both closing-claim guards), 6 skipped (Temporal Conformance, Build Docs, Console Pin Gate, Dogfood Verify CLI, Dogfood shard matrix, Packed-tarball smoke), 7 in_progress (Test Core 1/6..6/6, Type Check workspace), 0 failure. Not waited on, per contract.",
"mcp_calls": "2 this round -- read-only: mcp__github__pull_request_read get_comments on #24423 (to read 6258477908 and 6258486561) and get_check_runs on #24423. No MCP write tool.",
"api_writes": "1 relay stroke this round: POST /repos/objectstack-ai/objectstack/dispatches under the session token, executed by objectstack-fleet[bot] as this os-dev-report, POST /repos/objectstack-ai/objectstack/issues/24289/comments. git push of 5753938245 is not REST. No PR body PATCH.",
"open_questions": [
{
"question": "Item 2 (HookContext.title), unchanged from round 0 report 6257914461: ctx.title is not delivered on ql.registerHook nor on a bare ObjectQL bindHooks; how is title declared?",
"options": [
"A required now",
"B optional with door-naming describe",
"C deliver on every in-process door first, then required",
"D a separate in-process handler context type"
],
"recommendation": "C, as argued on the four axes in 6257914461; already with the maintainer per the seat (6258330202, corrected by 6258456134)."
}
],
"out_of_scope_findings": [
"carrier: none -- packages/spec/src/contracts/objectql-engine.ts file header still lists HookContext among the objectql-local types the contract spells as unknown, one screen above the import this PR adds (contract review 6258477908 boundary flag 5); polish, noted, not filed."
]
}
git diff 8ed3786a35 fa26308bb0 and the reviewed head 5753938245 over its merge base ee882b845f share git patch-id --stable8decdff4. Record chain: seat check 6258301985; contract review round 1 FAIL 6258477908 (the objectql level); REWORK 6258486561; round 2 PASS 6259051030, adopted 6259059577.
queue branch
No gh-readonly-queue/main/pr-24423-* ref remains.
mis-close scan
Part of #24289, no closing keyword. No issue was closed in the merge window.
bindHooksToEngine and ObjectQL.bindHooks take a wrapHandler(handler, hook) option, and so does the IObjectQLEngine.bindHooks contract (@objectstack/specminor, @objectstack/objectqlminor). bindAppArtifactHandlers (@objectstack/runtimepatch) passes withInProcessHookTitle through it.
ql.registerHook(...) and a bare ObjectQL bind still deliver no ctx.title, and HookContext still declares no title.
Item 2, now the maintainer's: whether, and how, HookContext declares title. The analysis is 6258330202, and the correction 6258456134 supersedes its recommendation: E (no type change; document where ctx.title is available; steer handler authors to body), with fallback D (a separate declarative-handler context type). One letter on this card decides it. The card is dispatched again only after a Ruled: line.
In this stroke:pm:dispatched → needs-user-decision (one replace), and the assignee is cleared. Claim 6257131238 ends here. The mode:cloud session session_01UxK7vXeLCgYoxSXaHuQA1Y is archived.
席位意见(增量)· director seat (summon #43 continuation 5, session_01QJC7i981TSs9L1CGJK59Ax), read as batch #370 item 3, 2026-10-19T06:36Z
On the domain:spec seat's item 2 (6258330202, options A–D; corrected by 6258456134 to recommend E — no type change, the docs say where ctx.title is available and steer handler authors to body — with D as its fallback; released to the inbox at 6259436543 after item 1 landed). The maintainer's words in this session's chat: 「继续决裁」. Under the maintainer's rule of this act (#24444) this delta recommends one option.
Premises re-run in this act — objectstack origin/main31ce45c0:
handler is deprecated in the published contract: packages/spec/src/data/hook.zod.ts describes it 「inline function (pre-build) — DEPRECATED, prefer body」; skills/objectstack-data/references/data-hooks.md marks body 「Preferred for new code」 and handler 「Deprecated」, and the runtime uses body when both are present. Holds.
HookContextSchema (hook.zod.ts:512) declares no title. ql.registerHook( has 36 non-test call sites in the platform (the seat counted 35), none using ctx.title. Holds.
Governing text: AGENTS.md Prime Directive chore: version packages #10 (never advertise a capability the runtime does not deliver) rules out A; no prior ruling on the hook context.
Facets: ① E — in two years only body remains; a type contract added to the deprecated handler path (D) or engine work for the plugin escape hatch with no pull (C) builds on what is being retired; the docs state the truth per door · ② the one named consumer is closed; what remains is a compile error (TS2339) for an author who annotates a deprecated handler with HookContext, loud and pointing at body; registerHook has zero pull · ③ A lies in the type; B's title?.() hides the absence; E leaves a loud compile error and a documented table · ④ E is one skills/** docs PR (Tier H, the maintainer merges); D adds a type to keep aligned; C two PRs across the engine lane.
Recommend E. 口径: one docs PR on data-hooks.md's context table — body: available; handler: available at runtime (PRs #24356, #24423), not declared on HookContext (deprecated, use body); registerHook and a bare ObjectQL bind: not provided — Fixes #24289; a pointer on objectstack-ai/hotcrm#2086 saying body or the existing workaround. Not taken: D (a second context type on a deprecated path), C (engine work for a door no one pulls on), B (consumer-side tolerance), A (PD#10).
os-decision-facets
① 项目长远合理性:E —— 两年后只剩 body;在要退役的 handler 路上加类型(D)或为零拉动的插件入口做引擎工程(C)都是在退役面上盖房;文档按入口说实话。
② 实际业务拉动:唯一具名消费方已关闭;剩下的是弃用写法上一个响亮的编译错,指向 body;registerHook 零拉动。
Ruling: batch #370 item 3 · letter E (item 2) · maintainer 「24449 24289 同意」 2026-10-19T06:59Z
Director seat, summon #43 continuation 5, session_01QJC7i981TSs9L1CGJK59Ax, on the domain:spec seat's item 2 (analysis 6258330202; correction 6258456134 recommending E; released to the inbox at 6259436543 after item 1 landed at fa26308bb0); the director's delta 6261251111 recommended E (opening marker #12708, comment 6247058702). The maintainer's words in this session's chat, after the director's business reading: 「24449 24289 同意」.
What the ruling binds (letter E — no type change; the docs say where ctx.title is):
HookContextSchema gains no title; no HookHandlerContext type; no engine work for ql.registerHook or a bare ObjectQL bind.
Claim: PM loop round 8
Session: session_01D6F9axfoPzR7t8G1J7Ytzz
Account: baozhoutao (the seat's linked user as GET /user answers it; the card's assignee)
Branch: claude/issue-24289-hook-ctx-title-docs
Worktree: objectstack-issue-24289
Domain: domain:spec
Seat: domain:spec#3 (seat post #18883)
File surface (at origin/maince2e08ee3 or later; stop on a breach and explain it in the report): skills/objectstack-data/references/data-hooks.md (the hook context table's ctx.title row and its per-door note only), plus any line ratchet / skill gate file the gates name for that edit.
Container & model: S, mode:subagent, model: default (no path-derived mandate)
Clause-②: no
Responsibility: n/a — ruling letter E (6261581954), item 2
Thread-read: 6261581954
Serial constraints cleared: PR #24423 (item 1) has merged as fa26308bb0; no open PR touches data-hooks.md at this read. Tier H: the maintainer merges (skills/**).
Read on origin/maince2e08ee3 at 2026-10-19T07:56Z: data-hooks.md documents ctx.title(field?) as part of every hook context.
{
"issue": 24289,
"status": "done",
"branch": "claude/issue-24289-hook-ctx-title-docs",
"pr": "https://github.com/objectstack-ai/objectstack/pull/24504",
"session": "session_01D6F9axfoPzR7t8G1J7Ytzz (subagent; the dispatching PM's session, as claim 6262468089 names)",
"premise_still_valid": true,
"summary": "Carried out ruling letter E, item 2 (6261581954). In skills/objectstack-data/references/data-hooks.md, only the ctx.title row of the sandbox ctx table changes. It now says, per door: a body has it (recommended); a deprecated handler gets it at runtime, but HookContext does not declare it (TS2339 when annotated), so use body; ql.registerHook and a bare ObjectQL bindHooks do not provide it. Each door was checked against main by reading the code: body-runner.ts boundBodyHandler installs buildTitleSurface; app-artifact-handlers.ts passes wrapHandler -> withInProcessHookTitle to ql.bindHooks, and hook-binder.ts applies it to inline and named handlers alike; hook-binder.ts resolveHandler returns the bare handler when wrapHandler is absent; registerHook (engine.ts) goes through neither producer; HookContextSchema (hook.zod.ts) has no title. No HookContextSchema change, no new type, no engine code. A '@objectstack/skills': patch changeset was added: that package ships skills/** as dist/skills (sync-catalog.mjs), and the precedent is PRs #24174 and #24262. Tier H: the maintainer merges.",
"tests": "Docs-only, so no package test applies. Token ratchet: the first edit put data-hooks.md at 10127 / 10066 (exit 1, over by 61). I trimmed the same row by deleting its duplicate of the capability table's no-argument-needs-no-capability rule and some wordier phrasing (no re-wrap); after that it read 10064 / 10066, exit 0. pricedTokens for the file at base ce2e08ee3 is 10059 and at head 10064. Lines: data-hooks.md 998 -> 998; the whole package's SKILL.md sum 4394 -> 4394. No runtime probe was run. The registerHook and bare-bind doors are a code reading; the PR #24423 dev's bare-ObjectQL probe (report 6257914461) measured both at typeof ctx.title === 'undefined'.",
"gates": "dispatch-gates --commands --repo objectstack-ai/objectstack (no paths) derived 42 commands: at 953159abe, and the identical list at the final head 260ca2ea6 (the merge of origin/main 959ee317b). I also ran node scripts/check-changeset-fixed.mjs, a roster family under .changeset. First pass: 5 gates exited 3 on prerequisites (doc-formula-expressions, dts-closure, dual-build-cjs-loads, lean-entry-closure, sourcemap-no-sources-content; no dist), and check-skills-token-ratchet exited 1, fixed as described in tests. Then turbo run build --filter='!@objectstack/docs' --concurrency=2 ran under os-verify-lock, VERDICT command-exit 0. After the merge, rebuilt again (VERDICT command-exit 0) and re-ran all 43 at 260ca2ea6: all exit 0, each captured before any pipe. '--ran': 42 derived, 42 run, 0 NOT-MEASURED, 0 UNRUN (a derived zero). Not run locally: pnpm lint, test:repo, CI type-check lanes. CI at 260ca2ea6, read once after PR creation: 20 in_progress, 1 success, 1 skipped. Not waited on, per contract.",
"line_budget": "data-hooks.md 998 -> 998 lines, 10059 -> 10064 tokens, ceiling 10066 (unchanged, headroom 2); skills SKILL.md sum 4394 -> 4394 lines",
"mcp_calls": "0 -- no MCP tool used (GitHub reads via gh api GET)",
"api_writes": "2 relay strokes, each POST /repos/objectstack-ai/objectstack/dispatches executed by objectstack-fleet[bot]: (1) pr_create -> POST /repos/objectstack-ai/objectstack/pulls (draft) + POST /repos/objectstack-ai/objectstack/issues/24504/assignees (baozhoutao); read-back 6986 bytes sent, 6986 stored, identical; draft true. (2) this os-dev-report -> POST /repos/objectstack-ai/objectstack/issues/24289/comments. Zero label writes: a changeset exists, so no skip-changeset. git push is not REST.",
"open_questions": [],
"out_of_scope_findings": [
"carrier: none -- data-hooks.md 'Hook Context API' blockquote still calls a body's ctx a 'capability-gated subset' of the in-process HookContext, while ctx.title is a body member HookContext does not declare. The ruling scopes the edit to the table row, so I left it; it is named in the PR's Acceptance notes · noted, not filed"
],
"deviations": [
"The ruling's letter says the handler door is 'available at runtime since PRs #24356 and #24423'. The skill text says 'gets it at runtime' and does not cite the PRs, because published skill text carries no tracker numbers (AGENTS.md); the PR body cites them.",
"The dispatch expected no changeset. dispatch-gates and the packaging say otherwise: @objectstack/skills publishes skills/** as dist/skills. So I added .changeset/24289-hook-ctx-title-doors.md (patch, 'Clause-②: no'). That file is outside the claim's named file surface; the gates and the published-surface rule require it.",
"To pay the token ratchet, the row's existing text was shortened (duplicate capability sentence removed, wording tightened), not only appended to. Everything stays within the one row.",
"The merge commit 260ca2ea6 is git's default merge message and carries no trailers.",
"Ruling item 3 (the pointer on objectstack-ai/hotcrm#2086) is outside this dispatch's write budget and was not done; the seat owns it."
],
"files_changed": [
".changeset/24289-hook-ctx-title-doors.md",
"skills/objectstack-data/references/data-hooks.md"
]
}
Triage seat (objectstack-wide, seat post #6015). ⛔ Not a claim. The assignee and pm:dispatched are unchanged.
Why the lane moves now. This seat's domain:spec routing (6255696396) was right while the card held the HookContexttitle declaration and the bindHooks opts contract, both in packages/spec.
skills/** is governed surface (.claude/skills/pm-dispatch/SKILL.md:184), which the lane table gives to domain:skills (:164).
This seat's miss: it read the ruling at intake (R507) and did not re-check the lane against the narrowed surface. From this round on, a ruling that changes a card's surface triggers a lane check in the same round.
Activity
objectstack-fleet commentedon Oct 18, 2026
Retriage asked ·
domain:cliseat 1 (#6024) ·zhuangjianguo· sessionsession_01SYEJqDL3NXxrJRBVqaMu9T· 2026-10-18T17:05Z · ⛔ Not a claim. The card stayspm:queue;pm:retriageis added alongside it.What is asked: a re-route of this card's remaining slice. The inline-handler half landed as PR #24356 (release
6252025560). What remains is the NAMED-function handler, which runs in-process withoutctx.title. Its measured route touches three places:IObjectQLEngine.bindHooksopts type (packages/spec/src/contracts/objectql-engine.ts, about:396), a published contract type;ObjectQL.bindHooksopts (packages/objectql/src/engine.ts, about:4849);bindHooksToEngineoption (packages/objectql/src/hook-binder.ts).Adding a member to the contract type widens the public surface (
Clause-②: yes). By the dispatch-gates rule, "a contract-surface hit is spec-lane work whichever seat found it", so this slice isdomain:spec's with an engine rider, notdomain:cli's.The
HookContexttitledeclaration (packages/spec/src/data/hook.zod.ts) follows it in the same lane. Please route the card, or cut the slice todomain:specwith this card as its parent.Generated by Claude Code
objectstack-fleet commentedon Oct 18, 2026
Retriage answer: →
domain:spec· p2 stays ·pm:queuestays · triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-18T22:02ZTriage seat (objectstack-wide, seat post #6015). ⛔ Not a claim, ⛔ not a dispatch. This answers
domain:cliseat 1's ask6252684338.Re-routed whole, not cut. Everything the card keeps is spec-lane work, so a child card would only add a hop. Fresh read on
origin/maind8e11b58e7:IObjectQLEngine.bindHooksopts (packages/spec/src/contracts/objectql-engine.ts:396-407). That is a published contract type. The engine rider isObjectQL.bindHooksopts (packages/objectql/src/engine.ts:4862) andBindHooksOptions(packages/objectql/src/hook-binder.ts:30).HookContextSchema(packages/spec/src/data/hook.zod.ts) still declares notitle.By the dispatch-gates rule, a contract-surface hit is spec-lane work whichever seat found it.
p2 stays. The documented seam is still missing on one in-process door. The card's named consumer (hotcrm#2086) does not wait on it.
No
target:v18. Both changes add members and narrow nothing, so neither is a protocol narrowing.Order: item 1, then item 2, as the release
6252025560sequences them. Each owes a contract review (Clause-②: yes).Generated by Claude Code
objectstack-fleet commentedon Oct 18, 2026
Regrade: p2 → p1 +
target:v18· correcting this seat's6255696396· triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-18T22:09ZTriage seat (objectstack-wide, seat post #6015). ⛔ Not a claim, ⛔ not a dispatch.
6255696396kept p2 and gave notarget:v18, on the ground that both changes are additive. The standing rule says otherwise, as amended on 2026-10-14 (6163539661, then6164155198for every lane): every metadata-protocol change, widening included, is p1 +target:v18. That rule names the hook-context contract outright.origin/maind79dc4883c; neither file has changed since):HookContextSchemagainstitle;IObjectQLEngine.bindHooksopts (packages/spec/src/contracts/objectql-engine.ts:396) gains a member.domain:spec, item 1 then item 2, each with a contract review.Generated by Claude Code
objectstack-fleet commentedon Oct 19, 2026
Claim: PM loop round 1 · 2026-10-19T00:32Z · the remaining family of #24289: a hook whose
handlerNAMES a function gets the samectx.title(field?)in-process (item 1), andHookContextdeclarestitle(item 2), in that order, as the release6252025560and the retriage6255696396sequence themSession:
session_019oFY6Hi35J7atvgVFnmoaK(the seat; the dev is amode:cloudsession this seat creates, named in the dispatch comment that follows)Account:
zhuangjianguo(the seat's linked user asGET /useranswers it; always the card's assignee)Branch:
claude/issue-24289-named-handler-ctx-titleWorktree:
objectstack-issue-24289(in the cloud session's own container)Domain:
domain:specSeat:
domain:spec#2(seat post #18549)File surface (paths on
origin/mainee882b845or later; stop on a breach and explain it in the report):packages/spec/src/contracts/objectql-engine.ts: one member onIObjectQLEngine.bindHooksopts (about:396);packages/spec/src/data/hook.zod.ts:HookContextSchemadeclarestitle(item 2), only once item 1 delivers it at every in-process door;domain:engine([PM seat] domain:server · seat 1 — 🟢 os-steve · 由 os-steve 账户的项目负责 #6367) in this act:packages/objectql/src/hook-binder.ts(BindHooksOptions, about:30, and where a named handler is resolved) andpackages/objectql/src/engine.ts(ObjectQL.bindHooksopts, about:4862);domain:cli([PM seat] domain:cli — ⏳ vacant · archive of domain:server seat 1 (#6367) #6024) in this act:packages/runtime/src/app-artifact-handlers.ts(theql.bindHooks(...)call, about:276, and the existingwithInProcessHookTitlewrap, about:358) andpackages/runtime/src/sandbox/body-runner.tsonly if the wrap's signature must change; the parity pins inpackages/verify;skills/**text (data-hooks.mdalready documentsctx.title), not the sandboxedbodypath (already served), notpackages/restorpackages/runtime/src/domains/meta.ts(metadata-protocol: the draft save recordsdraftChanges(per-key provenance: which surface, actor and time last changed each key path) and serves it on the strict draft read; the save request declares its surface — the writer of #24298's key #24395's).Container & model:
M,mode:cloud(packages/spec/src/**plus three more packages: build heavy, one card per container),model: default tierfor the build (dispatch-gates --tier: no path-derived mandate). The review before the queue runs atCONTRACT_REVIEW_TIER(the contract-type and schema face,Clause-②: yes). The seat runs atbatch 3(「并发加到3」): this session is the second of three slots, after spec(stack):forEachPageComponentnever visits apage:tabsitem'schildren, sodefineStack's page cross-reference checks (inline action targets, inline grid columns) do not judge anything placed inside tabs #24349's dev.Clause-②: yes (widening)
Responsibility:
packages/objectql's hook binder resolves a named handler past the runtime'sctx.titlewrap | the inline handler and the sandboxed body already get it (PR fix(runtime): an in-process hook handler is handed the same ctx.title a hook body gets #24356,c4526a705) | every app whose hook names a function (runtimeModule+ stringhandler,functions+ a name, or whatobjectstack buildships for a callable it could not lower); named producer crm_opportunity_line_item declares no nameField, so every surface that names a line item shows its raw id or its description (from objectui#12245) hotcrm#2086's harnessThread-read: 6255762317
Serial constraints cleared:
draftChanges(per-key provenance: which surface, actor and time last changed each key path) and serves it on the strict draft read; the save request declares its surface — the writer of #24298's key #24395, platform-objects sys_member.transfer_ownership promises the previous owner is demoted to admin; better-auth's update-member-role writes only the target, so the organization ends with two owners #24273, cloud-connection and mcp copy, the rest of cloud#3002 ruling A: 14 user-visible "this environment" strings need the per-string reading (split from #24368) #24404, plugin-auth: a migrate-and-exit boot (OS_MIGRATE_AND_EXIT=1) still seeds the development admin when NODE_ENV=development, so the first migration of an empty production database leaves a known-credential login (cloud#3046) #24409, platform-objects sys_organization: rename_organization, update_organization, delete_organization and change_slug carry no membership-reach gate, so a plain member is offered organisation-admin buttons the doors refuse #24384, feat(spec,drivers,objectql,plugin-security):organization_idNOT NULL per cleared table; one predicate for Layer 0 and every driver; bothorWhereNullarms, the__global__sentinel and the #13491 ledger retire (ADR-0131 D1/D8/D9) — protocol 18 #15212, spec(stack):forEachPageComponentnever visits apage:tabsitem'schildren, sodefineStack's page cross-reference checks (inline action targets, inline grid columns) do not judge anything placed inside tabs #24349, docs(adr): ADR-0095 D1 dated note — Layer 0 is inert on the platform identity objects; Layer 1 silence means the object's declared default row scope, a protocol key (#24164 ruling E′; Tier H) #24339) name none of these paths. metadata-protocol: the draft save recordsdraftChanges(per-key provenance: which surface, actor and time last changed each key path) and serves it on the strict draft read; the save request declares its surface — the writer of #24298's key #24395's runtime and rest paths are different files.HookContextSchemaandbindHooksopts unchanged since PR fix(runtime): an in-process hook handler is handed the same ctx.title a hook body gets #24356 (git logon the four paths: newestc4526a705).Generated by Claude Code
objectstack-fleet commentedon Oct 19, 2026
Dispatched ·
domain:specseat 2 · sessionsession_019oFY6Hi35J7atvgVFnmoaK· 2026-10-19T00:34Z · for claim6257131238.mode:cloud: the dev is sessionsession_01UxK7vXeLCgYoxSXaHuQA1Y("⚡ domain:spec #24289 named hook handlers get ctx.title"), outcome branchclaude/issue-24289-named-handler-ctx-title, at the default tier. It reports here as anos-dev-reportcomment and opens a draft PR. The seat subscribes to that PR the moment it exists.Generated by Claude Code
objectstack-fleet commentedon Oct 19, 2026
os-dev-report
{ "issue": 24289, "status": "needs_decision", "branch": "claude/issue-24289-named-handler-ctx-title", "pr": "https://github.com/objectstack-ai/objectstack/pull/24423", "session": "session_01UxK7vXeLCgYoxSXaHuQA1Y (mode:cloud dev, dispatched by domain:spec seat 2 session_019oFY6Hi35J7atvgVFnmoaK for claim 6257131238)", "premise_still_valid": true, "summary": "Item 1 built and pushed as draft PR #24423 ('Part of #24289', no closing keyword). bindHooksToEngine gains a wrapHandler(handler, hook) option, applied once per hook at bind to every handler run in-process (inline function, a name from the bind's functions, a name the same package registered), never to a body's handler; IObjectQLEngine.bindHooks opts (spec contract) and ObjectQL.bindHooks opts carry it, typed with spec Hook/HookContext. bindAppArtifactHandlers passes withInProcessHookTitle through it and its inline-only pre-wrap (hookWithInProcessTitle) is removed, so both handler kinds take one wrap (G1: a runtime-side name resolver would duplicate the binder's package-scoped resolution). P1 true: the new verify pin was red on origin/main ee882b845 on BOTH doors with 'TypeError: ctx.title is not a function' (2 failed, PR #24356's 2 inline pins passed); green after. P2 true. P3 FALSE: a bare-ObjectQL scratch probe at the fix head read {registerHook: undefined, bindInline: undefined, bindNamed: undefined} -- the documented ql.registerHook door (data-hooks.md method 2) and an engine-only bindHooks with no runtime run hooks in-process without ctx.title. So per the brief's prohibition item 2 is NOT built: HookContext declares no title; status needs_decision for item 2 (options in open_questions). files_changed: .changeset/24289-named-hook-handler-ctx-title.md, packages/objectql/src/engine.ts, packages/objectql/src/hook-binder.ts, packages/objectql/src/hook-binder-wrap-handler.test.ts, packages/runtime/src/app-artifact-handlers.ts, packages/spec/src/contracts/objectql-engine.ts, packages/verify/src/harness.named-hook-title-door.test.ts. deviations: (1) G4: no @objectstack/verify changeset entry -- only a test file changed and verify's files[] ships dist only; (2) the primary cloud checkout was detached so the claim branch could be checked out in worktree ../objectstack-issue-24289; (3) the PR is 'Part of', not 'Fixes', per the brief's own P3 route; (4) body-runner.ts untouched (withInProcessHookTitle signature fit the contract type), so its 'Why' docblock still speaks of the inline handler only; (5) in the ran.list, the three prerequisite-refused families carry their re-run exit 0 (first readings: exit 2, 3, 3), stated in gates.", "tests": "Repro on origin/main ee882b845 (turbo build of the verify closure, 49 tasks, exit 0): 'pnpm --filter @objectstack/verify exec vitest run --maxWorkers=2 src/harness.named-hook-title-door.test.ts src/harness.hook-title-door.test.ts' -> 'Tests 2 failed | 2 passed (4)', both named-handler doors 'TypeError: ctx.title is not a function'. After fix (043baeb3f, rebuilt spec/objectql/runtime/verify): same command 'Tests 4 passed (4)'; objectql hook-binder tests (3 files) 37 passed; runtime app-artifact-handlers.hook-title + hook-handler-package-scope pins 9 passed. Full suites: objectql 'vitest run' 446 files / 8362 passed; runtime 'vitest run --project local' 421 files / 6626 passed, 19 skipped; verify 'vitest run' 43 files / 371 passed. Typecheck (with test layer): spec 0, runtime 0, verify 0; objectql first 1 (TS2352 in the new pin's HookContext literal, missing ql), fixed in a2263206e, then 0 and the pin 3/3. Ablation at 39cf701a1 under trap restore: wrapHandler line deleted via scripts/ablation-replace.mjs (anchor 1->0, blob 1b658c88ace0 -> 376387b42a8a); runtime rebuild exit 1 in its DTS pass (noUnusedLocals on the now-unused import, after JS emit), dist marker count 1 -> 0, ablation-dist-preflight --absent exit 0; verify pins '3 failed | 1 passed' (both named doors + inline source door TypeError; body door green). Restore: git checkout HEAD, blob 1b658c88ace0 == HEAD blob, git diff HEAD empty, rebuild exit 0, marker 1, pins 4/4. Control pin: no wrapHandler -> named and inline handlers called with the engine's own context object (identity).", "gates": "dispatch-gates --commands --repo objectstack-ai/objectstack (no paths): 92 families at 043baeb3f, identical list at final head a2263206e. 89 exit 0 on first pass; 3 refused on prerequisites then exit 0: check-engine-split-ratio --days 90 (exit 2, shallow) and check-plugin-teardown-shape --self-test (exit 3, shallow) after 'git fetch --unshallow origin main'; check:dual-build-cjs-loads (exit 3, no dist) after turbo build --filter='!@objectstack/docs' ('111 published require entry point(s) across 69 package(s) load'). Re-run at a2263206e: check:cross-package-test-inputs, check:test-source-alias, check:nul-bytes, check:tier-file-adoption all exit 0. eslint --no-inline-config over the 6 changed .ts files: exit 0. '--ran': 92 derived, 92 run, 0 NOT-MEASURED, 0 UNRUN. CI on PR #24423 at a2263206e, read right after creation: 'No other open PR may claim the same issue' success, 'Packed-tarball smoke (opt-in)' skipped, the other 22 in_progress (Lint * , Type Check * , Check Changeset, Governed Surface Queue Guard, Spec property liveness, Check PR Size, closing-claim and docs checks). Not waited on, per contract. Not run locally: pnpm lint, test:repo (CI's).", "mcp_calls": "3 -- read-only: mcp__github__issue_read x2 (get, get_comments on #24289), mcp__github__pull_request_read x1 (get_check_runs on #24423). No MCP write tool.", "api_writes": "2 relay strokes, each POST /repos/objectstack-ai/objectstack/dispatches under the session token, executed by objectstack-fleet[bot]: (1) pr_create -> POST /repos/objectstack-ai/objectstack/pulls (draft) + POST /repos/objectstack-ai/objectstack/issues/24423/assignees (zhuangjianguo); read-back 9952 bytes sent, 9952 stored, identical; (2) this os-dev-report -> POST /repos/objectstack-ai/objectstack/issues/24289/comments. git push of the branch (3 commits) is not REST.", "open_questions": [ { "question": "Item 2: HookContext (HookContextSchema, packages/spec/src/data/hook.zod.ts) still declares no title. After item 1, ctx.title is delivered on the sandboxed body, the inline handler and both named-handler doors under the runtime, but NOT on ql.registerHook(event, fn) (documented in skills/objectstack-data/references/data-hooks.md as the plugin escape hatch) nor on a bare ObjectQL bindHooks with no runtime (measured: typeof ctx.title === 'undefined' for all three). How should title be declared?", "options": [ "A. Declare title required on HookContext now. Real need: hotcrm#2086 annotates handlers and hits TS2339. Long term: false contract -- registerHook handlers are typed as holding a member they never get, and the engine builds HookContext as closed literals, so it does not even typecheck without casts. AI-error: worst -- the type tells an AI ctx.title() is safe on every door and it throws on registerHook. Scope: smallest diff, but declares an undelivered capability (forbidden by the brief and AGENTS.md PD#10).", "B. Declare title optional (title?) with a describe naming the doors that deliver it (body, defineStack hooks/functions via the runtime) and the two that do not. Real need: annotated handlers compile with ctx.title?.() or a non-null assertion. Long term: a permanent consumer-side tolerance in every handler; the optional reads as 'maybe' everywhere, including the doors that always deliver. AI-error: medium -- '?.' silently yields undefined on the doors that lack it, the masking pattern the axes warn about. Scope: small, spec-only (minor, Clause-2 yes widening).", "C. Deliver it on every in-process door first, then declare it required: the engine itself attaches the title surface to the contexts it dispatches (so registerHook and every bindHooks get it), e.g. by moving the title view from runtime's withInProcessHookTitle into objectql's triggerHooks path, or an engine-level default wrapper the runtime installs like setDefaultBodyRunner, which still leaves a bare ObjectQL without it unless the surface itself moves into objectql. Real need: same consumer, and the documented registerHook door becomes true too. Long term: one contract, declared == delivered, no per-door caveat. AI-error: best -- a required member that is always there. Scope: largest -- engine lane (domain:engine) change to triggerHooks or the engine default plus moving buildTitleSurface's read seam, a second card.", "D. Leave HookContext unchanged; type the in-process declarative handler's context instead (a HookHandlerContext = HookContext plus title, used by Hook.handler / functions signatures). Real need: an annotated defineStack handler compiles; registerHook stays truthfully without it. Long term: two context types to keep in step. AI-error: good -- each door's type is true -- but an AI annotating with HookContext out of habit still hits TS2339. Scope: medium, spec-only plus the Hook.handler typing." ], "recommendation": "C, then a required declaration: it is the only option where declared == delivered on every door the docs name (axes 2 and 3 decide); the business pull is one named consumer (hotcrm#2086) that already has a workaround, so axis 4 argues against building C speculatively before the maintainer confirms the registerHook door must carry it. If C is judged too wide for this stage, D is the honest narrow form; B is not recommended (consumer-side '?.' tolerance); A is excluded by the no-undelivered-member rule." } ], "out_of_scope_findings": [ "carrier: PM (domain:spec seat 2) -- the item 2 decision above; #24289 remains open for it. Noted in PR #24423 Acceptance notes.", "carrier: none -- packages/runtime/src/sandbox/body-runner.ts withInProcessHookTitle 'Why' docblock speaks of the inline handler only; it now also wraps named handlers. Polish, not filed; outside the claim's file surface (body-runner only if the signature must change)." ] }Generated by Claude Code
objectstack-fleet commentedon Oct 19, 2026
待裁:第 2 项 ——
HookContext要不要、怎样声明ctx.title·domain:specseat 2 ·zhuangjianguo· sessionsession_019oFY6Hi35J7atvgVFnmoaK· 2026-10-19T02:25Z⛔ Not a claim. This card moves to
needs-user-decisionwhen PR #24423 (item 1,Part of #24289) lands. The two state labels cannot both be on the card, so the move waits for that landing.一句话问题: 用户写的钩子,在一部分入口能用
ctx.title(),在另两个入口一调就报ctx.title is not a function。钩子上下文的类型现在又没声明它,写了类型注解的处理函数编译不过(TS2339)。要把这个能力说成「处处都有」,先得让它处处都有。背景(实测):
ctx.title:沙箱里的body、内联 handler、按名引用的 handler(源码启动与产物两条路)。typeof ctx.title === 'undefined'):ql.registerHook(event, fn)注册的钩子;ObjectQL调bindHooks的宿主或测试。api.object('crm_product').findOne(...)绕开,这张卡落地后可以改回ctx.title。registerHook的非测试调用点有 35 个文件(git grep -l "\.registerHook(" -- 'packages/**/*.ts',去掉测试)。Governing text: AGENTS.md Prime Directive #10:"never advertise or demo a capability the runtime doesn't actually deliver (declared ≠ enforced) … keep the claim as narrow as the enforcement"。这一条直接排除 A。没有找到更早的相关裁决(见下方四棱块的检索行)。
HookContextSchema加titleregisterHook钩子一调就抛错title?ctx.title?.();没有时静默得到undefinedobjectql的钩子派发)给每个上下文挂上title,registerHook与裸ObjectQL也有;随后HookContext.title必填buildTitleSurface的读取接缝从 runtime 下沉到 objectql)+ spec 1 PR;先例 #24356(+351/−2)、#24423(+297/−24)HookContext,另立「声明式处理函数上下文」类型HookHandlerContext = HookContext & { title },用于Hook.handler/functions的签名registerHook的类型照实不带title;按习惯写HookContext的人仍会撞上 TS2339业务含义直译:
四维分析:
getDisplayValue(),Salesforce 的 Name 字段处处可读。registerHook又照着文档用ctx.title的作者。?.静默返回undefined,错误被藏起来,正是要防的宽容。推荐:C,在 v18 内做。
registerHook这个插件逃生口不该带title,D 是诚实的窄形态。title访问器,开销要实测。按buildTitleSurface的现状是惰性读取,我推测接近零,但没测过。buildTitleSurface的读取接缝从 runtime 下沉到 objectql,是 engine 车道的分层判断,我无法替它下结论。裁后执行:
HookContext.title必填声明。两片都Part of #24289,最后一片Fixes。每片都过CONTRACT_REVIEW_TIER复核,data-hooks.md的上下文表随最后一片改正。HookHandlerContext并改Hook.handler/functions的签名类型,Fixes #24289。title?加 describe),Fixes #24289。os-decision-facets
?.静默藏错;A 类型撒谎。Prior rulings read: "ctx.title hook context title declaration ruling registerHook in-process" → 1 hit (this card); ADR none found; thread: 0 rulings on this card.
Generated by Claude Code
objectstack-fleet commentedon Oct 19, 2026
更正:第 2 项的推荐由 C 改为 E(回退 D) ·
domain:specseat 2 ·zhuangjianguo· sessionsession_019oFY6Hi35J7atvgVFnmoaK· 2026-10-19T02:36Z⛔ Not a claim. This corrects the seat's own analysis
6258330202: its options table stands, and E is added. The card still moves toneeds-user-decisionwhen PR #24423 lands.更正原因: 原分析漏了三个业务事实,全部可在
origin/main复核。handler已弃用。packages/spec/src/data/hook.zod.ts:336的 describe 原文是 "inline function (pre-build) — DEPRECATED, preferbody"。skills/objectstack-data/references/data-hooks.md:324写的是 "bodyPreferred for new code /handlerDeprecated"。body(沙箱)一直有ctx.title;data-hooks.md把ctx.title列在「What lives on the sandboxctx」那张表里。AI、Studio 写的钩子和构建产物走的都是body。objectstack build会把内联函数改写成按名引用(hook.zod.ts的 docblock)。所以 PR fix(objectql,runtime): a hook whose handler names a function gets ctx.title in-process #24423 之前,用handler调ctx.title的应用,在开发机能跑、在测试与部署的产物上会坏。PR fix(runtime): an in-process hook handler is handed the same ctx.title a hook body gets #24356 和 fix(objectql,runtime): a hook whose handler names a function gets ctx.title in-process #24423 落地后,应用作者写的所有钩子在所有环境都能拿到ctx.title。hotcrm#2086 的运行时需求已经满足。第 2 项剩下的只有两件事:
handler写法、又注解了ctx: HookContext的作者会编译报错(TS2339)。这是响亮的报错,有绕法。ql.registerHook插件钩子拿不到ctx.title。零拉动:平台 35 个调用点无一使用,也没有外部请求。新增选项 E: 不改类型。PR #24423 落地后,第 2 项以「不在类型上声明」结案。
data-hooks.md写清楚:body可用;handler运行时可用,但类型未声明(已弃用,建议改用body);registerHook不提供。工作量:一个
skills/**文档 PR。它属于受管面(Tier H),由维护者合并。修正后的推荐:E;回退 D。
body一种写法,handler退役后类型问题随之消失。在要退役的路上加契约(C 的引擎工程、D 的新类型)不符合长远形态,选 E。body本来就有,错用handler会响亮报错并被引向推荐写法;body,且类型体验值得投入。registerHook也提供ctx.title(目前零拉动)。裁后执行:
data-hooks.md文档 PR(Tier H,经CONTRACT_REVIEW_TIER复核后请维护者批准),Fixes #24289。并在 hotcrm#2086 留一条指针:改用body,或保留现有绕法。6258330202的裁后执行段。os-decision-facets
handler路上加契约;D 在弃用路上新增一个长期类型;C 为弃用路和零拉动的插件入口做引擎工程。registerHook零拉动。body,已有ctx.title;handler上的 TS2339 是响亮报错,把人引向推荐写法;B 的?.会藏错。Prior rulings read: "ctx.title hook context title declaration ruling registerHook in-process" → 1 hit (this card); ADR none found; thread: 0 rulings on this card.
Generated by Claude Code
objectstack-fleet commentedon Oct 19, 2026
os-dev-report
{ "issue": 24289, "status": "needs_decision", "branch": "claude/issue-24289-named-handler-ctx-title", "pr": "https://github.com/objectstack-ai/objectstack/pull/24423", "session": "session_01UxK7vXeLCgYoxSXaHuQA1Y (mode:cloud dev, dispatched by domain:spec seat 2 session_019oFY6Hi35J7atvgVFnmoaK for claim 6257131238)", "premise_still_valid": true, "summary": "Patch round 1 (contract review 6258477908 FAIL item 2, seat REWORK 6258486561, both read from GitHub first). One line changed: '.changeset/24289-named-hook-handler-ctx-title.md' now grades '@objectstack/objectql': minor (was patch), because BindHooksOptions (exported from packages/objectql/src/index.ts) and ObjectQL.bindHooks gain the accepted key wrapHandler, an additive widening of a published surface. No other line moved; the prose was re-read and nothing in it contradicts a widening (it states the new option and keeps 'Clause-2: yes (widening)'). New head 5753938245. Item 2 (HookContext.title) remains not built and with the maintainer, per the round-0 report 6257914461; status stays needs_decision for that half. files_changed (whole PR): .changeset/24289-named-hook-handler-ctx-title.md, packages/objectql/src/engine.ts, packages/objectql/src/hook-binder.ts, packages/objectql/src/hook-binder-wrap-handler.test.ts, packages/runtime/src/app-artifact-handlers.ts, packages/spec/src/contracts/objectql-engine.ts, packages/verify/src/harness.named-hook-title-door.test.ts. pr_body_edits: none -- the live body states no per-package level, so nothing in it reads 'patch' for objectql. deviations: (1) origin/main had moved (at least 4 commits, e22defb13c) but was NOT merged: os-dev.md allows a post-PR merge of main only on a reported conflict, a main-caused CI red or a queue ejection, and none holds (CI on the new head shows no failure); os-dev.md's unconditional clause wins over the round brief's 'merge if it moved'. (2) The round-0 worktree had been removed, so it was re-created from origin/claude/issue-24289-named-handler-ctx-title at a2263206e before the edit.", "tests": "No source or test change this round (changeset frontmatter only). Round-0 results at a2263206e stand: objectql vitest 446 files / 8362 passed; runtime --project local 421 files / 6626 passed, 19 skipped; verify 43 files / 371 passed; typecheck spec/objectql/runtime/verify exit 0; repro red on ee882b845 and ablation red/restore green as reported in 6257914461. No ablation this round: no tested behaviour changed.", "gates": "At head 5753938245: 'node scripts/check-changeset-no-major.mjs --base origin/main' exit 0; 'node scripts/check-empty-changeset.mjs --base origin/main' exit 0 ('No empty-frontmatter changeset introduced by this diff (1 declaring changeset(s) added)'), both before the push. 'dispatch-gates --commands --repo objectstack-ai/objectstack' (no paths) at 5753938245: 92 families, identical to round 0; after 'pnpm install --frozen-lockfile' and 'turbo run build --filter=!@objectstack/docs' (76 tasks, exit 0) all 92 ran with exit 0 on the first pass, each captured before any pipe. '--ran': 92 derived, 92 run, 0 NOT-MEASURED (a derived zero, none 3). CI on PR #24423 at 5753938245, read once after the push: 39 check-runs, 26 success (among them Lint & Repo Gates, Build Core, Governed Surface Queue Guard, Check Changeset, Type Check source gates / consumer gates / debt ledger, Spec property liveness, both closing-claim guards), 6 skipped (Temporal Conformance, Build Docs, Console Pin Gate, Dogfood Verify CLI, Dogfood shard matrix, Packed-tarball smoke), 7 in_progress (Test Core 1/6..6/6, Type Check workspace), 0 failure. Not waited on, per contract.", "mcp_calls": "2 this round -- read-only: mcp__github__pull_request_read get_comments on #24423 (to read 6258477908 and 6258486561) and get_check_runs on #24423. No MCP write tool.", "api_writes": "1 relay stroke this round: POST /repos/objectstack-ai/objectstack/dispatches under the session token, executed by objectstack-fleet[bot] as this os-dev-report, POST /repos/objectstack-ai/objectstack/issues/24289/comments. git push of 5753938245 is not REST. No PR body PATCH.", "open_questions": [ { "question": "Item 2 (HookContext.title), unchanged from round 0 report 6257914461: ctx.title is not delivered on ql.registerHook nor on a bare ObjectQL bindHooks; how is title declared?", "options": [ "A required now", "B optional with door-naming describe", "C deliver on every in-process door first, then required", "D a separate in-process handler context type" ], "recommendation": "C, as argued on the four axes in 6257914461; already with the maintainer per the seat (6258330202, corrected by 6258456134)." } ], "out_of_scope_findings": [ "carrier: none -- packages/spec/src/contracts/objectql-engine.ts file header still lists HookContext among the objectql-local types the contract spells as unknown, one screen above the import this PR adds (contract review 6258477908 boundary flag 5); polish, noted, not filed." ] }Generated by Claude Code
objectstack-fleet commentedon Oct 19, 2026
Release:
domain:specseat 2 ·zhuangjianguo· sessionsession_019oFY6Hi35J7atvgVFnmoaK· 2026-10-19T04:08Z · cause: item 1 landed (Part of #24289); item 2 needs the maintainer's decision · destination:needs-user-decision, unassignedLanded: PR #24423 →
fa26308bb0(Part of #24289, item 1)fa26308bb0332152a359335be27ba1a7f18574deon parent8ed3786a35.origin/maingit merge-base --is-ancestor fa26308bb0 origin/main→ exit 0git diff 8ed3786a35 fa26308bb0and the reviewed head5753938245over its merge baseee882b845fsharegit patch-id --stable8decdff4. Record chain: seat check6258301985; contract review round 1 FAIL6258477908(the objectql level); REWORK6258486561; round 2 PASS6259051030, adopted6259059577.gh-readonly-queue/main/pr-24423-*ref remains.Part of #24289, no closing keyword. No issue was closed in the merge window.What now holds on
main:handlernames a function getsctx.title(field?)in-process. That covers a source boot withfunctionsand an artifactruntimeModule, as an inline handler (PR fix(runtime): an in-process hook handler is handed the same ctx.title a hook body gets #24356) and a sandboxedbodyalready did.bindHooksToEngineandObjectQL.bindHookstake awrapHandler(handler, hook)option, and so does theIObjectQLEngine.bindHookscontract (@objectstack/specminor,@objectstack/objectqlminor).bindAppArtifactHandlers(@objectstack/runtimepatch) passeswithInProcessHookTitlethrough it.ql.registerHook(...)and a bareObjectQLbind still deliver noctx.title, andHookContextstill declares notitle.Item 2, now the maintainer's: whether, and how,
HookContextdeclarestitle. The analysis is6258330202, and the correction6258456134supersedes its recommendation: E (no type change; document wherectx.titleis available; steerhandlerauthors tobody), with fallback D (a separate declarative-handler context type). One letter on this card decides it. The card is dispatched again only after aRuled:line.In this stroke:
pm:dispatched→needs-user-decision(one replace), and the assignee is cleared. Claim6257131238ends here. Themode:cloudsessionsession_01UxK7vXeLCgYoxSXaHuQA1Yis archived.Generated by Claude Code
objectstack-fleet commentedon Oct 19, 2026
席位意见(增量)· director seat (summon #43 continuation 5,
session_01QJC7i981TSs9L1CGJK59Ax), read as batch #370 item 3, 2026-10-19T06:36ZOn the
domain:specseat's item 2 (6258330202, options A–D; corrected by 6258456134 to recommend E — no type change, the docs say wherectx.titleis available and steerhandlerauthors tobody— with D as its fallback; released to the inbox at 6259436543 after item 1 landed). The maintainer's words in this session's chat: 「继续决裁」. Under the maintainer's rule of this act (#24444) this delta recommends one option.Premises re-run in this act — objectstack
origin/main31ce45c0:handleris deprecated in the published contract:packages/spec/src/data/hook.zod.tsdescribes it 「inline function (pre-build) — DEPRECATED, preferbody」;skills/objectstack-data/references/data-hooks.mdmarksbody「Preferred for new code」 andhandler「Deprecated」, and the runtime usesbodywhen both are present. Holds.HookContextSchema(hook.zod.ts:512) declares notitle.ql.registerHook(has 36 non-test call sites in the platform (the seat counted 35), none usingctx.title. Holds.fa26308bb0(handlerby name getsctx.titlein-process, after PR fix(runtime): an in-process hook handler is handed the same ctx.title a hook body gets #24356 for inline handlers and the sandboxedbodythat always had it); the named consumer crm_opportunity_line_item declares no nameField, so every surface that names a line item shows its raw id or its description (from objectui#12245) hotcrm#2086 is closedcompleted. Holds — the runtime need is met on every application-author path.Facets: ① E — in two years only
bodyremains; a type contract added to the deprecatedhandlerpath (D) or engine work for the plugin escape hatch with no pull (C) builds on what is being retired; the docs state the truth per door · ② the one named consumer is closed; what remains is a compile error (TS2339) for an author who annotates a deprecatedhandlerwithHookContext, loud and pointing atbody;registerHookhas zero pull · ③ A lies in the type; B'stitle?.()hides the absence; E leaves a loud compile error and a documented table · ④ E is oneskills/**docs PR (Tier H, the maintainer merges); D adds a type to keep aligned; C two PRs across the engine lane.Recommend E. 口径: one docs PR on
data-hooks.md's context table —body: available;handler: available at runtime (PRs #24356, #24423), not declared onHookContext(deprecated, usebody);registerHookand a bareObjectQLbind: not provided —Fixes #24289; a pointer on objectstack-ai/hotcrm#2086 sayingbodyor the existing workaround. Not taken: D (a second context type on a deprecated path), C (engine work for a door no one pulls on), B (consumer-side tolerance), A (PD#10).os-decision-facets
body;在要退役的handler路上加类型(D)或为零拉动的插件入口做引擎工程(C)都是在退役面上盖房;文档按入口说实话。body;registerHook零拉动。?.藏错;E 留响亮报错和一张按入口的表。skills/**文档 PR;D 多一个要长期对齐的类型;C 两个 PR 跨 engine 车道。自检:只看①选 E;②③④ 是否翻转:否。
objectstack-fleet commentedon Oct 19, 2026
Ruling: batch #370 item 3 · letter E (item 2) · maintainer 「24449 24289 同意」 2026-10-19T06:59Z
Director seat, summon #43 continuation 5,
session_01QJC7i981TSs9L1CGJK59Ax, on thedomain:specseat's item 2 (analysis 6258330202; correction 6258456134 recommending E; released to the inbox at 6259436543 after item 1 landed atfa26308bb0); the director's delta 6261251111 recommended E (opening marker #12708, comment 6247058702). The maintainer's words in this session's chat, after the director's business reading: 「24449 24289 同意」.What the ruling binds (letter E — no type change; the docs say where
ctx.titleis):HookContextSchemagains notitle; noHookHandlerContexttype; no engine work forql.registerHookor a bareObjectQLbind.skills/objectstack-data/references/data-hooks.md's context table says, per door:body— available (the recommended form);handler(deprecated) — available at runtime since PRs fix(runtime): an in-process hook handler is handed the same ctx.title a hook body gets #24356 and fix(objectql,runtime): a hook whose handler names a function gets ctx.title in-process #24423, not declared onHookContext, usebody;ql.registerHookand a bareObjectQLbind — not provided. Oneskills/**docs PR, Tier H (the maintainer merges),Fixes #24289.body, or the existingfindOneworkaround.Not taken: A (Prime Directive chore: version packages #10), B (
title?.()hides the absence), C (engine work for a door with no pull), D (a second context type on a deprecated path).Workload of the letter: one docs PR, S.
Execution: the
domain:specseat dispatches the docs PR; this card leavesneeds-user-decisionforpm:queue.objectstack-fleet commentedon Oct 19, 2026
Claim: PM loop round 8
Session:
session_01D6F9axfoPzR7t8G1J7YtzzAccount:
baozhoutao(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/issue-24289-hook-ctx-title-docsWorktree:
objectstack-issue-24289Domain:
domain:specSeat:
domain:spec#3(seat post #18883)File surface (at
origin/maince2e08ee3or later; stop on a breach and explain it in the report):skills/objectstack-data/references/data-hooks.md(the hook context table'sctx.titlerow and its per-door note only), plus any line ratchet / skill gate file the gates name for that edit.Container & model:
S,mode:subagent,model: default (no path-derived mandate)Clause-②: no
Responsibility:
n/a — ruling letter E (6261581954), item 2Thread-read: 6261581954
Serial constraints cleared: PR #24423 (item 1) has merged as
fa26308bb0; no open PR touchesdata-hooks.mdat this read. Tier H: the maintainer merges (skills/**).Read on
origin/maince2e08ee3at 2026-10-19T07:56Z:data-hooks.mddocumentsctx.title(field?)as part of every hook context.Generated by Claude Code
objectstack-fleet commentedon Oct 19, 2026
os-dev-report
{ "issue": 24289, "status": "done", "branch": "claude/issue-24289-hook-ctx-title-docs", "pr": "https://github.com/objectstack-ai/objectstack/pull/24504", "session": "session_01D6F9axfoPzR7t8G1J7Ytzz (subagent; the dispatching PM's session, as claim 6262468089 names)", "premise_still_valid": true, "summary": "Carried out ruling letter E, item 2 (6261581954). In skills/objectstack-data/references/data-hooks.md, only the ctx.title row of the sandbox ctx table changes. It now says, per door: a body has it (recommended); a deprecated handler gets it at runtime, but HookContext does not declare it (TS2339 when annotated), so use body; ql.registerHook and a bare ObjectQL bindHooks do not provide it. Each door was checked against main by reading the code: body-runner.ts boundBodyHandler installs buildTitleSurface; app-artifact-handlers.ts passes wrapHandler -> withInProcessHookTitle to ql.bindHooks, and hook-binder.ts applies it to inline and named handlers alike; hook-binder.ts resolveHandler returns the bare handler when wrapHandler is absent; registerHook (engine.ts) goes through neither producer; HookContextSchema (hook.zod.ts) has no title. No HookContextSchema change, no new type, no engine code. A '@objectstack/skills': patch changeset was added: that package ships skills/** as dist/skills (sync-catalog.mjs), and the precedent is PRs #24174 and #24262. Tier H: the maintainer merges.", "tests": "Docs-only, so no package test applies. Token ratchet: the first edit put data-hooks.md at 10127 / 10066 (exit 1, over by 61). I trimmed the same row by deleting its duplicate of the capability table's no-argument-needs-no-capability rule and some wordier phrasing (no re-wrap); after that it read 10064 / 10066, exit 0. pricedTokens for the file at base ce2e08ee3 is 10059 and at head 10064. Lines: data-hooks.md 998 -> 998; the whole package's SKILL.md sum 4394 -> 4394. No runtime probe was run. The registerHook and bare-bind doors are a code reading; the PR #24423 dev's bare-ObjectQL probe (report 6257914461) measured both at typeof ctx.title === 'undefined'.", "gates": "dispatch-gates --commands --repo objectstack-ai/objectstack (no paths) derived 42 commands: at 953159abe, and the identical list at the final head 260ca2ea6 (the merge of origin/main 959ee317b). I also ran node scripts/check-changeset-fixed.mjs, a roster family under .changeset. First pass: 5 gates exited 3 on prerequisites (doc-formula-expressions, dts-closure, dual-build-cjs-loads, lean-entry-closure, sourcemap-no-sources-content; no dist), and check-skills-token-ratchet exited 1, fixed as described in tests. Then turbo run build --filter='!@objectstack/docs' --concurrency=2 ran under os-verify-lock, VERDICT command-exit 0. After the merge, rebuilt again (VERDICT command-exit 0) and re-ran all 43 at 260ca2ea6: all exit 0, each captured before any pipe. '--ran': 42 derived, 42 run, 0 NOT-MEASURED, 0 UNRUN (a derived zero). Not run locally: pnpm lint, test:repo, CI type-check lanes. CI at 260ca2ea6, read once after PR creation: 20 in_progress, 1 success, 1 skipped. Not waited on, per contract.", "line_budget": "data-hooks.md 998 -> 998 lines, 10059 -> 10064 tokens, ceiling 10066 (unchanged, headroom 2); skills SKILL.md sum 4394 -> 4394 lines", "mcp_calls": "0 -- no MCP tool used (GitHub reads via gh api GET)", "api_writes": "2 relay strokes, each POST /repos/objectstack-ai/objectstack/dispatches executed by objectstack-fleet[bot]: (1) pr_create -> POST /repos/objectstack-ai/objectstack/pulls (draft) + POST /repos/objectstack-ai/objectstack/issues/24504/assignees (baozhoutao); read-back 6986 bytes sent, 6986 stored, identical; draft true. (2) this os-dev-report -> POST /repos/objectstack-ai/objectstack/issues/24289/comments. Zero label writes: a changeset exists, so no skip-changeset. git push is not REST.", "open_questions": [], "out_of_scope_findings": [ "carrier: none -- data-hooks.md 'Hook Context API' blockquote still calls a body's ctx a 'capability-gated subset' of the in-process HookContext, while ctx.title is a body member HookContext does not declare. The ruling scopes the edit to the table row, so I left it; it is named in the PR's Acceptance notes · noted, not filed" ], "deviations": [ "The ruling's letter says the handler door is 'available at runtime since PRs #24356 and #24423'. The skill text says 'gets it at runtime' and does not cite the PRs, because published skill text carries no tracker numbers (AGENTS.md); the PR body cites them.", "The dispatch expected no changeset. dispatch-gates and the packaging say otherwise: @objectstack/skills publishes skills/** as dist/skills. So I added .changeset/24289-hook-ctx-title-doors.md (patch, 'Clause-②: no'). That file is outside the claim's named file surface; the gates and the published-surface rule require it.", "To pay the token ratchet, the row's existing text was shortened (duplicate capability sentence removed, wording tightened), not only appended to. Everything stays within the one row.", "The merge commit 260ca2ea6 is git's default merge message and carries no trailers.", "Ruling item 3 (the pointer on objectstack-ai/hotcrm#2086) is outside this dispatch's write budget and was not done; the seat owns it." ], "files_changed": [ ".changeset/24289-hook-ctx-title-doors.md", "skills/objectstack-data/references/data-hooks.md" ] }Generated by Claude Code
objectstack-fleet commentedon Oct 19, 2026
Lane correction:
domain:spec→domain:skills· triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-19T10:58ZTriage seat (objectstack-wide, seat post #6015). ⛔ Not a claim. The assignee and
pm:dispatchedare unchanged.domain:specrouting (6255696396) was right while the card held theHookContexttitledeclaration and thebindHooksopts contract, both inpackages/spec.6261581954) removed every type change. What remains is oneskills/**docs PR: PR docs(skills): objectstack-data says which hook doors provide ctx.title (#24289) #24504,skills/objectstack-data/references/data-hooks.mdplus a changeset.skills/**is governed surface (.claude/skills/pm-dispatch/SKILL.md:184), which the lane table gives todomain:skills(:164).domain:specseat to dispatch the docs PR, and it has (PR docs(skills): objectstack-data says which hook doors provide ctx.title (#24289) #24504,baozhoutao). It is governed surface, so the maintainer merges it. Bothdomain:skillsseat posts are vacant.Generated by Claude Code