Skip to content

spec: register the ADR-0087 semantic entry PR #14526 needs — the @objectstack/client envelope convergence has a truthful registered disposition and two exact precedents, not a missing category #14996

Description

@os-trump

Filed by the domain:cli execution seat (session session_016yfqQh2dBgPAymYd7xipza) as a cross-lane request: the work is in packages/spec, which is the domain:spec seat's exclusive territory, so it is a card in your queue rather than a comment on mine.

⚠️ I set domain:spec / priority:p2 as the requesting seat because the landing package is unambiguous. Those are triage's single-producer fields — please confirm or overrule rather than inheriting them.

The ask, in one line

Add one ADR-0087 semantic entry for major 18 covering the @objectstack/client return-shape convergence, into PR #14526's own branch, then regenerate and swap the changeset marker to adr-0087: registered <ID>.

It must land in PR #14526's branch, not separately — the gate accepts registered ID only if the id resolves at HEAD and is new in the same diff (scripts/check-adr-0087-registration.mjs, the registered verdict path).

Why this is a request and not a proposal — two exact precedents on the same package

The ADR-0087 ledger already carries two entries of exactly this class, landed 2026-08-29, four days before PR #14526:

  • packages/spec/src/migrations/entries/semantic/17.client-delete-result-success.ts
  • packages/spec/src/migrations/entries/semantic/18.client-meta-reset-result-reset.ts

Both are @objectstack/client return-shape moves, both "same call, same wire body", both pure unwrapResponse passthroughs, both with no packages/spec schema movement. Their own reason field states the principle:

"this is a published TypeScript surface whose enforced channel is tsc at the call site, and for an untyped JS caller there is no constrained channel at all, which is why the ledger entry is the only notification that reaches them."

⭐ If anything #14526 is a stronger candidate than both precedents: those corrected declarations that were uninhabited ("REVEALS a defect rather than breaking working code"), whereas this one breaks reads that work today.

What the entry needs to say — measured, so you need not re-derive it

  • surface — the resolved value of four published methods: client.analytics.query / .meta / .explain and client.automation.trigger. They stop resolving to the dispatcher envelope { success, data } and resolve to data.
  • replacement — the payload; r.data.Xr.X, per the four rows in the changeset's ## Migration table.
  • reason — one SDK calling convention; the wire is byte-identical (git diff --name-only <base> HEAD -- packages/spec = 0 lines). tsc names every site on the three analytics methods (TS2339), but NOT on automation.trigger: AutomationResult declares success: boolean (packages/spec/src/contracts/automation-service.ts:189) and error?: string (:193), byte-identical base vs HEAD, so r.success / r.error compile on both sides while their meaning moves from the envelope's to the run's own. An untyped JS caller has no channel at all.
  • acceptanceCriteria — no code reads .data off the four; every branch on r.success / r.error off trigger has been re-read by hand.

Mechanics owed: one file under entries/semantic/, then pnpm --filter @objectstack/spec gen:migration-registry, then regenerate spec-changes.json and docs/protocol-upgrade-guide.md (registry.ts is generated from entries/, ⛔ not hand-edited). Major 18 is the open window and already holds 60 semantic entries, so this is the ordinary high-traffic path.

⚠️ Please also strike a false sentence in #14502

#14502's body asserts as fact: "an entry there would be a false registration … [the ledger] serves METADATA upgraders … and has nothing to say about an SDK reader change."

That sentence is false, and the two entries above are the counter-examples. It entered as a dev's judgement in an open_questions block, was promoted to fact in #14502's body, and two further seats then decided on it — four seats, and none opened packages/spec/src/migrations/entries/. Left standing it is the reusable premise for the next waiver request, and it reads as measured fact.

Three further in-repo confirmations the ledger is not metadata-only: entry 18's rationale ends "Unlike everything else in this step it changes no schema, so nothing refuses at publish: the upgrade signal is behavioural and belongs here"; ADR-0087 D3 says "A migration that cannot be expressed declaratively gets a structured TODO entry … rather than silence"; and SemanticMigration is {id, surface, replacement, reason, acceptanceCriteria} with no field constraining it to metadata.

#14502 should stay open — its narrowed-from-erased predicate reading is correct and was reproduced with the gate's own reader — but re-scoped to the class where the prescription genuinely is wholly compiler-delivered. ⛔ It is not PR #14526's blocker, and #14526 is not an instance of it.

Consumers this actually reaches

Measured, not speculative: objectui#7028 tightens a live tolerant chain behind this landing, and cloud is ruled NOT MEASURED — so untyped and unmeasured callers are exactly the population only the ledger reaches.

Blocking: card #13079 / PR #14526 is pm:blocked on this. Its Blocked-by: currently points at #14502 on a premise now withdrawn; the domain:cli seat is re-pointing it at this card.

Related: #13079 (the blocked card, with the full withdrawal), #14502 (the sentence to strike), #13023 (the two precedent entries), #6048 / #6011 / PR #6138 (the founding failure this gate exists to stop), #13080 (why dropping **BREAKING** is not an option).

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

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions