Skip to content

feat(client): typed CAS/approve guards, expand guard fields, list_instances cursor, as_of/scenario plumbing - #409

Merged
jrosskopf merged 1 commit into
mainfrom
client/contract-parity
Aug 15, 2026
Merged

feat(client): typed CAS/approve guards, expand guard fields, list_instances cursor, as_of/scenario plumbing#409
jrosskopf merged 1 commit into
mainfrom
client/contract-parity

Conversation

@jrosskopf

@jrosskopf jrosskopf commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

Summary

Contract-parity fixes (1/2): the typed crates (escurel-types, escurel-client) lagged the server wire surface, so typed consumers could not use documented features.

  • CAS/approve guards on typed writes: UpdatePageRequest gains base_version, require_exact_base, base_sha256, provenance — all optional-with-meaning (absent = unguarded, byte-identical wire traffic for existing callers). Some("") for base_sha256 is the approve-create sentinel and serializes as the explicit empty string. UpdatePageResponse gains auto_merged + head_version/head_sha256/head_content so a typed caller can act on a conflict refusal.
  • Guard fields on typed reads: ExpandResponse gains version (update_page: implement optimistic concurrency (base_version → merge) + monotonic versions #246) + content_sha256 (CR-2 (narrowed): atomic approve for held writes #354/feat(read): expand publishes content_sha256 — the approve-guard hash #408), serde-default and absent-tolerant, so a typed client can run the full read→hash→guarded-write loop.
  • list_instances cursor: ListInstancesRequest.cursor + client plumbing; only an absent next_cursor means done.
  • Dropped optional-with-meaning fields: the client now forwards as_of/scenario on expand/neighbours/list_instances (and scenario on resolve) — the typed requests carried them all along but they never reached the wire, silently reading current/base state.

Skill sync in the same PR: references/05-consume-from-rust.md (guarded-write recipe, cursor loop, as_of/scenario), skill CHANGELOG 0.6.27, VERSION bump.

Part 2 (typed shapes for fetch_blob/list_snapshots/list_op_authors/write_instance/session tools + error.data at the client boundary) follows in a stacked PR.

Test plan

Red→green against the REAL gateway (no mocks at the boundary):

  • crates/escurel-client/tests/contract_parity.rs (new, real EscurelProcess + real DuckDB + TestIssuer OIDC):
    • expand_hash_feeds_guarded_update_and_stale_hash_conflicts — expand publishes content_sha256; guarded update_page with the fresh hash lands; the stale hash refuses code: conflict with head_sha256/head_content; re-read hash matches head (live-CRDT gateway).
    • stale_base_version_with_require_exact_base_conflicts — strict version-CAS conflicts on a stale base, reports head_version, persists nothing, never auto-merges.
    • list_instances_paginates_to_exhaustion — 5 seeded instances, limit: 2, cursor-follow to exhaustion; every row exactly once; ≥3 pages.
    • expand_forwards_as_of / neighbours_forwards_as_of / list_instances_forwards_as_of / search_forwards_as_of — a pre-dating as_of cut hides the timed fixtures (these were RED at runtime before the client plumbing: the fields were silently dropped).
  • crates/escurel-types/tests/wire_contract.rs: update_page_request_guard_wire_semantics (absent guards omitted; Some("") sentinel), update_page_conflict_response_wire_shape, expand_response_guard_fields_wire_shape, list_instances_cursor_wire_shape.

Gate (un-piped exit codes): cargo fmt --check = 0, cargo clippy --workspace --all-targets -- -D warnings = 0, cargo test --workspace --all-targets = 0 (101 suites ok), cargo build --workspace --release = 0.

🤖 Generated with Claude Code


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

…tances cursor, as_of/scenario plumbing

Contract-parity fixes: the typed crates lagged the server wire surface,
so typed consumers could not use documented features.

- UpdatePageRequest gains the optional-with-meaning write guards the
  server has accepted since #246/#354: base_version, require_exact_base,
  base_sha256 (Some("") = approve-create sentinel, serialized as the
  explicit empty string; None = unguarded, omitted from the wire) and
  the provenance passthrough. UpdatePageResponse gains auto_merged +
  head_version/head_sha256/head_content so a typed caller can act on a
  conflict refusal.
- ExpandResponse gains version + content_sha256 (serde-default,
  absent-tolerant), closing the read->hash->guarded-write loop end to
  end through escurel-client.
- ListInstancesRequest gains cursor; the client forwards it and the
  documented "only an absent next_cursor means done" resume works.
- The client stopped dropping as_of/scenario on expand / neighbours /
  list_instances (and scenario on resolve) - the typed requests carried
  them all along but they never reached the wire.

Skill sync (same PR): references/05-consume-from-rust.md documents the
guarded-write recipe + cursor loop; skill CHANGELOG 0.6.27 + VERSION.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@jrosskopf
jrosskopf force-pushed the client/contract-parity branch from 219275c to 95d883b Compare August 15, 2026 08:32
@jrosskopf
jrosskopf merged commit bdc3dc3 into main Aug 15, 2026
5 checks passed
@jrosskopf
jrosskopf deleted the client/contract-parity branch August 15, 2026 08:56
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