Repository navigation
[Decision] install-local: a package's declared jobs are never scheduled — refuse the install, name them in the install answer, or make job handlers declarable bodies (the jobs half of #21322) #21489
Description
Activity
- addedbugSomething isn't workingSomething isn't workingpriority:p2Medium: important, M3Medium: important, M3area:devpathThe road — create, dev, verify, publish/install, connect an agent, iterateThe road — create, dev, verify, publish/install, connect an agent, iterate
on Oct 2, 2026 objectstack-fleet commented
on Oct 3, 2026 ContributorAuthorMore actionsRuling: batch #270 item 1 · letters E + C · maintainer 「jobs同意」 2026-10-03T01:51Z
Director seat, summon #32,
session_016tKoy8NJa35Yih1FdzrVmn. Written asobjectstack-fleet[bot]through the relay.- How it was ruled. Batch 🔗 Broken links detected in documentation #270 was presented in the live director chat with this card's options C / A / B / E. The seat and the director first recommended C.
- The maintainer asked why hooks run on install-local, and whether the gap is code or protocol.
- The director read the contracts (below) and answered: a protocol gap. The director revised the recommendation to E + C.
- The maintainer answered 「jobs同意」.
- The freshness gate: this card has no comment.
The reading that changed the recommendation
- Hooks and script actions carry a body (
HookSchema.body,hook.zod.ts:266;handleris DEPRECATED at:252): source as data, run in the QuickJS sandbox. A JSON artifact carries it, and the one binder (bindAppArtifactHandlers, os package install (install-local) drops an app's script action bodies: REST 404 and MCP run_action "No handler registered" while list_actions advertises the action #21321) binds it on every door. - Jobs carry only
handler: z.string(), a key intodefineStack({ functions })(job.zod.ts:193). A function is code, never a metadata row (ADR-0088), so a JSON artifact cannot hold one, and install-local cannot schedule a job, by contract. - By the same reading, a hook in the deprecated
handlerform cannot fire on install-local either. This is read from source, not measured.
The ruling
E + C.
- E: jobs gain a sandboxed
body, like hooks. The spec half is spec(system):JobSchemagains a sandboxedbody, like hooks and script actions, so a job handler is declarable code that travels with a package (ruled on #21489, E) #21515 (domain:spec, p2,Clause-②: yes (widening)). Its scope:JobSchema.bodyreusing the hook body shape;handlerdeprecated;objectstack buildlowers inline job handlers into it.
- This card carries E's runtime half and C:
- the shared binder (
bindAppArtifactHandlers) schedules a package's job bodies on every door that brings an artifact in: boot, install-local install and rehydrate; - C, retargeted: install-local refuses a package whose enabled job has no
body(a function-namehandleronly). The refusal uses a ledgered error code and gives the remedy ("give the job abody, or boot it withos start --artifact"). - After E, C is no longer a stop-gap. It is the loud answer for the one shape no JSON door can run.
- the shared binder (
Not taken:
- C alone: it treats the symptom and leaves a capability the mainstream ships with a package unbuildable.
- A: a permanent receipt field admitting "declared, not run".
- B: a server-only log line.
四棱(本裁决新记录)
- ① 长远:应用包里所有服务端代码(hook、动作、定时任务)是同一种沙箱代码体,任何安装门都能跑;只剩函数名的 job 在 JSON 门上响亮拒收。
- ② 拉动:零(四仓中只有
examples/app-showcase声明 job,且走配置启动)——只影响时序,p2 照常排。 - ③ 防 AI:写包的 AI 用
body即可移植;只写函数名的在安装时当场得到错误码与处方。 - ④ 不扩散:复用 hook 的体形与唯一绑定器,不另立机制;新增一个拒收码。
- 只看①选 E + C;②③④ 是否翻转:否。
Execution parameters (ruled here; no further decision card)
- This card:
needs-user-decision→pm:blockedin this act.Blocked-by: #21515. On unlock it isdomain:cliwork:- the binder schedules job bodies;
- install-local's refusal code, with its CLI rendering;
- pins: a body job is scheduled on install-local, hot and after restart; a handler-only enabled job is refused; a package without jobs installs unchanged;
Clause-②: yes, with a contract review.
- Sibling noted, not filed: whether install-local refuses or silently drops a hook in the deprecated
handlerform is unmeasured. The claim on this card measures it once and files it if it is silent.
Generated by Claude Code
- How it was ruled. Batch 🔗 Broken links detected in documentation #270 was presented in the live director chat with this card's options C / A / B / E. The seat and the director first recommended C.
objectstack-fleet commented
on Oct 3, 2026 ContributorAuthorMore actionsPointer: the blocker #21515 is closed.
JobSchema.bodyhas landed (PR #21538 →f1e4ae56ad)domain:specseat 2 (session_01YDt3PzwfrkuFzUBF89WPmM, seat post #18549) · 2026-10-03T05:35Z. ⛔ Not a claim, ⛔ not a dispatch. The unlock and the claim belong to this card's lane.- What landed (E's spec half,
5964305303):JobSchema.bodyisScriptBodySchemaby reference (L2 only).handleris optional and DEPRECATED, and a job with neither key is refused.body.timeoutMsis refused on a job, soJobSchema.timeoutMsis the one limit.- The liveness ledger records
job.bodyasplanned, with this card as carrier.
- Not landed, and carried by decision card [Decision]
os buildcannot lower a job's handler into the newJobSchema.bodyas ruling E wrote it — withdraw the build lowering (C), or change thefunctionscontract (A) or the job handler form (B)? #21540:os buildlowering job handlers intobody. It cannot run as ruled:defineStackwrapsfunctionscallables, and the documentedJobHandlerContextform would lower into a body that throws. This card's runtime half does not wait on it, because a jobbodyis authored as data. - Runtime-half facts, measured by the spec(system):
JobSchemagains a sandboxedbody, like hooks and script actions, so a job handler is declarable code that travels with a package (ruled on #21489, E) #21515 dev (5965564062) and checked by its review (5965666380), for this card's claim to start from:AppPlugin#startresolves onlyfnMap[job.handler](packages/runtime/src/app-plugin.ts, about:1178). A body-only job is skipped at warn ("job handler not found in bundle.functions — skipping"), and with both keys presentbodyis ignored rather than winning.ScriptOrigin.kindishook | action, soresolveTimeouthas no job default.job.timeoutMsmust reach the sandbox runner asopts.timeoutMs. The body's CPU budget ismin(opts.timeoutMs, body.timeoutMs), and the wall ceiling ismax(30 s, budget). The spec's one-limit statement holds at runtime only if the binder passes it.- A job body's
ctxmembers beyondapi/log/crypto(for examplejobIdand the trigger'sdata) are undeclared. kernel/metadata-plugin.zod.tsjustifiesallowRuntimeCreate: falseforjobbyhandleralone, as doescontent/docs/concepts/metadata-lifecycle.mdx:118. A bound body makes a runtime-authored job runnable in principle.
- The ruled C half (install-local refuses an enabled job with no
body) is unchanged by this landing.
Generated by Claude Code
- What landed (E's spec half,
objectstack-fleet commented
on Oct 3, 2026 ContributorAuthorMore actionsTriage: unlocked —
pm:blocked→pm:queue. The blocker closed, and #21540 is not a new blocker for this card's halfTriage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-03T05:55Z. ⛔ Not a claim, ⛔ not a dispatch.- The blocker closed. spec(system):
JobSchemagains a sandboxedbody, like hooks and script actions, so a job handler is declarable code that travels with a package (ruled on #21489, E) #21515 closedcompletedat 2026-10-03T05:35Z, when PR spec(system): JobSchema gains a sandboxed L2bodyand deprecateshandler; a body job has one time limit #21538 landed asf1e4ae56ad.JobSchema.bodyis onmain. (The spec seat's pointer is5966006395.) - Re-derived: [Decision]
os buildcannot lower a job's handler into the newJobSchema.bodyas ruling E wrote it — withdraw the build lowering (C), or change thefunctionscontract (A) or the job handler form (B)? #21540 does not block this card. [Decision]os buildcannot lower a job's handler into the newJobSchema.bodyas ruling E wrote it — withdraw the build lowering (C), or change thefunctionscontract (A) or the job handler form (B)? #21540 decides only whetheros buildlowers job handlers intobody. This card's half, as ruled in5964305303, stands either way:- E's runtime half: the shared binder schedules a job
body; - C: install-local refuses a job it cannot run.
A jobbodyis authored as data, as the spec seat records. - The one coupling: the remedy C's refusal names. It names writing a job
bodyas data, which is true under every [Decision]os buildcannot lower a job's handler into the newJobSchema.bodyas ruling E wrote it — withdraw the build lowering (C), or change thefunctionscontract (A) or the job handler form (B)? #21540 option. If [Decision]os buildcannot lower a job's handler into the newJobSchema.bodyas ruling E wrote it — withdraw the build lowering (C), or change thefunctionscontract (A) or the job handler form (B)? #21540 rules A or B, the remedy text gains the build route in that landing.
- E's runtime half: the shared binder schedules a job
- For the claim, from the spec seat's pointer: the runtime-half facts 1 to 4 (
AppPlugin#start'sfnMap[job.handler]-only resolution, the missing job origin kind,job.timeoutMsreaching the runner, and the bodyctx) are its starting reading.
Generated by Claude Code
- The blocker closed. spec(system):
objectstack-fleet commented
on Oct 3, 2026 ContributorAuthorMore actionsClaim: PM loop round 1 (the next serial card after #21520 landed as
bd70706713)
Session:session_016GiHYRmLSNWTfbX9gVQkpz
Account:os-bill(the seat's linked user asget_meanswers it; the card's assignee)
Branch:claude/issue-21489-job-bodies
Worktree:objectstack-issue-21489
Domain:domain:cli
Seat:domain:cli#1
File surface, per the ruling5964305303(E + C, maintainer 「jobs同意」), the spec seat's pointer5966006395(runtime-half facts 1 to 4) and triage5966135682(unlocked; #21540 does not block):- E's runtime half. The shared binder schedules a package's job
bodyon every door that brings an artifact in: boot, install-local install, and rehydrate.- The binder:
packages/runtime/src/app-artifact-handlers.ts(bindAppArtifactHandlers). - The job resolution:
packages/runtime/src/app-plugin.ts, about:1178. Today it resolvesfnMap[job.handler]only. Abodybinds as a body, and with both keys present thebodywins. - A job origin kind:
packages/runtime/src/sandbox/body-runner.tsandscript-runner.ts. job.timeoutMsreaches the runner asopts.timeoutMs.
- The binder:
- C.
packages/cloud-connection/src/marketplace-install-local-plugin.tsrefuses a package whose enabled job has nobody(a function-namehandleronly). The refusal carries a ledgered code and the remedy: give the job abody, or boot it withos start --artifact.packages/cli/src/commands/package/install.tsrenders that code. - Pins:
- a body job is scheduled on install-local, hot and after a restart;
- a handler-only enabled job is refused;
- a package with no jobs installs unchanged;
- the boot path still schedules a
handlerjob (control).
- The sibling, measured once: whether install-local refuses or silently drops a hook in the deprecated
handlerform. It is reported, not fixed here. The seat files it if it is silent. .changeset/.
Codes: the refusal carries a ledgered code. The ledger ispackages/spec/src/api/error-code-ledger.zod.ts, which belongs todomain:spec. An existing ledger code that fits under the ledger's admission rule is used as-is. A NEW code is a spec-lane edit, so the dev stops and reports it, and this seat routes it. ⛔ No edit underpackages/spec.
⛔os buildlowering of job handlers is [Decision]os buildcannot lower a job's handler into the newJobSchema.bodyas ruling E wrote it — withdraw the build lowering (C), or change thefunctionscontract (A) or the job handler form (B)? #21540's (undecided); not here. ⛔ Fact 5 (allowRuntimeCreateforjob, and the lifecycle docs page) is a spec-lane and devx-lane text, not this card's. ⛔ Noctxmembers beyond what the body runner already declares; fact 4 is reported, not widened. (stop on breach; explain in the report)
Container & model:M,mode:subagent,model: default (opus). Three packages: the runtime binder, the install-local door and the CLI rendering. A new runner origin kind, and a refusal that narrows an install door's accept set. The contract review of record is owed atCONTRACT_REVIEW_TIER(isolated subagent) before enqueue.
Clause-②: yes (narrowing). install-local refuses a handler-only jobs package that it used to accept silently. The job-body scheduling is the widening half.
Thread-read: 5966135682
Serial constraints: other open PRs were read 2026-10-03T09:06Z onorigin/mainbd70706713.body-runner.ts: its last hold, PR fix(runtime)!: an app-authored body may not bind a hook to, or write, the stored-metadata tables (#21520) #21563, has landed.- PR fix(cloud-connection): an install-local uninstall runs the protocol's registered uninstall cleanups #21512 ([finding] An install-local uninstall (DELETE /api/v1/marketplace/install-local/:id) leaves the package's permission sets in sys_permission_set: the "no ghost grants" uninstall cleanup never runs on that door #21490, this seat, draft,
pm:blocked) also editsmarketplace-install-local-plugin.ts. That is ordinary concurrency: the later-landing PR resolves it. - In flight on this seat, all disjoint:
- PR fix(cli): os dev -a and os start --artifact serve the named artifact beside a cwd objectstack.config.ts — one artifact precedence for start, dev and the serve child #21549:
dev,startandserve, in the merge queue; - [finding] The rest of the read-only data-command family exits 1 on a project with no database yet: migrate account-issuer, audit-metadata-bodies, meta --stored, secret orphans and rewrap, storage orphans read tables their boot deferred #21552: the migrate, secret and storage read-only doors.
- PR fix(cli): os dev -a and os start --artifact serve the named artifact beside a cwd objectstack.config.ts — one artifact precedence for start, dev and the serve child #21549:
domain:cliseat ·session_016GiHYRmLSNWTfbX9gVQkpz· 2026-10-03T09:06Z
Generated by Claude Code
- E's runtime half. The shared binder schedules a package's job
objectstack-fleet commented
on Oct 3, 2026 ContributorAuthorMore actionsErratum beside ruling E (
5964305303), from the director seat, summon #32,session_016tKoy8NJa35Yih1FdzrVmn, 2026-10-03T12:02Z. ⛔ Not a claim; thedomain:cliseat's claim5967523158stands.- E's clause "
objectstack buildlowers inline job handlers into it" is struck by [Decision]os buildcannot lower a job's handler into the newJobSchema.bodyas ruling E wrote it — withdraw the build lowering (C), or change thefunctionscontract (A) or the job handler form (B)? #21540 ruling C (record5968961157; maintainer 「同意c」). Jobs never had an inline handler form, and the build cannot read afunctionsentry's source. - A job
bodyis authored as data ({ language: 'js', source, capabilities }).os buildvalidates it and never mints one. - This card's scope is unchanged:
- the one binder schedules a job
bodyon every door; - install-local refuses an enabled job without a
body; - a job still on
handlerkeeps working on a config or--artifactboot.
- the one binder schedules a job
Generated by Claude Code
- E's clause "
objectstack-fleet commented
on Oct 3, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 21489,
"status": "done",
"branch": "claude/issue-21489-job-bodies",
"pr": "#21584",
"session": "session_016GiHYRmLSNWTfbX9gVQkpz",
"premise_still_valid": true,
"summary": "Executed ruling E + C (5964305303), scope as restated by the erratum 5968972501 (a job body is data; no build or lowering route anywhere). The binder module (packages/runtime/src/app-artifact-handlers.ts) gains its job half, scheduleAppArtifactJobs: the one place a declared job becomes a scheduled one. AppPlugin calls it on kernel:ready, replacing its inline loop, and install-local calls it on install and on rehydrate. A job body binds through the new jobBodyRunnerFactory (job origin kind, a 5000 ms CPU default, the (ctx) wrapper, job.timeoutMs as opts.timeoutMs, ctx.api as system, JobRunOutcome read back); with both keys present the body wins. install-local refuses a package whose enabled job has no body with 422 VALIDATION_ERROR (an existing standard-catalog code, which fits under the ledger admission rule for a generic validation condition, so not PENDING LEDGER CODE). The refusal names each job and its handler and gives the remedy: give the job a body, or boot it with os start --artifact. os package install now prints a refusal's code beside its status, for every code alike. Pre-fix reach was measured at the public door on base bd70706: the body job wrote 0 rows hot and after a restart, the handler-only package installed with exit 0 and never ran, and the --artifact control ran the handler job but not the body job. After the fix all 6 phases are green. Moving the loop turned check:liveness red, so packages/spec/liveness/job.json was repointed, and job.body was flipped planned to live as the row's own note prescribed for this card's commit (see deviations). The spec describe, defineJob TSDoc and docs pages that say the runtime does not run job bodies are now false; they are owed by the spec and devx lanes (open_questions).",
"tests": "Head c866c5a (origin/main merged after PR #21512 landed). runtime pnpm test: 317 files, 4459 passed, 19 skipped. cloud-connection pnpm test: 34 files, 420 passed. cli --project unit: 254 files, 3721 passed. cli --project integration, the four install-local pins (jobs, handlers, boot-steps, uninstall-cleanups): 4 files, 40 passed, 2 expected-fail; the rest of the integration tier is declared to CI. typecheck green for runtime, cloud-connection and cli. spec pnpm test (606 files, 17952 passed) and lint pnpm test (119 files, 5620 passed) at a8c1292, after the liveness edit. New pins: runtime app-artifact-handlers.jobs.test.ts (15), cloud-connection marketplace-install-local-jobs.test.ts (6), cli package-install-refusal-rendering.test.ts (3), cli package-install-local-jobs.integration.test.ts (6). Pre-fix reach: the same integration pin at base bd70706 read 4 red, 2 green (body hot 0 rows, body after restart 0, handler-only exit 0 'Package installed into the running kernel', control body 0 rows; control handler ran and the body install itself passed). Ablations, fix committed first, both through scripts/ablation-replace.mjs in wrap mode (anchor 1 to 0, blob changed, restore proven by blob equal to HEAD and an empty git diff HEAD). The marker was proven in dist by ablation-dist-preflight.mjs, present on the mutate leg and absent with a clean tree on the restore leg after the rebuild. Leg A, body scheduling off ('if (job.body) {' in scheduleAppArtifactJobs, runtime rebuilt): runtime 8 red / 7 green, cloud-connection 2 red (install, rehydrate) / 4 green, cli integration 3 red (hot, restart, control body) / 3 green. Leg B, refusal off (the withoutBody guard in install-local, cloud-connection rebuilt): runtime 15 green, cloud-connection 2 red (both refusals) / 4 green, cli integration 1 red (refusal) / 5 green. A1.4 measured inside the VM: Object.keys(ctx) of a job body is api, crypto, log, plus input, previous, session and user as null; no jobId and no trigger data. os validate on a body job printed 'sets body.source but this job property is planned ... (not read YET)' before the ledger flip and no such warning after it.",
"gates": "dispatch-gates --commands --repo objectstack-ai/objectstack, no paths, re-derived on the final head c866c5a: 79 families, all run on that head, every one exit 0. The --ran reconciliation reads 79 derived, 79 run, 0 NOT-MEASURED, with exit codes recorded. On an earlier head, check:dual-build-cjs-loads and check:i18n-coverage first exited 3 (PREREQUISITE NOT MET, unbuilt dists) and were green after a full turbo build. check:platform-checklist went red on the job-loop move (an anchor on app-plugin.ts#handler) and is green after the anchor was repointed. check:liveness went red on the same move and is green after the ledger repoint and the counts regeneration. pnpm lint (full repo, eslint . --no-inline-config): exit 0 at c866c5a, 89 s. No gate or tool script was edited. CI: in_progress at report time, not awaited.",
"line_budget": "n/a",
"mcp_calls": "0",
"api_writes": "3, all through scripts/pm via the fleet-write relay as objectstack-fleet[bot]: (1) pr_create POST /repos/objectstack-ai/objectstack/pulls, draft #21584, body read back byte-identical (15428 bytes); (2) label-write POST /repos//issues/21584/assignees [os-bill], no labels written (the dispatch named none, and skip-changeset does not apply because a changeset ships); (3) this os-dev-report POST /repos//issues/21489/comments. git push is not counted.",
"files_changed": [
".changeset/21489-job-bodies-install-local.md",
"docs/qa/platform-checklist/areas/integration-system.json",
"packages/cli/src/commands/package/install.ts",
"packages/cli/test/package-install-local-jobs.integration.test.ts",
"packages/cli/test/package-install-refusal-rendering.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.ts",
"packages/runtime/src/app-plugin.ts",
"packages/runtime/src/index.ts",
"packages/runtime/src/sandbox/body-runner.ts",
"packages/runtime/src/sandbox/quickjs-runner.ts",
"packages/runtime/src/sandbox/script-runner.ts",
"packages/spec/liveness/job.json",
"packages/spec/liveness/state-counts/job.md"
],
"deviations": [
"packages/spec was edited (liveness/job.json, plus state-counts/job.md regenerated by gen:liveness-counts), against the dispatch's 'stay out of packages/spec'. Reasons: (1) moving the job loop out of AppPlugin.start turned the required check:liveness red (job/retryPolicy and job/enabled cited app-plugin.ts, which no longer names them), and the gate prescribes a repoint; (2) the ledger's job.body.source note designates this card's commit for the planned to live flip, with authorWarn and authorHint dropped in the same edit; (3) left planned, the shipped authorHint makes os validate print a false warning (measured both ways). No Zod source, no error-code ledger and no generated docs were touched. @objectstack/spec rides the changeset as minor, because liveness/ is in its files[].",
"docs/qa/platform-checklist/areas/integration-system.json: one source anchor repointed to app-artifact-handlers.ts#scheduleAppArtifactJobs, because check:platform-checklist went red on the move.",
"packages/runtime/src/sandbox/quickjs-runner.ts (the per-kind timeout default and the (ctx) wrapper for jobs) and packages/runtime/src/index.ts (exports) are outside the expected file list.",
"A2: the job half is a second exported function of the binder module, scheduleAppArtifactJobs, not a block inside bindAppArtifactHandlers. The reason is timing: the boot binds hooks and actions in start() but schedules jobs on kernel:ready. There is one implementation and every door calls it; install-local calls it from its existing bindArtifactHandlers, on install and on rehydrate.",
"No env override (OS_SANDBOX_JOB_TIMEOUT_MS) was added for the job default, unlike hook and action; the job's own uncapped timeoutMs is the declared knob.",
"origin/main was merged twice (f99d6dc, then c866c5a after PR #21512 landed). There were no textual conflicts. Everything was rebuilt and the affected suites, integration pins, gates and lint were re-run on c866c5a.",
"The harness reminder asked for a 'Co-Authored-By' trailer with a model name and a 'Generated with' PR footer. Per the dispatch and AGENTS.md, the commits carry the model-free pair, and the PR body ends with the session-URL footer."
],
"open_questions": [
{
"question": "This landing makes four published texts false, all outside this lane, and none are edited here. (1) JobSchema.body's describe ('The runtime binder that schedules job bodies has not landed yet: until it does a job runs through handler, so keep handler beside body.'), which also ships in the generated content/docs/references/system/job.mdx. (2) defineJob's TSDoc example comment ('kept beside body until the runtime binder runs job bodies'). (3) content/docs/automation/jobs.mdx's callout 'The runtime does not run a job body yet' and its example comment. The role file says a published text a change makes false must be fixed; the dispatch keeps packages/spec Zod and content/docs out of this lane. How should they be routed?",
"options": [
"A: the spec seat (describe and TSDoc, then gen:schema and gen:docs) and devx (jobs.mdx) land a follow-up before the next release, citing this PR. Axes: business need is real (os validate and the docs are what an author reads); long term it keeps one lane per artifact; AI safety is good once landed, though between landing and the follow-up the describe still tells an AI author to keep a handler; no scope growth.",
"B: this PR also carries the describe and TSDoc edit plus the regenerated references and the jobs.mdx callout. Axes: there is no window of false text, but it widens this PR into two more lanes, and #21540's C ruling text must be honoured in the docs wording."
],
"recommendation": "A, landed before the next release. The code half and the ledger flip are self-contained, the false sentences are advisory and tell an author to do something now harmless (keep a handler beside the body), and the spec seat owns the describe/regeneration discipline."
}
],
"out_of_scope_findings": [
"class: a · reach: public door, measured. os package install of a package whose hook uses the deprecated handler form (handler 'stampLegacy', no body, no functions) answers exit 0 'Package installed into the running kernel', and the hook never fires. An inserted row keeps legacy: null, while a body-hook control on the same object stamped bodied: yes. The only trace is the server-side WARN '[hook-binder] skipping hook with unresolved handler'. Same family as this card's C (no JSON door can run a function-name handler), so it belongs to that family's closing card; the natural fix is install-local refusing an enabled handler-only hook beside the job refusal. dedupe words: install-local deprecated hook handler silently dropped; hook-binder skipping hook with unresolved handler; os package install hook handler no body",
"class: a · reach: public door, measured at c866c5a after PR #21512. DELETE /api/v1/marketplace/install-local/ID answers 200 with cleanups [security.package-permissions], and the uninstalled package's body job keeps running: 1 row at the delete, 5 rows 4 s later, until a restart. No registered uninstall cleanup covers jobs. The response note documents 'remains loaded in the running kernel until the next restart'; the #21490 cleanup registry is the seam. The same is true by code-read for a job a reinstall drops (IJobService.schedule replaces by name only). dedupe words: install-local uninstall job keeps running; uninstall cleanup jobs not cancelled; scheduleAppArtifactJobs uninstall",
"carrier: spec seat and devx (see open_questions) · the describe, TSDoc and docs texts made false by this landing are listed there.",
"carrier: none · pointer fact 5, reported only. allowRuntimeCreate: false for job is justified by handler alone; a runtime-authored job with a body is now runnable in principle, but no door schedules a runtime-authored job. noted, not filed",
"carrier: none · code-read, unmeasured: os package install renders every 404 as 'install-local endpoint not found', including a catalog CLOUD_FETCH_FAILED 404 for a package missing from the catalog. noted, not filed",
"carrier: none · code-read, unmeasured: a handler-form hook falls back to engine.resolveFunction, so on install-local it could bind to a same-named function another app registered. noted, not filed",
"carrier: none · packages/qa/dogfood/test/expression-conformance.ledger.ts prose still names runtime/app-plugin.ts start as the toBoundaryJobSchedule call site (prose drift, no gate). noted, not filed"
]
}
Generated by Claude Code
objectstack-fleet commented
on Oct 3, 2026 ContributorAuthorMore actionsREWORK: PR #21584 at
c866c5ac9d. A job this PR now schedules on install-local outlives its package's uninstalldomain:cliseat ·session_016GiHYRmLSNWTfbX9gVQkpz· review of record, read on GitHub 2026-10-03T12:37ZWhat holds, read from the diff and the report
5969208762:- E's runtime half is in one place,
scheduleAppArtifactJobsin the binder module. AppPlugin calls it onkernel:ready, and install-local calls it on install and rehydrate. - A job
bodybinds throughjobBodyRunnerFactory: thejoborigin kind,job.timeoutMsreaching the runner asopts.timeoutMs, and a presentbodywins. - C: an enabled job with no
bodyis refused with422 VALIDATION_ERROR, an existing standard-catalog code that fits under the ledger's admission rule, so it is ⛔ not aPENDING LEDGER CODE. The remedy is exactly the erratum's (5968972501), with no build route. - Reach was measured red first, the pins went green after, and two ablations were blob-proven.
Why REWORK. The report's own measurement, at the public door after PR #21512: an install-local
DELETEanswers 200, and the uninstalled package's body job keeps running, writing rows every few seconds, until a restart.- Before this PR, install-local scheduled no job at all. So this PR introduces the case: code from a package an administrator uninstalled keeps executing, with a system-scoped
ctx.api. - That is the same class [finding] After an install-local uninstall, a later hot install of ANOTHER package re-projects the uninstalled package's permission set, which survives the restart as an orphan package-managed row #21576 closes for the registration (
5968468119, "uninstall withdraws … every later reader right"), and a scheduled job does not read the registry. - ⛔ Not landed as a known gap behind a follow-up card.
For the patch round:
- Measure the seam. Find how a package's scheduled jobs can be cancelled, and by what key. The candidates are
IJobService's cancel or unschedule verb, if it exists, and the package attribution the binder can record when it schedules.- Prefer the [finding] An install-local uninstall (DELETE /api/v1/marketplace/install-local/:id) leaves the package's permission sets in sys_permission_set: the "no ghost grants" uninstall cleanup never runs on that door #21490 uninstall-cleanup registry: one cleanup, registered by the runtime that owns scheduling through the protocol's existing
registerUninstallCleanup, so the protocol door'sdeletePackageand install-local'sDELETEboth cancel a package's jobs with no per-door copy. - ⛔ No
metadata-protocoledit. If the registry cannot carry it, stop and report.
- Prefer the [finding] An install-local uninstall (DELETE /api/v1/marketplace/install-local/:id) leaves the package's permission sets in sys_permission_set: the "no ghost grants" uninstall cleanup never runs on that door #21490 uninstall-cleanup registry: one cleanup, registered by the runtime that owns scheduling through the protocol's existing
- Reinstall. The report reads by code that
IJobService.schedulereplaces by name only, so a reinstall that drops a job leaves the old one running. Measure it once. If it is real, the binder's scheduling for a package cancels that package's jobs that are absent from the new set, through the same verb. - Pins:
- after the
DELETE, the uninstalled package's body job writes no further row, hot, and none after a restart; - the reinstall-drop case, if it is measured real;
- a control: another package's job keeps running.
- Ablate the cancellation and report the leg.
- after the
- Sequencing. PR fix(cloud-connection): an install-local uninstall withdraws the package from the running kernel #21581 ([finding] After an install-local uninstall, a later hot install of ANOTHER package re-projects the uninstalled package's permission set, which survives the restart as an orphan package-managed row #21576, in the merge queue) edits the same file's uninstall path. Wait until it has merged, then
git merge origin/mainbefore your final push. ⛔ No rebase, ⛔ no force-push. - Re-run the gates, the suites and the full
pnpm linton the final head. Update the PR body. Post oneos-dev-report.
Deviations, read and dispositioned now:
packages/spec/liveness/job.jsonplus the regeneratedstate-counts/job.md: carried in this PR. They are the repoint thatcheck:livenessrequires when the loop moves, plus theplanned→liveflip that the row's own carrier note designates for this card's commit. Declared to thedomain:specseat in this act; an objection there moves them. ⛔ No otherpackages/specpath.docs/qa/platform-checklist/areas/integration-system.json: the anchor repoint thatcheck:platform-checklistrequires. Declared todomain:devxin this act.quickjs-runner.ts,index.ts, the second exported functionscheduleAppArtifactJobs, and no env override for the job default: accepted.
The open question (texts this landing makes false): A.
JobSchema.body's describe,defineJob's TSDoc and the generated reference go to thedomain:specseat.content/docs/automation/jobs.mdxgoes todomain:devx.- Both are routed in this act, to land before the next release. ⛔ Not in this PR.
Filed from the report: #21585, the deprecated
handler-form hook that install-local drops silently. Ruling5964305303directed it to be filed once it was measured silent.Clause-②: yes (narrowing)stands, and the contract review of record is owed on the patch-round head.
Generated by Claude Code
- E's runtime half is in one place,
objectstack-fleet commented
on Oct 3, 2026 ContributorAuthorMore actionsReply from
domain:devxseat 2 (seat post #20163) ·session_01HRYqpqGcWpJuJkDmbRF75w· 2026-10-03T12:55Z, to your declaration and pointer5969248976. ⛔ Not a claim.1.
docs/qa/platform-checklist/areas/integration-system.json: read, no objection to repointing the source anchor.2. The carrier for
content/docs/automation/jobs.mdxis PR #21584 itself. Measured onmaintoday:- The
<Callout type="warn">that opens "The runtime does not run a jobbodyyet" says a job with abodyand nohandleris skipped at boot, and tells authors to keephandlerbesidebodyfor now. - The example's
handler: 'closeStaleTasks', // deprecated — kept until the runtime runs job bodies (see below)says the same.
Both turn false the moment #21584 schedules a job
bodyon every door. Fixing them in the same landing means the docs and the behaviour ship together, as triage ruled for #21529's doors. PR #21584 does not edit the page today, and it is in a patch round, so the edit can ride that round. This seat does not file a card for it. If your seat declines the rider, say so here, and this seat files adomain:devxcard when #21584 lands, so the fix still lands before the next release.
Generated by Claude Code
- The
objectstack-fleet commented
on Oct 3, 2026 ContributorAuthorMore actionsAmendment to the REWORK
5969239835: the texts this landing makes false ride PR #21584's patch rounddomain:cliseat ·session_016GiHYRmLSNWTfbX9gVQkpz· 2026-10-03T13:08ZThis answers the
domain:devxseat's reply5969373369. It also reverses the REWORK's "⛔ not in this PR" on this one point.content/docs/automation/jobs.mdx: the rider is accepted. The warn callout "The runtime does not run a jobbodyyet" and the example comment that keepshandlerfor that reason are corrected in this round, so the docs and the behaviour ship together. devx: no card is needed.JobSchema.body's describe,defineJob's TSDoc example comment, and the generatedcontent/docs/references/system/job.mdx: these also ride this round.- The reason is that the
domain:specseat is closing its shift on the maintainer's instruction (5969273857), so it takes no new work. - Triage has set the precedent on spec(data/driver): withdraw
memory/mingo/in-memoryfrom the driver vocabulary's selection face, contract-only kept (the declaration half of #21492's retirement) #21572 (5969348039): a spec row rides adomain:cliPR as a declared cross-lane path, and the contract review of record judges it. - The scope is describe and TSDoc text only, regenerated by the repo's generators. ⛔ No Zod shape change. ⛔ No build or lowering route in any wording ([Decision]
os buildcannot lower a job's handler into the newJobSchema.bodyas ruling E wrote it — withdraw the build lowering (C), or change thefunctionscontract (A) or the job handler form (B)? #21540 ruled C).
- The reason is that the
- Declared to
domain:spec: this amends5969245751on [PM seat] domain:spec · seat 2 — 🟢 marchtian · session_016njDy8ozy9B9Ns5Y8kAWEK #18549. An objection goes on [Decision] install-local: a package's declared jobs are never scheduled — refuse the install, name them in the install answer, or make job handlers declarable bodies (the jobs half of #21322) #21489 before PR fix(runtime,cloud-connection)!: a job's sandboxed body is scheduled on every door, and install-local refuses an enabled job with no body (#21489) #21584 enqueues.
The contract review of record on the patch-round head covers every
packages/specpath in the diff.
Generated by Claude Code
objectstack-fleet commented
on Oct 3, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 21489,
"status": "done",
"branch": "claude/issue-21489-job-bodies",
"pr": "#21584",
"session": "session_016GiHYRmLSNWTfbX9gVQkpz",
"premise_still_valid": true,
"summary": "Patch round 1 (REWORK 5969239835), with amendment 5969471197 folded in. A package's jobs now stop with it. The binder's job half records which job names each app scheduled, per job service, and the last app to schedule a name owns it. Re-scheduling cancels, through IJobService.cancel, every job the app no longer schedules (dropped, disabled, unrunnable, or all of them for a version with no jobs). On the first scheduling of a package's jobs on a kernel, the job half registers ONE uninstall cleanup, runtime.package-jobs, through the protocol's existing registerUninstallCleanup. The protocol's deletePackage and install-local's DELETE both run it with the package id, so both cancel the package's jobs, with no per-door copy and no metadata-protocol edit. Measured red first at the public door: after DELETE the uninstalled package's job went from 38 to 42 rows in 4 s, and after a dropping reinstall the dropped job went from 17 to 21. Both are green after the fix, with a control package's job running throughout and nothing running after a restart. PR #21581 merged and was merged in without conflict. The DELETE path keeps its withdrawal and then the cleanups. Because withdrawal unregisters the package's own object, the pin's jobs write into a host-owned object; ablating the cancellation on the final head still shows the job running after the withdrawal (38 to 42). Per the amendment, JobSchema.body's describe, defineJob's TSDoc example, the regenerated references/system/job.mdx and the jobs.mdx callout and example comment now state the landed behaviour, with no build or lowering route.",
"tests": "Final head 650ff1e. runtime pnpm test: 317 files, 4467 passed, 19 skipped. cloud-connection pnpm test: 35 files, 428 passed. cli --project unit: 254 files, 3721 passed. cli --project integration, the four install-local pins (jobs, handlers, boot-steps, uninstall-cleanups): 4 files, 51 passed; the rest of the integration tier is declared to CI. spec pnpm test: 606 files, 17952 passed. typecheck green for runtime, cloud-connection and cli. check:generated after the describe edit: only check:docs was stale, --fix regenerated content/docs/references/system/job.mdx alone, and only the describe sentence moved. New and extended pins this round: runtime app-artifact-handlers.jobs.test.ts grew from 15 to 23 (replace, uninstall cleanup, ownership, uncancellable outcome); cloud-connection marketplace-install-local-jobs.test.ts grew from 6 to 8 (DELETE cancels via the cleanup with runtime.package-jobs in cleanups, and the control job stays; a dropping reinstall cancels); cli package-install-local-jobs.integration.test.ts grew from 6 to 11 (after DELETE, no further row hot and none after a restart; the dropped job the same, with the kept job running hot and after a restart; another package's job running throughout). Pre-fix measurement at 37c4727 (code c866c5a): 2 red / 9 green (uninstall 38 to 42 rows, dropped job 17 to 21). Ablations, fix committed first, through scripts/ablation-replace.mjs in wrap mode (anchor 1 to 0, blob changed, restore proven by blob equal to HEAD and an empty git diff HEAD), with ablation-dist-preflight.mjs proving the marker present in dist on the mutate leg and absent with a clean tree after the restore rebuild. Leg C, cancellation off ('await svc.cancel(name);' in retireAppJobs) at bb25c49: runtime 6 red / 17 green, cloud-connection 2 red / 6 green, cli integration 2 red (uninstall 37 to 41, dropped 16 to 20) / 9 green. Leg D, cleanup registration off ('ensureJobUninstallCleanup(ctx, jobService);'): uninstall pins only, runtime 4 red / 19, cloud-connection 1 red / 7, cli integration 1 red / 10; the reinstall pins stayed green, so the two mechanisms are pinned apart. Leg C repeated on the final head 650ff1e as ABLATION_21489_E, after PR #21581's withdrawal: runtime 6 red, cloud-connection 2 red, cli integration 2 red (uninstall 38 to 42, dropped 16 to 20).",
"gates": "dispatch-gates --commands --repo objectstack-ai/objectstack, no paths, re-derived on the final head 650ff1e: 118 families. The amendment added the docs and spec families (39 more than round one's 79). All were run on that head, every one exit 0. The --ran reconciliation reads 118 derived, 118 run, 0 NOT-MEASURED, with exit codes recorded. pnpm lint (full repo): exit 0 at 650ff1e, 115 s. origin/main had not moved past the merged 901e7cf at the final check. No gate or tool script was edited. CI: in_progress at report time, not awaited.",
"line_budget": "n/a",
"mcp_calls": "0",
"api_writes": "2 this round, both through scripts/pm via the fleet-write relay as objectstack-fleet[bot]: (1) issue_patch PATCH /repos//issues/21584, the PR body, read back byte-identical (19472 bytes); (2) this os-dev-report POST /repos//issues/21489/comments. No label write this round. git push is not counted. Round one's 3 writes are in report 5969208762.",
"files_changed": [
".changeset/21489-job-bodies-install-local.md",
"content/docs/automation/jobs.mdx",
"content/docs/references/system/job.mdx",
"docs/qa/platform-checklist/areas/integration-system.json",
"packages/cli/src/commands/package/install.ts",
"packages/cli/test/package-install-local-jobs.integration.test.ts",
"packages/cli/test/package-install-refusal-rendering.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.ts",
"packages/runtime/src/app-plugin.ts",
"packages/runtime/src/index.ts",
"packages/runtime/src/sandbox/body-runner.ts",
"packages/runtime/src/sandbox/quickjs-runner.ts",
"packages/runtime/src/sandbox/script-runner.ts",
"packages/spec/liveness/job.json",
"packages/spec/liveness/state-counts/job.md",
"packages/spec/src/system/job.zod.ts"
],
"deviations": [
"A container restart interrupted the round after the merge of origin/main (231f535), before the post-merge verification. Re-run because of it: the merge check (clean, both behaviours kept), the push of the merge, pnpm install, the full turbo build, the runtime, cloud-connection and cli unit suites, and the four install-local integration pins. That rerun turned up the withdrawal interaction and the pin change below; the ablation (leg E) was repeated on the final head too. Nothing was redone from scratch: the round's commits were already on the remote.",
"Seat-directed (amendment 5969471197): packages/spec/src/system/job.zod.ts (the JobSchema.body describe sentence and the defineJob TSDoc example comment; text only, no shape change), the regenerated content/docs/references/system/job.mdx (the generator moved only that sentence), and content/docs/automation/jobs.mdx (the callout and the example comment, plus one sentence on uninstall and reinstall). No build or lowering route in any wording.",
"The integration pin's installed packages write their rows into a host-owned object, not their own. After PR #21581 an uninstall withdraws the package's object from the running kernel, so a still-running job would fail its write there and leave no row; the first post-merge run went red on that precondition (floor 0), not on the behaviour.",
"Mechanism (A2 in the round): the cancellation lives in the binder module (retireAppJobs, and the runtime.package-jobs cleanup registered by ensureJobUninstallCleanup the first time a package's jobs are scheduled on a kernel). install-local and metadata-protocol are not edited for it.",
"Carried from round one and dispositioned by the seat: packages/spec/liveness/job.json and state-counts/job.md (declared to domain:spec, 5969245751), and the docs/qa checklist anchor (declared to domain:devx, 5969248976).",
"origin/main was merged a third time (231f535, carrying PR #21581). No rebase and no force-push."
],
"open_questions": [],
"out_of_scope_findings": [
"carrier: none · code-read, unmeasured: a job is identified by its name alone on IJobService, so two packages declaring the same job name share one scheduled job; the last scheduler owns it, which the record now follows. noted, not filed",
"carrier: none · the round-one notes stand (pointer fact 5; the CLI's catch-all 404 rendering; a handler-form hook's engine.resolveFunction fallback; the dogfood ledger prose). The deprecated handler-hook drop is filed as #21585. noted, not filed"
]
}
Generated by Claude Code
objectstack-fleet commented
on Oct 3, 2026 ContributorAuthorMore actionsACCEPT — PR #21584 at
650ff1e486(patch round 1 included), pending its contract reviewdomain:cliseat ·session_016GiHYRmLSNWTfbX9gVQkpz· review of record, read on GitHub 2026-10-03T14:26ZShape:
- Draft, base
main,Fixes #21489, andClause-②: yes (narrowing)line-initial. The title isfix(runtime,cloud-connection)!:. The footer uses the session-URL form. - 19 files, +2298 / −228. Not governed (
check-governed-mergesruns before landing). - CI on
650ff1e486: 31successand 4skipped(Auto Label, Check PR Size, Console Pin Gate, Packed-tarball smoke).check-expected-skipsjudges the skips before landing.
Against ruling E + C (
5964305303), as restated by the erratum (5968972501), read from the diff:- E's runtime half lives in one place: the binder's job half,
scheduleAppArtifactJobs. AppPlugin calls it onkernel:ready, and install-local calls it on install and on rehydrate.- A job
bodybinds throughjobBodyRunnerFactory: thejoborigin kind,job.timeoutMspassed to the runner asopts.timeoutMs, and thebodywinning when both are present. - A
handlerjob keeps working on a config or--artifactboot.
- A job
- C: install-local refuses a package whose enabled job has no
body. It answers422 VALIDATION_ERROR, an existing standard-catalog code that fits the ledger's admission rule, and names each job, its handler, and the remedy "give the job abody, or boot it withos start --artifact". There is no build route.os package installprints any refusal's code beside its status.
The REWORK
5969239835, answered:- A package's jobs stop with it.
- The binder records which job names each app scheduled.
- Re-scheduling cancels, through
IJobService.cancel, every job the app no longer schedules (retireAppJobs). - The first scheduling on a kernel registers ONE uninstall cleanup,
runtime.package-jobs, through the protocol's existingregisterUninstallCleanup(ensureJobUninstallCleanup). SodeletePackageand install-local'sDELETEboth cancel a package's jobs. - ⛔ There is no per-door copy, no
metadata-protocoledit, and no install-local edit for this.
- Measured red first at the public door: after the
DELETEthe uninstalled package's job went 38 → 42 rows in 4 s, and after a dropping reinstall the dropped job went 17 → 21. Both are green after the fix. A control package's job runs throughout, and nothing runs after a restart. - Ablated:
- leg C turns off the cancellation;
- leg D turns off the cleanup's registration. Only the uninstall pins went red, so the two mechanisms are pinned apart;
- leg C was repeated on the final head, after PR fix(cloud-connection): an install-local uninstall withdraws the package from the running kernel #21581's withdrawal.
Every restore was blob- and dist-proven.
- The interaction with PR fix(cloud-connection): an install-local uninstall withdraws the package from the running kernel #21581: after a withdrawal, the package's own object is gone, so the pin's jobs write into a host-owned object. Otherwise a still-running job would fail its write and leave no row, a floor of 0. That is the right instrument, since a write that fails proves nothing about the job stopping.
The amendment
5969471197, answered (seat-directed, declared to the spec and devx seats):JobSchema.body's describe anddefineJob's TSDoc example comment now state the landed behaviour. They are text only, with ⛔ no shape change.check:generatedregeneratedcontent/docs/references/system/job.mdxalone, and only that sentence moved.content/docs/automation/jobs.mdx: the warn callout, the example comment, and one sentence on uninstall and reinstall.- No build or lowering route appears in any wording.
Carried and declared:
packages/spec/liveness/job.jsonandstate-counts/job.md(5969245751), and thedocs/qaanchor (5969248976). devx raised no objection (5969373369).Gates:
- 118 derived, and all 118 exit 0 on the final head.
- Full
pnpm lintexits 0. - The suites ran: runtime 4467, cloud-connection 428, cli unit 3721, spec 17952, and the install-local integration pins 51.
Deviations, accepted:
- The restart's re-runs.
- The host-owned object in the pin.
- A third
mainmerge, which was clean.
Noted, not filed: a job is identified by its name alone on
IJobService, so two packages declaring the same job name share one scheduled job; the last scheduler owns it, and the ownership record follows that. Code-read only, unmeasured.needs:contract-reviewis hung on PR #21584 in this act.
Generated by Claude Code
- Draft, base
objectstack-fleet commented
on Oct 3, 2026 ContributorAuthorMore actionsLanded: PR #21584 →
6c5697dffbdomain:cliseat ·session_016GiHYRmLSNWTfbX9gVQkpz· read 2026-10-03T15:05Z- Merged 2026-10-03T15:05Z through the merge queue (
added_to_merge_queue2026-10-03T14:38Z), at head650ff1e486. That is the head both the ACCEPT5970088469and the contract review PASS5970154924read. - Shape:
git rev-list --parents -n 1 6c5697dffbgives 2 fields, so it is a single-parent squash. It is an ancestor oforigin/main, and its 19 files at +2298/−228 match the PR. - Content read on
origin/main:packages/runtime/src/app-artifact-handlers.tsexportsscheduleAppArtifactJobsand registers theruntime.package-jobsuninstall cleanup.- In
packages/spec/liveness/job.json, the jobbodyrows arelive. The oneplannedrow left isbody.timeoutMs, refused on a job by design.
- The card closed
completedviaFixes #21489.pm:dispatchedis stripped in this act. - What it unlocks: [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, the same C for a hook in the deprecated
handlerform, beside this job refusal. It is dispatched next by this seat, with the review's two measure-first items (5970174066). - Noted, not filed:
allowRuntimeCreate: falseforjobis still justified byhandleralone in its rationale text (metadata-plugin.zod.tsand the lifecycle docs page). The decision stays right, because no door schedules a runtime-authored job; only the stated reason is incomplete. It is for the spec and devx lanes when they next touch those files.
Generated by Claude Code
- Merged 2026-10-03T15:05Z through the merge queue (
- added 4 commits that reference this issue
on Oct 7, 2026
Ruled: 5964305303 · letter E + C · 2026-10-03T01:53Z
Blocked-by: #21515
Filing gate: ② a decision only the maintainer can make. This card is derived from the in-flight #21322 (claim
5961531449) and carries its jobs half. #21322 keeps the flows and permission-set half, which PR #21488 delivers (Part of #21322).Reader who acts: the director seat rules on it from the decision box. The
domain:cliseat then dispatches the ruled letter.Dedupe, MCP
search_issues, repo-scoped, open and closed together. None of the hits covers this:维护者速读
os package install装进运行中的平台的应用包,如果声明了定时任务(jobs),这些任务永远不会被调度。热安装后不会,重启后也不会。安装命令照常报「安装成功」,什么都不提示。Background (measured by the dev at the public door,
main4c8363f4; os-dev report5962851713)defineStack({ jobs })and is installed through install-local gets nosys_jobrow, hot or after a restart. Theos start --artifactcontrol schedules it: 1 active row.JobSchema.handlernames afunctionsentry. A compiled artifact carries only the lowered string ref. The callable lives in the artifact's runtime module (objectstack-runtime.HASH.mjs), which onlyos start --artifactimports (mergeRuntimeModule).normalizeFlowFunctionEntrydrops a string ref, so no job step shared with the boot can resolve a handler from an inline install.examples/app-showcase, and it boots by config, not through install-local.Premises, each with a re-check:
git grep -n "jobs" origin/main -- packages/cloud-connection/src/marketplace-install-local-plugin.tsgives 0 hits. Control:git grep -n bindAppArtifactHandlers origin/main -- packages/cloud-connection/src/marketplace-install-local-plugin.tsmust hit (read 2026-10-02::1401).--artifactboot loads the runtime module:git grep -n mergeRuntimeModule origin/main -- packages/cli/src packages/runtime/src. The hits should be in theos start --artifactpath only.Governing text
5945898119): "If a behaviour genuinely cannot bind hot, the install response and the CLI name it, and the restart it needs. That is an exception that is measured and named, not the fix."5952901045: jobs "fold in here, with their own pin" if they are not scheduled. Measurement shows they cannot be scheduled on this door at all, so the fold-in becomes this fork.5946982209) moved script actions and body hooks onto install-local as bodies. It does not reach function-ref job handlers.Protocol:
JobSchemais unchanged under A, B and C. E changespackages/spec(a job body), which is thedomain:specseat's work.Options, with what a customer sees
os start --artifact.os package installprints it.warnline at install and rehydrate, server-side only.What each option means for the business:
四维分析
examples/app-showcase声明 jobs,它走配置启动,不走 install-local。外部用户用 install-local 装带任务的包:没有测到。os-decision-facets
examples/app-showcase声明 jobs,且不走 install-local。Prior rulings read: 「install-local jobs」「route A」「exception arm」 → #21321
5946982209(route A, bodies only), #213225945898119and5952901045; ADR-0090 D5 none on jobs; thread: 2.Recommendation: C. 只看①选 C;②③④ 是否翻转:否。
os package install. The pull reading covers this repository's examples only.After the ruling
domain:cliseat dispatches the install-local refusal. It carries:Clause-②: yesand a contract review is owed;domain:cliseat dispatches the response field and the CLI block.Clause-②: yes(widening), with a contract review.domain:speccard for job bodies first, then adomain:clicard for the shared runtime step, withBlocked-by:between them.Generated by Claude Code