Skip to content

ADR-0087 P0: enforce the protocol handshake (make engines.protocol real) #2644

Description

@os-zhuang

Part of #2643 (ADR-0087). First phase — smallest change, highest value/cost. Turns a protocol/consumer version mismatch from an arbitrary downstream crash into a structured, machine-actionable load-time refusal, and pays down a standing ADR-0078 violation (a declarable field the runtime ignores).

Status: core enforcement + install seam shipped in #2650 (draft). Remaining items (load-path handshake, lint nudge, scaffold stamping) tracked below — this issue stays open until they land.

The bug this fixes

PluginEnginesSchema.protocol (packages/spec/src/kernel/manifest.zod.ts, ADR-0025 §3.2, protocol-first per §3.10 #3) is declared, documented, and checked by no loader, installer, or CLI command (repo-wide grep: the only non-test hit is the schema's own .describe()). A package built against protocol N and loaded by an incompatible runtime therefore fails deep inside a .parse() or a renderer contract instead of at the boundary.

Scope (D1)

  • PROTOCOL_VERSION / PROTOCOL_MAJOR constant exported from @objectstack/spec/kernel (drift-tested against the package major). — feat(protocol): enforce the engines.protocol handshake (ADR-0087 P0) #2650
  • Pure handshake core in @objectstack/metadata-core: checkProtocolCompat(), assertProtocolCompat(), structured ProtocolIncompatibleError (OS_PROTOCOL_INCOMPATIBLE) carrying both versions + the migrate meta --from N command; refuses only on a positive mismatch, grandfathers absent ranges, never false-rejects an unparsed range. — feat(protocol): enforce the engines.protocol handshake (ADR-0087 P0) #2650
  • Install seam: installPackage runs the handshake before the registry write. — feat(protocol): enforce the engines.protocol handshake (ADR-0087 P0) #2650
  • Load seam: run the handshake on the boot-time durable-package rehydration path and AppPlugin.start() for code-defined stacks (the same assertProtocolCompat call).
  • objectstack lint flags a package with no engines.protocol range (nudge toward declaring it).
  • Scaffold stamping: create-objectstack and the defineStack templates emit engines: { protocol: '^<current major>' } by default — the ratchet that closes grandfathering.

Acceptance criteria

Boundaries

  • No source rewriting here — this phase only refuses; the migration that resolves the refusal is P1 (conversion) / P2 (migrate meta).
  • The error must name the chain/command, not a doc the consumer "should have read" — see ADR-0087 D1.

Refs: #2643, #2650, ADR-0087 (D1), ADR-0078, ADR-0025 §3.2/§3.10.

Activity

  1. added theissue type on Jul 5, 2026
  2. added a commit that references this issue on Jul 6, 2026
    60dc3ba
  3. os-zhuang commented on Jul 15, 2026

    @os-zhuang
    ContributorAuthor

    记录:本 issue 被 #2650 自动关闭时,正文中还有三项未勾选(load seam、lint 提示、脚手架 stamping),此后一直没有跟踪载体。这三项已在 #2972 全部补齐:

    • Load seam:sys_packages 启动再水化路径(@objectstack/service-package)对不兼容包发出结构化 OS_PROTOCOL_INCOMPATIBLE 拒绝并跳过(boot 继续);AppPlugin(code-defined stack)在 manifest 分解前 fail-fast。
    • objectstack lint:新增 protocol/missing-engines-range(warning + fix-it)。
    • 脚手架 stamping:blank 模板 manifest 写入 engines: { protocol: '^<major>' },由 sync-template-versions.mjs 在版本时重新 stamp,并有一致性测试防漂移。

    #2972 合并后 D1 即完整落地(install + load 两侧 + 棘轮两端)。


    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

No one assigned

    Labels

    enhancementNew feature or request

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions