Skip to content

"Which protocol major was this stack authored against?" has no declared home — three CLI commands advise off manifest.specVersion, which ManifestSchema does not declare #13860

Description

@huangyiirene

Found by flight ⑨ of the published-skills factual sweep (#13658, member card #13840). Filed unassigned.

What was measured

os migrate meta --from N requires the operator to state the protocol major the metadata was authored against. The published upgrade skill told readers to read it from manifest.protocol — a phantom: manifest.protocol had zero occurrences in this repo outside that one skill line, and ManifestSchema (packages/spec/src/kernel/manifest.zod.ts) declares no such key. That falsehood is corrected in the sweep PR by pointing at manifest.engines.protocol, which is declared (PluginEnginesSchema) and genuinely checked at boot (assertProtocolCompat in packages/runtime/src/app-plugin.ts; assertArtifactProtocolCompatible in packages/runtime/src/artifact-reference.ts, raising OS_PROTOCOL_INCOMPATIBLE).

Chasing that surfaced a second, separate gap the sweep did not fix.

manifest.specVersion is read by three CLI commands and declared by nothing:

read at line
packages/cli/src/commands/validate.ts checkSpecVersionGap(config.manifest)
packages/cli/src/commands/doctor.ts checkSpecVersionGap(config.manifest)
packages/cli/src/commands/compile.ts checkSpecVersionGap((config as ...).manifest)

packages/cli/src/utils/spec-version.ts describes exactly what it wants: "when an app's authored manifest.specVersion declares an OLDER major than the @objectstack/spec actually installed ... there is breaking-change guidance the author should read before proceeding." That is precisely the reading the upgrade flow needs.

But the key that call reads is the stack config's manifest, and ManifestSchema has no specVersion member. The only specVersion in the spec is on a different surface — packages/spec/src/cloud/template-manifest.zod.ts (the marketplace template manifest). Consequences, in the order they bite:

  1. ManifestSchema is not .strict(), so an authored specVersion is accepted and dropped. Nothing tells the author it went nowhere.
  2. Being undeclared, it is invisible to check:authorable-surface, carries no .describe(), and therefore never reaches the generated reference docs or Studio property forms — the same declared-versus-enforced asymmetry ADR-0049 exists for, running in the read-but-undeclared direction.
  3. So the advisory is, in practice, dead for stack configs: it can only fire for a manifest that carries a key the schema does not offer.

Why this is not #9264

#9264 (closed) was the blank template declaring two protocol versions that disagreed — specVersion: "^6.0.0" in the manifest against engines.protocol: '^17' in the config — and fixed the sync between them. This is the layer under that: on the stack ManifestSchema there is no specVersion member to disagree with anything, while three commands read one.

The decision, not the patch

Two coherent shapes, and picking between them is a contract call rather than a bug fix:

Recommendation: B. Real business need favours it — engines.protocol has live readers on the boot path today while specVersion on a stack manifest has none; long-term soundness favours one declared version axis over two; it makes AI-authored metadata harder to get wrong, because the key an agent can write is the key the runtime reads; and it is the smaller surface, which is the startup-scope posture. A is the answer only if someone wants an app-declared spec range that is deliberately independent of the plugin compatibility range — a real need nobody has stated.

Refs: #13658 · #13840 · #9264 (closed, the adjacent template-side fix).

Generated by Claude Code

Metadata

Metadata

Assignees

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions