Skip to content

fix(cave): read the familiar roster in Cave's contract spelling - #342

Merged
BunsDev merged 1 commit into
mainfrom
fix/familiar-roster-contract-spelling
Oct 10, 2026
Merged

BunsDev merged 1 commit into
mainfrom
fix/familiar-roster-contract-spelling

Conversation

@BunsDev

@BunsDev BunsDev commented Oct 10, 2026

Copy link
Copy Markdown
Member

Observable behaviour

Against a real Cave with at least one familiar, credentialStatus() on a discovered client fails with invalid_response ("familiar.display_name must be a non-empty string") right after a successful exchange, and familiars() fails the same way. The managed native transport's familiars read shares the parser. listFamiliars() and the other canonical reads were already correct.

The discovered and managed transports parse GET /api/client/v1/familiars with the earlier snake_case field names (display_name, last_seen, active_sessions), while Cave has served the documented camelCase roster (displayName, lastSeenAt, activeSessions; docs/api/client-v1.md) since its canonical read projections landed. The unit tests fed this parser an empty roster, which is how the mismatch escaped.

Change

parseFamiliar reads the contract spelling first and keeps the snake_case spelling as a fallback for an older Cave. The record still leaves the parser in the wire type's spelling, so CaveFamiliarWire, CaveFamiliar, toFamiliar and every public type are unchanged.

Compatibility

Patch for @opencoven/cave-client (changeset included). No API change. A custom transport returning the snake_case roster keeps working.

Validation

  • New test in tests/cave-discovery-pairing.spec.ts: after a discovered exchange, the documented camelCase roster (one entry with every optional field, one with only the guaranteed three) makes credentialStatus() report valid / chat:read and familiars() return the mapped records with neither spelling leaking; a roster entry with no name under either spelling is still invalid_response. The test fails on main.
  • corepack pnpm@10.34.0 verify: typecheck, build, 92 test files (3155 tests) passed; the remaining stages were running at the time of opening and CI runs the same gate.
  • Live proof: a bundle with this change plus fix(cave): send request bodies as bytes so the pairing exchange is not text/plain #341, driven against a running Cave (v0.5.12 contract, nine familiars), completes pairing and then credentialStatus(), listFamiliars() and listConversations() all succeed; without this change credentialStatus() fails as described.

Release implications

Pairs with #341: an app on the discovered client needs both to work against a current Cave. OpenCoven/mamase (which vendors the SDK) is blocked on both; see OpenCoven/mamase#126.

🤖 Generated with Claude Code

The discovered and managed transports parsed GET /api/client/v1/familiars
with the earlier snake_case field names (`display_name`, `last_seen`,
`active_sessions`). A real Cave has served the documented camelCase roster
(`displayName`, `lastSeenAt`, `activeSessions`) since its canonical read
projections landed, so `credentialStatus()` and `familiars()` refused
every non-empty roster with `invalid_response` ("display_name must be a
non-empty string"). A freshly exchanged credential therefore read as
broken the moment the Cave had one familiar. The unit tests fed the
parser an empty roster, which is how this escaped.

Read the contract spelling first and keep the snake_case spelling as a
fallback for an older Cave. The record still leaves the parser in the
wire type's spelling, so the public types and `toFamiliar` are unchanged.
`listFamiliars()` and the other canonical reads were already correct.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@BunsDev
BunsDev merged commit ad930c8 into main Oct 10, 2026
8 checks passed
@BunsDev
BunsDev deleted the fix/familiar-roster-contract-spelling branch October 10, 2026 00:37
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