Skip to content

restAdapter — the reference SyncAdapter speaks wire protocol v1 (DAN-780) - #24

Merged
Danny-Devs merged 4 commits into
mainfrom
tackle/DAN-780-rest-adapter
Aug 3, 2026
Merged

restAdapter — the reference SyncAdapter speaks wire protocol v1 (DAN-780)#24
Danny-Devs merged 4 commits into
mainfrom
tackle/DAN-780-rest-adapter

Conversation

@Danny-Devs

Copy link
Copy Markdown
Owner

What

restAdapter(options) — the first real SyncAdapter: the client half of docs/protocol/sync-wire-protocol-v1.md. POSTs the §7 bodies to the wire-protocol.ts path constants (cursor in the body, never the URL — §2/C5), routes every status decision through classifyWireStatus (§8), throws the coordinator's own SchemaVersionError on 409 so D12 outbox suspension fires on the exact seam the coordinator detects, validates required response fields loudly on both channels, and passes unknown fields through untouched (§3, both halves). Deliberately absent: subscribe() (v1 defines no live channel), a comparator (server-authoritative reference), and any predicate surface (rev c C2-1 / DAN-676 — held closed at compile time by @ts-expect-error probes that fail typecheck the day a where/filter/query slot appears).

Unexported from index.ts (ADR-022 lines 1-2). Protocol §14 updated: v1 stays DRAFT — the freeze trigger is the first export/publish of the sync surface, not this in-tree landing.

Discretion calls (as granted by the packet)

  • Stub transport: injected fetch-handler returning real Response objects with genuine JSON bodies and real status codes — chosen over node:http for determinism; nothing under test lives below fetch, and serialization + status classification are fully exercised.
  • Options shape: { baseUrl, headers? (static record or async callback — supports token refresh; auth never enters the protocol per §4), fetch?, schemaVersion? }. Caller header keys are lowercased before merging so a canonical Content-Type overrides the default instead of duplicating into a joined header value.
  • Stub location: inline in the spec file; test fixture only — explicitly NOT ADR-023's server conformance kit (artifact 2) nor the reference server (artifact 3).

Gate evidence

  • Packet verification command: PASSED exactly as written (exit-code chain unlaundered).
  • CI=true pnpm -r test: 630 passed, 2 skipped at root (pre-existing sqlite-wasm pair) + 29 in packages/mcp. Suite 566 → 630.
  • pnpm -r typecheck / build / lint: green.
  • rest-adapter.spec.ts: 64 named passing tests, zero skips — every kit hook supplied (12 of 13 adapter obligations exercised; the live-channel one is satisfied by the deliberate absence of subscribe()).
  • Observe-run: the integration test drives the real flow — enableSync ↔ restAdapter ↔ stub v1 server: local write pushes over HTTP, pull's confirmedMutations drops the overlay, server-seeded change applies under sync-pull, nothing echoes.

Watched to fail (red-then-green, per house law)

  1. 409 → plain Error instead of SchemaVersionError: D12-seam test red — only this file's test caught it; the kit's D12 block accepts any throw.
  2. All non-schema statuses forced permanent: five transient-status tests red.
  3. Cursor moved to the URL / dropped from the body: wire-shape tests red.
  4. Pull result rebuilt field-by-field (stripping unknowns): §3 pass-through + kit subscription-echo tests red.
  5. Missing-cursor validation removed: malformed test red.
  6. Stub omitting confirmedMutations: integration overlay-drop red — the D1 behaviour proven end-to-end.
  7. where?: string added to the options type: pnpm typecheck fails via the now-unused expect-error probes.

Plus 11 review-round tests (6 in round 1, 5 in round 2), each watched red against the unfixed adapter before its fix.

Review verdict

Two adversarial fresh-context rounds, both with findings, all fixed in-branch:

  • Round 1 (pre-hand-off): 4 validation gaps on the pull channel + header casing (c51758f).
  • Round 2 (landing gauntlet, executed repros through the real coordinator): the sibling-branch twins on the push-verdict channel — version: null stamped into the version map (silent permanent divergence), truthy non-object transform data corrupting the store + reject shadow, remappedId: "" landing corrections under the empty id (f152b05).

