Skip to content

Structured error codes: stable enum through the FFI error callback - #39

Merged
adamski merged 2 commits into
mainfrom
adam/structured-error-codes
Jul 14, 2026
Merged

Structured error codes: stable enum through the FFI error callback#39
adamski merged 2 commits into
mainfrom
adam/structured-error-codes

Conversation

@adamski

@adamski adamski commented Jul 13, 2026

Copy link
Copy Markdown
Collaborator

Consumers currently detect credential rejection by substring-matching the free-form error message — the C++ consumer clears stored credentials off strstr. The library must own this knowledge.

  • ReplicantErrorCode: #[repr(i32)] enum with explicit stable values, banded by consumer action — 1xxx credential rejected (clear + re-enroll), 2xxx transient (retry, never clear — includes the timestamp/clock-skew reasons), 3xxx protocol, 4xxx identity drift (refuse sync, don't clear), 0 unknown. Exported through the pinned cbindgen; band helper replicant_error_is_credential_rejection() over FFI so bindings never re-implement band logic.
  • Error callback ABI: (event_type, int32_t error_code, const char* message, ctx) — code and message together. Breaking; next release is 0.5.0.
  • Join-reject reasons parsed structurally from the phoenix rejection payload (ChannelJoinError::Rejected { rejection }) via a pure per-reason mapping table, unit-tested per server reason atom. Identity-drift check emits IdentityDrift.
  • JUCE wrapper onSyncError now (int32_t, const std::string&).

Tests: workspace green (55 in replicant-client lib incl. 4 new mapping/band tests); live interop harness 28/28 with the credential_not_enrolled negative test extended to assert code 1003 through the production extractor.

https://claude.ai/code/session_01GwMbERgLK4KAuLrz4b4Liw

adamski added 2 commits July 13, 2026 21:17
Consumers currently detect credential rejection by substring-matching the
free-form SyncError message, which is fragile in both directions. Give the
library an owned, stable code so bindings can react to the action a failure
calls for.

Library:
- New ReplicantErrorCode (#[repr(i32)], explicit stable values) banded by
  action: 1xxx credential-rejected, 2xxx transient (incl. clock-skew
  timestamp reasons), 3xxx protocol, 4xxx identity drift.
- is_credential_rejection band helper + FFI
  replicant_error_is_credential_rejection(i32) -> bool so bindings don't
  re-derive the band.
- SyncEvent::SyncError and the internal QueuedEvent carry the code;
  ErrorEventCallback ABI becomes (event_type, i32 error_code, message, ctx).
- websocket.rs parses the phoenix join-rejection payload structurally
  (error_code_for_join_reject -> error_code_for_reason), mapping the server's
  {reason: "<atom>"} to a code; timeouts -> Timeout, other socket/connect
  failures -> ConnectionFailed, identity drift -> IdentityDrift.
- Other emit_sync_error sites pass the most specific code (Unknown where
  genuinely uncategorized).
- Regenerated include/replicant.h with the pinned cbindgen (=0.26.0);
  byte-stable.

Consumers/tests:
- Updated the FFI test callbacks, Rust examples, C++ examples and the JUCE
  wrapper (onSyncError now surfaces the code) to the new ABI.
- error_code unit tests (per server reason + band helper); interop
  credential test now asserts the derived code is CredentialNotEnrolled.

Callback ABI break; version bump deferred to release.

Claude-Session: https://claude.ai/code/session_01GwMbERgLK4KAuLrz4b4Liw
@adamski
adamski merged commit 07af077 into main Jul 14, 2026
4 checks passed
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