The previous chapter described the protocol record as a graph of typed structures in memory. A record that exists only in memory, however, cannot be reviewed by an institutional biosafety committee, cannot be sent to a collaborating institution, and cannot be produced two years later in response to an audit request. The record has to be written down in a form that two independent programs agree on, given a name that survives every later revision, and checked against a schema before anyone is allowed to act on it. That is the subject of this chapter: the wire format, the identifier, and the validation gate that stands between a submitted document and the record store.
This chapter owns serialization and identifiers for the whole book. Call signatures belong to Chapter 4, lifecycle states and timestamps to Chapter 5, transport and deployment to Chapter 6, and evidence packaging to Chapter 7. Where a topic touches one of those, this chapter states only the part that is a property of the document on disk.
WIA-BIO-014 §8.3.1 presents the protocol as a single JSON object. JSON is a good choice for this record class for unglamorous reasons: it is specified by a short, stable document (RFC 8259), every language in a research institution's software estate can parse it, it survives being pasted into a ticket, and it is readable by the compliance officer who has to sign the record without installing anything. The cost is that JSON has almost no semantics of its own — it cannot express a required field, an enumerated value, a unit, or an ordering rule. Everything that makes the document trustworthy therefore lives outside the document, in the schema and the validator described later in this chapter.
The canonical document is a single object whose members are the aggregate root's identity fields followed by
one member per child structure. The spec's example names those members protocol_id,
version, target, guide_rna, cas_system,
delivery and validation. The reference SDK describes the same record with
camelCase TypeScript properties — id, version, target,
guideRNA, casSystem, delivery, strategy,
validation, safety, parameters. The two are not the same vocabulary,
and the difference is not only capitalization: the JSON example carries no strategy and no
safety member at all, and it names the identity field protocol_id where the SDK
uses id.
An implementation that silently guesses at this correspondence will produce records that round-trip inside one codebase and lose fields at every boundary. The rule adopted in this book is therefore explicit: a conformant implementation publishes a field-mapping table as part of its documentation, and the mapping is code-generated from, or tested against, the schema rather than maintained by hand. The table below is the mapping this book uses; an institution that extends the record extends the table in the same commit.
| JSON member | SDK property | Type in the document | Required | Note |
|---|---|---|---|---|
protocol_id | id | string | yes | Immutable across versions; see §3.2 |
version | version | string | yes | Record version, not standard version |
created | created | string (date-time) | yes | SDK type is Date; serialize per §3.7 |
updated | updated | string (date-time) | yes | Set by the store, never by the client |
target | target | object | yes | TargetInfo |
target.gene | target.gene | string | yes | Symbol as written by the submitter |
target.gene_id | target.geneId | string | no | Namespace-qualified external identifier |
target.coordinates | target.coordinates | object | yes | GenomicCoordinates; flattened in the spec example |
target.coordinates.assembly | …assembly | string | yes | Spec example calls this genome |
target.region_type | target.regionType | enum string | yes | Controlled vocabulary, §3.3 |
target.purpose | target.purpose | enum string | yes | Controlled vocabulary, §3.3 |
guide_rna | guideRNA | object | yes | Record fields only; no design guidance in this book |
guide_rna.on_target_score | …onTargetScore | number | yes | Requires a tool-and-version provenance note |
cas_system | casSystem | object | yes | type, variant, source are enums |
delivery | delivery | object | yes | Typed envelope plus open parameters |
delivery.cell_type | delivery.cellType | string | yes | Free text unless the institution binds a vocabulary |
strategy | strategy | object | yes (SDK) | Absent from the spec's JSON example |
validation | validation | array of object | yes | Object in the spec example, array in the SDK |
safety | safety | object | yes (SDK) | Absent from the spec's JSON example |
parameters | parameters? | object | no | Open map; see the units rule in §2.4 |
The three bold rows are the practical work of adopting this standard. The published example is not a subset
of the SDK type; it is an older or looser shape. Two reconciliations are possible, and an institution must
choose one and write the choice down. The first is to treat the SDK type as authoritative and the example as
illustrative, which is the position this book takes: strategy and safety are
required members, and validation is an array so that a protocol can carry more than one
validation record. The second is to accept the spec shape on ingest and normalize it, in which case the
normalizer is itself a piece of conformance-relevant software and its behaviour on the three divergent
members must be covered by test vectors (Chapter 7).
The annotated example below is written in the shape this book uses. Every value that the published sources filled with a plausible-looking biological or instrument value has been replaced with an obvious placeholder in angle brackets. That is deliberate, and it is the second rule of this section: an example document must not be mistakable for a recommended configuration. A reader who copies the example gets a document that fails validation until they supply their own values, which is the correct outcome.
{
"protocol_id": "<INST>-CRISPR-<YYYY>-<NNNN>",
"version": "3",
"created": "2026-01-15T09:20:00+09:00",
"updated": "2026-02-02T14:05:11+09:00",
"target": {
"gene": "<GENE_SYMBOL>",
"gene_id": "ensembl:<ENSG_ID>",
"coordinates": {
"chromosome": "<CHR>",
"start": 0,
"end": 0,
"strand": "+",
"assembly": "<ASSEMBLY_LABEL>"
},
"region_type": "exon",
"purpose": "knockout",
"description": "<one line, no subject-identifying text>"
},
"guide_rna": {
"sequence": "<RECORDED_BY_SUBMITTER>",
"pam": "<PAM_LABEL>",
"on_target_score": null,
"specificity_score": null,
"provenance": {
"tool": "<tool name>",
"tool_version": "<semver or build id>",
"computed": "2026-01-15T09:18:42+09:00"
}
},
"cas_system": {
"type": "Custom",
"variant": "Custom",
"source": "recombinant"
},
"delivery": {
"method": "other",
"cell_type": "<cell line or primary cell label>",
"parameters": {
"<param_name>": { "value": null, "unit": "<unit>", "source": "<SOP id or instrument log>" }
}
},
"strategy": {
"type": "NHEJ",
"expected_outcome": "<free text, as written by the submitter>",
"timeline": "<free text>"
},
"validation": [
{
"method": "NGS",
"description": "<free text>",
"timing": null,
"parameters": { "<param_name>": { "value": null, "unit": "<unit>" } }
}
],
"safety": {
"off_target_analysis": false,
"ethics_approval": false,
"approval_refs": [],
"biosafety": "BSL2",
"regulatory": [],
"risk_level": "low",
"mitigation": []
},
"schema": {
"standard": "WIA-BIO-014",
"standard_version": "1.0",
"profile": "<institution profile name>@<profile version>"
}
}
Three details in that document are worth naming. Numeric fields that have not been measured are
null, not 0; a zero in a score field is a claim, and a false one. The
guide_rna.provenance object is an addition this book recommends rather than something the
published type carries — any stored number that a program computed needs the tool and version that computed
it, or the number cannot be reproduced or retracted. And the schema member records which
standard version and which institutional profile the document was written against, which is what lets a
reader five years from now validate the document with the right schema rather than the current one.
The identifier is the part of the record that other systems copy. Sample inventories, sequencing job tickets, committee minutes, purchase orders and publication supplements will all end up holding a protocol identifier as a string, and none of them will update it. The identifier therefore has one hard requirement that overrides every aesthetic preference: it never changes, and it never gets reused.
The published sources show an identifier of the general shape
CRISPR-<year>-<sequence>. That shape is serviceable inside one institution and
fails as soon as two institutions exchange records, because both will mint a
CRISPR-2026-001. The profile this book recommends prefixes the institution and separates the
record identity from the record version:
protocol-id = institution "-" domain "-" year "-" serial
institution = 2*16 ( ALPHA / DIGIT ) ; registered institutional prefix
domain = "CRISPR" ; fixed for WIA-BIO-014 records
year = 4DIGIT ; year the record was first created
serial = 3*8DIGIT ; zero-padded, monotonic per institution-year
version = 1*DIGIT ; separate field, never part of protocol-id
reference = protocol-id [ "@" version ] ; how other systems cite a specific version
The grammar above is an illustrative profile, not a normative requirement of WIA-BIO-014; the standard
constrains protocol_id only as a string. What matters is that the institution pins one grammar,
validates it in the schema with a pattern, and records the grammar in the profile manifest so that a partner
can tell a malformed identifier from an unfamiliar one.
An alternative worth considering is an opaque identifier — a UUID (RFC 9562) or another random token — with the human-readable label carried in a separate display field. Opaque identifiers cannot collide across institutions, cannot be guessed, and cannot silently encode information that later turns out to be wrong. Their cost is that nobody can read them aloud, and that a mistyped identifier produces a lookup failure rather than a visibly wrong string. A reasonable compromise, and the one most research-administration systems converge on, is an opaque primary key in the store with a structured, unique display identifier that the schema also constrains.
Versioning of the record is the other half of identity, and it is where most implementations go wrong. The
temptation is to treat a protocol record as a row that gets edited: a reviewer notices a wrong assembly
label, someone fixes the field, the updated timestamp moves, and the record now describes
something different from what the committee approved. The rule this book states plainly is that
a corrected record is a new version, and the previous version stays readable forever.
| Situation | protocol_id | version | Result |
|---|---|---|---|
| Typo in a free-text description, record still in draft | unchanged | unchanged | Draft edit; audit event records the field change |
| Any change after the record left draft | unchanged | incremented | New version; prior version retained and citable |
| Wrong value discovered after approval | unchanged | incremented | Correction version, with a reason and an approval reference |
| The project changes target or intent | new | starts at 1 | New protocol, with a supersedes pointer to the old one |
| Record created in error, never executed | unchanged | incremented | Withdrawn version; the record is not deleted |
| Two institutions merge their registries | unchanged | unchanged | Prefixes keep both readable; no renumbering |
The version field is a record counter and has nothing to do with the version of the standard or of the SDK.
Conflating the two is a common and expensive mistake: a store that writes "version": "1.0" into
every record — as the spec's example does — has thrown away the ability to cite a specific revision, and
every downstream citation becomes ambiguous. Use a plain integer counter for the record version (serialized as a digit string, as in the example above), keep the standard
version in the schema member, and let Chapter 8 handle semantic versioning of the standard
itself.
A large part of what makes these records comparable is that certain fields may only hold values from a fixed list. The SDK expresses those lists as TypeScript union types, which means the compiler enforces them in one language and nothing enforces them anywhere else. To make them enforceable at the document boundary they have to be restated as schema enumerations, and to make them reportable they have to be treated as a registry with its own change process — the idea formalized for metadata registries in ISO/IEC 11179.
Two rules apply to every vocabulary in this standard. First, enumerated values are stored in English
in both the Korean and the English edition of a record. The stored token is an identifier, not
display text; translating it inside the document breaks every query, every join and every exported report.
Localization happens in the user interface, from a label table keyed by the token. Second, an escape value
is not a free pass. Several vocabularies here provide one — Custom, other,
Other — and a record that uses it must carry the intended value in an adjacent free-text field,
because an escape value with no explanation is an unanswerable question a year later.
| Field | Vocabulary (SDK type) | Escape value | Reporting use |
|---|---|---|---|
cas_system.type | CasSystemType | Custom | Grouping records by system family |
cas_system.variant | variant union | Custom | Distinguishing revisions of one family |
cas_system.source | source union | none | Procurement and inventory joins |
guide_rna.pam | PAMType | None | Label only; no design meaning in this book |
strategy.type | EditType | none | Portfolio reporting by intent |
target.region_type | region union | other | Search facet |
target.purpose | purpose union | none | Approval routing (Chapter 5) |
delivery.method | method union | other | Label only; no parameter advice |
validation[].method | method union | Other | Evidence completeness checks |
safety.biosafety | BSL1|BSL2|BSL3|BSL4 | none | Stored label for reporting only |
safety.risk_level | low|medium|high | none | Review queue prioritization |
| organism (request-level) | Organism | custom | Reference-data selection |
The safety.biosafety row deserves the emphasis it gets in this book's scope statement. In the
record it is a four-value string and nothing more: a label that says which containment designation the
institution assigned to the work. It is not a facility specification, it does not imply any procedure, and a
validator must never infer one from it. What software may legitimately do with the label is route the record
to the right reviewer, filter a report, and refuse a transition when the label and the approval reference
disagree.
Adding a value to a vocabulary is a schema change, and it is the additive kind: old documents remain valid, old readers encounter a token they do not know. That is survivable only if readers are written to treat an unknown enumerated value as "unrecognized, preserve and flag" rather than as invalid input. Removing or renaming a value is a breaking change, handled in §3.6.
"Valid" is not one question. A document can be perfectly formed JSON with a missing approval reference, or
carry every required field while claiming coordinates from a different genome assembly than the one it
names. Separating validation into layers is what lets an implementation say precisely what it checked — which
is the difference between a validator that is useful as evidence and one that merely returns
true.
| Layer | Question it answers | Mechanism | Error codes (SDK CRISPRErrorCode) |
|---|---|---|---|
| 1 — Structural | Is this a well-formed WIA-BIO-014 document with all required members, correct types and legal enumerated values? | JSON Schema (2020-12) or the OpenAPI 3.0 schema object; runs without any reference data | C004 INVALID_SEQUENCE, C007 INVALID_PARAMETERS |
| 2 — Referential | Do the values resolve? Does the assembly label exist, do the coordinates fall inside it, does every referenced identifier exist in the system it names? | Lookups against reference data and sibling registries; needs network or a local mirror | C008 GENOME_NOT_FOUND, C009 OFF_TARGET_ANALYSIS_FAILED |
| 3 — Policy | Is the institution allowed to proceed? Is an unexpired approval referenced, is the required attestation present, is the requested transition permitted for this submitter? | Institutional rules plus lifecycle state (Chapter 5); configurable, versioned, and auditable | C006 ETHICS_VIOLATION |
| (not a record-validation layer) | Codes that belong to scoring and design functions rather than to record validation (the reference implementation does not actually raise any of them; Chapter 4 §4.5) | Out of this book's scope by the global exclusions; documented as codes only | C001, C002, C003, C005 |
Two honest notes about that table. The spec's §8.4 error table lists six codes; the SDK's enumeration lists
ten, and the four extra ones are the ones an implementer actually needs for record validation. This book
uses the SDK's set, and Chapter 4 gives the full code-to-caller-action mapping. The assignment of codes to
layers, by contrast, is this book's profile: the published sources do not classify their codes, and an
institution that disagrees with the assignment should publish its own mapping rather than leave callers to guess. C010 VALIDATION_FAILED is deliberately left out of the layers: this book reserves it for
"the validator could not run", which is an operational fault in whichever layer it occurs, not a finding
about the record (Chapter 4 §4.5).
The layering has one operational consequence that is easy to state and easy to violate: a validation result must record which layers ran. A structural-only pass reported as "validated" is the single most common way a conformance claim becomes false, because the policy layer — the one the committee cares about — is also the expensive one to run, and it is the first thing a busy pipeline disables. A result that names its layers makes the omission visible instead of invisible.
An error payload should therefore say where the failure is, which layer found it, and what the caller can do. The shape below follows the conventions of RFC 9457 problem details, with a JSON Pointer (RFC 6901) locating the offending member:
{
"type": "https://wiastandards.example/errors/wia-bio-014/validation",
"title": "Protocol record failed validation",
"status": 422,
"protocol_id": "<INST>-CRISPR-2026-0007",
"record_version": "3",
"validator": { "name": "<validator name>", "version": "<semver>", "policy_config": "<profile>@<version>" },
"layers_run": ["structural", "referential", "policy"],
"is_valid": false,
"errors": [
{
"code": "C007",
"layer": "structural",
"pointer": "/validation/0/timing",
"detail": "Required member is null; supply a value or remove the validation entry.",
"caller_action": "fix_document"
},
{
"code": "C006",
"layer": "policy",
"pointer": "/safety/approval_refs",
"detail": "No unexpired approval reference for the requested transition to in_execution.",
"caller_action": "obtain_approval"
}
],
"warnings": [
{
"layer": "referential",
"pointer": "/target/coordinates/assembly",
"detail": "Assembly label resolved from a local mirror dated <date>; confirm currency."
}
],
"validated": "2026-02-02T14:05:11+09:00"
}
The validator object is the part that makes this payload evidence rather than a log line. A
validation result that does not say which validator, which version and which policy configuration produced it
cannot be reproduced, and an irreproducible pass is worth very little when a reviewer asks why a record was
accepted. Chapter 7 treats the signed form of this payload as an artifact in the evidence package.
The reference SDK exports a frozen object named CRISPR_CONSTANTS. Its members include
OPTIMAL_GC_MIN, OPTIMAL_GC_MAX, MAX_POLY_T,
MIN_ON_TARGET_SCORE, MIN_SPECIFICITY_SCORE, MAX_OFF_TARGETS,
BE_WINDOW_START, BE_WINDOW_END, PE_PBS_LENGTH, and four members whose
names begin with TYPICAL_. The validator compares submitted values against several of them and
emits warnings or errors accordingly.
This book states the following about those constants, plainly and once, because a reader who misunderstands this will misuse the standard. They are unsourced software defaults. The specification's reference section for scientific literature is empty, so no published basis for any of the numeric values is available in the standard's own documents. They are not scientific recommendations, they are not design thresholds, they are not acceptance criteria, and no value in that object should be read as guidance about how to plan an experiment. What they are is the configuration of one reference implementation's warning behaviour — and configuration belongs in a configuration file, under version control, owned by the institution.
The practical instruction is to externalize them. A validator that reads its thresholds from a named, versioned policy configuration can (a) be audited, because the configuration is a file with a history, (b) be reproduced, because a validation result cites the configuration version, and (c) be corrected without a code release. A validator with the numbers compiled in can do none of these things, and worse, it makes its defaults look like the standard's requirements.
{
"policy_config": "<institution>-crispr-record-policy",
"policy_version": "2026.1",
"owner": "<role, e.g. research data governance>",
"basis": "Institutional decision; the standard supplies no sourced values for these keys.",
"thresholds": {
"OPTIMAL_GC_MIN": { "value": null, "action": "warn", "set_by": "<committee or role>" },
"OPTIMAL_GC_MAX": { "value": null, "action": "warn", "set_by": "<committee or role>" },
"MAX_POLY_T": { "value": null, "action": "warn", "set_by": "<committee or role>" },
"MIN_ON_TARGET_SCORE": { "value": null, "action": "warn", "set_by": "<committee or role>" },
"MIN_SPECIFICITY_SCORE": { "value": null, "action": "warn", "set_by": "<committee or role>" },
"MAX_OFF_TARGETS": { "value": null, "action": "warn", "set_by": "<committee or role>" }
},
"record_completeness": {
"require_approval_ref_before_execution": true,
"require_provenance_for_computed_scores": true,
"require_denominator_with_percentage": true
}
}
Note what is null in that file and what is not. The numeric thresholds are left empty because
this book has no sourced value to put there and will not invent one; the institution fills them or removes
the key. The three completeness rules, by contrast, are set to true, because they are
record-keeping requirements that follow from the data model rather than claims about biology. That is the
line this chapter asks implementers to hold: software may enforce whatever the institution decides about its
own records, and must not dress an institutional default as a scientific fact.
One further consequence: because these thresholds produce warnings rather than structural failures, they must not gate a lifecycle transition on their own. A warning that blocks a transition is an error with bad manners, and an error whose basis is an unsourced default is an audit finding waiting to happen.
Any record format that outlives one project will change. The useful distinction is between changes that old readers survive and changes that break them, and the discipline is to know which one you are making before you merge it.
| Change | Class | Effect on existing records | Required action |
|---|---|---|---|
| Add an optional member | Additive | Remain valid | Minor schema version; note in changelog |
| Add a value to a vocabulary | Additive | Remain valid | Minor version; readers must tolerate unknown tokens |
| Relax a constraint (widen a range, drop a pattern) | Additive | Remain valid | Minor version |
| Add a required member | Breaking | Every existing record becomes invalid | Major version, or add as optional with a documented default |
| Rename a member | Breaking | Readers lose the field silently | Major version; never rename in place |
| Remove a member | Breaking | Data loss on re-serialization | Deprecate first, remove at the next major version |
| Change a member's type | Breaking | Parse or coercion failures | Major version; new member name is usually safer |
| Remove or rename a vocabulary value | Breaking | Existing records hold an illegal token | Major version plus a documented mapping |
| Tighten a constraint | Breaking in effect | Some existing records become invalid | Treat as breaking; survey the corpus first |
The rule that covers most of the table is short: rename or remove implies a version bump, and a member name is never reused for a different meaning. The reason to be strict about the second half is that a reused name produces records that validate and lie, which is worse than records that fail. One source detail needs noting: the Annex E schema-evolution note in the PHASE documents says that renaming or removing a field "MUST trigger a minor version bump". This book's profile classifies those changes as breaking and therefore major, following the Annex H meaning of a major version; an institution should record which of the two readings it applies.
The companion rule is about the reader rather than the schema. When a validator or a store encounters a
member it does not know, it has three choices: reject the document, drop the member, or preserve it. Only
the third is acceptable for a record that will be exchanged between institutions. Dropping unknown members
is silent data loss, and it is loss that surfaces at the worst possible time — when the partner who sent the
record asks why the field they populated is missing from the copy you returned. An implementation should
keep unknown members in a preserved extension area, round-trip them byte-for-byte where possible, and report
their presence as a warning rather than an error. Where an institution needs to add fields of its own, a
reserved prefix convention such as x_, or a single nested extensions object, keeps
local additions from colliding with future standard members.
Migration between versions is a record-producing activity in its own right. A record rewritten to a new schema version is a new record version with the migration tool and its version cited, and the pre-migration document is retained. Chapter 5 covers the audit event that migration writes; Chapter 8 covers how the standard itself is versioned and deprecated.
The failures in this section are not exotic. They are the ones that quietly corrupt a record corpus over a few years, and every one of them has been observed in research-data systems of exactly this shape.
Percentages stored without their denominators. The record model carries both read counts and the percentages derived from them. JSON numbers are typically parsed as IEEE 754 binary64 doubles, so a percentage is an approximation of a ratio, and an approximation cannot be re-checked. Worse, a percentage with no denominator hides its own reliability: the same stored value can come from a handful of reads or a very large number, and no reader can tell the difference. The rule stated in Chapter 2 and enforced here as a completeness check is that a computed percentage is never stored without the numerator and denominator that produced it, and that the percentage is treated as a convenience for display rather than as the record of the measurement.
Date objects serialized without an offset. The SDK types declare
created, updated and ValidationResult.validated as
Date. On the wire they must be RFC 3339 date-time strings with an explicit UTC offset. A
timestamp written as a local wall-clock string is unorderable the moment a second site touches the record,
and a timestamp written as a numeric epoch is unreadable to the human reviewer. Store the offset, keep the
precision you actually have, and never accept a client-supplied value for updated — Chapter 5
explains why the store owns that field.
Non-ASCII text that is not normalized. Free-text members — descriptions, cell-type labels, mitigation notes — will contain Korean, accented Latin, and occasionally Greek letters used in gene and protein naming. Documents must be UTF-8 (RFC 3629), and text destined for comparison or search should be normalized, conventionally to NFC as described in Unicode Annex #15. Without normalization, two visually identical labels compare unequal, deduplication fails, and a search for a record returns nothing. This is also the place to enforce the privacy rule from Chapter 2: free-text members are exactly where subject-identifying text tends to appear, and a schema cannot detect it. A submission-time check plus a reviewer's attention is the only control that works.
Lossy re-export. Sooner or later someone will export the corpus to a spreadsheet for a report. CSV (RFC 4180) has no nesting, no types, no null distinct from empty, and no way to represent an array of validation records except by inventing a convention. An export is a one-way projection and must be labelled as such: derived, dated, not a record. The failure mode to guard against is the round trip — an exported sheet edited by hand and re-imported, arriving with numbers reformatted by the spreadsheet, leading zeros stripped from identifiers, and dates silently rewritten to the exporter's locale. If hand editing is a real requirement, the intake path is a generated template that the validator accepts, not a re-import of an export.
Digests over non-canonical JSON. Later chapters hash documents: the evidence envelope of Chapter 4, the audit-event chain of Chapter 5 and the evidence package of Chapter 7 all bind content by SHA-256 digest. Two serializations of the same record differ in key order, whitespace and number formatting, and a digest over either is stable only by accident. Whenever a digest is taken over a JSON document, the document is first canonicalized with the JSON Canonicalization Scheme (RFC 8785), and the canonicalization method is recorded beside the digest. This is the canonical-JSON rule the later chapters refer to.
| Pitfall | Symptom when it bites | Rule |
|---|---|---|
| Percentage without denominator | A result cannot be re-derived or defended | Store counts; treat the percentage as display |
| Zero used for "not measured" | Reports average in values that were never taken | null for absent, zero only for measured zero |
| Timestamp without offset | Audit events cannot be ordered across sites | RFC 3339 with explicit offset, store-assigned |
| Unnormalized Unicode | Duplicate records; search misses | UTF-8 plus NFC before comparison |
| Unknown members dropped on read | Partner's fields vanish from the returned copy | Preserve and warn; never silently discard |
| Identifier reused or renumbered | Citations in minutes and tickets point at the wrong record | Identifiers are immutable and never reused |
| Enumerated value translated in the document | Queries and joins fail across editions | Tokens in English; translate in the interface |
| Example values copied as configuration | Placeholder text becomes a real record | Examples use visibly invalid placeholders |
| CSV round trip | Identifiers and dates silently rewritten | Export is derived and one-way |
A protocol record becomes usable outside the program that made it only when three things are pinned: the document shape, the identifier, and the validation gate. The document shape requires an explicit mapping between the JSON member names and the SDK properties, including the three members where the published sources disagree with each other, and requires examples whose values cannot be mistaken for recommendations. The identifier must be institution-scoped, immutable and never reused, with the record version kept as a separate field and a correction expressed as a new version rather than an edit. The validation gate must be layered into structural, referential and policy checks, must report which layers ran, and must cite the validator and policy configuration that produced the result. Everything numeric that the reference implementation ships as a threshold is an unsourced software default and belongs in an institutional configuration file, never in prose that a reader could mistake for guidance. Chapter 4 takes the same record and describes the API surface that produces and checks it.