07. Exports
Draft 0.1. Reference implementation: shared/verifiableCredentials.js, api/issue-credential.js, api/verify-credential.js, api/did.js, api/jsonld-context.js, api/protocol-spec.js. Vector v20. No code is specified in this chapter; every mapping not backed by code is marked planned with the gap named.
7.1 What exists today
The reference implementation issues one kind of W3C Verifiable Credential: a vault-field credential over a category of the family's profile, signed with the organization key. It does not yet export a transcript, a course receipt, a work sample, or an attestation as a credential; those are the receipts of chapter 02, and their export is the subject of sections 7.3 and 7.4.
The issued credential
POST /api/issue-credential (family session; 20 per minute per IP) takes {categoryId, fields, provenance, personName} and returns:
| field | type | value or rule |
|---|---|---|
@context | array | ["https://www.w3.org/2018/credentials/v1", "https://eformogi.com/credentials/v1"] |
id | URI | urn:eformogi:vc:<32 hex random> |
type | array | ["VerifiableCredential", <one of the six types below>] |
issuer | object | {id: "did:web:eformogi.com", name: "Eformogi"} |
issuanceDate | ISO 8601 | |
credentialSubject | object | id: "did:eformogi:user:<account uuid>", one property per claim, displayName when given |
credentialStatus | object | {id: "https://api.eformogi.com/credentials/status/<id>", type: "StatusList2021Entry"} |
eformogiMetadata | object | {version: "1.0.0", verificationDetails {field: {method, source, confidence, sourceDocument, extractedAt}}, fieldCount} |
proof | object | {type: "DataIntegrityProof", cryptosuite: "eddsa-jcs-2022", created, verificationMethod: "did:web:eformogi.com#key-1", proofPurpose: "assertionMethod", proofValue: "z..."} |
Credential types: EformogiFamilyProfile, EformogiEducationRecord, EformogiMedicalRecord, EformogiIdentityDocument, EformogiFinancialRecord, EformogiInsuranceRecord. Provenance sources map to verification methods: document_extraction to DocumentExtraction, ai_extraction to AIExtraction, institutional_api to InstitutionalVerification, user_input to SelfAsserted, parent_entry to ParentAsserted, imported to ImportedData, verified to IndependentVerification.
Fields named ssn, creditCard, bankAccount, driversLicense, or passportNumber are stripped before issuance and MUST never appear in a credential.
The proof
The suite is Data Integrity eddsa-jcs-2022: signInput = sha256(utf8(JCS(proof options))) || sha256(utf8(JCS(document))), 64 bytes, signed raw with Ed25519; proofValue is multibase base58btc. JCS here is the canonical form of chapter 02, section 2.3, and every non-integer number in the document is coerced to a string before signing (jcsCoerceNumbers), so a GPA of 3.7 travels as "3.7". Any receiver verifies against the JWK at /.well-known/did.json with no secret and no call to the operator (vector v20). The former symmetric suite HmacSha256Signature2024 is retired and refused with a re-issue message.
POST /api/verify-credential (public) returns {verified, checks: {proof, issuer, revocation, expiration}, credential: {...summary}}; revocation is read from the operator's verifiable_credentials table, not from a status list.
Known gaps in the existing shape
| gap | status |
|---|---|
@context is the VC Data Model 1.1 context while the code describes itself as 2.0-compatible; issuanceDate is the 1.1 name (2.0 uses validFrom) | planned: move to https://www.w3.org/ns/credentials/v2 and validFrom |
credentialStatus names StatusList2021Entry but no status list is served; revocation is a database read | planned: publish a BitstringStatusList, or drop the field until it is real |
credentialSubject.id is did:eformogi:user:<uuid>, a non-resolvable DID method | planned: the learner's did:key once wallet custody exists (chapter 04) |
https://eformogi.com/credentials/v1 context resolves to api/jsonld-context.js, which maps vault fields (identity, contact, education, medical, financial, family, activities, employment, provenance) to schema.org and eformogi: terms; it does not define receipt or attestation terms | planned: a receipt context |
api/protocol-spec.js (FDEP 1.0.0-draft, 2026-03-02) still states credentialIntegrity: 'HMAC-SHA256 proof' | stale text; planned: update or retire (it is a Rule 8 audit item) |
7.2 The DID document
/.well-known/did.json publishes did:web:eformogi.com with one verification method (#key-1, JsonWebKey2020, the Ed25519 JWK of chapter 04), used for authentication and assertionMethod, and services #schema, #verify, #revocations, #issue, #vault, #context, #openapi, #fdep, and #receipt-log (type TransparencyLog, endpoint /api/tree-heads). A receiver resolving the issuer of any signed artifact in this specification lands here.
7.3 Mapping to Open Badges 3.0 (AchievementCredential)
Open Badges 3.0 is a VC profile: an AchievementCredential whose credentialSubject is an AchievementSubject holding an Achievement. The natural unit is one course row (cr_) or one work sample (ws_), with attestations as Evidence and the family as Profile issuer. Status: planned end to end; no OB3 credential is issued today.
| OB3 field | source in the Record | status |
|---|---|---|
@context | https://www.w3.org/ns/credentials/v2, https://purl.imsglobal.org/spec/ob/v3p0/context-3.0.3.json | planned |
type | ["VerifiableCredential", "AchievementCredential"] | planned |
id | https://eformogi.com/verify/<cr_ or ws_ id> | planned (gap: a resolvable credential URL that returns the VC, not the HTML page) |
issuer (Profile) | family: id = family did:key (chapter 04), name = the family's issuer string; organization issuer: planned with the issuer rail | planned (gap: Profile.type for a family issuer; OB3 assumes an organization) |
validFrom | issued_at (cr_: the row's created_at) | planned |
credentialSubject.type | AchievementSubject | planned |
credentialSubject.id | learner did:key | planned (gap: learners have no key today; the family key is per account, not per person) |
credentialSubject.achievement.type | Achievement | planned |
credentialSubject.achievement.name | course_title or ws_.title | planned |
credentialSubject.achievement.description | statement-free description of the course or entry | planned |
credentialSubject.achievement.criteria.narrative | for cr_: term, credits; for ws_: entry_kind, occurred_at | planned |
credentialSubject.achievement.achievementType | Course for cr_; Competency or LearningProgram for ws_ by kind | planned |
credentialSubject.achievement.creditsAvailable | credits (numeric) | planned (gap: credits is a string in the hash domain by design; the numeric projection is display-only) |
credentialSubject.result[] (Result) | family_stated_grade as value, resultDescription "Family-graded" | planned |
credentialSubject.source | the family Profile | planned |
evidence[] (Evidence) | one entry per attestation: id = /verify/<att_ id>, name = signer name and role, description = statement, genre = "witness attestation", audience = signer organization | planned (gap: OB3 Evidence has no slot for signed_grade disagreement; carry it in description and keep the authoritative rendering on the verify page) |
credentialStatus | BitstringStatusList entry driven by revoked_at | planned (gap: no status list today; revocation is online-only) |
proof | eddsa-jcs-2022 by the family key, plus the organization key as a second proof for the ceremony | planned (gap: the family key is escrowed; an OB3 issuer proof by an escrowed key must be disclosed as such) |
endorsement[] (EndorsementCredential) | organization countersignature (chapter 03, section 3.5) as an endorsement of the ceremony | planned (gap: the countersignature endorses processing, not the achievement; the endorsement text must say that) |
image, alignment[] | none | not applicable |
7.4 Mapping to Comprehensive Learner Record v2.0 (ClrCredential)
CLR v2 wraps many AchievementCredentials plus a ClrSubject and optional associations into one record. The natural unit is the transcript (tr_), holding one AchievementCredential per cr_. Status: planned; no CLR is issued today.
| CLR v2 field | source in the Record | status |
|---|---|---|
type | ["VerifiableCredential", "ClrCredential"] | planned |
id | https://eformogi.com/verify/<tr_ id> | planned |
issuer | family Profile (as above) | planned |
validFrom | tr_.issued_at | planned |
credentialSubject.type | ClrSubject | planned |
credentialSubject.id | learner did:key | planned (same gap as OB3) |
credentialSubject.verifiableCredential[] | one OB3 AchievementCredential per cr_ on the transcript | planned |
credentialSubject.achievement[] | the achievements referenced above | planned |
credentialSubject.association[] | tr_ to cr_ provenance (tr_receipt_id) as isChildOf | planned |
credentialSubject.identifier[] | the tr_ id and its sha256 as IdentifierEntry (identityType: "identifier") | planned |
credentialSubject.narrative | program, philosophy (only if the family opts in; it is on the printed transcript but not on the public payload today) | planned (gap: privacy decision) |
credentialSubject.activityStartDate / activityEndDate | earliest and latest year on the rows | planned |
credentialSchema | the CLR JSON schema | planned |
proof | as OB3 | planned |
tier chips (tier_counts, "issuer-declared" muting) | no CLR slot | gap: CLR has no notion of per-row trust tier; the honesty rendering stays on the verify page and the mapping MUST NOT upgrade a chip into a Result the ledger did not corroborate |
transcript_eligible: false objects (wn_, wl_) | excluded from CLR by rule | not applicable |
Chapter 11 (added 2026-09-14) checks these rows against the published OB 3.0 and CLR 2.0 schemas. Two rows above are withdrawn there: ClrSubject carries no narrative and no activity dates in the published ClrCredential schema (those sit on AchievementSubject), so program, philosophy, and the year range map as chapter 11, section 11.3.1 says. Chapter 11, section 11.6, holds the full list of divergences and open checks.
7.5 Rules for any export (MUST)
- An export MUST link back to the receipt id it was derived from, so a receiver can drop back to chapter 06 and verify the source independently of the export's own proof.
- An export MUST NOT contain more of the family's content than the public verify payload does, unless the family explicitly includes it at export time.
- An export MUST carry the same honesty statements as the verify page (chapter 06, section 6.6) in a human-readable field, and MUST label any proof by an escrowed key as escrowed (chapter 04, section 4.7).
- An export MUST NOT promote a self-declared witness or an issuer-declared tier into a claim that the receiver could read as third-party verification.
- Numbers in signed documents MUST be JCS-safe (strings for anything non-integer).
7.6 What this does not prove
A credential proves the organization key signed a document at a time. It does not prove the provenance labels inside it are true (they are the family's and the extraction pipeline's labels), and a verified: true from the verification endpoint is a proof and issuer check, not an endorsement of the claims. Planned mappings prove nothing until they ship; this chapter records intent so a standards reviewer can mark gaps against a fixed text.