Flagged, not fixed (out of scope): §7's reset example sends "cursor": null while the frozen type says cursor?: string — reconcile before v1 freezes; and the kit's live-channel test early-returns inside its it (pre-existing, DO-NOT-TOUCH here).

Linear: DAN-780

🤖 Generated with Claude Code

Danny-Devs and others added 3 commits August 2, 2026 18:08
…col v1 (DAN-780)

The client half of the HTTP contract: §7 bodies to the wire-protocol path
constants, every status decision routed through classifyWireStatus, 409
throwing the coordinator's own SchemaVersionError so D12 suspension fires
on the exact detection seam, loud validation of required response fields
with §3 pass-through of unknown ones, and no subscribe/comparator/predicate
surface by design (rev c C2-1, DAN-676).

Proven, not definitionally, conformant: runSyncAdapterContract against a
minimally §13-conformant stub server with every hook supplied (zero skipped
blocks), wire-mapping tests for the layer the kit cannot see, and an
end-to-end enableSync ↔ restAdapter ↔ stub integration — the first time
Stage 3 speaks HTTP. Seven mechanisms watched to fail. Suite 566 → 619.

Unexported (ADR-022 lines 1-2); protocol §14 updated — v1 stays DRAFT, the
freeze trigger is the first export/publish of the sync surface.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…fresh-context review

A set change's data is now required to be an entity object (null/absent data
would pass the coordinator's falsy write-skip while the version still got
stamped — permanent silent divergence on the re-delivered payload);
confirmedMutations values must be numbers (a null seq passes '!== undefined'
and pins the overlay forever — the silent half of D1); a reset's cursor must
be string-or-null (the coordinator would adopt anything via '?? null'); and
caller headers are lowercased before merging so a canonical Content-Type
overrides the default instead of duplicating it into a joined header value.

All six new tests watched red against the unfixed adapter first. Also
tightened the 'zero skipped blocks' claim: 12 of 13 obligations exercised,
the live-channel one satisfied by the deliberate absence of subscribe().
Suite 619 → 625.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…d 1's pull-side fixes (landing gauntlet)

toPushResult now validates what the coordinator trusts: a verdict's version
must be string|number when present (version:null would be stamped into the
version map, where every later HLC-string token classifies lexicographically
older than 'null' — server truth silently dropped forever); transform data
must be an entity object (a truthy string reaches store.replace and poisons
the reject re-base shadow); remappedId must be a non-empty string ('' is
non-nullish so it becomes the target id, yet falsy so the remap block skips —
the correction lands under id ""). Each corruption path was executed through
the real coordinator by the gauntlet reviewer before fixing.

Also: a non-JSON 200 body now throws the adapter's loud malformed error
instead of a bare SyntaxError, and the protocol doc's updated: date is
corrected to today. All five new tests watched red first. Suite 625 → 630.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@Danny-Devs, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 59 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 44ec8791-47fb-412d-84e3-d7368cc1c663

📥 Commits

Reviewing files that changed from the base of the PR and between eb23db9 and ecf2481.

📒 Files selected for processing (6)
  • CHANGELOG.md
  • docs/adr/006-sync-adapter-interface.md
  • docs/adr/023-the-sync-wire-protocol-is-versioned-in-the-path.md
  • docs/protocol/sync-wire-protocol-v1.md
  • src/rest-adapter.spec.ts
  • src/rest-adapter.ts
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch tackle/DAN-780-rest-adapter

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

…col client half exercised (ADR-023)

Land-step-7 decision-loop closure: the field is what makes
'grep Implementation:' measure the conceived-to-shipped ratio.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@Danny-Devs
Danny-Devs merged commit dbd9656 into main Aug 3, 2026
13 checks passed
@Danny-Devs
Danny-Devs deleted the tackle/DAN-780-rest-adapter branch August 3, 2026 01:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant