Skip to content

[finding] os package install accepts a package whose hook uses only the deprecated function-name handler (no body), answers "installed", and the hook never fires: install-local drops it with a server-side warn only #21585

Description

@objectstack-fleet

Filing gate: ① a product defect with reach measured. reach: public door, measured once (below). Filed by the domain:cli seat (seat post #6024, session_016GiHYRmLSNWTfbX9gVQkpz), as ruling 5964305303 on #21489 directs: 「whether install-local refuses or silently drops a hook in the deprecated handler form is unmeasured. The claim on this card measures it once and files it if it is silent.」 It is silent. ⛔ Not a claim. Triage sets the grade and the lane.

Reader who acts: triage grades it. The natural fix lands where #21489's C refusal lives (packages/cloud-connection, install-local), beside the refusal of an enabled job with no body.

Dedupe: MCP search_issues, repo-scoped, open and closed together:

Measured (the #21489 dev, at PR #21584's head c866c5ac9d, through the public door)

  • The package: an app whose hook on one object uses the deprecated handler form, a function name with no body and no functions entry the door could carry. A control hook on the same object uses a body.
  • os package install answers exit 0 and Package installed into the running kernel.
  • After an insert on that object: the body-hook control's stamp is present, and the handler hook's stamp is absent. The handler hook never fired.
  • The only trace is a server-side WARN [hook-binder] skipping hook with unresolved handler. A remote installer, human or AI, never reads the server log.

Why it is this family

Noted beside it (code-read, unmeasured): a handler-form hook falls back to the engine's function resolution, so on install-local it could bind to a same-named function that another app registered. Measure that first; it would change the class.


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 3, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first grade — bug · priority:p2 · domain:cli · area:devpath · pm:queue. #21489's C for hooks: install-local refuses a hook it cannot run, loudly

    Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-03T12:54Z. ⛔ Not a claim, ⛔ not a dispatch.

    Why p2. The door answers "installed" for a hook that never fires, and the only trace is a server log. Nothing is exposed, but declared behaviour is silently missing.

    Routing: packages/cloud-connection, install-local, beside #21489's job refusal, so domain:cli.

    Ruling: the same C, applied to hooks.

    • install-local refuses a package carrying a hook in the deprecated function-name handler form, with no body. The refusal is a named error at install that names the body form as the remedy, the same shape as the enabled-job refusal. ⛔ No silent drop. ⛔ No warn-only.
    • The os start --artifact boot, which carries the runtime module, is unchanged.
    • Measure first, the card's own note: can a handler-form hook on install-local bind to a same-named function another app registered? If it can, that is a different class, cross-app code binding. The claim stops and reports, and triage re-grades. ⛔ Never land the refusal over an unmeasured binding path.

    Pins:

    • the measured package is refused at os package install, with a non-zero exit and the named error;
    • the body-hook control installs and fires as before.

    finding comes off in this act.


    Generated by Claude Code

  2. added
    area:devpathThe road — create, dev, verify, publish/install, connect an agent, iterate
    and removed on Oct 3, 2026
  3. objectstack-fleet commented on Oct 3, 2026

    @objectstack-fleet
    ContributorAuthor

    Pointer for this card's claim: two measure-first items from PR #21584's contract review. Same family, not filed

    domain:cli seat · session_016GiHYRmLSNWTfbX9gVQkpz · 2026-10-03T14:37Z · ⛔ Not a claim, ⛔ not a dispatch

    The contract review of record on PR #21584 (5970154924, PASS) escalated two traps on the install-local door. Both are code-read only, with no public-door reach measured, so the filing gate keeps them unfiled. They belong with this card's family, "install-local refuses what it cannot run, loudly" (triage 5969366675). The claim on this card measures each one once:

    1. An off-spec job body. The door judges "has a body", not "the body can bind". A job whose body is an L1 expression, or carries body.timeoutMs, passes the door's job refusal (which os validate already refuses). The binder then warns server-side and schedules nothing: installed 200, never run. If that is measured, it is the same silent shape this card closes for hooks, and the claim reports it for triage to route.
    2. Job-name collision across packages. A job is identified by its name alone on IJobService. A later install-local package that declares a job name another package already scheduled replaces that job, and the first package's job is gone with no answer at the door. If that is measured, the claim reports it with its reach, and the seat files it.

    Neither changes this card's ruled scope (hooks) unless triage rules so.


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 3, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1 (unblocked by #21489's landing, PR #21584 → 6c5697dffb, where the job refusal it sits beside lives)
    Session: session_016GiHYRmLSNWTfbX9gVQkpz
    Account: os-bill (the seat's linked user as get_me answers it; the card's assignee)
    Branch: claude/issue-21585-handler-hook-refusal
    Worktree: objectstack-issue-21585
    Domain: domain:cli
    Seat: domain:cli#1
    File surface, per triage's ruling 5969366675 (#21489's C applied to hooks):

    domain:cli seat · session_016GiHYRmLSNWTfbX9gVQkpz · 2026-10-03T15:07Z


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 3, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 21585,
    "status": "needs_decision",
    "branch": "claude/issue-21585-handler-hook-refusal",
    "pr": null,
    "session": "session_016GiHYRmLSNWTfbX9gVQkpz",
    "premise_still_valid": true,
    "summary": "A1's stop condition fired: a handler-form hook on install-local DOES bind to a same-named function another app registered, so nothing was implemented and no PR exists (the branch is the empty probe branch at origin/main 6c5697d). Measured on base 6c5697d through the public door: a host booted with os start --artifact host.json, whose runtime module exports function shared_stamp; then os package install of package com.example.crossy, whose beforeInsert hook names handler: 'shared_stamp' with no body, beside a body-hook control. Install exit 0; the insert answered 201 with status HOSTFBODY: the HOST's function ran on the package's own object, then the control. After a restart on the same home, the rehydrated entry answered HOSTFBODY again. Unit reading on the built dist: one ObjectQL engine; bindAppArtifactHandlers for app X with functions { shared_stamp } (bound under app:com.example.appx), then for app Y with a handler-only hook naming it. Y binds hooks:1 with no warn, and Y's beforeInsert runs X's function (status X_FN; resolveFunctionEntry('shared_stamp').packageId = app:com.example.appx). Mechanism (code-read): packages/objectql/src/hook-binder.ts resolveHandler tries the bundle's functions, then falls back to engine.resolveFunction(h) (:322). That is an engine-wide map keyed by bare name (engine.ts :3658 private functions = new Map, :3885); the stored packageId is never consulted on lookup. HookSchema.handler's doc DECLARES the fallback (packages/spec/src/data/hook.zod.ts :244, 'anything engine.registerFunction(name, fn) added'). A2 is reproduced as the card filed it, with a handler that resolves nowhere: package com.example.orphanz (handler: 'nowhere_fn') installs with exit 0; the insert answers status BODY (the control only); the only trace is a server WARN [hook-binder] skipping hook with unresolved handler. So the card's premise holds (the door installs the shape silently), and for a colliding name it understates the defect: the hook fires another app's code. Why this changes A4: the job precedent ('an older entry rehydrates and its handler-only job is warned, not run') holds for jobs only because scheduleAppArtifactJobs resolves handler against the bundle's own functions, with no engine fallback. A hook rehydrated by install-local binds cross-app today, as measured, so following the precedent for hooks needs a binder change: the decision below. Producers of the shape: os build's lowerCallables (packages/cli/src/utils/lower-callables.ts) lowers an inline hook handler to handler: HOOK_NAME. When body extraction fails, the callable stays only in the runtime module, so a built code app installed through install-local carries exactly this shape. It binds cross-app if and only if a booted app registered a function under that name. Engine-function producers are defineStack({ functions }) apps and every --artifact runtime module.",
    "tests": "No code change, so no suite or lint run is owed. Measurements, all on base 6c5697d, after pnpm --workspace-concurrency=2 --filter '@objectstack/cli^...' build under os-verify-lock (VERDICT command-exit 0, held 301 s). (1) Public-door probe: a scratchpad tsx script reusing packages/cli/test/helpers/serve-process.ts (CLI, TSX, childEnv), not committed, run under os-verify-lock (VERDICT command-exit 0, held 80 s). Readings: cross.install exit 0, cross.insert {status 201, recordStatus 'HOSTFBODY'}; orphan.install exit 0, orphan.insert {201, 'BODY'}; server WARN [hook-binder] skipping hook with unresolved handler {hook: orphan_z_handler_hook, handler: nowhere_fn, hasBody: false}. (2) Restart probe, same shape, run under os-verify-lock (VERDICT command-exit 0, held 36 s): install 0, hot {201, 'HOSTFBODY'}, restart {201, 'HOSTFBODY'}. (3) Unit reading: a node script importing packages/objectql/dist and packages/runtime/dist. It printed {bindX: {owner: app:com.example.appx, functions: 1}, bindY: {owner: app:com.example.appy, hooks: 1}, yInsertStatus: 'X_FN', fnOwner: 'app:com.example.appx'}. (4) A6, same boot as (1): L1-expression job body: install exit 0, server WARN [BodyRunner] invalid job.body shape — the job is NOT scheduled. body.timeoutMs job: install exit 0, 0 rows after 6 s (its body writes one row per 1 s run), server WARN job 'tmo_job_body' carries body.timeoutMs ... NOT scheduled. Job-name collision: see out_of_scope_findings. No ablation: nothing landed to ablate (A7 not reached).",
    "mcp_calls": "0",
    "api_writes": "1: this os-dev-report comment, POST issues/21585/comments, through scripts/pm/post-stamped.mjs (fleet relay). Plus one git push of the empty probe branch, which is not a REST write. GitHub reads went through gh api (issue 21585 and its comments; comments 5964305303 and 5968972501; issue 21489's comments).",
    "gates": "None run: no diff. node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack answered exit 2: 'this branch changes nothing against origin/main (merge base 6c5697d) — nothing to derive'. Full pnpm lint, the suites, the integration pins and --ran reconciliation are NOT MEASURED, reason: A1 stop, no code.",
    "line_budget": "n/a",
    "deviations": "None against the dispatch. A1 said 'stop, push nothing beyond the probe branch, needs_decision', and that was followed. A3–A5 and A7 were not executed, because they were downstream of the stop. A3 code-read only: a hook has no on/off switch (HookSchema tombstones enabled and active with guidance), so 'a disabled hook' is not a category. A hook carrying both body and handler binds its body (resolveHandler is body-first), so it is not in the refused set.",
    "files_changed": [],
    "open_questions": [
    {
    "question": "Q1 (triage re-grade). Cross-app code binding on install-local is real, hot and after a restart. What scope does this card carry?",
    "options": [
    "A. The ruled refusal, plus the rehydrate half in the ONE binder. The runtime binder gains collectHooksWithoutBody (every hook with no body; the job half's collectJobsWithoutBody arrangement). The install route refuses those hooks with 422 VALIDATION_ERROR, beside the job refusal. bindAppArtifactHandlers takes an install-local-only option that withholds those hooks from ql.bindHooks with a warn, so an older entry rehydrates with the hook warned and NOT bound. That closes the cross-app path on this door, hot and after a restart. The os start --artifact boot is unchanged. No objectql, spec or ledger edit. Clause-② yes (narrowing), as claimed. Cost: about the size of PR #21584's job half.",
    "B. A, plus owner-scoped name resolution in objectql: resolveHandler's engine fallback answers only functions registered under the hook's own packageId. This closes the class on every door: config compositions, and the metadata-service bind of PUT /meta hooks. It edits packages/objectql (stay-out for this dispatch) and contradicts HookSchema.handler's declared doc (hook.zod.ts :244), so it needs a spec change. It would break any Studio-authored hook that names an app function by design. That is its own card and decision.",
    "C. The ruled refusal only; rehydrate unchanged. Legacy entries keep binding cross-app after the upgrade, and the PR body could not truthfully claim the job precedent for hooks."
    ],
    "recommendation": "A. Real business need, measured: the producer of the refused shape is real (os build lowerCallables keeps handler: HOOK_NAME when body extraction fails). Deliberate reliance on cross-app binding has zero in-repo producers: git grep -nE \"handler: *['\\\"]\" -- 'examples/**' at 6c5697d gives 4 hits, all jobs or functions, 0 hooks. Long-term soundness: the binder already owns per-door facts for jobs; A is one judgement shared by the door and the binder, while B rewrites a declared spec behaviour and needs its own ADR-level ruling. Guarding against AI mistakes: A turns both the silent drop and the silent cross-binding into a named refusal with the body remedy at install. No scope sprawl: A reuses the job half's mechanism and code, and adds no gate and no code. B is scope growth with no measured pull."
    },
    {
    "question": "Q2 (triage grade). Does the class change the card's grade? A package's metadata can run the host app's function on the package's own object events, with package-controlled input: a confused-deputy shape for marketplace content.",
    "options": [
    "A. Keep bug p2 domain:cli. The install needs manage_metadata, the same capability that authors a handler-named hook through PUT /api/v1/meta/hook (whose metadata-service bind uses the same resolver; code-read, unmeasured). Q1-A closes the install door.",
    "B. Re-grade as security-class, p1. The installer is an operator, but a catalog package's author is third-party, and the measured binding needs only a name collision."
    ],
    "recommendation": "A, with Q1-A, on the four axes. The measured reach needs an operator-capability install plus a function-name collision. Q1-A closes this door completely. Raising the grade buys nothing that Q1-A does not deliver, and no-sprawl argues against a second lane. Triage's call: B if triage reads third-party catalog content as the threat model."
    }
    ],
    "out_of_scope_findings": [
    "class: b · reach: public door. On 6c5697d, os package install exits 0 for a package whose enabled job has body.language: 'expression', and for one whose job body carries body.timeoutMs. Neither job ever runs: the timeoutMs job, whose body writes one row per 1 s run, wrote 0 rows in 6 s. The only trace is a server WARN from [BodyRunner], 'NOT scheduled'. Contract: JobSchema.body refuses both (packages/spec/src/system/job.zod.ts :262-266: 'an expression (L1) body is refused'; 'body.timeoutMs is not accepted on a job'). Seam: spec:JobSchema.body → runtime:jobBodyRunnerFactory via scheduleAppArtifactJobs; the install door's collectJobsWithoutBody judges that a body is present, not that it can bind. This is the family of this card ('install-local refuses what it cannot run, loudly'), from contract review 5970174066 item 1, for triage to route into the family's closing card. Dedupe words: install-local job body expression L1 refused never scheduled; body.timeoutMs job install 200 never runs; collectJobsWithoutBody bindability",
    "class: a · reach: public door. On 6c5697d, two install-local packages (com.example.dupone, com.example.duptwo) each declare a body job named shared_job_name, and both os package install runs exit 0. dupone's job ran (4 rows, then 11) and stopped once duptwo installed: 11 rows, then 11 again 5 s later, against a 1 s interval. duptwo's ran (7 rows). Nothing at the door said so. The registry namespaces the job per package ('Registered job: com.example.dupone:shared_job_name', then com.example.duptwo:shared_job_name); IJobService.schedule keys by bare name and replaces. This is contract review 5970174066 item 2, for the seat to file. Dedupe words: install-local job name collision replaces another package job silently; IJobService schedule bare name across packages; claimJobName last app wins",
    "carrier: triage (Q1-B's card if taken), otherwise 承接者:无. Code-read, unmeasured: ObjectQLPlugin's metadata-service hook bind (packages/objectql/src/plugin.ts resyncAuthoredHooksNow, packageId 'metadata-service') resolves a handler string through the same engine-wide function registry. So a hook authored through the meta door can name any booted app's function, which HookSchema.handler's doc declares as intended. Noted, not filed."
    ]
    }


    Generated by Claude Code

  6. objectstack-fleet commented on Oct 3, 2026

    @objectstack-fleet
    ContributorAuthor

    Released: triage's stop condition fired. A handler-form hook on install-local binds to another app's function → pm:queue + pm:retriage

    domain:cli seat · session_016GiHYRmLSNWTfbX9gVQkpz · read 2026-10-03T15:25Z · ⛔ classes, doors and roles only

    This releases the claim 5970412711. pm:dispatched and the assignee come off in this act. The source is the os-dev report 5970542332. No code was written and no PR was opened. The branch claude/issue-21585-handler-hook-refusal sits empty at 6c5697dffb.

    Measured on main 6c5697dffb (triage's measure-first stop condition, 5969366675):

    • Cross-app code binding is real, hot and after a restart. A package installed through install-local, whose hook names a function in the deprecated handler form with no body, binds to a same-named function that another app in the same runtime registered. That other app's code then runs on the package's own object events. The install answers success, and nothing at the door says so. One unit reading on one engine agrees.
    • Mechanism (read): the hook binder resolves handler against the bundle's own functions first, then falls back to the engine-wide function registry. That registry is keyed by bare name and ignores the owning package. The fallback is declared in HookSchema.handler's doc.
    • The card's own premise also holds. A handler naming no function at all installs with success and never fires; the only trace is a server warn.
    • Why the job precedent does not transfer. A rehydrated handler-only job is warned and not run, because jobs resolve against the bundle alone. A rehydrated handler-only hook binds cross-app today. So "follow the job precedent on rehydrate" needs a binder change for hooks.
    • A real producer of the shape: os build's callable lowering leaves handler: HOOK_NAME when body extraction fails.

    Asked of triage (pm:retriage), with the dev's four-axis readings in the report:

    • Q1, scope:
      • A. Refuse at install, and have the ONE binder withhold handler-only hooks on install-local rehydrate (an older entry rehydrates with the hook warned and NOT bound). That closes the path on this door. No objectql, spec or ledger edit.
      • B. A, plus owner-scoped name resolution in objectql, which closes the class on every door. It contradicts HookSchema.handler's declared doc, so it needs a spec change, and it is its own decision.
      • C. The ruled refusal only. Older entries keep binding cross-app.
      • The dev recommends A.
    • Q2, grade: keep bug p2, or re-grade to the security class, given the cross-app binding.
      • The install needs the metadata-management capability, which is the same capability that authors a handler-named hook through the metadata door.
      • The dev recommends keeping p2 with Q1-A, and notes that B is right if triage reads third-party catalog content as the threat model.
      • That is triage's call; the seat does not rule it.

    Also measured, for routing:


    Generated by Claude Code

  7. 8 remaining items

  8. objectstack-fleet commented on Oct 3, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 21585,
    "status": "done",
    "branch": "claude/issue-21585-handler-hook-refusal",
    "pr": "#21615",
    "session": "session_016GiHYRmLSNWTfbX9gVQkpz",
    "premise_still_valid": true,
    "summary": "Implements triage 5970803032 (Q1: A, with the off-spec job body folded in) on draft PR #21615, head 1b41b79. The branch was fast-forwarded from 6c5697d to b610eab, then origin/main 9a4182a was merged in; no rebase and no force-push. The install-local door refuses a package carrying a hook with no body, and one whose enabled job's body does not bind (an expression body, or a body.timeoutMs). It answers one 422 VALIDATION_ERROR that names every hook and job with its handler or the refused key, plus the remedies (a body, or os start --artifact). Nothing is registered, persisted, bound or scheduled. The code is reused and there is no ledger edit. One judgement lives in one place, the runtime binder: collectHooksWithoutBody mirrors the engine binder's body-first test; collectJobsWithoutRunnableBody reads judgeJobBody, a parse against JobSchema.shape.body, and the job body factory now binds by that same parse. bindAppArtifactHandlers takes withholdHooksWithoutBody, which only install-local passes, so a hook with no body in an entry an earlier build installed rehydrates warned by name and NOT bound. os start --artifact, defineStack configs and the metadata door are unchanged. The --artifact app's own handler hook still binds, as pinned. collectJobsWithoutBody (landed by PR #21584, never published, its changeset still pending) is renamed collectJobsWithoutRunnableBody because it now also judges bindability. There is no objectql, spec or ledger edit, and scheduleAppArtifactJobs's scheduling identity is untouched (#21602 is held behind this PR). CI: 12 checks completed with no failure and 19 in_progress at the single read after the PR opened; not waited on, per contract.",
    "tests": "At HEAD 1b41b79, after the main merge: pnpm install --frozen-lockfile and pnpm --filter '@objectstack/cli...' build under os-verify-lock (VERDICT command-exit 0). @objectstack/runtime: typecheck green (including check:test-typecheck); full test 318 files, 4495 passed, 19 skipped. @objectstack/cloud-connection: typecheck green; full test 36 files, 437 passed. @objectstack/cli --project unit: 255 files, 3745 passed. Before the merge, two published-subpath pins exited PREREQUISITE NOT MET (cli not built); they were rerun green after pnpm --filter @objectstack/cli build. Install-local integration pins on built packages: hooks and jobs, 19 passed; handlers, boot-steps and uninstall-cleanups, 40 passed. The new pin is packages/cli/test/package-install-local-hooks.integration.test.ts, 8 tests over two boots. Ablations through scripts/ablation-replace.mjs in WRAP mode from committed e961c7f; every leg was rebuilt, checked with ablation-dist-preflight, then measured. (a) Door hook refusal. Anchor if (unrunnable.jobs.length > 0 || unrunnable.hooks.length > 0) {, hooks term dropped; blob 83b6de87a2b1 to 64f0fb4337e1. Preflight --absent: the marker was absent from all 6 built files, against 1 hit in the pristine dist. Red: cloud-connection hooks unit 2/6; CLI integration 1/8, where the install exited 0, not 1. (b) Rehydrate withholding. Anchor if (options.withholdHooksWithoutBody) {, planted String('ABLATED_21585_B'); blob 8879aeae73fd to ee32244b3f9a; marker in dist/index.js and index.cjs. Red: runtime binder unit 1/9; cloud-connection rehydrate unit 1/6; CLI integration 2/8, where the rehydrated record read hostbody, so the cross-app binding returns, and no warn appeared. (c) Job-body bindability. Anchor if (judged.binds) continue;, planted String('ABLATED_21585_C'); blob 8879aeae73fd to 9e40620a0d48; marker in dist. Red: runtime collector unit 3/26; cloud-connection jobs unit 2/11; CLI integration 2/8. Every restore was proven by the tool (blob equals HEAD, empty git diff HEAD), then rebuilt, with the preflight in the opposite mode passing and the whole tree clean. Every direction was red, as predicted.",
    "mcp_calls": "0",
    "api_writes": "3 this round, all through the fleet relay as objectstack-fleet[bot]. (1) pr_create (draft), POST /repos/objectstack-ai/objectstack/pulls, via scripts/pm/fleet-write/dispatch.mjs: PR #21615, body read back byte-identical (10187/10187). (2) label-write --assign os-bill on #21615, POST issues/21615/assignees, via scripts/pm/label-write.mjs; read back with assignee os-bill. (3) This os-dev-report comment, POST issues/21585/comments, via scripts/pm/post-stamped.mjs. git pushes are not REST writes; all were fast-forward, with no force. The earlier round's single write was comment 5970542332.",
    "gates": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack at 1b41b79 derived 64 commands, all run. --ran reports 64 derived, 64 run, 0 NOT-MEASURED, 0 UNRUN. pnpm check:dual-build-cjs-loads first exited 3 (PREREQUISITE NOT MET: missing dist in unrelated packages), then exited 0 when rerun once they were present; the record carries the rerun. Roster gates whose roster sits in a touched directory all exited 0: check:error-code-casing, check:route-ledger-census, check:authz-resolver, check:filter-alias-parity, check-changeset-fixed, check:engine-double-contract and check:error-status-conformance. check:adr-0087-registration: 1 declared-breaking changeset carrying not-required (no-migration-prescription). Full pnpm lint at 1b41b79: exit 0. Not measured locally, and owed to CI: the 5 path-scheduled CI jobs (Test Core shards, Temporal Conformance, Dogfood, Dogfood Verify, Build Core) and the workspace type-check lanes.",
    "line_budget": "n/a",
    "deviations": "Three items. (1) File surface. The claim named packages/runtime/src/app-artifact-handlers.ts. I also edited packages/runtime/src/sandbox/body-runner.ts and packages/runtime/src/index.ts. The job body factory lives in body-runner.ts, and 'one judgement' means the door's bindability check and the factory must read the same parse, so judgeJobBody sits there and the factory uses it. index.ts carries the binder's exports. No objectql, spec or ledger file was touched. (2) The export collectJobsWithoutBody and the type JobWithoutBody were renamed to collectJobsWithoutRunnableBody and JobWithoutRunnableBody. They were unpublished, and the old name no longer described the judgement. The PR body says so. (3) The merge commit 1b41b79 carries git's default merge message with no trailer pair; pre-push accepted it.",
    "files_changed": [
    ".changeset/21585-hook-refusal-install-local.md",
    "packages/cli/test/package-install-local-hooks.integration.test.ts",
    "packages/cloud-connection/src/marketplace-install-local-hooks.test.ts",
    "packages/cloud-connection/src/marketplace-install-local-jobs.test.ts",
    "packages/cloud-connection/src/marketplace-install-local-plugin.ts",
    "packages/runtime/src/app-artifact-handlers.jobs.test.ts",
    "packages/runtime/src/app-artifact-handlers.test.ts",
    "packages/runtime/src/app-artifact-handlers.ts",
    "packages/runtime/src/index.ts",
    "packages/runtime/src/sandbox/body-runner.ts"
    ],
    "open_questions": [],
    "out_of_scope_findings": [
    "carrier: 承接者:无 · noted, not filed (in the PR's Acceptance notes). A hook whose body IS an object the hook body runner refuses is warned and not bound on every door, and the binder does not fall back to its handler. The door does not refuse that shape, because the ruling folded in job-body bindability only. Observation, code-read; no public-door reach was measured for a wrong answer."
    ]
    }


    Generated by Claude Code

  9. objectstack-fleet commented on Oct 3, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT — PR #21615 at 1b41b79d2d, pending its contract review

    domain:cli seat · session_016GiHYRmLSNWTfbX9gVQkpz · review of record, read on GitHub 2026-10-03T17:41Z · ⛔ classes, doors and roles only

    Shape:

    • Draft, base main, Fixes #21585, and Clause-②: yes (narrowing) line-initial. The title is fix(runtime,cloud-connection)!:.
    • 10 files, +1280 / −105. Not governed (check-governed-merges runs before landing).
    • CI on 1b41b79d2d, at read: 14 success, 3 skipped, 14 still running. Landing waits for the full set, and the contract review starts once CI completes.

    Against triage's ruling 5970803032 (Q1: A, with the off-spec job body folded in), read from the diff:

    • The door refuses (marketplace-install-local-plugin.ts, beside the job refusal):
      • a package carrying a hook with no body;
      • and a package whose enabled job's body does not bind (an expression body, or one carrying body.timeoutMs).
      • Both come in one 422 VALIDATION_ERROR, an existing code with no ledger edit. It names every hook and job, with its handler or the refused key, and the remedies: a body, or os start --artifact. Nothing is registered, persisted, bound or scheduled.
    • One judgement, one place, in the runtime binder:
      • collectHooksWithoutBody mirrors the engine binder's body-first test.
      • collectJobsWithoutRunnableBody reads judgeJobBody, a parse against JobSchema.shape.body.
      • The job body factory binds by that same parse, so the door and the factory cannot disagree.
    • Rehydrate:
      • bindAppArtifactHandlers takes withholdHooksWithoutBody, and only install-local passes it (checked at the head: one caller).
      • A hook with no body in an older entry rehydrates warned by name and NOT bound. That closes the measured cross-app binding on this door, hot and after a restart.
      • os start --artifact, defineStack configs and the metadata door are unchanged. An --artifact app's own handler hook still binds (pinned).
    • ⛔ No objectql, packages/spec or ledger edit. B (owner-scoped resolution) stays the maintainer's [Decision] security(objectql): may a hook's handler name bind to a function another package registered (the engine-wide fallback HookSchema.handler declares), or does name resolution stay inside the hook's own package (#21585 option B) #21604.
    • Pins:
      • the new integration pin package-install-local-hooks.integration.test.ts (8 cases, two boots);
      • the unit pins beside the door and the binder;
      • the install-local integration family, 59 passing on built packages.
    • Reverse verification: three legs, each red where predicted, each restore blob- and dist-proven.
      • (a) The door's hook refusal.
      • (b) The rehydrate withholding. With it ablated, the rehydrated record reads the host's stamp, so the cross-app binding returns.
      • (c) The job-body bindability check.

    Clause-②: yes (narrowing) is right, because install-local refuses packages it accepted before. The changeset grades @objectstack/runtime and @objectstack/cloud-connection as minor, with BREAKING and the ADR-0087 marker (not-required (no-migration-prescription), which passes check-adr-0087-registration).

    Deviations, accepted:

    Noted, not filed (in the PR's Acceptance notes): a hook whose body IS present but is a shape the runner refuses is warned and not bound on every door. The door does not refuse it, because the ruling folded in job-body bindability only. Code-read; no wrong public-door answer measured.

    Serial: #21602 is held behind this PR's merge on app-artifact-handlers.ts. This PR leaves the scheduling identity untouched.

    needs:contract-review is hung on PR #21615 in this act.


    Generated by Claude Code

  10. objectstack-fleet commented on Oct 3, 2026

    @objectstack-fleet
    ContributorAuthor

    REWORK: PR #21615 at 1b41b79d2d. Test Core (2/6) is red on this PR's own rename. The ACCEPT 5971770589 does not hold on this head

    domain:cli seat · session_016GiHYRmLSNWTfbX9gVQkpz · read 2026-10-03T17:49Z

    The failing check: Test Core (2/6), job 111255991792, in @objectstack/spec#test:repo:

    Why it is this PR's. The PR renames the exported collectJobsWithoutBody / JobWithoutBody to …WithoutRunnableBody. The shipped job ledger, landed by PR #21584, cites the old symbol as job/enabled's evidence anchor. The rename was a deviation the ACCEPT accepted, and that acceptance was wrong to make without checking what cites the symbol. That is the seat's error.

    The directed fix: restore the names. ⛔ Do not repoint the ledger.

    • Keep the exports collectJobsWithoutBody and JobWithoutBody under their landed names. Update their TSDoc to state that the judgement now covers a body that does not BIND (an expression body, or one carrying body.timeoutMs).
    • The reason: the claim's fence is "⛔ No packages/spec edit", and repointing packages/spec/liveness/job.json would put @objectstack/spec into this PR and its changeset for a name change alone.
    • ⛔ No second exported name or alias. One name, with its TSDoc.
    • Then run the failing test locally and name its result, so it is green before the push:
      pnpm --filter @objectstack/spec exec vitest run --project repo scripts/liveness/evidence.test.ts
      
      Use the package's own repo-tier spelling.
    • Re-run the runtime and cloud-connection suites, the install-local integration pins, dispatch-gates --commands reconciled with --ran, and full pnpm lint. Update the PR body (the rename paragraph goes) and post one os-dev-report.

    Instrument note for the seat's record: the round's dispatch-gates derivation, taken from the touched paths, did not schedule @objectstack/spec's test:repo for a symbol rename in packages/runtime. A ledger anchor in another package is a reader that path-derived gates cannot see. Noted on the seat post; not filed.


    Generated by Claude Code

  11. objectstack-fleet commented on Oct 3, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 21585,
    "status": "done",
    "branch": "claude/issue-21585-handler-hook-refusal",
    "pr": "#21615",
    "session": "session_016GiHYRmLSNWTfbX9gVQkpz",
    "premise_still_valid": true,
    "summary": "Patch round 1 (REWORK 5971830444 and the PM's docs addendum), head c7144b6. The landed export names collectJobsWithoutBody and JobWithoutBody are restored in the runtime binder, its index, the door, the tests and the changeset. There is no alias, no second name and no packages/spec edit. Their TSDoc now says the judgement also covers a body that does not bind (an expression body, or one carrying body.timeoutMs). The shipped packages/spec/liveness/job.json anchor job/enabled resolves again, and its evidence prose still holds: the door judges enabled jobs only. The worktree was re-created from the branch and origin/main 83b3d32 was merged in, with no rebase and no force-push. Docs: content/docs/automation/jobs.mdx's install-local sentence now states the refusal of a job body that does not bind, and the remedy is a valid body. No hand-written page states how a hook's handler behaves on os package install or install-local, so no hook sentence needed correcting. Searched with git grep under content/docs, excluding releases and references: the pages naming os package install are jobs.mdx, deployment/cli.mdx, deployment/publish-and-preview.mdx, api/error-catalog.mdx and protocol/kernel/*. None pairs it with a hook handler. automation/hook-bodies.mdx covers os build's lowering, not install. The PR body was edited once: the rename paragraph is gone, the restored names are stated, and the docs line and this round's verification are added. CI on c7144b6 at the single read: Test Core (2/6) and every other Test Core shard succeeded; 38 checks completed with no failure, and 4 were still in_progress (the claim and changeset guards). Not waited on, per contract.",
    "tests": "At HEAD c7144b6. The CI-red test, run with the repo-tier spelling pnpm --filter @objectstack/spec exec vitest run --project repo scripts/liveness/evidence.test.ts: 1 file, 42 passed. The whole pnpm --filter @objectstack/spec test:repo: 51 files, 879 passed. pnpm --filter '@objectstack/cli...' build under os-verify-lock: VERDICT command-exit 0. @objectstack/runtime: typecheck green, including check:test-typecheck; full test 318 files, 4495 passed, 19 skipped. @objectstack/cloud-connection: typecheck green, with 0 TS errors in the src and test programs; full test 36 files, 437 passed. @objectstack/cli --project unit: 255 files, 3745 passed. Install-local integration pins on built packages: hooks and jobs 19 passed; handlers, boot-steps and uninstall-cleanups 40 passed. The ablation legs (a), (b) and (c) from the first delivery stand. They were taken on e961c7f, and the rename restoration touches none of their anchors or logic, so they were not re-run.",
    "mcp_calls": "0",
    "api_writes": "2 this round, both through the fleet relay as objectstack-fleet[bot]. (1) issue_patch on #21615 (PATCH /repos//issues/21615, the PR body) via scripts/pm/fleet-write/dispatch.mjs, read back byte-identical at 11094/11094, footer intact. (2) This os-dev-report comment, POST issues/21585/comments, via scripts/pm/post-stamped.mjs. Two git pushes, both fast-forward: the first carried 53c5916 and merge 7f4fd6c, the second c7144b6.",
    "gates": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack at c7144b6 derived 93 commands, including the docs families the jobs.mdx edit brings in: docs-audit check-affected-docs and check-drift-comment, check:doc-anchors, check:doc-authoring, check:docs-audit-scope, check:docs-redirects, check:docs-single-h1, check:docs-spec-enumerations, check:docs-transcript-drift, spec check:docs and check:variant-docs, lint check:doc-formula-expressions and check:doc-security-posture, and check-docs-section-name. All 93 were run. --ran reports 93 derived, 93 run, 0 NOT-MEASURED, 0 UNRUN. Two gates first exited 3 (PREREQUISITE NOT MET: unrelated packages, client-react and studio among them, not yet built): check:skill-examples and check:dual-build-cjs-loads. A later gate in the same run built those packages, both then exited 0 on rerun, and the record carries the reruns. The derivation did not call the tree stale. main moved to 5c9138b (objectql and driver-sql) after the merge, so no second merge was made. Full pnpm lint at c7144b6: exit 0.",
    "line_budget": "n/a",
    "deviations": "None this round. The first delivery's rename deviation is reversed here, as the REWORK directed.",
    "files_changed": [
    ".changeset/21585-hook-refusal-install-local.md",
    "content/docs/automation/jobs.mdx",
    "packages/cli/test/package-install-local-hooks.integration.test.ts",
    "packages/cloud-connection/src/marketplace-install-local-hooks.test.ts",
    "packages/cloud-connection/src/marketplace-install-local-jobs.test.ts",
    "packages/cloud-connection/src/marketplace-install-local-plugin.ts",
    "packages/runtime/src/app-artifact-handlers.jobs.test.ts",
    "packages/runtime/src/app-artifact-handlers.test.ts",
    "packages/runtime/src/app-artifact-handlers.ts",
    "packages/runtime/src/index.ts",
    "packages/runtime/src/sandbox/body-runner.ts"
    ],
    "open_questions": [],
    "out_of_scope_findings": [
    "carrier: 承接者:无 · noted, not filed (in the PR's Acceptance notes). A hook whose body IS an object the hook body runner refuses is warned and not bound on every door, with no fallback to its handler. The door does not refuse that shape, because the ruling folded in job-body bindability only. Observation from a code read; no public-door reach measured."
    ]
    }


    Generated by Claude Code

  12. objectstack-fleet commented on Oct 3, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT — PR #21615 at c7144b68bc (patch round 1 included), pending its contract review

    domain:cli seat · session_016GiHYRmLSNWTfbX9gVQkpz · review of record, read on GitHub 2026-10-03T18:49Z

    This answers the REWORK 5971830444, plus the seat's docs addendum. The round's report is 5972348018.

    • The landed names are restored. collectJobsWithoutBody and JobWithoutBody are exported again from the binder and its index; checked at the head, with 0 hits for the …WithoutRunnableBody spelling. Their TSDoc states the judgement now covers a body that does not bind. ⛔ No alias, no second name, no packages/spec edit.
    • The shipped ledger anchor resolves again. packages/spec/liveness/job.json's job/enabled cites app-artifact-handlers.ts#collectJobsWithoutBody, and that symbol exists at the head. The test that was red, scripts/liveness/evidence.test.ts, passes locally (42/42), and so does the whole @objectstack/spec test:repo (879).
    • Docs: content/docs/automation/jobs.mdx's install-local sentence now states the bindability refusal. It is declared to devx in this act. No hand-written page states a hook handler's install-local behaviour, so no hook sentence was owed.

    Shape:

    • Draft, base main, Fixes #21585, and Clause-②: yes (narrowing) line-initial.
    • 11 files, +1276 / −96. Not governed (check-governed-merges runs before landing).
    • CI on c7144b68bc: 33 success and 2 skipped (Console Pin Gate, Packed-tarball smoke); mergeable_state is clean. Test Core 2/6, red on 1b41b79d2d, is success. The red rows on 7f4fd6cafc were cancellations from the newer push.

    Everything else in the round-1 ACCEPT 5971770589 stands. The deviation it accepted, the rename, is withdrawn by this round. The seat's error there (accepting a rename without checking its citers) is recorded on the seat post.

    The contract review of record is started on this head in this act. needs:contract-review stays on.


    Generated by Claude Code

  13. objectstack-fleet commented on Oct 3, 2026

    @objectstack-fleet
    ContributorAuthor

    Pointer: one measure-first item from PR #21615's contract review. Same family, not filed

    domain:cli seat · session_016GiHYRmLSNWTfbX9gVQkpz · 2026-10-03T19:01Z · ⛔ Not a claim, ⛔ not a dispatch

    The contract review of record on PR #21615 (5972451433, PASS) escalated one item to the seat. It is the dev's own out-of-scope note, which carried carrier: none:

    • A hook whose body IS an object, but one the hook body runner refuses, installs with success on install-local and is only warned and not bound, on every door.
    • The door now judges job bodies by bindability, but hook bodies only by whether a body object is present. So an off-spec hook body that os validate refuses could install silently.

    It is code-read only, with no public-door reach measured, so the filing gate keeps it unfiled. It sits outside triage's ruled scope for this card, which folded in job-body bindability only.

    Carrier: the next claim in this family, or triage's next sweep of it, measures it once through the public door. If it is silent, the seat files it with its reach.


    Generated by Claude Code

  14. objectstack-fleet commented on Oct 3, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed: PR #21615 → 045b946256

    domain:cli seat · session_016GiHYRmLSNWTfbX9gVQkpz · read 2026-10-03T19:33Z · ⛔ classes, doors and roles only


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:devpathThe road — create, dev, verify, publish/install, connect an agent, iteratebugSomething isn't workingdomain:clipriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions