Skip to content

Payload fingerprint renders every custom-codec type as the same opaque tag, so a retype between two of them is invisible #245

Description

@Yaraslaut

What is true

morph::model::payloadShapeString<A>() (include/morph/core/payload_schema.hpp,
added for #174) renders every type that carries its own glz::meta as the
single opaque tag x, because it deliberately refuses to use glz::name_v
that name is derived from __PRETTY_FUNCTION__/__FUNCSIG__ and differs
between compilers, and a journal fingerprint that changes when the compiler
changes would make a journal unreadable by a peer build.

Measured on the #174 branch (clang++ -std=c++23, standalone probe):

struct WithSpecial {
    morph::math::Rational amount;
    morph::time::Timestamp at;
    morph::util::Tagged<std::string, "acct"> id;
};
payloadShapeString<WithSpecial>()  ->  (amount:x,at:x,id:x)

So swapping Rational for Timestamp in a recorded action's field does not
change the fingerprint, and replay()'s mismatch gate does not fire. (Swapping
a custom-codec type for a plain one is caught — x versus s — and every
member rename, addition, and removal is caught regardless of the member types.)

This is documented as a known boundary in docs/spec/journal/journal.md, "What
the fingerprint does not catch". Filing it so the boundary is tracked rather
than only described.

What a fix would need

A per-type discriminator that is stable across compilers, standard libraries,
and platforms — not a compiler-spelled type name. Candidates:

  • An opt-in trait a custom-codec type specialises to declare its own stable
    shape tag (morph::model::PayloadShapeTag<T>), defaulted to x. Cheap;
    requires each such type to opt in, and a new type silently gets x again.
  • Hashing glz::write_json_schema<T>(). Type-aware and derived, but its
    $defs keys come from glz::name_v, which reintroduces exactly the
    portability problem — would need the $defs normalised away first.

Verification status

Reproduced. The probe above was compiled and run on the #174 branch. Not
measured: whether any shipped action in examples/** actually has two
custom-codec fields whose types could plausibly be swapped for one another —
this is a gap in the mechanism, not an observed defect in a rung.

What would change the verdict

Close when the fingerprint distinguishes at least Rational, Quantity,
DateTime, and Tagged from one another without introducing a
compiler-dependent term, or when a decision is recorded that x is the
permanent answer and the spec's boundary paragraph is the whole story.

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