Chapter 5: The Sample Lifecycle Protocol — State Transitions, Chain of Custody and Excursions

Chapter 4 described the interfaces: which endpoints exist, which SDK methods to call and which error codes come back. This chapter describes the behaviour behind them. A sample moves through a fixed set of states. Every move is written into a hash-linked custody record. The record has to survive freezers that warm up, spills and requests from outside the institution. We follow a single plasma sample from the needle to the requesting laboratory and note, at each step, what WIA-BIO-019 requires, what the reference SDK actually does, and where the two differ.

5.1 The Lifecycle as a State Machine

The TypeScript types of WIA-BIO-019 declare ten lifecycle states in the enum SampleStatus. Read in order, they tell the life story of a specimen: it is drawn, shipped, received, processed and stored. Later it is taken out, used, and then either put back, used up or destroyed. Every Sample and every Aliquot carries exactly one of these values in its status field.

SampleStatusMeaningCustody action that normally records entry into the state
collectedMaterial has left the donor and carries a labelcollection
in_transitBetween sites, in a transport containertransport (or transfer to another institution)
receivedAccepted at the biobank; identity and condition checkedreceipt
processingBeing centrifuged, separated, extracted or fixedprocessing, aliquoting
storedAt its assigned freezer/rack/box/positionstorage
retrievedRemoved from storage for a specific purposeretrieval
in_useBeing analysed or held by an approved userno dedicated action (see below)
returnedUnused remainder brought backreturn
depletedNo usable material leftthe event that consumed the last volume
destroyedDisposed of under an SOPdestruction

The events that move a sample between states are listed in a second enum, CustodyAction, which has eleven values. Nine of them correspond one-to-one to the nine required tracking points of spec §7.1.2. The other two refine that list. The spec folds aliquoting into "Processing (aliquoting, centrifugation)", while the SDK gives aliquoting its own action. The SDK also adds quality_check, an event that leaves the state unchanged and is used, among other things, for temperature excursions.

Tracking point (spec §7.1.2)CustodyActionTypical state change
1. Collection (donor → collector)collection(start) → collected
2. Transport (clinic → biobank)transportcollected → in_transit
3. Receiptreceiptin_transit → received
4. Processingprocessing, aliquotingreceived → processing
5. Storagestorageprocessing → stored
6. Retrievalretrievalstored → retrieved
7. Return (to storage or disposal)returnin_use → returned
8. Transfer (to another institution)transferretrieved → in_transit
9. Destructiondestructionany non-terminal state → destroyed
(SDK only)quality_checknone; the state stays the same
Figure 5-1. SampleStatus transitions with the CustodyAction that records each one Top row from left to right: collected, in_transit, received, processing, stored. Middle row from right to left: retrieved, in_use, returned; returned leads back up to stored. Bottom row: depleted below in_use and destroyed, which is reachable from every non-terminal state. collected in_transit received processing stored transport receipt processing storage (+ aliquoting) retrieved in_use returned retrieval use / transfer return storage (back on the shelf) depleted last volume consumed destroyed destruction — from any non-terminal state quality_check: an event in any state, no state change (e.g. excursions)
Figure 5-1. Lifecycle states and the custody actions that record the transitions. The transition set is this book's reading of the enums; v1.0 does not publish it as a normative table.

What the reference SDK enforces, and what it does not

The figure shows how a laboratory would expect the states to behave. The v1.0 specification lists the states and tracking points but has no normative transition table, and the reference SDK does not enforce one. We ran it and found four things implementers need to know:

A production system therefore needs its own guard that changes the status and writes the custody event in one step. The sketch below is one way to build it. The transition table is the book's proposal and is not normative. v1.0 has no error code for an illegal transition, so the sketch throws a plain error; Chapter 8 lists this as a gap.

import { BioBankingSDK, SampleStatus as S, CustodyAction as A } from '@wia/bio-019';

