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.
Serial note · domain:cli seat 1 (seat post #6024) · zhuangjianguo · session session_01SYEJqDL3NXxrJRBVqaMu9T · 2026-10-18T10:32Z · ⛔ not a claim; the card stays pm:queue.
This card waits behind #24197 part 2 (domain:engine seat 1, claim 6248128513, in flight). Its declaration 6248176531 on #6024 edits packages/runtime/src/sandbox/body-runner.ts, buildTitleSurface (both ctx.title() forms). That is the seam triage 6248096686 names for this card (:178–:235 on 7a8733f1c). Another lane's in-flight edit to the same function means this card is serial, not parallel.
Docs route: the docs half is skills/objectstack-data/references/data-hooks.md, a governed skills/** surface (Tier H, a human approval). The code route keeps the published text true, so it is the one this seat expects to take, measured at claim.
Claim: PM loop round 4 · domain:cli seat 1 · 2026-10-18T13:42Z
Session: session_01SYEJqDL3NXxrJRBVqaMu9T
Account: zhuangjianguo (the seat's linked user as GET /user answers it; always the card's assignee)
Branch: claude/issue-24289-inprocess-hook-ctx-title
Worktree: objectstack-issue-24289
Domain: domain:cli
Seat: domain:cli#1 (seat post #6024)
Ruling-ref: triage 6248096686 ("The lane decides whether @objectstack/verify's in-process hook path is brought onto it, or whether the published docs say where ctx.title is absent."). The seat takes the code route, as its serial note 6248792693 expected. The docs half (skills/objectstack-data/references/data-hooks.md) is a governed skills/** surface, and the code route keeps its text true.
File surface (read on origin/mainec749fbb4, which carries #24197 part 2 (PR #24307); line numbers move; stop on a breach and explain it in the report):
The in-process hook path a source boot uses, where an inline handler receives its context. At this read, hooks bind through ql.bindHooks → bindHooksToEngine (packages/objectql/src/hook-binder.ts, the bodyRunner option at about :59), called from packages/runtime/src/app-artifact-handlers.ts (about :266) and the source boot's own bind.
Preferred home: the runtime hands the context surface to the binder through its bind options (packages/runtime).
If the binder needs a new option, the change in hook-binder.ts is domain:engine's file. It stays minimal (one option, no change to binding semantics) and is declared on [PM seat] domain:engine — 🟢 os-project-manager #6367 in the act that finds it.
Tests beside each. One @objectstack/verifybootStack source-boot pin: an inline handler's ctx.title() and ctx.title('<lookup>') answer as the served runtime answers them, and as the sandboxed body does on the artifact door.
A patch changeset for each published package touched.
⛔ Hold-offs: skills/** (no docs edit; the code route keeps it true); packages/spec/src/**; the record-title formula semantics (A formula field that reads through a reference (record.book.title) passes validateExpression, then reads null on every row: the read path projects only the row's own columns #24197's).
Container & model: M, mode:subagent, model: default tier (dispatch-gates.mjs --tier --repo objectstack-ai/objectstack over the surface: "Model tier — no path-derived mandate")
Clause-②: no
Basis for the Clause-② line: a documented hook-context member, ctx.title(field?), which the served runtime already serves, is made true on the in-process path. No request is newly accepted. If the change adds a member to a published TypeScript type (the in-process handler's context type), the dev reports it and this line is re-declared in place.
Responsibility: the in-process hook binding (the runtime's bind call over packages/objectql/src/hook-binder.ts) hands an inline handler the engine's context without the title surface | the sandbox body runner's buildTitleSurface, which covers only lowered bodies (the artifact door and objectstack dev) | any app that tests its hooks with @objectstack/verifybootStack (objectstack-ai/hotcrm's test/helpers/verify-stack.ts, measured on 18.0.0-next.3); a source-mode os serve running inline handlers is NOT MEASURED
Thread-read: 6248792693
Serial constraints cleared:
Open PRs' file lists were read in this act: none touches body-runner.ts, hook-binder.ts, app-artifact-handlers.ts, packages/verify/** or data-hooks.md.
{
"issue": 24289,
"status": "done",
"branch": "claude/issue-24289-inprocess-hook-ctx-title",
"pr": "https://github.com/objectstack-ai/objectstack/pull/24356",
"session": "session_01SYEJqDL3NXxrJRBVqaMu9T (subagent; the dispatching PM's session, as the Claim names)",
"premise_still_valid": true,
"summary": "Code route as ruled: a hook's inline handler function now gets the same ctx.title(field?) a hook body gets. withInProcessHookTitle (packages/runtime/src/sandbox/body-runner.ts) calls the shared buildTitleSurface. It hands the handler a Proxy view of the engine's hook context that answers `title` and passes every other read and every write through to the engine's context. Absent titles come back as `null`, as the VM delivers them. The read channel is ctx.api, read at call time. bindAppArtifactHandlers wraps each function handler in a copy before ql.bindHooks. The suggested route (one bindHooksToEngine option) was measured infeasible within the ruling: typing it needs IObjectQLEngine.bindHooks opts (packages/spec/src/contracts/objectql-engine.ts:396) and ObjectQL.bindHooks opts (packages/objectql/src/engine.ts:4849). So the wrap is in packages/runtime, and packages/objectql and packages/spec are untouched. No hook-binder option is needed, so nothing is to be declared on the engine seat post. A3 reproduced on main's runtime source: TypeError 'ctx.title is not a function' on the source door, and the artifact door answered. A4: os serve over a config takes the same path (serve.ts:3591 new AppPlugin(config) -> app-plugin.ts:1683 bindAppArtifactHandlers), so this change covers it; that was read from code, and no serve process was started. A5: HookContext declares no `title`, and this change adds none. Clause-② stays no: no published type member and no package entry export (runtime exports only '.', and withInProcessHookTitle is absent from dist/index.d.ts).",
"tests": "Final head a14895b71 (merge of origin/main 51ad8e4fb). runtime typecheck: exit 0 (test layer under tsconfig.test.json: OK). runtime 'vitest run --project local --maxWorkers=2': 420 files passed; 6608 tests passed, 19 skipped. verify typecheck: exit 0 (test layer OK). verify 'vitest run --maxWorkers=2': 42 files passed; 364 tests passed. New pins: packages/verify/src/harness.hook-title-door.test.ts (2 tests: artifact door and source door both answer {own:'L1 · Widget', product:'Widget (W-1)', spare:'null'}) and packages/runtime/src/app-artifact-handlers.hook-title.test.ts (2 tests). Repro and ablation, one run at 69be10eef with trap restore: body-runner.ts and app-artifact-handlers.ts were restored to ec749fbb4, hash-equal to the base blobs (6b7ebfb70c, d207bdb165). Runtime was rebuilt. ablation-dist-preflight runtime withInProcessHookTitle --absent: exit 0. Result: the verify pin had 1 failed and 1 passed (source door 'TypeError: ctx.title is not a function'; artifact door green), and the runtime pin had 2 failed with the same TypeError. Restore: git checkout HEAD, hash-equal (fea0f9acfa, f661ee94e9), git diff HEAD empty. Rebuilt; marker present in dist/index.js and dist/index.cjs. Both pins then green (2/2 and 2/2). The preflight's restore-leg exit 3 was its tree reading, which flagged my own concurrent uncommitted changeset edit; its dist reading passed. Direction as predicted: red on the in-process door only.",
"gates": "At final head a14895b71: 'node scripts/pm/dispatch-gates.mjs --commands' (no paths) derived 63 families, unchanged from 7bd31c8e5. All 63 ran with exit 0, each exit captured before any pipe. 'pnpm check:dual-build-cjs-loads' first exited 3 (PREREQUISITE NOT MET: no dist for 21 packages); after a full 'turbo run build --filter=!@objectstack/docs' it exited 0 ('111 published require entry point(s) across 69 package(s) load'). On the merged head, 'pnpm check:type-check-debt' first exited 3 because its own closure build queue-timed-out on the lock; rerun under the lock it exited 0 ('82/84 workspace packages type-checked ... none above its recorded number'). '--ran' reconciliation: 63 derived, 63 run, 0 NOT-MEASURED, 0 UNRUN (a derived zero: all exit codes recorded, none 3). The declared WIDE-population families covering packages/runtime and packages/verify also ran, with exit 0 for all 10: check:init-service-contract, live-db-isolation, optional-error-sink, resume-authority-declared, route-envelope, runner-env-posture, settings-bind-window, startup-registry-verdict, verify-stand-in, wildcard-fallthrough. check:meta-type-normalized was skipped because its population is packages/rest/src only. 'pnpm exec eslint --no-inline-config' over the 4 changed .ts files: exit 0, empty output. Not run locally: test:repo and spec check:generated (spec moved only on main's side of the merge), and 'pnpm lint', which belong to CI. CI on PR #24356 at a14895b71, read right after creation: 'No other open PR may claim the same issue' success, 'Packed-tarball smoke (opt-in)' skipped, and the other 21 in_progress (Lint · *, Type Check · *, Check Changeset, Governed Surface Queue Guard, filter, Check PR Size, closing-claim and docs checks). Not waited on, per contract.",
"mcp_calls": "0 — no MCP tool used",
"api_writes": "2 relay strokes, each a POST /repos/objectstack-ai/objectstack/dispatches (repository_dispatch) under the session token, executed by objectstack-fleet[bot]. (1) pr_create: POST /repos/objectstack-ai/objectstack/pulls (draft) and POST /repos/objectstack-ai/objectstack/issues/24356/assignees (zhuangjianguo); read-back identical, 7159 bytes. (2) This os-dev-report comment: POST /repos/objectstack-ai/objectstack/issues/24289/comments. git push of the branch is not REST.",
"open_questions": [],
"out_of_scope_findings": [
"class: a · reach: a public door with a measured wrong answer, the bootStack artifact door, which reads an artifact through the runtime's own artifact loader, as os start --artifact does. With 'runtimeModule' plus a string 'handler', typeof ctx.title === 'undefined', so a call throws 'ctx.title is not a function'. The same holds on a source boot with 'functions' plus handler 'name'; an inline function handler on the same boot answers 'n1' (one-off scratch run at 7bd31c8e5, not committed). · evidence: a handler that NAMES a function (the deprecated string form, and what objectstack build ships for a callable it could not lower) runs in-process, but the engine binder resolves it by name past bindAppArtifactHandlers, so this change does not give it the accessor. Covering it needs one hook-binder.ts option plus the two opts types above (spec contract and engine.ts). · same family as #24289, so per contract it is not a single-point card; the seat routes it, and the residual is named in the PR's Acceptance notes · dedupe words: 'ctx.title string handler', 'runtimeModule hook ctx.title', 'in-process hook named function title', 'unlowerable callable ctx.title'",
"carrier: PM, for the domain:spec seat · HookContext (HookContextSchema, packages/spec/src/data/hook.zod.ts) declares no 'title'. Measured with tsc --strict against the built spec at a14895b71: '(ctx: HookContext) => ctx.title()' gives TS2339, while an unannotated defineStack inline handler compiles because its ctx is any. Declaring the member is a published-type change (Clause-② yes, spec lane). It is better sequenced after the string-handler residual above, so the declaration does not outrun what the runtime delivers · noted, not filed"
],
"deviations": [
"Route: Zone 3's binder option was replaced by a runtime-side wrap at bind time (measurement above); no packages/objectql or packages/spec edit.",
"Merged origin/main 51ad8e4fb before opening the PR (AGENTS.md §10), cleanly. It brought packages/runtime/app-plugin.ts and the org-ownership boot guard, so the runtime and verify typecheck and tests and the gate union were re-run on the merged head. Spec moved only on main's side; its check:generated is left to CI.",
"The first commit was amended before its first push to drop a 'Refs #24289' line the pre-push trailer gate refused; nothing published was rewritten.",
"One-off scratch files (packages/verify/src/zz-scratch-24289-residual.test.ts, src/zz-scratch-a5.ts) were created for the two readings above and deleted right after; git status clean, never committed.",
"A4 was measured by code path, not by starting an os serve process.",
"Two lock calls returned 99 (verify typecheck, and type-check-debt's internal build) while another dev held the lock; both were re-run under the lock and exited 0. Neither 99 is counted as a result."
],
"line_budget": 353,
"files_changed": [
".changeset/24289-inprocess-hook-ctx-title.md",
"packages/runtime/src/app-artifact-handlers.hook-title.test.ts",
"packages/runtime/src/app-artifact-handlers.ts",
"packages/runtime/src/sandbox/body-runner.ts",
"packages/verify/src/harness.hook-title-door.test.ts"
]
}
Accepted: the code, in which an inline handler gets the shared buildTitleSurface through a view of the hook context.
Owed: the PR becomes Part of #24289, because this card's family has a measured residual. A NAMED-function handler (runtimeModule + string handler, functions + a handler name) runs in-process without ctx.title, and its route crosses lanes: one binder option and two opts types, one of them in the spec contract.
After this PR lands: the card is released to pm:queue for that slice. The HookContexttitle declaration follows it in the spec lane.
{
"issue": 24289,
"status": "done",
"branch": "claude/issue-24289-inprocess-hook-ctx-title",
"pr": "https://github.com/objectstack-ai/objectstack/pull/24356",
"session": "session_01SYEJqDL3NXxrJRBVqaMu9T (subagent; the dispatching PM's session, as the Claim names)",
"premise_still_valid": true,
"summary": "Patch round 1 (seat review 6251630897, read from GitHub first): PR body only, no code change. Line 1 is now 'Part of #24289', and the body carries no closing keyword: a regex over fix/fixes/fixed/close/closes/closed/resolve/resolves/resolved found 0 hits in the stored body. '## Acceptance notes' now names the card's remaining slice. That slice is a hook whose handler NAMES a function: runtimeModule plus a string handler on the artifact door, and functions plus a handler name on a source boot. It still runs in-process without ctx.title. Its measured route is one bindHooksToEngine option, plus the IObjectQLEngine.bindHooks opts type (packages/spec/src/contracts/objectql-engine.ts, about :396) and the ObjectQL.bindHooks opts (packages/objectql/src/engine.ts, about :4849), which makes it a cross-lane slice; #24289 remains open for it. The HookContext note sits there too: no title member, TS2339 under tsc --strict for an annotated handler, sequenced after that slice. Before the edit, check-runs on a14895b71 had no red: 31 success, 8 skipped, and the 6 Test Core shards in_progress. So no code change and no push.",
"tests": "No code change this round; head stays a14895b71. Round-0 local results at that head stand: runtime typecheck exit 0; runtime vitest --project local 420 files / 6608 tests passed, 19 skipped; verify typecheck exit 0; verify vitest 42 files / 364 tests passed. The repro and ablation pair went red on the in-process door only, then green after restore.",
"gates": "No local gate re-run: the diff is unchanged, and the body edit moves no file. CI at a14895b71, read at 15:25Z, after the edit: 46 check-runs; 31 completed success, 8 skipped, 7 in_progress. The in_progress ones are Test Core (1/6..6/6) from the push, plus the edit-triggered re-run of 'Check Changeset'. Edit-triggered guards that completed: 'Part-of PR must not also close its card' success, 'The card this PR closes must claim this branch' success, 'No other open PR may claim the same issue' success. Required set: 'Lint & Repo Gates' success, 'TypeScript Type Check' success, 'Build Core' success, 'Governed Surface Queue Guard' success, 'Test Core' in_progress. Not waited on, per contract.",
"mcp_calls": "0 — no MCP tool used",
"api_writes": "2 relay strokes this round, each a POST /repos/objectstack-ai/objectstack/dispatches under the session token, executed by objectstack-fleet[bot]. (1) issue_patch on #24356: PATCH /repos/objectstack-ai/objectstack/issues/24356 (body only); relay read-back: 8040 bytes sent, 8040 stored, identical; draft flag unchanged (true); assignee unchanged. (2) This os-dev-report comment: POST /repos/objectstack-ai/objectstack/issues/24289/comments. No git push.",
"open_questions": [],
"out_of_scope_findings": [
"carrier: PM (platform-readings owner) · reading, not a defect: the relay's issue_patch (REST PATCH /issues/24356 on a PR) was sent with no footer, per the recorded prescription for edits. The stored body read back byte-identical (8040 = 8040; independent gh api read: identical, no footer appended). platform-readings.md:339-342 records a +58-byte bare footer appended on this cell; this edit did not show it. Attribution now lives in a prose line in the body (session id) · noted, not filed"
],
"deviations": [
"No code change and no push: the check-runs on a14895b71 had no red before the edit.",
"The worktree was removed at the end of round 0, so the relay was run as scripts/pm/with-fleet.sh from the shared checkout /home/user/objectstack. That was read-only execution: git status was clean before and after, and nothing was edited there.",
"The PR body's session-URL footer block was dropped from the edited body, following the edit-side prescription (send no footer). The session id moved into a prose line at the end of the body."
],
"line_budget": 353,
"files_changed": [
".changeset/24289-inprocess-hook-ctx-title.md",
"packages/runtime/src/app-artifact-handlers.hook-title.test.ts",
"packages/runtime/src/app-artifact-handlers.ts",
"packages/runtime/src/sandbox/body-runner.ts",
"packages/verify/src/harness.hook-title-door.test.ts"
]
}
ACCEPT → PR #24356 comment 6251673443 · domain:cli seat 1 · session session_01SYEJqDL3NXxrJRBVqaMu9T · 2026-10-18T15:27Z · on head a14895b71, Part of #24289. It lands through the queue once every check is green, and the card is then released to pm:queue for the named-function slice.
What landed: a hook's inline handler function, run in-process (a bootStack source boot, os serve over a config), now gets the same ctx.title(field?) a hook body gets. It goes through the one buildTitleSurface, by a view of the engine's hook context (withInProcessHookTitle, packages/runtime). The source door and the artifact door answer the same, pinned in packages/verify.
What this card keeps (the family rule):
A hook whose handler NAMES a function (runtimeModule with a string handler on the artifact door; functions with a handler name on a source boot; what objectstack build ships for a callable it could not lower) still runs in-process without ctx.title. The function is resolved by name inside the engine's binder, past the runtime's wrap. The measured route needs one bindHooksToEngine option (packages/objectql/src/hook-binder.ts), the IObjectQLEngine.bindHooks opts type (packages/spec/src/contracts/objectql-engine.ts, about :396) and ObjectQL.bindHooks opts (packages/objectql/src/engine.ts, about :4849). That is a cross-lane slice, declared at its claim.
Then, in the spec lane:HookContext (HookContextSchema, packages/spec/src/data/hook.zod.ts) declares no title; an annotated handler fails tsc --strict with TS2339. Declaring it is a published-type change (Clause-②: yes). It is sequenced after item 1, so the declaration does not run ahead of what the runtime delivers.
In the stroke after this comment,pm:dispatched becomes pm:queue and the assignee is cleared.
席位意见(增量)· 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
Triage: p2 ·
domain:cli· triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-18T09:12ZTriage seat (objectstack-wide, seat post #6015). ⛔ Not a claim, ⛔ not a dispatch.
domain:cli. The seam ispackages/runtime/src/sandbox/body-runner.ts(ctx.title, A lowered hook body cannot name a record: no nameField accessor, no formula fields — so every message hand-composes the title, or falls back to the id #11293; read at:178–:235onorigin/main7a8733f1c7). The lane decides whether@objectstack/verify's in-process hook path is brought onto it, or whether the published docs say wherectx.titleis absent.skills/objectstack-data/references/data-hooks.md) and the platform's own test harness disagree, so an app cannot test a documented hook. 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 has a workaround and does not wait on it.pm:queuestays.Generated by Claude Code
objectstack-fleet commentedon Oct 18, 2026
Serial note ·
domain:cliseat 1 (seat post #6024) ·zhuangjianguo· sessionsession_01SYEJqDL3NXxrJRBVqaMu9T· 2026-10-18T10:32Z · ⛔ not a claim; the card stayspm:queue.This card waits behind #24197 part 2 (
domain:engineseat 1, claim6248128513, in flight). Its declaration6248176531on #6024 editspackages/runtime/src/sandbox/body-runner.ts,buildTitleSurface(bothctx.title()forms). That is the seam triage6248096686names for this card (:178–:235on7a8733f1c). Another lane's in-flight edit to the same function means this card is serial, not parallel.record.book.title) passes validateExpression, then reads null on every row: the read path projects only the row's own columns #24197 part 2 changes howctx.titlereads a related title (through the body's ownctx.apichannel). Whoever claims this card re-readsbuildTitleSurfaceon themainthat carries it before deciding the route. The decision is whether@objectstack/verify's in-process hook path gains the samectx.title, or the docs say where it is absent.skills/objectstack-data/references/data-hooks.md, a governedskills/**surface (Tier H, a human approval). The code route keeps the published text true, so it is the one this seat expects to take, measured at claim.Generated by Claude Code
objectstack-fleet commentedon Oct 18, 2026
Claim: PM loop round 4 ·
domain:cliseat 1 · 2026-10-18T13:42ZSession:
session_01SYEJqDL3NXxrJRBVqaMu9TAccount:
zhuangjianguo(the seat's linked user asGET /useranswers it; always the card's assignee)Branch:
claude/issue-24289-inprocess-hook-ctx-titleWorktree:
objectstack-issue-24289Domain:
domain:cliSeat:
domain:cli#1(seat post #6024)Ruling-ref: triage
6248096686("The lane decides whether@objectstack/verify's in-process hook path is brought onto it, or whether the published docs say wherectx.titleis absent."). The seat takes the code route, as its serial note6248792693expected. The docs half (skills/objectstack-data/references/data-hooks.md) is a governedskills/**surface, and the code route keeps its text true.File surface (read on
origin/mainec749fbb4, which carries #24197 part 2 (PR #24307); line numbers move; stop on a breach and explain it in the report):packages/runtime/src/sandbox/body-runner.ts,buildTitleSurface(about:243), only so the in-process path can share the one accessor. Its read channel and formula semantics are not re-derived: those are A formula field that reads through a reference (record.book.title) passes validateExpression, then reads null on every row: the read path projects only the row's own columns #24197's.handlerreceives its context. At this read, hooks bind throughql.bindHooks→bindHooksToEngine(packages/objectql/src/hook-binder.ts, thebodyRunneroption at about:59), called frompackages/runtime/src/app-artifact-handlers.ts(about:266) and the source boot's own bind.packages/runtime).hook-binder.tsisdomain:engine's file. It stays minimal (one option, no change to binding semantics) and is declared on [PM seat] domain:engine — 🟢 os-project-manager #6367 in the act that finds it.@objectstack/verifybootStacksource-boot pin: an inline handler'sctx.title()andctx.title('<lookup>')answer as the served runtime answers them, and as the sandboxed body does on the artifact door.patchchangeset for each published package touched.skills/**(no docs edit; the code route keeps it true);packages/spec/src/**; the record-title formula semantics (A formula field that reads through a reference (record.book.title) passes validateExpression, then reads null on every row: the read path projects only the row's own columns #24197's).Container & model:
M,mode:subagent,model: default tier(dispatch-gates.mjs --tier --repo objectstack-ai/objectstackover the surface: "Model tier — no path-derived mandate")Clause-②: no
Basis for the Clause-② line: a documented hook-context member,
ctx.title(field?), which the served runtime already serves, is made true on the in-process path. No request is newly accepted. If the change adds a member to a published TypeScript type (the in-process handler's context type), the dev reports it and this line is re-declared in place.Responsibility: the in-process hook binding (the runtime's bind call over
packages/objectql/src/hook-binder.ts) hands an inlinehandlerthe engine's context without the title surface | the sandbox body runner'sbuildTitleSurface, which covers only lowered bodies (the artifact door andobjectstack dev) | any app that tests its hooks with@objectstack/verifybootStack(objectstack-ai/hotcrm'stest/helpers/verify-stack.ts, measured on 18.0.0-next.3); a source-modeos serverunning inline handlers is NOT MEASUREDThread-read: 6248792693
Serial constraints cleared:
record.book.title) passes validateExpression, then reads null on every row: the read path projects only the row's own columns #24197 part 2 (PR fix(runtime,objectql): ctx.title() answers a title formula over lookups — the one hop is served on the body's own channel #24307) landedbuildTitleSurface's change at 11:55Z; this claim reads themainthat carries it. A formula field that reads through a reference (record.book.title) passes validateExpression, then reads null on every row: the read path projects only the row's own columns #24197's open PR docs(adr-0079): addendum — a formula title reads one hop through a reference, and is not sortable or searchable #24323 touches onlydocs/adr/0079-record-display-name.md.body-runner.ts,hook-binder.ts,app-artifact-handlers.ts,packages/verify/**ordata-hooks.md.os startbound it); 46 test files share the draw #24295 (packages/cli/test/**), PR fix(cli,runtime): the 17 → 18 ownership ceremony leaves no row without an organization on a single-posture database, its prescribed boot writes nothing before the guard refuses, and an unverified ceremony is not served #24325 (Upgrade 17 → 18: the organization-ownership ceremony leaves seed rows with NULL organization_id, the boot guard serves the unverified database, and an organization-scoped seed load inserts a twin of every such row #24282 leg A;packages/cli/src, the runtime boot guard,app-plugin.ts'soptionalDependencies, plugin-auth), PR refactor(rest): move the /meta object state-machine read and the published read out of rest-server.ts (#23050 slice 13e) #24330 / docs(adr-0119): the /batch atomic-refusal precedent anchor names batch-routes.ts (#23050 slice 13e, split-off anchor) #24333 ([maintainer] refactor(rest): splitpackages/rest/src/rest-server.ts(13,701 lines, 70 commits in 30 days) along itsregister*Endpointsseams, one move-only slice per PR,/metaafter #22877 stage 2 #23050;packages/rest, ADR-0119).Generated by Claude Code
objectstack-fleet commentedon Oct 18, 2026
os-dev-report
{ "issue": 24289, "status": "done", "branch": "claude/issue-24289-inprocess-hook-ctx-title", "pr": "https://github.com/objectstack-ai/objectstack/pull/24356", "session": "session_01SYEJqDL3NXxrJRBVqaMu9T (subagent; the dispatching PM's session, as the Claim names)", "premise_still_valid": true, "summary": "Code route as ruled: a hook's inline handler function now gets the same ctx.title(field?) a hook body gets. withInProcessHookTitle (packages/runtime/src/sandbox/body-runner.ts) calls the shared buildTitleSurface. It hands the handler a Proxy view of the engine's hook context that answers `title` and passes every other read and every write through to the engine's context. Absent titles come back as `null`, as the VM delivers them. The read channel is ctx.api, read at call time. bindAppArtifactHandlers wraps each function handler in a copy before ql.bindHooks. The suggested route (one bindHooksToEngine option) was measured infeasible within the ruling: typing it needs IObjectQLEngine.bindHooks opts (packages/spec/src/contracts/objectql-engine.ts:396) and ObjectQL.bindHooks opts (packages/objectql/src/engine.ts:4849). So the wrap is in packages/runtime, and packages/objectql and packages/spec are untouched. No hook-binder option is needed, so nothing is to be declared on the engine seat post. A3 reproduced on main's runtime source: TypeError 'ctx.title is not a function' on the source door, and the artifact door answered. A4: os serve over a config takes the same path (serve.ts:3591 new AppPlugin(config) -> app-plugin.ts:1683 bindAppArtifactHandlers), so this change covers it; that was read from code, and no serve process was started. A5: HookContext declares no `title`, and this change adds none. Clause-② stays no: no published type member and no package entry export (runtime exports only '.', and withInProcessHookTitle is absent from dist/index.d.ts).", "tests": "Final head a14895b71 (merge of origin/main 51ad8e4fb). runtime typecheck: exit 0 (test layer under tsconfig.test.json: OK). runtime 'vitest run --project local --maxWorkers=2': 420 files passed; 6608 tests passed, 19 skipped. verify typecheck: exit 0 (test layer OK). verify 'vitest run --maxWorkers=2': 42 files passed; 364 tests passed. New pins: packages/verify/src/harness.hook-title-door.test.ts (2 tests: artifact door and source door both answer {own:'L1 · Widget', product:'Widget (W-1)', spare:'null'}) and packages/runtime/src/app-artifact-handlers.hook-title.test.ts (2 tests). Repro and ablation, one run at 69be10eef with trap restore: body-runner.ts and app-artifact-handlers.ts were restored to ec749fbb4, hash-equal to the base blobs (6b7ebfb70c, d207bdb165). Runtime was rebuilt. ablation-dist-preflight runtime withInProcessHookTitle --absent: exit 0. Result: the verify pin had 1 failed and 1 passed (source door 'TypeError: ctx.title is not a function'; artifact door green), and the runtime pin had 2 failed with the same TypeError. Restore: git checkout HEAD, hash-equal (fea0f9acfa, f661ee94e9), git diff HEAD empty. Rebuilt; marker present in dist/index.js and dist/index.cjs. Both pins then green (2/2 and 2/2). The preflight's restore-leg exit 3 was its tree reading, which flagged my own concurrent uncommitted changeset edit; its dist reading passed. Direction as predicted: red on the in-process door only.", "gates": "At final head a14895b71: 'node scripts/pm/dispatch-gates.mjs --commands' (no paths) derived 63 families, unchanged from 7bd31c8e5. All 63 ran with exit 0, each exit captured before any pipe. 'pnpm check:dual-build-cjs-loads' first exited 3 (PREREQUISITE NOT MET: no dist for 21 packages); after a full 'turbo run build --filter=!@objectstack/docs' it exited 0 ('111 published require entry point(s) across 69 package(s) load'). On the merged head, 'pnpm check:type-check-debt' first exited 3 because its own closure build queue-timed-out on the lock; rerun under the lock it exited 0 ('82/84 workspace packages type-checked ... none above its recorded number'). '--ran' reconciliation: 63 derived, 63 run, 0 NOT-MEASURED, 0 UNRUN (a derived zero: all exit codes recorded, none 3). The declared WIDE-population families covering packages/runtime and packages/verify also ran, with exit 0 for all 10: check:init-service-contract, live-db-isolation, optional-error-sink, resume-authority-declared, route-envelope, runner-env-posture, settings-bind-window, startup-registry-verdict, verify-stand-in, wildcard-fallthrough. check:meta-type-normalized was skipped because its population is packages/rest/src only. 'pnpm exec eslint --no-inline-config' over the 4 changed .ts files: exit 0, empty output. Not run locally: test:repo and spec check:generated (spec moved only on main's side of the merge), and 'pnpm lint', which belong to CI. CI on PR #24356 at a14895b71, read right after creation: 'No other open PR may claim the same issue' success, 'Packed-tarball smoke (opt-in)' skipped, and the other 21 in_progress (Lint · *, Type Check · *, Check Changeset, Governed Surface Queue Guard, filter, Check PR Size, closing-claim and docs checks). Not waited on, per contract.", "mcp_calls": "0 — no MCP tool used", "api_writes": "2 relay strokes, each a POST /repos/objectstack-ai/objectstack/dispatches (repository_dispatch) under the session token, executed by objectstack-fleet[bot]. (1) pr_create: POST /repos/objectstack-ai/objectstack/pulls (draft) and POST /repos/objectstack-ai/objectstack/issues/24356/assignees (zhuangjianguo); read-back identical, 7159 bytes. (2) This os-dev-report comment: POST /repos/objectstack-ai/objectstack/issues/24289/comments. git push of the branch is not REST.", "open_questions": [], "out_of_scope_findings": [ "class: a · reach: a public door with a measured wrong answer, the bootStack artifact door, which reads an artifact through the runtime's own artifact loader, as os start --artifact does. With 'runtimeModule' plus a string 'handler', typeof ctx.title === 'undefined', so a call throws 'ctx.title is not a function'. The same holds on a source boot with 'functions' plus handler 'name'; an inline function handler on the same boot answers 'n1' (one-off scratch run at 7bd31c8e5, not committed). · evidence: a handler that NAMES a function (the deprecated string form, and what objectstack build ships for a callable it could not lower) runs in-process, but the engine binder resolves it by name past bindAppArtifactHandlers, so this change does not give it the accessor. Covering it needs one hook-binder.ts option plus the two opts types above (spec contract and engine.ts). · same family as #24289, so per contract it is not a single-point card; the seat routes it, and the residual is named in the PR's Acceptance notes · dedupe words: 'ctx.title string handler', 'runtimeModule hook ctx.title', 'in-process hook named function title', 'unlowerable callable ctx.title'", "carrier: PM, for the domain:spec seat · HookContext (HookContextSchema, packages/spec/src/data/hook.zod.ts) declares no 'title'. Measured with tsc --strict against the built spec at a14895b71: '(ctx: HookContext) => ctx.title()' gives TS2339, while an unannotated defineStack inline handler compiles because its ctx is any. Declaring the member is a published-type change (Clause-② yes, spec lane). It is better sequenced after the string-handler residual above, so the declaration does not outrun what the runtime delivers · noted, not filed" ], "deviations": [ "Route: Zone 3's binder option was replaced by a runtime-side wrap at bind time (measurement above); no packages/objectql or packages/spec edit.", "Merged origin/main 51ad8e4fb before opening the PR (AGENTS.md §10), cleanly. It brought packages/runtime/app-plugin.ts and the org-ownership boot guard, so the runtime and verify typecheck and tests and the gate union were re-run on the merged head. Spec moved only on main's side; its check:generated is left to CI.", "The first commit was amended before its first push to drop a 'Refs #24289' line the pre-push trailer gate refused; nothing published was rewritten.", "One-off scratch files (packages/verify/src/zz-scratch-24289-residual.test.ts, src/zz-scratch-a5.ts) were created for the two readings above and deleted right after; git status clean, never committed.", "A4 was measured by code path, not by starting an os serve process.", "Two lock calls returned 99 (verify typecheck, and type-check-debt's internal build) while another dev held the lock; both were re-run under the lock and exited 0. Neither 99 is counted as a result." ], "line_budget": 353, "files_changed": [ ".changeset/24289-inprocess-hook-ctx-title.md", "packages/runtime/src/app-artifact-handlers.hook-title.test.ts", "packages/runtime/src/app-artifact-handlers.ts", "packages/runtime/src/sandbox/body-runner.ts", "packages/verify/src/harness.hook-title-door.test.ts" ] }Generated by Claude Code
objectstack-fleet commentedon Oct 18, 2026
REWORK (patch round 1: the closing keyword only) → PR #24356 comment
6251630897·domain:cliseat 1 · sessionsession_01SYEJqDL3NXxrJRBVqaMu9T· 2026-10-18T15:23Zhandlergets the sharedbuildTitleSurfacethrough a view of the hook context.Part of #24289, because this card's family has a measured residual. A NAMED-function handler (runtimeModule+ stringhandler,functions+ a handler name) runs in-process withoutctx.title, and its route crosses lanes: one binder option and two opts types, one of them in the spec contract.pm:queuefor that slice. TheHookContexttitledeclaration follows it in the spec lane.Generated by Claude Code
objectstack-fleet commentedon Oct 18, 2026
os-dev-report
{ "issue": 24289, "status": "done", "branch": "claude/issue-24289-inprocess-hook-ctx-title", "pr": "https://github.com/objectstack-ai/objectstack/pull/24356", "session": "session_01SYEJqDL3NXxrJRBVqaMu9T (subagent; the dispatching PM's session, as the Claim names)", "premise_still_valid": true, "summary": "Patch round 1 (seat review 6251630897, read from GitHub first): PR body only, no code change. Line 1 is now 'Part of #24289', and the body carries no closing keyword: a regex over fix/fixes/fixed/close/closes/closed/resolve/resolves/resolved found 0 hits in the stored body. '## Acceptance notes' now names the card's remaining slice. That slice is a hook whose handler NAMES a function: runtimeModule plus a string handler on the artifact door, and functions plus a handler name on a source boot. It still runs in-process without ctx.title. Its measured route is one bindHooksToEngine option, plus the IObjectQLEngine.bindHooks opts type (packages/spec/src/contracts/objectql-engine.ts, about :396) and the ObjectQL.bindHooks opts (packages/objectql/src/engine.ts, about :4849), which makes it a cross-lane slice; #24289 remains open for it. The HookContext note sits there too: no title member, TS2339 under tsc --strict for an annotated handler, sequenced after that slice. Before the edit, check-runs on a14895b71 had no red: 31 success, 8 skipped, and the 6 Test Core shards in_progress. So no code change and no push.", "tests": "No code change this round; head stays a14895b71. Round-0 local results at that head stand: runtime typecheck exit 0; runtime vitest --project local 420 files / 6608 tests passed, 19 skipped; verify typecheck exit 0; verify vitest 42 files / 364 tests passed. The repro and ablation pair went red on the in-process door only, then green after restore.", "gates": "No local gate re-run: the diff is unchanged, and the body edit moves no file. CI at a14895b71, read at 15:25Z, after the edit: 46 check-runs; 31 completed success, 8 skipped, 7 in_progress. The in_progress ones are Test Core (1/6..6/6) from the push, plus the edit-triggered re-run of 'Check Changeset'. Edit-triggered guards that completed: 'Part-of PR must not also close its card' success, 'The card this PR closes must claim this branch' success, 'No other open PR may claim the same issue' success. Required set: 'Lint & Repo Gates' success, 'TypeScript Type Check' success, 'Build Core' success, 'Governed Surface Queue Guard' success, 'Test Core' in_progress. Not waited on, per contract.", "mcp_calls": "0 — no MCP tool used", "api_writes": "2 relay strokes this round, each a POST /repos/objectstack-ai/objectstack/dispatches under the session token, executed by objectstack-fleet[bot]. (1) issue_patch on #24356: PATCH /repos/objectstack-ai/objectstack/issues/24356 (body only); relay read-back: 8040 bytes sent, 8040 stored, identical; draft flag unchanged (true); assignee unchanged. (2) This os-dev-report comment: POST /repos/objectstack-ai/objectstack/issues/24289/comments. No git push.", "open_questions": [], "out_of_scope_findings": [ "carrier: PM (platform-readings owner) · reading, not a defect: the relay's issue_patch (REST PATCH /issues/24356 on a PR) was sent with no footer, per the recorded prescription for edits. The stored body read back byte-identical (8040 = 8040; independent gh api read: identical, no footer appended). platform-readings.md:339-342 records a +58-byte bare footer appended on this cell; this edit did not show it. Attribution now lives in a prose line in the body (session id) · noted, not filed" ], "deviations": [ "No code change and no push: the check-runs on a14895b71 had no red before the edit.", "The worktree was removed at the end of round 0, so the relay was run as scripts/pm/with-fleet.sh from the shared checkout /home/user/objectstack. That was read-only execution: git status was clean before and after, and nothing was edited there.", "The PR body's session-URL footer block was dropped from the edited body, following the edit-side prescription (send no footer). The session id moved into a prose line at the end of the body." ], "line_budget": 353, "files_changed": [ ".changeset/24289-inprocess-hook-ctx-title.md", "packages/runtime/src/app-artifact-handlers.hook-title.test.ts", "packages/runtime/src/app-artifact-handlers.ts", "packages/runtime/src/sandbox/body-runner.ts", "packages/verify/src/harness.hook-title-door.test.ts" ] }Generated by Claude Code
objectstack-fleet commentedon Oct 18, 2026
ACCEPT → PR #24356 comment
6251673443·domain:cliseat 1 · sessionsession_01SYEJqDL3NXxrJRBVqaMu9T· 2026-10-18T15:27Z · on heada14895b71,Part of #24289. It lands through the queue once every check is green, and the card is then released topm:queuefor the named-function slice.Generated by Claude Code
objectstack-fleet commentedon Oct 18, 2026
Release:
domain:cliseat 1 ·zhuangjianguo· sessionsession_01SYEJqDL3NXxrJRBVqaMu9T· 2026-10-18T16:01Z · cause: the inline-handler half landed (Part of #24289) · destination:pm:queue, unassigned, for the named-function slice (cross-lane), then the spec lane'sHookContexttitledeclarationLanded: PR #24356 →
c4526a705(Part of #24289)merged: true, merge commitc4526a705, merged 2026-10-18T16:01Z through the merge queue (enqueued 2026-10-18T15:40Z)origin/mainc4526a705is an ancestor oforigin/main, with a single parent (squash). The 5 files and +351 / −2 match what was reviewed ata14895b71gh-readonly-queue/main/*pr-24356*head remains6250629067→ report6251604887→ REWORK (the closing keyword only)6251630897on PR fix(runtime): an in-process hook handler is handed the same ctx.title a hook body gets #24356, pointer6251639062→ report6251662766→ ACCEPT6251673443→ pointer6251680684.handlerfunction, run in-process (abootStacksource boot,os serveover a config), now gets the samectx.title(field?)a hookbodygets. It goes through the onebuildTitleSurface, by a view of the engine's hook context (withInProcessHookTitle,packages/runtime). The source door and the artifact door answer the same, pinned inpackages/verify.handlerNAMES a function (runtimeModulewith a stringhandleron the artifact door;functionswith a handler name on a source boot; whatobjectstack buildships for a callable it could not lower) still runs in-process withoutctx.title. The function is resolved by name inside the engine's binder, past the runtime's wrap. The measured route needs onebindHooksToEngineoption (packages/objectql/src/hook-binder.ts), theIObjectQLEngine.bindHooksopts type (packages/spec/src/contracts/objectql-engine.ts, about:396) andObjectQL.bindHooksopts (packages/objectql/src/engine.ts, about:4849). That is a cross-lane slice, declared at its claim.HookContext(HookContextSchema,packages/spec/src/data/hook.zod.ts) declares notitle; an annotated handler failstsc --strictwith TS2339. Declaring it is a published-type change (Clause-②: yes). It is sequenced after item 1, so the declaration does not run ahead of what the runtime delivers.pm:dispatchedbecomespm:queueand the assignee is cleared.Generated by Claude Code
25 remaining items
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