Summary
Supermemory strips the NUL character (U+0000) from content server-side, so assert_provider's round-trip-fidelity case fails against the live service. The adapter is not at fault — the API alters the content before storing it — but the conformance suite is right to fail, and the driver currently makes a promise the engine does not keep.
Found by running the suite against the real https://api.supermemory.ai rather than the double.
Evidence
Not inferred from a read-back. The creation response itself echoes the mangled value — a two-letter string sent with a NUL between the letters comes back as the two letters alone:
POST https://api.supermemory.ai/v4/memories
{"memories":[{"content":"a<NUL>b","isStatic":false,"metadata":{}}],"containerTag":"..."}
201 Created
{"documentId":"...","memories":[{"id":"...","memory":"ab", ...}]}
Three characters in, two stored. The double never modelled this, because it was written from the same documentation the adapter was — which is the general hazard rather than a criticism of the double.
The failing assertion
tinymemory-conformance/src/suite/mod.rs, the newlines case (its literal carries \n, \r\n and a NUL):
supermemory: `newlines` content was mangled
left: "a\nb\r\ncd"
right: "a\nb\r\nc<NUL>d"
The suite's own comment explains why this must fail rather than be tolerated: a driver may refuse content, but silently altering it is exactly what the assertion exists to catch.
Who this does and does not affect
It does not reach OpenCompany, and saying so keeps the fix from being scoped wrongly. Its facades JSON-encode every record into content, so a NUL inside a trace, fact or chunk crosses the wire as the six ASCII characters of its JSON escape and arrives as text there is nothing to strip. Verified rather than assumed: with the provider contract failing, that same live service passes OpenCompany's full port suite — ContextStore / FactStore / MemoryStore round-trips and the (addr, label) semantics included (opencompany#1557).
It does affect any consumer that hands the driver raw text. MemoryCore::store promises that what you store is what you get back, and against Supermemory that is untrue for this character.
Options
- Document it. Note in the Supermemory adapter that NUL does not survive, and give the conformance case a driver-scoped exemption with the reason attached. Cheapest and honest, but it weakens a suite whose whole value is that it does not bend.
- Refuse it. Reject content containing NUL from the Supermemory adapter with
MemoryError::Invalid. The suite already accepts a refusal — "may refuse" is its documented alternative to storing faithfully — so this passes without weakening the assertion, and the caller learns at the boundary instead of discovering it later.
- Encode it. Escape NUL on write and unescape on read inside the adapter. Round-trip becomes true, at the cost of content in the vendor's own UI no longer matching what the caller sent, and of a scheme every other reader of that account would have to know.
Option 2 reads best to me: it keeps the contract meaning what it says, and turns silent data loss into a refusal the caller can act on. The call belongs to whoever owns the adapter's compatibility posture.
Reproducing
OPENCOMPANY_TEST_SUPERMEMORY_URL=https://api.supermemory.ai \
OPENCOMPANY_TEST_SUPERMEMORY_KEY=... \
cargo test --features tinymemory --lib live_hosted
from opencompany#1557, which added the live-service lane. Without the pair it skips, so an offline run is unaffected.
Related
Summary
Supermemory strips the NUL character (
U+0000) from content server-side, soassert_provider's round-trip-fidelity case fails against the live service. The adapter is not at fault — the API alters the content before storing it — but the conformance suite is right to fail, and the driver currently makes a promise the engine does not keep.Found by running the suite against the real
https://api.supermemory.airather than the double.Evidence
Not inferred from a read-back. The creation response itself echoes the mangled value — a two-letter string sent with a NUL between the letters comes back as the two letters alone:
Three characters in, two stored. The double never modelled this, because it was written from the same documentation the adapter was — which is the general hazard rather than a criticism of the double.
The failing assertion
tinymemory-conformance/src/suite/mod.rs, thenewlinescase (its literal carries\n,\r\nand a NUL):The suite's own comment explains why this must fail rather than be tolerated: a driver may refuse content, but silently altering it is exactly what the assertion exists to catch.
Who this does and does not affect
It does not reach OpenCompany, and saying so keeps the fix from being scoped wrongly. Its facades JSON-encode every record into
content, so a NUL inside a trace, fact or chunk crosses the wire as the six ASCII characters of its JSON escape and arrives as text there is nothing to strip. Verified rather than assumed: with the provider contract failing, that same live service passes OpenCompany's full port suite —ContextStore/FactStore/MemoryStoreround-trips and the(addr, label)semantics included (opencompany#1557).It does affect any consumer that hands the driver raw text.
MemoryCore::storepromises that what you store is what you get back, and against Supermemory that is untrue for this character.Options
MemoryError::Invalid. The suite already accepts a refusal — "may refuse" is its documented alternative to storing faithfully — so this passes without weakening the assertion, and the caller learns at the boundary instead of discovering it later.Option 2 reads best to me: it keeps the contract meaning what it says, and turns silent data loss into a refusal the caller can act on. The call belongs to whoever owns the adapter's compatibility posture.
Reproducing
OPENCOMPANY_TEST_SUPERMEMORY_URL=https://api.supermemory.ai \ OPENCOMPANY_TEST_SUPERMEMORY_KEY=... \ cargo test --features tinymemory --lib live_hostedfrom opencompany#1557, which added the live-service lane. Without the pair it skips, so an offline run is unaffected.
Related
PATCHdroppingcontainerTag. Same adapter, same class of defect: one only a real endpoint reveals.