// Book proposal - WIA-BIO-019 v1.0 does not define or enforce these transitions.
const ALLOWED: Record<S, S[]> = {
  [S.COLLECTED]:  [S.IN_TRANSIT, S.RECEIVED, S.DESTROYED],
  [S.IN_TRANSIT]: [S.RECEIVED, S.DESTROYED],
  [S.RECEIVED]:   [S.PROCESSING, S.STORED, S.DESTROYED],
  [S.PROCESSING]: [S.STORED, S.DEPLETED, S.DESTROYED],
  [S.STORED]:     [S.RETRIEVED, S.DESTROYED],
  [S.RETRIEVED]:  [S.IN_USE, S.IN_TRANSIT, S.STORED, S.DESTROYED],
  [S.IN_USE]:     [S.RETURNED, S.DEPLETED, S.DESTROYED],
  [S.RETURNED]:   [S.STORED, S.DESTROYED],
  [S.DEPLETED]:   [],
  [S.DESTROYED]:  [],
};

const DEFAULT_ACTION: Partial<Record<S, A>> = {
  [S.IN_TRANSIT]: A.TRANSPORT, [S.RECEIVED]: A.RECEIPT, [S.PROCESSING]: A.PROCESSING,
  [S.STORED]: A.STORAGE,       [S.RETRIEVED]: A.RETRIEVAL, [S.RETURNED]: A.RETURN,
  [S.DESTROYED]: A.DESTRUCTION,
};

export function transition(
  sdk: BioBankingSDK, sampleId: string, to: S,
  who: { operator: string; location: string },
  opts: { action?: A; temperature?: number; notes?: string } = {},
) {
  const current = sdk.getSample(sampleId).status;
  if (!ALLOWED[current].includes(to)) {
    throw new Error(`Illegal transition ${current} -> ${to} for ${sampleId}`);
  }
  const action = opts.action ?? DEFAULT_ACTION[to];
  if (!action) throw new Error(`State ${to} needs an explicit custody action`);

  // Write the custody event first: if it fails, the status must not move.
  const event = sdk.addCustodyEvent(sampleId, {
    action, operator: who.operator, location: who.location,
    condition: opts.temperature !== undefined ? { temperature: opts.temperature } : undefined,
    notes: opts.notes ?? `${current} -> ${to}`,
  });
  sdk.updateSample(sampleId, { status: to });
  return event;
}

in_use and depleted have no default action. The caller chooses: transfer when the material leaves the institution, and processing or aliquoting when it is consumed in house. Forcing that choice means every status change leaves a custody event with a named operator behind it.

5.2 Collection: Where the Record Begins

Most of what can go wrong with a sample goes wrong in the first hour (Chapter 1). The collection protocol (spec §3.1, §3.2 and the Appendix A checklist) is therefore short and strict. Its job is to create a record that is correct from the first minute.

Before the needle

The Appendix A pre-collection checklist asks for five things. The collector verifies the donor's consent, confirms the donor's identity and checks fasting status where the protocol requires it. Collection materials are prepared, and tubes or containers are labelled with the sample ID. Labelling before collection is deliberate. An unlabelled tube that is full of blood is the most common way to swap two donors. The consent check at this point is operational: it confirms that a valid consent record exists and that this collection falls within its scope. Chapter 6 covers the consent model itself.

Blood

Spec §3.2.1 gives a six-step blood procedure:

  1. Verify donor identity and consent.
  2. Select the tubes appropriate for the intended tests.
  3. Follow the order of draw: sterile (culture) → coagulation → serum → heparin → EDTA. The order limits carry-over of additives from one tube to the next.
  4. Label the tubes immediately with donor ID, date and time.
  5. Mix anticoagulant tubes by gentle inversion, 8–10 times. Shaking causes hemolysis.
  6. Record the collection time and conditions.

Step 6 starts the clocks. The spec sets maximum times from collection to processing: two hours for plasma and serum, four hours for whole blood, and eight hours for PBMC isolation. These limits are only enforceable if the collection time is recorded precisely and as part of the sample record. It is not enough for it to sit on a paper requisition form. In WIA terms the collection time is the timestamp of the collection event and the collectionDate of the Sample. When the sample is received, the receiving technician can compare the receipt time with the collection time and flag any sample that missed its window.

Tissue

Tissue (spec §3.2.2) is collected during surgery or biopsy and placed in a sterile container. If processing is delayed it is kept on ice. It is then either snap-frozen in liquid nitrogen within 30 minutes or placed in formalin within one hour, at a tissue-to-formalin volume ratio of 1:10. Pieces for freezing should be 5–10 mm³. The anatomical site and pathology are documented, and the specimen is photographed if the research needs it. The interval that matters here is the time between loss of blood supply and stabilization. The operating room and pathology each record part of it, and the biobank record should hold both timestamps.

