Files
Dario LipicarandClaude Opus 5 b790e1daee fix(conformance): three xfails become one, and one of them was measuring the driver (#30)
* fix(conformance): the scalar divergence is closed, not pinned

hostile/uint/negative and hostile/int/fractional were pinned as
`expect_by_provider` — C++ answered 18446744073709551615 and 3, Rust rejected
both. Both providers now reject, so these become ordinary agreeing cells and the
`divergence` tag comes off.

Worth being blunt about how these two read before: the pinned C++ expectation was
the stated reason the C++ leniency was never fixed. The generator's own comment
pointed at `hostile/int/fractional` as behaviour "something depends on" — the
something being this file. A cell recorded to DOCUMENT a divergence had become the
argument for keeping it, while its own `why` text said the strict behaviour was
correct. Pinning a divergence has to stay a way of watching it, not a way of
sanctioning it.

Also adds the three cases that would have caught this class at depth and did not
exist: a negative and a fractional element inside [uint], and a fractional inside
[int]. The top-level cases could not catch it, because composites and scalars were
decoded by DIFFERENT code — composites already went through the codec. An element
buried in a container is exactly the case that tells the two paths apart.

Fixed by logos-protocol fix/codec-signedness + logos-cpp-sdk fix/cdylib-scalar-codec.
Measured: 85 cases, 164 pass / 6 xfail, differential 73 -> 78.

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

* fix(conformance): M2 retired — void agrees across providers

void/return goes from a pinned per-provider divergence (C++ `true`, Rust
METHOD_FAILED) to a single agreeing expectation, and M2 leaves the registry.

The entry's framing was "agree one answer", which read like a design disagreement
to settle. It was not one: the Rust backend simply had no void arm, so
`-> void` (Named("void"), not a builtin) fell to the catch-all and returned JSON
null — and null is the failure token in the C++ layer above, which is where the
METHOD_FAILED came from. Neither backend had an opinion about void; one of them
just never implemented it.

The two Rust modules move to `fn do_void(&mut self)`. The proxy drops the
forwarded call's Result rather than turning it into a value, which is all a void
method can express: it either forwarded or it did not, and it has no channel to
say which.

The registry entry records the residual rather than closing clean: `true` is a
fabricated value, the spec answer is null, and null-as-failure is untouched
underneath. Both backends now tell the same lie. That is convergence, not a fix
of the conflation.

Measured: differential-pass 78 -> 79; 164 pass / 6 xfail (M3, M4).

Fixed by logos-qt-sdk fix/void-convergence + logos-rust-sdk fix/void-arms.

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

* fix(conformance): M4 narrowed with its residual pinned; M3 was measuring the driver

M4 -> fixed + M4-residual. The shape check in logos-protocol closes what M4
actually measured (a map merely CARRYING the sentinel key, which hung the call
for the full timeout). It cannot close a canonical-shape forgery, because that IS
the sentinel — so a new case pins the surviving half rather than letting a green
row imply safety. The entry also corrects two errors in the old one: `measured:
RPC_FAILED` was stale (it is a ~20s hang), and `fix_is: same class as M3` was
wrong — M3 is inherent, this was a missing guard two sibling detectors in the
same repo already had.

The residual case states the CORRECT answer (the map echoed back) and is
registered as an xfail, rather than pinning the measured TimeoutExpired as an
expectation. Pinning observed-broken behaviour is what turned the scalar
divergence into an argument against fixing itself; a case says what should
happen, the registry says we know it doesn't.

M3 -> `unmeasurable`, not fixed and no longer an xfail. Its four cells were
measuring the python client, which decoded the tagged form unconditionally on the
way back. With that off they all pass: an `any` slot carries `{"_bytes": ...}`
through the system untouched, so the `measured` line — b'hi', "the map is gone" —
was a driver artifact. Two things follow. No C++ change could ever have made
those cells xpass, so the forcing function was inert for M3 the whole time. And
the collision is real on the Qt/C++ consumer surface, which is exactly the
consumer this matrix has no driver for — the defect and the measurement were
never on the same surface.

Also corrects the old `fix_is`: it called M3 a wire-format question. qt_remote
already carries QByteArray natively and the collision still happens; it is in the
type-erased converters, upstream of every wire. Closing it is a C-ABI data-model
change, which is protocol-MAJOR.

Measured: 86 cases, 170 pass / 2 xfail (M4-residual), differential 80. Ext 40/40.

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

* chore: re-pin onto the merged scalar + void fixes

  logos-module-builder -> 6a5073e  (#170 — carries protocol c0df466,
                                    cpp-sdk ff8c300, qt-sdk a5874fe,
                                    rust-sdk e43eb3d)
  logos-logoscore-cli  -> 37a6df0  (#78)
  logos-liblogos       -> 6f0ca4b  (#169)

This is the commit that makes this branch's expectations reachable. Until now the
Rust test module implemented `fn do_void(&mut self)` against a trait the pinned
generator still emitted as `-> serde_json::Value` (E0053), and the scalar
expectations described a decoder the pinned cdylib backend did not have.

All five conformance providers build on the merged chain: test_fullapi_cpp,
test_fullapi_rust, test_fullapi_ext_cpp, test_fullapi_ext_rust and
test_fullapi_proxy_rust.

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

* chore: re-pin onto the whole-valued-float fix

  logos-module-builder -> ec38874  (#171 — carries protocol 3da8de9,
                                    cpp-sdk c364133)

The previous pin carried a signedness check that rejected 3.0 as well as 3.7,
which failed the four test_basic_module_cpp double→int64 cases. Those cases are
right — JSON does not distinguish 3 from 3.0 — and the codec now accepts a
whole-valued float for an integer while still refusing a fractional one.

176/176 with no overrides.

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

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 15:06:59 -03:00
..