Skip to content

ADR-0087 type-surface-only describes "the only thing that moved is a type annotation", so it has no word for a narrowing whose diff ALSO changes the body — which is the only way some erased returns can be bound at all #15674

Description

@os-litant

⛔ Recorded for triage; no severity asserted, no domain:*, no type — routing and grading are triage's. Found while binding oauth.applications.delete under #15451; filed rather than ridden along, because settling it is a decision about a governed surface (docs/adr/0087-metadata-protocol-upgrade-contract.md and the gate that enforces its vocabulary).

The shape

ADR-0087's sixth category, added 2026-08-30 under #13080, is described in the ADR and in the gate header as

a published SDK method whose declared return moves off any onto the contract it always answered

and D8 justifies the exemption with "the only thing that moved is a type annotation".

That description fits a narrowing where the body is untouched. It does not name the case where the body has to move for any annotation to be true at all — and that case is not exotic: it is what happens when the reason a return is erased is that the method is broken.

Worked example, measured end to end on #15451. ObjectStackClient.oauth.applications.delete ended return res.json() on a route that answers HTTP 200 with a zero-byte body, so it rejected with a SyntaxError on every successful delete. Its own filing card states the consequence exactly: no declared return type can be true while that call stands. void becomes the wire fact only after the body changes. So binding it is:

  1. a type-surface narrowing off an erased any — predicates 1 through 4 of type-surface-only all hold on the real diff; and
  2. a runtime behaviour change from always-rejecting to resolving.

Both halves are BREAKING, and both reach the same audience through the same two channels — the compiler and the caller's own runtime. Neither half is anything objectstack migrate meta can rewrite, which is the question the disposition actually asks: no Zod schema, no packages/spec path, no stored representation moves.

Why this is not already covered

Why it matters rather than being a wording nit

The erosion #13080 was filed about is a class of genuinely breaking change being nudged away from the **BREAKING** token because the token routes it into a ledger it does not belong in. This is the same pressure one notch further along, and the exits are the ones that ADR already closed:

⇒ Three exits, each closed by something already written down. That is the finding.

Options a decision could take (not a recommendation to any of them)

  • A. Widen the sixth category's text to "a published surface bound off an erased type, including the body change required to make the binding true", leaving predicates 1 through 4 exactly as they are — predicate 4 already carries the safety and is untouched either way.
  • B. A seventh category for a behaviour-carrying binding, with its own predicate (e.g. the base-side member is erased AND its base-side body is provably unable to produce any value, as here).
  • C. Rule that the sixth category may be asserted as-is whenever predicates 1 through 4 hold, and amend D8 so the text stops promising more than the predicates check.

⚠️ Any of these edits docs/adr/**, a governed surface — maintainer's, by hand.

Reproduce

gh pr diff <the #15451 PR>            # the diff both halves are read off
node scripts/check-adr-0087-registration.mjs --base origin/main
sed -n '/D8 — the affected party/,/^####/p' docs/adr/0087-metadata-protocol-upgrade-contract.md

Refs: #15451 (where it was measured) · #13080 (the sixth category) · #14502 (the mirror shape) · #15627 (the mechanical half) · PR #15445.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions