Skip to content

The MORPH_CLIENT_ONLY binary-skew test is still unwritten, and now has to assert two different mechanisms #246

Description

@Yaraslaut

What is true

examples/lims/README.md asks for a binary-skew test as the executable form of
the journal's data-at-rest contract, and #174 restates it as "the other half":

build an old client with MORPH_CLIENT_ONLY and run it against a new server —
an additive field must work, a renamed field must fail loudly, not decode
a lab result to a default.

It is still unwritten. Searching tests/ for any skew/version-mismatch
harness finds none. The three MORPH_CLIENT_ONLY gates that do exist
(tests/CMakeLists.txt:283-425, over compile_checks/client_only_no_model_link.cpp,
client_only_runtime_throw.cpp, client_only_facade_no_model_header.cpp)
prove unrelated things — that the macro suppresses model-owning link
dependencies, and that Bridge::executeVia's localOp throws when misused
against LocalBackend. None builds a client against a differently-shaped
server.

Scope: journal-path skew test (buildable now)

The journal-path mechanism is real and shipped:
morph::model::payloadFingerprint<A>() (include/morph/core/payload_schema.hpp)
is wired into ActionDispatcher's runner and Bridge::executeVia's localOp,
and journal::replay()'s mismatch gate throws SchemaMismatchError on a
fingerprint mismatch, with a migration seam (PayloadMigrationRegistry) and an
UnstampedPayloadPolicy. tests/test_journal_payload_evolution.cpp already
documents simulating an older build inside one process, via a PESetStateV1
struct that is never registered — that proves the gate fires, but it is not a
two-binary skew test.

This issue's scope is a genuine two-binary journal-path skew test: compile
"old" and "new" binaries from two struct-renamed versions of the same action,
following the try_compile/try_run pattern already used for the other
MORPH_CLIENT_ONLY gates in tests/CMakeLists.txt:283-425. Have the old
binary journal an entry, replay it under the new binary's registry, and
assert, as separate cases:

  • an additive-only field round-trips;
  • a renamed field throws SchemaMismatchError rather than silently
    reconstructing a default.

Invariant-7 guidance for the implementer: the renamed-field case must
actually verify SchemaMismatchError is thrown, not just that the test
compiles/runs alongside the additive-field case — a harness that only
exercises the additive path would pass identically whether or not the
fingerprint gate exists at all. Confirm both that the additive case does
not throw and that the renamed case does, in the same harness.

Out of scope: wire-path mechanism — tracked by #207

The wire path (deployment scope) is still lenient by design, defended by the
published "Action-evolution policy" in docs/spec/core/wire.md and the
kProtocolVersion handshake. That leniency (error_on_unknown_keys = false
in both BRIDGE_REGISTER_ACTION-generated codecs and wire::decode) is a
deliberate, published contract, not an oversight — additive fields must keep
working, and a rename is defined as "a break, not a rename," requiring a
kProtocolVersion bump. But nothing mechanically enforces that policy today:
searching for SignatureMismatch / actionFingerprint / any per-action
hello check across include/ and docs/spec/ finds nothing. #207 proposes
the fix — a per-action fingerprint exchanged at hello — and remains open
and unimplemented.

A wire-path skew test (a MORPH_CLIENT_ONLY client hitting a
differently-shaped server over the loopback backend) cannot be written until
#207's mechanism exists to assert against. That half tracks #207, not this
issue.

Close condition

Close once a CI job builds and runs the two-binary journal-path skew test
described above, asserting both the additive-round-trips case and the
renamed-field-throws-SchemaMismatchError case. The wire-path half is
tracked separately by #207.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions