Skip to content

feat(protocol): enforce the engines.protocol handshake (ADR-0087 P0)#2650

Merged
os-zhuang merged 2 commits into
mainfrom
claude/bold-clarke-o4op29
Jul 6, 2026
Merged

feat(protocol): enforce the engines.protocol handshake (ADR-0087 P0)#2650
os-zhuang merged 2 commits into
mainfrom
claude/bold-clarke-o4op29

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Implements P0 of the ADR-0087 epic (#2643) — resolves #2644. First real code change of the metadata-protocol upgrade contract.

What & why

PluginEnginesSchema.protocol (packages/spec/src/kernel/manifest.zod.ts, ADR-0025 §3.2, protocol-first per §3.10 #3) was declared, documented, and checked by no loader or installer — an ADR-0078 "declarable-but-inert" violation, and the root cause of "a version mismatch crashes the app": a package built against an incompatible protocol major failed deep in a schema .parse() or renderer contract instead of at the boundary.

This turns that into a structured, machine-actionable load-time refusal.

Changes

  • @objectstack/spec — exports PROTOCOL_VERSION / PROTOCOL_MAJOR from /kernel, the single source of truth the handshake checks against. A drift test (protocol-version.test.ts) asserts it against package.json so the protocol major and the published package major can never diverge.
  • @objectstack/metadata-core — the pure logic core:
    • checkProtocolCompat(manifest, runtimeVersion?) — major-grained range check supporting ^, ~, >=/</>/<=, compound (>=11 <13), hyphen, and wildcard forms. Returns a discriminated result (ok / no-range / unparsed-range / incompatible).
    • assertProtocolCompat() + ProtocolIncompatibleError (code: OS_PROTOCOL_INCOMPATIBLE) carrying both versions and the exact objectstack migrate meta --from N command (wired end-to-end in P2).
    • Refuses only on a positive mismatch: absent ranges are grandfathered (warn), unrecognized ranges never cause a false rejection.
  • @objectstack/metadata-protocolinstallPackage runs the handshake before the registry write, so an incompatible package is refused with a diagnostic instead of crashing later.

Design notes (from ADR-0087 D1)

  • The diagnostic is identical in shape one major behind or five — it names the migrate command, not a guide the consumer "should have read" (timeliness is never load-bearing).
  • migrate meta --from N (P2) does not exist yet; P0's value is the diagnosable refusal, and the diagnostic already carries the command string for when P2 lands.
  • Deferred to follow-ups (tracked in ADR-0087 P0: enforce the protocol handshake (make engines.protocol real) #2644): the boot-time load-path handshake (AppPlugin.start() + durable rehydration), the objectstack lint nudge for a missing range, and scaffold stamping (create-objectstack / defineStack templates) — the ratchet that closes grandfathering. This PR delivers the enforcement core + the primary install seam.

Verification

  • @objectstack/spec — full suite green (incl. the drift test); check:api-surface shows 2 added, 0 breaking, snapshot regenerated per ADR-0059 §4.
  • @objectstack/metadata-core — 99 tests green (exhaustive range-logic coverage incl. the <13.0.0 boundary and the bare->N npm-desugaring case).
  • @objectstack/metadata-protocol — 15 tests green: new install-seam tests prove reject-before-registry-write + structured diagnostic + compatible-installs + grandfathering; pre-existing durable-package tests still pass.
  • DTS builds clean for all three packages; changeset added (minor, fixed group).

🤖 Generated with Claude Code

https://claude.ai/code/session_01HjDghc79qFSJkJt2zvBxtU


Generated by Claude Code

Turn a protocol/consumer version mismatch from an arbitrary downstream
crash into a structured, machine-actionable load-time refusal, and pay
down a standing ADR-0078 violation: engines.protocol (ADR-0025 §3.2,
protocol-first per §3.10 #3) was declared, documented, and checked by no
loader or installer.

- spec: export PROTOCOL_VERSION / PROTOCOL_MAJOR from /kernel — the single
  source of truth the handshake checks against; a drift test keeps it in
  lockstep with the package major.
- metadata-core: checkProtocolCompat() (pure, major-grained range check
  supporting ^/~/>=/</ranges/wildcards), assertProtocolCompat(), and the
  structured ProtocolIncompatibleError (OS_PROTOCOL_INCOMPATIBLE, carrying
  both versions and the 'migrate meta --from N' command). Refuses only on a
  positive mismatch; absent ranges are grandfathered (warn), unrecognized
  ranges never cause a false rejection.
- metadata-protocol: installPackage runs the handshake before the registry
  write — incompatible packages are refused with a diagnostic, not a crash.

Additive and backward compatible; api-surface snapshot regenerated (2 added,
0 breaking). Part of #2643; resolves #2644.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HjDghc79qFSJkJt2zvBxtU
@vercel

vercel Bot commented Jul 5, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
spec Ready Ready Preview, Comment Jul 5, 2026 5:04pm

Request Review

@github-actions github-actions Bot added size/l documentation Improvements or additions to documentation tests tooling and removed size/l labels Jul 5, 2026
@github-actions

github-actions Bot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 3 package(s): @objectstack/metadata-core, @objectstack/metadata-protocol, @objectstack/spec.

93 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/agents.mdx (via @objectstack/spec)
  • content/docs/ai/skills-reference.mdx (via @objectstack/spec)
  • content/docs/ai/skills.mdx (via @objectstack/spec)
  • content/docs/api/client-sdk.mdx (via @objectstack/spec)
  • content/docs/api/environment-routing.mdx (via @objectstack/spec)
  • content/docs/api/error-catalog.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-client.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-server.mdx (via @objectstack/spec)
  • content/docs/api/index.mdx (via @objectstack/spec)
  • content/docs/automation/approvals.mdx (via packages/spec)
  • content/docs/automation/flows.mdx (via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx (via packages/spec)
  • content/docs/automation/hooks.mdx (via @objectstack/spec)
  • content/docs/automation/index.mdx (via @objectstack/spec)
  • content/docs/automation/webhooks.mdx (via @objectstack/spec)
  • content/docs/automation/workflows.mdx (via @objectstack/spec)
  • content/docs/concepts/architecture.mdx (via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx (via packages/spec)
  • content/docs/concepts/index.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx (via @objectstack/metadata-core, @objectstack/metadata-protocol, packages/spec)
  • content/docs/concepts/north-star.mdx (via packages/spec)
  • content/docs/data-modeling/analytics.mdx (via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx (via @objectstack/spec)
  • content/docs/data-modeling/external-datasources.mdx (via @objectstack/spec)
  • content/docs/data-modeling/field-types.mdx (via @objectstack/spec)
  • content/docs/data-modeling/fields.mdx (via @objectstack/spec)
  • content/docs/data-modeling/formulas.mdx (via @objectstack/spec)
  • content/docs/data-modeling/index.mdx (via @objectstack/spec)
  • content/docs/data-modeling/objects.mdx (via @objectstack/spec)
  • content/docs/data-modeling/queries.mdx (via @objectstack/spec)
  • content/docs/data-modeling/schema-design.mdx (via @objectstack/spec)
  • content/docs/data-modeling/seed-data.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation-rules.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation.mdx (via @objectstack/spec)
  • content/docs/deployment/troubleshooting.mdx (via @objectstack/spec)
  • content/docs/getting-started/cli.mdx (via @objectstack/spec)
  • content/docs/getting-started/common-patterns.mdx (via @objectstack/spec)
  • content/docs/getting-started/examples.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-reference.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx (via @objectstack/spec)
  • content/docs/getting-started/validating-metadata.mdx (via @objectstack/spec)
  • content/docs/kernel/cluster.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/auth-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/cache-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/data-engine.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/index.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/metadata-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/storage-service.mdx (via packages/spec)
  • content/docs/kernel/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/email-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sharing-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx (via packages/spec)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/spec)
  • content/docs/permissions/authorization.mdx (via packages/spec)
  • content/docs/permissions/permission-sets.mdx (via @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx (via @objectstack/spec)
  • content/docs/permissions/profiles.mdx (via @objectstack/spec)
  • content/docs/permissions/roles.mdx (via @objectstack/spec)
  • content/docs/permissions/sharing-rules.mdx (via @objectstack/spec)
  • content/docs/plugins/adding-a-metadata-type.mdx (via @objectstack/spec)
  • content/docs/plugins/development.mdx (via @objectstack/spec)
  • content/docs/plugins/index.mdx (via @objectstack/spec)
  • content/docs/plugins/packages.mdx (via @objectstack/spec)
  • content/docs/protocol/backward-compatibility.mdx (via @objectstack/spec)
  • content/docs/protocol/diagram.mdx (via packages/spec)
  • content/docs/protocol/knowledge.mdx (via @objectstack/spec)
  • content/docs/protocol/objectos/config-resolution.mdx (via @objectstack/spec)
  • content/docs/protocol/objectos/i18n-standard.mdx (via @objectstack/spec)
  • content/docs/protocol/objectos/lifecycle.mdx (via @objectstack/spec)
  • content/docs/protocol/objectos/plugin-spec.mdx (via @objectstack/spec)
  • content/docs/protocol/objectos/runtime-capabilities.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx (via packages/spec)
  • content/docs/protocol/objectql/query-syntax.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx (via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx (via packages/spec)
  • content/docs/protocol/objectui/record-alert.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx (via @objectstack/spec)
  • content/docs/releases/implementation-status.mdx (via @objectstack/spec)
  • content/docs/releases/index.mdx (via @objectstack/spec)
  • content/docs/releases/v9.mdx (via @objectstack/spec)
  • content/docs/ui/create-vs-edit-form.mdx (via @objectstack/spec)
  • content/docs/ui/dashboards.mdx (via @objectstack/spec)
  • content/docs/ui/forms.mdx (via @objectstack/spec)
  • content/docs/ui/index.mdx (via @objectstack/spec)
  • content/docs/ui/setup-app.mdx (via @objectstack/spec)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

Comment thread packages/metadata-core/src/protocol-handshake.ts Fixed
Comment thread packages/metadata-core/src/protocol-handshake.ts Fixed
…838)

The comparator match (`^(<=|>=|<|>)\s*(.+)$`) and the hyphen-range match
(`^(.+?)\s+-\s+(.+)$`) had whitespace/any-char overlap — polynomial-ReDoS
shapes on the externally-authored engines string.

- comparator: peel the operator by fixed prefix + trim, no backtracking match
- hyphen range: split on the whitespace-delimited hyphen (fixed anchor), not a
  lazy (.+?)…(.+) match
- bound the range string to 128 chars up front (a real SemVer range is short;
  overlong input is unrecognized = admit-with-warning, never a slow scan)

Behavior unchanged (100 tests green); adds a ReDoS-regression test asserting
pathological inputs resolve to null in well under 50ms.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HjDghc79qFSJkJt2zvBxtU
@github-actions github-actions Bot added the size/l label Jul 5, 2026
@os-zhuang
os-zhuang marked this pull request as ready for review July 6, 2026 00:28
@os-zhuang
os-zhuang merged commit 60dc3ba into main Jul 6, 2026
17 checks passed
@os-zhuang
os-zhuang deleted the claude/bold-clarke-o4op29 branch July 6, 2026 00:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/l tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

3 participants