Read an .exar1 out, classify every sequence on a scanner, and name CMRR's spectroscopy suite - #17
Merged
Merged
Conversation
An archive read as 1/1800th of itself and reported no error. archive/P1/ Investigators.exar1 is a 97 MB whole-scanner export written by twelve successive CopyProgramsPipeline saves; reading it yielded 21 instances of 31164, one program of 499, and 317 KB of JSON. Nothing raised, and the result looks exactly like a small archive. _live_instances resolved the live set by filtering InstanceChangeSet to the head changeset. That table records what each changeset *touched*, so on a file written in one changeset it is the whole tree and on any other it is the last save's delta. The live set is the head's element map instead: a ChangeSet names a BaseElementMapId holding the tree as of an earlier point and a DeltaElementMapId holding this save's changes, each a flat run of 32-byte element/instance GUID pairs, the delta superseding the base element by element. 28109 plus 3056 is every Element row: 499 programs, 61 directories, 8217 scans, 1953 pauses. generate._head_map_id already wrote into that map; nothing read from it. Every other corpus archive gains the same four nodes -- two EdfDirectory and their two EdfString labels, created in an earlier changeset and never touched again -- which is precisely what made the folder tree look flat. One gains 99: NAV_optionscan_P1_loadtest holds its program twice, both named NAV_optionscan_P1 (2), under Investigators/Frederick and Investigators (2)/ Frederick, so the console disambiguates a repeated directory name as it does a program name and that file really was imported twice. With the versions right the hierarchy is recoverable, and it is not where the node structure suggests: an EdfDirectory carries no Children in any archive and an EdfProgram no ParentElementId. It is in the root EdfStructure's own content document under ParentDirectoryId, keyed in two GUID spaces at once -- a directory under its ObjectId, a program under its Element_id. Resolving every key in one space finds all 61 folders, misses all 499 protocols, and reads as a tree of empty folders rather than as a lookup in the wrong space. directory_parents, parent_of and path_of read it; the recovered path agrees with the printed one component for component, differing only at the root. Surfacing the duplicate broke the driver in a way worth keeping: apply_protocol paired against archive.steps, which flattens every program, so each scan name appeared twice against a printout naming it once and the guard against pairing a repeated name to the wrong copy refused all 31 -- a driver that writes nothing, reported as a clean run. build.target_steps now picks one program, a lone one or the one program_name reads out of the printout's header. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
siemens-protocol-tool archive <file.exar1> is the reading half, where exar is the writing half. exar/inspect.py composes archive, patch and geometry and establishes nothing new about the format beyond the observations below; the document is not a parsed printout and does not pretend to be one. Three things in it have no counterpart on the PDF side. The whole ASCCONV block, nested by the structure its key names describe. The prescription links, which a printout cannot record at all -- a linked scan prints byte-identically to an unlinked one. And who supplied the sequence: the protocol names its binary under %SiemensSeq% or %CustomerSeq%, which is Siemens stating the owner rather than this tool inferring it, and it carries the same standing as VB17A's printed SIEMENS:/USER:. Verified over 1031 scans in 31 archives with no contradiction -- no binary under both prefixes, every %SiemensSeq% binary the catalog knows is one of its kernels, every %CustomerSeq% one it knows matches a third-party signature. It is carried into the catalog under the archive's own spelling rather than translated, so an evidence line quotes what the file says. On Potpourri_P1 that resolves all 18 scans where the PDF, which prints only the kernel, leaves four unaccounted. Indices nest as decimal string keys rather than JSON list positions, for two reasons that both come from the file: an array node also carries an __attribute__ member, so it is not a pure sequence, and the arrays are sparse. Literals are kept as spelled, since the console writes 0x1 for some flags and 1 for others. ASCCONV also spells an index two ways -- alTE[0] and a bare sComment.0, 2214 assignments over one key family -- and a tokenizer built on the bracketed form alone declines the rest silently, which is why the nesting test counts leaves against the flat table. list, sequences and check now take an archive wherever they take a PDF, and --program picks one protocol out of a file holding several. What those commands see of an archive is the console's Preview summary, roughly forty parameters a scan rather than the several hundred a page prints, so a policy written against a printout mostly misses; the complete set is the ascconv tree. Two claims elsewhere in CLAUDE.md did not survive the corpus sweep this makes cheap. The empty-Kind relation is not one export's quirk but 1248 of 1440 relations across 18 of 51 programs, and it is not the running order mirrored into the relations map: only 2 of K23EB's 19 distinct pairs are adjacent in the chain. And CMRR is not the only sequence that stamps a build -- rslh_ep3d_vaso writes vx_ep3d 7b674ae rslh6.0, with no semicolon, so build_id returns it whole and it can never match a gate. The navigator .prot rule is settled in the direction its own note left open. On console-authored scans it is exception-free: all 53 pair alFree[15] = 2 with an _ABCD_* name and = 1 with the bare one. T09/T19 are not console-authored, which was the proposed explanation -- NAV_optionscan_P1 holds them at 2, so the move to 1 is the driver's, exactly as in the driver_loadtest scan. Two returns therefore disagree on identical input, which puts the counterexample on the import side rather than the authoring side. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
cmrr_mbep2d_se is CMRR's multiband EPI spin echo, supplied by the protocols' owner. It is the third member of a family whose other two the catalog already names, and the largest unnamed third-party binary in the shipped corpus at 282 scans across 89 protocols in the whole-scanner export. Which detector can name it depends on which file you have. A Numaris/X page prints the kernel, so cmrr_mbep2d_se and cmrr_mbep2d_diff both read as epse there and only the Special card can separate them; an .exar1 prints the sequence file name and settles it outright. The entry carries both routes -- binaries for the archive, base_binaries plus special_all for the page -- and they agree on every corpus scan that has both. Deriving the card route needed the archive to label the page. Joining the 18 archive/PDF pairs on scan name gives each printed Special card the binary that actually wrote it, and the fingerprints then separate cleanly: Triggering scheme is on all 17 SE scans and on none of the 23 diffusion ones, nor anywhere in their 22-label union, while Disable B1 control loop and PF omits higher k-space are on every diffusion card and no SE one. That is a method worth reusing -- the printout says what a card holds and the archive says whose card it is, and neither file answers both questions alone. 54 shipped scans match, all epse; an archive confirms 30 of them as cmrr_mbep2d_se and none as anything else. 52 move off cmrr-epi, whose "EPI package, multiband off or not printed" was the vague but correct answer for the CMRR field maps. The other two are literally named cmrr_mbep2d_se and cmrr_mbep2d_se_3echo and were reported as diffusion, because with multiband on they print the generic MB card on the spin-echo kernel. Priority 30 rather than 20 is what corrects those; condition count says the opposite, as it did for the package entry. The residual risk is stated in the note: Triggering scheme is an option rather than a property of the binary, so a diffusion protocol with it enabled would read as SE from a printout alone. Unobserved across the corpus, and the archive names the binary when there is one -- which is what makes "prefer the archive where both exist" a rule with teeth rather than a preference. Regenerating the snapshots surfaced a gap this change did not cause: examples/XA60/driver_loadtest.pdf shipped without a golden snapshot, so it was covered by neither the golden test nor the accounting one, and that test had been skipping for want of it. Its snapshot is added and its five MGH scans join the pinned unaccounted product, which now reads over four Potpourri exports rather than three. Also carried in this commit, and not the author's work: spice-epsi gains the binaries ZPL_RG_EPSI_FID_v1h and ZPL_RG_EPSI_SE_v1b. Those were authored in a parallel session and were sitting in the working tree when catalog.json was staged whole. They name 20 scans in the whole-scanner export that the entry's four earlier spellings missed. Recorded here rather than split out because the commit is already published; the lesson is to stage by hunk when two sessions share a tree. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Several parameters exist only on diffusion scans -- the number of directions, the diffusion scheme, the b values -- and the console prints them on a card of their own. That makes the card a structural property of the sequence where every Special-card label is a checkbox someone can clear, which is a better thing to identify a diffusion scan by than the multiband card it shares with BOLD and spin echo. cards_all is the clause. It requires a scan to print a named card, matched on the group at the *head* of the printed title rather than the page at its tail: a title is "<group> - <page>", so Special is a page of the Sequence card while Diff is a group with pages. VE11C splits diffusion into Diff - Body, Diff - Neuro and Diff - Composing against Numaris/X's single Diff, so matching the tail finds Body and Neuro and misses all 26 VE11C diffusion scans -- which is the first thing this got wrong, and it reported them as printing no card at all rather than failing. Like base_binaries it gates the Special-card route only, never binaries, for a sharper reason than the kernel gate has: a scan read from an .exar1 prints no cards at all, so gating the binary route would make every archive stop matching. Both routes are exercised -- a page identifies cmrr_mbep2d_diff by "prints the Diff card", an archive by the binary with cards=['Preview']. cmrr-mb-epi-diffusion gains cards_all: ["Diff"]. All 96 corpus scans it matched before print one and nothing that is not diffusion prints one, so nothing is reclassified: the snapshot diff is 96 pure insertions of one evidence line and no deletions. Two corrections to the entry added in the previous commit. Its priority of 30 was unnecessary and the reason given for it was false -- cmrr-mb-epi-se names three Special labels against diffusion's two, so it already outranked it on weight at equal priority, and "condition count says the opposite" was simply not true. It is back at 20, and CLAUDE.md now says to read rank() before reaching for a priority, because a priority added on a wrong reading is one nothing will ever question. Diffusion instead moves to 25, deliberately above it: with the card clause the two tie on weight, so the winner would otherwise fall to file order, and if a diffusion protocol ever enables the Triggering scheme option the SE entry keys on, the card it cannot help printing is the better evidence. That closes the residual risk the SE note recorded. Not folded in: the twelve cmrr-epi scans that print a Diff card -- ABCD_dMRI_DistortionMap, DWI_98Dir, one named cmrr_mbep2d_diff -- stay where they are, because claiming them means loosening special_all, which is a separate judgement. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The two files name a sequence differently: a Numaris/X page prints the kernel mpres, an .exar1 prints the sequence file name eja_svs_mpress. cmrr-megapress knew only the kernel, so it matched every printout and no archive at all -- 427 scans in the whole-scanner export were reported third-party on the owner statement alone, with nothing saying which sequence they run. They are the same sequence rather than two, joined archive-to-printout on the five scans in Frederick_P2 where both spellings are visible: same scan names, kernel mpres, and both Special-card labels on every one. So this lists the second spelling beside the first, as cmrr-slaser already does for sead and slaser, rather than adding an entry. No golden snapshot moves, because the kernel route already covered every PDF in the corpus. The whole gain is on the archive side. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
CMRR ships two of them, by different authors, and the catalog held one entry covering the pair. Both attributions come from the protocols' owner: Deelchand's is now dkd-semilaser and Auerbach's is cmrr-semilaser. They are easy to confuse and hard to confuse, in that order. The kernels are one character apart -- a page prints slaser for Deelchand's and slasr for Auerbach's -- but the cards share exactly one label out of 9 and 39: Deelchand's prints GOIA/FOCI and FA AutoCalib, Auerbach's the OVS and VAPOR blocks. The three labels this keys Auerbach's on appear on every one of his scans and on none of Deelchand's. Nothing was mis-attributed before, because slasr matched no entry at all; the eja scans were simply unidentified. dkd-semilaser also gains the archive spelling svs_slaser_dkd beside the two kernels, so one sequence is now named by all three of the ways the files spell it -- sead on a VE11C page, slaser on a Numaris/X one, svs_slaser_dkd in an .exar1. That is what makes its 421 scans in the whole-scanner export resolve, against 7 for Auerbach's. The pair also shows the two files disagreeing about what they can resolve. An archive names eja_svs_slaser and eja_csi_slaser apart; a page prints slasr for both and their cards share 39 of 40 labels, the one difference being an option that may simply be off. So the archive declines the CSI variant and the page claims it. Rather than assert a split the corpus cannot support on one scan of each, the family is worded without "single-voxel" and the note says so. INVESTIGATOR_UNACCOUNTED drops from 73 to 70 and loses slasr from its kernel list -- the pin behaving as its comment says it should, coming down when an attribution arrives from the owner rather than because anything was written to make it fall. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Twelve minutes serially, five at -n 8, with identical results -- 2558 passed and 18 skipped either way, verified at -n 8 and -n auto before this landed. pytest-xdist joins the dev extra, which is what CI installs, and -n auto goes on the whole-suite step alone. Not on the targeted steps above it: those tee their output and grep it for "skipped", because a skip in the OCR, front-end or .exar1 suites means the fixtures were missing rather than that the tests passed. Parallel workers interleave that output, so the guard would be reading scrambled logs to answer the one question it exists to ask. It stays out of addopts for the same reason -- a developer running one of those files by hand should get the serial output the greps expect. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
eja_svs_slaser and eja_csi_slaser share the kernel slasr and 39 of the 40 labels on their Special cards, the one difference being an option that may simply be off. So an .exar1 named them apart and a page did not, and cmrr-semilaser claimed both -- which the entry's note recorded as a cost rather than a defect, for want of anything better. There is something better, and it is what single voxel means: whether the scan phase encodes. Scan Res. A >> P and R >> L are absent on all 27 single-voxel spectroscopy scans in the corpus and 8 or 16 on all 6 CSI ones, with nothing against, and they are printed only on Resolution - Common. parameters_at_most is the clause, and it is the first that reads a printed value rather than the presence of a label. It is satisfied by absence as well as by a value within the bound, because a sequence with no such setting prints none; the bound is 1 rather than 0 on the owner's reading that a 1 x 1 matrix would mean the same thing, which the corpus does not show. An unparsable value is refused rather than assumed small. A label printed on several cards must satisfy the bound on every one -- the same flattening trap the Position note describes, met in the matching rules this time rather than in a reader. Exactly one scan moves: eja_csi_slaser drops out of cmrr-semilaser, so the two routes stop disagreeing and the entry earns "single-voxel" back in its family. INVESTIGATOR_UNACCOUNTED goes 70 to 71 and keeps slasr with one scan under it rather than losing it with three, because that CSI variant is still unattributed and the pin should say so. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
MEGA-PRESS and MEGA-semi-LASER are different sequences, per the protocols' owner. cmrr-megapress matched on MEGA flip angle and Editing pulse BW, which are what any MEGA-edited sequence prints rather than anything about this one, so its card route also claimed three mslsr scans -- eja_svs_mslaser twice and eja_csi_mslaser once. base_binaries: ["mpres"] gates the card route to its own kernel. It costs nothing: mpres is the kernel on both VE11C and XA60, and the binary route is never gated, so an archive still resolves the sequence by its file name. The three scans are unrecognized now, which is the right answer until someone names them, and no mpres scan moves. The shape generalises. Labels that describe a technique -- editing, multiband, navigation -- are printed by every sequence using it, where labels naming a sequence's own implementation are not. The CMRR multiband card had this problem and is why base_binaries exists; this is the second instance, so a two-label fingerprint of common parameters should be read as needing a gate rather than as a signature. INVESTIGATOR_UNACCOUNTED goes 71 to 74 and gains mslsr. That pin has now moved in both directions in three commits -- down when Auerbach's semi-LASER was named, up when its CSI variant was declined, up again here -- which is what it is for. A count that only ever falls would be a target, and writing signatures to make it fall is the one thing the attribution rule forbids. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two more attributions from the protocols' owner, and between them the eja spectroscopy suite is now told apart three different ways. cmrr-semilaser-csi is the chemical-shift-imaging variant of cmrr-semilaser and the same author's. It shares that entry's kernel and all 39 of its Special-card labels, so only the Resolution card separates them, and it needs the exact complement of the bound that entry uses. parameters_at_least is that complement, and deliberately not a negation: absence fails there where it satisfies parameters_at_most. So "at most 1" and "at least 2" partition -- a scan printing a 1 x 1 matrix goes to the single-voxel entry alone. Two negations would have left a gap at 1 that both entries claimed, on a value neither the corpus nor the owner rules out. cmrr-mega-semilaser is the MEGA-edited semi-LASER that cmrr-megapress was claiming until the previous commit gated it off. Its kernel mslsr is unique to it, and its card is exactly the pair of techniques its name says: the MEGA editing labels MEGA-PRESS also prints, over the semi-LASER refocusing block -- GOIA refoc. pulses, HS refoc. pulse N and R -- that MEGA-PRESS does not. One label from each is what names the sequence rather than either technique, which is the lesson the previous commit recorded, applied. The single-voxel and CSI spellings share the entry rather than being split, because the owner named them together. Four golden scans move, all from unrecognized: three MEGA-semi-LASER and one CSI semi-LASER. INVESTIGATOR_UNACCOUNTED returns to 70 and loses both slasr and mslsr, having gone 73, 70, 71, 74 and back over five commits -- which is what a pin is for, as against a bound that would only ever have been allowed to fall. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three more from the protocols' owner. With them the eja spectroscopy suite runs to eight sequences over six kernels, and it is where the catalog's clauses divide the work cleanly: the card names the author, the kernel names the technique, and neither is sufficient alone. 22 labels -- the VAPOR water-suppression delays, the spoiler and timing controls -- are printed by every eja sequence, so a card fingerprint says only whose implementation a scan runs. PRESS, LASER and STEAM are techniques anyone may implement, so a kernel says only which technique. Each entry pairs three of the shared labels with its own kernel. Keying on a technique name would be reckless if Siemens shipped a sequence using one, so a test asserts that every scan printing press, laser, steam, slasr or mslsr anywhere in the corpus is an eja_ scan -- and the archives put all of those under %CustomerSeq%, which is the scanner saying the same thing. Eight golden scans move, all out of unrecognized: four PRESS, two LASER, two STEAM. INVESTIGATOR_UNACCOUNTED goes 70 to 62 and loses those three kernels. Auerbach's diffusion-weighted semi-LASER was named too and is deliberately not here. eja_svs_slaser_diff appears 22 times in the whole-scanner export and never in examples/, so an entry would be one nothing shipped could exercise and test_every_shipped_signature_matches_something_in_the_examples would fail on it, correctly. Widening the examples is the fix that rule names -- shipping one of the seven DWS protocols that run it -- rather than relaxing the test or letting the entry sit unverified. The attribution is recorded in CLAUDE.md so it is not lost while the example is missing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The scanner rejected both archives from the previous commit. Each opened and each navigated to Investigators/Frederick, where nothing appeared -- and in scanner terms a Program is a protocol, so that is the program failing to build rather than a program opening empty. Two real defects turned up, and this is honest about what they explain, which is: not necessarily this. Content is addressed by hash, so rewriting a document re-addresses it and strands the old row -- and appending is a loop, so a 96-scan program left 95 dead copies of its own document beside one live program instance. Every console archive carries exactly one orphan, the placeholder branch's structure content, and nothing else in 21 files. Archive.prune collects the rest at write, taking what this library created *or displaced*: origin alone keeps the seed's own program document forever, since that arrived with the file and was stranded by editing it. A read and write with no edit between still drops nothing, on all 20 corpus archives. The five step-keyed maps were left in append order where every archive the scanner has accepted keys them lexically -- which this file already said, under "do not sort on re-encode", about the lists inside them. Newtonsoft reads these into dictionaries and so probably does not care; matching costs nothing and removes a difference from every known-good file. Both now fail loudly: validate refuses either, three tests cover them, and reintroducing each defect at runtime makes the checks fire. A test helper that staged a multi-program backup was building its maps in running order too, so it was staging a shape no scanner has been handed. Neither defect is established as the cause. The 40-scan assembly that loaded 33 scans back went through this same code and carried both. Everything else checks out against a console export -- folder tree, all five maps, ranks, element types, changeset states, the element map, link field sets, Children resolution, content hashing, and no $ref preceding its $id. What is left is size, since the largest program any corpus archive holds is 74 and these are 96 and 117, or something about these particular protocols, which are by construction the rarest sequences we have. A scanner is the only way to tell. So the deliverable is now six files rather than two. gaps_1of4..4of4 carry the same 96 sequences in programs of 25, well inside the range the console is known to handle, and each opens with a control scan the scanner has exported before -- so a chunk that appears holding only the control says the other protocols were refused one by one, while a chunk that does not appear says the program could not be built and rules size out. The two whole archives are rebuilt with both fixes and kept, since if they load the chunking was unnecessary. All six validate clean with zero orphans and sorted maps, and every protocol in them is byte-identical to the corpus scan it was copied from -- 117, 96 and 25/25/25/24, checked against every protocol in the corpus rather than against the donor the manifest names. Also noted while sweeping: tSequenceFileName can carry a subdirectory under the owner prefix, and one scan spells it with a doubled separator. Four binaries appear under both a bare and a subdirectory spelling, so counting distinct values overcounts the sequences by four. Nothing is broken by it, because sequence_owner splits on the first separator and header_of takes the binary with rsplit -- correct by luck rather than design, and worth keeping deliberately. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The 96 gap sequences went to the scanner as four chunks of 25. Chunks 2 and 4 imported; 1 and 3 were refused outright, same size, same build path, same seed. So the size hypothesis this file was carrying is dead, and what is left is which protocols are in them. That is a harder failure than the greying-out rule describes. A scan the console dislikes normally arrives and is greyed out, and scans whose binaries are missing were simply dropped from the 40-scan assembly while the program still built. Here nothing arrives: the folder is there and the program is not. The two that imported settle something else. All 49 protocols came back byte-identical with the running order preserved and no scan missing -- including the 26 the console greyed out. So "inconsistent" is a verdict recorded against a protocol stored faithfully rather than an edit to it, and the write path is sound: what fails is the console's judgement of these protocols, not our construction of them. 26 of 49 greyed out is itself worth recording. Each is a byte-exact copy of a protocol some console authored, so consistency really is a property of the whole parameter set in its context, and a protocol lifted out of the export it was written in has lost that context. For the corpus's rarest sequences that is the common case rather than the exception. The 23 that survived have printouts now. The retry is nine groups of six rather than two of twenty-four, so one bad scan costs five others. The three hcp_mbep2d_* are grouped together on purpose: they are already known to be refused whatever we write, so a known failure is confined instead of taking six good scans with it. The archives and the returned files are not committed -- inbound/ and outbound/ are gitignored as of a26c78c, alongside loadtest/ and archive/. The outbound files from the two commits before that are still tracked; say the word and they come out of the index. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The 48 stranded sequences went back as nine groups of six; six imported. Groups 3, 4 and 8 were refused, so the blocker is confined to eighteen scans and is not what it looked like. It is not the exotic protocols. Group 4 is six small ordinary ones -- ciss, three csi_*, two can_neuromelanin -- at 16 coils and one slice, while group 1's three hcp_mbep2d_* at 130-172 KB imported without complaint. csi_st imported and csi_fid, csi_se and csi_slaser did not; se_mc imported and se did not. Sweeping every ASCCONV key and XProtocol tag across the 38 accepted scans and the 18 refused ones finds no key present in even two of the three refused groups and absent from every accepted one, so there is nothing offline to write a check against and bisection is the only instrument left. Two things this file said need correcting. "A protocol from an older CMRR build can be rejected on import, whatever we write" was too strong. Sent on their own, all three hcp_mbep2d_* imported, returned byte-identical, kept the running order -- and were greyed out. The earlier assembly showed them not arriving; this shows the same protocols arriving and being inconsistent. So the outcome is not a property of the protocol alone, and the old-build reconciliation account no longer explains both observations. And the churn list was ASCCONV-only. fl3d_vibe and se_mc came back with their parametric-mapping functor removed -- <Connection."c1"> losing its T1mapFunctor/T2mapFunctor, EXECUTE emptied, sParametricMapping.* changed -- while BEAT and space had sCommonIterRecon.*, sPat.* and the derived scan times recomputed. A diff reading only the ASCCONV block reports all four as unchanged. 45 of 49 did return byte-identical, which is what makes the four legible rather than lost in noise. The archives are not committed; inbound/ and outbound/ are gitignored. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The eighteen candidates went back as nine pairs. Six imported, and the three
refused are exactly one pair from each of the three refused groups:
{ZPL_RG_EPSI_FID_v2f, blade}, {ciss, csi_fid}, {jn_svs_special_ve11c,
medic}. So there is one offending scan per group of six, and six candidates
left. All eighteen returned protocols were byte-identical, with no repairs.
The pairs rule out two things on their own. csi_se and csi_slaser import
while one of ciss/csi_fid does not, and resolve, se and petra import while
one of medic/jn_svs_special_ve11c does not -- so it is neither stock versus
third-party nor the sequence family.
Repeating the key sweep against every scan now known to import, a 1067-key
vocabulary over three rounds, leaves blade as the only candidate carrying
anything unseen: sBladePara.*. That is weak. It is the corpus's only BLADE
sequence, so a unique parameter family is what it would have whether or not
it is the offender, and the other two pairs show nothing at all.
Eight more sequences came back consistent -- can_neuromelanin and its _pk
variant, csi_se, csi_slaser, resolve, se, petra and ZPL_RG_EPSI_FID_v2e --
which puts the corpus at 51 sequences with printouts that had none.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Bisection ran to singles. blade, ciss and medic are refused alone, in a two-scan archive holding nothing but the control, so the cause is inside those protocols rather than an interaction between scans -- a single-scan program being the smallest possible test of that. ZPL_RG_EPSI_FID_v2f, csi_fid and jn_svs_special_ve11c imported alone and clear their pairs. What the search excludes is worth more than what it found. Against the 93 scans now observed to import, a 1067-key vocabulary, there is no ASCCONV key or XProtocol tag the three blockers share and the accepted scans lack: ciss and medic have none at all, and blade has only sBladePara.*, which is what the corpus's one BLADE sequence carries whether or not it is the offender. Sweeping values rather than keys turns up seven keys where all three hold something unseen, and all seven are per-scan unique by nature -- alTR, dRefSNR, lScanTimeSec, tProtocolName. It is not size, coils, slices, sequence family, or stock versus third party. csi_se and csi_slaser import while ciss does not; resolve, se and petra import while medic does not; all three blockers are %SiemensSeq%. Nor is it provenance -- petra and se appear only in the same unshipped whole-scanner export and import fine. All three are licensed Siemens product options, which is the leading explanation and is not established, since petra is one too and imported. Either way the protocol is the wrong place to keep looking: nothing in it distinguishes them, so the next question is for the console. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three sources describe .exar1: Tobias Rautenkranz's exar1-read (gitlab.com/tobiasrautenkranz/exar1-read, GPL-3.0), the NeuroStars thread "Parsing EXAR files", and a Stack Overflow answer. They independently confirm the container, raw DEFLATE at wbits=-15, the EDF V1 header line, the JSON beneath it, XProtocol in Data, SHA-1 content addressing, .NET Guid.ToByteArray ordering, the seven tables, the FirstStepId/LinksFrom chain, and that the printed Card grouping is absent from the archive. Nothing already established had to change, which is worth stating plainly: the reverse engineering holds up against independent descriptions. Four additions, three adopted and one recorded. The structure document states the folder tree twice. Beside ParentDirectoryId it carries RootDirectoryId, SubdirectoryIds and SubprogramElementIds -- the tree downwards, with the top named outright. directory_children and declared_root read them and validate compares the two directions. This is the one place a source would have saved real work: reading the tree wrongly turned a 97 MB export into 61 empty folders and 499 orphan protocols and presented as a small file rather than an error, where two independent statements of one tree make it a single failing line. They agree on all 20 corpus archives and RootDirectoryId is the unique zero-parent directory in each, so it is pure redundancy -- which is what makes it worth checking. SubprogramElementIds is in none of the three sources; it turned up on dumping the document once the others said where to look. EdfDecisionStep is a seventh step kind and is now in STEP_KINDS. The corpus has none, but the errors are lopsided: a kind that never appears costs nothing, while omitting one drops its step from the running order and reports the file as corrupt rather than as unknown. Content.Format is the literal "DS" -- confirmed on all 20 archives rather than adopted. InstanceChangeSet.State is 0 current, 1 change, 2 delete; the corpus holds only 0 and 1, so delete is taken on their word and nothing depends on it, the live set coming from the element map. One correction to my own reading while doing this. I wrote that every archive carries two EdfStructure nodes and that picking the first was luck. That was a raw-table count read as a live-set count: the second belongs to the placeholder branch and is not live, so the live set has exactly one. tree_root now prefers the node declaring ParentDirectoryId anyway, since a reader reaching the other would report a flat archive rather than an error. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Reading the Stack Overflow answer sent me back through the corpus, and the sweep turned up a defect that has nothing to do with the sources: a step's ObjectId is not unique, and we were reading it as though it were. This file claimed copying a protocol inside a directory reuses the source's step node -- "one element id per shared object id". It is the other way round. The copy gets its own element and its own live instance and keeps the source's ObjectId, so Frederick_P2 has 510 live step instances over 510 elements but only 435 distinct object ids, 67 objects carrying two apiece. The running order is a chain of object ids, so resolving it through an archive-wide object index hands both programs the same instance. 75 step elements were never walked and 27 protocol documents never read, and 39 of the 67 pairs hold different protocols -- one being eja_svs_laser beside eja_svs_press, different sequences, with the wrong one returned. steps_of now resolves each chain id against the program's own Children, which are element ids, keeping the global index as a fallback no corpus archive needs. _step_coverage exists to catch exactly this and was passing vacuously, for the same reason: it compared object ids, 435 against 435, while 75 elements sat in no running order at all. It counts elements now. The general lesson is that a GUID-space error cannot be checked in the space it occurs in, and the old claim had all three of its supporting checks phrased in that space. No pinned count moved, which is worth stating: the twins mostly run the same sequence, so the corpus totals were right by luck while individual scans were not. Two tests cover it -- every live step walked exactly once by element, and the laser/press pair each reading its own protocol. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The Stack Overflow question links a shoulder protocol Siemens distributes from magnetomworld, shoulder_4ch_flex.exar1. Every archive in the corpus came from one site's two scanners, so this is the first independent file the reader has met -- and it is not the same Numaris generation. Its baseline is N4_VE11S_LATEST_20170215: .exar1 is not an XA format, and Numaris 4 spells the baseline as one underscored token with no keyed fields at all. Nothing had to change to read it. 33 steps in one program, the folder tree resolving to Root/New Tree/Shouolder/Flex_4Ch/routine (the typo is the file's), validate clean, and all 76 content blobs re-encoding to their stored hash -- so the Newtonsoft serializer derived from XA60 reproduces a 2017 Numaris 4 file byte for byte. Read and write with no edit returns an archive identical in every table, every content row, the running order and all 28 protocols. sequences names all 28 scans as stock Siemens, and one step is an EdfPauseStep used as a section divider. The slice-geometry model holds as well: 24 groups read, worst deviation of the stored array from the recomputed one 7.1e-15 mm. That formula came entirely from XA60 option scans, and a release eight years older agrees with it. Two differences. major_version returned empty, because the release is a token rather than a field; it now reads both spellings, keyed field first, with a narrow anchored fallback rather than a general search for a version-looking token -- a loose pattern would give a confident wrong answer where the empty string was merely unhelpful. And a VE11S protocol names its first XProtocol block "MultiStep Controller" where an XA60 one leaves it unnamed; both carry two blocks with the ASCCONV in the second, so it is a labelling difference rather than a structural one. That block is what the Stack Overflow answer printed, and reading it as a protocol document without parameters would be wrong. The file is not added to examples/. It is Siemens' to distribute and that is not this layer's decision; CLAUDE.md names it so it can be fetched again. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Six rounds of import and export have now given every one of the corpus's 117 sequences a scanner verdict. 68 are consistent -- 53 of them gap sequences that had no printout at all before, now covered by a single 53-scan export -- 46 import and are greyed out, and 3 refuse to import: blade, ciss, medic. The three counts are exhaustive and disjoint, which is what says the classification is complete rather than merely large. This round is what makes it trustworthy rather than circular. The verdicts came from four rounds of differently-shaped archives; the fifth rebuilt them into one 53-scan CORPUS_CONSISTENT and all 53 printed, while CORPUS_INCONSISTENT returned all 41 protocols byte-identical with only its anchor printable. The split therefore predicts behaviour in a build it was not derived from. 53 scans is also the largest program this library has had a scanner accept. Six of the 21 catalogued sequences were greyed out: ZPL_RG_EPSI_FID_v1h, ep2d_DE_pcasl_iPAT, ep2d_bold_MGH_tb, ep2d_se_VASO, mjd_mclean_flipback and svs_slaser_dkd. Every one carries a signature because shipped examples exercise it, and svs_slaser_dkd runs 421 corpus scans. So a signature, a printout and hundreds of examples say nothing about whether a protocol lifted out of its own export will load elsewhere -- the context rule, in its sharpest form yet, on the sequences we know best. And the console's repairs reproduce. Importing CORPUS_CONSISTENT changed exactly four protocols -- BEAT, fl3d_vibe, se_mc, space -- the same four, with the same field sets, it changed when those protocols arrived inside differently-shaped archives a round earlier. Two independent imports agreeing field for field makes the repairs a property of the protocol meeting this console rather than an artefact of one import. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Chasing the owner's note about the six greyed-out catalogued sequences turned up sProtConsistencyInfo.tBaselineString, which reads "ConversionNeeded" on a protocol the console will grey out and is absent from every one that loads. Across the 96 scans with a scanner verdict: 43 of 43 carrying it were greyed out, 0 of 53 consistent scans carry it. That makes it a sufficient predictor, checkable offline, and the first thing found that anticipates the greying-out rule rather than restating that only a scanner can answer it. It is not necessary -- ep2d_asl, svs_edit and BEAT_FQ_nav were greyed out without it -- so absence means "not predicted to fail", never "will load". Inverting that asymmetry is the expensive error. The neighbouring tMeasuredBaselineString says where a protocol came from: "N4_VE11C_LATEST_20160120" on ZPL_RG_EPSI_FID_v1h. That spelling is only recognisable because a Siemens-published VE11S archive was read a day earlier. ulVersion is no help -- all six read 66010002, the XA60 value, because an XA60 console wrote the archive. Origin release lives in sProtConsistencyInfo, not in the protocol version. It also separates the two failure modes: blade, ciss and medic, which stop a program building outright, carry no tBaselineString at all, so whatever refuses them is not conversion. The owner reports ep2d_DE_pcasl_iPAT, ep2d_bold_MGH_tb and mjd_mclean_flipback as VE11C/VB17-era sequences with no XA60 build here, which is why they cannot load; svs_slaser_dkd and ZPL_RG_EPSI_FID_v1h he reports as installed and working and could not explain, and he did not address ep2d_se_VASO. Recorded as supplied. ConversionNeeded covers all six and resolves the puzzle without contradicting him: the protocol is stale even where the sequence is current. svs_slaser_dkd runs 421 corpus scans on an installed sequence -- what fails is this protocol, saved under an older baseline and never converted. So a greyed-out scan is not evidence that its sequence is missing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Where a sequence has several copies, the one to keep is the one needing no conversion -- sProtConsistencyInfo.tBaselineString absent rather than "ConversionNeeded" -- and among stale copies the later measured baseline. The later-release-wins rule is the protocols' owner's; that field is what implements it, since tBaselineString never holds a release string, only the flag. Over the whole corpus it moves exactly one exemplar of 117. ZPL_RG_EPSI_FID_v1h has 9 copies, 4 of them current, and all 4 sit in the unshipped whole-scanner export -- so the old prefer-a-shipped-donor rule picked a stale one, and that scan came back greyed out. outbound/zpl_retry.exar1 carries the current copy for a scanner check. The sweep also settles that most of the greyed-out set cannot be rescued this way. 42 sequences have no current copy anywhere, svs_slaser_dkd at 421 of 421 copies needing conversion on a sequence its owner confirms is installed and working -- so every protocol this centre holds for it predates XA60 and none was re-saved. Where no better copy exists the fix is a fresh save on the scanner, not a better search. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Eight archives and seven printouts from six rounds of import and export now ship under examples/XA60/scanner_returns/, with their golden snapshots. The corpus goes from 20 archives to 28 and from 73 protocols to 80, and it gains what it most lacked: printouts for 53 sequences that had none, and the first examples of a great deal of spectroscopy. Wider data corrected six readings, each widened to what is true rather than excused. A repeated scan name is ordinary, not a hazard. An option scan runs thirty copies of one sequence and Keto MRS prints fastestmap five times. Earlier notes here said to drop non-unique names before joining a printout to an archive; that was a workaround that silently discarded real scans. They are paired instead -- grouped by name and zipped in running order, which both sides preserve -- and build.pair_scans is that rule in one place. apply_protocol already worked this way; the tests were the half still dropping duplicates, which is how a printed Coronal came to be compared against a different scan's stored normal. A group whose sides differ in length is still skipped, since the returns were printed after their inconsistent scans were deleted and choosing which copy a printed one refers to would write one scan's values into another. The Sequence card belongs to the binary. Keto MRS's spectroscopy scans print Measurements on Sequence - Common and Sequence - Special reading 9, counting transients, while lRepetitions is absent -- a different parameter from the Measurements a Contrast or BOLD card prints, and the driver was writing the first into the second. build.sequence_card_only refuses a general mapping for a label printed only there. One label is affected, on 76 scans. Sequence - Special is a page of the Sequence card, so this and the Special-card rule are one rule: the flattening trap the Position note describes, met a third time. An empty Preview means the protocol awaits conversion -- 24 of 24 empty ones carry ConversionNeeded and none of 928 current protocols is empty -- so the test asserts the implication rather than tolerating the emptiness. Three printout readings: a sequence with several repetition times prints TR 1 and no bare TR, as a multi-echo scan prints TE 1..TE 4; an unlocalized FID prints neither voxel nor VoI, and is the only kernel that may; and an acquisition time can run to hours, 1:42:33 h, which the clock pattern had anticipated as 1:02:03 while allowing only min after it. alFree[15] is an ABCD-generation field: every _ABCD navigator carries it over 226 scans and the older ones never do. The rule was written as "must be the setter", true of a narrower corpus. And a single-voxel spectroscopy scan's printed orientation describes its VoI. Of 21 in the corpus, 10 print Coronal against a stored transversal normal and 11 print Transversal, matching only because that is what an unused slice normal holds -- so none is evidence for the normal formula. The tolerance now follows printed precision, radians(0.05) per tilt; extravals X08 still separates the two composition orders at 7.1e-3 while tgse_asl, at 1.0e-4, never could. dkd_svs_sLASER joins dkd-semilaser as a fourth spelling: it is the XA60 build where svs_slaser_dkd is the VE11C one, and they split cleanly -- all 422 of the latter need conversion, all 10 of the former are current. That is why every svs_slaser_dkd scan greys out on a console whose sequence is installed and working, and the catalog had been naming the superseded spelling while reporting the current one as unrecognized. From the protocols' owner, via a Keto MRS export whose scans are named svs_slaser_dkd* and run dkd_svs_sLASER. UNACCOUNTED gains 24 entries, all sequences already unaccounted: the returns are built to carry one scan per sequence the catalog cannot name, so shipping them changes what the corpus exercises, not what the catalog knows. None of the pinned scans resolved. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A binary can be renamed between releases, and svs_slaser_dkd is. The owner confirms it: an XA60 save of Deelchand's semi-LASER is named dkd_svs_sLASER, where svs_slaser_dkd is the VE11C build. That falsifies something this file asserted two commits ago. The exemplar sweep reported 42 sequences with no current copy anywhere, svs_slaser_dkd among them at 422 of 422 needing conversion, and concluded that none of this centre's protocols for it had ever been re-saved. They had been -- under the other spelling, which the sweep was not looking for because it keyed on the binary. Grouping by sequence identity instead gives 41, and dkd-semilaser is the one signature in the corpus whose binaries split that way: 14 current under the new name, 0 under the old, nothing on either side against. So an exemplar search must ask the catalog which binaries are one sequence before concluding none is current. "No current copy of this binary" is not that finding on its own. For the remaining 41 there really is none, and a fresh save on the scanner is still the only fix. The split is pinned two-sidedly rather than as a count, because a current protocol appearing under the old name -- or a stale one under the new -- is what would break the rename account rather than merely move a number. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
examples/XA60/allcustomer_20260909.{exar1,pdf} is one scan per customer
sequence installed on the scanners: 35 protocols, every one current, with
a printout whose names and running order agree exactly. Twelve of them get
their first console-authored copy in examples/ -- until now they appeared
only inside archives this library assembled and a scanner handed back.
It weights sequences rather than scans, so its 8 unnamed sequences state
the catalog's gap list once instead of in proportion to anything, and
folding it into the scan-weighted unrecognized rate took that from 4.96%
to 5.46% for reasons that have nothing to do with the catalog.
ROSTER_EXPORTS excludes it there, the way the bulk import already is, and
UNACCOUNTED_ROSTER pins its scans by name instead -- the stricter check.
Two things the file settled:
An absent sparse assignment has two readings, not one. The navigator
setter prints Protocol filename: Generic with no sWipMemBlock.alFree[1] at
all, while all 46 corpus setters that store a value store 1 for Generic --
corroborated by tFree, which names Prisma_epi_moco_navigator.prot there.
So the sequence supplies its own default for an element nobody set, and
that default need not be the choice stored as zero. Mapping.absent_choice
records it, and both readings have to stand together: dropping "absent is
zero" turns MTC, Prio Recon and three others red at once. The writer now
leaves such a protocol byte-identical rather than refusing it for want of
a sibling element to insert beside.
And the three diffusion-weighted eja builds can now be told from their
parents. Joining archive to printout on scan name over 12 pairs, 6 each
way in three archives, shows each diff card is its parent's card plus
exactly "Diffusion weighting" -- 38 to 39, 40 to 41, 36 to 37 -- and that
label is on 6 of 1487 corpus scans and nothing else. The obvious reach is
wrong here: unlike CMRR's multiband EPI, these print no Diff card at all,
so cards_all matches nothing. The label alone names a technique, the
cmrr-megapress trap, so each entry carries it over its parent's
implementation labels and behind the same kernel gate; naming one more
condition outranks the parent on weight, so no priority is needed.
Attributions for eja_svs_press_diff and eja_svs_steam_diff were derived
from the card-and-kernel pairing, put to the protocols' owner as a
derivation to check, and confirmed by him.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The floor was unrecognized scans over curated scans, asserted under 5%. It had already been raised from 2% and had two tiers excluded from it, and it sat at 4.96% -- one curated example running three unnamed sequences short of tripping for no reason to do with the catalog. Per export the legitimate figures run 4.2% to 50%: GAPS_ONE_4 is one scan of two, CORPUS_CONSISTENT is 43% because it was built to carry what nothing names, the roster export is 23% because it states the gap list once. No bound separates a healthy export from an unhealthy one, and the aggregate sat under 5% only because many large fully-named protocols diluted them. The bound was measuring dilution. Everything it stood proxy for is already exact. The named pin catches a signature that stops matching and one that starts over-claiming, immediately and by scan name; the bulk import has its own counted pin. What neither covers is that each looks at its own tier, so an export excluded from both would be answered by neither -- which is how a future failure would get "fixed". test_no_unrecognized_scan_escapes_every_pin sweeps the whole corpus and subtracts what each pin claims. Falsified three ways: dropping the roster from the named pin leaves 8 scans standing, dropping the scanner returns 24, emptying the bulk binary set 62. It carries the usual two-halves guard, so a sweep that found nothing cannot pass by finding nothing loose either. third_party > stock stays, now over the whole corpus with no exclusions -- 947 against 410, a margin nothing has to tune. ROSTER_EXPORTS is deleted: it existed only for the rate, and being the one constant no other check wanted was the clearest sign the bound rather than the file was wrong. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
outbound/ holds archives this library built to send to a scanner. They are evidence for a round trip rather than examples of what the tool parses, so they do not belong in the repository -- .gitignore has covered the directory for a while, which has no effect on files already tracked. The branch is rewritten so they were never added; the note that cites two of them by name now says where they came from, so a reader is not sent looking for a file that is deliberately absent. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
bbfrederick
force-pushed
the
exar1-driver
branch
from
September 9, 2026 22:53
42f27c3 to
12e6a79
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Everything on
exar1-driversince #16 merged: 29 commits, mostly.exar1work, plus the sequence catalog growing to cover CMRR's spectroscopy suite.
Reading an archive out
siemens-protocol-tool archive <file.exar1>is the reading half of the.exar1support, whereexaris the writing half.exar/inspect.pycomposesarchive,patchandgeometryinto hierarchical JSON: the console'sPreviewsummary under the labels a printout uses, the whole ASCCONV blocknested by the structure its key names describe, the slice geometry, and the
prescription links between scans — which a printout does not record at all,
since a linked scan prints exactly like an unlinked one.
list,sequencesand
checknow take an archive wherever they take a PDF.Format corrections, and three of them were silent
changeset touched. Right on a file written in one save and catastrophically
wrong otherwise: a 97 MB whole-scanner export read as 21 instances of
31164, with no error anywhere. Every other corpus archive gains the same
four scaffolding nodes under the fix, which is what had made the folder tree
look flat.
ObjectIdis not unique. A copied protocol keeps the source's,so resolving the running order through an archive-wide object index hands two
programs the same instance — 75 step elements never walked, 27 protocol
documents never read, and 39 of 67 pairs held different protocols, one
being
eja_svs_laserbesideeja_svs_press. The check that should havecaught it was comparing object ids, 435 against 435, and so was passing
vacuously. A GUID-space error cannot be checked in the space it occurs in.
and the structure document states it in both directions — so the two can now
be checked against each other.
What a scanner said
Six rounds of import-and-export classify all 117 corpus sequences: 68
consistent, 46 imported and greyed out, 3 that refuse to import
(
blade,ciss,medic, all licensed product options). The counts areexhaustive and disjoint, and the final round rebuilt them into one 53-scan
program that all printed — so the split predicts behaviour in a build it was
not derived from.
sProtConsistencyInfo.tBaselineString == "ConversionNeeded"is the firstthing found that anticipates a greyed-out protocol offline: 43 of 43
scans carrying it were greyed out, 0 of 53 consistent scans carry it. It is
sufficient, not necessary — three greyed-out scans lack it — and inverting
that is the expensive error.
The catalog
CMRR's spectroscopy suite is now eleven sequences over six kernels. The two
semi-LASERs are split (Deelchand's from Auerbach's, one character apart in the
kernel); single voxel is told from CSI by the phase-encoding matrix it does
not print; MEGA-PRESS no longer claims MEGA-semi-LASER; and the three
diffusion-weighted builds separate from their parents on one Special-card
label,
Diffusion weighting, which is on 6 of 1487 corpus scans and nothingelse. Every attribution here came from the protocols' owner — two were derived
from the card-and-kernel pairing, put to him to check, and confirmed.
Corpus
The scanner returns ship as examples, and so does
allcustomer_20260909.{exar1,pdf}— one scan per customer sequence installedon the scanners, 35 protocols, every one current. Twelve sequences get their
first console-authored copy in
examples/.Test design
The unrecognized rate is retired. It measured the corpus rather than the
catalog: per export the legitimate figures run 4.2% to 50%, and the aggregate
cleared 5% only because large fully-named protocols diluted the rest. What
replaces it sweeps the whole corpus and subtracts what each pin claims, so an
export excluded from both pins leaves a scan standing — falsified three ways
rather than assumed to bite.
Checks
Full suite green: 3103 passed, 26 skipped.
black,isortandcodespellclean.
A note on history
The branch was rewritten to drop
outbound/— six generated.exar1archivesplus a manifest, 6.3 MB, committed before
.gitignorecovered the directory.They are evidence for the scanner round trips rather than source, and are kept
outside the repository. One commit,
Ship two archives to load on the scanner and re-export, held nothing else and so no longer exists; nothing elsechanged, and the tree is otherwise identical to what was pushed before.
🤖 Generated with Claude Code