Artifact and scope
This lab uses the unchanged public head.001.001.04 XSD obtained from the official catalogue. Python code and instances are DR originals; the schema is an external artifact identified in the provenance record. The objective is to validate synthetic AppHdr headers, not send payments. The inspected catalogue lists V04, while the BAH overview still mentions V03; for this rehearsal, the file and its targetNamespace determine the version. The version adopted by a real community still depends on its applicable contract. Do not replace that agreement with the newest date on a webpage.
Prepare a repeatable execution
Save run.py and head.001.001.04.xsd in the same directory. The official download appears in the lesson references. The script checks SHA-256 against the recorded value before calling xmllint; a mismatch stops the rehearsal for investigation. Python 3.13 and an available xmllint are required, or set DR_XMLLINT to the executable path. The observed run used Python 3.13.1 and libxml2 2.9.13. The program installs no tools and downloads no files during testing. It processes only synthetic fixtures generated in its own code; this is no recommendation of that runtime for a production gateway.
Actually executed boundaries
Predict every outcome before execution. Removing Fr, reversing Fr and To, leaving BizMsgIdr empty, or using 36 characters causes rejection under the selected schema. The 35-character case passes. This pair distinguishes an inclusive boundary from a generation defect without automatically truncating identities. Another fixture emits both FIId and OrgId inside Fr and fails the structural choice. An impossible date also fails. The report retains exit code and diagnostic for each instance. The runner treats tool failures separately: a missing schema or execution failure must not count as an expected message rejection.
Schema passed, contract still incomplete
Some fixtures pass specifically to expose limits. MsgDefIdr is bounded text in the XSD and accepts an arbitrary identifier; definition existence and suitability remain unproven. CreDt without a timezone and an empty FinInstnId also pass the tested base cases. Fictional profile DR-BAH-A adds an explicit timezone, agreed identifier, and BICFI for both parties. These are teaching rules, not universal ISO requirements. A five-character external code passes its length constraint without consulting a list. Likewise, synthetic BICs matching the pattern were not confirmed in any directory or community.
Structure does not prove authenticity
Sgntr contains a signature-namespace wildcard with processContents lax. In the rehearsal, with no signature schema loaded, an empty Signature element in that namespace passes header validation. No cryptographic operation, certificate, key, or trust chain was checked. Switching to strict structural validation would not by itself perform cryptographic verification either. This case helps a PM request the appropriate evidence from the security owner: define the verification point, trust material, covered data, and expected outcome. The lab only demonstrates the boundary between structural presence and the assurance incorrectly inferred from it.
Interpret and deliver evidence
Reserve ten minutes to prepare tools and files, fifteen to predict outcomes, twenty to execute and compare, and fifteen to explain two insufficient acceptances. Output includes 22 XSD cases and fifteen profile and planning checks, totaling 37. Retain hashes of script, schema, and fixtures, plus validator version and diagnostics. The command runs --schema against a local file, with catalogs disabled and --nonet on this runtime. These options do not constitute a parser security assessment. Proper closure is local evidence for recorded cases; full MDR rules, financial body, signature, counterparty, and operational acceptance require their own validation.
"""Original DR BAH schema workshop. Python 3.13 + xmllint.
Obtain head.001.001.04.xsd from https://www.iso20022.org/message/23104/download
Place the unchanged file next to this script. Run:
python3 run.py --output evidence.json
Trusted generated fixtures only. No messages sent or cryptographic checks.
"""
import argparse
import hashlib
import json
import os
import re
import shutil
import subprocess
import sys
import tempfile
from pathlib import Path
from xml.etree import ElementTree as ET
SCHEMA_SHA = '73d68e98ec079806a23f37494ed47662a74cebfae91807bfbaacda3728d65796'
NS = 'urn:iso:std:iso:20022:tech:xsd:head.001.001.04'
MSG = 'pacs.008.001.08'
BASE = f'''<AppHdr xmlns="{NS}">
<Fr><FIId><FinInstnId><BICFI>DRXXZZ00</BICFI></FinInstnId></FIId></Fr>
<To><FIId><FinInstnId><BICFI>DRYYZZ00</BICFI></FinInstnId></FIId></To>
<BizMsgIdr>DR-HEADER-001</BizMsgIdr><MsgDefIdr>{MSG}</MsgDefIdr>
<CreDt>2026-10-03T10:00:00Z</CreDt></AppHdr>'''
# Synthetic identifiers are not verified directory members or real participants.
checks = []
def check(name, actual, expected):
assert actual == expected, (name,actual,expected)
checks.append(dict(name=name,actual=actual,expected=expected,passed=True))
def profile_errors(xml):
"""Invented DR-BAH-A profile, separate from base schema validation."""
root=ET.fromstring(xml);n={'h':NS};errors=[]
if root.findtext('h:MsgDefIdr',namespaces=n)!=MSG: errors.append('message contract')
date=root.findtext('h:CreDt',default='',namespaces=n)
if not re.search(r'(Z|[+-][0-9]{2}:[0-9]{2})$',date):errors.append('explicit timezone')
for party in ('Fr','To'):
if not root.findtext(f'h:{party}/h:FIId/h:FinInstnId/h:BICFI',namespaces=n):errors.append(party+' identification')
return errors
def route_gaps(required, evidence, bundle):
# Coverage under an original fictional release contract; not statistical assurance.
observed={x['route'] for x in evidence if x['bundle']==bundle and x['passed']}
return sorted(set(required)-observed)
def main:
parser=argparse.ArgumentParser;parser.add_argument('--output',required=True);args=parser.parse_args
schema=Path(__file__).with_name('head.001.001.04.xsd')
if hashlib.sha256(schema.read_bytes).hexdigest!=SCHEMA_SHA:raise SystemExit('Unexpected schema bytes; investigate before updating the pin.')
exe=os.environ.get('DR_XMLLINT') or shutil.which('xmllint')
if not exe:raise SystemExit('xmllint is required; no schema checks were performed.')
version=subprocess.run([exe,'--version'],capture_output=True,text=True,check=True)
env={**os.environ,'XML_CATALOG_FILES':'','SGML_CATALOG_FILES':''}
root=ET.fromstring(schema.read_bytes);x='{http://www.w3.org/2001/XMLSchema}'
assert not any(root.findall(x+name) for name in ('import','include','redefine'))
fr='<Fr><FIId><FinInstnId><BICFI>DRXXZZ00</BICFI></FinInstnId></FIId></Fr>'
to='<To><FIId><FinInstnId><BICFI>DRYYZZ00</BICFI></FinInstnId></FIId></To>'
nozone=BASE.replace('10:00:00Z','10:00:00');othermsg=BASE.replace(MSG,'not-a-message-definition')
emptyid=BASE.replace('<BICFI>DRXXZZ00</BICFI>','')
cases=[
('baseline',BASE,True),
('required sender absent',BASE.replace(fr,''),False),
('sender receiver sequence reversed',BASE.replace(fr+'\n'+to,to+'\n'+fr),False),
('empty message identity',BASE.replace('DR-HEADER-001',''),False),
('thirty six character identity',BASE.replace('DR-HEADER-001','A'*36),False),
('thirty five character identity',BASE.replace('DR-HEADER-001','A'*35),True),
('message identifier is only bounded text',othermsg,True),
('invalid calendar date',BASE.replace('2026-10-03','2026-02-30'),False),
('timezone absent at base schema',nozone,True),
('prefix alias',re.sub(r'<(/?)([A-Za-z][A-Za-z0-9]*)',r'<\1h:\2',BASE).replace('xmlns=','xmlns:h='),True),
('different header namespace',BASE.replace('head.001.001.04','head.001.001.03'),False),
('both sender choice branches',BASE.replace('</Fr>','<OrgId/></Fr>'),False),
('empty financial institution at base schema',emptyid,True),
('malformed BIC shape',BASE.replace('DRXXZZ00','bad'),False),
('unexpected root child',BASE.replace('</AppHdr>','<Extra/></AppHdr>'),False),
('lax signature structure is not verification',BASE.replace('</AppHdr>','<Sgntr><Signature xmlns="http://www.w3.org/2000/09/xmldsig#"/></Sgntr></AppHdr>'),True),
('signature wrong namespace',BASE.replace('</AppHdr>','<Sgntr><Signature xmlns="urn:dr:wrong"/></Sgntr></AppHdr>'),False),
('boolean true',BASE.replace('</AppHdr>','<PssblDplct>true</PssblDplct></AppHdr>'),True),
('boolean yes',BASE.replace('</AppHdr>','<PssblDplct>yes</PssblDplct></AppHdr>'),False),
('external code length five',BASE.replace('</BICFI>','</BICFI><ClrSysMmbId><ClrSysId><Cd>ZZZZZ</Cd></ClrSysId><MmbId>DR-MEMBER</MmbId></ClrSysMmbId>',1),True),
('external code length six',BASE.replace('</BICFI>','</BICFI><ClrSysMmbId><ClrSysId><Cd>ZZZZZZ</Cd></ClrSysId><MmbId>DR-MEMBER</MmbId></ClrSysMmbId>',1),False),
('sender choice absent',BASE.replace(fr,'<Fr/>'),False),
]
results=[]
with tempfile.TemporaryDirectory(prefix='dr-bah-schema-') as td:
for index,(name,xml,expected) in enumerate(cases):
p=Path(td)/f'case-{index:02}.xml'p.write_text(xml)
run=subprocess.run([exe,'--nonet','--noout','--schema',str(schema),str(p)],env=env,capture_output=True,text=True,timeout=10)
if run.returncode not in (0,3):raise AssertionError((name,'unexpected tool failure',run.returncode,run.stderr))
check('XSD '+name,run.returncode==0,expected)
results.append(dict(name=name,xml=xml,expectedValid=expected,exitCode=run.returncode,diagnostic=run.stderr.replace(td,'<temporary>'),sha256=hashlib.sha256(xml.encode).hexdigest))
check('custom profile baseline',profile_errors(BASE),[])
check('custom profile requires timezone',profile_errors(nozone),['explicit timezone'])
check('custom profile binds message identifier',profile_errors(othermsg),['message contract'])
check('custom profile requires sender identifier',profile_errors(emptyid),['Fr identification'])
routes=['A-in','A-out','B-in','B-out']
evidence=[dict(route=p,bundle='release-1',passed=True) for p in routes]
check('complete route evidence',route_gaps(routes,evidence,'release-1'),[])
check('outbound gap despite other routes',route_gaps(routes,evidence[:-1],'release-1'),['B-out'])
check('duplicate route cannot fill gap',route_gaps(routes,evidence[:-1]+[evidence[0]],'release-1'),['B-out'])
check('changed bundle needs applicable evidence',route_gaps(routes,evidence,'release-2'),sorted(routes))
failed=[{**e,'passed':e['route']!='A-in'} for e in evidence]
check('failed route stays uncovered',route_gaps(routes,failed,'release-1'),['A-in'])
# Fictional 90 minute window; reserve 15 rollback +10 reconciliation.
check('latest safe rollback decision minute',90-15-10,65)
check('remaining after decision at minute 70',90-70,20)
check('late decision cannot fit reserved recovery',90-70>=15+10,False)
check('candidate at minute 60 leaves five minute reserve',90-60-15-10,5)
activation=lambda tested,approved,contract,staffed:all((tested,approved,contract,staffed))
check('tests alone do not authorize activation',activation(True,False,True,True),False)
check('agreed fictional activation conditions',activation(True,True,True,True),True)
output=dict(scope='Actual local XSD validation of original synthetic BAH headers against pinned public head.001.001.04; custom profile and release arithmetic only. No full-message, MDR, community, directory, signature verification, settlement or real cutover acceptance.',python=sys.version.split[0],validator=(version.stdout+version.stderr).strip,schemaSha256=SCHEMA_SHA,runnerSha256=hashlib.sha256(Path(__file__).read_bytes).hexdigest,passed=len(checks),checks=checks,schemaCases=results)
Path(args.output).write_text(json.dumps(output,indent=2)+'\n');print(json.dumps({'passed':len(checks),'schemaCases':len(cases),'schemaSha256':SCHEMA_SHA}))
if __name__=='__main__':main
Case: the same header passes XSD and fails two teaching-profile conditions. The report must preserve both observations and identify the responsible rule.
Common pitfalls
Green schema result treated as full acceptance; signature presence treated as authenticity; text patterns treated as directories; tool failure treated as functional rejection.
Related topics: Messages, headers, and states · Versions, extensions, and interoperability · Testing, diagnosis, and operations
Pin artifacts and execute positive and negative boundaries while separating structure, profile, semantics, authenticity, and business effects.
Reference: BusinessApplicationHeaderV04 XSD · BigSavant ISO20022 fundamentals professional assessment2026.10