What the collection event must capture

Spec §7.1.2 lists the fields every custody event records: an ISO 8601 timestamp, the operator ID, the location, the temperature where applicable, the sample condition, and a digital signature or hash. For the collection event, "condition" should include the tube type and, for blood, the drawn volume. The state example in the spec does exactly this, as "tube_type": "EDTA" and "volume": 10.0. The SDK's condition object has temperature, volume and a free-text appearance field, but no tube type. An implementation that follows the spec example should carry tube type in appearance or notes until the type gains a field. Because of the SDK's hard-coded first event (Section 5.1), a site that registers samples through the SDK should add a second, correct collection event with the real collector and clinic. Alternatively, it can register samples at the collection site itself.

5.3 Chain of Custody as a Hash Chain

A custody log is only worth something if nobody can quietly edit it afterwards. WIA-BIO-019 §7.1.1 makes the log tamper-evident by chaining events together:

COC = H(S0) → H(S1) → H(S2) → ... → H(Sn)
Hash: SHA-256(JSON.stringify(state))

Each state Sn is a JSON object that includes the hash of the previous state. The spec's example state is:

{
  "timestamp": "2025-12-26T10:30:00Z",
  "action": "collection",
  "operator": "TECH-001",
  "location": "CLINIC-A",
  "sample_id": "SAM-123456",
  "condition": {
    "temperature": 22,
    "volume": 10.0,
    "tube_type": "EDTA"
  },
  "previous_hash": "a4b2c1d3e4f5..."
}

SHA-256 (specified in FIPS 180-4) maps any input to a 256-bit digest, and changing even a single bit of the input produces an unrelated digest. Because each event embeds the digest of its predecessor, editing any past event changes its hash. That hash then no longer matches the previous_hash stored in the next event. A verifier walks the chain from the start and stops at the first mismatch.

Figure 5-2. Three linked custody events Three event boxes, collection, transport and receipt. Each hash field feeds the previousHash field of the next box. The hash of the last event is the head of the chain and should be anchored outside the database. Event 0 · collection operator: SYSTEM location: BIOBANK previousHash: (none) hash: 9a50d5dd… Event 1 · transport operator: COUR-07 condition: 4 °C previousHash: 9a50d5dd… hash: e945ec97… Event 2 · receipt operator: TECH-014 condition: 6 °C previousHash: e945ec97… hash: 26189840… (head) anchor the head outside the database (Section 5.4)
Figure 5-2. Each event stores the hash of its predecessor. The hash prefixes come from one test run; they change every run because the event ID and timestamp are part of the hashed data.

How the SDK builds and checks the chain

In the SDK, addCustodyEvent() takes the hash of the last event in the chain as previousHash. It then builds the event and computes its hash with a private helper, calculateEventHash(). The helper hashes the JSON serialization of exactly nine fields in this order: id, timestamp (as toISOString()), action, operator, location, sampleId, condition, previousHash and notes. The hash field itself is excluded, and so is the optional signature.

verifyChainOfCustody(sampleId) performs two checks per event. It recomputes the hash and compares it with the stored one. For every event after the first, it also checks that previousHash equals the stored hash of the event before it. On the first failure it sets chain.intact = false and returns false. It does not throw B010 CHAIN_OF_CUSTODY_BROKEN. The SDK raises that code only when no chain exists for the sample at all.

The following session creates three events and then tries three kinds of tampering. The // → comments show the values we observed when running the reference SDK. The demonstration relies on a detail that is itself a finding: getChainOfCustody() returns the SDK's live internal object, not a copy, so any caller can mutate the stored history.

import { BioBankingSDK, SampleType, StorageCondition, CustodyAction } from '@wia/bio-019';

