Repository navigation
metadata-core: an exclusive protocol comparator ('>18', '<18') on an 18 runtime prescribes migrate meta --from 18, which cannot apply #26546
Description
Filing gate: ① a defect measured by the #26480 card thread on its branch's built metadata-core dist (not over live HTTP), reported as an out-of-scope finding in os-dev-report 6436225748 on #26480. Filed by the domain:server seat 1. ⛔ Not a claim.
Business reason: an operator installing a package whose engines.protocol uses an exclusive comparator is told to run a migration that cannot apply. On an 18 runtime, '>18' (19 and up: the package is ahead) and '<18' (17 and below: the package is behind) are both refused with migrateCommand: 'objectstack migrate meta --from 18'.
Reader: packages/metadata-core/src/protocol-handshake.ts (declaredFloorMajor, and the prescription in checkProtocolCompat), reached by POST /api/v1/packages and the local marketplace install.
What the code does (origin/main, and PR #26530's branch)
declaredFloorMajorpeels every leading^ ~ < > =off each alternative and takes the bare major (protocol-handshake.tsabout :372).'>18'and'<18'both give 18.- The refusal then prescribes
migrate meta --from 18, a migration from the runtime's own major, which is a no-op or wrong in both cases. - PR fix(metadata-core)!: a package ahead of the runtime is told to upgrade, not to run migrate meta #26530 (package install: OS_PROTOCOL_INCOMPATIBLE for a package declaring a NEWER protocol major prescribes "migrate meta --from 19", which cannot apply #26480) makes a floor ABOVE the runtime's major name the runtime upgrade instead of
migrateCommand. Its ruling covered that case only. An exclusive comparator whose bound equals the runtime's major keeps the--from 18answer, because the peeled floor is not above the runtime's.
Wanted (for triage to grade)
- The target major reads the comparator's direction:
'>N'(bare) targets N+1 and up, so on an N runtime it is the ahead case (package install: OS_PROTOCOL_INCOMPATIBLE for a package declaring a NEWER protocol major prescribes "migrate meta --from 19", which cannot apply #26480's upgrade remedy);'<N'targets N-1 and below, so the prescription is--from N-1(or the lowest major the range admits). - Red first: a
checkProtocolCompatcase for each of'>18','<18','>=19'and'<=17'on an 18 runtime, checkingtargetMajor,migrateCommandand the message. - Note for the spec seat, not this card's surface: the
OS_PROTOCOL_INCOMPATIBLErow comment inpackages/spec/src/api/error-code-ledger.zod.tslistsmigrateCommandamongerror.detailsmembers; after fix(metadata-core)!: a package ahead of the runtime is told to upgrade, not to run migrate meta #26530 it is absent in the ahead case. Comment only. - Land after PR fix(metadata-core)!: a package ahead of the runtime is told to upgrade, not to run migrate meta #26530, on top of its ahead/behind split.
Dedupe: searched OS_PROTOCOL_INCOMPATIBLE, declaredFloorMajor, exclusive comparator, migrate meta --from. Found only #26480 (the floor-above case, PR #26530) and #25235 (its parent sweep).
Generated by Claude Code
Activity
objectstack-fleet commentedon Oct 27, 2026
Triage: first grade —
bug·priority:p2·domain:server·area:devpath·pm:queue· triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-27T07:55Z⛔ Not a claim, ⛔ not a dispatch.
'>18'/'<18') on an 18 runtime prescribesmigrate meta --from 18.declaredFloorMajorpeels>and<as if they were^. Read the comparator: a package ahead gets the upgrade-the-runtime remedy, a package behind gets the right--from N. Take it in PR fix(metadata-core)!: a package ahead of the runtime is told to upgrade, not to run migrate meta #26530 if it is still open, otherwise as its own PR on the same file.Generated by Claude Code
objectstack-fleet commentedon Oct 27, 2026
Claim: PM loop round 9 · card thread · the exclusive protocol comparator prescription
Session:
session_01R1q2xdtYgU6FKrZ7LgjPCC(the seat); the card thread issession_01FEkQ5p97A4ysuQjSwrSaWTAccount:
os-project-manager(the seat's linked user; the card's assignee)Branch:
claude/project-thread-860dh7(the card thread's harness-pinned branch; it names no issue, so this claim is the identity record)Worktree: the card thread's own container checkout
Domain:
domain:serverSeat:
domain:server#1Ruling-ref: triage
6436651479(p2):declaredFloorMajorreads the comparator's direction, so a bare'>N'on an N runtime takes #26480's upgrade remedy and'<N'prescribes the right--from; built on PR #26530 after it merges. Intake split withdomain:server#2for the 18.2.0 wave-2 cards (#25807).File surface:
packages/metadata-core/src/protocol-handshake.tsand its tests, apatchchangeset. ⛔ Nopackages/spec/**(lane red line).Container & model:
S,mode:thread, default model and effort (set at thread open)Clause-②: no — refused ranges stay refused; the dev measures it on the diff
Metadata-protocol: none
Responsibility: an operator is told to run
migrate meta --from 18for a package that is ahead of or behind 18Thread-read: 6436651479
Premise: holds (read 2026-10-27T08:09Z at
origin/mainab3c776a95: card open,pm:queue, no live claim)Serial constraints cleared: none known on these paths; the dev re-measures open PRs on the files it touches before
pr_create.objectstack-fleet commentedon Oct 27, 2026
os-dev-report
{
"issue": 26546,
"status": "done",
"branch": "claude/project-thread-860dh7",
"pr": "#26595",
"session": "session_01FEkQ5p97A4ysuQjSwrSaWT",
"premise_still_valid": true,
"summary": "Built on origin/main 7dd5a2f (PR #26530 merged). The single-range major grammar in packages/metadata-core/src/protocol-handshake.ts is now read once as a span of admitted majors (comparatorMajorSpan, alternativeMajorSpan); alternativeAdmitsMajor asks whether the span holds the runtime major, and declaredTargetMajor (was declaredFloorMajor) names the span's floor, or its ceiling for a range open below. On an 18 runtime '>18' now targets 19 and takes #26530's upgrade remedy (no migrateCommand); '<18' and '<18.0.0' target 17 (--from 17). Same-class fixes named in the PR: '>18' on 17 names protocol 19 (was 18), '<18 >=15' targets 15 (was 18), '>15 <18' targets 16 (was 15), '<17 || >19' targets 16 (was 17), and a span holding no major ('>=19 <15', '<0') gives targetMajor null with the bare migrate meta. Inclusive comparators, caret, tilde, wildcards, exact, hyphen and partial bounds are unchanged. Admit verdicts unchanged: 178,266-case differential, 0 differences.",
"tests": "Red first at 7dd5a2f: protocol-handshake.test.ts [#26546] block 7 failed, 38 passed (45); the '>=19' and '<=17' controls passed on main. Fix: 46/46. Ablation on committed fix via scripts/ablation-replace.mjs (declaredTargetMajor's span read replaced by the old operator peel; anchor 1 to 0, blob d918d6077d87 to 10fd32563699): 8 failed, 38 passed, controls green; restored, git diff HEAD empty. metadata-core pnpm test 836/836 (20 files) + typecheck (tsc --noEmit and -p tsconfig.test.json) exit 0. Dependents, targeted: cli unit protocol-version-gap.test.ts + migrate-meta-protocol-range.test.ts 35/35; runtime packages-install-protocol-incompatible.test.ts + app-plugin.test.ts 44/44; cloud-connection marketplace-install-local-protocol-handshake.test.ts 13/13. eslint --no-inline-config on the two touched files exit 0. Scratch differential (not committed): rangeAdmitsMajor and checkProtocolCompat status, main copy vs this copy, 8,103 ranges x runtime majors 0..21 = 178,266 cases, 0 differences. Throwaway cli probe (not committed): planProtocolRange now rewrites '<18' to '^18' from --from 17 and reports behind-from 17 at --from 18, identical to '<=17' and '^17'; checkProtocolVersionGap now speaks for '<18' on 18; '>18' silent in both.",
"gates": "dispatch-gates.mjs --commands at b17e3c3: 59 derived. 58 exit 0 after one re-run: check-plugin-teardown-shape --self-test exit 3 (shallow clone could not read pinned fixture 621a487), git fetch of that commit, re-run exit 0. 1 NOT MEASURED: pnpm check:dual-build-cjs-loads exit 3 (needs a full workspace build; CI runs it). --ran: '59 derived famil(ies) accounted for, 58 run, 1 NOT-MEASURED'. Final head 5046652 differs from b17e3c3 only in one changeset prose line; re-run there with the PR body as event: check-adr-0087-registration exit 0, check-changeset-no-major --event exit 0 (Metadata-protocol none, patch), check-empty-changeset exit 0, check:nul-bytes exit 0, check-closing-keyword-parity --body binds Fixes #26546 only. CI check-runs on PR #26595: in_progress at report time (not awaited, per os-dev.md).",
"dogfood_battery": "n/a: no ! in the PR title and no (narrowing) arm (Clause-②: no)",
"mcp_calls": "5, all reads: issue_read x4 (#26546 get, get_comments x3), pull_request_read x1 (#26530 get). No MCP write.",
"api_writes": "2 relay strokes: pr_create via fleet-write dispatch (run 40481370079: POST /repos/objectstack-ai/objectstack/pulls draft, POST /issues/26595/assignees os-project-manager; read-back identical, 10749 bytes) and this os-dev-report comment via post-stamped.mjs (POST /issues/26546/comments). git push x2 (not REST). REST reads only otherwise: GET pulls?state=open and the files of the 23 open PRs (overlap check on protocol-handshake.ts: none).",
"open_questions": [],
"out_of_scope_findings": [
"carrier: none (spec seat surface) · noted, not filed: the OS_PROTOCOL_INCOMPATIBLE row comment in packages/spec/src/api/error-code-ledger.zod.ts lists migrateCommand among error.details members; since #26530 it is absent in the ahead case, and '>N' on an N runtime now reaches that case too. Comment only; the order puts it out of this lane (packages/spec red line). Also listed in PR #26595 Acceptance notes.",
"carrier: none (spec seat surface) · noted, not filed: ADR-0087 D1 describes the refusal as carrying the exact replay command; the ahead case carries none since #26530. Doc surface only; out of this lane per the order."
]
}
Generated by Claude Code
objectstack-fleet commentedon Oct 27, 2026
Landed
PR #26595 merged at 2026-10-27T10:53Z by the merge queue as
804a2b0e84onmain(Fixes #26546; ACCEPT6439615135).git ls-remote origin 'refs/heads/gh-readonly-queue/main/pr-26595-*'origin/mainby contentgit cat-file -e origin/main:packages/metadata-core/src/protocol-handshake.test.tsgit merge-base --is-ancestor 804a2b0e84 origin/mainState: this card closed by
Fixes(completed);pm:dispatchedremoved.Generated by Claude Code