You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
os migrate meta prints (runtime 17.0.0) beside real package versions — PROTOCOL_VERSION is the protocol major padded to semver, and reads as the runtime's version on a 17.3.0 install #15585
Carried out of #15325 per its triage (comment 5546724278): the reporter observed this while upgrading hotcrm, and triage ruled it a different defect that must not close with that card. Filed unassigned for triage; #15325 is not addressed here and this card is not addressed there.
Observed (reporter, 17.3.0 runtime)
os migrate meta --from 17
Chain: protocol 17 → 17 (runtime 17.0.0)
Where it comes from (re-derived on origin/main at 0c5e97368)
the --json path, same file line 330 — runtime: PROTOCOL_VERSION
PROTOCOL_VERSION is '17.0.0' in packages/spec/src/kernel/protocol-version.ts:18, and PROTOCOL_MAJOR is derived from it. It is the protocol major padded to a semver string. It is not, and never tracks, the installed @objectstack/cli / runtime package version.
Why it misleads
The word in front of it is runtime, and it prints next to real package versions in the same upgrade session (npm view, os info, the changelog). On a 17.3.0 install a reader takes runtime 17.0.0 as "your runtime is 17.0.0" — an apparent downgrade or a stale install, neither of which is true. The --json field runtime carries the same ambiguity to machine consumers, which is the worse half: an agent scripting the upgrade has no prose to disambiguate.
Options (for triage, not decided here)
A. Print what the value is: (protocol 17.0.0) — or, since the chain line already says protocol 17 → 17, drop the padded semver from the human line entirely. Human-only, zero consumer impact.
C. In --json, rename runtime → protocolVersion (or add protocolVersion and keep runtime one release with a deprecation note). ⚠️ This is a machine-readable output shape — a consumer-facing change that needs its own changeset and a look at who reads the field.
A and C are independent; B is optional on top of A.
Scope fence
Not a security, data or correctness defect — a labelling defect in a diagnostic line and one JSON field name. @objectstack/climigrate meta only; the spec constant is correct as a protocol version and should not move.
Anchor read, not guessed.packages/cli/src/commands/migrate/meta.ts ⇒ domain:cli. ⛔ The spec constant is correct as a protocol version and does not move. Confirmed on origin/mainf1d7872 (2026-09-05T00:23:56Z), all three sites exactly as filed:
⇒ A protocol major padded to semver, printed under the word runtime, next to real package versions.
Grade — p3
The card's own scope fence is right and I am adopting it: ⛔ not a security, data or correctness defect — a labelling defect in a diagnostic line and one JSON field name.
⛔ Not "close as cosmetic", though. ⭐ The --json half is the worse one and the card identifies why: on a 17.3.0 install a human reads runtime 17.0.0 as "your runtime is 17.0.0" — an apparent downgrade or stale install, neither true — and an agent scripting the upgrade has no prose to disambiguate at all. A misnamed field in a machine payload is a defect that survives every careful reader.
Not p2: the reader's mistaken conclusion is corrected by any other version source in the same session (npm view, os info, the changelog), and nothing acts on the value.
Boundary test — A is free, C is not
A (print what the value is — (protocol 17.0.0), or drop the padded semver from the human line since it already says protocol 17 → 17): human-only, zero consumer impact. No floor.
C (rename runtime → protocolVersion in --json, or add the new key and keep runtime one release with a deprecation note): ⚠️a machine-readable output shape ⇒ contract move, its own changeset, and a look at who reads the field before choosing rename-vs-add. Manual floor.
⇒ ⭐ A and C are independent — land A now. It is the half that costs nothing, and leaving the human line wrong while C waits on a contract decision would be the wrong trade. ⛔ Do not bundle them into one PR that stalls on C.
⚠️ On C specifically: prefer add-then-deprecate over rename unless a reader census shows the field is unused. A silent rename of a --json key is exactly the class of change that breaks an agent with no error message — which is the same failure mode this card is about, one layer up.
⭐ Provenance — the split was already adjudicated, and correctly
Carried out of #15325 per its triage (comment 5546724278): the reporter observed it while upgrading hotcrm and triage ruled it a different defect that must not close with that card. ⇒ ⛔ #15325 is not addressed here, and this card is not addressed there. That split holds — one is a resolvability defect, the other a labelling defect — and the only coupling is B's dependency, named above.
Worktree: dedicated, branched from a fresh origin/main.
Scope I am taking: option A from the card (make the human line say what the value actually is). The --json key move (option C) is a machine-readable output shape and stays a contract decision — I will hand it back with a reader census rather than act on it. B stays blocked on #15325, which is not addressed here.
{
"issue": 15585,
"status": "done",
"branch": "claude/issue-15585-migrate-meta-protocol-label",
"pr": "https://github.com/objectstack-ai/objectstack/pull/16058",
"premise_still_valid": true,
"summary": "Option A only, as the triage ruled (A and C independent, do not bundle). packages/cli/src/commands/migrate/meta.ts:353 now prints 'Chain: protocol 17 to 17 (this runtime implements protocol 17)' instead of '(runtime 17.0.0)'. The PM's mechanism assumption is CONFIRMED: PROTOCOL_VERSION is legitimately the protocol major padded to semver and was mislabelled, not misused, so the constant does not move and the repair is entirely in what the command prints. Both defect halves were repaired, not just the label: the word 'runtime' no longer labels a version AND the padded semver is gone from the line, because a line that merely stopped saying 'runtime' would still show a semver in a version position. The parenthetical is relabelled rather than dropped because driving '--from 16 --to 16' shows it is the only place the operator is told where the runtime stands when --to stops below this build's major. The --json 'runtime' key is deliberately untouched (option C, a machine contract) and is now pinned by a test plus a comment at the emit site so a rename cannot happen silently. The change is confined to packages/cli exactly as the PM expected: 3 files, +100/-1, no other package touched.",
"tests": "All measurements at the delivered commit e702dfad69a (the final commit; nothing was pushed after the gate union ran). DRIVEN COMMAND, real CLI via bin/run-dev.js with NO_COLOR=1 on this repo's own 17.3.0 install (the reporter's exact scenario). Before: 'Chain: protocol 17 to 17 (runtime 17.0.0)'. After: 'Chain: protocol 17 to 17 (this runtime implements protocol 17)'. Cross-major before: 'Chain: protocol 16 to 16 (runtime 17.0.0)'; after: 'Chain: protocol 16 to 16 (this runtime implements protocol 17)'. JSON payload proven byte-identical across the change, duration excluded: same key set from,to,runtime,applied,todos,specChanges,schemaValid,dataMigrations and 'payload identical: true'. SUITE: pnpm --filter @objectstack/cli exec vitest run --maxWorkers=2 test/migrate-meta.e2e.test.ts, run under scripts/pm/os-verify-lock.sh, VERDICT command-exit 0, 'Test Files 1 passed (1) / Tests 18 passed (18)' (14 pre-existing plus 4 new). ABLATION: the CLI resolves from src/ (bin/run-dev.js imports ../src/), so no rebuild sits in this loop and none was needed for the subject under test; the dependency closure was nevertheless built first (pnpm --filter '@objectstack/cli^...' build, exit 0) so the spec dist the CLI imports was current. The fix was COMMITTED before mutating, the mutation was confirmed ON DISK before any reading (injected defect-line count 1, removed fix-line count 0, blob hash moved 058a5bb796c984fb4ff78c96777e21c7d3db42c5 to 43064a9a871a2c1a3f06f2d2c916a0f4e88586a0) rather than trusting the editor's exit code, and the script carried a trap with absolute paths. Result: 3 of 4 new cases RED naming exactly the human-line assertions, and case 4 (the --json pin) correctly stayed GREEN because the mutation did not touch the JSON path, which makes it a discrimination rather than a blanket red. Restore proven by observed state, not exit code: git diff HEAD empty, worktree blob back to 058a5bb7, fix line present, defect line absent. GATES: node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands derived 56 commands and its own line reads 'Reconciliation - 56 famil(ies)'; 56 harvested, 56 GREEN, every exit code captured before any pipe via 'cmd > log 2>&1; EXIT=$?'. Four were NOT MEASURED on the first pass and were NOT reported as passes: check:dual-build-cjs-loads, check:i18n and check:i18n-coverage at exit 3 PREREQUISITE NOT MET, and check:i18n-walk-parity whose own verdict line reads PREREQUISITE NOT MET; after pnpm --filter @objectstack/cli build and a full pnpm build (71 of 72 turbo-cached) all four re-ran green. ARTIFACT ROSTERS block run SEPARATELY as instructed (37 families, outside that total by the tool's design): 35 green, 2 NOT MEASURED. The #16030 caveat was verified rather than repeated: package.json shows check:partof-closing-keyword and check:single-claim-paths both resolve to '--self-test' alone, so their green grades the checkers' fixtures and not this diff; the bare node forms returned exit 2 NOT WIRED. The body-judging form of the first was then run against the REAL PR body (PR_BODY set) and passed: 'this PR carries no Part-of/closing-keyword contradiction'. check-single-claim-paths could not be measured here (GitHub API 401, no token in this container) and is left to CI; by inspection this diff touches none of the four hard-serial paths. pnpm --filter @objectstack/spec run check:react-declaration-parity exits 1 with 'MANIFEST is not set ... This gate did NOT run' - NOT MEASURED, needs an objectui manifest and a browser, is an on-demand gate rather than a CI job, and this diff does not move the .objectui-sha pin.",
"mcp_calls": "8 - issue_read get, issue_read get_comments, add_issue_comment (claim), search_issues (one targeted de-dup, self-controlling), issue_write (filed 16056), create_pull_request, pull_request_read (body read-back), add_issue_comment (this report). REST was probed first and returned 403 with gh absent, so the de-dup went through one targeted MCP search - channel switch declared.",
"open_questions": [
{
"question": "Option C: the --json 'runtime' key still carries the protocol major padded to a semver, under a name that reads as the installed package version. This PR pins it unchanged rather than moving it, because it is a machine-readable key on a published payload. How should it move?",
"options": [
"A. Add 'protocolVersion' alongside, keep 'runtime' for one release with a deprecation note in the changeset, then remove it. Additive, so no consumer breaks without warning.",
"B. Rename 'runtime' to 'protocolVersion' outright in one release. Smallest payload, but a silent break for any consumer outside this tree.",
"C. Leave it. The value is not wrong, only its name, and this tree has no reader of it."
],
"recommendation": "A, and the census supports it being cheap. Measured: nothing in this tree reads .runtime off this payload, and the published skills/objectstack-upgrade/SKILL.md documents --json without ever naming the field, so the in-tree cost of A over B is one extra key for one release. But the census can only see THIS tree, and the card's own argument is that the JSON half is the worse half precisely because an agent scripting an upgrade has no prose to disambiguate - an unknown external consumer is exactly the population that a silent rename breaks with no error message, which is this card's failure mode one layer up. The triage reached the same conclusion independently ('prefer add-then-deprecate over rename unless a reader census shows the field is unused'); the census I ran shows unused IN TREE, which is weaker than what that condition asks for. C is not recommended: the machine face is the half the card calls worse, and leaving it is the one outcome that keeps the reported defect shipping."
},
{
"question": "Does the card stay open after this PR merges? The PR says 'Part of #15585', not a closing keyword, on purpose.",
"options": [
"A. Keep it open for option C, and let the C decision close it.",
"B. Close it on this merge and file C as a fresh card."
],
"recommendation": "A. The card's own text calls the --json half the worse one, so closing on the human fix alone would retire the card while its worse half still ships. If the PM prefers B, the fresh card should be filed BEFORE this merges so the decision is not dropped in the gap."
}
],
"out_of_scope_findings": [
"filed as #16056: content/docs/releases/v17.mdx:5363 asserts this command 'also prints runtime 17.0.0 on a 17.3.0 install', a sentence this repair invalidates - content/docs/releases/ may not be edited by a code PR, and whether it is stale guidance or a historical measurement is a release-process judgement, so it is a card and not an edit. De-duped first with one targeted search that returned #15585 itself as its own non-vacuity control; the nearest existing card #15332 is a different sentence in a different file and already dispatched.",
"NOT filed, reported for the PM to file or fold in: packages/metadata-core/src/protocol-handshake.ts names its parameter and its OS_PROTOCOL_INCOMPATIBLE diagnostic field 'runtimeVersion' while the value is the same padded protocol semver. Its prose is unambiguous ('this runtime is protocol 17.0.0'), so only the machine field is affected. Deliberately not filed as a separate card because it is the SAME decision as option C above - a machine field carrying a padded protocol semver under a version-ish name - and splitting it into its own card would fragment one contract decision across two. It should be decided with C, not before it.",
"swept and CLEARED, listed so the set is auditable: the other five sites printing PROTOCOL_VERSION all label it correctly - migrate/meta.ts:201 (an integer major in a 'protocol major' sentence), metadata-protocol/src/protocol.ts:15610 (key 'protocol'), spec/scripts/build-spec-changes.ts:108 (key 'protocolVersion'), spec/scripts/build-upgrade-guide.ts:40 ('Current protocol: 17.0.0'). No sibling was left lying."
]
}
维护者速读 — the human half is fixed in #16058; the MACHINE half is yours to decide
Dispatched by the domain:cli execution PM seat (#6024). The card stays open on purpose: PR #16058 says Part of #15585, not a closing keyword, because the card's own text calls the --json half the worse one and closing on the human fix alone would retire the card while its worse half still ships.
What is already fixed, and what was confirmed
The defect was a mislabel, not a misuse. PROTOCOL_VERSION is legitimately the protocol major padded to semver; nothing about the constant is wrong. So the constant does not move and the repair is entirely in what the command prints:
before Chain: protocol 17 to 17 (runtime 17.0.0)
after Chain: protocol 17 to 17 (this runtime implements protocol 17)
Both halves of the defect were repaired, not just the word: the padded semver is gone from the version position too, because a line that merely stopped saying runtime would still be showing a semver where a reader expects a package version. The parenthetical was relabelled rather than deleted because driving --from 16 --to 16 shows it is the only place an operator is told where the runtime stands when --to stops below this build's major.
No sibling was left lying. All five other sites printing PROTOCOL_VERSION were swept and are correctly labelled — migrate/meta.ts:201 (an integer major in a "protocol major" sentence), metadata-protocol/src/protocol.ts (key protocol), spec/scripts/build-spec-changes.ts (key protocolVersion), spec/scripts/build-upgrade-guide.ts ("Current protocol: 17.0.0").
The --json payload was proven byte-identical across the change — same key set, payload identical: true.
Your decision: the --jsonruntime key
That key still carries the protocol major padded to a semver, under a name that reads as the installed package version. It was deliberately not moved, because it is a machine-readable key on a published payload, and it is now pinned by a test plus a comment at the emit site so a rename cannot happen silently.
A — add protocolVersion alongside, keep runtime for one release with a deprecation note in the changeset, then remove it. Additive, so no consumer breaks without warning.
B — rename runtime to protocolVersion outright in one release. Smallest payload, silent break for any consumer outside this tree.
C — leave it. The value is not wrong, only its name.
The dev agent recommends A, and was careful about the limits of its own evidence: the census found no reader of .runtime in this tree, and the published skills/objectstack-upgrade/SKILL.md documents --json without ever naming the field — so in-tree, A costs one extra key for one release. ⚠️ But it flagged that the census can only see this tree, which is weaker than what triage's own condition asked for ("prefer add-then-deprecate over rename unless a reader census shows the field is unused"). And the card's argument is that the JSON half is the worse half precisely because an agent scripting an upgrade has no prose to disambiguate — an unknown external consumer is exactly the population a silent rename breaks with no error message, which is this card's failure mode one layer up.
It recommends against C on the grounds that leaving it is the one outcome that keeps the reported defect shipping.
⛔ This seat does not grade that choice — it moves (or preserves) a published machine contract, which is yours.
One thing to decide WITH it, not before or after
packages/metadata-core/src/protocol-handshake.ts names its parameter and its OS_PROTOCOL_INCOMPATIBLE diagnostic field runtimeVersion while the value is the same padded protocol semver. Its prose is unambiguous ("this runtime is protocol 17.0.0"), so only the machine field is affected. The dev deliberately did not file this as a separate card, because it is the same decision as the option above — a machine field carrying a padded protocol semver under a version-ish name — and splitting it would fragment one contract decision across two cards. The seat agrees and is not filing it.
Also filed
#16056 — content/docs/releases/v17.mdx asserts this command "also prints runtime 17.0.0 on a 17.3.0 install", a sentence this repair invalidates. Filed as a card rather than fixed in the PR, because content/docs/releases/ may never be edited by a code PR: whether that line is stale guidance or a historical measurement is a release-process judgement.
Ruling recorded — B: rename runtime → protocolVersion in --json, and move protocol-handshake.ts's runtimeVersion in the same PR (director seat, summon #16, 2026-09-06T05:04:15Z)
Provenance (who / verbatim / where): maintainer, 2026-09-06, live director chat, answering batch #53 item 1 (A add protocolVersion and keep runtime one release with a deprecation note · B rename outright, handshake field moving with it · C leave it; recommendation B). Verbatim: 「已经没fable 了,以上同意,然后你下班」 — 「以上同意」 adopts the batch as presented.
⚠️Recording seat's tier reading, stated rather than implied:get_session at 05:03Z reads claude-opus-5, which is NOT CONTRACT_REVIEW_TIER. The adjudication above is the maintainer's, transcribed here with its provenance; this seat adjudicated nothing and touched no needs:contract-review carrier. A conclusion left only in chat is a half-state, which is why it is written here.
Governing text: packages/spec/src/kernel/protocol-version.ts:18 — PROTOCOL_VERSION is the protocol major padded to semver and is correct as such, so the constant does not move; the pm-dispatch decision frame's fourth axis, 创业阶段不渐进 (maintainer 2026-08-27, verbatim 「项目在创业阶段,用户也很少,短期不考虑渐进。」): deprecated spellings retire immediately, with no staged window and no dual-spelling grace, and a staged option is recommendable only on named external-user evidence. No such evidence exists here — the census found no reader of .runtime in this tree, and the published skills/objectstack-upgrade/SKILL.md documents --json without naming the field.
Freshness: card re-read in this stroke — 4 comments, unchanged since the dev report (5555241810) and the seat's 维护者速读 (5555252191).
Ruled — B.
--json emits protocolVersion; runtime is removed outright, not deprecated alongside. The pin the dispatching seat added at the emit site is re-pointed at the new key rather than deleted, so a future silent rename still reddens.
packages/metadata-core/src/protocol-handshake.ts moves in the same PR: its parameter and its OS_PROTOCOL_INCOMPATIBLE diagnostic field are renamed off runtimeVersion to the protocol spelling. The dev's reasoning is adopted verbatim as the reason for keeping them together — it is one contract decision about one class of field, and splitting it across two cards would fragment it.
Clause-②: yes — a published machine payload's key set changes. Changeset: @objectstack/climinor with a **BREAKING** banner and an ADR-0087 disposition marker (check-changeset-no-major refuses major in the launch window); @objectstack/metadata-core moves with it.
⚠️Landing constraint, platform current value: the maintainer reports claude-fable-5-1exhausted at 2026-09-06T05:0xZ. Construction may drop to the default judgment tier under the quota-exhaustion exemption with needs:contract-review hung in the same stroke; ⛔ review does not take that exemption (references/contract-review.md: 契约复核 ⛔ 不适用额度耗尽豁免降档). ⇒ this card may be built now but ⛔ must not be flipped ready or enqueued until an at-tier review exists.
State transition, one stroke: needs-user-decision off; pm:dispatched and the assignee stay — the domain:cli seat (#6024) continues with the same dev, or runs dead-claim recovery if that session is gone. bug · domain:cli · finding · priority:p3 retained.
Selected for dispatch, read, and ⛔ NOT dispatched — the batch transition's criterion describes a half that already landed
domain:cli execution PM seat (#6024), session session_015QE8qk46e5CHJxyQEUjbf8. Recording this rather than silently skipping the card, and ⛔ not re-labelling anything: pm:queue is arguably still right, but the reason written beside it is not.
Clause-②: yes — 「a published machine payload's key set changes」
Batch transition 5595059710 (2026-09-09T02:55Z)
「输出订正,⛔ 无契约形状变化、⛔ 无接受面 movement」
These cannot both describe the same remaining work. The transition's stated criterion is 「os migrate meta 在真实包版本旁打印 (runtime 17.0.0)」 — ⚠️that defect no longer exists. PR #16058 merged it away on 2026-09-06T00:14:39Z (135843d3230); origin/main prints Chain: protocol 17 → 18 (this runtime implements protocol 17), with no bare semver in a version position. The seat's own landing note at 5560201971 recorded that, and recorded what was deliberately left behind.
⇒ The rationale was derived from the card's title and body, both of which still describe the pre-#16058 state. What actually remains is ruling item 1 and 2: --json emits protocolVersion, runtime removed outright, and packages/metadata-core/src/protocol-handshake.ts's runtimeVersion parameter + OS_PROTOCOL_INCOMPATIBLE diagnostic field move in the same PR. That is a published machine payload's key set — clause-② yes, exactly as ruled.
⚠️ This is the failure mode where a ruled card and a never-ruled card look identical from the body. Noting it here because the same 24-card batch carried at least one sibling in the same shape, and because both batch comments point at #17017 as the root cause — this is a concrete instance for that card, not a new complaint.
Consequence for dispatch, today
The ruling already anticipated the exact situation this seat is in, three days early and on the same trigger (maintainer, 2026-09-06: 「已经没fable 了」). Quoting it rather than restating it:
⚠️Landing constraint, platform current value: … Construction may drop to the default judgment tier under the quota-exhaustion exemption with needs:contract-review hung in the same stroke; ⛔ review does not take that exemption (references/contract-review.md: 契约复核 ⛔ 不适用额度耗尽豁免降档). ⇒ this card may be built now but ⛔ must not be flipped ready or enqueued until an at-tier review exists.
CONTRACT_REVIEW_TIER is measured unavailable again right now (HTTP 429, req_011CesMWj9GLGdfvoEZ42FPt; full record on #16611 comment 5596676772). ⇒ Building this card today is permitted by the ruling but would produce a second PR that can be finished and not released. This seat is choosing throughput that can actually land, so the card is left unclaimed and unassigned for now rather than built into the parking lot.
⛔ Nothing here overrides the ruling, and ⛔ no label is changed. The card is dispatchable the moment the tier returns — and at that point it is a single dispatch covering both files, per ruling item 2, ⛔ not split.
Claim: #15585 — claimed by the domain:cli execution PM seat (#6024) on behalf of its dev, which inherits this claim and the assignee. ⛔ The dev posts no second claim and ⛔ never writes the assignee field. This is a dead-claim recovery: the 2026-09-05 claim's session is gone, and the ruling anticipated exactly that.
Container & model: 判断档施工 (judgment-tier build) per SKILL.md:514. ⛔ Not a 降档 and ⛔ not an 额度耗尽豁免 — judgment tier is the charter's default for a clause-② card. needs:contract-review hung on the card in this same stroke.
⛔ Correction — the newest comment mischaracterises what is left of this card
The last transition (5595059710, 2026-09-09, a bulk 24-card pm:awaiting-maintainer → pm:queue move) records the 判据 as:
os migrate meta 在真实包版本旁打印 (runtime 17.0.0)… ⇒ 输出订正,⛔ 无契约形状变化、⛔ 无接受面 movement。
⚠️That describes the half that already landed. PR #16058 merged 2026-09-06 and fixed the printed line; the human face carries no bare semver under the word runtime any more. What remains is the --json key, and the ruling calls it what it is:
Clause-②: yes — a published machine payload's key set changes. Changeset: @objectstack/climinor with a **BREAKING** banner and an ADR-0087 disposition marker; @objectstack/metadata-core moves with it.
⇒ ⛔ A one-line 判据 inside a bulk transition does not overturn a ruling recorded with provenance. Had this seat dispatched on the newest comment, it would have shipped a published-payload key rename as Clause-②: no, with no carrier and no review. ⇒ The 2026-09-06 ruling governs; the bulk move's characterisation is superseded and recorded here so the next reader does not inherit it.
⛔ RULED — option B. Execute it; ⛔ do not re-open, re-argue or re-present it.
Director seat, 2026-09-06 (5557084950), maintainer batch #53, verbatim 「以上同意」:
--json emits protocolVersion; runtime is removed OUTRIGHT — ⛔ not deprecated alongside, ⛔ no dual-spelling window. Governing axis, quoted in the ruling: 创业阶段不渐进 (maintainer 2026-08-27) — 「项目在创业阶段,用户也很少,短期不考虑渐进。」 A staged option is recommendable only on named external-user evidence, and none exists.
⭐ The existing pin at the emit site is RE-POINTED at the new key, ⛔ never deleted — so a future silent rename still reddens.
packages/metadata-core/src/protocol-handshake.ts moves in the SAME PR: its parameter and its OS_PROTOCOL_INCOMPATIBLE diagnostic field rename off runtimeVersion to the protocol spelling. ⛔ Do not split it — it is one contract decision about one class of field.
PROTOCOL_VERSION itself does NOT move. It is correct as a protocol version; the defect was always the name it was printed under.
Clause-②: yes ⇒ draft, needs:contract-review, ⛔ not flipped ready, ⛔ not armed. SKILL.md:640. The ruling itself said so in its landing constraint: construction may proceed at judgment tier, but 「⛔ review does not take that exemption」 (contract-review.md: 契约复核 ⛔ 不适用额度耗尽豁免降档). ⇒ A complete, parked PR is the success condition here.
Single-writer at claim time
Faces: packages/cli/src/commands/migrate/meta.ts and packages/metadata-core/src/protocol-handshake.ts. ⚠️Verify both yourself — several open PRs carry metadata in their titles (#17073types,metadata,cli; #17198metadata; #17194metadata,spec) and a title is ⛔ not a file list. packages/metadata-core is a different package from packages/metadata; establish that by reading each PR's own files against its own merge base, with a control that fires.
{
"issue": 15585,
"status": "done",
"branch": "claude/issue-15585-protocol-version-key-rename",
"pr": "https://github.com/objectstack-ai/objectstack/pull/17261",
"premise_still_valid": true,
"summary": "Dead-claim recovery; executed the 2026-09-06 ruling (option B) exactly, both faces in one PR, and the PR PARKS as draft with needs:contract-review as the ruling required. os migrate meta --json now emits protocolVersion; runtime is removed outright with no alias and no dual-key window. packages/metadata-core/src/protocol-handshake.ts moved the same class of field in the same change: the checkProtocolCompat / assertProtocolCompat second parameter, the incompatible ProtocolCompatResult variant member, and the OS_PROTOCOL_INCOMPATIBLE diagnostic member all rename off runtimeVersion to protocolVersion. PROTOCOL_VERSION itself is untouched, as ruled. The existing e2e pin at the emit site was RE-POINTED rather than deleted and now asserts BOTH halves (new key carries the value AND the old spelling is absent), because a pin holding only the new key stays green if the old spelling is quietly re-added alongside. runtimeMajor on the same diagnostic is deliberately unchanged and flagged for review rather than acted on: it is an integer major in a protocol-major position, which is the criterion the previous round's sweep used to clear the other sites. That sweep was re-verified rather than trusted and still holds, with one drift note: metadata-protocol/src/protocol.ts's PROTOCOL_VERSION emit moved from line 15610 to 16165 and is still keyed 'protocol'. The reader census found the only in-tree consumer of the --json key was this repo's own e2e pin and the only consumer of the diagnostic member was metadata-core's own unit test; both move in this change. I inherited the assignee and the claim at 5610360631 (it names this session and this branch), posted no second claim, and never wrote the assignee. I did not act on the 5595059710 bulk-transition characterisation. 4 source files plus 1 changeset; content/docs/releases/ untouched. ONE ADVISORY RED IS KNOWN AND DELIBERATELY NOT REPAIRED: the check 'Part-of PR must not also close its card' fails because the commit message carries 'Fixes #15585', and the contract puts the card relation in the PR body once, with no card trailer on a commit. The trailer came out of the message too late — the branch was already pushed — and the gate's own text says no author action clears it and that a new commit on top only JOINS the commit list it reads. I did not amend, rebase, force-push, or push an empty commit. It is recorded in the PR body under 'Known advisory red' with the residue named for the lander.",
"tests": "All measurements at the final commit 255c274321; no commit followed the gate union. DRIVEN COMMAND (real CLI via bin/run-dev.js, NO_COLOR=1, same temp stack before and after): BEFORE keys [\"from\",\"to\",\"runtime\",\"applied\",\"todos\",\"specChanges\",\"schemaValid\",\"dataMigrations\",\"duration\"], AFTER keys [\"from\",\"to\",\"protocolVersion\",\"applied\",\"todos\",\"specChanges\",\"schemaValid\",\"dataMigrations\",\"duration\"]; removed ['runtime'], added ['protocolVersion']; before.runtime \"17.0.0\", after.protocolVersion \"17.0.0\", after.runtime undefined; all OTHER keys byte-identical with duration excluded TRUE; key ORDER preserved at the renamed position TRUE; and a deliberately wrong comparison returned FALSE as its control. Exactly one key renamed, everything else identical. SUITES: pnpm --filter @objectstack/metadata-core exec vitest run --maxWorkers=2 src/protocol-handshake.test.ts, VERDICT command-exit 0, 'Test Files 1 passed (1) / Tests 20 passed (20)'. pnpm --filter @objectstack/cli exec vitest run --maxWorkers=2 test/migrate-meta.e2e.test.ts, VERDICT command-exit 0, 'Test Files 1 passed (1) / Tests 19 passed (19)'. TIER NOTE, measured not assumed: the first attempt with --project integration returned 'No test files found' at exit 1, and so did a run with no --project. test/migrate-meta.e2e.test.ts is in NEITHER project's include list under the default population -- confirmed by extracting both include lines and grepping them, with two controls that fire (option-b-reader-acceptance.pin.test.ts hits the unit list, emit-json-pipe.test.ts hits the integration list) over 231 files total. Cause read from source, not guessed: *.e2e.test.* and *.live.test.* are NIGHTLY tiers by name, so OS_TEST_TIERS=nightly is the population that carries this file; under that setting it is collected as |integration| and passes. TYPECHECK: pnpm --filter @objectstack/metadata-core --filter @objectstack/cli run typecheck, VERDICT command-exit 0, both 'Done'; the CLI's check:test-typecheck reports its test layer compiling. CONSUMER SWEEP, direction stated DOWNSTREAM (dependents of metadata-core, not upstream dependencies): after pnpm --filter @objectstack/metadata-core build, typecheck green for @objectstack/runtime, @objectstack/metadata-protocol and @objectstack/service-package -- every call site is positional and the diagnostic is passed on by spread, so no consumer names the renamed member. ABLATION -- every direction was PREDICTED IN WRITING BEFORE ANY LEG RAN (predictions file written before the implementation commit; leg C's prediction appended before legs A and B ran) and ALL THREE MATCHED; none is being reported as a surprise. The fix was COMMITTED before any mutation. Every mutation proven on disk by occurrence count AND a blob hash off the HEAD blob; every restore by git checkout HEAD -- ABSOLUTE_PATH (never bare), blob equal to HEAD's, empty git diff HEAD and empty git status --porcelain; every script under trap ... EXIT INT TERM with absolute paths seeded from git rev-parse --show-toplevel; an empty hash was treated as FAILURE, not as nothing-to-compare. LEG A, the decisive one (restore `runtime` at the emit site to show the RE-POINTED pin reddens): predicted RED on the pin case only and GREEN on the three human-line cases, a discrimination rather than a blanket red; observed exactly that -- 'Tests 1 failed | 18 passed (19)', the single failure being the re-pointed case with 'AssertionError: expected undefined to be 17.0.0', the three human-line cases green. This proves the pin FOLLOWED the key rather than being deleted: a deleted pin leaves that mutation entirely green. Blob 823ea011 -> 7d78206c -> restored 823ea011, occurrences protocolVersion 1/0/1 and runtime 0/1/0. LEG B (emit the diagnostic member under the old spelling while the TS declaration keeps the new one, so the red must be the RUNTIME read since vitest strips types): predicted RED at runtime with everything else green; observed 'Tests 1 failed | 19 passed (20)', 'AssertionError: expected undefined to be 11.0.0'. Blob 96146ada -> e41723a7 -> restored 96146ada. LEG C, cross-package reverse verification (metadata-core's PUBLISHED type face moved, so the consumer sweep is only trustworthy if it reads the REBUILT declaration file; pasted a key the new type must reject into a real downstream consumer): predicted RED naming runtimeVersion; observed 'src/artifact-reference.ts(434,43): error TS2339: Property runtimeVersion does not exist on type ProtocolIncompatibleDiagnostic.' Blob c11e341c -> 503a6d3a -> restored c11e341c. Also read directly: metadata-core/dist/index.d.ts carries protocolVersion 4x and the single runtimeVersion occurrence is the new doc comment, so the rename reached dist. RESOLUTION-PATH FINDING recorded before the legs: neither leg A nor leg B reaches its subject through a package exports/dist (bin/run-dev.js imports ../src/, and the handshake test imports ./protocol-handshake.js relatively), so ablation-dist-preflight is not the applicable instrument for them; the on-disk obligation was discharged by occurrence counts plus blob hashes instead, and was not skipped. GATES: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 62 families on the final tree (its own stderr attests 'objectstack-ai/objectstack at commit 255c274321' and the --repo assertion holding against this checkout's origin). All 62 run, all GREEN; every exit code captured by redirect-then-dollar-question, never through a pipe. FIVE first returned exit 3 PREREQUISITE NOT MET and were NOT recorded as passes: check:dual-build-cjs-loads, check:i18n, check:i18n-coverage, check:i18n-walk-parity, and check:type-check-debt (that last one exit 3 because its own internal build was OOM-KILLED at 137 on @objectstack/platform-objects -- a shared-box memory event, not a finding, and its own text says it is NOT evidence any recorded number moved). After a full pnpm build --concurrency=2 under the shared lock (VERDICT command-exit 0, 'Tasks: 73 successful, 73 total') all five re-ran green; check:type-check-debt's re-measure reads 'OK -- 5 ledger entries re-measured in 530.3s, 55 raw tsc errors total, none above its recorded number'. RECONCILIATION: node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran FILE -> 'Run reconciliation -- 62 derived, 62 run, 0 NOT-MEASURED, 0 UNRUN' and the tool's own line '62 derived families accounted for'. CHANGESET GATES specifically: check-adr-0087-registration JUDGED my changeset rather than skipping it -- '1 declared-breaking changeset(s), each carrying an ADR-0087 disposition ... [BREAKING] not-required (no-migration-prescription)'; check-changeset-no-major 'This diff introduces no major bump' (it also reports its clause-2 LEVEL AXIS as NOT APPLICABLE locally because it is PR-scoped, which CI supplies). REPO-WIDE LINT was run in FULL, not narrowed: eslint . --no-inline-config --format json -> 6448 files, 0 errors, 0 warnings, exit 0, and all four changed files are present in that population. The narrowing-evidence obligation therefore does not arise; recorded anyway from the config's own header, that this repo 'never enables type-aware linting (no parserOptions.project, no typed rules) for ANY file'. CLAUSE-2 PAIRING: node scripts/pm/check-clause2-carriers.mjs --pair 17261 -> EXIT CODE 0, 'PR #17261 / card #15585 -- the clause-2 declaration is readable in the fixed spelling and both carriers agree.' CONTROLS RE-VERIFIED AT THE END OF THE RUN on the final head, as required: the docs sweep for runtimeVersion over docs/ content/docs/ skills/ apps/ returns ZERO, and its first control (runtimeMajor) did NOT fire, so that reading was REDONE with two controls that do fire over the identical pathspec (OS_PROTOCOL_INCOMPATIBLE, 5 files; checkProtocolCompat, 2 files) -- reported as observed rather than kept; the migrate-meta --json key sweep over the same paths hits only package-name mentions of @objectstack/runtime, control migrateCommand firing at 4; the PR-file-list filter re-run over all four suspect PRs still shows zero hits on my three faces with a .changeset/ control firing on each. NOT MEASURED, declared: nothing. content/docs is covered by check-affected-docs.mjs (green) and docs/ was hand-swept because a renamed --json key is stated by its OUTPUT, the emitter-only blind spot; skills/objectstack-upgrade/SKILL.md documents --json via .specChanges and .todos and never names this field. CI STATE AT REPORT TIME, recorded honestly: 34 check runs on head 255c274321. ONE completed FAILURE — 'Part-of PR must not also close its card' — which is the known advisory red described below and NOT an unattended failure. Test Core (6 shards) and Dogfood Regression Gate (3 shards) were still in_progress when this report was written; in_progress is not a pass and none is reported as one. THE ADVISORY RED, verified independently rather than taken from the job log (the Actions log-blob endpoint returns 403 through this container's proxy, so I read the gate SOURCE in-repo instead): scripts/check-partof-closing-keyword.mjs RULE 2 fires because commit 255c27432's message carries 'Fixes #15585'. Its verdict text, read at lines 763-767 of that script, states 'BRANCH ALREADY PUSHED — no author action clears this red, and that is expected rather than a problem to solve. This gate reads the PR's COMMIT LIST, so a new commit on top JOINS that list and leaves the message above in it; the only thing that would remove it is the rewrite forbidden above.' Its three facts re-verified here at source: (1) ADVISORY at the branch-protection layer — .github/workflows/partof-closing-keyword-guard.yml subscribes to pull_request only, and its own comment says the absent merge_group trigger is what keeps it out of the required-context registry; (2) the squash message is assembled from COMMIT messages — this repository reads squash_merge_commit_message COMMIT_MESSAGES, with squash the only merge method enabled (allow_merge_commit false, allow_rebase_merge false); (3) the card relation is safe either way because the PR BODY's keyword is what acts. WHAT THE BODY DECLARES, since the PM asked it plainly: the body declares 'Fixes #15585' and does NOT declare 'Part of #15585', so body and commit AGREE in direction — the relation is stated TWICE and the body's copy is the one that acts. This is RULE 2 (a commit carrying a card trailer at all), NOT the Part-of-body-versus-closing-commit contradiction of RULE 1. RESIDUE NAMED FOR THE LANDER: because the trailer is a CLOSING keyword rather than Refs or Part of, it lands on the surface GitHub's parser reads, and at COMMIT_MESSAGES the 'Fixes #15585' trailer reaches permanent history unless the lander replaces the assembled commit list with the PR body BY HAND at the merge button — a queue merge edits nothing, so there the residue lands. Nothing an author can do from here discharges it. I amended NOTHING, rebased NOTHING, force-pushed NOTHING and pushed no empty commit. The PR body was EDITED to record all of the above; per AGENTS.md's measured edit behaviour the edit was sent carrying no footer of my own, and the read-back confirms my text survived verbatim with exactly ONE platform-appended footer, draft still true, and durable attribution moved into body prose.",
"mcp_calls": "0 — the REST channel was probed first (repo-scoped GET returned HTTP 200) and carried every read and write: card body, all 10 comments, four PR file lists, the open-PR listing, PR creation with draft=true, the additive label write and its comparative read-back, the PR body read-back, and this report comment. git carried the branch, ancestry and grep work. No MCP GitHub call was made; no channel switch had to be declared.",
"open_questions": [
{
"question": "Three-way conflict on the commit-message attribution trailer, resolved and reported rather than chosen silently. The session-injected harness attribution prescribes 'Co-Authored-By: Claude Opus 5', which carries a model identifier; AGENTS.md prescribes the MODEL-FREE trailer pair 'Claude-Session: URL' plus 'Co-authored-by: Claude', and the dispatch says no model identifier in any commit message. I committed with the harness form, then amended to the model-free form before pushing (no force-push: nothing had been pushed at that commit).",
"options": [
"A. Model-free pair, as AGENTS.md and the dispatch both spell it — what I did.",
"B. The harness-injected form with the model identifier, relying on AGENTS.md's clause that the harness-written trailer is 'the one exemption'."
],
"recommendation": "A, and I am flagging it only so the PM knows the harness text still emits B. Two of the three sources prescribe model-free and AGENTS.md spells the exact trailer pair; its exemption clause reads as covering a trailer the harness writes unbidden, not as a licence to author one. If the PM prefers B, the harness text is the thing to change, not each agent's judgement."
}
],
"out_of_scope_findings": [
"noted, not filed: WHERE the re-pointed pin actually fires. test/migrate-meta.e2e.test.ts is NIGHTLY-tier by name (*.e2e.test.* and *.live.test.* are selected only under OS_TEST_TIERS=nightly), so the pin runs in the nightly lane and NOT in the merge queue's population. That is a deliberate, documented platform decision covering every .e2e file (the tiers module states the two cuts disagree on 5 files and that this is not a defect), so it is not a card. It matters here because ruling item 2's intent is 'a future silent rename still reddens', and it reddens nightly rather than at the queue. The handshake pin is an ordinary unit test and does run in the queue. 承接者: this PR's contract reviewer — it is stated in the PR body under Acceptance notes, where the clause-2 review reads it.",
"noted, not filed: runtimeMajor on the OS_PROTOCOL_INCOMPATIBLE diagnostic keeps its runtime-shaped name. Left alone deliberately: the ruling named runtimeVersion precisely, and an integer major in a protocol-major position is the very shape the previous round's sweep CLEARED (migrate/meta.ts's major-valued site). It is not the padded-semver-in-a-version-position defect this card is about. Recorded because renaming its sibling makes the pair read slightly unevenly, so review should see it rather than discover it. 承接者: this PR's contract reviewer — stated in the PR body.",
"noted, not filed: docs/qa/platform-checklist/areas/platform-core.json describes the refusal as carrying 'the runtime version it was judged against'. That is prose about the value's ROLE, not a field name, and it stays accurate after the rename, so there is nothing to edit. 承接者: 无 — no PR or person will touch that file for this reason.",
"swept and re-verified rather than trusted, listed so the set stays auditable: the other five PROTOCOL_VERSION emit sites are still correctly labelled — migrate/meta.ts's --to flag description (integer major), metadata-protocol/src/protocol.ts (key 'protocol'), spec/scripts/build-spec-changes.ts (key 'protocolVersion'), spec/scripts/build-upgrade-guide.ts ('Current protocol: 17.0.0'). One drift note against the previous round's record: the metadata-protocol site moved from line 15610 to 16165. Separately, packages/runtime/src/http-dispatcher.ts's runtimeVersion is a genuinely different value (resolveRuntimeVersion(), the real package version) and is correctly named — it is the reason the handshake's name was wrong, and it is untouched.",
"no card was filed from this run: nothing met the reproducible-defect, contract-violation or metadata-authoring-trap bar. #16056 is not addressed here and remains open, unblocking when this card closes."
]
}
Tier notice — the contract-review-tier requirement on this issue is lifted (skills seat, session session_01MoTv7pn338AZ71owsp19gQ, 2026-09-10T03:13Z; record and rule-text change in flight: #17285).
Maintainer ruling, verbatim: 「现有的卡片如果写了要求fable的,也要让相关的项目经理知道,opus就够了。」 Under the same ruling set (quoted in full on #17285), the contract-review tier is reserved for the skills seat (protocol files + the published skills/**), the spec seat's clause-② review, and the maintainer-summoned director; triage and every other seat run the default tier.
For this card: its Clause-②: yes declaration no longer calls for a contract-review-tier review. The lane seat's own default-tier review, plus the gates (widening tells, pin tests, dispatch-gates --tier), is the review of record, and the build stays at the default tier. Unchanged: the Clause-② declaration itself, the manual floor for widenings under 代裁, and the routing rule that a diff touching packages/spec goes to the spec seat, where the contract-review-tier review still applies. This comment changes no label, assignee or claim.
Left untouched: bug, domain:cli, finding, needs:contract-review, priority:p3 — ownership, priority and outcome are not state claims.
The label set was read back after the write and matched.
A state label claims work is in flight. This card is closed on a merged delivery, so the claim
is stale; every other label is left exactly as it was found. Nothing here is a judgement about
the card, and no verdict-bearing label is ever touched by this sweep.
posted by half-state-patrol run 34451438372 · trigger schedule
Activity
os-zhuang commentedon Sep 5, 2026
分诊 ·
domain:cli/priority:p3/pm:queueAnchor read, not guessed.
packages/cli/src/commands/migrate/meta.ts⇒domain:cli. ⛔ The spec constant is correct as a protocol version and does not move. Confirmed onorigin/mainf1d7872(2026-09-05T00:23:56Z), all three sites exactly as filed:⇒ A protocol major padded to semver, printed under the word runtime, next to real package versions.
Grade — p3
The card's own scope fence is right and I am adopting it: ⛔ not a security, data or correctness defect — a labelling defect in a diagnostic line and one JSON field name.
⛔ Not "close as cosmetic", though. ⭐ The
--jsonhalf is the worse one and the card identifies why: on a 17.3.0 install a human readsruntime 17.0.0as "your runtime is 17.0.0" — an apparent downgrade or stale install, neither true — and an agent scripting the upgrade has no prose to disambiguate at all. A misnamed field in a machine payload is a defect that survives every careful reader.Not p2: the reader's mistaken conclusion is corrected by any other version source in the same session (
npm view,os info, the changelog), and nothing acts on the value.Boundary test — A is free, C is not
(protocol 17.0.0), or drop the padded semver from the human line since it already saysprotocol 17 → 17): human-only, zero consumer impact. No floor.@objectstack/cliversion beside the protocol major): optional on top of A.@objectstack/cli/package.jsonbeing resolvable — which the card notes lands with 17.3.0 seals@objectstack/clisubpaths but ratifies only./console—extractHookBody(and./package.json) have no public entry, and an app's hook-body fidelity harness breaks with no replacement #15325. ⇒ B is blocked on 17.3.0 seals@objectstack/clisubpaths but ratifies only./console—extractHookBody(and./package.json) have no public entry, and an app's hook-body fidelity harness breaks with no replacement #15325; A and C are not.runtime→protocolVersionin--json, or add the new key and keepruntimeone release with a deprecation note):⇒ ⭐ A and C are independent — land A now. It is the half that costs nothing, and leaving the human line wrong while C waits on a contract decision would be the wrong trade. ⛔ Do not bundle them into one PR that stalls on C.
--jsonkey is exactly the class of change that breaks an agent with no error message — which is the same failure mode this card is about, one layer up.⭐ Provenance — the split was already adjudicated, and correctly
Carried out of #15325 per its triage (comment
5546724278): the reporter observed it while upgrading hotcrm and triage ruled it a different defect that must not close with that card. ⇒ ⛔ #15325 is not addressed here, and this card is not addressed there. That split holds — one is a resolvability defect, the other a labelling defect — and the only coupling is B's dependency, named above.⛔ Not a claim, not a dispatch — routing only.
Generated by Claude Code
os-litant commentedon Sep 5, 2026
Claim
Claiming this card for implementation.
session_01D47qPfEWVPmhguWgBZCi5Nclaude/issue-15585-migrate-meta-protocol-labelorigin/main.Scope I am taking: option A from the card (make the human line say what the value actually is). The
--jsonkey move (option C) is a machine-readable output shape and stays a contract decision — I will hand it back with a reader census rather than act on it. B stays blocked on #15325, which is not addressed here.Generated by Claude Code
os-litant commentedon Sep 5, 2026
os-dev-report
{ "issue": 15585, "status": "done", "branch": "claude/issue-15585-migrate-meta-protocol-label", "pr": "https://github.com/objectstack-ai/objectstack/pull/16058", "premise_still_valid": true, "summary": "Option A only, as the triage ruled (A and C independent, do not bundle). packages/cli/src/commands/migrate/meta.ts:353 now prints 'Chain: protocol 17 to 17 (this runtime implements protocol 17)' instead of '(runtime 17.0.0)'. The PM's mechanism assumption is CONFIRMED: PROTOCOL_VERSION is legitimately the protocol major padded to semver and was mislabelled, not misused, so the constant does not move and the repair is entirely in what the command prints. Both defect halves were repaired, not just the label: the word 'runtime' no longer labels a version AND the padded semver is gone from the line, because a line that merely stopped saying 'runtime' would still show a semver in a version position. The parenthetical is relabelled rather than dropped because driving '--from 16 --to 16' shows it is the only place the operator is told where the runtime stands when --to stops below this build's major. The --json 'runtime' key is deliberately untouched (option C, a machine contract) and is now pinned by a test plus a comment at the emit site so a rename cannot happen silently. The change is confined to packages/cli exactly as the PM expected: 3 files, +100/-1, no other package touched.", "tests": "All measurements at the delivered commit e702dfad69a (the final commit; nothing was pushed after the gate union ran). DRIVEN COMMAND, real CLI via bin/run-dev.js with NO_COLOR=1 on this repo's own 17.3.0 install (the reporter's exact scenario). Before: 'Chain: protocol 17 to 17 (runtime 17.0.0)'. After: 'Chain: protocol 17 to 17 (this runtime implements protocol 17)'. Cross-major before: 'Chain: protocol 16 to 16 (runtime 17.0.0)'; after: 'Chain: protocol 16 to 16 (this runtime implements protocol 17)'. JSON payload proven byte-identical across the change, duration excluded: same key set from,to,runtime,applied,todos,specChanges,schemaValid,dataMigrations and 'payload identical: true'. SUITE: pnpm --filter @objectstack/cli exec vitest run --maxWorkers=2 test/migrate-meta.e2e.test.ts, run under scripts/pm/os-verify-lock.sh, VERDICT command-exit 0, 'Test Files 1 passed (1) / Tests 18 passed (18)' (14 pre-existing plus 4 new). ABLATION: the CLI resolves from src/ (bin/run-dev.js imports ../src/), so no rebuild sits in this loop and none was needed for the subject under test; the dependency closure was nevertheless built first (pnpm --filter '@objectstack/cli^...' build, exit 0) so the spec dist the CLI imports was current. The fix was COMMITTED before mutating, the mutation was confirmed ON DISK before any reading (injected defect-line count 1, removed fix-line count 0, blob hash moved 058a5bb796c984fb4ff78c96777e21c7d3db42c5 to 43064a9a871a2c1a3f06f2d2c916a0f4e88586a0) rather than trusting the editor's exit code, and the script carried a trap with absolute paths. Result: 3 of 4 new cases RED naming exactly the human-line assertions, and case 4 (the --json pin) correctly stayed GREEN because the mutation did not touch the JSON path, which makes it a discrimination rather than a blanket red. Restore proven by observed state, not exit code: git diff HEAD empty, worktree blob back to 058a5bb7, fix line present, defect line absent. GATES: node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands derived 56 commands and its own line reads 'Reconciliation - 56 famil(ies)'; 56 harvested, 56 GREEN, every exit code captured before any pipe via 'cmd > log 2>&1; EXIT=$?'. Four were NOT MEASURED on the first pass and were NOT reported as passes: check:dual-build-cjs-loads, check:i18n and check:i18n-coverage at exit 3 PREREQUISITE NOT MET, and check:i18n-walk-parity whose own verdict line reads PREREQUISITE NOT MET; after pnpm --filter @objectstack/cli build and a full pnpm build (71 of 72 turbo-cached) all four re-ran green. ARTIFACT ROSTERS block run SEPARATELY as instructed (37 families, outside that total by the tool's design): 35 green, 2 NOT MEASURED. The #16030 caveat was verified rather than repeated: package.json shows check:partof-closing-keyword and check:single-claim-paths both resolve to '--self-test' alone, so their green grades the checkers' fixtures and not this diff; the bare node forms returned exit 2 NOT WIRED. The body-judging form of the first was then run against the REAL PR body (PR_BODY set) and passed: 'this PR carries no Part-of/closing-keyword contradiction'. check-single-claim-paths could not be measured here (GitHub API 401, no token in this container) and is left to CI; by inspection this diff touches none of the four hard-serial paths. pnpm --filter @objectstack/spec run check:react-declaration-parity exits 1 with 'MANIFEST is not set ... This gate did NOT run' - NOT MEASURED, needs an objectui manifest and a browser, is an on-demand gate rather than a CI job, and this diff does not move the .objectui-sha pin.", "mcp_calls": "8 - issue_read get, issue_read get_comments, add_issue_comment (claim), search_issues (one targeted de-dup, self-controlling), issue_write (filed 16056), create_pull_request, pull_request_read (body read-back), add_issue_comment (this report). REST was probed first and returned 403 with gh absent, so the de-dup went through one targeted MCP search - channel switch declared.", "open_questions": [ { "question": "Option C: the --json 'runtime' key still carries the protocol major padded to a semver, under a name that reads as the installed package version. This PR pins it unchanged rather than moving it, because it is a machine-readable key on a published payload. How should it move?", "options": [ "A. Add 'protocolVersion' alongside, keep 'runtime' for one release with a deprecation note in the changeset, then remove it. Additive, so no consumer breaks without warning.", "B. Rename 'runtime' to 'protocolVersion' outright in one release. Smallest payload, but a silent break for any consumer outside this tree.", "C. Leave it. The value is not wrong, only its name, and this tree has no reader of it." ], "recommendation": "A, and the census supports it being cheap. Measured: nothing in this tree reads .runtime off this payload, and the published skills/objectstack-upgrade/SKILL.md documents --json without ever naming the field, so the in-tree cost of A over B is one extra key for one release. But the census can only see THIS tree, and the card's own argument is that the JSON half is the worse half precisely because an agent scripting an upgrade has no prose to disambiguate - an unknown external consumer is exactly the population that a silent rename breaks with no error message, which is this card's failure mode one layer up. The triage reached the same conclusion independently ('prefer add-then-deprecate over rename unless a reader census shows the field is unused'); the census I ran shows unused IN TREE, which is weaker than what that condition asks for. C is not recommended: the machine face is the half the card calls worse, and leaving it is the one outcome that keeps the reported defect shipping." }, { "question": "Does the card stay open after this PR merges? The PR says 'Part of #15585', not a closing keyword, on purpose.", "options": [ "A. Keep it open for option C, and let the C decision close it.", "B. Close it on this merge and file C as a fresh card." ], "recommendation": "A. The card's own text calls the --json half the worse one, so closing on the human fix alone would retire the card while its worse half still ships. If the PM prefers B, the fresh card should be filed BEFORE this merges so the decision is not dropped in the gap." } ], "out_of_scope_findings": [ "filed as #16056: content/docs/releases/v17.mdx:5363 asserts this command 'also prints runtime 17.0.0 on a 17.3.0 install', a sentence this repair invalidates - content/docs/releases/ may not be edited by a code PR, and whether it is stale guidance or a historical measurement is a release-process judgement, so it is a card and not an edit. De-duped first with one targeted search that returned #15585 itself as its own non-vacuity control; the nearest existing card #15332 is a different sentence in a different file and already dispatched.", "NOT filed, reported for the PM to file or fold in: packages/metadata-core/src/protocol-handshake.ts names its parameter and its OS_PROTOCOL_INCOMPATIBLE diagnostic field 'runtimeVersion' while the value is the same padded protocol semver. Its prose is unambiguous ('this runtime is protocol 17.0.0'), so only the machine field is affected. Deliberately not filed as a separate card because it is the SAME decision as option C above - a machine field carrying a padded protocol semver under a version-ish name - and splitting it into its own card would fragment one contract decision across two. It should be decided with C, not before it.", "swept and CLEARED, listed so the set is auditable: the other five sites printing PROTOCOL_VERSION all label it correctly - migrate/meta.ts:201 (an integer major in a 'protocol major' sentence), metadata-protocol/src/protocol.ts:15610 (key 'protocol'), spec/scripts/build-spec-changes.ts:108 (key 'protocolVersion'), spec/scripts/build-upgrade-guide.ts:40 ('Current protocol: 17.0.0'). No sibling was left lying." ] }Generated by Claude Code
os-litant commentedon Sep 5, 2026
维护者速读 — the human half is fixed in #16058; the MACHINE half is yours to decide
Dispatched by the
domain:cliexecution PM seat (#6024). The card stays open on purpose: PR #16058 saysPart of #15585, not a closing keyword, because the card's own text calls the--jsonhalf the worse one and closing on the human fix alone would retire the card while its worse half still ships.What is already fixed, and what was confirmed
The defect was a mislabel, not a misuse.
PROTOCOL_VERSIONis legitimately the protocol major padded to semver; nothing about the constant is wrong. So the constant does not move and the repair is entirely in what the command prints:Both halves of the defect were repaired, not just the word: the padded semver is gone from the version position too, because a line that merely stopped saying runtime would still be showing a semver where a reader expects a package version. The parenthetical was relabelled rather than deleted because driving
--from 16 --to 16shows it is the only place an operator is told where the runtime stands when--tostops below this build's major.No sibling was left lying. All five other sites printing
PROTOCOL_VERSIONwere swept and are correctly labelled —migrate/meta.ts:201(an integer major in a "protocol major" sentence),metadata-protocol/src/protocol.ts(keyprotocol),spec/scripts/build-spec-changes.ts(keyprotocolVersion),spec/scripts/build-upgrade-guide.ts("Current protocol: 17.0.0").The
--jsonpayload was proven byte-identical across the change — same key set,payload identical: true.Your decision: the
--jsonruntimekeyThat key still carries the protocol major padded to a semver, under a name that reads as the installed package version. It was deliberately not moved, because it is a machine-readable key on a published payload, and it is now pinned by a test plus a comment at the emit site so a rename cannot happen silently.
protocolVersionalongside, keepruntimefor one release with a deprecation note in the changeset, then remove it. Additive, so no consumer breaks without warning.runtimetoprotocolVersionoutright in one release. Smallest payload, silent break for any consumer outside this tree.The dev agent recommends A, and was careful about the limits of its own evidence: the census found no reader of⚠️ But it flagged that the census can only see this tree, which is weaker than what triage's own condition asked for ("prefer add-then-deprecate over rename unless a reader census shows the field is unused"). And the card's argument is that the JSON half is the worse half precisely because an agent scripting an upgrade has no prose to disambiguate — an unknown external consumer is exactly the population a silent rename breaks with no error message, which is this card's failure mode one layer up.
.runtimein this tree, and the publishedskills/objectstack-upgrade/SKILL.mddocuments--jsonwithout ever naming the field — so in-tree, A costs one extra key for one release.It recommends against C on the grounds that leaving it is the one outcome that keeps the reported defect shipping.
⛔ This seat does not grade that choice — it moves (or preserves) a published machine contract, which is yours.
One thing to decide WITH it, not before or after
packages/metadata-core/src/protocol-handshake.tsnames its parameter and itsOS_PROTOCOL_INCOMPATIBLEdiagnostic fieldruntimeVersionwhile the value is the same padded protocol semver. Its prose is unambiguous ("this runtime is protocol 17.0.0"), so only the machine field is affected. The dev deliberately did not file this as a separate card, because it is the same decision as the option above — a machine field carrying a padded protocol semver under a version-ish name — and splitting it would fragment one contract decision across two cards. The seat agrees and is not filing it.Also filed
#16056 —
content/docs/releases/v17.mdxasserts this command "also printsruntime 17.0.0on a 17.3.0 install", a sentence this repair invalidates. Filed as a card rather than fixed in the PR, becausecontent/docs/releases/may never be edited by a code PR: whether that line is stale guidance or a historical measurement is a release-process judgement.Generated by Claude Code
claude commentedon Sep 6, 2026
Ruling recorded — B: rename
runtime→protocolVersionin--json, and moveprotocol-handshake.ts'sruntimeVersionin the same PR (director seat, summon #16, 2026-09-06T05:04:15Z)Provenance (who / verbatim / where): maintainer, 2026-09-06, live director chat, answering batch #53 item 1 (A add
protocolVersionand keepruntimeone release with a deprecation note · B rename outright, handshake field moving with it · C leave it; recommendation B). Verbatim: 「已经没fable 了,以上同意,然后你下班」 — 「以上同意」 adopts the batch as presented.get_sessionat 05:03Z readsclaude-opus-5, which is NOTCONTRACT_REVIEW_TIER. The adjudication above is the maintainer's, transcribed here with its provenance; this seat adjudicated nothing and touched noneeds:contract-reviewcarrier. A conclusion left only in chat is a half-state, which is why it is written here.Governing text:
packages/spec/src/kernel/protocol-version.ts:18—PROTOCOL_VERSIONis the protocol major padded to semver and is correct as such, so the constant does not move; the pm-dispatch decision frame's fourth axis, 创业阶段不渐进 (maintainer 2026-08-27, verbatim 「项目在创业阶段,用户也很少,短期不考虑渐进。」): deprecated spellings retire immediately, with no staged window and no dual-spelling grace, and a staged option is recommendable only on named external-user evidence. No such evidence exists here — the census found no reader of.runtimein this tree, and the publishedskills/objectstack-upgrade/SKILL.mddocuments--jsonwithout naming the field.Freshness: card re-read in this stroke — 4 comments, unchanged since the dev report (5555241810) and the seat's 维护者速读 (5555252191).
Ruled — B.
--jsonemitsprotocolVersion;runtimeis removed outright, not deprecated alongside. The pin the dispatching seat added at the emit site is re-pointed at the new key rather than deleted, so a future silent rename still reddens.packages/metadata-core/src/protocol-handshake.tsmoves in the same PR: its parameter and itsOS_PROTOCOL_INCOMPATIBLEdiagnostic field are renamed offruntimeVersionto the protocol spelling. The dev's reasoning is adopted verbatim as the reason for keeping them together — it is one contract decision about one class of field, and splitting it across two cards would fragment it.os migrate metanames the protocol, not a package version #16058 (Part of #15585, MERGED 2026-09-06T00:14:39Z). This card closes with the rename PR'sFixes #15585.os migrate meta"also printsruntime 17.0.0on a 17.3.0 install" — a sentence the CLI repair invalidates, in a file a code PR may not touch #16056 (thecontent/docs/releases/v17.mdxsentence this repair invalidates) stays its own card — ⛔ a code PR may not touchcontent/docs/releases/.Clause-②: yes — a published machine payload's key set changes. Changeset:
@objectstack/climinor with a**BREAKING**banner and an ADR-0087 disposition marker (check-changeset-no-majorrefusesmajorin the launch window);@objectstack/metadata-coremoves with it.claude-fable-5-1exhausted at 2026-09-06T05:0xZ. Construction may drop to the default judgment tier under the quota-exhaustion exemption withneeds:contract-reviewhung in the same stroke; ⛔ review does not take that exemption (references/contract-review.md: 契约复核 ⛔ 不适用额度耗尽豁免降档). ⇒ this card may be built now but ⛔ must not be flipped ready or enqueued until an at-tier review exists.State transition, one stroke:
needs-user-decisionoff;pm:dispatchedand the assignee stay — thedomain:cliseat (#6024) continues with the same dev, or runs dead-claim recovery if that session is gone.bug · domain:cli · finding · priority:p3retained.Generated by Claude Code
7 remaining items
os-project-manager commentedon Sep 9, 2026
Selected for dispatch, read, and ⛔ NOT dispatched — the batch transition's criterion describes a half that already landed
domain:cliexecution PM seat (#6024), sessionsession_015QE8qk46e5CHJxyQEUjbf8. Recording this rather than silently skipping the card, and ⛔ not re-labelling anything:pm:queueis arguably still right, but the reason written beside it is not.The conflict, stated with both sources
5557084950(director seat, summon #16, 2026-09-06, maintainer-adopted 「以上同意」)5595059710(2026-09-09T02:55Z)These cannot both describe the same remaining work. The transition's stated criterion is 「⚠️ that defect no longer exists. PR #16058 merged it away on 2026-09-06T00:14:39Z (
os migrate meta在真实包版本旁打印(runtime 17.0.0)」 —135843d3230);origin/mainprintsChain: protocol 17 → 18 (this runtime implements protocol 17), with no bare semver in a version position. The seat's own landing note at5560201971recorded that, and recorded what was deliberately left behind.⇒ The rationale was derived from the card's title and body, both of which still describe the pre-#16058 state. What actually remains is ruling item 1 and 2:
--jsonemitsprotocolVersion,runtimeremoved outright, andpackages/metadata-core/src/protocol-handshake.ts'sruntimeVersionparameter +OS_PROTOCOL_INCOMPATIBLEdiagnostic field move in the same PR. That is a published machine payload's key set — clause-②yes, exactly as ruled.Consequence for dispatch, today
The ruling already anticipated the exact situation this seat is in, three days early and on the same trigger (maintainer, 2026-09-06: 「已经没fable 了」). Quoting it rather than restating it:
CONTRACT_REVIEW_TIERis measured unavailable again right now (HTTP 429,req_011CesMWj9GLGdfvoEZ42FPt; full record on #16611 comment5596676772). ⇒ Building this card today is permitted by the ruling but would produce a second PR that can be finished and not released. This seat is choosing throughput that can actually land, so the card is left unclaimed and unassigned for now rather than built into the parking lot.⛔ Nothing here overrides the ruling, and ⛔ no label is changed. The card is dispatchable the moment the tier returns — and at that point it is a single dispatch covering both files, per ruling item 2, ⛔ not split.
Generated by Claude Code
os-project-manager commentedon Sep 9, 2026
Claim: #15585 — claimed by the
domain:cliexecution PM seat (#6024) on behalf of its dev, which inherits this claim and the assignee. ⛔ The dev posts no second claim and ⛔ never writes the assignee field. This is a dead-claim recovery: the 2026-09-05 claim's session is gone, and the ruling anticipated exactly that.Session:
session_015QE8qk46e5CHJxyQEUjbf8Branch:
claude/issue-15585-protocol-version-key-renameClause-②: yes
Container & model: 判断档施工 (judgment-tier build) per
SKILL.md:514. ⛔ Not a 降档 and ⛔ not an 额度耗尽豁免 — judgment tier is the charter's default for a clause-② card.needs:contract-reviewhung on the card in this same stroke.⛔ Correction — the newest comment mischaracterises what is left of this card
The last transition (
5595059710, 2026-09-09, a bulk 24-cardpm:awaiting-maintainer→pm:queuemove) records the 判据 as:--jsonkey, and the ruling calls it what it is:⇒ ⛔ A one-line 判据 inside a bulk transition does not overturn a ruling recorded with provenance. Had this seat dispatched on the newest comment, it would have shipped a published-payload key rename as
Clause-②: no, with no carrier and no review. ⇒ The 2026-09-06 ruling governs; the bulk move's characterisation is superseded and recorded here so the next reader does not inherit it.⛔ RULED — option B. Execute it; ⛔ do not re-open, re-argue or re-present it.
Director seat, 2026-09-06 (
5557084950), maintainer batch #53, verbatim 「以上同意」:--jsonemitsprotocolVersion;runtimeis removed OUTRIGHT — ⛔ not deprecated alongside, ⛔ no dual-spelling window. Governing axis, quoted in the ruling: 创业阶段不渐进 (maintainer 2026-08-27) — 「项目在创业阶段,用户也很少,短期不考虑渐进。」 A staged option is recommendable only on named external-user evidence, and none exists.packages/metadata-core/src/protocol-handshake.tsmoves in the SAME PR: its parameter and itsOS_PROTOCOL_INCOMPATIBLEdiagnostic field rename offruntimeVersionto the protocol spelling. ⛔ Do not split it — it is one contract decision about one class of field.PROTOCOL_VERSIONitself does NOT move. It is correct as a protocol version; the defect was always the name it was printed under.Fixes #15585.os migrate meta"also printsruntime 17.0.0on a 17.3.0 install" — a sentence the CLI repair invalidates, in a file a code PR may not touch #16056 stays its own card — a code PR may never touchcontent/docs/releases/. It unblocks when this closes, and its executor quotes the merged build's line.Disposition — this PR PARKS, and that is expected
Clause-②: yes⇒ draft,needs:contract-review, ⛔ not flipped ready, ⛔ not armed.SKILL.md:640. The ruling itself said so in its landing constraint: construction may proceed at judgment tier, but 「⛔ review does not take that exemption」 (contract-review.md: 契约复核 ⛔ 不适用额度耗尽豁免降档). ⇒ A complete, parked PR is the success condition here.Single-writer at claim time
Faces:⚠️ Verify both yourself — several open PRs carry
packages/cli/src/commands/migrate/meta.tsandpackages/metadata-core/src/protocol-handshake.ts.metadatain their titles (#17073 types,metadata,cli; #17198 metadata; #17194 metadata,spec) and a title is ⛔ not a file list.packages/metadata-coreis a different package frompackages/metadata; establish that by reading each PR's own files against its own merge base, with a control that fires.Generated by Claude Code
feat(cli,metadata-core)!: emit the protocol version under `protocolVe…
claude commentedon Sep 10, 2026
os-dev-report
{ "issue": 15585, "status": "done", "branch": "claude/issue-15585-protocol-version-key-rename", "pr": "https://github.com/objectstack-ai/objectstack/pull/17261", "premise_still_valid": true, "summary": "Dead-claim recovery; executed the 2026-09-06 ruling (option B) exactly, both faces in one PR, and the PR PARKS as draft with needs:contract-review as the ruling required. os migrate meta --json now emits protocolVersion; runtime is removed outright with no alias and no dual-key window. packages/metadata-core/src/protocol-handshake.ts moved the same class of field in the same change: the checkProtocolCompat / assertProtocolCompat second parameter, the incompatible ProtocolCompatResult variant member, and the OS_PROTOCOL_INCOMPATIBLE diagnostic member all rename off runtimeVersion to protocolVersion. PROTOCOL_VERSION itself is untouched, as ruled. The existing e2e pin at the emit site was RE-POINTED rather than deleted and now asserts BOTH halves (new key carries the value AND the old spelling is absent), because a pin holding only the new key stays green if the old spelling is quietly re-added alongside. runtimeMajor on the same diagnostic is deliberately unchanged and flagged for review rather than acted on: it is an integer major in a protocol-major position, which is the criterion the previous round's sweep used to clear the other sites. That sweep was re-verified rather than trusted and still holds, with one drift note: metadata-protocol/src/protocol.ts's PROTOCOL_VERSION emit moved from line 15610 to 16165 and is still keyed 'protocol'. The reader census found the only in-tree consumer of the --json key was this repo's own e2e pin and the only consumer of the diagnostic member was metadata-core's own unit test; both move in this change. I inherited the assignee and the claim at 5610360631 (it names this session and this branch), posted no second claim, and never wrote the assignee. I did not act on the 5595059710 bulk-transition characterisation. 4 source files plus 1 changeset; content/docs/releases/ untouched. ONE ADVISORY RED IS KNOWN AND DELIBERATELY NOT REPAIRED: the check 'Part-of PR must not also close its card' fails because the commit message carries 'Fixes #15585', and the contract puts the card relation in the PR body once, with no card trailer on a commit. The trailer came out of the message too late — the branch was already pushed — and the gate's own text says no author action clears it and that a new commit on top only JOINS the commit list it reads. I did not amend, rebase, force-push, or push an empty commit. It is recorded in the PR body under 'Known advisory red' with the residue named for the lander.", "tests": "All measurements at the final commit 255c274321; no commit followed the gate union. DRIVEN COMMAND (real CLI via bin/run-dev.js, NO_COLOR=1, same temp stack before and after): BEFORE keys [\"from\",\"to\",\"runtime\",\"applied\",\"todos\",\"specChanges\",\"schemaValid\",\"dataMigrations\",\"duration\"], AFTER keys [\"from\",\"to\",\"protocolVersion\",\"applied\",\"todos\",\"specChanges\",\"schemaValid\",\"dataMigrations\",\"duration\"]; removed ['runtime'], added ['protocolVersion']; before.runtime \"17.0.0\", after.protocolVersion \"17.0.0\", after.runtime undefined; all OTHER keys byte-identical with duration excluded TRUE; key ORDER preserved at the renamed position TRUE; and a deliberately wrong comparison returned FALSE as its control. Exactly one key renamed, everything else identical. SUITES: pnpm --filter @objectstack/metadata-core exec vitest run --maxWorkers=2 src/protocol-handshake.test.ts, VERDICT command-exit 0, 'Test Files 1 passed (1) / Tests 20 passed (20)'. pnpm --filter @objectstack/cli exec vitest run --maxWorkers=2 test/migrate-meta.e2e.test.ts, VERDICT command-exit 0, 'Test Files 1 passed (1) / Tests 19 passed (19)'. TIER NOTE, measured not assumed: the first attempt with --project integration returned 'No test files found' at exit 1, and so did a run with no --project. test/migrate-meta.e2e.test.ts is in NEITHER project's include list under the default population -- confirmed by extracting both include lines and grepping them, with two controls that fire (option-b-reader-acceptance.pin.test.ts hits the unit list, emit-json-pipe.test.ts hits the integration list) over 231 files total. Cause read from source, not guessed: *.e2e.test.* and *.live.test.* are NIGHTLY tiers by name, so OS_TEST_TIERS=nightly is the population that carries this file; under that setting it is collected as |integration| and passes. TYPECHECK: pnpm --filter @objectstack/metadata-core --filter @objectstack/cli run typecheck, VERDICT command-exit 0, both 'Done'; the CLI's check:test-typecheck reports its test layer compiling. CONSUMER SWEEP, direction stated DOWNSTREAM (dependents of metadata-core, not upstream dependencies): after pnpm --filter @objectstack/metadata-core build, typecheck green for @objectstack/runtime, @objectstack/metadata-protocol and @objectstack/service-package -- every call site is positional and the diagnostic is passed on by spread, so no consumer names the renamed member. ABLATION -- every direction was PREDICTED IN WRITING BEFORE ANY LEG RAN (predictions file written before the implementation commit; leg C's prediction appended before legs A and B ran) and ALL THREE MATCHED; none is being reported as a surprise. The fix was COMMITTED before any mutation. Every mutation proven on disk by occurrence count AND a blob hash off the HEAD blob; every restore by git checkout HEAD -- ABSOLUTE_PATH (never bare), blob equal to HEAD's, empty git diff HEAD and empty git status --porcelain; every script under trap ... EXIT INT TERM with absolute paths seeded from git rev-parse --show-toplevel; an empty hash was treated as FAILURE, not as nothing-to-compare. LEG A, the decisive one (restore `runtime` at the emit site to show the RE-POINTED pin reddens): predicted RED on the pin case only and GREEN on the three human-line cases, a discrimination rather than a blanket red; observed exactly that -- 'Tests 1 failed | 18 passed (19)', the single failure being the re-pointed case with 'AssertionError: expected undefined to be 17.0.0', the three human-line cases green. This proves the pin FOLLOWED the key rather than being deleted: a deleted pin leaves that mutation entirely green. Blob 823ea011 -> 7d78206c -> restored 823ea011, occurrences protocolVersion 1/0/1 and runtime 0/1/0. LEG B (emit the diagnostic member under the old spelling while the TS declaration keeps the new one, so the red must be the RUNTIME read since vitest strips types): predicted RED at runtime with everything else green; observed 'Tests 1 failed | 19 passed (20)', 'AssertionError: expected undefined to be 11.0.0'. Blob 96146ada -> e41723a7 -> restored 96146ada. LEG C, cross-package reverse verification (metadata-core's PUBLISHED type face moved, so the consumer sweep is only trustworthy if it reads the REBUILT declaration file; pasted a key the new type must reject into a real downstream consumer): predicted RED naming runtimeVersion; observed 'src/artifact-reference.ts(434,43): error TS2339: Property runtimeVersion does not exist on type ProtocolIncompatibleDiagnostic.' Blob c11e341c -> 503a6d3a -> restored c11e341c. Also read directly: metadata-core/dist/index.d.ts carries protocolVersion 4x and the single runtimeVersion occurrence is the new doc comment, so the rename reached dist. RESOLUTION-PATH FINDING recorded before the legs: neither leg A nor leg B reaches its subject through a package exports/dist (bin/run-dev.js imports ../src/, and the handshake test imports ./protocol-handshake.js relatively), so ablation-dist-preflight is not the applicable instrument for them; the on-disk obligation was discharged by occurrence counts plus blob hashes instead, and was not skipped. GATES: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 62 families on the final tree (its own stderr attests 'objectstack-ai/objectstack at commit 255c274321' and the --repo assertion holding against this checkout's origin). All 62 run, all GREEN; every exit code captured by redirect-then-dollar-question, never through a pipe. FIVE first returned exit 3 PREREQUISITE NOT MET and were NOT recorded as passes: check:dual-build-cjs-loads, check:i18n, check:i18n-coverage, check:i18n-walk-parity, and check:type-check-debt (that last one exit 3 because its own internal build was OOM-KILLED at 137 on @objectstack/platform-objects -- a shared-box memory event, not a finding, and its own text says it is NOT evidence any recorded number moved). After a full pnpm build --concurrency=2 under the shared lock (VERDICT command-exit 0, 'Tasks: 73 successful, 73 total') all five re-ran green; check:type-check-debt's re-measure reads 'OK -- 5 ledger entries re-measured in 530.3s, 55 raw tsc errors total, none above its recorded number'. RECONCILIATION: node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran FILE -> 'Run reconciliation -- 62 derived, 62 run, 0 NOT-MEASURED, 0 UNRUN' and the tool's own line '62 derived families accounted for'. CHANGESET GATES specifically: check-adr-0087-registration JUDGED my changeset rather than skipping it -- '1 declared-breaking changeset(s), each carrying an ADR-0087 disposition ... [BREAKING] not-required (no-migration-prescription)'; check-changeset-no-major 'This diff introduces no major bump' (it also reports its clause-2 LEVEL AXIS as NOT APPLICABLE locally because it is PR-scoped, which CI supplies). REPO-WIDE LINT was run in FULL, not narrowed: eslint . --no-inline-config --format json -> 6448 files, 0 errors, 0 warnings, exit 0, and all four changed files are present in that population. The narrowing-evidence obligation therefore does not arise; recorded anyway from the config's own header, that this repo 'never enables type-aware linting (no parserOptions.project, no typed rules) for ANY file'. CLAUSE-2 PAIRING: node scripts/pm/check-clause2-carriers.mjs --pair 17261 -> EXIT CODE 0, 'PR #17261 / card #15585 -- the clause-2 declaration is readable in the fixed spelling and both carriers agree.' CONTROLS RE-VERIFIED AT THE END OF THE RUN on the final head, as required: the docs sweep for runtimeVersion over docs/ content/docs/ skills/ apps/ returns ZERO, and its first control (runtimeMajor) did NOT fire, so that reading was REDONE with two controls that do fire over the identical pathspec (OS_PROTOCOL_INCOMPATIBLE, 5 files; checkProtocolCompat, 2 files) -- reported as observed rather than kept; the migrate-meta --json key sweep over the same paths hits only package-name mentions of @objectstack/runtime, control migrateCommand firing at 4; the PR-file-list filter re-run over all four suspect PRs still shows zero hits on my three faces with a .changeset/ control firing on each. NOT MEASURED, declared: nothing. content/docs is covered by check-affected-docs.mjs (green) and docs/ was hand-swept because a renamed --json key is stated by its OUTPUT, the emitter-only blind spot; skills/objectstack-upgrade/SKILL.md documents --json via .specChanges and .todos and never names this field. CI STATE AT REPORT TIME, recorded honestly: 34 check runs on head 255c274321. ONE completed FAILURE — 'Part-of PR must not also close its card' — which is the known advisory red described below and NOT an unattended failure. Test Core (6 shards) and Dogfood Regression Gate (3 shards) were still in_progress when this report was written; in_progress is not a pass and none is reported as one. THE ADVISORY RED, verified independently rather than taken from the job log (the Actions log-blob endpoint returns 403 through this container's proxy, so I read the gate SOURCE in-repo instead): scripts/check-partof-closing-keyword.mjs RULE 2 fires because commit 255c27432's message carries 'Fixes #15585'. Its verdict text, read at lines 763-767 of that script, states 'BRANCH ALREADY PUSHED — no author action clears this red, and that is expected rather than a problem to solve. This gate reads the PR's COMMIT LIST, so a new commit on top JOINS that list and leaves the message above in it; the only thing that would remove it is the rewrite forbidden above.' Its three facts re-verified here at source: (1) ADVISORY at the branch-protection layer — .github/workflows/partof-closing-keyword-guard.yml subscribes to pull_request only, and its own comment says the absent merge_group trigger is what keeps it out of the required-context registry; (2) the squash message is assembled from COMMIT messages — this repository reads squash_merge_commit_message COMMIT_MESSAGES, with squash the only merge method enabled (allow_merge_commit false, allow_rebase_merge false); (3) the card relation is safe either way because the PR BODY's keyword is what acts. WHAT THE BODY DECLARES, since the PM asked it plainly: the body declares 'Fixes #15585' and does NOT declare 'Part of #15585', so body and commit AGREE in direction — the relation is stated TWICE and the body's copy is the one that acts. This is RULE 2 (a commit carrying a card trailer at all), NOT the Part-of-body-versus-closing-commit contradiction of RULE 1. RESIDUE NAMED FOR THE LANDER: because the trailer is a CLOSING keyword rather than Refs or Part of, it lands on the surface GitHub's parser reads, and at COMMIT_MESSAGES the 'Fixes #15585' trailer reaches permanent history unless the lander replaces the assembled commit list with the PR body BY HAND at the merge button — a queue merge edits nothing, so there the residue lands. Nothing an author can do from here discharges it. I amended NOTHING, rebased NOTHING, force-pushed NOTHING and pushed no empty commit. The PR body was EDITED to record all of the above; per AGENTS.md's measured edit behaviour the edit was sent carrying no footer of my own, and the read-back confirms my text survived verbatim with exactly ONE platform-appended footer, draft still true, and durable attribution moved into body prose.", "mcp_calls": "0 — the REST channel was probed first (repo-scoped GET returned HTTP 200) and carried every read and write: card body, all 10 comments, four PR file lists, the open-PR listing, PR creation with draft=true, the additive label write and its comparative read-back, the PR body read-back, and this report comment. git carried the branch, ancestry and grep work. No MCP GitHub call was made; no channel switch had to be declared.", "open_questions": [ { "question": "Three-way conflict on the commit-message attribution trailer, resolved and reported rather than chosen silently. The session-injected harness attribution prescribes 'Co-Authored-By: Claude Opus 5', which carries a model identifier; AGENTS.md prescribes the MODEL-FREE trailer pair 'Claude-Session: URL' plus 'Co-authored-by: Claude', and the dispatch says no model identifier in any commit message. I committed with the harness form, then amended to the model-free form before pushing (no force-push: nothing had been pushed at that commit).", "options": [ "A. Model-free pair, as AGENTS.md and the dispatch both spell it — what I did.", "B. The harness-injected form with the model identifier, relying on AGENTS.md's clause that the harness-written trailer is 'the one exemption'." ], "recommendation": "A, and I am flagging it only so the PM knows the harness text still emits B. Two of the three sources prescribe model-free and AGENTS.md spells the exact trailer pair; its exemption clause reads as covering a trailer the harness writes unbidden, not as a licence to author one. If the PM prefers B, the harness text is the thing to change, not each agent's judgement." } ], "out_of_scope_findings": [ "noted, not filed: WHERE the re-pointed pin actually fires. test/migrate-meta.e2e.test.ts is NIGHTLY-tier by name (*.e2e.test.* and *.live.test.* are selected only under OS_TEST_TIERS=nightly), so the pin runs in the nightly lane and NOT in the merge queue's population. That is a deliberate, documented platform decision covering every .e2e file (the tiers module states the two cuts disagree on 5 files and that this is not a defect), so it is not a card. It matters here because ruling item 2's intent is 'a future silent rename still reddens', and it reddens nightly rather than at the queue. The handshake pin is an ordinary unit test and does run in the queue. 承接者: this PR's contract reviewer — it is stated in the PR body under Acceptance notes, where the clause-2 review reads it.", "noted, not filed: runtimeMajor on the OS_PROTOCOL_INCOMPATIBLE diagnostic keeps its runtime-shaped name. Left alone deliberately: the ruling named runtimeVersion precisely, and an integer major in a protocol-major position is the very shape the previous round's sweep CLEARED (migrate/meta.ts's major-valued site). It is not the padded-semver-in-a-version-position defect this card is about. Recorded because renaming its sibling makes the pair read slightly unevenly, so review should see it rather than discover it. 承接者: this PR's contract reviewer — stated in the PR body.", "noted, not filed: docs/qa/platform-checklist/areas/platform-core.json describes the refusal as carrying 'the runtime version it was judged against'. That is prose about the value's ROLE, not a field name, and it stays accurate after the rename, so there is nothing to edit. 承接者: 无 — no PR or person will touch that file for this reason.", "swept and re-verified rather than trusted, listed so the set stays auditable: the other five PROTOCOL_VERSION emit sites are still correctly labelled — migrate/meta.ts's --to flag description (integer major), metadata-protocol/src/protocol.ts (key 'protocol'), spec/scripts/build-spec-changes.ts (key 'protocolVersion'), spec/scripts/build-upgrade-guide.ts ('Current protocol: 17.0.0'). One drift note against the previous round's record: the metadata-protocol site moved from line 15610 to 16165. Separately, packages/runtime/src/http-dispatcher.ts's runtimeVersion is a genuinely different value (resolveRuntimeVersion(), the real package version) and is correctly named — it is the reason the handshake's name was wrong, and it is untouched.", "no card was filed from this run: nothing met the reproducible-defect, contract-violation or metadata-authoring-trap bar. #16056 is not addressed here and remains open, unblocking when this card closes." ] }Generated by Claude Code
os-justin commentedon Sep 10, 2026
Tier notice — the contract-review-tier requirement on this issue is lifted (skills seat, session
session_01MoTv7pn338AZ71owsp19gQ, 2026-09-10T03:13Z; record and rule-text change in flight: #17285).Maintainer ruling, verbatim: 「现有的卡片如果写了要求fable的,也要让相关的项目经理知道,opus就够了。」 Under the same ruling set (quoted in full on #17285), the contract-review tier is reserved for the skills seat (protocol files + the published
skills/**), the spec seat's clause-② review, and the maintainer-summoned director; triage and every other seat run the default tier.For this card: its
Clause-②: yesdeclaration no longer calls for a contract-review-tier review. The lane seat's own default-tier review, plus the gates (widening tells, pin tests,dispatch-gates --tier), is the review of record, and the build stays at the default tier. Unchanged: theClause-②declaration itself, the manual floor for widenings under 代裁, and the routing rule that a diff touchingpackages/specgoes to the spec seat, where the contract-review-tier review still applies. This comment changes no label, assignee or claim.Generated by Claude Code
github-actions commentedon Sep 10, 2026
os-closed-card-sweep — machine-findable marker for this generated comment.
Removed the pm-loop state label(s) this closed card no longer claims:
pm:dispatched,pm:queue.protocolVersion, not a runtime-shaped name #17261, merged.cca1dc0bfb, merged intomain.bug,domain:cli,finding,needs:contract-review,priority:p3— ownership, priority and outcome are not state claims.A state label claims work is in flight. This card is closed on a merged delivery, so the claim
is stale; every other label is left exactly as it was found. Nothing here is a judgement about
the card, and no verdict-bearing label is ever touched by this sweep.
posted by half-state-patrol run 34451438372 · trigger
scheduleGenerated by Claude Code
feat(cli,metadata-core)!: emit the protocol version under `protocolVe…
docs(releases): append a dated correction to the 17.3.0 migrate-meta …