const sdk = new BioBankingSDK();
sdk.registerSample({
  id: 'SAM-7K2Q9X', type: SampleType.PLASMA,
  donor: { donorId: 'D-789012', consentId: 'CNS-456789' },
  volume: 10.5, volumeUnit: 'mL',
  storageCondition: StorageCondition.ULTRA_LOW_FREEZER,
});                                              // opens the chain with 'collection'
sdk.addCustodyEvent('SAM-7K2Q9X', { action: CustodyAction.TRANSPORT,
  operator: 'COUR-07', location: 'CLINIC-A', condition: { temperature: 4 } });
sdk.addCustodyEvent('SAM-7K2Q9X', { action: CustodyAction.RECEIPT,
  operator: 'TECH-014', location: 'BIOBANK-RECEIVING', condition: { temperature: 6 } });

const chain = sdk.getChainOfCustody('SAM-7K2Q9X');   // live object, not a copy
chain.events.map(e => e.action);            // → ['collection', 'transport', 'receipt']
sdk.verifyChainOfCustody('SAM-7K2Q9X');     // → true

// Tamper 1: rewrite a recorded transport temperature
chain.events[1].condition!.temperature = 22;
sdk.verifyChainOfCustody('SAM-7K2Q9X');     // → false (event 1 hash mismatch)
chain.events[1].condition!.temperature = 4; // undo

// Tamper 2: delete the middle event
const [removed] = chain.events.splice(1, 1);
sdk.verifyChainOfCustody('SAM-7K2Q9X');     // → false (receipt.previousHash dangles)
chain.events.splice(1, 0, removed);         // undo

// Tamper 3: silently drop the newest event
chain.events.pop();
sdk.verifyChainOfCustody('SAM-7K2Q9X');     // → true   <-- not detected

Editing and deleting from the middle are caught. Truncation is not. Removing the most recent events leaves a shorter chain that is still perfectly consistent. For a biobank, the events most likely to be "forgotten" are the recent ones: a retrieval that should not have happened, or an excursion nobody wants on record. The next section explains why this happens and what closes the gap.

5.4 Canonicalization and the Limits of a Bare Hash Chain

Two problems sit behind a hash chain. First, two parties must turn the same event into exactly the same bytes, or their hashes will disagree even though nothing was tampered with. Second, a chain of hashes proves internal consistency and nothing more.

Same event, different bytes

The spec's rule is "SHA-256 of JSON.stringify(state)". JSON.stringify writes object keys in insertion order, so two implementations that build the same object in a different order produce different hashes:

import { createHash } from 'crypto';
const h = (o: unknown) => createHash('sha256').update(JSON.stringify(o)).digest('hex').slice(0, 16);

h({ a: 1, b: 2 });                  // → '43258cff783fe703'
h({ b: 2, a: 1 });                  // → '3fb75453225c732a'   same data, other key order
h({ sample_id: 'SAM-1' });          // → '6085f0222a4eb45e'   spec field naming
h({ sampleId: 'SAM-1' });           // → 'c04590f0e6183fc8'   SDK field naming
JSON.stringify({ previousHash: undefined, n: 1 });   // → '{"n":1}'  key vanishes

The last line matters for the first event. It has no predecessor, so its previousHash is undefined and the key disappears from the serialization. An implementation that writes null or an empty string there will compute a different hash. The spec and the SDK differ in four such details:

The practical consequence is that a chain written by the SDK cannot be verified by an implementation that follows the spec example literally, and vice versa. Do not claim cross-implementation hash compatibility for v1.0. Within one system this does not matter as long as the same code writes and verifies. It matters as soon as custody records cross an institutional boundary, which is exactly when a chain of custody is most needed. A robust fix is to hash a canonical form. RFC 8785, the JSON Canonicalization Scheme (JCS), fixes key ordering, number formatting and string escaping. On top of it, a profile should fix the field names, the field set, the timestamp precision and how an absent previous hash is written.

Consistency is not authenticity

Anyone who can write to the database can also rewrite the whole chain. They can change an old event, recompute its hash, then recompute every later event's previousHash and hash. We did this against the reference SDK, and verifyChainOfCustody() returned true. The chain proves that the events agree with one another. It does not prove who wrote them, or that nothing was added, removed or rewritten from the start. The table summarizes which threats a bare chain detects.

