Uh oh!
There was an error while loading. Please reload this page.
fix(metadata-protocol): fold diffMetaItem's type at the boundary and stop serving a history outage as an empty diff - #8921
Conversation
…stop swallowing a history outage Two independent changes in one method, both on `diffMetaItem`: - #8868: route the request through `canonicalizeMetaRequestType` instead of the `PLURAL_TO_SINGULAR` manifest map. The ninth `/meta` entry point was the last one still deriving its type key from that map, so a plural of a manifest-absent type (`fields`) reached the history query unfolded, matched no row, and answered a well-formed EMPTY diff for an item that has history. The #7894 boundary refusal now also runs here (400 INVALID_REQUEST). - #8833: the empty-bodied `catch` left `histRows` at [] and the code below read that as a real answer, so a history outage was served as 200 with an empty diff. It now routes through `rethrowUnlessMetadataStoreUnprovisioned`: a genuinely-absent table keeps its benign answer, every other read failure propagates 503 (ADR-0110 D3). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XeQRiAa7vYRVX5Fog7Zby8
…tage 503 New file protocol.diff-canonical-type-and-history-outage.test.ts pins both halves, each with a discriminating control: - the fold uses `fields` (manifest-ABSENT, so folded and raw DISAGREE) as the subject, with `views` (manifest-present) as the control and an explicit anti-vacuity arm asserting PLURAL_TO_SINGULAR still does not fold `fields`. Ablating the fold turns the subject red on its BODY (changed: []) while the control's body arms stay green — only its echo arm moves. - the 503 pins the ADR-0112 envelope (code AND status) for a non-benign read failure, with a genuinely-missing-table positive control proving the discrimination is selective rather than blanket. Also re-points the two #8798 outage pins, which asserted the swallow this change removes. Their subject (one outage, one answer, not decided by the type) is unchanged; the asserted value moved from an empty diff to the 503. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XeQRiAa7vYRVX5Fog7Zby8
…f-verb-fold-and-503
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
📓 Docs Drift CheckThis PR changes 1 package(s): 3 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
⛔ 1 release-owned page(s) also reference the affected code. These are read-only:
|
…on its docblock The method's thrown-error contract changed from "answers 200" to "may answer 400 or 503", and a contract change that lives only in an in-body comment is the declared-not-enforced shape this repo keeps paying for. The docblock is what a caller reads. States the DISCRIMINATION rather than just the code: a genuinely unprovisioned sys_metadata_history still answers a benign empty diff, and that asymmetry is the whole point of the #8833 ruling. Follows the file's existing @throws idiom (getEffectiveLock, the layered read). Docblock only — no behaviour change, no other method touched. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XeQRiAa7vYRVX5Fog7Zby8
Uh oh!
There was an error while loading. Please reload this page.
Fixes#8868
Fixes#8833
Two independent defects in one method —
diffMetaIteminpackages/metadata-protocol/src/protocol.ts— folded into one PR at themaintainer's suggestion on #8833 (comment 5302933802: "the metadata seat may
sensibly dispatch #8868 + this card as one
diffMetaItemPR (fold + 503)").They are kept legible as two separate changes in the diff.
GET /api/v1/meta/:type/:name/diffis a routed live endpoint with acaller-supplied
:type(rest-route-ledger.ts:184).1. #8868 — the canonical fold
diffMetaItemwas the NINTH/metaentry point on this URL family and the lastone still deriving its type key from
PLURAL_TO_SINGULAR, the manifest-COLLECTIONmap that #7894 moved this boundary off. It now routes through
canonicalizeMetaRequestType— the same one-line replacement #8769(
publishMetaItem) and #8819 (rollbackMetaItem) made. Three things change:field,seed,external_catalog,translation— legitimately absent from that map becausethey are not stack collections) a plural spelling stayed plural all the way into
the
sys_metadata_historyquery, matched no row, and the endpoint answered awell-formed empty diff for an item that does have history. Measured:
PLURAL_TO_SINGULAR['fields']isundefinedwhilePLURAL_TO_SINGULAR['views']is
'view', which is exactly why manifest-present types were never affected.PUT /meta/fields/<name>walks around the whole two-tier registry gate — 4 registry types have no entry inPLURAL_TO_SINGULAR#7894 boundary refusal never ran on this verb.viewesis now refused400 INVALID_REQUESTnaming both accepted spellings.The refusal stays narrow: a name reaching for no declared type (
fieldz, apossible plugin kind) is still served.
type. Now the canonical spelling — i.e. the one the readactually used. See "the one sub-question" below.
2. #8833 — the swallowed outage
The history read sat in a
trywhosecatchwas empty apart from a comment.histRowsstayed[]and the code below read that never-filled accumulator as areal answer, so a
sys_metadata_historyoutage was served as a successful 200with an empty diff — byte-identical to "these two versions are the same", with no
log line either.
Implementing the maintainer ruling as written, not re-adjudicated: the
catchroutes through the platform's existing discrimination,
rethrowUnlessMetadataStoreUnprovisioned(called, not modified).503 SERVICE_UNAVAILABLEwith thedriver error as
cause. ADR-0110 D3 — a miss and an outage are different facts.Option B (a
historyUnavailableresponse key) was declined in the ruling and isnot present here.
last path that threw here, so the outage had become silent for every type. The
ruling was made against that post-#8841 state, and I re-verified it on today's
mainbefore building: nohistoryMetaItemround trip remains in this method.Both thrown contracts are declared on the method's docblock, stating the
discrimination rather than just the status code — a caller reading the docblock
learns that an unprovisioned history table still answers benignly, which is the
whole point of the ruling.
The one sub-question the card named, answered from precedent
Should the response echo the canonical spelling or the caller's? #8769 and #8819
agree, so no third answer was minted: both reassign
requestat the top, whichmakes every downstream read of
request.typereport the canonical spelling.#8819's own header calls reporting the caller's spelling for a row addressed under
the canonical one the defect it was fixing. The two folded siblings that do echo
a
typein their response body —saveMetaItemanddeleteMetaItem— bothreturn { type: request.type }after their fold. This PR inherits thatmechanism exactly.
Verification — full union at HEAD
f9811727dGate union re-derived from the actual changed paths via
node scripts/pm/dispatch-gates.mjs, then run:check:changeset-gate-self-tests,check:objectui-changesetcheck:cross-package-test-inputscheck:durability-log-levelcheck:filter-alias-paritycheck:query-options-erasurecheck:type-check-coveragecheck:type-check-debt --re-measurecheck:engine-double-contractcheck:error-code-casing,check:nul-bytescheck-adr-0087-registration,check-changeset-no-major,check-empty-changesetAdded beyond the script's derived list (it derives from paths and cannot see
diff-content-keyed families):
check:engine-double-contract(new fake engine),check:error-code-casing,check:nul-bytes.Tests, with the consumption radius run rather than just the edited package:
@objectstack/metadata-protocol— 103 files, 1496 tests pass@objectstack/objectql— 210 files, 3675 tests pass (the consumer that went redon fix(metadata-protocol): fold publish Phase 2's object-table lookup at the producer #8867 after a green edited-package suite)
@objectstack/rest— 118 files, 1948 tests pass (owns the route)typecheckgreen for all threemetadata-protocolhides
**/*.test.tsfrom tsc and a greentypecheckcarries zero informationabout the ratchet
Second commit at HEAD
3dae00805— docblock only3dae00805adds the two@throwsentries todiffMetaItem's docblock andchanges nothing else (one hunk, 18 insertions, 0 deletions, no behaviour). Gates
re-derived for that single path and re-run at the new head:
check:cross-package-test-inputs,check:durability-log-level,check:filter-alias-parity,check:nul-bytes— all PASS. The ratchet familiesare not re-run for this commit by explicit instruction, and are structurally
unmovable by it:
check:type-check-debtcounts tsc errors,query-options-erasurecounts sites in test files, and
engine-double-contractscans doubles — acomment-only diff touches none of those inputs.
The pins are proved capable of failing
#8867 in this lane shipped a pin whose fixture made folded and raw agree, so it
could not fail. Both halves here were ablated and observed red:
Fold ablated (
canonicalizeMetaRequestTypecall removed, manifest lookuprestored) — 4 red, and the failure text is the defect itself:
plus
expected 'fields' to be 'field'on the echo and the 400 refusal goingunrefused. The
#8833arms stayed green — the two changes are independent.503 ablated (swallow restored) — the two outage pins plus the two re-pointed
#8798 pins went red (
expected null to be an instance of Error), whileTHE CONTROL: a genuinely-absent table still answers benignlystayed GREEN.That pair is what shows the discrimination is selective rather than a blanket
"any failure 503s".
One observed direction did not match my prediction, reported rather than
smoothed over. I expected the manifest-PRESENT control (
views) to stay fullygreen under the fold ablation; it went red. Cause: my first version compared whole
response objects, and the echo moves for manifest-present types too (the echo
never read the manifest map). The control has been split so each half states its
own fact — body arms (unchanged by this fix, which is the control's real claim)
and echo arm (does move) — and the comment records the measurement. Re-ablated
after the split: body arms green, only the echo arm red, at
protocol.diff-canonical-type-and-history-outage.test.ts:255.Fixture triage on the pre-existing pins
protocol.diff-dead-history-read.test.tshad two#8798pins asserting that anoutage falls through to an empty diff — exactly the limb this PR deletes. They
were re-pointed, not deleted: their subject was never "an outage is empty" but
"one outage, one answer, not decided by the type", and both arms still pin that,
now against the 503. Their stub throws a generic
Error, whichisMissingTableErrordoes not classify as benign, so it is a genuine outage underthe new rule. The benign case gets its own positive control in the new file.
Scope
Edits are confined to
diffMetaItem— the region declared at dispatch(
origin/main@8664a2c99:15911→ end of method) plus its docblock(
:15902–:15910), the latter under a narrow widening the PM authorized after Istopped and reported rather than taking it myself.
rethrowUnlessMetadataStoreUnprovisionedis called, not modified. No sibling region touched, and no
content/docs/**change:
http-protocol.mdxdocuments neither this endpoint nor any 503, sonothing there is stale.
Generated by Claude Code