Skip to content

Commit 4f3d232

Browse files
docs(mcp): diagnoseEmptyRead 的 TSDoc 更正一句被证伪的事实 —— MetadataFacade.getObject 就是 get('object', n) (#6948)
* docs(mcp): correct diagnoseEmptyRead's falsified claim about MetadataFacade.getObject (#6724) The TSDoc justified its extra miss-path read with two grounds; the factual one was false. SchemaRegistry.getItem special-cases 'object'/'objects' straight back to getObject, so the facade's get('object', n) resolves through the same lookup and the item?.content ?? item unwrap is a no-op (a merged ServiceObject has no content key). Measured: identical object reference on a hit, both undefined on a miss — pinned across all three implementations by PR #6839, and documented on IMetadataService.getObject by PR #6723. Keeps the still-valid ground (no documented equivalence at #6055 time, so presuming one at a consumer is the private dialect PD #12 forbids) and states explicitly that this correction does not decide whether the resolver changes. Comments only, zero behaviour change. Same claim restated in mcp-server-runtime.metadata-outage.test.ts is corrected with it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017uFVNMmTxLpmfQYiuKM1Yx * docs(mcp): note the residual-risk bullet is now a contract violation, not a live divergence (#6724) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017uFVNMmTxLpmfQYiuKM1Yx * docs(mcp): keep the quoted identifier on one line in the outage-test comment (#6724) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017uFVNMmTxLpmfQYiuKM1Yx --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent a2443e3 commit 4f3d232

3 files changed

Lines changed: 75 additions & 12 deletions

File tree

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
---
2+
"@objectstack/mcp": patch
3+
---
4+
5+
docs(mcp): `diagnoseEmptyRead` 的 TSDoc 更正一句被证伪的事实 (#6724)
6+
7+
`packages/mcp/src/mcp-server-runtime.ts``diagnoseEmptyRead` 的 TSDoc(#6055
8+
由 PR #6051 落地)为"在空答案之后再跑一次仅取结论的探针,而不是把
9+
`getObject` 换成 `getDiagnosed('object', name)`"这个设计选择给出了两条理由。
10+
其中一条是事实陈述,而它是**错的**:
11+
12+
> `MetadataFacade.getObject`(objectql)返回 `registry.getObject(name)` —— a
13+
> different shape from its own `get()`,因此等价关系在一般情况下不成立。
14+
15+
`SchemaRegistry.getItem``'object'` / `'objects'` 类型直接特判回
16+
`getObject`,所以 facade 的 `get('object', n)` 走的是同一次查找;其后的
17+
`item?.content ?? item` 解包是空操作 —— 合并后的 `ServiceObject` 根本没有
18+
`content` 键。实测:命中时两个成员交回**同一个对象引用**,未命中时双方都是
19+
`undefined`。三个已发布实现由 `packages/objectql/src/
20+
metadata-service-getobject-equivalence.test.ts`(PR #6839)钉住,契约侧的
21+
`IMetadataService.getObject` 自 PR #6723(#6505)起也写明了这条等价关系。
22+
23+
同一句话在 `mcp-server-runtime.metadata-outage.test.ts` 里被复述过一次,一并
24+
更正。
25+
26+
仍然成立的那半条理由被保留:`getObject``IMetadataService` 自己的成员,
27+
#6055 当时它并**没有**被文档化的等价关系,在消费端擅自假定一条正是 Prime
28+
Directive #12 禁止的私有方言 —— 所以解析器当初没有被换掉。
29+
30+
**纯注释,零行为变化。** 这次更正****主张把解析器换成
31+
`getDiagnosed('object', name)`:那是一次独立的判断,由接手的人按其自身利弊
32+
去做,本次改动既不作出也不预设。

packages/mcp/src/mcp-server-runtime.metadata-outage.test.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -282,9 +282,18 @@ describe('object_schema resource — a metadata outage is not "Object not found"
282282
const svc = holding(OBJECT);
283283
await buildObjectSchemaResource(svc, 'acct');
284284

285-
// `getObject` stays the resolver (it is its own contract member, and
286-
// `MetadataFacade.getObject` is NOT `get('object', name)`); the diagnosed
287-
// read is a verdict probe on the MISS path only.
285+
// `getObject` stays the resolver: it is its own contract member, and #6055
286+
// declined to presume an equivalence the contract did not then document
287+
// (Prime Directive #12).
288+
//
289+
// [#6724] The parenthetical that used to sit here claimed that the facade's
290+
// `getObject` is NOT `get('object', name)`. That was false: the two hand
291+
// back the identical object on every implementation this repo ships (pinned by
292+
// `packages/objectql/src/metadata-service-getobject-equivalence.test.ts`,
293+
// PR #6839; documented on `IMetadataService.getObject` by PR #6723).
294+
// Whether the resolver should change is a separate call this correction
295+
// does not make. What this case pins is unchanged either way: the
296+
// diagnosed read is a verdict probe on the MISS path only.
288297
expect((svc as AnyRecord).getObject).toHaveBeenCalledWith('acct');
289298
expect((svc as AnyRecord).getDiagnosed).not.toHaveBeenCalled();
290299
});

packages/mcp/src/mcp-server-runtime.ts

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -157,15 +157,34 @@ async function diagnosedGet(
157157
* `object_schema` resource, whose resolver is `getObject(name)`.
158158
*
159159
* Deliberately a **verdict-only probe run after the empty answer**, rather than
160-
* swapping `getObject` out for `getDiagnosed('object', name)`. `getObject` is
161-
* its own member of `IMetadataService` with no documented equivalence to
162-
* `get('object', name)`, and the equivalence does not hold in general:
163-
* `MetadataManager.getObject` delegates to `get('object', name)`, but
164-
* `MetadataFacade.getObject` (objectql) returns `registry.getObject(name)` — a
165-
* different shape from its own `get()`. Presuming the equivalence at a consumer
166-
* is exactly the private dialect Prime Directive #12 forbids, so the resolver
167-
* is left untouched and only the *question* "could this answer be trusted as
168-
* complete?" is asked of the contract member that is declared to answer it.
160+
* swapping `getObject` out for `getDiagnosed('object', name)`. The ground is
161+
* the *contract*, not the runtime: `getObject` is its own member of
162+
* `IMetadataService`, and at the time of #6055 that member carried **no
163+
* documented equivalence** to `get('object', name)`. Presuming an undocumented
164+
* equivalence at a consumer is exactly the private dialect Prime Directive #12
165+
* forbids, so the resolver is left untouched and only the *question* "could
166+
* this answer be trusted as complete?" is asked of the contract member that is
167+
* declared to answer it.
168+
*
169+
* [#6724] This TSDoc used to offer a second, factual ground — that the
170+
* equivalence "does not hold in general", `MetadataFacade.getObject` (objectql)
171+
* returning "a different shape from its own `get()`". That claim is **false**,
172+
* and it was asserted rather than measured. `SchemaRegistry.getItem`
173+
* special-cases `'object'`/`'objects'` straight back to `getObject`, so the
174+
* facade's `get('object', n)` resolves through the very same lookup, and the
175+
* `item?.content ?? item` unwrap that follows is a no-op — a merged
176+
* `ServiceObject` has no `content` key. Measured: the two members hand back the
177+
* **identical object reference** on a hit, and both answer `undefined` on a
178+
* miss. All three shipped implementations are pinned that way by
179+
* `packages/objectql/src/metadata-service-getobject-equivalence.test.ts`
180+
* (PR #6839 for #6745), and `IMetadataService.getObject` has documented the
181+
* equivalence since PR #6723 (#6505) — so the "no documented equivalence" half
182+
* above is a fact about #6055's repo, not today's.
183+
*
184+
* Correcting the record does not decide the design question, and this note
185+
* deliberately does not make that call: whether the resolver should become
186+
* `getDiagnosed('object', name)` and shed the extra miss-path read is a
187+
* separate judgement, to be made on its own merits by whoever takes it up.
169188
*
170189
* Consequences of that choice, both acceptable and both deliberate:
171190
* - one extra read on the MISS path only (never on a hit, never on success);
@@ -174,6 +193,9 @@ async function diagnosedGet(
174193
* object that is genuinely absent. That is the conservative direction — it
175194
* withholds, it never admits — and no such host exists today
176195
* (`MetadataManager` is the only `getDiagnosed` implementation on `main`).
196+
* Since PR #6723 the contract also rules such a host out by declaration, so
197+
* this reads as residual risk against a contract violation, not as a live
198+
* divergence anyone can point at.
177199
*/
178200
async function diagnoseEmptyRead(
179201
metadataService: IMetadataService,

0 commit comments

Comments
 (0)