ThreatDetected by a bare hash chain?Mitigation
Edit a field in a past eventYes (hash mismatch)—
Delete or reorder events in the middleYes (linkage mismatch)—
Drop the newest events (truncation)NoAnchor the head hash and event count externally after each event, or at least daily
Rewrite the entire chainNoPer-event digital signatures by the operator or a service key; external anchoring
False event written by an insiderNoSignatures bound to authenticated users; four-eyes for critical actions; audit log outside the application
Two implementations disagree on the hashReports false tamperingCanonical serialization (RFC 8785) plus a fixed field profile

The CustodyEvent type already has an optional signature field, and spec §7.1.2 asks for a "digital signature or hash". A deployment that needs the record to stand up in a dispute should fill the signature field. One option is Ed25519 (RFC 8032), computed over the canonical event bytes with a key held in an HSM or key vault. It should also anchor the current head hash somewhere the database administrator cannot rewrite. Suitable places are a trusted timestamping authority (RFC 3161), a write-once audit store, or a partner institution's system. Chapter 7 turns these recommendations into test vectors, and Chapter 8 lists them as v1.x corrections.

5.5 Barcode and RFID Tracking

A custody chain is only as trustworthy as the way each event is captured. If a technician types the sample ID by hand, the chain faithfully records a typo. Spec §7.2 therefore expects machine-readable identification at every station.

Labels and tags must survive the tier they go to: cryogenic-resistant down to −196 °C, solvent-resistant and permanently adhesive (spec §3.3). Test RFID performance at the storage temperature before relying on it. Frost, metal racks and liquid nitrogen all affect read range, and a tag that reads on the bench may not read inside a dewar. Automatic capture changes the chain in a second way. A bulk read produces many events at once, attributed to a reader device rather than a person. The operator field should then name the device, and the event should reference the user session that triggered the scan. Otherwise, "who moved this box" becomes unanswerable.

5.6 Temperature Monitoring and the Excursion Protocol

Storage failures rarely announce themselves. A compressor slowly loses capacity, a door gasket hardens, or a liquid nitrogen level sinks over a long weekend. Spec §7.3 requires continuous monitoring with data loggers at one-minute intervals, reporting to a monitoring system with alarms. It also requires quarterly temperature mapping of critical equipment, to find warm and cold spots and to place the most valuable samples in the most stable zones.

EquipmentNominal temperatureAlarm threshold (spec §7.3)First response
ULT freezer−80 °Cabove −75 °CCheck door, alarm cause and trend; prepare backup capacity. Transfer if the unit passes −70 °C (Section 5.7).
LN₂ tank (vapor or liquid phase)−150 °C or colderabove −150 °CCheck the LN₂ level and auto-fill supply; refill. Consider transfer if the level cannot be restored.
Refrigerator4 °Cabove 8 °CCheck the door and the unit; move short-term samples, which are only meant to stay here for less than 72 hours anyway.

The spec does not set an alarm threshold for −20 °C freezers or for ambient storage. A site should define them in its SOP, based on its own temperature mapping, rather than borrow numbers from another tier.

Six steps from alarm to closure

Spec §7.3 defines the excursion flow as six steps. The right-hand column shows where each one lands in the WIA data model.

StepWhat happensWhere it is recorded
1. Alarm triggeredLogger reading crosses the thresholdEquipmentAlarm (type: high_temp, power_failure, door_open, low_ln2 …; severity: info / warning / critical)
2. Automatic notificationEmail, SMS and phone to the on-call chainMonitoring system; delivery receipts kept
3. Staff response loggedSomeone acknowledges and actsacknowledged, acknowledgedBy on the alarm
4. Sample integrity assessmentWhich samples, how warm, for how longOne TemperatureExcursion per affected sample (targetTemp, actualTempRange, duration, severity)
5. Decision: keep, flag or discardPer sample, by an authorized personimpactAssessment; a status change to destroyed for discards
6. Documentation and resolutionCause, fix, closurecorrectiveAction; resolution and resolvedAt on the alarm; CAPA record (Chapter 7)

Two scales of severity are in play, and they should not be confused. EquipmentAlarm.severity (info / warning / critical) describes the equipment event. TemperatureExcursion.severity (minor / moderate / severe / critical) describes the consequence for one sample. The same power cut can be minor for DNA in the back of a well-mapped freezer and severe for RNA in the door rack. v1.0 names the four excursion levels but gives no criteria for them. The policy below is an example of how a site might define them in its SOP. It is not normative, and the right criteria depend on the analytes.

