Skip to content

Commit 31baf09

Browse files
claude[bot]claude
andauthored
docs(kernel): document the plural reads' failure posture on the metadata-service contract page (#16261)
`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. Claude-Session: https://claude.ai/code/session_01Vbw3RPgdtqesx4azk9SbW8 Co-authored-by: Claude <noreply@anthropic.com>
1 parent e6279dc commit 31baf09

1 file changed

Lines changed: 46 additions & 0 deletions

File tree

‎content/docs/kernel/contracts/metadata-service.mdx‎

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ export interface IMetadataService {
3131
registerInMemory?(type: string, name: string, data: unknown): void;
3232
get(type: string, name: string): Promise<unknown | undefined>;
3333
list(type: string): Promise<unknown[]>;
34+
listDiagnosed?(type: string): Promise<{ items: unknown[]; degraded: boolean; errors: string[] }>;
3435
unregister(type: string, name: string): Promise<void>;
3536
exists(type: string, name: string): Promise<boolean>;
3637
listNames(type: string): Promise<string[]>;
@@ -131,6 +132,51 @@ if (degraded) {
131132
}
132133
```
133134

135+
### list / listNames
136+
137+
The plural reads' failure posture. Both read a **set** through the same
138+
registered loaders, and — unlike the singular reads above, which collapse every
139+
fault into one `null` — they answer two different kinds of fault differently.
140+
141+
| condition | outcome |
142+
|:---|:---|
143+
| A loader cannot be read — a storage outage, an unreachable `sys_metadata`, any other throw | **Degrade** — that loader is reported once and skipped; the read resolves with what the reachable loaders hold |
144+
| One metadata name is derived from more than one file — `twin.json` beside `twin.yaml` in one type directory | **Refuse** — `AmbiguousMetadataStemError` propagates out of both reads |
145+
146+
**Degrade** is the older of the two postures and the one nothing announces to
147+
the caller: `list` and `listNames` still resolve, the caller still gets an
148+
array, nothing 500s, and the set is quietly short. `listDiagnosed` is what
149+
tells a short set apart from a complete one — it returns the same items plus
150+
`degraded` and `errors`, and `degraded` is true when at least one loader could
151+
not be read while the set was assembled. It says the set is **known-partial**,
152+
never that it is empty and never that it is wrong: a reason to withhold a claim
153+
of *completeness*, never a reason to withhold the items.
154+
155+
`listNames` has **no diagnosed counterpart**. A short name set is not
156+
distinguishable by its caller at all — the lost loader is reported at `error`
157+
in the server log and nowhere else.
158+
159+
**Refuse** is an authoring error rather than an outage, so it is deliberately
160+
not absorbed by the degrade seam above. The filesystem loader derives a
161+
metadata name by stripping the extension from a flat file's basename, so two
162+
files under one type directory sharing a stem produce one name that is listed
163+
twice while only one of them is reachable under that name. Instead of picking a
164+
winner by extension precedence, the loader throws, and both plural reads
165+
re-raise it. The error carries the ADR-0112 envelope — code
166+
`AMBIGUOUS_METADATA_STEM`, status `500` (the request is well formed and no
167+
caller can fix it by sending something else; only deleting or renaming a file
168+
does), plus the metadata `type`, the `stem`, and **every** colliding path,
169+
sorted — never just the precedence winner. Catch it with
170+
`isAmbiguousMetadataStemError` from `@objectstack/metadata` wherever you need
171+
to tell it apart from an outage.
172+
173+
<Callout type="info">
174+
Only stems the loader would actually resolve collide: the comparison is
175+
case-sensitive, it covers just the extensions whose serializers are registered
176+
(`.js` is not in the default set), and a nested file sharing a flat file's
177+
basename is not a collision.
178+
</Callout>
179+
134180
### register / unregister
135181

136182
`register` saves (creates or replaces) the full definition for a `(type, name)`.

0 commit comments

Comments
 (0)