Description
Finding
tepp.membership_observation_support_projection.v1 currently serializes the owner-issued opaque-support support_sha256, but parsed wire validation only checks that this field is a 64-character lowercase hex string. The exact owner digest intentionally hashes raw MemberId/GroupId UUID bytes, while the privacy-reduced projection intentionally omits those identifiers. Therefore a released consumer cannot recompute that owner digest from the serialized support rows, and a byte-valid payload can substitute an unrelated 64-hex support digest while retaining the same reconstructable topology.
This does not make parsed wire authoritative—the parser is already explicitly non-authoritative—but it leaves the released reconstruction bytes without a self-checkable content identity and makes the current “binds the source-support digest” wording too strong for the serialized boundary. #638/#639 need a deterministic consumer coordinate that can be checked from the released bytes without copying Membership source identity.
Required RED
Starting from a valid owner-produced canonical payload:
- mutate only
support_sha256to another valid lowercase 64-hex value and require parsing to fail unless the wire-content commitment is updated consistently; - mutate one reconstructable observation coordinate/assignment while preserving otherwise-valid design/topology and require parsing to fail against the unchanged wire-content commitment;
- require owner-produced payloads to expose a deterministic versioned wire digest invariant under caller input ordering and network insertion ordering, while duplicate observation multiplicity remains significant.
Minimal causal repair
Keep support_sha256 as the owner-attested opaque-support provenance fingerprint; do not pretend it is recomputable after UUID redaction. Add a separate versioned digest over the canonical privacy-reduced projection payload excluding the digest field itself, and make MembershipObservationSupportWire::from_json recompute and compare it before accepting bytes. The content digest must cover schema/design/support-digest coordinates, counts, canonical event times, local ordinals, roles, exact binary64 weight bits, and support_sha256 so these fields cannot drift independently under the supported parser contract.
Do not add signatures, secrets, source UUIDs, consumer-local hashing, or a second Membership topology classifier. Cryptographic authenticity remains release/provenance responsibility; this issue is deterministic cross-field/content binding at the owner wire boundary.
Activity
test(membership): reject detached support digest (#652)
fix(membership): bind support wire content digest (#652)
feat(membership): export support wire digest contract (#652)
test(membership): cover recomputable support wire digest (#652)
docs(membership): distinguish source and wire support digests (#652)
seonghobae commentedon Sep 21, 2026
Implemented on canonical #605 owner branch.
RED
028a0660eeea76c6ce0c16ecfd7b179accb99a9dadds a source-level parser contract proving that a valid-looking 64-hexsupport_sha256cannot be substituted independently of the released reconstruction bytes. Predecessor parsing accepted that detached fingerprint because UUID-redacted wire bytes cannot recompute the opaque-source digest.Causal repair
4e5b64e663868d63a77d9dbed2baedd7cae27621keepssupport_sha256as the owner-attested opaque-support provenance fingerprint and addstepp.membership_observation_support_projection_digest.v1, recomputed from schema/design/source-digest coordinates, counts, canonical local observations, roles, and exact binary64 weight bits.MembershipObservationSupportWire::from_jsonnow checks that digest after existing reachability/topology validation. This is deterministic content binding, not authentication; parsed bytes remain non-authoritative.39ef9f7ec0c30b1a9c1f353c07d2118eb11930b4exports the wire-digest version,a66613cc4a10e82877956d40efa04de4c5c560ffadds deterministic-order and detached-event-time coverage, and CHANGELOG/current head is32871b52e801bd501d3ff1f4fee8cd12f17530b3.Fresh current-head runs are non-terminal: Rust Foundation
35645901615, Security35645901416, Semgrep35645901376, CodeQL PR35645901422, Documentation35645901308are queued. Keep #652 open through exact-head GREEN, qualifying independent review, protected landing, and downstream released-contract consumption.