Excursion severityExample criterion (site SOP)Example decision
minorThreshold crossed briefly (e.g. door opening); the sample itself is not expected to have warmed materiallyKeep; the event is on record
moderateSustained excursion, but the sample stayed well inside the frozen range for its typeKeep and flag; disclose to requesters
severeThe sample probably warmed into a range where its analytes are known to changeFlag; QC test before any release; restrict uses
criticalThaw, or thaw cannot be ruled outQuarantine; discard unless QC shows fitness (irreplaceable samples are always tested first)

The SDK records a per-sample excursion like this (the values are illustrative):

sdk.recordTemperatureExcursion('SAM-7K2Q9X', {
  startTime:  new Date('2026-03-14T02:10:00Z'),
  endTime:    new Date('2026-03-14T03:45:00Z'),
  targetTemp: -80,
  actualTempRange: { min: -80, max: -68 },
  duration:   95,                        // minutes
  severity:   'moderate',
  correctiveAction: 'Door gasket reseated; unit back below -75 °C at 03:45Z',
});
// Appends to sample.quality.temperatureExcursions and adds a custody event:
//   action 'quality_check', operator 'SYSTEM',
//   notes  'Temperature excursion recorded: moderate severity, 95 minutes'

Three details matter when you rely on this method. First, it does not check that duration agrees with startTime and endTime. The caller should compute it rather than type it in. Second, the custody event is always attributed to SYSTEM, so the person who made the keep/flag/discard decision has to be recorded elsewhere, for example in impactAssessment or in a separate, signed event. Third, the error code B008 TEMPERATURE_EXCURSION exists in BioErrorCode, but no SDK method raises it. A sensible use is at release time. If a request selects a sample with an unresolved severe or critical excursion, the fulfilment step should refuse with B008 until someone has recorded an assessment.

Finally, the Sample Integrity Score from Chapter 3 uses a single temperature deviation ΔT. An excursion record holds the peak (actualTempRange.max) and the duration, but the score uses only the peak, so a ten-minute spike and a ten-hour plateau at the same peak score the same. Until v1.x adds a time-weighted term (Chapter 8), treat SIS as an aid to the excursion decision rather than the decision itself.

5.7 Emergency Protocols

Freezer failure

Spec §10.3.1 gives the response to a failing freezer as a sequence with a hard first deadline: the alarm must be acknowledged within five minutes. The responder then assesses three things: the temperature reading, whether the unit is still running, and how long it has been failing. If the cabinet has passed −70 °C, samples are moved to a backup freezer. Dry ice keeps them cold during the move; it sublimes at about −78.5 °C at atmospheric pressure, close to the −80 °C set point. If the unit is beyond recovery, critical samples are salvaged first, the excursion is documented and sample integrity is assessed. The incident ends with a root-cause analysis and preventive measures.

When there is not enough time or backup capacity to move everything, the spec fixes a priority order:

  1. Irreplaceable samples, such as unique cohorts, rare disease material and longitudinal series with no duplicates
  2. Living cells, whose viability is the most temperature-sensitive property in the bank
  3. RNA
  4. DNA, proteins and plasma

The order only works if it is prepared in advance. The LIMS must be able to produce a per-freezer pick list sorted by this priority, and the temperature map should already have placed the first two categories where they are easiest to reach. Every moved box generates retrieval and storage events with the new location. Every sample in the failed unit gets a TemperatureExcursion, including the ones that were moved in time.

Figure 5-3. Freezer-failure response sequence Four lifelines: monitoring system, on-call staff, QA or biobank manager, and backup freezer. The monitoring system raises an alarm above −75 °C and notifies staff. Staff acknowledge within five minutes and assess. If the unit is above −70 °C, they transfer samples on dry ice to the backup freezer in priority order and record custody events. They then report excursions to QA, which leads the integrity assessment, the keep/flag/discard decisions, root-cause analysis and CAPA. Monitoring system On-call staff QA / manager Backup freezer 1. alarm > −75 °C, notify 2. acknowledge (≤ 5 min) 3. assess: temperature, running?, time to failure 4. if > −70 °C: move on dry ice, priority order retrieval + storage events, new locations 5. excursion per sample 6. integrity assessment: keep / flag / discard 7. root cause analysis, preventive action (CAPA)
Figure 5-3. Freezer-failure response following spec §10.3.1, with the custody and excursion records written at each step.

