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.
What is true
examples/lims/README.mdasks for a binary-skew test as the executable form ofthe journal's data-at-rest contract, and #174 restates it as "the other half":
It is still unwritten. Searching
tests/for any skew/version-mismatchharness finds none. The three
MORPH_CLIENT_ONLYgates that do exist(
tests/CMakeLists.txt:283-425, overcompile_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'slocalOpthrows when misusedagainst
LocalBackend. None builds a client against a differently-shapedserver.
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 andBridge::executeVia'slocalOp,and
journal::replay()'s mismatch gate throwsSchemaMismatchErroron afingerprint mismatch, with a migration seam (
PayloadMigrationRegistry) and anUnstampedPayloadPolicy.tests/test_journal_payload_evolution.cppalreadydocuments simulating an older build inside one process, via a
PESetStateV1struct 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_runpattern already used for the otherMORPH_CLIENT_ONLYgates intests/CMakeLists.txt:283-425. Have the oldbinary journal an entry, replay it under the new binary's registry, and
assert, as separate cases:
SchemaMismatchErrorrather than silentlyreconstructing a default.
Invariant-7 guidance for the implementer: the renamed-field case must
actually verify
SchemaMismatchErroris thrown, not just that the testcompiles/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.mdand thekProtocolVersionhandshake. That leniency (error_on_unknown_keys = falsein both
BRIDGE_REGISTER_ACTION-generated codecs andwire::decode) is adeliberate, published contract, not an oversight — additive fields must keep
working, and a rename is defined as "a break, not a rename," requiring a
kProtocolVersionbump. But nothing mechanically enforces that policy today:searching for
SignatureMismatch/actionFingerprint/ any per-actionhellocheck acrossinclude/anddocs/spec/finds nothing. #207 proposesthe fix — a per-action fingerprint exchanged at
hello— and remains openand unimplemented.
A wire-path skew test (a
MORPH_CLIENT_ONLYclient hitting adifferently-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-
SchemaMismatchErrorcase. The wire-path half istracked separately by #207.