Eformogi
The Record: protocol draft 0.1 (CC BY 4.0; verifier Apache-2.0)

10. Conformance

Draft 0.1. Reference: spec/verifier/verify-offline.js, spec/verifier/run-vectors.sh, spec/verifier/vectors/*.json.

10.1 What conformance means

Conformance says nothing about the truth of the claims inside a Record, the identity of any witness, or acceptance by any receiver. The trademark and conformance-claim policy is pending (LICENSE-PENDING.md).

10.2 Vector structure

Each vector is one JSON file:

fieldtypemeaning
idstringv<nn>-<slug>; the file name without .json
chapterstringthe chapter the vector exercises
titlestringthe normative sentence being tested
checkenumwhich algorithm the verifier runs (section 10.3)
inputobjectthe check's inputs; shapes per check kind
expectobjectverdict: "pass" or "fail", plus expected values (canonical, id, sha256, roots, empty_root, snapshot_sha256) where the check produces values

A vector whose expect.verdict is fail passes when the verifier correctly refuses. Signature vectors were generated once with ephemeral keys; only public halves are embedded, and the vectors do not depend on any live key or endpoint.

10.3 Check kinds

checkinputwhat the verifier does
canonicalizevaluechapter 02 section 2.3 canonical form; compares to expect.canonical
leaf_payloadleaf {receipt_id, prefix, sha256, issued_at}builds the four-field leaf with normalization; refuses non-loggable prefixes
receipt_idkind and the raw inputs for that kind (payload, course, attestation, worksample, note, wall)applies the per-prefix hash domain and derives <prefix>_ + 12 hex
kb_bindingkey_id, public_jwk, established_atderives did:key from x, builds the frozen kb_ payload, derives id and sha256
family_sigleaf_payload, sig, public_jwkEd25519 under eformogi-receipt-sig-v1:
countersigleaf_payload, org_countersig, public_jwkEd25519 under eformogi-witness-countersig-v1:
merkle_rootleaf_payloads[]leaf hashes and RFC 6962 roots over prefixes of the list
inclusionleaf_hash, leaf_index, tree_size, audit_path, root_hashthe RFC 9162 fold
sthsth, public_jwkwhitelist the seven fields, canonicalize, Ed25519 under eformogi-sth-v1:
proofproof, public_jwk, sha256?the four-step algorithm of chapter 06
vc_proofcredential, public_jwkeddsa-jcs-2022 DataIntegrityProof

10.4 The vectors

idchapterexercisesexpected
v01-canonical-json02sorted keys, arrays in order, unicode, nestingpass with exact string
v02-leaf-payload-exact-bytes02id and sha lowercased, ISO ms timestamp, sorted keyspass with exact string
v03-leaf-payload-rejects-ecr02ecr_ is never loggablefail
v04-cr-derivation02cr_ hash domain with credits as posted stringpass, cr_0ad5487e3ca4
v05-att-derivation-ws-target02, 03att_ hash domain with lowercased email as email_hash_inputpass, att_5d9b296a66cb
v06-ws-derivation-v102ws_ v1 domain; issued_at outsidepass, ws_53071e72cd4a
v07-wn-derivation02wn_ domain with author: kid fixedpass, wn_e45fc1d88617
v08-tr-derivation02tr_ whole-payload domain (gold-demo shape)pass, tr_d83e84e54c94
v09-kb-binding-frozen04did:key derivation and the frozen kb_ payloadpass, kb_b5ed898815ac
v10-family-sig-valid04family signature under its contextpass
v11-family-sig-context-stripped04a context-free signature must not verifyfail
v12-org-countersig-valid03organization countersignature under its contextpass
v13-merkle-roots-frozen05roots for 1, 2, 3, 7, 8, 16 leaves and the empty rootpass with exact hashes
v14-inclusion-valid05leaf 3 of 7 folds to the rootpass
v15-inclusion-wrong-index05same path, wrong indexfail
v16-sth-valid05seven-field signed tree headpass
v17-sth-tampered-tree-size05tampered tree_sizefail
v18-proof-four-steps-valid06artifact hash, leaf hash, inclusion, STH signaturepass
v19-proof-tampered-path06one flipped audit-path elementfail
v20-vc-eddsa-jcs-202207DataIntegrityProof over a JCS-coerced credentialpass
v21-proof-with-family-signature06a real proof carrying issuer_signature: leaf hash, inclusion, tree head, family signature, did:key derivationpass
v22-proof-family-signature-tampered06the same proof with one character of the family signature flippedfail
v23-proof-relabeled-receipt-id06the v18 proof with its top-level receipt_id rewritten to another id; the leaf and head are untouched and still verifyfail
v24-proof-receipt-id-not-a-string06the v18 proof with its top-level receipt_id carried as an array; a label of the wrong type fails the check rather than skipping itfail
v25-att-derivation-cr-target-no-grade02, 03, 08att_ on a cr_ target with signed_grade absent: a Portrait evidence signature re-derives with the grade canonicalized as nullpass, att_ea43a539c234
v26-att-cr-target-no-relationship03the same signature with signer_relationship absent; the relationship stays required on every cr_ targetfail
v27-wl-derivation02wl_ domain {kind:wall, curator, person_id, created_at}; snapshot_sha256 over pinned_ids in curator order, not sortedpass, wl_889040b4d6e3
v28-att-both-targets-refused03att_ with both ws_receipt_id and cr_receipt_id present is refused: exactly one target, never twofail

v21 and v22 are frozen from the live production proof for tr_8e59bb9641e8 plus the issuer_signature block /api/verify-receipt already serves publicly, so the offline path is pinned against a real receipt rather than a fixture. Neither vector asserts anything about key_established_at: that ordering is advisory (chapter 06, step 4) and never enters a verdict.

v25 and v26 (added 2026-09-15 with the competency taxonomy plugin, chapter 08 section 8.7) pin the one shape that plugin adds to the ledger: a cr_ attestation whose signed_grade is null, a Portrait evidence signature (chapter 03 section 3.4). Before v25 the reference verifier refused every such signature, so a record carrying one could not have passed this suite even though no byte of any hash domain had changed; v26 pins that the relationship stays required. v23 pins the label check added on 2026-09-14 after property-based testing of the verifier (chapter 09, section 9.10) showed that a proof's top-level receipt_id was never compared with the id inside the logged leaf: the verifier printed VERIFIED with the relabeled id on the final line. The same check covers tree_size against sth.tree_size. v24 (added the next day, after review) pins that a label of the wrong type fails the check instead of skipping it: the first fix ran the check only for a string receipt_id, so an array, a number, or null in that field bypassed it.

Chapters 01, 08, and 09 have no vectors: they specify structure, plugin interfaces, and controls rather than byte-level algorithms. Frozen values in v09 and v13 are identical to the values pinned by tests/test-family-signing.js and tests/test-merkle-log.js, so the trust gate and this suite cannot drift apart silently.

v27 (added 2026-09-19) pins the wl_ (wall) derivation, the one loggable receipt prefix with no prior vector even though deriveWl has covered it in verify-offline.js since the suite's first draft. It fixes both halves of chapter 02's wall description: the id, which is stable over the wall's lifetime and derives only from {kind, curator, person_id, created_at}, and snapshot_sha256, the mutable half, which is sha256(JSON.stringify(pinned_ids)) over the curator's chosen order and never sorted. The vector's two pinned ids are deliberately not in alphabetical order, to make that provable rather than assumed. Values were generated from the production derivation the offline verifier mirrors: deriveWallId and snapshotHash in frontend/src/utils/timeline.js, and the server-side recomputation in api/publish-wall.js.

v28 (added 2026-09-21) pins chapter 03's "exactly one" rule on an att_ target: a signature must name either a ws_receipt_id or a cr_receipt_id, never both. Before v28 the rule was normative text and one live guard (api/publish-attestation.js, hasWs/hasCr, refused with 400) but no vector, so a verifier could accept a two-target attestation without failing this suite. The input supplies both fields on the same attestation the way v05 supplies only ws_receipt_id; deriveAtt's hasWs === hasCr check refuses it, matching the live endpoint's XOR guard byte for byte.

10.5 Running

sh spec/verifier/run-vectors.sh        # all vectors; non-zero exit on any mismatch
sh spec/verifier/run-vectors.sh -v     # print every step
node spec/verifier/verify-offline.js spec/verifier/vectors/v14-inclusion-valid.json

Output at the time of this draft: vectors: 28/28 matched expected verdicts.

10.6 Adding a vector

  1. State the normative sentence it tests and name the chapter.
  2. Generate expected values from the reference implementation in shared/, never by hand, and never from the verifier under test.
  3. If the vector needs a key, use an ephemeral key and embed only the public half.
  4. Prefer one positive and one negative vector per mechanism; a suite that cannot fail proves nothing.
  5. Never embed a real family's data. Fixtures use the public demo student (Maya Chen) or synthetic values.

10.7 What this does not prove

Twenty-eight vectors pin the algorithms. They do not exercise the online endpoints, rate limits, revocation states, tier corroboration, or any legal control; those are covered by the trust gate (npm run test:trust, the trust gate at the time of this draft) and by the operational history in TRUST_REPORT.md.

10.8 The Witness Transcript name

Witness Transcript is a mark of Eformogi, Inc. An implementation whose records pass this conformance suite, in the version of the specification it claims, may call the resulting artifact a Witness Transcript and may use the name in its own prose. An implementation MUST NOT call a record a Witness Transcript unless it passes the suite. The mark MUST NOT be used in the name of a company or a product. This is a trademark policy conditioned on a technical suite, not a certification program: passing it is still not "certified", "official", or "accredited", and nobody, including Eformogi, may describe an implementation that way. The full trademark text is in LICENSING.md.