docs(kernel): document the plural reads' failure posture (degrade vs refuse) on the metadata-service contract page - #16261
Merged
baozhoutao merged 1 commit intoSep 6, 2026
Conversation
…ata-service contract page `content/docs/kernel/contracts/metadata-service.mdx` documented the SINGULAR read's failure posture (`### load / loadDiagnosed`: `load` collapses "no loader has this item" and "every loader failed" into one `null`) and said nothing about the plural reads'. `list` / `listNames` have two distinct outcomes and the page distinguished neither: - degrade — a loader that cannot be read is reported once and skipped, the read resolves over the remaining loaders (`MetadataManager.readListUncached`, `listNames`); `listDiagnosed` carries the `degraded`/`errors` verdict and `listNames` has no diagnosed counterpart at all; - refuse — `AmbiguousMetadataStemError` propagates out of both plural reads (ADR-0112 envelope, `AMBIGUOUS_METADATA_STEM`, status 500, every colliding path named). Addition only: the singular passage is unchanged and the two postures now sit side by side. `listDiagnosed?` is added to the page's interface listing because the new prose names it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vbw3RPgdtqesx4azk9SbW8
This was referenced Sep 6, 2026
baozhoutao
marked this pull request as ready for review
September 6, 2026 08:54
baozhoutao
enabled auto-merge
September 6, 2026 08:54
baozhoutao
deleted the
claude/issue-16090-metadata-service-plural-read-posture
branch
September 6, 2026 09:39
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #16090
What changed
One file, addition only.
content/docs/kernel/contracts/metadata-service.mdxgains a### list / listNamessubsection placed immediately after### load / loadDiagnosed, so the singular and the plural failure postures sit side by side and a reader can see whyloadanswersnullwhilelistNameseither degrades or refuses. The singular passage is untouched — the card's own framing is that silence is not falsehood, so nothing here rewrites what was already correct.Also in the same file and the same claim:
listDiagnosed?is added to the page's## Interface Definitionfence, because the new prose names it and the fence omitted it. Declared explicitly rather than slipped in — see Scope below.Every claim in the passage, and where it was measured
Read off
origin/main@0e16fc45(this branch's base); the card's table was used as a lead, never transcribed.MetadataManager.readListUncached(packages/metadata/src/metadata-manager.ts:1172-1190) andlistNames(:1627-1645) — bothcatchper loader and call the sharedreportLoaderReadFailure;list's own docblock: "a loader that cannot be read is reported once and skipped … so this resolves with what the reachable loaders hold rather than throwing"listNames' docblock (:1608-1611), which grades the log level on exactly that sentencelistDiagnosedreturns the same items plusdegraded/errors;degradedis true when at least one loader could not be read; the set is known-partial, never empty and never wrongMetadataManager.listDiagnosed(:1010-1014) and the contract TSDoc inpackages/spec/src/contracts/metadata-service.ts:378-412listNameshas no diagnosed counterpartlistNamesDiagnosedoccurs zero times in the repo;IMetadataServicedeclareslistNameswith no diagnosed twin (:435-440)AmbiguousMetadataStemErrorpropagates out of both plural readsreadListUncached(:1183) andlistNames(:1638), each guarded byisAmbiguousMetadataStemError; pinned byfilesystem-loader-ambiguous-stem.test.tsPIN 1 ("MetadataManager.listNames()PROPAGATES rather than absorbing" / "MetadataManager.list()propagates too")AMBIGUOUS_METADATA_STEM, status500, plustype,stem, and every colliding path, sortedpackages/metadata/src/loaders/ambiguous-metadata-stem.ts:52(code),:63(status, with its own reason for 500 rather than 503 or 4xx),:76-105(fields); pinned in PIN 1FilesystemLoader.nameFromFilename/resolvableNameForPath/resolvableNames(filesystem-loader.ts:549-627) and the module doc onambiguous-metadata-stem.ts.jsnot in the default set); a nested file sharing a flat basename is not a collisionresolvableNameForPath's "Case-SENSITIVE on purpose" comment (:570-574); the two CONTROL cases infilesystem-loader-ambiguous-stem.test.ts("dual.json+dual.jsis NOT ambiguous under the default set", "a NESTED file sharing a flat name is not a collision")isAmbiguousMetadataStemErroris importable by a consumerpackages/metadata/src/index.ts:28-33alongside the code and status constantsPremise re-check (A3): still valid
The page at
:113-125still opens "Both read one item through the registered loaders" and still states thenullcollapse. The passage was placed beside it, not over it.What the code measured that the card did not say
The card's table has one row for degrade. The code has an asymmetry inside that row, and the passage documents it:
listrecords the degradation and offers it throughlistDiagnosed, whilelistNameshas no diagnosed counterpart at all — its lost loader reaches the server log and nothing else. That is a fact about the contract a plural-read caller has to know, and it is why the passage is prose plus a table rather than the table alone.Two things deliberately not claimed: what the singular
loaddoes on an ambiguous tree (no pin asserts it, andfindFileis a different code path — unmeasured, so unwritten), and any totality claim for the plural reads beyond what the seams above do.Gates
38 derived, 38 run, 0 NOT-MEASURED, 0 UNRUN — reconciled mechanically, not by hand:
All 38 exited 0 at HEAD
6dad5a168, exit codes captured before any pipe. Sample verdict lines:check-doc-anchors: 313 internal #fragment link(s) across 408 source file(s) all resolve to a real heading;check-docs-transcript-drift: 4 declared transcript value(s) across 403 page(s) … equal what the registry derives today;check-nul-bytes: OK (scanned 7960 text file(s) … no raw ASCII control bytes);✅ 228 generated files in sync with packages/spec.check:docs-transcript-driftfirst answeredPREREQUISITE NOT MET(exit 3 — NOT MEASURED,@objectstack/lintunbuilt); it is green above afterpnpm --workspace-concurrency=2 --filter '@objectstack/spec...' --filter '@objectstack/lint...' --filter '@objectstack/client-react...' buildunder the shared verify lock (VERDICT command-exit 0).Repo-wide
pnpm lintis not owed here, and that is a measurement rather than a skip. eslint has no configuration matching this file: every config object ineslint.config.mjsscopesfilesto{ts,tsx,mts,cts,js,jsx,mjs,cjs}, the literal stringmdxoccurs 0 times in it, andeslint --no-inline-config --format json content/docs/kernel/contracts/metadata-service.mdxreturns 1 result, 0 errors, and the single warning "File ignored because no matching configuration was supplied." Since the diff touches no linted file at all, no untouched file's verdict can move.Scope
git diff --name-only 0e16fc45is exactly one path:content/docs/kernel/contracts/metadata-service.mdx. Nopackages/**edit — this documents behaviour that is already onmain(PR fix(metadata): refuse an ambiguous metadata stem with both paths named, instead of listing one name twice and serving it by extension precedence #16086, merged 2026-09-06T03:25Z).listDiagnosed?line in the## Interface Definitionfence is outside the "a passage beside### load / loadDiagnosed" location the dispatch described, though inside the declared file. It is one mechanical line copied from the contract source, and it exists so the new prose does not name a member the page's own listing hides. The wider drift it exposes was not fixed here and is filed separately (see below).## Interface Definitionblock on metadata-service.mdx under-declaresIMetadataService— 4 members declared in the contract source are absent from the page's listing #16255 — the same fence omits four membersIMetadataServicedeclares (getDiagnosed,loadMany,matchEndpoint,subscribe). Not repaired here; the repair is a scope question for triage.MetadataManager.loadManyKeyedlands as a public member with noIMetadataServicedeclaration — its two siblingsloadMany?andloadDiagnosed?are declared, and the call site narrows with a local structural type instead #15385 remains open and writes this same file if its option 1 is taken — a hard serial noted on both cards, and now on docs: the## Interface Definitionblock on metadata-service.mdx under-declaresIMetadataService— 4 members declared in the contract source are absent from the page's listing #16255 too.skip-changeset, applied at PR open.Generated by Claude Code