Repository navigation
Releases: kineuro/nils
Release list
NILS v1.0.0-beta.1
The first beta (record 55, ruling 31 (g)).
Fixed
- The Data page stays fast on a dataset of 30 million files.
GET /api/datasets/{name}/summarykeeps what it counts while nothing it reads moves (a minute, or 15 seconds while a job or a read runs) and reads it once for requests that ask at the same time, while the jobs, the steps' states and their progress are read on every request. Its files are counted from a new index on a file's source and status (migration 106, a few minutes once on a large registry, while the engine starts). The set-aside subjects and the held IDs door's subjects are read from the copies' index a subject at a time, and the sources door's stamp reads an index instead of counting every pseudonymised file. On a registry of three million files the summary answers in 40 ms where it took 6.6 s, ten at once in 70 ms where they took 27 s, and reads fresh in 1.2 s; the sources door answers in 15 ms where it took 300 ms, and the held IDs door in 60 ms where it took 770 ms. The answers are unchanged. - A bring-in queued at the keyboard a step at a time (
nils jobs enqueue) is scored by System 1 at arrival.nils jobs enqueuerecorded detail sensitive and no grants, and the score, which runs under the grants its job recorded, skipped System 1 withno_grant. A job queued at the keyboard now records the keyboard's reach: every granteverythingholds, andsealed:seeonly where--unsealed-accessgave a reason.nils bring-inand a score run at the keyboard record the same, where they always recordedsealed:seebefore. - System 1 at arrival over a large batch runs in parts. The score queued one run, one container, over every new stack, and over 456,110 stacks the 4 GB container was killed for memory and every unit failed. The arrival score, a dataset's or a cohort's step door and System 1's archive door now queue a run over more stacks than one part holds with
--chunk N, each part a run of its own in the one job: 1,250 stacks for each GB a unit of the pipeline declares (5,000 for System 1), for pipelines whose units run together. Under a worker the job stays running until its last part ends, and a stop asked between two parts stops them there. - A new run never starts in a folder holding another run's files. A registry made again numbers its runs from 1, and a new run used the
runs/<id>/an older registry's run had left. Such a folder is now moved aside whole toruns/<id>.before-<when>, never deleted, and the run says so. A run taken up again keeps its own folder. - The sort no longer waits for pictures; a sort's previews and 3D views are made on the pictures lane, several scans at once. On a dataset of 30 million files the rules judged 493,000 scans in about ten minutes, and then the same classify job made previews at two scans a second, about 19 MB/s where the digest before it had read 138 MB/s, holding the main lane for days with the pick and the score waiting behind it. A classify's job now ends done as soon as it has judged its scans and read their headers, and the previews of what it judged are a job of their own on the pictures lane (
preview build --classified JOB --place NAME), queued before its 3D views, so the pick and the score start at once. Each scan's files were read a few at a time and one scan after another, and where the files lie on a network share every read waits on its round trip: a build over many scans now makes a quarter of its workers' worth of scans at once for the previews (one to sixteen) and an eighth for the 3D views (one to eight), each scan with its share of the workers, so a large dataset's pictures take hours, not days. A machine of seven workers or fewer (fifteen for the 3D views) builds one scan after another, as before, and the scans built at once hold about 4 GiB of their volumes between them at most, as the registry's rows, columns and frames count them, so a dataset of large scans is built a few at a time and a scan larger than that alone. What was built, current, skipped or failed is counted as before, a stop waits for the scans being built and keeps them, and a picture a reader asks for while the 3D views are built is still built between two scans, never beside a builder writing the same scan. The lists in a build's result are in the order the scans ended. A preview whose write fails leaves no part file.
Changed
- A classify job holds no pictures: its result and its progress no longer carry
previews. The worker that ran a sort queues its previews and then its pyramids on the pictures lane, under the sort's own principal, grants and detail, each once; a sort run at the keyboard queues its previews itself and says so (previews: queued as job N on the pictures lane), and--no-previewsqueues none. Jobs of kindpreviewrun on the pictures lane, at its priority and with its workers, never on the main lane.GET /api/datasets/{name}/summary's pictures step is the preview job's, queued or running with its progress, with the previews under the working place as its counts andin_sortfalse; the sorted step is done once the scans are judged. The OpenAPI 7 contract is amended in place, with no field added or changed in shape.
Added
- Install dcm2niix from a file where the engine cannot reach the releases (gate T31, walkthrough step 15.7; record 55, ruling 35 (c)).
POST /api/tools/dcm2niix/install-filetakes the archive of the pinned build as its body, underinstall:worklike the install, read only once the caller holds the grant and never past 4 MiB; it is checked against the pinned sha256 before anything is unpacked and installed exactly as the download is, audited astool.installwithgiven: true. An archive that is not the pinned build is refused (422) with the archive it takes named, and nothing is unpacked or written.nils tools install dcm2niix --from FILEis its twin at the machine. The tools' status names the archive to fetch (install.file), and an install refused because the releases could not be reached carries it asfile. The OpenAPI 7 contract is amended in place, additively. nils run <pipeline> --handle <id> --chunk Nruns a handle in parts of at most N stacks in stack order, each a run of its own over a handle of its own pinned as the handle is, as--select ... --chunk Ndoes. Taking a part's run up again (nils run --resume,nils jobs resume, or the pipeline lane after its engine went away) ends that part and goes on with the parts after it that no run holds yet.nils preview build --classified JOB, the previews of the stacks a classify job judged: the job a sort queues as it ends.nils preview buildandnils pyramid buildtake--at-once N(1 to 64), how many scans a build over many makes at once, and the jobs door queues it onpyramid build; the job's args sayat_once. The OpenAPI 7 contract is amended in place, additively. Tested by unit tests over twelve synthetic scans (several at once make the pictures one at a time makes, file for file; a failed scan is counted by its class; a stop keeps what was made and leaves no part file; one at a time keeps the order asked), a unit test of what a sort queues on which lane, and through both verbs, the pictures lane and the jobs door.
NILS v1.0.0-alpha.89
Fixed
- A dataset's pseudonymised step says done once its pseudonymise run has done the work, however large the dataset. After a run over a dataset of 30 million files ended with every file written or refused and none held, the step stayed
waitingwith no file pseudonymised, and the Data card offered to pseudonymise the whole dataset again. The step counted the originals and the copy from the dataset's probe, which is bounded (a page's worth of entries, two seconds a tree) and stops short of a large tree, and the probe the run made as it ended reached part of the originals and, on a share still busy with the run's writes, none of the copy. Once a whole run of the originals has ended, done or cancelled, the step now counts from the runs: the copy is what that run wrote or found unchanged, with what the runs of the held files after it wrote, and the originals are the most of what it saw and what the probe counts, so files that arrived since a run still wait. The probe counts only before any run. - The pseudonymised step's
waitingno longer counts a refused file or a held one. It counted every original not in the copy, so a dataset with one refused file was never done, and a held file was counted in bothheldandwaiting. A held file still keeps the step from done; a refused one is final. After a cancelled run the copy is at least the more of what that run decided and what the whole run done before it and the runs of the held files between them wrote or found unchanged, andpartialsays it is a lower bound until a whole run ends done;waiting_partialsays the originals' count is one, after a cancelled run or where the probe counted more originals than any run saw. Either way the step is not done. - The sources door's trees agree with the summary, so the Data page's Originals box and the copy's count say what the step says. An identified dataset's originals and copy are counted as its pseudonymised step counts them, the originals' bytes as the run measured them; a de-identified dataset's tree, where the probe stopped short of it, is counted by its newest finished read, as its found step counts it. The place's own
probedstays the probe's. - The found step of an identified dataset counts its newest whole pseudonymise run that ended done, never a run of its held files alone (a
--heldrun after it made the step count only the held files), and gives that run's bytes, the size of every original it saw. The OpenAPI 7 contract is amended in place, with no field added or changed in shape. Tested by the summary's unit tests (a_whole_run_counts_the_copy_where_the_probe_stopped_short_of_both_treesand six more) and through both doors, on both registry backends, bythe_copy_is_counted_from_the_runs_where_the_probe_stopped_short.
NILS v1.0.0-alpha.88
Fixed
- A job queued by hand or by a client of the jobs door reads packs from the deployment's pack directory when its own command line names none. A server started with
--pack-dirand no packs in the registry's home or the standard folders ran a job queued withnils jobs enqueue -- classify --pack mri(and any other verb that reads packs:digest --pack,pick run,score,explain) with no pack directory of its own, and the job failed at once with "no pack directory: pass --pack-dir, set NILS_PACK_DIR, or put packs in one of ...". Only the steps of a bring-in's chain, and the verbs the jobs door knew by name, were queued with the deployment's--pack-dirappended. The worker now hands the packs the server serves to every job it runs asNILS_PACK_DIR, in one place, wherever the job came from; a command line that names its own--pack-dirkeeps it.
Changed
- A dataset's arrival is scored by System 1 alone (record 55, ruling 35 (b) of 2026-10-16; gate test T26). The last step of a bring-in,
nils score @dataset --name <batch>, queues System 1's run over the batch's new stacks and no longer queues body part or post-contrast, though their pipelines declarex-nils.on-arrival: they are optional models a person runs from the dataset's step (POST /api/datasets/{name}/steps/{step}/run) when the data need them. The score's result names them underoptional.nils score ... --step body_part(orpost_contrast) still queues one over a batch's new stacks, with the parameters its pipeline declares for a run at arrival (body part's slice cache among them).
Added
- Each operation's step on a dataset's summary and a cohort's document says how many of its scans its model would add an answer to (
hint {kind, scans}, ruling 35 (b)): for body part the scans with no body part, neither the rules' nor its model's (no_body_part); for post-contrast the scans that can be T1-type brain by their stored axes, the test its cascade applies, whose contrast neither the rules nor its model told (t1_brain_contrast_unknown). A count only. GET /api/datasets/{name}/summarysays when the pseudonymised step's counts stopped short, as the found step does. Itsfilesandwaitingcome from the same bounded probe of the originals and the copy, so the step gainspartial(the copy's count stopped short and it holds at leastfiles) andwaiting_partial(waitingis at least that where only the originals' count stopped short, and not known where the copy's did too). The OpenAPI 7 contract is amended in place, additively. Tested bythe_pseudonymised_step_says_where_its_counts_stopped_short.- The setup steps the desk printed as commands are doors of the supervisor (record 55, ruling 35 (c), T31):
POST /api/supervise/signinchanges how people sign in (a password the desk keeps, with the first person where it keeps nobody yet, or a provider the desk is registered at already),POST /api/supervise/backupsgives the engine another folder for its archives,POST /api/supervise/modelnames the model server the assistant's stations use, andPOST /api/supervise/restoreputs an archive of the engine's backup folder back: the engine stopped,nils restorerun as the engine's account, which writes the pre-restore archive first as before, and the engine started again. Each is checked, left in a request file of the supervisor's own folder, and made by a run apart from the call (nils supervise change,nils supervise restore), asnils setupmakes it: the record written, then the install repaired from it. Where the services are the machine's own, the run asks the helper with one word and no argument (change,restore), so the sudoers rule still names whole command lines, and root reads the request, checks it again and takes it away; a helper written before these words is not asked, and the door says to write it again. Turning sign-in off and registering the desk at an Authentik stay at the machine.GET /api/supervise/installsays which changes the supervisor makes there (changes). - Two audit actions:
install.change, a change of the setup made through the supervisor, naming what changed and never a secret, andrestore, an archive put back, written into the registry as restored so it is there afterwards and moving the epoch. Both are under the person the desk names to the supervisor inX-Nils-Person;nils restore --bynames one at the keyboard. - The release door takes the release's expert options (gate T31, walkthrough step 13.8), each as the flag of
nils releaseit stands for and checked with that flag's own parser before a job is queued:localizers,syntheticanddicom(where a BIDS tree puts localizers, a vendor's synthetic contrasts and the DICOM copies),converter(dcm2niix, the engine's own, for anyone; a path only from a caller holdinginstall:work, and only to a regular file named dcm2niix, not a link, owned by root or the engine's account and writable by nobody else, since the job runs it),compress(false for.nii),authors,categories(at least one),uid_root,pickedandroles.GET /api/capabilitiesnames them inreleases.expert, so a desk offers only what the engine reads. - The runtime pipelines run under and the starter catalog at doors (gate T31, step 15.8):
GETandPUT /api/pipelines/runtimeread and choose the runtime and how apptainer keeps an image, asnils pipeline runtime --setand--apptainer-imagedo;GETandPUT /api/pipelines/startersread the starters and turn their seeding at the engine's start on or off, andPOST /api/pipelines/starters/seedseeds what the catalog lacks now, asnils pipeline starterdoes. Reading ispipelines:see, changinginstall:work, and every change is audited aspipeline.runtimeunder the caller.GET /api/pipeline-runs/{id}/seedsanswers a run's seeds asnils pipeline seedsreads them, counted by axis and value, with the stacks it suggests curating, which a client saves as a selection atPUT /api/ask/selections/{name}; a seed or a stack of a sample sealed now is left out for a caller who does not read sealed samples, andwithheldsays how many. - The clinical vocabulary and the clinical importer at doors (gate T31, step 22.8; record 55, ruling 35 (c)):
GET /api/clinical/vocabularylists the diseases and the kinds of observation (data:see);POST /api/clinical/vocabularyloads a vocabulary's text, or the pack's ownclinical/vocabulary.ymlwhen none is sent, asnils clinical vocabulary loaddoes;POST /api/clinical/import {mapping, csv, apply}previews an import or applies it under the caller in one transaction, asnils clinical importdoes (data:work). A mapping whose rows name their subjects by an ID looks each up in the linkage store and needs detail sensitive; a preview's subject codes are shown from detail quasi up. Every load and every apply is audited under the caller (vocabulary.load,clinical.import). - The supervisor uninstalls NILS for the desk (gate T31, step 1.9).
GET /api/supervise/uninstallsays what an uninstall would remove and keep, both ways, in the words ofnils uninstall --print, with the install directory's name;POST /api/supervise/uninstall {leave: nils | everything, confirm}runsnils uninstall --keep-dataor--purgewithout asking, apart from the call, onceconfirmis that name. Where the services are the machine's own, it goes through the helper, which gains the wordsuninstall keep-dataanduninstall purgeand its rule the two command lines; a helper written before refuses them untilnils setupwrites it again. Who asked, as the desk names the person it forwards for (X-Nils-Person), is written toacts.logbeside the supervisor's configuration before the run starts, and the run keeps it asby. - A campaign's results at one door, for its maker and an administrator (record 55, ruling 35 (d)).
GET /api/campaigns/{id}/results, under campaigns:see, answers per item the answers standing now by rater, the adjudications, the settled value and the decisions or picks the close wrote, with the campaign's agreement (exact, Fleiss and Cohen kappa, per axis), the unsure count and the counts by value (answers given and items settled, per axis on an axes question). While the campaign is open it answers the maker, unless the maker rates in it, an adjudicator of it and a holder of review:work, and refuses any other rater with 403, as the answers door is blind. The raters are named to the maker and a holder of review:work alone; anyone else reads each other person asrater Noradjudicator N, and their own answers by their own name. The desk's campaign page reads it for its Results view. - The key store at the door (record 55, ruling 35 (c): every step that can live in the desk gets a place in it; gate T31, steps 8.6, 8.7 and 13.4).
GET /api/keyslists the keys by name with their length, fingerprint and which one the pseudonyms come from, underdatabase:see, never a byte of one.POST /api/keys {name, passphrase}makes a key from its passphrase asnils key addmakes it from its input (one final newline dropped, at most 64 bytes), andPOST /api/keys/{name}/remove {confirm}takes one away with its name typed again; both underdatabase:workandinstall:workat detail sensitive, which no ladder step below admin holds. A key is never replaced, and the registry's pseudonym key, made again after a restore, is first tried on what the linkage store keeps: a passphrase that does not open it is refused with nothing kept. A passphrase is never written to the registry, a job, the audit or a log, and no refusal quotes one; an answer that carries one is sentCache-Control: no-store. Each act is audited by the key's name alone (key.add,key.remove), and a call that acts as an agent or a model is refused whatever its grants. - Each handover has a password of its own, and encryption is a choice (record 55, ruling 35 (c),...
NILS v1.0.0-alpha.87
Wave 7a's last engine: hospital reserve numbers read beside the personnummer and never kept, spare numbers through a map, model cards that gain their missing claims, the damaged-header flag's three signs (pack contract 11, MRI pack 1.3.0), the post-contrast label's clock across studies (time-rule-2), kept Query cards, LST-AI's thin image, and fixes from the wave's audit.
Added
- The install door says when it looked at the releases, and looks again when asked.
GET /api/supervise/installgainsrelease.looked_at(UTC, to the second), and?fresh=1makes the supervisor look at the releases again where the look it kept is at leastlook_again_secondsold (60 by default: GitHub keeps its own answer for a minute, and an address has sixty requests an hour that every look spends). Where the look is younger, the answer carries the look it has. The desk's Parts page says how old the look is beside its table and asks for a new one with Look again. - A person keeps a card, and an assistant's draft is no card until they do (record 55, K6; registry schema 103).
PUT /api/ask/documents/{id}/keptkeeps the card a document is a version of andDELETEputs it back among the drafts, underquery:work. A card is a line of versions, and the mark (kept_by,kept_atonask_document) sits on the line's first document, so keeping any version keeps the line; a card already kept stays kept by whoever kept it first, when they did. It is a person's act: a caller that acts as an agent or a model is refused with 403, since the assistant proposes and a person keeps. Putting a card back deletes nothing, and the mark writes no audit row and moves no epoch. Migration 103 adds the two columns and holds every line the registry had as kept by whoever made it, when it was made, so no card leaves a list at the upgrade. Tested on both backends (migration_103_*,a_draft_is_a_card_only_when_a_person_keeps_it,a_draft_is_listed_as_a_card_only_when_a_person_keeps_it). - A hospital's reserve number, the number a hospital gives a person who has no personnummer or whose personnummer it does not know, is a built-in ID type,
reserve-number(registry schema 104), identifying and never kept: like a personnummer, its identity is its keyed lookup alone, with nothing sealed in the linkage store or on a held file's row, and the linkage store applies that to every row of either type it files, whatever a caller sealed (linkage::keeps). A map takes it asidentifier:reserve-number, beside the person's code or another identifier of theirs, in one written form (spaces and one separator dropped, no date or check digit asked of it); acanonical:reserve-numbercolumn is refused, since no code is ever made from a reserve number, and no dataset writes one into PatientID. Migration 104 adds the type to a linkage store from before, and drops whatever a type of that name a person had made sealed.nils custodyand the custody page say that a reserve number is never kept. nils custodyand the custody page (docs/reference/custody.md) list the stores Wave 7a added (record 55, T36): where each scan's files are and the hashes of their bytes, the pseudonymise records, System 1's opinions, the review rounds and the audit's draws, the blind-first reads, the post-contrast labels, the main scans and their rules, the rule proposals, the slice cache, the pictures (previews and pyramids) and the places export. The linkage store's entry says a personnummer is never kept, not even sealed, and the quarantine list's that a file the read set aside by policy is a row of it, with no review item.- A registered model's card gains the claims it lacks (record 55, ruling 31 (a)): the label set it was trained on, its tested claims and its speed.
nils model add-claims MODEL --claims FILEandPOST /api/models/{id}/claims(under models:work, in the caller's name) add them; the file is the claims in the card's own shape (trained_on,tested, andspeedwithcpu,gpuor both), or holds them asclaimsbeside themodelthey are for, itsdigestandwhy. Additions only: a claim the card states already is refused and named, and so is a speed with a GPU on a card whosegpuisnone, and any other field, which stays a new version of the model. Each claim is checked as registration checks it: the label set by its digest, registered in this registry and clear of every sample sealed now (records 40 R3 and 55 E6), the tested claims and the speed as the card's contract has them. The model'strained_onfollows its card. Each addition is aclaims_addedevent, holding the fields filled, the claims as added, what the card lacked before and after and why, and an audit row (model.add_claims), both written in the transaction that writes the card.nils model showsays whether a card is complete or what it lacks, and lists what it gained since it was registered;GET /api/models/{id}andnils model show --jsonanswer it asadditions. The model contract v1 and the OpenAPI 7 contract are amended in place, additively; no migration. - The damaged-header flag reads all three of its signs and is set at ingest (record 56 section 3; record 55, ruling 31 (c)). At the end of every sort, each dataset holding a scan the sort judged has its signs read with the sort's own pack and kept beside its flag: its MR scans with neither a series description nor a protocol name, its named MR scans none of whose words the pack knows (folded as the pack folds a series' names for its rules), and its MR scans by a maker the rules were not built on that no dataset brought in before it holds, or by none. Each share is held to the pack's threshold, and a dataset is flagged when one reaches it, so System 1's gate runs over it and stages its answers there for a person to confirm. The sort's result says what it read (
headers), and so does its log.GET /api/datasets/{name}/headersanswers each sign with its count, its share, its threshold and whether it flags, the makers it counted, the pack that read them and when, and in words why the dataset is flagged or not. A person's word still wins: a flag set or cleared with a why, from the Data card or withPUT, stands until the person lets the signs decide again, and the signs are kept under it. A dataset no sort has read since the signs were kept is read when asked and kept at its next sort; a root, each folder under it a dataset of its own, is flagged only by a person. - Pack contract 11: one key under
review,damaged_headers, the thresholds of the three signs, the makers the rules were built on and the derived text a series' names are folded by. Refused in a pack that declares less. A pack before contract 11 flags a dataset by the nameless share at a half, as before, and reads the other two signs without flagging on them.
The MRI pack 1.3.0, the damaged-header flag's thresholds (record 56 section 3; record 55, ruling 31 (c)). It declares pack contract 11, so it needs an engine that implements it, the one these notes' Unreleased section describes: an earlier engine refuses it by its contract, and its update says so and waits.nils update --pin mri@1.2.3goes back to the version before.
Changed
- The pseudonymiser reads a PatientID that is no personnummer, in a dataset whose rule reads PatientID as an ID that is the same everywhere, as a reserve number (above), and looks it up under
reserve-number: a file whose number a map names is that person's and is written like any other, so one person's main number and spare reserve number land on one subject. A file whose number no map names is held under the number's keyed lookup and shown by its shape, no longer under its study's UID, so a map row naming the number releases it and the next run writes it. No subject is ever made from a reserve number, whatever the dataset says of an identifier no map names, and code anyway (POST /api/linkage/held/code) leaves such a file held. A file whose study a map named is still found by the study's UID, and a file an earlier run held under its study's UID is released by a map of that UID as before. A valid personnummer is read exactly as before, and so is every rule a digest reads. Tested on both backends (reserve_ids.rs). - The post-contrast label's check for an earlier injection (C1) reads a second study of the same visit by the clock, as the rule's dated version
time-rule-2(record 55, ruling 25 of 2026-10-15). Two studies of a subject that started less than 24 hours apart are one visit; such a study with contrast counts for a stack only when the stack's last image was acquired at or after the first image of that study's first post-contrast T1 or first bolus series, so a scan taken before it stayspre(flaggedC1_clock_before), and a study of the visit that started later but was injected before the stack now counts for it. Where the stack's time or the other study's is missing, or either clock fails the trust guard, the study counts by its start time as before (C1_no_clock); a study with contrast 24 to 72 hours earlier counts for every stack, as before. The 72-hour flag is the stack's own. A label made before keepstime-rule-1as its rule;nils contrast labellabels again under the new one. GET /api/ask/documentslists the kept cards by default;kept=nolists the drafts nobody kept andkept=allboth, and a client that lists the documents and wants the assistant's drafts as well asks withkept=all. Each row says who kept the line and when (kept, null for a draft), and the answer counts thecardsand thedrafts, whatever the page and the filter.GET /api/ask/documents/{id}sayskeptfor the card the document is a version of. Storing a document the request carries atPOST /api/ask/documentsas a person keeps its card, since that is what saving a card is; as an agent or a model, or by naming one already stored (document_id, as keeping a draft under a parent does), it keeps nothing. The retention of un...
NILS v1.0.0-alpha.86
Post-contrast's fallback keeps its card arms inside its image, and a release rebuilds only the model images that changed.
Changed
- A release builds a model image (body part, post-contrast, System 1) only when its package's files, the release workflow or the descriptors script changed since the previous release's tag. An unchanged image is the one the previous release shipped, by the digest its
IMAGESnames, once the registry says anyone may pull it; the descriptors andIMAGESkeep their form. - A release, and a rules release, may take its commit's checks from the pull request it is the squash merge of, where the merge's tree is byte for byte the pull request head's and main's tip before the merge is in that head, so the pull request's ci ran on exactly this tree (
scripts/release-pr-head.sh). The release then takes the six binaries from that pull request's ci run when main's run has not kept them.
Fixed
- Post-contrast's fallback (
postcontrast-full, nils-postcontrast 0.2.2) keeps arms U and V on a card in its image. With the Frangi filter on the card, CuPy writes every kernel it compiles to a cache under the home, and in a container whose home is missing on a read-only root (apptainer's--no-home, the image's unprivileged user) every stack that reached the filter lost both arms to anOSError: 19 of 93 stacks in the image's card test. The cache now goes in the run's work folder unlessCUPY_CACHE_DIRnames one. The CPU path and the answers elsewhere are unchanged. The descriptor pins the image built from 0.2.2.
Rules mri 1.3.0
The MRI pack 1.3.0, the damaged-header flag's thresholds (record 56 section 3; record 55, ruling 31 (c)). It declares pack contract 11, so it needs an engine that implements it, the one these notes' Unreleased section describes: an earlier engine refuses it by its contract, and its update says so and waits. nils update --pin mri@1.2.3 goes back to the version before.
Changed
- The pack states the thresholds at which a dataset's signs flag its headers as damaged (
review.damaged_headers): 10 % of its MR scans nameless (the engine's half before), 2 % of its named MR scans with no word the pack knows, and 10 % of its MR scans by a maker the rules were not built on (Siemens, GE, Philips) that no dataset brought in before it holds. A series' words are folded astechnique_textfolds them. The numbers were set from measurements over a production archive of 550,680 MR stacks in seven datasets and over the damage study's damaged copies (the pack's PROVENANCE). - No rule changed, so no stack is sorted differently: the flag decides only where System 1's gate runs.
Rules mri 1.0.1
The MRI pack 1.0.1 on a tag of its own so that an install can be pinned to it. nils update --pin mri@1.0.1 puts these rules in place, and nils update --unpin mri lifts the pin.
It is the pack that engine 1.0.0-alpha.79 shipped, file for file: 83 files, the same as packs/mri in that release's packs.tar.gz. Rules releases began with 1.2.0, and 1.0.0 and 1.0.1 shipped only inside engine releases, so until now no pin could name them. Nothing in the pack changed; this release changes nothing about how it decides.
Certificate 2's five rule gaps and three questions of convention, which deep research and a production archive's counts settled, on top of the certified pack 1.0.0. It is not certified: 1.0.0 is, and 1.0.1 claims that certificate only after a small fresh read. Replayed over the 546,440 unsealed MR stacks of the archive, 28 stacks move against 1.0.0; over every held development reference, 10 settled cells move, all to the reference's value, and none is broken.
-
A spin-echo inversion recovery above 2,600 ms whose inversion time nulls CSF at its long repetition time is a FLAIR (a
t2_tirmat TR 10,140 ms read STIR by itstirm). -
GE's private pulse sequence name states an inversion (
T1FLAIR/Prop), so a PROPELLER written ScanningSequence SE at a STIR's inversion time is a STIR. -
A Philips diffusion stack with direction
Iand no direction count that holds more images a slice than b values is a diffusion set, no Trace, as one with a direction count already was. -
A Siemens susceptibility-map-weighted image (SMWI) is the SWI output of its QSM protocol, not an acquired echo.
-
GE's
Reg -copy of a perfusion run written DERIVED is motion corrected, as Philips' is. -
A real (phase-sensitive) inversion recovery in the STIR window whose name states no weighting is T2-weighted at an echo time of 40 ms or more (PD-weighted where named PD), beside its PSIR and Real; at a shorter echo time its base is left for a person.
-
A series named for a pituitary or sella dynamic of fewer than 10 time points, or frames more than 10 s apart, is a T1-weighted run, never DCE or PWI, whatever its name says; and DCE now also needs frames no more than 10 s apart where the fingerprint knows the interval.
-
A declared local convention: GE's motion-corrected diffusion outputs name each output in ReceiveCoilName (
isoDWI/b1000/R3,T2w(b=0)/R3,isoADC/...,FA/...,ExpAtt/...), and within that closed set the label outranks the series name and the b value:isoDWIis Trace, any other label is no Trace,isoADC,FAandExpAttare the ADC, FA and exponential ADC. It rests on what the labels mean, not on vendor documentation, and is to be revisited if the tool that writes them is documented. -
It is pack contract 8 and names no engine range, because ranges began with pack contract 10. An engine that reads rules releases, 1.0.0-alpha.80 and later, takes it by its contract alone.
-
No install takes it on its own. It is older than the pack an engine carries, so
nils update,nils update --part rulesandnils update --allleave the rules where they are. Only the pin puts it in place, and the pin keeps it through every update until it is lifted. The pack it replaces is kept inpacks.previous, one update deep.
Rules mri 1.0.0
The MRI pack 1.0.0, the certified pack, on a tag of its own so that an install can be pinned to it. nils update --pin mri@1.0.0 puts these rules in place to reproduce what the paper reports, and nils update --unpin mri lifts the pin.
It is the pack that engine 1.0.0-alpha.78 shipped, file for file: 82 files, the same as packs/mri in that release's packs.tar.gz. Rules releases began with 1.2.0, and 1.0.0 and 1.0.1 shipped only inside engine releases, so until now no pin could name them. Nothing in the pack changed; this release changes nothing about how it decides.
These are MRI pack 0.25.0's rules, unchanged: only the version moved. Pack 0.25.0 passed the final header certificate of 2026-10-04, a fresh, sealed read of 5,000 stacks drawn from a production archive, graded once on the answers the pack had stored, with 7 of the 5,000 wrong (the one stack the read left out counted wrong) and a Korn-Graubard upper bound of 0.26 % against a bar of 1 %. Replayed over the 546,440 unsealed MR stacks of that archive, 1.0.0 gives the same answer as 0.25.0 on every axis of every stack.
- It is pack contract 8 and names no engine range, because ranges began with pack contract 10. An engine that reads rules releases, 1.0.0-alpha.80 and later, takes it by its contract alone.
- No install takes it on its own. It is older than the pack an engine carries, so
nils update,nils update --part rulesandnils update --allleave the rules where they are. Only the pin puts it in place, and the pin keeps it through every update until it is lifted. The pack it replaces is kept inpacks.previous, one update deep.
NILS v1.0.0-alpha.85
Post-contrast is sent only the scans that can be T1-type brain.
Changed
nils score --step post_contrast, and the scoring at the end of a bring-in, queue post-contrast's cascade only over the new stacks that can be T1-type brain by their stored axes (the base is T1w and the technique is not TOF or PC, and the body part says brain, the body region says head, or the body part is not answered yet), the test the cascade itself applies before it scores. A stack left out is not sent and gets no answer; the score's result says how many were left out (left_out, "not T1-type brain by the stored axes"), and where none qualifies nothing is queued and the step is skipped with that reason.
NILS v1.0.0-alpha.84
Ruling 27: a model stages on its admission; certificates serve the paper.
Changed
- A System 1 model that is admitted and chosen for its task stages its answers without a recorded certificate (record 55, ruling 27). Its admission, and for the rules their replay, is what stands for it in production. On a dataset flagged as damaged, the gate runs and System 1's answer is staged where the rules name nothing and it is confident at its threshold, for a person to commit, as before; the gate no longer waits for a certificate, and Review no longer shows the chosen model's opinion as not confirmed. A model neither admitted nor chosen stages nothing. Certificates stay: recording one works as before, and the model's page, the review settings and the suggestions show one where it exists (the questions door now names it too as
system1.certificate_id). Seals, the person-only unseal and a person over an agent over a model are unchanged.