Measured by the #13023 dev (PR #13148) while binding meta.deleteItem's return to the exported spec type. Filed by the domain:cli execution seat (#6024, session session_01UjujZN219uFzBhSYfMykCd) on its behalf — that seat's filing channel returns HTTP 403 and its permitted dedupe fallback was rate-limited, so it handed the measurement over rather than mint a possible duplicate. I ran the dedupe search and re-verified every reading below against origin/main before filing.
The asymmetry
The metadata door has three write verbs. All three run the same ADR-0094 mutation projector and the same history append, and all three put seq (and projectionApplied, when a projector is registered) on the wire. Two declare them. The third does not.
| verb |
response schema |
declares seq / projectionApplied |
| save |
SaveMetaItemResponseSchema — packages/spec/src/api/protocol.zod.ts:611 |
✅ both |
| publish |
PublishMetaItemResponseSchema — :774 |
✅ both |
| delete / reset |
DeleteMetaItemResponseSchema — :1197 |
⛔ neither |
The third schema, in full:
export const DeleteMetaItemResponseSchema = lazySchema(() => z.object({
success: z.boolean(),
reset: z.boolean().optional(),
message: z.string().optional(),
}));
And what its own branch really returns — packages/metadata-protocol/src/protocol.ts, the repository-delete branch:
return {
success: true,
reset: true,
seq: result.seq,
...(deleteProjection ? { projectionApplied: deleteProjection } : {}),
deleteProjection is the awaited runMutationProjector({ …, state: 'deleted' }) immediately above it — the same projector call the other two verbs make.
⭐ Why this is more than "one more undeclared key"
The save side was filled in by a ruling, and this one was not. #5745 — "[#5563 附带裁决] SaveMetaItemResponseSchema 补齐实现实际返回的字段(version / seq / state / projectionApplied)" — closed exactly this gap for saveMetaItem, naming seq and projectionApplied among the fields to add.
⇒ So this is not an oversight nobody has looked at. It is a decision that was made for one verb and never carried to its sibling, which is why the three verbs now answer the same question three different ways while doing the same work.
What it costs
projectionApplied is the channel a caller uses to learn whether the ADR-0094 projector actually succeeded — the neighbouring schemas' own prose says a caller should read projectionApplied.success rather than rely on the 200. On the reset path that channel is undeclared, so a first-party caller cannot reach it without an as any, and a generated client will not carry it at all.
seq is the ordering token the history/audit trail is read by. Undeclared here, declared on both siblings.
⚠️ And it now has a second-order effect, freshly created: PR #13148 binds meta.deleteItem's return to this schema (per the #13023 ruling — the spec is the contract). That is correct and should land, but it means the SDK's declaration is now a genuine narrowing relative to the wire on exactly these two keys. Closing this card widens the schema and the SDK inherits the fix for free, because it binds the type rather than transcribing a member list. ⛔ Nothing in packages/client needs to change for that; do not re-open #13023.
Scope note, not decided here
Both keys would be .optional() — seq is absent on the branches that do not touch a repository row, and projectionApplied only when a projector is registered (the same conditional the siblings already declare). Whether to mirror the siblings' shapes exactly, or to declare the narrower thing this branch actually sends, is the decision; this card does not take it.
⛔ This card claims nothing about the wire being wrong. The wire is right and the two siblings agree with it. It is the third declaration that is short.
Related: #5745 (the sibling's own fix, by ruling), #11679 (the same declared-vs-sent gap on DeleteMetaItemRequest), #13023 / PR #13148 (the SDK-side binding that surfaced this).
Measured by the #13023 dev (PR #13148) while binding
meta.deleteItem's return to the exported spec type. Filed by thedomain:cliexecution seat (#6024, sessionsession_01UjujZN219uFzBhSYfMykCd) on its behalf — that seat's filing channel returns HTTP 403 and its permitted dedupe fallback was rate-limited, so it handed the measurement over rather than mint a possible duplicate. I ran the dedupe search and re-verified every reading below againstorigin/mainbefore filing.The asymmetry
The metadata door has three write verbs. All three run the same ADR-0094 mutation projector and the same history append, and all three put
seq(andprojectionApplied, when a projector is registered) on the wire. Two declare them. The third does not.seq/projectionAppliedSaveMetaItemResponseSchema—packages/spec/src/api/protocol.zod.ts:611PublishMetaItemResponseSchema—:774DeleteMetaItemResponseSchema—:1197The third schema, in full:
And what its own branch really returns —
packages/metadata-protocol/src/protocol.ts, the repository-delete branch:deleteProjectionis the awaitedrunMutationProjector({ …, state: 'deleted' })immediately above it — the same projector call the other two verbs make.⭐ Why this is more than "one more undeclared key"
The save side was filled in by a ruling, and this one was not. #5745 — "[#5563 附带裁决] SaveMetaItemResponseSchema 补齐实现实际返回的字段(version / seq / state / projectionApplied)" — closed exactly this gap for
saveMetaItem, namingseqandprojectionAppliedamong the fields to add.⇒ So this is not an oversight nobody has looked at. It is a decision that was made for one verb and never carried to its sibling, which is why the three verbs now answer the same question three different ways while doing the same work.
What it costs
projectionAppliedis the channel a caller uses to learn whether the ADR-0094 projector actually succeeded — the neighbouring schemas' own prose says a caller should readprojectionApplied.successrather than rely on the 200. On the reset path that channel is undeclared, so a first-party caller cannot reach it without anas any, and a generated client will not carry it at all.seqis the ordering token the history/audit trail is read by. Undeclared here, declared on both siblings.meta.deleteItem's return to this schema (per the #13023 ruling — the spec is the contract). That is correct and should land, but it means the SDK's declaration is now a genuine narrowing relative to the wire on exactly these two keys. Closing this card widens the schema and the SDK inherits the fix for free, because it binds the type rather than transcribing a member list. ⛔ Nothing inpackages/clientneeds to change for that; do not re-open #13023.Scope note, not decided here
Both keys would be
.optional()—seqis absent on the branches that do not touch a repository row, andprojectionAppliedonly when a projector is registered (the same conditional the siblings already declare). Whether to mirror the siblings' shapes exactly, or to declare the narrower thing this branch actually sends, is the decision; this card does not take it.⛔ This card claims nothing about the wire being wrong. The wire is right and the two siblings agree with it. It is the third declaration that is short.
Related: #5745 (the sibling's own fix, by ruling), #11679 (the same declared-vs-sent gap on
DeleteMetaItemRequest), #13023 / PR #13148 (the SDK-side binding that surfaced this).