Liquid nitrogen shortage

When the LN₂ supply is at risk, spec §10.3.2 asks the site to conserve LN₂ use first and to arrange an emergency delivery. If no LN₂ can be obtained, the standard states that cells may be moved to −80 °C temporarily, for at most 48 hours, with dry ice as backup. The 48-hour limit is the standard's own guidance and is not a general property of every cell type; a site's SOP should say how it applies to its cell types. After the event, viability is reassessed (spec §10.3.2). The excursion records should include the time spent at −80 °C, because that is what the viability result will be interpreted against.

Spills

A biological spill and a cryogenic spill need opposite reflexes, and spec §10.3.3 treats them separately.

Biological spillCryogenic (LN₂) spill
Main hazardInfection from the materialOxygen displacement as LN₂ evaporates; cold burns
First actionAlert others; evacuate if necessaryEvacuate the area
ProtectionGloves, lab coat, face protectionDo not touch; ventilate
Clean-upCover with absorbent; disinfect with 10% bleach, 10 min contact time; clean with soap and water; dispose as biohazard wasteLet it evaporate
Re-entryAfter clean-upOnly after an oxygen monitor confirms safe levels
RecordIncident report; destruction events for lost samplesIncident report; excursions for samples in the affected tank

The danger of a nitrogen spill is easy to underestimate. Liquid nitrogen expands roughly seven-hundredfold when it turns into gas at room temperature. A small spill in a closed storage room can lower the oxygen concentration without any warning sign a person can smell or see. For the same reason, spec §4.1.2 calls for oxygen monitors in LN₂ storage areas.

5.8 The Request and Distribution Protocol

The last stage of the lifecycle is often the reason the bank exists: releasing material to researchers. It is also where consent, custody and quality converge. A sample leaves only if the donor's consent covers the use, the custody record is intact, and the quality is fit for the purpose.

Request states

A SampleRequest moves through the six values of RequestStatus:

pending ──► under_review ──► approved ──► fulfilled
   │              │
   │              └──► rejected
   └──────────────────────────────► cancelled   (also possible from under_review / approved)

The access committee (spec §8.2.2) reviews requests during under_review. Its tasks are to evaluate scientific merit, ensure consent compatibility and approve the Material Transfer Agreement (MTA). Its decision criteria are the scientific quality of the proposal, feasibility, ethical approval of the requesting project, alignment with donor consent, and sample availability. The SampleRequest type has fields for the results: ethicsApproval (the requester's IRB approval number), mta, approvedBy, approvalDate and notes.

What the request asks for

Each entry in samples is a RequestedSample with a sampleId, a volumeRequested with its volumeUnit, and one of two fates. permanent: true means the material is consumed or kept by the requester. permanent: false with a returnDate means the remainder comes back. The distinction decides how the lifecycle ends. A permanent release ends in depleted for that aliquot, with a transfer event naming the receiving institution. A returnable release goes retrieved → in_transit → (at the requester) → returned → stored. The returned material also needs a quality flag, because it has been outside the bank's control.

Three mismatches between the wire format and the SDK types are worth handling explicitly:

Checks at fulfilment

When an approved request is fulfilled, the system should repeat a short set of checks, each tied to a v1.0 error code (Chapter 4):

  1. Consent is still valid and covers the stated purpose. Withdrawal or expiry since approval produces B005; a purpose mismatch produces B006.
  2. The custody chain verifies. A failure produces B010, and the sample is held for investigation.
  3. There is no unresolved severe or critical excursion. If there is, refuse with B008 (Section 5.6).
  4. Enough volume remains. If not, B003; the sensible response is to aliquot what is needed rather than ship the parent tube.

Only then are the retrieval and transfer events written. The request moves to fulfilled once the receiving institution confirms receipt, and that confirmation should itself arrive as a receipt event from the partner. In that way the chain of custody extends across the institutional boundary. It can only do so if the partner verifies the chain with the same canonical serialization (Section 5.4).

Key points of this chapter