Skip to content

[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

@objectstack-fleet

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:cli seat 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),这些任务永远不会被调度。热安装后不会,重启后也不会。安装命令照常报「安装成功」,什么都不提示。
  • 原因: 定时任务的处理函数是代码。代码在应用产物的运行时模块里,而这条安装通道只带 JSON。
  • 选项:
    • C: 安装时直接拒绝带定时任务的包,给出错误码和补救办法。
    • A: 照装,在安装回执和 CLI 里点名哪些任务没生效。
    • B: 只写服务端日志。
    • E: 把任务处理函数改成可声明的沙箱代码体,像动作和钩子那样随包走。
  • 席位推荐: C,回退 A。
  • 你要做的: 回一个字母。

Background (measured by the dev at the public door, main 4c8363f4; os-dev report 5962851713)

  • A package that declares defineStack({ jobs }) and is installed through install-local gets no sys_job row, hot or after a restart. The os start --artifact control schedules it: 1 active row.
  • The cause: JobSchema.handler names a functions entry. A compiled artifact carries only the lowered string ref. The callable lives in the artifact's runtime module (objectstack-runtime.HASH.mjs), which only os start --artifact imports (mergeRuntimeModule). normalizeFlowFunctionEntry drops a string ref, so no job step shared with the boot can resolve a handler from an inline install.
  • Who declares jobs today: only examples/app-showcase, and it boots by config, not through install-local.

Premises, each with a re-check:

  1. install-local has no jobs step: git grep -n "jobs" origin/main -- packages/cloud-connection/src/marketplace-install-local-plugin.ts gives 0 hits. Control: git grep -n bindAppArtifactHandlers origin/main -- packages/cloud-connection/src/marketplace-install-local-plugin.ts must hit (read 2026-10-02: :1401).
  2. Only the --artifact boot loads the runtime module: git grep -n mergeRuntimeModule origin/main -- packages/cli/src packages/runtime/src. The hits should be in the os start --artifact path only.

Governing text

Protocol: JobSchema is unchanged under A, B and C. E changes packages/spec (a job body), which is the domain:spec seat's work.

Options, with what a customer sees

What it does What a customer sees
C install-local refuses a package that declares enabled jobs, with a ledgered error code and the remedy os start --artifact. The install fails loudly and says why. No half-working app.
A install-local installs, the response lists each job that did not bind (kind, name, reason, remedy), and os package install prints it. The install succeeds with a visible "these jobs will not run" block.
B a warn line at install and rehydrate, server-side only. Nothing. A remote installer (human or AI) never reads the server log.
E job handlers become declarable sandboxed bodies, like script actions and body hooks, so install-local can run the shared job step. Jobs simply work on every install door.

What each option means for the business:

  • C is a store that refuses to sell a product it cannot deliver.
  • A sells the product with a note saying part of it is missing.
  • B sells the product and writes the gap in an internal ledger nobody reads.
  • E builds the missing delivery route.

四维分析

  • 实际业务需求: 实测零拉动。全仓只有 examples/app-showcase 声明 jobs,它走配置启动,不走 install-local。外部用户用 install-local 装带任务的包:没有测到。
  • 项目长远合理性: 这条安装通道长期该有的契约,是「能跑的全装,跑不了的响亮拒绝」。
    • C 正是这个契约,而且以后加 E 是纯放宽,不破坏任何人。
    • A 多出一个永久的回执字段,承认「声明了但不兑现」是合法常态。
    • B 是临时补丁。
    • E 是完整形态,但它是能力扩张,需要先改 spec。
  • 防 AI 写代码犯错:
    • 出错时,C 让写包的 AI 当场看到错误码和补救办法。
    • A 让 AI 在回执里看到一段提示,但包照样装上,AI 很可能忽略它。
    • B 让 AI 什么都看不到,声明静默落空。
    • E 从结构上消除这个陷阱。
  • 创业阶段不扩散: C 只加一个拒收码,不加回执字段。A 加一个要永久维护的回执字段。E 新增 spec 能力面,没有拉动,默认从紧。

os-decision-facets

  • ① 长远:C 收窄特例(一个门、一种拒收),不增契约字段;E 才是终态,以后是纯放宽,可在出现具名用户时再做;A 把「声明未兑现」固化为回执字段。
  • ② 拉动:零——只有 examples/app-showcase 声明 jobs,且不走 install-local。
  • ③ 防 AI:C 响亮拒绝并给处方;A 是提示,包照装;B 静默;E 结构性闭合但现在不做。
  • ④ 不扩散:C 加一个错误码;A 加一个永久回执字段;E 加 spec 能力面。默认从紧。

Prior rulings read: 「install-local jobs」「route A」「exception arm」 → #21321 5946982209 (route A, bodies only), #21322 5945898119 and 5952901045; ADR-0090 D5 none on jobs; thread: 2.

Recommendation: C. 只看①选 C;②③④ 是否翻转:否。

  • Fallback: A, if the maintainer wants a jobs-bearing package to install anyway.
  • Confidence gap: the seat cannot see external installers of os package install. The pull reading covers this repository's examples only.

After the ruling

  • C: the domain:cli seat dispatches the install-local refusal. It carries:
    • a new ledgered error code, so Clause-②: yes and a contract review is owed;
    • the CLI's rendering of that code;
    • a pin that a jobs-bearing package is refused while a package with no jobs installs unchanged.
  • A: the domain:cli seat dispatches the response field and the CLI block. Clause-②: yes (widening), with a contract review.
  • B: a log line only, no contract change.
  • E: a domain:spec card for job bodies first, then a domain:cli card for the shared runtime step, with Blocked-by: between them.

Generated by Claude Code

Activity

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