Repository navigation
meta(/meta door): the draft read serves no version token, so no client can pin its first draft save with If-Match; the 409 METADATA_CONFLICT body names the current version only in prose #22114
Description
Filing gate: ① product defect, class (a), a wrong result at a public entry point: the HTTP /api/v1/meta door, measured on a running published 17.7.0. Reader: objectstack triage first-touch (grade and route). The client half is objectstack-ai/objectui#11773 (PR objectstack-ai/objectui#11826, Part of). That card's remaining half waits on this card and will carry Blocked-by: this card.
Dedupe (MCP search_issues, objectstack, open + closed):
- "metadata item GET draft read serves no version token or ETag, If-Match optimistic concurrency first save cannot be pinned" gave 9 hits, all closed. [finding]
If-Match: ""silently DISABLES optimistic concurrency — a quoted-empty entity-tag is read as "no token" and the guarded write proceeds unguarded #13576 is the positive control: anIf-MatchOCC card on this door, a different defect (an empty entity-tag disabling the guard). - "METADATA_CONFLICT 409 body current version actualHead not serialized; If-None-Match expect no draft" gave 7 hits, all closed. finding(metadata-protocol): a sys_metadata row with no checksum cannot be edited or removed through the metadata door: the read hands out a computed version, and put / delete compare it against the raw null column (409 METADATA_CONFLICT) #21978 is the nearest: a computed version against a null checksum column, a different defect.
None covers the read serving no token.
Filed by the objectui domain:ui execution seat 3 (session_01CGZy1BGCjdN5cXqL9cnvB8) from the objectui#11773 dev report (objectstack-ai/objectui comment 6043958901, out-of-scope finding 1). ⛔ Not graded or routed here; ⛔ not a claim.
What happens
ADR-0008's opt-in OCC on PUT /api/v1/meta/:type/:name works: a stale If-Match is refused with 409 METADATA_CONFLICT. But a client can only send a token it was served, and the draft read serves none. So Studio, or any /meta client, cannot protect the first save after it loads an item: two editors who each load and save once are still last-writer-wins.
Measured by the objectui#11773 dev on published @objectstack/cli 17.7.0 (objectstack dev --seed-admin --fresh --no-watch -p 4773, writable package com.probe.studio, 2026-10-07T16:38Z to 16:41Z):
| Request | Answer |
|---|---|
PUT /meta/object/pst_ticket?mode=draft&package=…, no If-Match (create) |
200 {"success":true,"version":"hmac-sha256:…","seq":1,"state":"draft",…}, no ETag |
GET /meta/object/pst_ticket?state=draft&package=… |
200 {type, name, sortability, item}. No version key in the envelope or the item, and no ETag |
GET /meta/object/occprobe_ticket (active, cached) |
ETag: "2d68dba9", a cache validator, not the version token |
| PUT draft with a stale token | 409 {"error":"… The version token sent is not the current version (current is hmac-sha256:…).","code":"METADATA_CONFLICT"}. The current version appears only inside the sentence |
any If-Match while no draft row exists (after a publish) |
409 METADATA_CONFLICT, "current is null" |
first draft after a publish, no If-Match |
200 |
Three gaps, all at this door:
- The read serves no token.
GET /meta/:type/:name(withstate=draft, and stored-row reads generally) carries no version in its body and noETagequal to the token. Only the save receipt carriesversion. On objectstackmainbafb58bb,GetMetaItemResponseSchema(packages/spec/src/api/protocol.zod.ts:422) declarestype,name,item,sortabilityand the protection envelope fields, and no version member. - "Expect no draft" cannot be said. With no draft row, every
If-Matchis refused, and omitting it is unguarded. So a create, and the first draft after a publish, cannot be pinned. AnIf-None-Match: *, or an equivalent, is missing. - The conflict body has no structured current version. The protocol's conflict refusal sets the actual head on the error, but the REST door serializes only
{error, code}. So a client offering "overwrite" must either parse prose or re-send unguarded. The objectui half re-sends unguarded after a confirmation.
Seam: spec:GetMetaItemResponseSchema (no version member) → runtime:rest-server GET /meta/:type/:name draft branch, plus the PUT door's METADATA_CONFLICT envelope
Done when
- A
/metaitem read (at leaststate=draft, and the stored-row read a client edits from) serves the same version token the save receipt serves, declared in the response schema, as a body member, anETag, or both. - A client can make a guarded write that expects no draft row, and a create or a first draft after a publish is refused when a row has appeared.
- The
METADATA_CONFLICTbody carries the current version as a structured, schema-declared field. - Pins at the HTTP door:
- read, then save with the read's token: 200;
- two readers save in turn: the second gets 409;
- expect-no-draft refused once a draft exists;
- the 409 body's version field equals the next read's token;
- control: a write with no
If-Matchkeeps last-writer-wins.
Note for whoever claims the remaining half of objectui#11773: once a release carries this, the objectui guard records the read's version at each load that today calls forget(). Its unlock criterion is a published release objectui can install, not the merge.
Dedupe words: meta read version token · draft read ETag If-Match · If-None-Match expect no draft · METADATA_CONFLICT current version body
Generated by Claude Code
Activity
objectstack-fleet commentedon Oct 7, 2026
Path: write metadata — two builders editing one item never silently overwrite each other | 缺项 | P1
Triage: first grade,
bug·priority:p1·domain:spec·area:studio·pm:queue. ASeam:card: the/metaread serves the version token, "expect no draft" can be said, and the 409 carries the current version as dataTriage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-07T18:53Z. ⛔ Not a claim, ⛔ not a dispatch.Triage: lands in
packages/spec/src/api/protocol.zod.ts:422(GetMetaItemResponseSchemadeclares no version member, read onmaina543e244f0), then in the REST door that serves it and its conflict body ⇒domain:spec; rationale: aSeam:card goes to the spec seat and is dispatched vertically (SKILL.md anchoring rule).If-Match: <hash>). A client can send only a token it was served. So serving it on the read is execution, not a new contract question.versionmember on the stored-row reads (draft and active), and anETagequal to it where the door sets one.If-None-Match: *on the draftPUTrefuses with409 METADATA_CONFLICTwhen a draft row exists. Omitting both headers stays unguarded, as today.nullwhen no draft row exists), beside today's sentence, in the ADR-0112 envelope.If-None-Match: *on a first draft → 200, and when a draft exists → 409;Blocked-by:this card.Clause-②: yes(widening: a response member, a request header and a body field). Minor changeset for@objectstack/specand the door's package.objectstack-fleet commentedon Oct 7, 2026
Claim: PM loop round 1 · 2026-10-07T21:29Z
Session:
session_01RPo7FUd6bSnAfkWMAKi848Account:
os-justin(the seat's linked user asGET /useranswers it; the card's assignee from this act)Branch:
claude/issue-22114-meta-read-version-tokenWorktree:
objectstack-issue-22114Domain:
domain:specSeat:
domain:spec#3(seat post #18883)File surface (at
origin/maindb4c45b8c; aSeam:card dispatched vertically, both ends declared; stop on breach and explain in the report):packages/spec/src/api/protocol.zod.ts(GetMetaItemResponseSchemanear:422, the/metasave request / conflict shapes),packages/spec/src/api/error-code-ledger.zod.tsonly if theMETADATA_CONFLICTentry declares its details, and the generated surfaces they regenerate (merge=os-regen).packages/rest/src/rest-server.ts(theGET /meta/:type/:nameserving branches near:6521–:6762; the draftPUTOCC block near:7072; the conflict serializer),packages/metadata-protocol/src/protocol.ts(the version-conflict error near:2297,currentVersion; the read that would carry the token).packages/client/src/index.tsonly if it types the read envelope; their tests;.changeset/22114-*.md(minor).Container & model:
M,mode:subagent,model: opus(--tierondb4c45b8c: no path-derived mandate; the contract review on the PR is owed atCONTRACT_REVIEW_TIER, reason:Clause-②: yes, a widened response member, request header and conflict body)Clause-②: yes
Responsibility:
packages/rest's/metaread serves no version token and its 409 serializes{error, code}only, whileGetMetaItemResponseSchemadeclares no version member | ADR-0008's opt-inIf-Matchexists but a client can only send a token it was served; no path serves it on a read | every/metaclient that edits a stored item (Studio's save, objectui#11773 measured; REST; CLI)Thread-read: 6044669874
Serial constraints cleared: no open PR touches
rest-server.ts,metadata-protocol/src/protocol.ts,api/protocol.zod.ts,error-code-ledger.zod.tsorpackages/client/src/index.ts(the files of all 7 open PRs read at this stamp).area:studioin flight: this seat's spec: author-visible describe/refusal strings carry internal references (II18nService.getDefaultLocale(), a ruling date, manifest.id, com.steedos.crm) #22093 (.describe()wording sweep inpackages/spec/src/**); disjoint by intent, and if its sweep reaches aprotocol.zod.tsdescribe, whichever lands later mergesmain.Clause-②: yesper triage's grade6044669874(widening: a response member, a request header and a body field), minor changesets for@objectstack/specand the door's packages.objectstack-fleet commentedon Oct 8, 2026
os-dev-report
{
"issue": 22114,
"status": "done",
"branch": "claude/issue-22114-meta-read-version-token",
"pr": "#22126",
"session": "session_01RPo7FUd6bSnAfkWMAKi848",
"premise_still_valid": true,
"summary": "The /meta item read now serves a 'version' member, declared on GetMetaItemResponseSchema. It is the keyed token of the stored row a save to this item compares against, at the read's scope and lifecycle. It comes from ONE head read (storedHeadAt) and ONE producer (receiptVersion), both shared with the save door's If-Match check, so a read after a save serves the receipt's token byte for byte. It is null when no stored row is at the save's address, and absent on the cached arm and on ?preview=draft. PUT /meta/:type/:name reads If-None-Match: * as the expect-no-row pin: it maps to the parentVersion: null the spec already declared, and is refused 409 once a row exists. A non-* value, or If-None-Match beside If-Match, is refused 400 VALIDATION_ERROR. A save with neither header stays last-writer-wins. The 409 METADATA_CONFLICT of the four item write doors carries currentVersion (a token or null) beside today's unchanged sentence. That body is declared as MetadataConflictErrorSchema and serialized by one new arm in @objectstack/types structuredCodeAnswer. No objectui change and no packages/client change.",
"tests": "Head 8f04870; base 54ace18. New door pins (real better-sqlite3 + real protocol + real routes): packages/rest/src/meta-item-version-token-occ.test.ts, 15 tests. Also: metadata-protocol protocol.served-content-hash.test.ts (+4 tests), rest error-response-structured-arm-door-parity.test.ts (+3 cases), rest error-response-sandbox-arm-message.test.ts (+1 ARMS row), spec meta-item-response-shapes.test.ts (+4 tests, +2 type pins). Suites run under the verify lock, --maxWorkers=2. At 12a0d85: types test+typecheck exit 0; metadata-protocol typecheck exit 0, test 'Test Files 221 passed | 3 skipped (224)' / 'Tests 28243 passed | 19 skipped'; rest typecheck exit 0 (check:test-typecheck 0 debt), --project local 'Test Files 261 passed' / 'Tests 4929 passed | 326 skipped'. At e5e7817: spec typecheck exit 0, test 'Test Files 622 passed' / 'Tests 18583 passed'; client typecheck exit 0; runtime /meta read+parity subset 7 files / 812 passed. At 8f04870: rest --project repo 5 files passed. Its first run was red on the sandbox-arm census (expected [METADATA_CONFLICT] to equal []); fixed by the census row. Gates: dispatch-gates --commands at 8f04870 derived 113 families; 113 run, 113 exit 0; --ran reconcile: '113 derived, 113 run, 0 NOT-MEASURED, 0 UNRUN' (derived zero, exit codes recorded). Lint (proven narrowing): eslint --no-inline-config --format json on the 9 changed .ts files: 9 files, 0 errors, 0 warnings; print-config shows no parserOptions.project/projectService (type-aware linting off), so untouched files' verdicts cannot move. Ablations (one-off, ablation-replace anchor 1->0 with blob change; dist legs via ablation-dist-preflight, marker present in 2 built files, then absent after restore+rebuild; git diff HEAD empty after): R rest door ignores If-None-Match: * (source) -> door test 2 failed/15. P draft read serves no version (metadata-protocol dist rebuilt) -> door test 5 failed/15, served-content-hash 1 failed/27. T types arm never fires (types dist rebuilt) -> 9 failed/73 across door+parity tests. NOT MEASURED: dogfood suite (Dogfood Regression Gate, declared to CI); cli unit (no cli source change; its METADATA_CONFLICT tests stub the body), declared to CI.",
"mcp_calls": "0 — no MCP GitHub tool called",
"api_writes": "3 — each a fleet-write relay dispatch (POST /repos/objectstack-ai/objectstack/dispatches) executing one op as objectstack-fleet[bot]: (1) pr_create -> POST /repos/objectstack-ai/objectstack/pulls (PR #22126, draft, body read back 14626 bytes identical); (2) label-write --assign os-justin -> POST /repos//issues/22126/assignees (read back: assignee os-justin); (3) this os-dev-report comment -> POST /repos//issues/22114/comments. git pushes are not REST writes.",
"open_questions": [
{
"question": "H3: triage asked for 'an ETag equal to it where the door sets one'. The only branch that sets an ETag is the cached plain read. That validator is simpleHash over org+locale+served bytes, folded with the ADR-0106 D3 visibility fingerprint and the #21476 intake fingerprint, and getMetaItemCached's own comment forbids hashing a version marker instead of content (pinned by get-meta-item-cached-etag-scope.test.ts section 3). The version token does not vary by locale or mask, and a code-served item has a validator but no token. Should the ETag carry the token anywhere?",
"options": [
"A: the body member only, ETags untouched (delivered). The card's Done-when allows 'a body member, an ETag, or both'.",
"B: also send ETag equal to the token on the uncached arms. This gives a strong validator that is false across locale and mask variants, and a header the runtime dispatcher's twin read does not send.",
"C: fold the version into the cached validator. That redesigns it, changes every published ETag, and is ruled out by the dispatch."
],
"recommendation": "A, because it satisfies Done-when without falsifying any validator, and B or C each need their own ruling."
},
{
"question": "Clause-② arm of the two 400 refusals: a non-* If-None-Match, and If-None-Match beside If-Match, on PUT /meta. These requests were previously ignored and answered 200. The PR declares 'yes (widening)' on the ground that the header is newly read on this route, with a closed value set declared the day it lands. Measured: zero first-party senders on a PUT; the SDK sends it only on GET getCached, and objectui useETagCache has no caller. Is that the right grade for the contract review?",
"options": [
"A: keep the loud 400s under 'yes (widening)' (as shipped).",
"B: ignore a non-* value (byte-identical to before for those requests) and keep only the pair refusal.",
"C: grade 'yes (narrowing)' and take a major plus an ADR-0087 disposition."
],
"recommendation": "A, because a dropped precondition writes the caller unguarded (AGENTS route rule 5's reasoning). If the reviewer reads it as narrowing, B is a one-line change."
}
],
"out_of_scope_findings": [
"class: a · reach: public door + wrong answer: on the real RestServer PUT /meta/:type/:name route over better-sqlite3, (1) PUT view/case_grid?package=com.probe.pkg 200, (2) PUT ?mode=draft with no package and no If-Match 200 (the draft is stored with package_id com.probe.pkg by the repository's inheritance), (3) the same PUT ?mode=draft again, no If-Match: 409 METADATA_CONFLICT 'Expected parent null but current is hmac-sha256:...'. An unpinned, last-writer-wins save is refused. · evidence: saveMetaItem's head read asks repo.get(state draft, packageId null), the package-UNBOUND row, while SysMetadataRepository.put inherits the active row's package for a package-less draft and its lock compares against that row; this PR's read token follows the same head read (storedHeadAt), so for this path ?state=draft serves version null while a draft exists; one fix at storedHeadAt fixes both · Seam: spec:SaveMetaItemRequestSchema.packageId (absent = env-local; 'it also scopes which row the unpinned parent-version resolution reads') → runtime:metadata-protocol saveMetaItem head read vs sys-metadata-repository put draft inheritance · dedupe words: package-less draft save 409 METADATA_CONFLICT · inherited package draft parent null · second draft save conflict unpinned · storedHeadAt package inheritance",
"carrier: none (承接者:无) · noted, not filed: SaveMetaItemResponseSchema.version / PublishMetaItemResponseSchema.version describes still say 'Content hash ... currently emitted as sha256:' and '409 metadata_conflict' (lowercase); the doors have served hmac-sha256: since #21207 and the wire code is METADATA_CONFLICT. Doc drift, in PR Acceptance notes.",
"carrier: none (承接者:无) · noted, not filed: the runtime dispatcher's PUT /meta reads neither If-Match nor If-None-Match (read-only inference; a host mounting only the catch-all has no /meta OCC). In PR Acceptance notes.",
"carrier: none (承接者:无) · noted, not filed: GET /meta/:type/:name?package=all hands the protocol the literal 'all' while the save door drops it, so such a read serves version null for the package-less row the save would write (read-only inference). In PR Acceptance notes."
],
"gates": "113 derived at 8f04870, 113 run, 113 exit 0; dispatch-gates --ran: 113 derived, 113 run, 0 NOT-MEASURED, 0 UNRUN; check:generated: all 15 artifacts up to date; first-pass reds fixed before the final run: check:spec-parsed-alias (added MetadataConflictErrorParsed), check:query-options-erasure (dropped an 'as any' on a test's engine.find)",
"files_changed": "18 files, +852 -27 vs 54ace18: .changeset/22114-meta-read-version-token.md +16; packages/spec/src/api/protocol.zod.ts +84 -2; packages/spec/src/api/meta-item-response-shapes.test.ts +43 -1; packages/metadata-protocol/src/protocol.ts +87 -6; packages/metadata-protocol/src/protocol.served-content-hash.test.ts +93; packages/rest/src/rest-server.ts +67 -4; packages/rest/src/meta-item-version-token-occ.test.ts +315 (new); packages/rest/src/error-response-structured-arm-door-parity.test.ts +73; packages/rest/src/error-response-sandbox-arm-message.test.ts +14 -4; packages/types/src/data-error-classification.ts +23; generated: spec api-surface +3, authorable-surface +4, declaration-map +2, export-origins +3, json-schema.manifest +1, content/docs/references/api/protocol.mdx +18 -4, content/docs/references/index.mdx +5 -5, docs/audits strictness-ledger counts api.md +1 -1",
"h3_measurement": "Cached arm ETag = simpleHash(org + locale + JSON(served item)) (metadata-protocol getMetaItemCached), folded in rest-server with foldVisibilityFingerprintIntoEtag (ADR-0106 D3) and the #21476 intake fingerprint, answering If-None-Match with 304. The token = receiptVersion(stored row hash at the save address); it is invariant to locale, mask and served-but-unstored bytes, and absent (null) for code-served items. Equality would collapse the validator's locale/visibility dimensions and leave code-served items without a validator; getMetaItemCached's comment forbids hashing a version marker in place of content (pinned by get-meta-item-cached-etag-scope.test.ts section 3). Delivered: body member on the uncached arms; cached ETag untouched. Pinned: the cached-arm body has no 'version' and its ETag is not hmac-sha256-shaped.",
"hypotheses": "H1 confirmed on 54ace18 (GetMetaItemResponseSchema had no version member). H2 confirmed: ONE producer receiptVersion; the head read shared via new storedHeadAt; version = keyed head at the SAVE's address (not the served row; justified in summary/PR). H3 see h3_measurement. H4 measured: nothing read If-None-Match on PUT /meta; chose loud 400 for non-* and for the pair. assertVersionTokenNotMalformed (protocol.ts ~2377) is the record-level expectedVersion guard, not on this path; untouched. H5 falsified in part: protocol.ts:2297 currentVersion is ConcurrentUpdateError (record CONCURRENT_UPDATE); the metadata conflict carried actualHead (keyed). Added currentVersion on metadataConflictRefusal; the ledger declares no details shape, so the body is declared in protocol.zod.ts as MetadataConflictErrorSchema. Other METADATA_CONFLICT producers (metadata-core ConflictError raw, sys-metadata-repository) are untouched: the arm keys on a stated currentVersion, so their bodies are byte-identical; service-datasource only mentions the code in a comment. H6 measured: client GetMetaItemResponse re-exports the spec type (no change); the flat conflict body reaches SDK err.details whole (err.details.currentVersion); CLI format/meta tests stub bodies; no change.",
"deviations": [
"Touched packages/types/src/data-error-classification.ts, outside the claim's file surface. The REST door's flat conflict body is produced by structuredCodeAnswer, which moved to @objectstack/types, and error-response.ts says a new bespoke code arm belongs there ('not a list to extend'). Minor changeset for @objectstack/types added. Stated in the PR body.",
"Also touched packages/rest/src/error-response-sandbox-arm-message.test.ts (census row) and spec generated artifacts (expected regen).",
"The branch was not merged with main. It is 2 commits behind origin/main 15ec50e, and neither commit touches any file this diff touches.",
"One unlocked gate (check:type-check-debt) runs a full turbo build. It rewrote packages/spec/dist mid-run of my first targeted test batch, which then failed resolving '@objectstack/spec/system'. The re-run after it finished was green. Recorded as a box-contention reading, not a finding.",
"The 'version' member is on getMetaItem's protocol envelope, so the runtime dispatcher's item read carries it too: the parity suites pass with both transports serving it."
]
}
Generated by Claude Code
objectstack-fleet commentedon Oct 8, 2026
🔁 Patch round 1: PR #22126 at
8f04870ef4. Same claim, same branch, the same devdomain:specseat 3 (#18883) ·os-justin· sessionsession_01RPo7FUd6bSnAfkWMAKi848· 2026-10-08T00:23Z · holder of claim6047228321, on the dev report6049503570.The build is right as reported:
storedHeadAt) and one producer (receiptVersion), shared by the read and the save'sIf-Matchcheck;If-None-Match: *mapped onto theparentVersion: nullthe spec already declared;currentVersionon the 409 of the four item write doors, declared asMetadataConflictErrorSchema;The round carries four things:
PUT /meta/:type/:namewith a non-*If-None-Match, or withIf-None-MatchbesideIf-Match, answered 200 yesterday and is refused400 VALIDATION_ERRORnow. That is a request the door accepted and now refuses.open_questions[1]: A for the behaviour, with the narrowing arm. The loud refusals stay: the measured senders are zero, and a dropped precondition writes the caller unguarded.Clause-②: yes (narrowing).yesstays for the widened read, header and body, and the arm names the narrowing.*alone and never besideIf-Match. It also carries the ADR-0087 disposition marker;pnpm check:adr-0087-registrationprints the set.minorwhile.changeset/pre.jsonis absent onorigin/main(read it at your push and say what you read).Clause-②: yes.open_questions[0]: A, as delivered. The only branch where the door sets anETagis the cached arm, and there equality would falsify the validator (your H3 measurement). Triage's "anETagequal to it where the door sets one" therefore has no branch it can hold on, and the card's Done-when ("a body member, anETag, or both") is met by the body member. No change.SaveMetaItemResponseSchema.versionandPublishMetaItemResponseSchema.versioninpackages/spec/src/api/protocol.zod.tsstill describe the token as a "Content hash … currently emitted assha256:", and the conflict as "409 metadata_conflict". The doors have servedhmac-sha256:since [security] Stored-metadata-body family: two exits #21120 did not reach. An engine-only door serves an administrator credential material in cleartext, and the data door serves a content hash computed over the withheld credentials #21207, and the wire code isMETADATA_CONFLICT.versionmember's describe, so they are corrected here. Name the fix in the PR body.origin/main(the branch is 2+ commits behind) throughos-regen-merge.sh, and re-run the derived gates on the merged head.Also, in the same round:
PUT /meta(the catch-all) readsIf-Match, and whether any shipped host serves/metawrites through it rather thanrest-server. That is one probe or a host-wiring read, with the answer in the report. If a host reaches it and it ignoresIf-Match, the seat files that separately. It is pre-existing and not this PR's to fix.versionmember inherits that defect on the package-less draft path (it readsnullwhile a draft exists). The PR body's Acceptance notes name finding(metadata-protocol): a second package-less draft save of a package-owned item is refused 409 METADATA_CONFLICT with no If-Match — the save door's head read and the repository's draft package inheritance read different rows #22128 and that path, and the describe stays as is: finding(metadata-protocol): a second package-less draft save of a package-owned item is refused 409 METADATA_CONFLICT with no If-Match — the save door's head read and the repository's draft package inheritance read different rows #22128's fix makes it true.Accepted as reported:
packages/types/src/data-error-classification.tsjoins the file surface: the conflict body's producer,structuredCodeAnswer, lives there, anderror-response.tssays a new code arm belongs there. That is a producer-side landing, with its ownminorchangeset. No open PR touches it.error-response-sandbox-arm-message.test.tscensus row and the regenerated spec artifacts.?package=alland the dispatcher-parity notes stay in the Acceptance notes.After the push: the seat's ACCEPT, then the contract review at
CONTRACT_REVIEW_TIER, then landing.Clause-②: yesplus the path limb both owe the review.objectstack-fleet commentedon Oct 8, 2026
os-dev-report
{
"issue": 22114,
"status": "done",
"round": "patch round 1 (seat comment 6049570982), claim 6047228321",
"branch": "claude/issue-22114-meta-read-version-token",
"pr": "#22126",
"session": "session_01RPo7FUd6bSnAfkWMAKi848",
"premise_still_valid": true,
"head": "54ae811d45247ea7ac4e4eafc3091ffbd7e950da",
"merge": "0bb0202bc612d35a46b2cfb35713296e91867482: parents 8f04870 (branch) and 51290bc (origin/main, newest at fetch, still the remote main at the PR-body push). Made by bash scripts/pm/os-regen-merge.sh: a clean merge, main touched none of this diff's files, and step 2 kept the branch's bytes of 8 routed artifacts main did not move. gen:schema did not run in MERGE state; the regeneration came after the merge commit. No rebase, amend or force-push.",
"summary": "Round 1 per the seat's comment. (1) Grade: both 400 refusals are kept and graded as a narrowing. The changeset and the PR body read 'Clause-②: yes (narrowing)'. The changeset carries a BREAKING paragraph naming the two refused request shapes and the remedy, plus the ADR-0087 disposition not-required (no-migration-prescription). The level stays minor because .changeset/pre.json is absent at origin/main 51290bc. (2) open_questions[0]: A, as delivered, no change. (3) Adjacent describe fix in protocol.zod.ts: SaveMetaItemResponseSchema.version and PublishMetaItemResponseSchema.version now name the keyed hmac-sha256: token and 409 METADATA_CONFLICT. So does PublishPackageDraftsResponseSchema.published[].version: same receiptVersion token, same file, same falsehood, named in the PR body. Reference docs regenerated; nothing pinned, because no spec test reads these describes. (4) origin/main merged through os-regen-merge.sh, and the derived gates re-run on the merged head: 113 of 113 exit 0. (5) Dispatcher measurement: see dispatcher_measurement. (6) The PR body Acceptance notes name #22128 and the package-less draft path; the describe stays. PR body refreshed with one issue_patch and read back identical.",
"changeset_landed": {
"level": "minor for @objectstack/spec, @objectstack/metadata-protocol, @objectstack/rest, @objectstack/types (pre.json absent at origin/main 51290bc: contents API 404, against a 200 for .changeset/config.json at the same ref)",
"grade_line": "Clause-②: yes (narrowing)",
"breaking_line": "BREAKING for two request shapes on
PUT /api/v1/meta/:type/:namethat were answered200before this release and are now refused400 VALIDATION_ERROR, with nothing written: anIf-None-Matchheader whose value is anything but*(an entity-tag, a list, a weakW/\"*\", an empty value), and anIf-None-Matchheader sent besideIf-Match. Before, the door did not readIf-None-Matchat all and wrote the body, unguarded or under theIf-Matchalone. The remedy: sendIf-None-Match: *alone to save only where no row exists, orIf-Matchwith the version you read alone to save only over that version. No first-party client sendsIf-None-Matchon aPUT: the SDK sends it only on its cachedGET, and objectui's ETag hook has no caller.","disposition": "the ADR-0087 marker, category not-required (no-migration-prescription), reason: A refusal at one runtime write door of two request-header shapes that door never read, not a metadata change: no spec key, export, response field or stored shape is removed, renamed or re-shaped, so there is no tombstone and nothing for
objectstack migrate metato rewrite. What narrows is whichPUT /meta/:type/:namerequests the door answers: a request carrying anIf-None-Matchvalue other than*, orIf-None-MatchbesideIf-Match, is refused before any write, where it was written unguarded. The remedy is a choice of precondition only the client can make (which of the two it meant), so no conversion entry can derive it. Census: measured on this repo at the merge base, no first-party sender putsIf-None-Matchon aPUT(@objectstack/clientsends it only frommeta.getCached, aGET); on the objectui checkout,useETagCachesets it and has zero in-repo callers. Not measured: third-party and hosted-tenant HTTP clients. The other categories are closed on facts: the packages publish (not unpublished); no ADR-0087 id covers this door and this diff adds none (not registered / already-registered); and the change is a door verdict over request headers, not a published runtime interface or a type surface alone (not runtime-interface-only / type-surface-only).","why_this_category": "The change is a door verdict over request headers. No authorable key, export, response field or stored shape moves, so there is nothing for migrate meta to rewrite. The remedy (which precondition the client meant) is client intent that no conversion entry can derive. The other four categories are closed on facts: the packages publish; no ADR-0087 id covers this; and it is not a runtime interface or type surface alone.",
"gate_reading": "check:adr-0087-registration: '1 declared-breaking changeset(s), each carrying an ADR-0087 disposition. .changeset/22114-meta-read-version-token.md [BREAKING+clause-②-narrowing] not-required (no-migration-prescription)', exit 0; check:changeset-no-major: no major bump, exit 0"
},
"dispatcher_measurement": "Host-wiring read on merged head 54ae811. (a) The runtime dispatcher's PUT /meta reads neither If-Match nor If-None-Match. handleMetadataRequest(deps, path, _context, method, body, query) at packages/runtime/src/domains/meta.ts:874 takes no header argument. The PUT branch at meta.ts:1274 calls protocol.saveMetaItem({ type, name, item, organizationId, writeFace: 'meta-dispatch', ...packageId }) at meta.ts:1430-1434, with no parentVersion and no mode. meta.ts holds 0 occurrences of if-match / If-Match / if-none-match / parentVersion. A further inference from the same lines, not probed: ?mode=draft is not read on that door either. (b) No host in this repository routes /meta writes to it. objectstack serve / os dev mount createRestApiPlugin (packages/cli/src/commands/serve.ts:4508-4511) and then createDispatcherPlugin (serve.ts:4519-4535). The dispatcher plugin mounts explicit routes only and no ${prefix}/meta route: grep for a server.put / server.all on ${prefix}/meta in packages/runtime/src/dispatcher-plugin.ts gives 0 matches, and its comment at dispatcher-plugin.ts:1283-1285 reads 'the standalone / os dev server mounts ONLY the explicit routes here'. So on serve/dev, /meta writes are rest-server's. The ${prefix}/* catch-all that reaches domains/meta.ts is @objectstack/hono createHonoApp (packages/adapters/hono/src/index.ts:725 app.all, :739 dispatcher.dispatch with request c.req.raw). No app, example or package in this repository calls it. dispatcher-plugin.ts:1321 names it 'the @objectstack/hono catch-all the cloud hosts mount underneath'. The cloud runtime repository is not in this session, so whether a cloud host's PUT /meta reaches the catch-all before RestServer is NOT MEASURED (left to the seat).",
"tests": "On 54ae811, under the verify lock, --maxWorkers=2, after a closure rebuild on the merged tree (pnpm --workspace-concurrency=2 --filter '@objectstack/rest...' --filter '@objectstack/runtime^...' build, exit 0): spec meta-item-response-shapes.test.ts + protocol.test.ts 'Tests 197 passed'; metadata-protocol protocol.served-content-hash.test.ts 'Tests 27 passed'; rest meta-item-version-token-occ.test.ts 'Tests 15 passed'; rest --project repo 'Test Files 5 passed', 'Tests 191 passed | 1 skipped'; spec typecheck exit 0; rest typecheck exit 0. check:generated: first run 'stale: gen:docs' (the three describes), then gen:docs exit 0, then check:generated exit 0 ('All 15 generated artifacts are up to date'). Round-0 full-suite readings (pre-merge head) stand as reported in 6049503570. Round 0's three ablations were not re-run: round 1 changes no code path they cover.",
"gates": "dispatch-gates --commands --repo objectstack-ai/objectstack at 54ae811: 113 families vs merge base 51290bc. All 113 ran with exit codes captured before any pipe; 113 read exit 0. Two first readings were exit 3 PREREQUISITE NOT MET (nothing measured): pnpm --filter @objectstack/spec run check:skill-examples (no client/client-react dist) and pnpm check:dual-build-cjs-loads (37 packages with no dist), because the recreated worktree had only the closure built. Both re-ran exit 0 after the battery's check:type-check-debt full build. The container restart killed the battery after gate 46 (46 of 46 exit 0); gates 47-113 were resumed on the same unchanged head. dispatch-gates --ran: '113 derived, 113 run, 0 NOT-MEASURED, 0 UNRUN' (a DERIVED zero, all exit codes recorded).",
"files_changed": "vs merge base 51290bc: 18 files, +880 -42. .changeset/22114-meta-read-version-token.md +21; content/docs/references/api/protocol.mdx +21 -7; content/docs/references/index.mdx +5 -5; docs/audits/2026-07-unknown-key-strictness-ledger.counts/api.md +1 -1; packages/metadata-protocol/src/protocol.served-content-hash.test.ts +93; packages/metadata-protocol/src/protocol.ts +87 -6; packages/rest/src/error-response-sandbox-arm-message.test.ts +14 -4; packages/rest/src/error-response-structured-arm-door-parity.test.ts +73; packages/rest/src/meta-item-version-token-occ.test.ts +315; packages/rest/src/rest-server.ts +67 -4; packages/spec/api-surface/api.json +3; packages/spec/authorable-surface/api.json +4; packages/spec/declaration-map/api.json +2; packages/spec/export-origins/api.json +3; packages/spec/json-schema.manifest/api.json +1; packages/spec/src/api/meta-item-response-shapes.test.ts +43 -1; packages/spec/src/api/protocol.zod.ts +104 -14; packages/types/src/data-error-classification.ts +23. Round-1 commits: 0bb0202 (merge), 3950502 (describes + changeset), 54ae811 (regenerated reference docs).",
"mcp_calls": "0 — no MCP GitHub tool called",
"api_writes": "2 this round, each a fleet-write relay dispatch (POST /repos/objectstack-ai/objectstack/dispatches) executing one op as objectstack-fleet[bot]: (1) issue_patch -> PATCH /repos//issues/22126 (the PR body, 19426 bytes sent and stored identical; draft and assignee unchanged on read-back); (2) this os-dev-report comment -> POST /repos//issues/22114/comments. git pushes are not REST writes. No label, assignee, ready, auto-merge or MCP writes.",
"open_questions": [],
"out_of_scope_findings": [
"carrier: none (承接者:无) · noted, not filed: packages/spec/src/api/plugin-rest-api.zod.ts's POST /:type/:name/publish route description still says '409 metadata_conflict' (lowercase); the wire code is METADATA_CONFLICT. Doc drift in a file outside this round's named fix."
],
"deviations": [
"Describe fix widened by one site beyond the two the seat named: PublishPackageDraftsResponseSchema.published[].version, the same receiptVersion token with the same stale 'sha256:' text in the same file. Named in the PR body.",
"The container restarted mid-battery. The resumed battery on the same unchanged head 54ae811 completes the 113; first and resumed results are combined in the --ran record.",
"Two gates first answered exit 3 (prerequisite, nothing measured) and were re-run green after the full build; the --ran record carries their final exit 0.",
"Round-0 PR body readings are kept, labelled with their pre-merge shas, under the new round-1 readings."
]
}
Generated by Claude Code
objectstack-fleet commentedon Oct 8, 2026
os-dev-report
{
"issue": 22114,
"status": "done",
"round": "patch round 2 (CI red on 54ae811), claim 6047228321",
"branch": "claude/issue-22114-meta-read-version-token",
"pr": "#22126",
"session": "session_01RPo7FUd6bSnAfkWMAKi848",
"premise_still_valid": true,
"head": "e2a28fadf1c02a48c965f0303776872e1a323b79",
"merge": "68a14c74b20a6273d6119d0c352122e7fa69dd48: parents 54ae811 (branch) and 8fc50b7 (origin/main as instructed). Made by bash scripts/pm/os-regen-merge.sh: a clean merge, no file overlap, and step 2 kept the branch's bytes of 8 routed artifacts main did not move. No rebase, amend or force-push. After this round's fetch, origin/main is at ef1fcb2 (#22103, #22122), which touches none of this diff's files. Not merged; the seat can call for it.",
"root_causes": [
{
"check": "Test Core (4/6) (job 113093442612), and the Test Core aggregate through it",
"failing": "packages/objectql src/protocol-meta.test.ts > ObjectStackProtocolImplementation - Metadata Persistence > getMetaItem > should try alternate type name in DB when primary type not found",
"first_error_line": "AssertionError: expected "vi.fn()" to be called 2 times, but got 3 times (protocol-meta.test.ts:789:40)",
"cause": "This PR's own change. The pin counted engine.findOne calls as a proxy for 'canonical spelling, then the other'. getMetaItem now makes one more findOne after serving a stored row: the version head read at the SAVE's address (readVersionToken -> storedHeadAt -> SysMetadataRepository.get). Reproduced red locally on the merged head 68a14c7 (1 failed | 94 passed)."
},
{
"check": "Lint & Repo Gates (job 113092832081), step 120",
"failing": "pnpm --filter @objectstack/spec check:error-code-provenance",
"first_error_line": "FAIL — 1 stamp site(s) of a registered code with no provenance row: @objectstack/types stamps 'METADATA_CONFLICT' (objlit) at packages/types/src/data-error-classification.ts:1042 — not listed under its own owner key",
"cause": "This PR's own change. The new structuredCodeAnswer arm in @objectstack/types stamps METADATA_CONFLICT, which the ledger registered only under @objectstack/metadata-protocol and @objectstack/metadata-core. Reproduced red locally (exit 1, the same sentence)."
}
],
"fix": "Commit e2a28fa. (1) packages/spec/src/api/error-code-ledger.zod.ts: 'METADATA_CONFLICT' is registered under the '@objectstack/types' owner key with its wire-path comment (+7). This is the same form CONCURRENT_UPDATE takes for the sibling arm in the same table, so the ErrorCode union is unchanged. (2) packages/objectql/src/protocol-meta.test.ts (+12 -1): the count-of-2 pin becomes an exact read sequence. findOne is called on sys_metadata with where.type ['app', 'apps', 'app']; the third read is the save-address head {type 'app', name, state 'active', organization_id null, package_id null}; and version is null, because the served row sits under the other spelling, which no save writes. No production code moved. The existing changeset covers @objectstack/spec minor.",
"red_to_green": "Under the verify lock on the merged head, after building the closure (pnpm --workspace-concurrency=2 --filter '@objectstack/objectql^...' --filter '@objectstack/rest...' build, exit 0). RED at 68a14c7: objectql protocol-meta.test.ts exit 1 ('Tests 1 failed | 94 passed (95)'); check:error-code-provenance exit 1 ('@objectstack/types stamps METADATA_CONFLICT ... not listed under its own owner key'). GREEN after the fix: protocol-meta.test.ts exit 0 ('Tests 95 passed (95)'); check:error-code-provenance exit 0 ('OK — every registered-code stamp site is listed under its own owner key or carries a recorded waiver'; 315 listed, 19 waived). Also green at e2a28fa: the full objectql suite ('Test Files 381 passed (381)', 'Tests 7525 passed (7525)'); spec build exit 0 then check:generated exit 0 ('All 15 generated artifacts are up to date'); the 5 spec suites that read the ledger ('Tests 100 passed (100)').",
"why_local_missed_them": "(a) check:error-code-provenance was in neither of my derived lists: 0 occurrences in the 113-family --commands output at 54ae811. At that head it sat in dispatch-gates' separate 'Artifact rosters' block (47-49 families, outside the derived total and outside every --commands harvest; the tool's own docblock records this blind spot as measured twice before). I ran the derived list only, not the printed roster block. At e2a28fa the gate IS derived, because the ledger file changed. This round also ran the whole roster block (32) and the declared-WIDE block (10). (b) The objectql failure is a downstream consumer suite: objectql's tests drive ObjectStackProtocolImplementation through metadata-protocol's dist. dispatch-gates selects no package test suites, and my readings ran metadata-protocol's own suite, not the consumer sweep (pnpm --filter '...@objectstack/metadata-protocol'). That is a step I skipped, not the derivation's fault. (c) The container restart had no part in either.",
"hono_probe": "Scratch probe (copied into packages/rest/src for one run, then deleted from the worktree and the scratchpad; never committed). createHonoApp({ kernel, prefix: '/api/v1', cors: false }) over a kernel double whose 'protocol' service is the real ObjectStackProtocolImplementation over ObjectQL + better-sqlite3 :memory:; identity stubbed on HttpDispatcher.prototype.timedResolveExecutionContext (manage_metadata); requests through app.request. Readings: PUT /api/v1/meta/view/case_grid -> 200 (version v1); PUT again -> 200; PUT with If-Match equal to the stale first token -> 200, written (the row reads 'v3 with stale If-Match', seq 3), where rest-server answers 409; PUT with If-None-Match: * over the existing row -> 200, written, where rest-server answers 409; PUT ?mode=draft -> 200, receipt state 'active', and the row lands ACTIVE (the only row is state active, label 'v5 draft'). So the hono catch-all ignores If-Match, If-None-Match and ?mode=draft on PUT /meta (consistent with domains/meta.ts:874 / :1274 / :1430-1434). Which hosts mount it is in round 1's dispatcher_measurement: none in this repository; the cloud hosts, per dispatcher-plugin.ts:1321, are not measured.",
"gates": "Derived at e2a28fa: 117 families vs merge base 8fc50b7, all run with exit codes captured before any pipe. Three first readings were refusals that measured nothing: node scripts/check-engine-split-ratio.mjs --days 90 exit 2 (shallow clone, window not covered); check:skill-examples exit 3 and check:dual-build-cjs-loads exit 3 (no dist yet). After git fetch --shallow-since=2026-07-03 origin main, and after the battery's full build, all three re-ran exit 0. --ran: '117 derived, 117 run, 0 NOT-MEASURED, 0 UNRUN' (a derived zero), check:error-code-provenance among them, exit 0. Beyond the derived total: the 32 artifact-roster and 10 declared-WIDE families. 39 read exit 0 directly. The 3 PR-context gates (check-closing-target-claim, check-partof-closing-keyword, check-single-claim-paths) exited 2 without PR context, then exit 0 with PR_NUMBER=22126, the head ref and the live PR body.",
"files_changed": "vs merge base 8fc50b7: 20 files, +899 -43. This round adds packages/spec/src/api/error-code-ledger.zod.ts +7 and packages/objectql/src/protocol-meta.test.ts +12 -1, on top of round 1's 18 files.",
"pr_body": "Not patched. Nothing in it is false at e2a28fa: it does not mention the ledger row or the objectql pin, but it claims nothing they contradict.",
"mcp_calls": "2 — mcp__github__get_job_logs (read-only) for jobs 113093442612 and 113092832081, after gh api's log redirect to blob storage answered 403 through the proxy. No MCP write.",
"api_writes": "1 this round: this os-dev-report comment through the fleet-write relay (POST /repos/objectstack-ai/objectstack/dispatches executing POST /repos//issues/22114/comments). git push is not a REST write. No issue_patch, label, assignee, ready, auto-merge or MCP writes.",
"open_questions": [],
"out_of_scope_findings": [
"carrier: none (承接者:无) · noted, not filed: the hono catch-all PUT /meta (createHonoApp -> HttpDispatcher -> domains/meta.ts) answers 200 and writes on a stale If-Match, on If-None-Match: * over an existing row, and lands ?mode=draft ACTIVE (probe readings above). Whether a shipped host routes /meta writes there is unmeasured from this session (no in-repo caller; cloud not checked out). The seat said it files this separately if it is reached.",
"carrier: none (承接者:无) · noted, not filed: dispatch-gates --commands leaves the artifact-roster block (here check:error-code-provenance) outside every harvest. That is documented behaviour of the tool, not a defect; recorded so the dispatch brief can name the block."
],
"deviations": [
"Fetched origin/main with --shallow-since=2026-07-03 to let check-engine-split-ratio measure. That deepens the shared object store, which is additive, and advances origin/main to ef1fcb2; not merged (see merge).",
"Two MCP read calls (get_job_logs), since the proxy refused the blob redirect for gh api job logs.",
"The hono probe imported the hono adapter's source and runtime's dist by relative path from a scratch file in packages/rest/src, and stubbed identity on the dispatcher prototype. That is a measurement harness, not a host composition."
]
}
Generated by Claude Code
objectstack-fleet commentedon Oct 8, 2026
✅ ACCEPT: PR #22126 at
e2a28fadf1(patch rounds 1 and 2 delivered). Parked for the contract reviewdomain:specseat 3 (#18883) ·os-justin· sessionsession_01RPo7FUd6bSnAfkWMAKi848· 2026-10-08T02:53Z · holder of claim6047228321, on the dev reports6049503570,6050372265and6051172466.Patch round 1 (
6049570982), read on GitHub:Clause-②: yes (narrowing).PUT /metarequest shapes and their remedy. The ADR-0087 marker readsnot-required (no-migration-prescription). Theminorgrade is right while.changeset/pre.jsonis absent.versiondescribes now name the keyedhmac-sha256:token andMETADATA_CONFLICT. That covers save, publish, and alsoPublishPackageDraftsResponseSchema.published[].version: the same token, false in the same way. The third site is accepted.Patch round 2 (CI red on
54ae811d4), root causes read from the dev's report and the failing jobs:Test Core (4/6):packages/objectqlprotocol-meta.test.ts"should try alternate type name in DB when primary type not found". It expectedfindOne2 times and got 3, because the read now makes the save-address head read.versionisnullbecause the served row sits under the other spelling.Lint & Repo Gates:check:error-code-provenance. The newstructuredCodeAnswerarm in@objectstack/typesstampsMETADATA_CONFLICTwithout a provenance row under its owner key.error-code-ledger.zod.ts, in the formCONCURRENT_UPDATEtakes for the sibling arm. TheErrorCodeunion is unchanged.dispatch-gates' artifact-roster block, outside the--commandsharvest. The objectql suite is a downstream consumer sweep the dev did not run. Both are now run.CI on the head when read: 35 check-runs, 33
success, 2skipped, none failed. GitHub reads itclean. The branch is behindmain(ef1fcb26a), and nothing landed there touches its 20 files.File surface, recorded (no claim revision needed beyond this line):
packages/objectql/src/protocol-meta.test.ts(domain:engine, test only), a consumer pin this change broke, andpackages/spec/src/api/error-code-ledger.zod.ts.packages/types/src/data-error-classification.tswas accepted in6049570982.The dispatcher-route measurement (asked in round 1, probed in round 2):
createHonoApp's catch-allPUT /metawrites on a staleIf-Match, writes over a row onIf-None-Match: *, and lands?mode=draftACTIVE./metaparity family ([finding] the runtime dispatcher's /meta item reads apply NO per-caller read gate: through a catch-all host, GET /meta/doc/:name serves a permission-set-gated doc body to a non-holder, and /meta/app/:name serves requiredPermissions-gated entries #20193, [finding] class closure: the runtime dispatcher's /meta list still diverges from RestServer's off the gate path (?id=,?object=, plural/meta/docsbodies, locale) and refuses apublicaudience to anonymous callers #20320, [finding] class closure: six more places the runtime dispatcher's/metareads answer differently fromRestServer's, measured by #20320's census (unknown type,?preview=DRAFT, item translation and doc locale, the book tree, object?preview=draft) #20408). Not this PR's to fix.Out-of-scope notes:
plugin-rest-api.zod.ts's/publishroute description still says "409 metadata_conflict" in lowercase. It rides the PR for #22128, which edits the same OCC describes next.Landing to-do:
Clause-②: yesand the path limb both owe a## Contract reviewatCONTRACT_REVIEW_TIERon the landing head. It is dispatched now, withneeds:contract-reviewon the PR in the same act.check-governed-merges --pr 22126,pr_readyand auto-merge.objectstack-fleet commentedon Oct 8, 2026
Landed: PR #22126 →
8f2e808117. The card closescompleteddomain:specseat 3 (#18883) ·os-justin· sessionsession_01RPo7FUd6bSnAfkWMAKi848· 2026-10-08T03:40Z · holder of claim6047228321.8f2e808117. It has one parent,6c17a5018, and is an ancestor oforigin/main.e2a28fadf1(ACCEPT6051199442; contract review PASS6051314809).content/docs/references/api/protocol.mdx. This PR's own 28 changed lines landed identically (its diff from the merge base against the squash's diff from its parent), and the one other line ismain's.GET /meta/:type/:nameservesversion, the keyed token of the row a save to that address compares against, from the one producer the save receipt uses. It isnullwhere no row is stored there, and absent on the cached and?preview=draftarms.PUTtakesIf-None-Match: *to save only where no row exists, and refuses a non-*value, or the header besideIf-Match, with400.METADATA_CONFLICTof the four item write doors carriescurrentVersion(MetadataConflictErrorSchema).minorfor@objectstack/spec,@objectstack/metadata-protocol,@objectstack/restand@objectstack/types, withClause-②: yes (narrowing), a BREAKING paragraph and ADR-0087not-required (no-migration-prescription).Carried elsewhere:
?package=allread folded in by6051335917) is now claimable.@objectstack/honocatch-all'sPUT /metaignores both headers and?mode=draft) is with triage.This act removes
pm:dispatchedfrom the closed card.domain:spec,area:studioand the type label stay.feat(meta): the /meta item read serves the version token, If-None-Mat…
fix(metadata-protocol): one head row per /meta write address — a seco…
test(spec): the class (e) remainder's titles state each cited decisio…