Validation: Competency Questions as Executable Queries
📍 Where we are: Part VI · Validation — the phase where the model is judged, not built. The governing methodology is SAMOD's test-first loop (SAMOD and NeOn are two established ontology-engineering methods; SAMOD's loop is: write the question, model the slice, test it) closing over the whole ontology, and NeOn's principle that an ontology is validated by answering its requirements. This chapter is where the executable ORSD (Ontology Requirements Specification Document — the catalog of competency questions) stops being a promise and becomes a run.
For five parts we have laid edges (the named links of the knowledge graph) through a single mAb campaign: a working cell bank derived from its master and research banks (CHO = Chinese hamster ovary, the standard antibody-producing cell line), a seed train, a bioreactor batch, a capture pool, two orthogonal viral-clearance pools, a polished pool, the drug substance, and the drug-product lots filled from it — the physical campaign laid out end to end in Biologic Drug Manufacturing. Each chapter trusted that those edges could answer a real manufacturing question — trace this vial to its cell bank, scope a failed lot's fate, prove the viral barriers sum. Validation is the moment we stop trusting and start running. The specification chapter fixed 23 competency questions in cq-catalog.json and called them an executable requirements brief; the running example showed all 23 pass as a single green table. This chapter opens that table and reads the queries underneath it — the actual SPARQL that turns each manufacturing question into a tested fact over a loaded, reasoned graph.
A batch record promises that you can trace a vial back to the exact cell bank it came from. The quality reviewer does not re-read the promise — she runs the trace. A competency question is the promise the batch record makes ("can you tell me everything this vial was made from?"); the SPARQL query (SPARQL is the standard query language for knowledge graphs, the way SQL is for tables) is running the trace; and the expected result in the catalog is the answer the record must produce — here, eleven ancestors ending at the cell bank. This chapter is the walkthrough where the reviewer runs every trace the campaign promises and writes PASS or FAIL next to each — and where one trace is supposed to come back empty, because keeping what-it-is-packed-in separate from what-it-was-made-from is itself a requirement. (New vocabulary — SPARQL, property path, LRV, OWL-RL closure — is collected in Key terms at the foot of this page.)
Start from the questions
This chapter is the home of the question-as-query, so it carries the largest share of the ORSD catalog — and every question is a real thing an investigator, a release reviewer, or a recall team asks about this antibody. The lineage questions — what does this lot derive from, to any depth? (CQ-01) and what is the originating bioreactor batch and its monomer value? (CQ-03) — are the trace a deviation investigation starts with. The impact questions — what across the whole campaign descends from the working cell bank? (CQ-02) and which sibling lots share a failed lot's fate? (CQ-04) — are the question a recall team fires first. The trajectory question (CQ-05) follows a critical quality attribute down the purification chain; the QbD (Quality-by-Design — designing quality into the process rather than testing it in afterward) questions (CQ-06, CQ-07) cross from a feed-rate parameter set in development to the monomer purity of a released lot; the viral question (CQ-12) sums two orthogonal clearance barriers; the packaging pair (CQ-13, CQ-14) keeps what a vial is packed inside separate from what it was made from; provenance (CQ-15, CQ-16), characterization (CQ-18), and the units/structural questions (CQ-19, CQ-20, CQ-21) round out the set. This chapter walks nine of those questions in full; the other eight query-backed CQs — the originating-batch trace (CQ-03), the parameter-to-CQA map (CQ-06), the signed-release ASK (CQ-09), the provenance and characterization checks (CQ-15, CQ-16, CQ-18), the no-bare-numbers audit (CQ-19), and the equipment-vs-batch structural split (CQ-21) — follow the same shape and appear as their own green rows in the running example's full table. Each is judged not by prose but by whether its query returns exactly what the catalog says it must.
How a competency question becomes an executable query. The specification chapter showed how a CQ is written — atomic, with the shape of its answer fixed in advance. This chapter does the next step: turning that written question into something validate.py runs and decides. The procedure is the same for all twenty-three, and worth doing in the open with the signature case, CQ-01 (which materials does a lot derive from, to any depth?) — chosen because the lineage walk is the question a manufacturing record exists to answer, so its derivation is the one every later CQ in this chapter is a variation on:
- Pick the deciding artifact from the answer-shape. The ORSD fixed CQ-01's answer as a set of ancestor materials of fixed size — an enumerable result, so the artifact is a SPARQL
SELECT(a query that returns rows of results), not a SHACL gate (SHACL is a language for checking that required structure is present — it answers "is anything missing?") or a reasoner check (a reasoner derives new facts that logically follow from the stated ones — it answers "was this inferred?"). The answer-shape chooses the tool. - Write the query against the laid edges. The model already carries
derivedFromas a transitive property (if A derives from B and B from C, then A derives from C, automatically), so the walk is one property path —bp:DS-001 (bp:derivedFrom)+ ?ancestor, read as "start at the drug substanceDS-001, followderivedFromone-or-more times (+), and collect each material reached into the variable?ancestor"; thebp:is just a short prefix for the project's own namespace — and nothing has to be pre-computed; the query reads the same edges the modeling chapters laid, one per real material transformation. - Name the graph the query runs over. CQ-01's catalog entry records
"graph": "raw": the property path computes reachability natively, so the query runs on the un-reasoned triples (a triple is the atomic unit of a knowledge graph — a subject-predicate-object statement like DS-001 → derivedFrom → POLpool-001; the "raw" graph is just the triples as loaded, before a reasoner adds any), and only the CQs that test inference (CQ-22) ask for the OWL-RL closure (the larger set of triples after a reasoner has added every fact that logically follows). Choosing the graph is part of binding the question. - Bind the
check.typeto the answer-shape. A fixed-size set means{"type": "row_count", "value": 11}; a named set would beequals/contains, a booleanask, a totalled columnsum, a required rowrow_match. Thecheck.typeis the falsifiable answer-shape made mechanical —validate.pydispatches on it directly. - Register it in
cq-catalog.json. The entry —id,question,queryfile,graph, andcheck— is the one recordvalidate.pyreads; once registered, the CQ runs as a pass/fail line in the acceptance table on every build, and a model that stops returning the right eleven fails visibly. A question not in the catalog is a question the model does not promise to answer.
Every section below is one pass of this recipe: the prose names the question and its motivation, the .rq block is step 2, and the catalog check quoted beside it is step 4 — so each PASS line is the recipe run end to end.
Validate: the lineage walk answers CQ-01
The signature query is the lineage walk — reconstruct everything a lot derives from, however many hops away. This is the question a manufacturing record exists to answer: a cell bank is the single root every batch in the campaign descends from, and a misidentified or contaminated bank propagates with full confidence to every downstream material, so being able to walk back to it from any lot is not a convenience but a regulatory and safety necessity. Because derivedFrom is transitive, the walk is a single SPARQL property path: (bp:derivedFrom)+ follows one-or-more hops, so the same line works whether a lineage is three steps or twenty [1]. queries/CQ-01.rq walks back from the drug substance and binds the type of each ancestor (its closing FILTER(STRSTARTS(...)) line simply keeps only the project's own bp: types, dropping any imported upper-ontology classes):
# queries/CQ-01.rq — Lineage walk: everything DS-001 derives from, to any depth, in one property path.
# (bp:derivedFrom)+ = one-or-more hops; works whether the lineage is 3 steps or 20.
PREFIX bp: <https://example.org/bioproc#>
SELECT ?ancestor ?type WHERE {
bp:DS-001 (bp:derivedFrom)+ ?ancestor .
?ancestor a ?type .
FILTER(STRSTARTS(STR(?type), STR(bp:)))
} ORDER BY ?ancestor
The catalog's check is { "type": "row_count", "value": 11 }, and over the loaded graph the query returns exactly eleven ancestors — every unit-operation intermediate the coarse CSV chain collapses. Note which graph: validate.py runs the query-backed CQs over the raw, un-reasoned triples, because the property path (bp:derivedFrom)+ already does the walk — the reasoner is not needed to answer a lineage question, only to materialize the long edges as their own triples (to write out, as new stored triples, the shortcut edges like DS-001 derivedFrom RCB-CHO-001 that the path otherwise computes step by step), which is the separate thing CQ-22 certifies. Running the same query after the OWL-RL closure would in fact return more than eleven — because those added shortcut triples introduce extra ?ancestor matches the raw graph does not store; eleven is the answer a manufacturing record should give, and it is the raw property-path that gives it. validate.py reports the pass as a single row of its acceptance table:
CQ-01 lineage PASS 11 row(s)
Those eleven ancestors are the eleven bound ?ancestor rows the SELECT walks back through (BATCH-2026-001, CLAR-001, MCB-CHO-001, PApool-001, POLpool-001, RCB-CHO-001, SEED-001, SEEDFLASK-001, VFpool-001, VIpool-001, WCB-CHO-001). The depth retraces the whole physical process in reverse: DS-001 traces back through the polishing pool (POLpool-001), the two orthogonal viral-clearance pools (VFpool-001, VIpool-001), the Protein A capture pool and clarified harvest (PApool-001, CLAR-001), the bioreactor batch, the seed train (SEED-001, SEEDFLASK-001), and the three cell-bank tiers — working, master, and research bank (WCB-CHO-001, MCB-CHO-001, RCB-CHO-001). Those three tiers are not an arbitrary hierarchy: the research bank seeds the master bank, the master bank seeds the working bank, and only the working bank is expanded into production, so that the genetically characterized origin of the antibody is preserved across the years a commercial product is made. The long-range reachability from DS-001 to RCB-CHO-001 is never asserted by hand: the property path computes it on demand, and the transitive closure materializes it as an explicit triple. CQ-01's PASS certifies the first half — that the walk over the laid edges returns the right eleven; CQ-22's PASS certifies the second — that a reasoner will assert DS-001 derivedFrom RCB-CHO-001 as a fact. Either way the point holds: lay the edges faithfully — one per real material transformation — and the lineage is computed, not reconstructed by spreadsheet archaeology when an investigation needs it under deadline.
Validate: impact analysis answers CQ-02 and CQ-04
Impact analysis is the inverse, outward query a recall investigation actually fires. CQ-02 walks (bp:derivedFrom)+ inward to the root and returns the whole campaign that descends from WCB-CHO-001 — every batch, pool, substance, and product lot that traces to that one bank, which is the set a cell-bank-level concern (a misidentification flag, a sterility question) puts at risk in a single step. CQ-04 is the harder one. When DP-004 fails release, the question is what else is affected? — and the graph answers by walking up DP-004's lineage to a shared ancestor, then back down to every other drug product that traces to it:
# queries/CQ-04.rq — Impact analysis: when DP-004 fails, what shares its fate?
PREFIX bp: <https://example.org/bioproc#>
SELECT DISTINCT ?affected WHERE {
bp:DP-004 (bp:derivedFrom)+ ?shared . # an ancestor of the failed lot
?affected (bp:derivedFrom)+ ?shared . # anything else derived from it
?affected a bp:DrugProduct .
FILTER(?affected != bp:DP-004)
} ORDER BY ?affected
The failure that drives CQ-04 is concrete: DP-004 is the sibling lineage where polishing did not clear enough high-molecular-weight (HMW) aggregate — clumped antibody an immunogenicity risk the release gate caps — so its hmwPct lands at 2.41 % against a 2.0 % release ceiling while its monomer purity stays in spec — the realistic out-of-spec mode that makes "what else is affected?" an urgent question rather than a hypothetical. The catalog demands { "type": "equals", "var": "affected", "values": ["DP-001", "DP-002"] }, and validate.py returns precisely those two siblings — the released lots filled from the same drug substance, sharing DP-004's WCB-CHO-001 cell bank. The equals check prints the matched set in the acceptance table:
CQ-04 impact PASS affected = [DP-001, DP-002]
Because every batch in the campaign traces to that one cell bank, a contamination concern is answerable across the whole campaign in a single traversal — the difference between a recall scoped by query and a blind campaign-wide quarantine. The forward fork the model carries (bp:DS-001 bp:fillsInto bp:DP-001 , bp:DP-002) is what makes the affected set complete by construction rather than by luck: it is the same shared-fate fork in the forward direction, mirroring the backward pooling forks the downstream chapters laid, so a single drug substance fanning out into multiple filled lots is fully traversable. CQ-04's equals check — not merely contains — is what holds the model to returning every sibling and no spurious one, which is exactly the standard a recall scope has to meet to be defensible. The same walk would also scope siblings that share a consumable rather than the bank (the model carries resin-lot lifetime too); here the failed path runs through the shared cell bank, so the bank is the ancestor the walk lands on. That equals standard — every true sibling, no spurious one — is the recall standard itself: a contains check that let a false negative slip would leave an affected lot on the market, and a false positive would quarantine a sound one.
Validate: the cross-lifecycle walk answers CQ-07
The third class crosses the seams between development, manufacturing, and release — the question only this book's modeling makes askable. It is the Quality-by-Design promise made queryable: in development a design-of-experiments study (a structured set of experiments that varies inputs to see which ones move an output) finds that feed rate moves monomer purity, and that finding is recorded as an authored affectsQuality edge — a link a modeler deliberately adds — from a process parameter to a quality attribute rather than buried in a study report's prose. CQ-07 starts at that critical process parameter (CPP — a process input, here feed rate, controlled because it drives product quality), follows its development-era affectsQuality link to a quality attribute (CQA — a measurable product property, here monomer purity, that must stay within its release limits), finds the run that realized that parameter as a setpoint on the production bioreactor, steps to the batch the run output, and walks derivedFrom forward to the released drug-substance lot carrying the monomer result:
# queries/CQ-07.rq — From a CPP, through affectsQuality, to the run that REALIZED it,
# to the batch it output, forward down derivedFrom to the released DS lot.
PREFIX bp: <https://example.org/bioproc#>
SELECT ?parameter ?attribute ?lot WHERE {
?parameter bp:affectsQuality ?attribute .
?setting bp:parameterType ?parameter .
?phase bp:realizesParameter ?setting .
?run bp:hasPhase ?phase ;
bp:hasOutput ?batch .
?lot bp:derivedFrom+ ?batch ;
bp:monomerPct ?m ;
a bp:DrugSubstance .
}
The catalog's check is { "type": "row_match", "match": { "parameter": "FeedRate", "lot": "DS-001" } } — and the query lands the feed-rate CPP on the released lot it ultimately shaped, joining three systems a fragmented plant keeps in three dialects: a development study, the plant historian and the QC release record (the time-series sensor archive and the quality-control lab's results database, defined in Book 2). The five lines of the query chain those systems together hop by hop — ?parameter affectsQuality ?attribute is the development finding, ?setting parameterType ?parameter and ?phase realizesParameter ?setting step to the setpoint actually run on the bioreactor, ?run hasPhase ?phase ; hasOutput ?batch names the run and the batch it produced, and ?lot derivedFrom+ ?batch walks forward to the released drug-substance lot carrying the monomer result. The row_match check confirms the required row is present (the bound row carries ?attribute = MonomerPct-CQA, the monomer attribute the feed rate affects):
CQ-07 qbd PASS row {parameter: FeedRate, lot: DS-001} present
That is the single query the whole book was building toward [2]: a development-era affectsQuality assertion, the realized feed-rate setting on this run, and the QC verdict on DS-001, joined in one statement. Without the shared model these three live in three filing systems and the question "did the parameter we tuned in development actually land the purity we promised at release?" is a manual cross-reference; with it, the QbD knowledge is a connected, queryable structure.
The same affectsQuality edge and derivedFrom lineage that answer CQ-07 are also the ground truth a machine-learning model has to be honest against. A design-space model that predicts monomer purity from feed rate is asserting a causal claim; the graph holds the asserted, human-curated version of that claim, and a learned model that contradicts a reasoned, SHACL-conformant graph is the validation paradox the companion volume names — a fluent model versus a checked record, where the record wins. More immediately, the lineage walk is the cross-validation fold. The cardinal sin in bioprocess ML is a row-wise random train/test split, because sibling batches are not independent examples — every lot in this campaign descends from the same WCB-CHO-001, so two lots off one cell bank are near-twins, and splitting them across the train/test line lets the model see the answer and report a fantasy score. The fix is a grouped split — every record of a batch goes wholly to train or wholly to test — and the (bp:derivedFrom)+ walk back to the shared cell bank is exactly the group key, the leave-one-batch-out (or leave-one-cell-bank-out) grouping that Book 5's data chapter makes the default and its models-and-validation chapter turns into GroupKFold and nested cross-validation. The graph that scopes a recall (CQ-04) and the graph that defines an honest validation fold are the same graph — which is also why a GraphRAG retrieval that answers "what was this lot derived from?" reuses CQ-01's property path verbatim: the retrieval layer's source of truth is the catalog's already-green query, not a paragraph the model hopes exists.
Three competency questions, one graph: a property path walks lineage back to the cell bank (CQ-01), an impact query scopes a failure across siblings via the shared cell bank (CQ-04), and a cross-lifecycle link ties a process parameter (FeedRate) to the critical quality attribute it affects (MonomerPct-CQA), whose released value on DS-001 is 98.611 % (CQ-07) — each run by
validate.py and checked against the catalog.
Original diagram by the authors, created with AI assistance.
Validate: the quality trajectory answers CQ-05
CQ-05 asks where did monomer purity get cleaned up? — and the honest answer is a trajectory, not a single point. The high-molecular-weight (HMW) aggregate fraction matters because aggregates are an immunogenicity risk; the release gate caps them, and the whole downstream train works to drive them down. But no single step "owns" the final number: capture raises purity, viral safety protects it, and polishing refines it — purity is a cumulative property of the whole purification sequence, almost emergent, never the act of one operation [3]. Polishing earns its place by separating the antibody from closely-related product variants — slightly altered forms of the same antibody molecule, here the aggregate bp:AGG-1 and the acidic charge variant bp:CHV-1 (a version carrying a slightly different electrical charge, separable from the intended product) — both modeled as real entities derivedFrom the viral-filtered pool and routedTo the polishing waste stream, so the graph records what was removed and to where, not just that the number improved. The query collects every in-process result carrying an HMW value:
# queries/CQ-05.rq — Quality-attribute trajectory: the in-process HMW aggregate value at
# each step along the purification chain, so an analyst can see WHERE a quality changed.
PREFIX bp: <https://example.org/bioproc#>
SELECT ?material ?hmw WHERE {
?result a bp:InProcessResult ;
bp:isAbout ?material ;
bp:hmwPct ?hmw .
} ORDER BY DESC(?hmw)
The catalog requires the result to contain PApool-001 and POLpool-001, and the query returns the two-point chain — HMW falling from 4.1 % after capture to 1.4 % after polishing, the loadable, ordered history of one quality attribute. A precise reader will note the ORDER BY DESC(?hmw) sorts by the value, not by lineage position — the descending order coincides with the purification sequence here only because purity improves monotonically; the authoritative process order is the derivedFrom chain, which is what CQ-01's lineage walk reads. CQ-05 reports the values; their position on the chain is read from CQ-01. This is derivedFrom read not just as lineage but as a chain of custody for quality: each edge carries the material and the evolving state of its attributes, so the graph answers not only "where did this come from?" but "how did its purity get here, step by step?" — which turns a genealogy into a process-understanding record an analyst can replay against the design space. CQ-05's PASS certifies that history is queryable rather than scattered across per-step test reports filed in different systems. What CQ-05 deliberately does not claim is causation: the trajectory shows where the value changed, but because the steps are not independent — what polishing can achieve depends on what capture left it — the graph cannot apportion credit among them. That apportionment is a causal-inference question the design-space models answer, not the lineage; the chapter's discipline is to record the trajectory and refuse to assert a single "purity-determining step."
A reviewer reads the authority level off this trajectory too. The 4.1 % and 1.4 % points are in-process results (bp:InProcessResult, carried on the pool via bp:isAbout), measured for process control — not for lot disposition. The number that releases the lot is a different, higher-authority result: the SEC (size-exclusion chromatography, the assay that measures aggregate fraction) HMW on the drug-substance lot itself (bp:DS-001 bp:hmwPct "1.287"), tested on the release sample against the <= 2.0 % criterion. Conflating an in-process monitoring value with a release verdict is a real data-integrity failure mode: 1.4 % off the polishing pool is process knowledge; 1.287 % on DS-001 is the disposition fact, and only the latter is what 21 CFR 211 (the US FDA regulation governing drug-manufacturing release) release rests on. The distinction is an ALCOA+ one — the regulators' shorthand for data that is Attributable, Legible, Contemporaneous, Original, Accurate, and (the "+") complete, consistent, and enduring: the disposition value must be attributable (whose signature releases it) and original (the SEC result, not a transcribed monitoring number), and a typed graph that keeps bp:InProcessResult distinct from the release CQA on bp:DS-001 is enforcing that attribution structurally rather than by procedure. CQ-05 answers the process-understanding question; the release gate the next chapter opens answers the disposition question, and the model never lets one masquerade as the other.
Validate: orthogonal viral clearance answers CQ-12
Viral safety is where the model's honesty is tested, because the quantity that matters is not a product property at all but a measure of risk reduction — assurance about a contaminant you hope was never there. You cannot measure "zero virus" in a batch; instead the process is built from orthogonal barriers that clear virus by different mechanisms, so a virus surviving one is unlikely to survive the other. The running example carries the two standard barriers: a low-pH hold (VI-001) that inactivates enveloped viruses, and a virus-retentive nanofiltration step (VF-001) that removes virus particles by size. Because the mechanisms are independent, their log reduction values (LRVs — each a base-10 log of how much virus the step removes, so an LRV of 4.5 means the step lowers the virus level roughly 10^4.5-fold, about 30,000x) are independent too, and total clearance is their sum — adding the logs, so 4.5 + 4.2 = 8.7 is a combined ~10^8.7-fold (roughly 500-million-fold) reduction — a sum that is only defensible because the steps are orthogonal [4]. The model keeps a sharp distinction the safety case depends on: each step's LRV (4.5, 4.2) is a validated capability, proven once in dedicated clearance studies and hung off the step via bp:hasClearanceCapability, while the per-batch conditions those studies relied on — the pH 3.6 and 60-minute hold actually achieved this run — are measured qualities inhering in the cleared material. CQ-12 walks each step's validated capability and reads its LRV and mechanism:
# queries/CQ-12.rq — Viral safety: each clearance step's VALIDATED log reduction value
# (a capability, not a per-batch measurement) and its mechanism.
PREFIX bp: <https://example.org/bioproc#>
SELECT ?step ?lrv ?mechanism WHERE {
?step bp:hasClearanceCapability ?cap .
?cap bp:lrvValue ?lrv ;
bp:clearanceMechanism ?mechanism .
} ORDER BY ?step
The catalog's check is { "type": "sum", "var": "lrv", "round": 1, "value": 8.7 }. The query returns [('VF-001', 4.2), ('VI-001', 4.5)] — the nanofiltration step's size-based removal (nanofiltration) at 4.2 and the low-pH hold's enveloped-virus inactivation by low pH at 4.5 — and their sum, 8.7, matches the derived bp:TotalClearance-mAb-A individual exactly, the orthogonal total established once by bp:VClear-Study. The LRV hangs off the step through bp:hasClearanceCapability precisely so it can never be confused with a per-batch measurement; if a graph recorded an LRV as if it were observed on this batch's product, it would assert something no instrument saw — the single most safety-critical misrepresentation the model can make.
One honest caveat the ontology records against itself. BFO (the Basic Formal Ontology, the foundational upper ontology this model builds on) draws a sharp line between material entities (things that persist, like a filter or a pool of liquid) and occurrents (things that happen, like a process step). In BFO a disposition — a latent tendency to behave a certain way under the right conditions, and a capability is one kind of disposition — inheres in (belongs to) a material entity, never in an occurrent — yet bp:VI-001 and bp:VF-001 are processes (bp:ViralClearanceStep ⊑ bp:UnitOperation ⊑ bp:Process, where ⊑ reads "is a subclass of"). Hanging the LRV off the step via bp:hasClearanceCapability is therefore a deliberate, flagged simplification for teaching clarity, and the bp:hasClearanceCapability definition says so in the file. The BFO-correct anchor is visible right beside it: the nanofilter bp:NF-01 bears a clearance bp:Function (bp:hasFunction bp:NF-clearance-fn) that is realized in bp:VF-001. The per-batch conditions are already modeled the strict way — bp:VIcond-pH and bp:VIcond-time are qualities that bp:inheresIn bp:VIpool-001, the cleared material. The lesson the CQ teaches survives the caveat: the LRV is a validated capability, not an observation.
What makes the sum defensible to a reviewer is what the model encodes. Under ICH Q5A(R2) (the international viral-safety guideline adopted by the FDA, EMA, and others), total clearance may be claimed as the sum of individual log reduction values only when the steps are mechanistically orthogonal and each LRV was established in a scaled-down, spiked validation study (a lab-scale replica of the step deliberately dosed with known virus so the clearance can be measured) using relevant and model viruses — worst-case stand-ins for the viruses of concern (e.g. X-MuLV as the enveloped model, MMV as the small non-enveloped model). The graph carries that provenance: each bp:LogReductionValue is bp:establishedBy bp:VClear-Study, so the LRV is a claim with a study behind it — the form bp:hasClearanceCapability takes when an inspector asks where did 4.5 come from? The two barriers must clear by genuinely different mechanisms — low pH denatures the lipid envelope, nanofiltration excludes by size — which is why a non-enveloped, filtration-resistant agent would defeat the orthogonality assumption and is the boundary case a defensible safety argument has to acknowledge rather than hide. The condition ranges (pH 3.6, 60-minute hold) become an enforceable gate in the next chapter.
CQ-12 validates that the safety argument is a connected, checkable structure — each LRV linked to its mechanism and the orthogonal sum derivable — rather than a manual tally in two unrelated filing cabinets.
Two orthogonal barriers, summed and checked: CQ-12 walks
bp:hasClearanceCapability to each step's validated LRV (4.5 and 4.2) and the catalog verifies their sum is the total clearance of 8.7 — a derived, queryable safety fact, not a spreadsheet total.
Original diagram by the authors, created with AI assistance.
Validate: containment is not genealogy (CQ-13 and CQ-14)
Once the drug substance is filled into vials and the vials are packed for distribution, two transitive hierarchies meet on the same physical object — and the model must keep them sharp. Containment (what a vial is packed inside, now — a pallet holds a case holds a carton holds the vial, and a vial can be repacked) is not genealogy (what it was made from — the cell bank, the batch, the substance, permanent and backward in time). Conflating them would let a packaging change look like a lineage change, which is exactly the kind of error that breaks a recall trace. CQ-13 walks the transitive bp:contains aggregation hierarchy:
# queries/CQ-13.rq — Packaging containment (NOT lineage): walk the aggregation hierarchy
# pallet -> case -> carton -> serialized vial with the transitive bp:contains property.
PREFIX bp: <https://example.org/bioproc#>
SELECT ?package ?content WHERE {
?package bp:contains+ ?content .
?content a bp:SerializedUnit .
} ORDER BY ?package
It returns the packages [CARTON-001, CASE-001, PALLET-001], matching the catalog. CQ-14 is the one query that is supposed to come back empty — it uses ASK (a SPARQL form that returns a single true/false, not rows like SELECT) to ask whether any node is on both the containment and the genealogy path of the serialized vial VIAL-DP-001-000042, and the model is correct only when the answer is False:
# queries/CQ-14.rq — containment is NOT genealogy. The model is correct when this ASK is FALSE.
PREFIX bp: <https://example.org/bioproc#>
ASK {
?container bp:contains+ bp:VIAL-DP-001-000042 .
bp:VIAL-DP-001-000042 bp:derivedFrom+ ?container .
}
The catalog's check is { "type": "ask", "value": false }, and validate.py reports ASK = False. This is the subtle part of validation that prose alone misses: a correct model here produces no match, because nothing a vial is packed inside is also something it was made from. The carton is part of the vial's present packaging, not its manufacturing history; the cell bank is part of its history, not its packaging. An empty result is the requirement, and CQ-14's PASS is the proof the two transitive hierarchies stay disjoint — so a repack never looks like a re-manufacture, and a recall trace down derivedFrom never wanders off into the shipping container. That emptiness is structurally guaranteed, not lucky: bp:contains and bp:derivedFrom are independent properties — neither a sub-property of the other (one does not imply the other, the way "is-mother-of" implies "is-parent-of") nor its inverse (following one backward is not the same as following the other forward) — and no individual in the campaign is both a package a vial sits inside and a material the vial was made from, so the two transitive hierarchies stay disjoint (share no node) and can never overlap.
Validate: stable host identity answers CQ-20
CQ-20 closes the set with interoperable identity: what host organism, by stable identifier, does the line express its product in? The answer matters because "CHO" as a bare string is ambiguous across labs and decades, while the antibody's expression host is a regulated fact a partner organization must be able to read without guessing. The query resolves the cell bank's host through its class's rdfs:subClassOf alignment to a public taxonomy class rather than the local label. The alignment is deliberate subsumption — bp:HostOrganism rdfs:subClassOf obo:NCBITaxon_10029, which says the local host class is a kind of the public taxon class ("is-a"), the weakest, safest of the linking options — never owl:equivalentClass (the two classes are exactly the same set) or owl:sameAs (two names for the identical individual). (The prefixes name where each term lives: rdfs: and owl: are the standard RDF-schema and OWL vocabularies, obo: the public OBO library, bp: the project's own namespace.) The program does not claim its local host class is the NCBI taxon, only that every host falls under it — the conservative move that keeps an alignment from over-merging two vocabularies. The query climbs from the host individual to its class to that taxon IRI:
# queries/CQ-20.rq — the working cell bank's host organism is named by a STABLE public identifier.
PREFIX bp: <https://example.org/bioproc#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX obo: <http://purl.obolibrary.org/obo/>
SELECT ?host ?taxon WHERE {
bp:WCB-CHO-001 bp:hasHostOrganism ?host .
?host a ?cls .
?cls rdfs:subClassOf ?taxon .
FILTER(STRSTARTS(STR(?taxon), STR(obo:)))
}
The catalog requires the row { "host": "CHO-host", "taxon": "NCBITaxon_10029" }, and the query lands on obo:NCBITaxon_10029 — Cricetulus griseus, the Chinese hamster whose CHO cells dominate commercial antibody production — so "what species?" resolves to a stable IRI a partner organization can dereference, not a local string. This is also where the cross-ontology seam shows: the biological side of the cell bank reaches up to an NCBI Taxonomy IRI, while its manufacturing description reaches up to the IOF biopharma module, and that bridge is authored and maintained in the alignment, not silently imported.
Evaluation: the whole catalog, green
These nine are a slice of the twenty-three; validate.py reads the entire catalog, runs every entry against the loaded and OWL-RL-reasoned graph, prints a PASS/FAIL line per competency question, and exits non-zero if even one fails. The running-example chapter shows the full table; the rows this chapter's queries produce read:
CQ-01 lineage PASS 11 row(s)
CQ-02 impact PASS descendant superset of {DP-001, DP-002, DP-004, DS-001} (26 total)
CQ-04 impact PASS affected = [DP-001, DP-002]
CQ-05 trajectory PASS material superset of {PApool-001, POLpool-001} (2 total)
CQ-07 qbd PASS row {parameter: FeedRate, lot: DS-001} present
CQ-12 viral PASS sum(lrv) = 8.7 over 2 step(s)
CQ-13 packaging PASS package = [CARTON-001, CASE-001, PALLET-001]
CQ-14 packaging PASS ASK = False
CQ-20 units PASS row {host: CHO-host, taxon: NCBITaxon_10029} present
That table is produced by an entirely open-source stack a reader can run in a minute, which is the point of an executable ORSD: validate.py parses the three Turtle files with RDFLib (the Python RDF library), materializes the OWL-RL closure with owlrl, runs the closed-world gates with pyShacl, and dispatches each cq-catalog.json entry on its check.type — all three permissively licensed (RDFLib and pyShacl BSD-style, owlrl W3C) and installable with one pip line, no server. The whole harness runs offline against a local graph (python -m venv .venv && .venv/bin/pip install rdflib pyshacl owlrl, then .venv/bin/python examples/platform/ontology/validate.py), which is what makes the acceptance suite a CI step rather than a vendor demo: the same checks the open-source book builds with RDFLib and SHACL on a laptop, here wired to the requirements catalog. At plant scale the identical .rq files would run unchanged against a persistent triplestore — a Fuseki or GraphDB endpoint — because SPARQL property paths are engine-portable; the suite's value is that it pins the answers, so swapping the store cannot silently change a recall scope. The catalog itself stays vendor-neutral by design: a CQ entry names a .rq file, a graph, and a check, so the requirement is the same whether the triples live in RDFLib's in-memory graph or an enterprise store.
The RESULT column is the contract; the DETAIL column is the evidence. A SPARQL query answers seventeen of the twenty-three; of the remaining six, four are closed-world release-and-characterization questions a SELECT cannot pose — does the released lot carry exactly one in-spec value for every required CQA? (CQ-08), do the finished lots meet finish-specific criteria — sterile, appearance, plausible fill volume? (CQ-10), is the out-of-spec lot flagged on exactly the failing path and nowhere else? (CQ-11), and is the working cell bank fully characterized for identity, sterility, viral safety, and genetic stability? (CQ-17) — and they are answered by SHACL gates the next chapter opens, because "is anything missing?" is not a question about the triples that exist but about the triples that should. (The last two non-SELECT entries, CQ-22 and CQ-23, are the reasoner and disjointness guards exercised structurally in Formalization.) Those four release-and-characterization questions are where the polishing-driven release specification and the cell-bank characterization requirements become enforceable rather than merely traceable.
The split is the boundary between open-world and closed-world logic. SPARQL property paths walk existing edges, so the query-backed CQs ask "what is reachable?" — an answer that only grows as triples are added, which is why they run on the raw, un-reasoned graph (gq = load()): the (bp:derivedFrom)+ path computes transitive reachability natively, with no description-logic reasoner, so the lineage walk is independent of whether you pick OWL 2 EL, QL, or RL (three standard OWL profiles — restricted dialects of the ontology language, each tuned for a different kind of reasoning; the names need not be retained here, only that the choice does not affect this walk). The OWL-RL closure validate.py materializes is needed only for the one CQ that checks inference itself — CQ-22. The closed-world questions ask the opposite, open-world-illegal question — "is anything missing?" — which no SELECT can answer, because under the open-world assumption — the default in this kind of logic, where what is not stated is treated as merely unknown rather than false — an absent triple is unknown, not false. (The opposite, closed-world logic, treats anything not present as false, which is what lets a SHACL gate declare a required value "missing.") Those become SHACL shapes in the next chapter. Validation, in short, is not a reading exercise: it is a run, and the model is validated exactly when every line says PASS.
The unsolved part: a passing query proves the model answers, not that the data is true
The competency-question suite certifies that the model can answer its requirements — but it runs over a derived view, and a query is only as true as its last load. The graph's triples are copied from the relational batch records, the plant historian and the LIMS (the time-series sensor archive and the Laboratory Information Management System, the QC lab's results database) that are the actual sources of truth, so unless the load is validated, complete, and re-run under change control, the graph can silently drift from the systems it mirrors. Those sources are not arbitrary tables: the realized run that CQ-07 walks reaches the graph through the ISA-95 / B2MML batch-production record (the standard XML exchange for an as-run batch — its <Equipment> reference becomes the occursIn edge, never a second type on the batch) and the OPC UA tag below the historian, the unit-bearing reads that land as the typed setpoints CQ-07 joins on, as Book 2's data-model chapter specifies. A loader that respects those data models keeps the graph's lineage isomorphic to the MES's batch genealogy rather than a lossy re-keying of it. And because the load is a system that produces a record a regulator may inspect, it inherits 21 CFR Part 11 / EU Annex 11 obligations — a validated, audit-trailed, attributable transformation — so a re-load that drops a derivedFrom fork is not merely a data bug but a change that must be controlled, logged, and re-validated, the same discipline a computer-system validation moving from CSV to CSA applies to any GxP transformation. The danger is sharpest exactly where this chapter's value lives: a lineage walk that looks authoritative can be quietly incomplete — a derivedFrom edge the loader skipped, a source edit made after the last load — and if the dock that fills the graph mistakes a batch for the bioreactor it ran in, or drops a fork to a sibling lot, the genealogy collapses and a recall trace silently misses a vial. CQ-01 returning eleven ancestors proves the property path works and the loaded edges connect; it cannot prove those eleven are the right eleven if the load that produced them was partial. The deeper limit is federation (querying across graphs that separate organizations own and host, rather than one graph under a single roof): the cross-lifecycle and lineage walks are ironclad inside the factory one organization controls, but the moment the thread reaches distributors and the supply chain it depends on other parties' graphs in their identifiers, with no one able to mandate the whole. The query was never the hard part; keeping the graph it runs over true is — engineering and governance work, not a missing SELECT.
Why it matters
Validation is where modeling stops being a claim and becomes a measurable property — and for a regulated antibody the stakes are concrete. Without the executable catalog, "can this model trace a vial to its cell bank, or scope a failed lot's recall?" has no test; with it, those questions reduce to running validate.py and reading the table. Every modeling decision in the book — the upper spine, the typed values, the faithful derivedFrom edges per real transformation, the orthogonal-clearance shape, the disjointness guards — exists to make these queries trustworthy, and each chapter earns its classes by pointing at the competency question they serve. A model that answers all its questions can still be useless for the twenty-fourth no one wrote down, but a model that fails one of its own questions — that cannot sum the viral barriers, or returns the wrong recall scope — is broken now, visibly, in the build.
In the real world
The competency-question discipline is the spine of the methodologies industry adopts, and on real platforms these same walks are everyday operations: the derivedFrom lineage path is a Palantir Foundry object link or a Neo4j Cypher traversal (Foundry and Neo4j are widely used graph-data platforms; Cypher is Neo4j's query language, the property-graph counterpart of SPARQL), and impact analysis is the query a recall team fires the moment a lot fails. The orthogonal-clearance summation and the cell-bank lineage trace map directly onto the viral-safety dossiers and bank-characterization records every commercial mAb process already maintains — the modeling advance is to link them so the safety argument and the genealogy are connected, queryable structures rather than separate filing cabinets reconciled by hand. The cross-lifecycle walk also survives the event a real product cannot avoid — tech transfer, the qualified move of a process from a development site to a commercial plant or a second manufacturer. When the same recipe is re-realized on a new bioreactor, the realizesParameter edges re-bind to setpoints proven on equipment whose installation, operation, and performance were re-qualified (the IQ/OQ/PQ a transferred line must pass before it makes a releasable lot), so CQ-07 still lands the development-era affectsQuality finding on the receiving site's released lot — the graph carries the genealogy across the transfer rather than starting a fresh, disconnected record. And the derivedFrom lineage CQ-01 walks is precisely the structure a MES (manufacturing execution system) batch record asserts and review-by-exception trusts: the executable catalog makes that genealogy a tested invariant rather than a paper claim a reviewer re-checks by hand under deadline. What is still rare — and what this book argues for — is making the competency questions executable, so the requirements and the regression suite never drift apart [1]. The digital-thread idea, semantic links as lifecycle-spanning connective tissue, is established in the smart-manufacturing and digital-twin literature [2]; what separates a demo from a production thread is exactly this chapter's unsolved part — the validated, drift-controlled load — which is governance work, not a query no one has written.
Key terms
- ORSD (Ontology Requirements Specification Document) — the up-front catalog of what the ontology must do; here it is executable — the 23 competency questions in
cq-catalog.json, each run as a query and checked against its expected result. - Competency question (CQ) — a query the finished ontology must answer, fixed before modeling; here, a real manufacturing question (trace, recall scope, QbD link) run by
validate.pyand checked againstcq-catalog.json. - SPARQL property path — the
(bp:derivedFrom)+"one-or-more hops" construct that expresses a lineage or containment walk of arbitrary depth in one line. - Lineage walk (CQ-01) — the property-path query reconstructing all eleven ancestors of
DS-001back through the pools, batch, seed train, and three cell-bank tiers; the(bp:derivedFrom)+path computes the long-range reachability on the raw graph (the OWL-RL closure that materializes it as a triple is the separate thing CQ-22 certifies). - Impact analysis (CQ-04) — the inverse, outward query that scopes a failed lot's fate by walking up to the shared cell bank and back down to its two siblings
DP-001andDP-002via thefillsIntoforward fork. - Cross-lifecycle query (CQ-07) — the QbD question crossing development, manufacturing, and release seams (feed-rate parameter → realized run → released lot), made askable only by the shared model.
- Quality trajectory (CQ-05) — the in-process HMW values down the chain (
4.1 %→1.4 %),derivedFromread as a chain of custody for quality; shows where purity changed, not which step caused it. - Orthogonal clearance (CQ-12) — the sum of two mechanistically independent LRVs (low-pH inactivation 4.5 + size-based nanofiltration 4.2 = 8.7), valid because the barriers are orthogonal; a validated capability of the step, not a per-batch measurement.
- Containment-is-not-genealogy (CQ-14) — the
ASKthat is correct only when False: no node is on both a vial'scontainsand itsderivedFrompath. - Derived view / drift — the graph's triples are copied from source systems, so a passing query proves the model answers, not that an ungoverned, stale load is still true.
- Grouped / leave-one-batch-out split — the cross-validation discipline that keeps every record of a batch wholly in train or wholly in test; the
(bp:derivedFrom)+walk back to the shared cell bank is the group key, so the lineage graph and the honest validation fold are one structure (see Book 5's data and models-and-validation). - ALCOA+ — the data-integrity standard (Attributable, Legible, Contemporaneous, Original, Accurate, plus complete/consistent/enduring); keeping
bp:InProcessResultdistinct from the release CQA enforces attribution structurally, and a Part 11 / Annex 11 audit trail governs the load that fills the graph. - Tech transfer / IQ-OQ-PQ — the qualified move of a process to a new line; the realized-parameter edges re-bind to re-qualified equipment, so CQ-07 lands the development finding on the receiving site's released lot rather than starting a disconnected record.
Where this leads
The competency questions a SPARQL query can answer are answered and green. But four of the twenty-three are closed-world questions a query cannot pose — is the cell bank fully characterized? is this lot in spec on every required attribute? The next chapter, The Release Gate and SHACL: Validating Completeness and Specification, turns to those: it builds the SHACL shapes — the required-structure checks introduced above — that gate the release decision, shows why the out-of-spec sibling DP-004 fails on exactly one path (hmwPct at 2.41 % against a 2.0 % ceiling) and nowhere else, and completes the validation phase with the closed-world half the queries cannot reach.