# Changelog Semantic versions with the date in the heading: `v0.2.0 — 20.08.2026`. The number is the unique key of an entry, the date is informative: a working day may produce several versions or none. Rules for choosing a number — `docs/VERSIONING.md`. **The numbering starts here.** This journal opens at the first public release. The development that produced it ran under its own numbering, up to `2.24.0`, and that numbering was retired at publication: it measured how much had been built, and a version number is meant to promise something else — that a person holding the previous version knows what moving to this one does to their data and their commands. Nobody outside had run any of those versions, so there was nothing to promise. They are kept privately, in `CHANGELOG.pre-0.1.0.md`, and their tags live in a namespace of their own (`pre-0.1.0/v2.24.0`), so that if the published numbering ever reaches `2.24.0` it is a different tag on a different commit and cannot be mistaken for one of them. **How to read it.** The main sections of an entry are not the file list but three curated ones: **what this changes in the conclusions**, **what is retracted**, **what needs recomputing**. The file list at the bottom is generated from git (`python3 src/tools/release_notes.py`). A **series break** is marked separately — a change to `src/scholion/knowledge/` that alters the result on unchanged input. Values from before and after such a change cannot go on the same chart without a note. **Who it is for.** Somebody who has never seen this repository and wants to know what this version does. That decides what an entry contains: **new capabilities, fixed defects, and what either of those means for data already stored.** It also decides what an entry does NOT contain — the path a change took, which attempt came first, what was learned along the way, whose machine anything ran on, and who wrote it. Those are worth recording and belong elsewhere; in a release entry they crowd out the two questions a reader actually has: what can I do now that I could not, and what was wrong that is now right. **What is not here.** Personal data and one person's findings: no genotypes, no lab values, no dates of anyone's tests. This journal records what changed in the **system**, not what was found in somebody. --- ## v0.7.7 — 04.11.2026 The new Mitochondria body system links saved mtDNA readings to dated laboratory observations, annual changes, related current prescriptions and recorded responses. It shares the same answer across the web, CLI and assistant interfaces. No overall mitochondrial health score or biological-age estimate is inferred. Opening the system reuses the saved BAM analysis; no genome recalculation is required. - Gene cards open with the role of the gene and then explain what the available reading can support. Reading depth and repeat information stay together. Genome lookup also returns the same structured result to compatible MCP clients. Existing saved readings are used directly. - Where the evidence supports a genotype comparison, cards explain the possible personal meaning, the comparison group and the proposed mechanism before the limitations. Relevant laboratory observations remain distinct from a measured genetic effect. Weak or conflicting readings keep the study explanation without assigning its effect to the person. These explanations update from saved readings without a new BAM or PGS calculation. - A failed coordinate lookup now distinguishes an unavailable reference, an empty result and an ambiguous placement. A local-only query stays local when it opens a gene's details. ### What this changes in the conclusions All 71 included polygenic models show the populations reported for variant discovery, training and evaluation, with links to the PGS Catalog records. The summary distinguishes group-specific results, pooled evaluations, modified weights and the reported independence of the evaluation sample. Missing evidence remains explicit. The population comparison is separate from the reference panel used to calculate a percentile and cannot establish personal applicability. Open Genome → PGS in `scholion serve` or run `scholion prs`. Genome → Mitochondria can analyze an indexed BAM locally. It reports the estimated maternal haplogroup, mitochondrial variants with observed read fractions and strand support, and coverage across the mitochondrial genome. When an indexed ClinVar snapshot is available locally, it shows the annotation for the exact allele alongside the reading. The analysis is opt-in and is also available through the command line, MCP and plugins. Progress shows the current stage, and cancellation preserves the previous report. If only the ClinVar snapshot changes, annotations can be refreshed from saved variants. Genome locus search shows progress and places results or errors beneath the search field. Gene names and rsIDs work in either case. Search and gene panels group positions by gene and retain the reading, evidence level and sources for each position. Thematic panels show curated calls first and keep other VCF records in region details. New sourced positions cover circadian, neurological, metabolic and other thematic genes. The APOE card shows each available position reading and identifies unresolved phase. Thematic gene panels now separate 178 exact-allele ClinVar reference records from four additional research associations. Disease classifications retain their source condition or condition group, review status and versioned record. Existing region readings remain available. A different alternative allele at the same rsID is not treated as the selected allele; unsupported deletion readings remain unresolved rather than inferred. Gene descriptions show their sources and the recorded review date. Reference functions and literature associations remain distinct from an established personal variant effect. Source-review reminders do not trigger a network request or genome recalculation automatically. ### What is retracted None. ### What needs recomputing **Run `scholion genotype-sites` — when newly added catalogue positions are missing and an indexed BAM with its reference genome is available.** Previously stored PGS scores do not need recalculation for these catalogue and presentation changes. Existing exact VCF calls are used directly. To fill newly added supported positions that are absent from a variant-only VCF, run the catalogue-position update from an indexed BAM with its reference genome. The update now includes supported thematic-panel positions and preserves the prior file if calling fails or is cancelled. It does not recompute PGS. Once the saved site catalogue matches, explanation-only updates do not request another BAM run. Unsupported complex variants remain explicitly unresolved. The mitochondrial analysis requires a separate, explicitly requested first BAM run. Opening its results reuses the saved report. ## v0.7.6 — 03.11.2026 ### What this changes in the conclusions Gene and prescription views use the same underlying reading and keep its source and quality visible. Matching allele letters at different known positions no longer confirm a reading. ClinVar and ACMG findings retain the source record and show the limits that apply to the reading. System summaries and the visit sheet show which measurements and dates support a conclusion. Comparisons keep distinct units and draws separate. A saved review can be compared with the current assessment to see whether the result changed after an update to the data, rules or evaluation date. The original review remains available. The assistant also reports where profile data came from. Run `scholion serve` to open the local application. ### What is retracted Automatic interpretations based on incompatible genetic readings are withheld. The recorded readings remain visible. ### What needs recomputing Nothing. Existing measurements, genome files and PGS results remain compatible. You can open a current review without regenerating genome data or scores. ## v0.7.5 — 02.11.2026 ### What this changes in the conclusions Measurement cards and annual summaries retain their existing values, dates, source labels, trend qualifications and appearance across the application. Gene cards, system insights, visit reports and prescription views retain their existing content, navigation and expandable details. Closing a browser tab or cancelling a request no longer stops the local server. Screen changes release chart and polling resources, and delayed replies cannot restore stale screen caches. A form from a previous profile is refused before it can write to the newly selected profile. Genotype views now retain conflicting saved and current readings consistently. A confirmed reference call no longer loses to an older summary, and missing VCF rows do not erase stored findings. Conflicting readings remain visible but no longer support an automatic allele interpretation. Known alternative alleles at two curated positions can be shown without assigning them another allele's effect. Coverage messages distinguish single-copy and diploid intervals and no longer print missing percentages or duplicate lists. Low interval coverage is explained as a data limitation, not an automatic recommendation for more sequencing. System pages place the genetic panel summary after the main results. Gene cards combine read depth and any low-depth warning in one line, with the full note in the reading details. Within each ClinVar category, better-reviewed records lead and supported homozygous alternate calls come first among records of comparable review confidence. Selected previously unnamed entries now show a sourced gene or locus label and the studied trait. Where a gene cannot be established, the card says so and explains what the cited study assessed. Continue using `scholion serve` for the local application. ### What is retracted Automatic allele interpretations based on conflicting source readings are withheld until the discrepancy is resolved; the original observations are retained. ### What needs recomputing Nothing. Existing profiles, genome results and measurement histories remain compatible; no genome preparation or PGS recalculation is required. ## v0.7.4 — 27.10.2026 ### What this changes in the conclusions Gene cards retain the same information, reading states, scientific qualifications and appearance across Genome, Radar, screening, gene panels and visit reports. Their presentation is maintained together so future improvements can remain consistent across these views. Navigation, expandable annotations, inheritance details, laboratory comparisons and printing keep their existing behavior. MCP tools also work when Ouroboros is installed alongside Scholion, including clients using older MCP protocols. ClinVar annotation selects the reference for the genome's assembly and preserves previous results if a refresh fails. Unsupported or ambiguous HTTP request framing is rejected before changing data. ### What is retracted A negative ACMG screen cannot be established from a multi-allelic reference whose classification cannot be assigned to a particular allele. Explicit per-allele classifications are matched to the allele actually carried. Saved ClinVar tables with conflicting assemblies or altered recorded contents are preserved but no longer used as current findings. Small ACMG source files receive a content check even when their filesystem timestamps coincide. ### What needs recomputing Nothing is required for unchanged valid data. No general genome reimport or PGS recalculation is required. Unchanged valid reports and normal workflows retain their behavior. If a saved ClinVar result was produced against a different assembly, refresh that annotation using the matching reference. An ACMG screen refused for ambiguous reference alleles needs an allele-specific reference; the previous result stays on disk. Verification of a large genome's full contents remains an explicit operation, separate from the quick file-identity check used when opening a saved report. Use `scholion acmg-scan --verify-only` when that full verification is needed. ## v0.7.3 — 27.10.2026 ### What this changes in the conclusions Gene and variant cards now use the same layout across genome findings, disease screening, gene panels, body-system views, laboratory and medication context, and visit sheets. Each card separates interpretation confidence, gene/position, personal reading with zygosity, and biological function and personal meaning. Open the local interface with `scholion serve`. Personal inheritance status remains distinct from a database classification. Database annotations and inheritance details open separately. Personal gene topics now read existing records from each gene interval when an indexed genome and compatible gene coordinates are available, including genes without a selected catalogue SNP. They retain each recorded allele and distinguish a failed query from an empty result. Opening a topic does not call new variants or remeasure BAM coverage. Cached results are bound to the selected profile, input files, index, annotation and sample. Packaged public GRCh38 intervals provide an offline coordinate fallback. They do not invent coding exons or protein effects when transcript annotation is absent. Scientific context separates gene function, gene–disease validity, a variant association and individual applicability. References with verified missing bibliography have been repaired. Incomplete project curation, conflicting studies, an unverified individual estimate and absence of a source are separate states; an unfilled field is not described as absence of scientific research. Versioned database and guideline sources can support their own assertion types without a publication identifier; they do not inherit permission for personal clinical conclusions. ### What is retracted Several positive or individual-effect descriptions overstated the cited evidence: null pooled results, population-specific findings and genotype models were previously presented too broadly. These descriptions and unsupported numerical predictions have been corrected. Interpretation confidence is lower where the sources conflict or only establish a gene–disease relationship. Database classifications and actual recorded genotypes remain separate and unchanged. Adding a reference never automatically enables a clinical recommendation. ### What needs recomputing Nothing needs new genome calling, BAM preparation or polygenic scoring. Existing readings remain valid. Reopen the affected panels to use the corrected reference interpretations and gene-interval display. No personal data need to be uploaded or reimported. ## v0.7.2 — 26.10.2026 **Carrier status is distinguished from disease.** Genome findings now show zygosity, sourced inheritance and what the recorded call can establish. A single heterozygous finding under a supported recessive model is presented as carrier context, separately from the variant's ClinVar classification. Multiple variants without established phase, mixed inheritance and incomplete readings remain explicitly unresolved. The same context reaches the web, command line and assistant tools. Existing saved findings need no genome recalculation; their classification and secondary-finding reporting rules remain unchanged. **Visit handouts connect genetics to the actual discussion.** Current deviations are grouped by System with their measurements and available annual history. A genetic factor is included for an explicit link to a discussed measurement or a recorded prescription, with its inclusion reason and evidence limits. System membership or alphabetical order alone no longer selects a factor. Counts cover the complete relevant set before preview limits and distinguish unique factors from their appearances in several Systems. Existing genomic readings need no recalculation for this presentation change. **The Today radar is visible immediately.** The system chart appears alongside the existing overview without an extra disclosure click. **Attention cards load faster.** A page shares repeated reference lookups and reads indexed catalogue positions in bounded batches. Startup PDF integrity checks run separately from the web interpreter. Measurements, genetic reading checks and card contents are retained; this change requires no recalculation. **Clinical estimates recognise available inputs more accurately.** Laboratory values with an import-source witness and registered unit spellings are accepted by the same conversion rules as other measurements. FIB-4 and PhenoAge use measurements from one draw; missing inputs are named without borrowing values from another draw. **Verified reference readings remain visible.** Some newly added SNPs could be shown as unread even when the aligned-read evidence confirmed the reference alleles. The reading now retains the evidence file's assembly and coordinate. An existing valid sites file needs no recalculation solely for this correction. **Prescriptions have broader, qualified System links.** Named nutrients and substances can relate to several Systems, with physiological context, research, possible influence and monitoring distinguished. A link is not a demonstrated treatment response. Food, gastrointestinal context outside the System map and formulation-dependent records receive an explanation instead of an arbitrary System assignment. The same relationships are available in the web, CLI and assistant tools; saved prescriptions and doses are not rewritten. **Compound laboratory documents retain separate collection dates.** Dated sections inside a discharge summary can be imported without assigning every result to the first date. Import, completeness checks and reverse provenance use the same section boundaries. Invalid section dates are reported; different draws remain separate. Reviewed archive previews retain their source and specimen checks. **FHIR measurements retain their identity.** Additional verified LOINC mappings preserve specimen, reported assay and source identifiers. Blood-specific concentrations stay separate from serum/plasma; a reported MDRD estimate does not become CKD-EPI. Vital signs and blood-pressure components enter the metrics layer. An issue date without a collection date remains approximate, and conflicting codes or invalid quantities receive a named refusal. FHIR bundles can also be imported through the shared folder-import path used by the web and assistant. Serum calcium imported from FHIR could previously be assigned to the separate elemental whole-blood series. It now enters the serum total-calcium series; a method-unspecified blood result stays separate from both. **Reference gene panels keep biological context separate from personal findings.** Pathway panels cover 525 genes and one separately labelled gene region within the existing body Systems. Methylation is shared across the relevant Systems; research associations in neurotransmission and stress are separate from clinical neurogenetics. Gene functions, actual readings, variant context and available measurements use one core across the application, CLI and assistant tools. Scholion confidence, GenCC gene–disease validity and ClinVar classifications remain separate. The supplementary GenCC snapshot retains all submitters and validity classes, including disagreement; the absence of a submission is not a negative genetic result. The main GenCC catalogue also retains all 1469 genes, with refreshed identity and function-source witnesses, bilingual descriptions, and current validity assertions for the existing disease concepts. Gene-level biology does not establish the effect of a personal variant. Gene names with mixed letter case retain their functions, translated disease labels and source identity when searched in upper case. Text reports also retain known functions and GenCC assertions when personal genotype reading is unavailable. Every thematic gene card also opens the existing gene-region reader, including genes without selected catalogue positions. It shows observed variants, the reference transcript and measured coverage where available. Coding changes are kept separate from other regional variants and from clinical classifications. Opening a panel does not start a region scan or genome preparation; the CLI and assistant expose the same explicitly requested reading. **Gene-region coverage could incorrectly appear low or absent.** Some larger alignment regions were read only in part, while other indexed intervals could include extra reads. Coverage now respects the complete indexed block boundaries, and a truncated alignment record produces an explicit failure instead of an ordinary coverage result. Reference transcripts, observed sequence changes and the limits of an absence claim remain visible together. **Reference coordinates are not an effect allele.** Newly verified SNP positions can be read in either supported genome build without borrowing a risk allele or inferring reference homozygosity from a missing VCF row. Verified insertion, deletion and repeat identities remain reference entries where specialized allele reading is required. Known readings and source documents are retained. **Gene descriptions lead finding cards.** Known gene biology and the recorded reason for including a variant appear before inheritance and personal-meaning qualifications. Additional reference descriptions distinguish protein genes, noncoding transcripts and regulatory DNA regions. Stored readings and variant classifications are retained; these descriptive changes do not require a genome recalculation. **Clinical estimates share one panel.** The existing FIB-4 index and PhenoAge appear in a compact Today block and a detailed panel. Each retains its own outcome, scale, dated inputs, source and applicability. Completed estimates also appear in the visit sheet; missing inputs are not borrowed from another draw. **Lumiflex application PDFs can be previewed and imported.** Printed summary and daily statistics, recorded meal/activity events and approximate readings of visible curves stay distinct. Post-meal comparisons require a complete visible window and retain overlapping events. Sleep alignment uses explicit report timezones and sourced Garmin, WHOOP or recorded sleep intervals. Approximate sleep-period means, ranges and variation remain separate from device sleep summaries; the last recorded meal is not assumed to be the last actual meal. Within-device night comparisons retain their included and excluded records and exploratory sensitivity. Food and activity events have separate visible comparison windows. Sensor data never become a fasting laboratory glucose or HbA1c series. Source revisions remain available after an update; failed or stopped imports preserve saved reports. The CLI, local web application, MCP and Ouroboros tools use the same import and reading paths. **Visit handouts follow the appointment focus.** Select body systems, thematic gene panels and a question for the visit. The short sheet combines dated measurements, existing system insights, relevant recorded prescriptions and qualified genetic context; important recorded alerts stay visible outside a narrow selection. GenCC gene–disease validity remains separate from variant confidence. The full handout and original structured fields remain available through the web, CLI, MCP and Ouroboros tools. Preparing a handout does not start genome or polygenic-score recalculation. **Insight sources and changes are easier to inspect.** A system conclusion keeps its used measurements, source identifiers, knowledge records and rule versions. Comparisons with a saved review distinguish changed observations from changed knowledge; missing original files remain explicit. Opening a report does not start genome preparation or polygenic-score recalculation. **Corrected reference statements.** NAT2 descriptions no longer claim that no guideline exists: the hydralazine guideline and isoniazid studies have different scopes, and an isolated position does not establish a complete acetylator phenotype or dose. Longevity references correct mismatched citations, preserve conflicting study results and do not count linked positions as independent benefits. A low-quality or unverified genotype does not receive a personalized longevity conclusion. ### What needs recomputing **Run `scholion ingest-labs` — if a previous compound-form or FHIR import omitted measurements or used the wrong calcium series.** Re-import the affected source folder. Earlier stored values are unchanged; inspect the import preview before replacing results. **Run `scholion genotype-sites` — if newly added catalogue positions are unread and a local alignment with its matching reference is available.** Review the targeted site-genotyping preview before confirming the operation. Existing results remain visible. This release does not require whole-genome preparation or polygenic-score recalculation merely because the version changed. Reopen a previously exported gene-region report if its coverage informed a decision: these reports calculate coverage again when requested. The coverage reader correction does not change saved polygenic scores or require their recalculation. The catalogue update is offered when its saved position set is outdated; an already current set is not queued again. For sleep alignment, re-import a wearable export if its saved night summaries do not yet contain start and end timestamps. Missing timestamps are not inferred from sleep duration. This does not require re-importing laboratory data. Saved sensor reports are unchanged by a preview. Reference descriptions and clinical estimates update on read; use `scholion system clinical_estimates` and `scholion lifestyle` to inspect them. ## v0.7.1 — 23.10.2026 ### New capabilities - The updated Ouroboros integration opens Scholion's web interface inside its Widgets panel. A guided start creates a profile or connects an existing one after confirmation; the interface and assistant tools use the same profile. Laboratory imports and saved changes remain available after a restart. Existing medical files stay in place; this integration does not require recalculating genetic results or connecting a language model. ### Corrected conclusions - Source-checked allele definitions follow the selected genome assembly, including positions whose reference base differs between GRCh37 and GRCh38. Site-reference evidence retains its actual read base; partial or unselected-sample calls cannot confirm a reference genotype. - A different substitution at a curated position retains its observed genotype without inheriting the selected variant's effect. Conflicting calls at one position remain unresolved. Corrected multi-allelic definitions distinguish the read alleles, variant identity and effect allele; an unconfirmed effect direction stays explicit without hiding known gene function. - A moved ACMG source can be verified by content instead of requiring a clinical rescan. Saved findings whose source or knowledge cannot be verified remain visible as historical rows, separately from current findings. The recorded ClinVar date, ACMG catalogue and interpretation rules remain distinct from a claim to have checked the latest online source. - Body-composition estimates retain their units, original values and measurement pairing. Fat mass and fat-free mass are derived only from weight and fat percentage in the same exported record. Water, fat, muscle and bone estimates overlap and are never added as independent compartments. The manufacturer's bone-mass estimate is displayed separately from bone density. - Body-composition changes are descriptive unless a sourced, applicable personal goal and comparable measurements support a goal comparison. Monthly means stay distinct from dated readings; invalid values and unknown units remain visible without becoming numeric health conclusions. - Folder selection provides a system dialog on Windows and macOS and manual path entry on every platform. Selecting a source folder is separate from confirming its path or importing its contents. Cancellation and failed validation preserve the previous setting. Selecting a genome folder does not start an analysis; preparing a selected genome remains an explicit operation. ### Development checks - Selecting a test module, class, method or file now runs only the requested tests in both test runners. Duplicate checks and repeated command launches have been consolidated while retaining the distinct regression cases. This does not change stored medical data or require recalculation. ### What is retracted - Allele-specific interpretations with an unconfirmed correspondence to the genomic strand no longer establish a personal effect. Corrected reference explanations remain available with their limitations. - A universal body-fat target and automatic improvement labels for unsupported body-composition comparisons are withdrawn. Device estimates do not count as several independent confirmations of a health change. ### What needs recomputing Nothing needs a mandatory genomic recalculation solely because of this update. Curated locus readings and panel explanations use the corrected definitions on the next request. Existing genotypes are preserved; this update alone does not require BAM preparation or PGS recalculation. If a position remains unread, inspect `scholion genome-updates` before requesting site genotyping. Use `scholion recompute` to inspect the data-driven ACMG action. A moved source with a saved content witness can use `scholion acmg-scan --verify-only`. A changed genome, changed knowledge, or an older report without the required content witness needs an explicit clinical scan with the named inputs. Failed or cancelled verification preserves the saved report. No clinical scan starts merely by opening a page. Re-import an available Garmin export with `scholion ingest-wearable FOLDER --device garmin` to retain per-record body-composition pairing and the bone estimate. Saved monthly history and local corrections are preserved; missing source pairing is not reconstructed from monthly averages. No genomic recalculation is needed for these changes. ## v0.7.0 — 19.10.2026 - Detailed genetic-panel effect and provenance explanations now follow the selected interface language; study values and original references are preserved. - Radar units, saved lifestyle metric captions and compound ClinVar classification labels follow the selected language. Original measurements and personal notes are retained. - Long-term laboratory trends can show a forming direction despite overlapping readings; isolated spikes remain separate observations. Dated patient reports of symptoms, everyday function and tolerability appear alongside system trends and in the visit brief, without being treated as measured treatment effects. Existing journal entries are preserved. ### What you can do now - Today summarises the last year by body system, alongside the person map. Comparable period medians are assessed against recorded targets or stable laboratory ranges; changing ranges and unknown targets remain explicit. Treatment comparisons require recorded starts and monitored markers, and retain uncertainty about cause. - Today brings the person and system map together with one attention list. Repeated laboratory deviations, changes distinguishable from recorded variation, movement toward an explicit target, and qualified wearable changes share their facts and sources. Related reasons appear together under highlighted, initially collapsed system headings; individual laboratory results remain in Labs. - Today shows one annual phenotype overview. The CLI and MCP retain 30, 90 and 365-day analysis, for example `scholion overview --period 90`. Recorded monthly means remain labelled as means, and incomplete or incompatible observations do not establish a confident change. - Save a local review explicitly and compare it with the next review. The comparison distinguishes changed observations from changed rules, brings withdrawn interpretations first, and does not mistake missing data for recovery. - A visit brief brings together system changes over the year, current prescriptions and specific discussion questions, with completed courses and detailed measurements kept separately. - Choose attention items for the visit agenda. The sheet reuses their evidence and keeps historical measurements and the optional genetic appendix separate. The same results and review operations are available through the CLI and MCP. - A specific, calibrated LDL polygenic score can be presented alongside elevated measured LDL with its study population and treatment limitations. This is an association, not an individual disease probability or a causal explanation. - Curated FOXO3 context includes positive and negative human studies, variant mechanism and applicability limits. The immune panel adds four distinct ORAI1 variants, separating recessive loss of function, dominant activation, benign classification and conflicting classifications. - System insights now include the person's read genetic variants and dated lifestyle context. Supported matches between a variant association and a current laboratory abnormality are visible even without a trend, with evidence level and source. Normal readings remain normal despite a predisposition. The same context accompanies the clinician brief and agent responses; no genomic recalculation is required. - System panels now compare source-checked variant associations with the same dated laboratory observations used in insights. Evidence level, call quality and applicability stay separate: historical or uncertain results remain visible without confirming a current association. The clinician view includes direct sources, knowledge-check dates, limitations and contrary observations; hypotheses remain labelled. - A position can be compared with several relevant measurements. Composite contexts include phase-aware APOE, iron markers with HFE, thyroid-hormone ratios and possible HbA1c bias; collection identity and specimen requirements are checked before combining results. Broader gene mechanisms remain sourced hypotheses with the actual variant and GenCC assertion, rather than gene names being treated as diagnoses. Additional panels use the same comparison logic. - Gene descriptions share one reading order across panels and genome views: known function, available variant interpretation, then confidence and expandable evidence. Low-confidence explanations remain readable, and GenCC validity stays distinct from Scholion's variant evidence level. - Gene-function references now cover previously undescribed longevity catalogue genes, with NCBI Gene and Gene Ontology sources in both languages. Predicted functions, non-coding loci, pseudogenes and withdrawn models are distinguished. Study gene sets are kept separate from variant-to-gene assignments. Longevity catalogue disclosures use visible buttons, and gene lookup states retain the same description format. - Current prescriptions can relate to several body systems through recognised classes, explicitly recorded ingredients and laboratory monitoring. Possible effects and monitoring remain distinct; unrecognised products are identified rather than assigned an unsupported action. - Labs open as collapsed system groups with measured and abnormal counts. System cards can expand their current measurements and open the matching Labs group. ### What is retracted **The individual favourable FOXO3 verdict is retired.** Positive human associations and studies that did not reproduce them are now shown together at Scholion level C. Existing genotype readings and the reference explanation remain available; no individual lifespan effect is assigned. ### Corrected display and consistency - Empty genetic catalogues no longer appear as fully read negative results. Longevity counts distinguish individual gene symbols from study gene sets. Gene searches retain reference biology when a region cannot be read without an index. Undated assistant text is no longer labelled fresh. - System trend insights follow the same selected measurement source as current values and period comparisons. Secondary device histories remain inspectable without silently replacing the main trend. - Review history tracks changes to the effective laboratory range and its origin. Switching the display language does not change measurement identity or invalidate an otherwise unchanged review confirmation. - An ambiguous device choice no longer produces a difference between unrelated device histories. Approximate laboratory dates remain visible but do not establish an exact before/during-treatment comparison. A visit agenda whose selected topics changed offers retry and explicit reselection instead of an unfinished loading screen. - Recorded weight and wearable weight now share one source-labelled history across Today, Lifestyle, goals, charts and assistant responses. Weight and derived BMI use one plotted line; each point retains its source. Attention cards link to the system genetic panel and Lifestyle instead of repeating those details. BMI uses the same weights and recorded height. A newer recorded weight no longer disappears from a chart or leaves BMI pointing in an older direction. Month-only recorded values remain distinct from device monthly means; differing devices are never silently treated as one measurement method. - The same current-value rule applies to mapped sleep, steps, resting heart rate and activity readings. Imported measurements no longer appear missing merely because they were not entered by hand. - Personal and Visit views now open different working screens. The clinician introduction follows the selected language, and the complete technical reference remains available. - The first screen renders the person map and phenotype before the heavier contextual review. Chart switches and visit actions are explicit, duplicate overview blocks are removed, and completed prescriptions remain in history instead of the current regimen. ### Existing data - Expired courses no longer remain current solely because an old status says active. Absolute albumin in a protein-fraction panel is recognised separately from biochemical albumin; existing records are retained for source review. - Existing laboratory, wearable and genetic records are retained. Reviews are saved only on request. This update does not automatically rerun PGS or whole-genome preparation. - Garmin and WHOOP exports can be imported from the installed wheel without a separate source checkout. - Older wearable summaries without sufficient coverage or uncertainty keep their values but cannot establish a qualified change. Newly imported daily Garmin records include the uncertainty needed for median comparisons. - New curated positions use available verified readings; an unread position remains unread. Reference explanations do not imply that the variant is present. ### What needs recomputing **Run `scholion genotype-sites` — if the new ORAI1 or FOXO3 positions are unread and your local alignment is available.** Inspect unread positions with `scholion genome-updates` before requesting this explicit catalogue-site genotyping operation. Existing verified calls need no repeat. Without the source alignment, retain the saved readings and reference explanations; an unread position remains unknown. PGS scores do not need recalculation for these additions. ## v0.6.9 — 15.10.2026 ### What you can do now **Preserve qualitative and interval laboratory results.** Urinalysis absence statements, microscopy counts and intervals, and respiratory PCR targets now retain their printed result, specimen, method, date, unit, reference text and source. They appear beside numeric series in CLI, web and agent lab reports and participate in archive completeness checks. Absence is never converted to zero and an interval is never reduced to a midpoint. Conflicting same-draw forms are reported without silently replacing another source. Reviewed archive previews include these rows and apply them atomically after confirmation. Wrapped quantitative SARS-CoV-2 S RBD total antibody rows are recognised separately from their interpretation thresholds. Existing imported forms need a forced re-import to add these newly supported rows (`scholion ingest-labs FOLDER --force`). New recognition rules remain proposed and do not supply clinical reference intervals or verdicts. No genome recalculation is required. ### Corrected conclusions **Keep reporting limits distinct from exact measurements.** Review summaries retain less-than and greater-than signs and use the laboratory flag at the boundary. A bounded observation no longer becomes an exact percentage change, personal baseline shift or estimate of measurement scatter. **Count confirmed deviations consistently.** A value recognised through an unconfirmed rule remains visible, but does not increase the out-of-range count. Invalid or future dates no longer establish that monitoring was completed recently. **Keep historical results out of the current visit agenda.** Results older than one year remain available in a separate history section. Values whose dates cannot establish recency are retained for review rather than presented as current. **Preserve laboratory row identity across wrapped lines.** Non-HDL cholesterol and separately named renal formulae are retained instead of omitted or mistaken for HDL or creatinine. Standard and high-sensitivity CRP remain separate assays; a neighbouring analyte’s reference scale no longer supplies a false grade. Unrecognised qualitative urinalysis rows are disclosed as labels for review, without turning absent findings or intervals into invented scalar values. New marker rules remain provisional until reviewed. ### Existing stored data Inspect the updated summaries with `scholion labs`, `scholion overview` or `scholion brief-review`. They update when reopened; no laboratory values or dates are rewritten. No genome or polygenic-score recalculation is needed for these corrections. Previously saved polygenic results and the existing recalculation workflow remain available. ### What needs recomputing **Run `scholion ingest-labs FOLDER --force` — if laboratory forms were imported before this update.** Re-import the original forms to recover omitted numeric rows, qualitative urinalysis results and PCR targets. This also separates newly recognised assay identities. Re-import does not automatically remove legacy points filed under a different assay: review those against the originals. Reopen the affected summaries or regenerate a saved visit sheet. No genome preparation or PGS recalculation is required. --- ## v0.6.8 — 11.10.2026 ### What you can do now **Recalculate saved PGS through the installed application.** Preview with `scholion prs --recalculate` and confirm with `scholion prs --recalculate --yes`. The web update panel and `sch_recompute` use the same operation, with progress and cancellation. A source checkout is no longer required. The operation reuses available scoring calls, including reference homozygotes, and reads a matching BAM only for missing model positions. VCF-only inputs retain their actual coverage; absent positions are not imputed. A report moved to another computer names missing local inputs. Failed or stopped work preserves the previous report, and a successful replacement keeps a backup. **Read a gene with the scope of its examination.** Gene lookup combines catalogue positions with available region findings, including noncoding differences. The answer identifies the reference build, interval and transcript, links the reference sequence, and shows measured depth and the fractions covered or lacking reads. Unmeasured coverage is distinguished from zero coverage. A zero variant count does not mean the gene is absent or every base matches the reference. Treatment lists and timelines use the recorded prescription date as the intake start when no separate intake start is recorded. Existing intake dates take precedence. The result retains the source of the default; an ambiguous or invalid prescription date does not supply a start. The treatment view distinguishes the scheme date from the intake start and remembers the selected interface language. ### What changes in the conclusions Saved PGS scores are rebuilt with checked called-allele scoring and pinned models. Previously recorded numbers remain visible as unverified saved values in PGS, system panels, lipid views, CLI and agent results until a new report is saved successfully. They carry a recalculation recommendation and do not enter confirmed high-score summaries. Only verified calibration can establish a population percentile; an unavailable reference remains an explicit limitation. Coverage alone does not establish calibration or a disease probability. Gene conclusions remain bounded by the positions called and the coverage measured. A missing intake start no longer makes a dated prescription appear undated. This default does not establish the actual first dose or a treatment response. ### What is retracted Previously saved percentiles lacking verified scoring or calibration remain diagnostic history; this release does not restore them as confirmed results. ### What needs recomputing **Run `scholion prs --recalculate --yes` — if saved PGS scoring is unverified or the genome inputs, pinned model or reference population changed.** Preview with `scholion prs --recalculate` before confirming. An application update alone does not require repeating an already verified calculation. Review the plan and confirm before starting; genome preparation can take time. Repeating BAM calls cannot supply a missing reference distribution. Models without a pinned identity remain outside the recalculation rather than being silently changed. Gene lookups and existing prescription records are resolved when read, without rewriting their stored history or automatically preparing the whole genome. ## v0.6.7 — 11.10.2026 ### What you can do now **Read genetic interpretations with their confidence and limitations.** Available catalogue interpretations remain accessible at every Scholion evidence level A–E. Inspect them with `scholion system --json` and `scholion panel `. Missing support is identified beside the interpretation rather than hiding the recorded wording. Gene function, variant evidence and GenCC gene–disease validity remain distinct. Clinical decisions belong to the clinician; automatic findings and decision routes retain their eligibility checks. **Scan genetic panels in a consistent compact layout.** System summaries lead with the evidence letter, linked gene, reading and a short preview. Open a row's details for the full wording, sources, hypothesis passport and analytical checks. The complete catalogue starts folded in both patient and clinician views, including panels outside the radar. Evidence letters open the guide and gene names open the detailed reference. **Keep the visit agenda focused.** Catalogue gaps such as a missing genetic mechanism, an undefined risk allele or unwritten genotype wording stay in variant details instead of becoming repetitive questions to the clinician. Questions about measured values, carriership and applicable polygenic scores remain. Prescriptions use a structured table with readable class names; uncertain system assignments and correction-route support start folded. **Open the radar sooner.** The diagram appears before detailed cards finish loading. System cards are read together and reused when opened from the radar. Repeated input-file searches and coverage-reference calculations are avoided within the reading; the next reading still checks current inputs. Only supported test proposals appear in the main interface, and an empty genotype-to-laboratory comparison is omitted. **Clearer explanations in English and Russian.** Screens present results, evidence and limitations without development history. The evidence guide consistently distinguishes an available interpretation from eligibility for an automatic clinical finding. **Combine wearable and recorded weight.** Fitness presents one weight card with the current reading and separately labelled monthly device averages and dated measurements. Daily intensity minutes also populate the activity row of the current phenotype table. Factual measurement cards do not show clinical-support warnings intended for interpretations. **Read additional laboratory rows.** Recognition-only analytes now retain units printed after a reference interval, including wrapped result rows. Unknown or conflicting units still stop ingestion. Existing omitted rows require a targeted re-import of their source forms; proposed markers do not receive an inferred normal range. **See one current phenotype table.** The first tab combines current measurements, saved personal targets and dated changes within the past year. Personal focus notes and goal reasons start folded. Device history remains labelled separately from a newer recorded measurement. Imported data invalidates browser answers when the view is reopened or the application regains focus. **Review current laboratory results.** The main laboratory list and counters cover the past 12 months. Older measurements remain in expandable history and charts; they no longer appear as current deviations or current clinician-target questions. Use `scholion labs --history` for older readings. Units use the selected language without changing stored values. Unstructured legacy notes are retained in the data rather than presented as current product conclusions. Verified PGS readings with unavailable reference calibration now say which part remains unconfirmed. **Read genome references consistently.** Longevity, ClinVar, class screening, lipids and gene lookup use compact A–E descriptions with expandable sources. Scholion confidence remains separate from an external variant classification or GenCC gene–disease validity. Legacy ClinVar tables can recover gene names from an indexed local annotation using exact coordinate and allele matches. **Understand APOE results and the longevity catalogue.** Confirmed common APOE combinations have plain-language, sourced explanations with Scholion confidence and population limitations. Catalogue position counts are labelled explicitly. Literature associations without a curated variant effect remain available in a separate folded list, with publication links. All four PCSK9 positions retain their reading; an unresolved effect direction is distinct from absence of the allele. **Inspect PGS calculation separately from model accuracy.** Each trait can show called-position and weight coverage, recorded AUROC and reference provenance. A verified percentile establishes a checked population position; it does not establish clinical accuracy or an individual disease probability. An independently checked EUR reference is available for the exact supported PGS000646 model files. Its percentile is approximate and identifies the reference cohort. ### What is fixed The lipid panel retains the verification status of its Lp(a) polygenic estimate and never presents an unverified percentile as an ordinary reading. Extreme percentile labels remain below P100. Recovery of missing ClinVar gene names requires matching recorded coordinate provenance. ClinVar annotation also retains valid source metadata when reading long VCF headers. Today tables wrap long reference descriptions to fit the screen. Known source-language unit labels are rendered in the selected language without changing values or stored units. Saved goal and focus text can carry separate English and Russian versions. Class screening now explains stale or unverifiable scan provenance instead of exposing a message identifier. Polygenic scoring now counts the actual effect alleles at multi-allelic sites and accepts the case of soft-masked reference bases. Partial calls, conflicting records and unresolved allele representations reduce measured coverage instead of becoming reference genotypes or zero-dose matches. The score retains both position and weight coverage and an input/model witness before reference calibration. Recalculation uses the pinned model directly; a different candidate cannot replace it merely because the previous result was unusable. Exact trait-name fallback can resolve a missing catalogue search result without substituting a different endpoint. Lifestyle views could omit a newer recorded weight and show only the device's monthly average. The current value now follows the same source-selection rule as personal measurements; device averages are kept separately. A focus metric could show no current value even when a measurement was available. An unverified stored PGS percentile no longer appears as if its selected model were absent: the model identifier remains visible and calibration details start folded. Missing reference ancestry is distinguished from a confirmed population mismatch. A called variant could appear outside the found-variant table because its clinical interpretation lacked complete support. Readings are now grouped by what was actually read, independently of clinical eligibility. Simultaneous panel requests could leave a test proposal unassessed. Laboratory flags are now isolated per reading; an unassessed proposal has a readable status rather than a debug message. Repeated reference descriptions and automatically expanded catalogues no longer overwhelm the initial system view. ### What this changes in the conclusions An interpretation's A–E confidence describes the evidence available for assessing it. Displaying an interpretation does not promote it to a supported automatic clinical finding. Scoring models and clinical thresholds remain the same. The PGS summary includes all admitted percentiles at or above P80; incomplete position or weight coverage is explained separately and does not become a high flag. Corrected allele dosage can change a polygenic score and its calibrated percentile on unchanged genomic input; no genotype is altered. ### What is retracted The presentation rule that an existing catalogue interpretation must disappear solely because its confidence is below B or its support record is incomplete. The unrelated obesity publication previously cited for APOE is replaced with publications on APOE; this does not change the genotype reading. ### What needs recomputing PGS000646 can use its separately verified EUR reference when the corrected score, exact model identity and effective coordinate build match. Its reference differs from earlier external calibrations: keep that change with the result and do not splice percentile series across references. Existing calls can be reused; a calibration change alone does not require BAM genotyping. Unsupported calibrations for other models remain unavailable. **By hand — if polygenic scores were computed before this release.** Check saved polygenic scores with the corrected matcher using existing scoring-site calls. Only missing required calls need targeted BAM genotyping. The application proposes this check when corrected scoring evidence is missing, and stops proposing it once the report has been checked. Updating the application does not start a genome operation. Earlier results may contain incorrect multi-allelic or soft-masked allele dosage, or overstate coverage. Do not grant old results a new scoring witness merely by copying metadata. Models without a validated reference distribution still have no supported population percentile; additional genotyping cannot establish that distribution. Reload the application for presentation changes. Existing measurements and saved personal goals are retained. ## v0.6.6 — 10.10.2026 ### What you can do now **Population assignment is part of connecting a genome.** Connecting genomic inputs prepares a coarse reference-panel assignment automatically when depth-checked genotypes or a usable BAM and indexed reference are available. Missing or ambiguous input leaves the population unresolved, and PGS scoring stops instead of silently assuming EUR. The determination is bound to its input files and active profile. See the reference population determined automatically from the genome beside saved polygenic results, including whether the calculation used the same panel. Inspect these facts with `scholion prs --json`. New PGS calculations use the available genome-derived population automatically unless explicitly overridden, and retain the source of that choice. **Read lower-level context directly on genetic panels.** Both patient and clinician views show sourced C/D descriptions immediately, labelled as hypotheses. Genetic summaries have a dedicated A–E evidence column; its letter opens the reference guide. Click a gene name in a system card to open its detailed panel reference. ### What is fixed A missing selection-source field in older results could produce a warning that no population had been determined, even when genome analysis had established the same population used for scoring. The application, command line and assistant reports now distinguish the current determination from the historical selection source. Different or unrecorded calculation populations remain explicit warnings. ### What this changes in the conclusions Matching panel labels establish agreement with the current population assignment, not valid PGS calibration. Calibration, coordinate and coverage checks continue to apply independently. ### What is retracted The statement that population analysis had not been performed solely because an older result lacked its panel-selection source. ### What needs recomputing **By hand — if saved PGS results lack calibration provenance or use a different reference panel.** Verify or rebuild the scoring report. No recalculation is required solely to correct this warning when the recorded calculation panel matches the current determination. Saved personal results are not rewritten automatically. ## v0.6.5 — 09.10.2026 ### What you can do now **Read polygenic results with their calibration and coordinate provenance.** Use `scholion prs --json` to inspect the result and its provenance. Raw scores, calculation methods, reference populations, reference parameters and coordinate builds travel with each result. Variant coverage, weight coverage, calibration and model informativeness are separate facts. Displayed percentiles retain fractional values; rounded endpoints are shown as bounds rather than an exact P100 or P0. A percentile remains a population position, not a probability of developing a disease. ### What is fixed A fully covered genome could receive a seemingly reliable P100 when the score had no suitable reference distribution. Such approximations are now withheld from ordinary percentile results, high findings and clinical questions. A missing or rejected reference, an unsupported calibration method, inconsistent reference parameters, and unknown or mismatched coordinate builds cannot be repaired by a coverage flag. Model selection and the reported spread use the same validation. Models originally published on one genome build can still be used through their matching harmonized coordinates. The scoring request carries the verified input build. Preparing scoring sites from BAM refuses inconsistent position lists, BAM headers or reference sequences before genotyping starts. ### What this changes in the conclusions A stored reliability flag alone no longer establishes a valid percentile. A verified high percentile can remain high; no disease-specific exclusion or cap is applied. The application, command line, assistant tools, body-system panels and visit sheets use the same validation and explain why a value was withheld. ### What is retracted Percentiles without verifiable calibration or coordinate provenance, including AUROC-only approximations, are withdrawn as population positions. Previous values and explanations remain available as diagnostic history rather than ordinary findings. They are not replaced with percentiles from rejected reference data. These checks apply to low values as well as high ones. ### What needs recomputing **By hand — if polygenic results were saved before this release.** Rebuild saved polygenic results from a report with verified calibration and coordinate provenance. Retain the original report and backup. A result without that provenance stays withheld until a verified scoring report is available; re-genotyping BAM is required only if the scoring-site input itself is missing or inconsistent. Existing laboratory and curated-gene results do not require recomputation for this correction. ## v0.6.4 — 09.10.2026 ### What you can do now **Review several laboratory forms inside one PDF before importing them.** Each self-contained form keeps its collection date and time, specimen and source pages. The application, command line and assistant tools show a preview; an explicit approval imports it only while the source files and destination still match. Numbered continuation pages require the same order identifier and consecutive pagination. Ambiguous boundaries and contradictory observations prevent the archive from being written. Start with `scholion ingest-labs "" --archive-preview`; the preview supplies the matching approval command. **Prepare a visit sheet with traceable facts.** The sheet names the data container, software version and preparation date, retains measurement dates and sources, and separates unassessed measurements from assessed findings. A separate optional appendix keeps reference genetics and hypothesis passports with their evidence levels. The same sheet is available through the application, command line and assistant tools, in English and Russian. **Read the liver fibrosis index (FIB-4) in the liver panel.** A compact row opens to show its meaning and calculation details. It uses AST, ALT and platelets from one recorded draw and age at that draw. Inputs, units, missing data, population, purpose and primary sources accompany the number. Invalid inputs and insufficiently established dates withhold calculation; no diagnostic category is assigned automatically. The body-system radar is explicitly labelled a Scholion heuristic rather than a validated risk score. **Use a consistent interface throughout.** Remaining application screens now use the same controls and visual system as the main views. The visit sheet adapts its measurement rows for narrow screens and has a dedicated print layout. ### What this changes in the conclusions Laboratory forms from different draws remain separate observations. An English ISO collection header also retains its time, so two draws on the same day do not silently become one measurement. ### What is retracted No existing stored observation is reclassified automatically. ### What needs recomputing Nothing needs automatic recomputing. No profile migration is required. Preview an archive explicitly to import forms that the single-form importer previously refused. Previously saved observations remain unchanged until the user imports or edits them. --- ## v0.6.3 — 07.10.2026 ### What you can do now **Read more precise reference explanations across genetic panels.** Molecular mechanisms and bounded implications for 38 existing positions now identify primary studies, experimental models, negative findings and variant nomenclature. Gene function remains visible when a variant's influence is uncurated. Open a genetic panel in the application or use `scholion system` to read the same context. **Read gene biology throughout the GenCC system panels.** All 1,469 existing GenCC genes now carry source-bound reference descriptions. Russian views also translate the disease names, inheritance and validity labels. Each gene–disease assertion retains its organisation, date and original classification; conflicting assessments remain visible side by side. GenCC validity and Scholion A–E variant confidence are labelled separately, and a whole gene receives no variant grade. When a gene's reading status is unknown, its row shows an unread state instead of appearing to be a negative finding. **Keep concurrent profile changes on Windows.** A temporarily inaccessible write lock is retried; if it remains unavailable, the write is refused explicitly. Separate processes no longer proceed without the lock and silently lose a change. **Distinguish variants and the tests they represent.** CYP2C19*17 has its own increased-transcription explanation, separate from the splice defect associated with *2. ADRB1 context includes the current beta-blocker guideline and its lack of an ADRB1-based recommendation. HLA tags remain distinct from direct typing; historical protein names and reference-strand changes are explained explicitly. ### What this changes in the conclusions Reference wording is more specific about the studied tissue, population and endpoint. Clinical grades, personal interpretation checks, allele readings and calculated results are unchanged. A molecular experiment or biomarker association does not establish an individual diagnosis, supplement response or treatment. ### What is retracted The generic reduced-function explanation beside CYP2C19*17 and the assertion that ADRB1 has no CPIC guideline are replaced. Vitamin D, selenium, iron, fatty-acid and thyroid explanations distinguish associations from unproven universal treatment effects. Reference and alternate alleles do not mean beneficial and adverse, respectively. ### What needs recomputing Nothing. Reopen or regenerate panel reports to obtain the new reference wording. Stored genotypes, locus coordinates, measurements and clinical conclusions are unchanged; no alignment rescan or genome re-indexing is required. Earlier exported reports retain their previous wording. ## v0.6.2 — 06.10.2026 ### What you can do now **Read allele presence separately from clinical interpretation.** A positively read named allele stays present even if its interpretation is withheld. An unread, presumed or unconfirmed position is unknown, rather than a negative result. Clinical carrier counts and findings retain their independent checks. **Understand why a sentence is missing.** System cards, reports and clinician questions distinguish incomplete clinical support from a phrase that has never been written. Reference panels show the number of withheld A/B interpretations; gene biology and reading quality remain visible at every A–E level. Use `scholion panel cardio` for the reference panel, `scholion system cardio` for readings and `scholion longevity` for the literature catalogue. **Use restored, sourced explanations for key clinical positions.** F2, F5, HFE, G6PD, TPMT, NUDT15, UGT1A1, SERPINA1, APOB and two PRSS1 variants have their own identifiable references and mechanisms. Single positions do not establish a complete pharmacogenetic phenotype, a diagnosis or a drug dose. Source curation remains incomplete for 119 of 151 A/B system-panel rows (117 of 140 distinct positions) and all nine A/B on-demand rows. Their personal interpretations stay withheld; this is a knowledge gap, not loss of genome data. **Read concise longevity support notices.** Each variant lists its unsupported fields together once, while structured output retains support for every field. A supported field is still shown with its own provenance. ### What this changes in the conclusions Supported explanations return only within the scope of their own sources. Presence of an allele grants neither a finding nor a treatment decision. Reference gene lookups and panel explanations use consistent neutral context for the revised positions. Other unsupported clinical claims remain withheld. ### What is retracted A withheld interpretation no longer reports a read heterozygote as lacking the named allele or promises an unwritten phrase. Unsourced personal risk multipliers, hormone-route and haematocrit advice, single-SNP dose instructions and diagnoses are removed from the revised explanations and lookup notes. The catalogued PRSS1 A>C change is identified as N29T, distinct from the A>T variant sharing its rsID. ### What needs recomputing Nothing. Reopen or regenerate panel and longevity reports. Stored genotypes, coordinates, alignment data and laboratory measurements are unchanged; no genome re-indexing or alignment rescan is required. Previously exported wording is not rewritten. ## v0.6.1 — 06.10.2026 ### What you can do now **Gene panels explain the biology beside the reading at every evidence level.** Both personal and clinician views show gene function, known variant mechanisms or literature associations, the Scholion A–E confidence level and separate sources. Use `scholion panel cardio` for the reference catalogue or `scholion system cardio` for readings beside it. Reference panels also work without a connected genome. Unknown variant influence is named explicitly, even when the gene's function is known. **Missing clinical support no longer hides useful reference knowledge.** The coagulation panel again explains prothrombin and Factor V Leiden, with primary sources. Research contexts below A/B remain visible as reference knowledge. Reading quality and an unread or unconfirmed position remain visible; a gene description does not imply that a variant was found. ### What this changes in the conclusions Reference biology is separate from personalised analysis. It grants no finding, carrier status, individual risk estimate, treatment rule or laboratory proposal. Personal conclusions still require their own evidence, applicability and reading checks; an unsupported conclusion remains withheld. ### What is retracted The display policy that reduced lower-confidence positions to counts or hid gene explanations together with unsupported clinical sentences is replaced by visible reference context. Previously withheld clinical claims are not restored by this change. ### What needs recomputing Nothing. Stored measurements, genotypes and analytical results are unchanged. Reopen or regenerate panels to see their reference explanations. No genome re-indexing or alignment rescan is required. ## v0.6.0 — 05.10.2026 ### What you can do now **Printing stays readable when the screen uses the dark theme.** Printed Crossread cards and legacy views share a light paper palette without changing the selected screen theme. Regenerate previously saved dark-theme PDFs; stored measurements and interpretations are unchanged. **Windows file checks no longer mistake unchanged genomes for changed inputs.** ACMG screening still refuses a replaced or modified source. If an older table is refused, rerun the screen; stored findings are not silently revalidated. Edits to local notes are also reread even when successive saves share the same rounded modification time, instead of retaining the previous note. **Amino-acid reference contexts are shared, not diagnoses.** Low measured values can link to one source-backed explanation of protein intake, malabsorption and catabolism, with the limits of each single study. These contexts do not affect scores or establish a cause. Value-only markers and censored observations do not trigger them. Urinary urea nitrogen is described as part of nutrition assessment, not a direct measurement of absorbed protein; an amino-acid panel alone cannot establish an order or a repeat interval. **A 16-position folate, histamine, redox and choline panel.** Available through `scholion panel folate_histamine_redox_choline` and `scholion system folate_histamine_redox_choline`, it preserves existing interpretations. Thirteen new positions carry literature context, not approved genotype-specific clinical conclusions. Coordinates support GRCh38 and GRCh37; genotypes must be considered alongside measured phenotype. **Personal and Visit are explicit display modes.** Visit defaults to the professional register and offers a patient selector only when there are several local containers. Changing the display mode does not change the patient. The technical container ID and engine version remain visible, including in print. **Recorded goals have a shared view and explicit origins.** Personal targets, clinician targets and recorded focus tracks appear together. A person can state whether a goal comes from a complaint, a lab result, the genome, history, a clinician or prevention, with a reason. Changing that metadata does not change the target or its observations. Older records without a stated origin remain unspecified rather than acquiring invented provenance. **Genome positions name their data workflow.** Each position distinguishes an available linked measurement, a missing measurement, or no independently supported marker link in this build. Unread or unconfirmed genotypes and missing claim support keep the route blocked. These routes are not test orders or treatment decisions; an undeclared link does not mean that no marker exists. **Dose and longevity sentences cannot borrow nearby citations.** Effects, formulations, alternatives, actions and population statements each need their own source and mechanism. Unsupported sentences are explicitly withheld; genotypes, recorded doses, laboratory values and their dates remain visible. A withheld dose-context assessment cannot produce a reassuring prescribing verdict. Lipoprotein(a) uses the dated laboratory comparison, and an inexact or unassessed value cannot acquire a green grade. **Calculations are distinguished from clinical conclusions.** BMI retains its calculated value without an unsupported clinical grade. Radar and wearable indices state their display-comparison method, not a clinical prediction. PhenoAge carries its population-model source and method, not an individual prognosis. Recorded focus expectations are labelled as notes, not measured treatment effects. Stored observations and previously entered goals are not rewritten; regenerate saved reports to see the withdrawn interpretations. **Clinical thresholds and actions need independent support.** A catalogue boundary without held interpretation is shown as unresolved, not safe or below an action threshold. The qualified marked-CK warning uses the applicable upper reference bound rather than a fixed number; treatment decisions stay with the clinician. Measurements and prescribed schedules are not changed. **Suggested tests need their own grounds.** Test selection, reason, trigger applicability, priority, referral and repeat interval no longer borrow one another's support. Unsupported candidates stay visible without an order or a reassuring low priority. There is no implicit three-month repeat interval; one recent result cannot establish completion of a whole monitoring panel. Existing measurements and prescribed schedules are unchanged. **An observed number is not automatically a treatment goal.** Historical extrema and reference bounds remain visible as observations. An assumed risk category or unconfirmed diagnosis cannot select a clinical target, and a guideline citation alone does not support the target or an adjacent quotation. Previously entered or accepted goals are kept; review previously generated targets with your clinician and regenerate saved proposals to replace the unsupported advice. Stored values are not recalculated or erased. **Laboratory scales are no longer mistaken for normal intervals.** Printed grades and conditional rows stay with the measurement; a graded scale is not replaced by a dictionary interval. Time-dependent rows use the recorded draw time, while an unknown posture leaves the comparison unresolved. Unrecognised result labels are named even when other rows in the file were imported. An inexact result expressed as a bound is not assigned a unique printed grade. **Import refuses ambiguous combined forms instead of corrupting results.** Documents with several draw dates or mixed blood/stool forms must be split before importing. A numeric stool count cannot be truncated onto the worded score scale. Decimal results beside thousands-separated ranges, unbracketed age bands, a paediatric first reference row, wrapped vitamin B6 and the Cyrillic OH spelling are read without the reported losses. Published ClinVar and other sites-only resources are not counted as a person's genome. Existing stored measurements and dates are not repaired automatically: review affected histories against the originals and re-import individual forms with forced re-reading, rather than relying on a previous import's success message. Printed grading labels are reported as the form's words, not as Scholion diagnoses. **Recorded tests are separate from monitoring advice.** Monitoring reasons and the proposed use of each marker require their own support; unsupported reasons and timing are withheld and named, rather than printing a validated plan or a reassuring prescribing verdict. The selected observations keep their values, dates, units and laboratory comparisons. A missing profile row now says "not recorded in this profile", not that the test was never done, and uses the marker's display name. Recorded tests and prescriber schedules are unchanged; regenerate saved prescription checks to replace the unsupported advice. **An interaction match is not automatically a clinical judgement.** Unsupported effects and management statements are withheld independently, with their missing basis named beside the matched medicines. Such a gap cannot produce "no interactions" or a low prescribing verdict. The supported warfarin/NSAID bleeding caution remains prominent without a personal risk estimate or an automatic gastroprotection plan; it is not lent to every vitamin K antagonist. Mixed partners remain separate. Stored medicines are unchanged; regenerate saved prescription checks to replace the withdrawn wording. **A prescribing statement cannot borrow another statement's support.** Clinical notes, gene-role explanations and guideline quotations now require their own source and mechanism. A changed quotation loses its recorded support rather than inheriting that of the previous text. Missing support is named; the phenotype and its observations remain visible without a reassuring assessment. Indication-specific tables are withheld when the indication is not established, and an omeprazole or citalopram quotation is not lent to another drug. The supported DPYD poor-metaboliser toxicity warning remains prominent, without supplying an individual dose. Recheck saved drug and prescription reports; recorded readings and treatments are not changed. **Prescribing tables must match the drug and joint phenotype.** A simvastatin table is no longer quoted for another statin, or a codeine table for tramadol. Two intermediate TPMT/NUDT15 readings no longer receive the held single-IM dose recommendation. Both readings remain visible; the missing applicable table is named instead of yielding a reassuring verdict. A normal phenotype found online also cannot stand in for a prescribing table that is not held. Stored readings and prescriptions are unchanged. Regenerate saved drug checks to replace these earlier recommendations; treatment decisions remain with the prescriber. **Connection instructions fit long installation paths.** Commands and path details wrap on narrow screens without being shortened or changing the text to copy. **A missing route dependency is not a negative genotype.** When a triggered rule needs a reading unavailable in the current panel, the gap is named instead of being silently omitted or described as no genetic influence. The patient's view counts lower-level rules; the clinician's view retains their missing dependency. No genotype is inferred from the gap. **Genotype-linked prescribing cautions carry their own basis.** A caution without a primary reference and mechanism, or below evidence level B, no longer supplies a prescribing interpretation. The recorded genotype remains available. The factor V Leiden warning retains its priority and now cites contraceptive eligibility guidance rather than an unqualified risk multiplier; that multiplier is withdrawn, not converted into a personal probability. Treatment decisions remain the prescriber's. Stored prescriptions and genotypes are unchanged; regenerate saved reports to replace the old wording. **Separate support for separate claims.** A position's source no longer automatically supports its marker expectation, intake route, response to a load or proposed next step. Each unsupported claim is withheld with a reason, including in reference panels and marker links; readings and evidence levels remain available. Existing reports should be regenerated. **Supported next steps retain their explanation.** Laboratory, genome and discussion lists carry the step's own mechanism and reference. A genome step or low-coverage warning no longer disappears when every listed gene was read, or gets attached to the last gene instead of the gene it concerns. **PCSK9 interpretations no longer exclude other causes of low LDL-C.** R46L and C679X descriptions distinguish population associations from personal predictions and experimental mechanisms. Cohort estimates are not doubled for two copies; the pooled nonsense-variant estimate is not presented as specific to C679X. The previous reassuring wording and per-allele estimate are withdrawn. Genotypes, laboratory values and prescriptions are unchanged. **Correction routes distinguish missing support from no effect.** A route and its recheck timing and change criterion each need their own reference and mechanism. Unsupported advice is withheld with a reason, not presented as a negative genetic conclusion. Lower-level evidence no longer transmits the same treatment claim under a different heading. Measurements and prescriptions are unchanged; regenerate saved reports to replace the withdrawn advice. **Explicit gaps in curated interpretations.** A curated A/B position or group without an identifiable primary reference and a recorded mechanism no longer prints a clinical interpretation. Its evidence level and reading status remain visible, with the missing basis named. Unsupported positions do not count as findings or support a reassuring panel summary; their marker links no longer assert a direction. This withdraws earlier unsupported positive and negative panel wording. Genotypes, measurements and prescriptions are not rewritten; regenerate saved panel reports to see the corrected interpretation status. **Consistent CYP2C19 labels.** A newer locally cached reference can no longer restore the withdrawn compensation claim in the computed IM label. The local reference file, its date and the computed phenotype code remain unchanged. **Clearer limits on genetic predictions.** ABCG2 urate explanations no longer promise a response to allopurinol or turn a population odds ratio into a personal risk prediction. CYP2C19 panel rows no longer infer a complete metaboliser phenotype or select treatment from a single position, and no longer combine stroke and coronary-treatment advice. The unsupported claim that `*17` partly compensates for a no-function allele is withdrawn; the computed IM code is unchanged. Revised explanations carry primary references and mechanisms; their human review is open. Stored readings and prescriptions are unchanged. **Corrected pharmacogenetic-panel explanations.** The lipid panel no longer treats the SLCO1B1 c.521 position as a complete `*5` haplotype or places poor-function dose advice beside a one-copy ABCG2 result. Those earlier formulations are withdrawn. The revised explanations distinguish the reading from prescribing decisions, carry a mechanism and primary reference, and explicitly await human review. An unrelated urate claim was removed from the statin explanation. Stored genotypes and prescriptions are unchanged. Cardiac, inflammatory and renal panel explanations also no longer turn a single CYP2C9 position into general NSAID dose instructions. Warfarin-related rows explicitly require the complete genetic and clinical context and INR, rather than proposing a dose from one position. **Read chart gaps and goals without implied health judgements.** Missing values break observation lines instead of being bridged. Goals and reference windows use neutral, labelled marks; calendar years no longer make an observation look favourable. Paired series have labels, distinct line patterns and point shapes. Chart colours follow the active theme. Stored observations and targets are unchanged. **Read the complete panel list.** `scholion panel author_list` includes positions awaiting inclusion and positions that were refused, with the reason for each. Available panel-author explanations retain their Russian original and a labelled English translation. They are shown as opinions at the position's evidence level, not as additional findings or individual treatment recommendations. Existing genotypes, findings and patient-specific clinician notes are unchanged. The clinician's view (`scholion system author_list --register clinician`) adds local genotype readings and separately labelled values recorded in the profile, including positions not accepted into a panel. Missing calls and unsupported insertions/deletions remain explicit refusals; no remote variant lookup is made by opening the list. The web reading list groups compact, keyboard-expandable rows by gene. Each row distinguishes a current local-file call, a recorded profile value and an unconfirmed value, with evidence level and provenance kept beside the reading. Printing includes the folded details without changing which rows stay open afterward. **Keep hypotheses separate from patient conclusions.** Patient-facing system cards and text reports now count C–D hypotheses and E-level values without listing their individual positions. Previously, those positions could still appear in summaries even when their explanatory text was withheld. Clinician views retain the details; JSON and assistant tools still carry all positions, their levels and hypothesis passports. No stored genotype is removed or changed. **Separate local patient containers.** A workstation can hold several people without mixing their files. CLI, web and assistant replies name the technical container ID; an assistant session refuses a switch until a new session starts. Local labels are not sent through the model tools. **Whole-container export and deliberate erasure.** Local CLI and web controls export the owned files and a manifest, or erase an inactive container only after an inventory and exact-ID confirmation. Uncertain or shared external ownership refuses. Erasure includes raw originals and cannot be undone; it is not forensic secure deletion and does not remove separately retained backups. **Local access journals.** Registered workstations record the technical ID, time, action and entry point in the container and a surviving clinic journal, without labels or medical content. These are access records, not tamper-proof evidence. Export and erasure are not assistant tools. **Privacy boundaries are available offline.** The installed documentation and plugin distinguish the local engine from an assistant host: tool answers can reach a remote model provider, and a technical ID is not anonymisation. The policy explains network queries, journals, export and erasure limits; offline mode blocks the engine's requests, not the host's model requests. ### What is fixed **Position panels name rows, not genes.** A completed sixteen-position panel covering eight genes no longer claims sixteen genes were read. X-linked calls retain their single-copy checks even without an assigned risk allele; an apparent heterozygous male call is withheld rather than treated as diploid. **A gene-region report is visible in the genome view.** Missing annotation, unselected samples, unreadable inputs and unavailable protein calculations remain named gaps, not zero variants. Variant counts use the selected sample and exclude reference homozygotes. **PhenoAge does not assemble a blood draw from a month.** Different collection dates and times are kept separate. Legacy records with only a month no longer produce a composite result until their draw dates have been recovered. **An interrupted form import can resume.** Completed files are checkpointed, rejected table rows and storage errors are named, and bounded numeric results keep their qualifiers. Time-limited tool hosts return a terminal command for an unfinished import or an uncached whole-genome pass, not a partial report. Resuming preserves the preferred measurement method and reports disagreements with later forms instead of silently replacing the selected value. A valid English collection date in a table is no longer also reported as rejected. The first tool-based import into an unnamed local container no longer invalidates the assistant's session after saving the results. Previously overwritten values are not repaired automatically: compare the source forms and force a re-import of the affected folder. **Genetic conclusions require confirmed inputs.** APOE epsilon assignment requires two confirmed calls. Stale indexes are not used for seeking reads, coverage refuses an unknown or mismatched assembly, padded equivalent alleles can match secondary-findings records, and unanchored report genotypes are not counted as reference-oriented calls. Moderate gene–disease relationships are not findings; X-linked recessive findings distinguish sex and single-copy calls. **A curated repeat is read as repeat alleles, not individual letters.** SERPINE1 4G/5G uses rs1799762 and verified reference windows on both genome assemblies. rs1799768 remains a distinct insertion, not an alias. A missing variant row, unsupported representation or duplicate call cannot establish 4G/4G. The repeat count does not establish an individual clinical risk. AGXT Gly170Arg, Phe152Ile and Ile244Thr are available for coordinate-based lookup; their literature context does not establish a treatment response in an unaffected carrier. **Correction routes can observe existing cross-panel dependencies.** Read state is preserved without importing another panel's clinical sentence. Unobserved inputs remain explicit gaps; an observed genotype cannot bypass the independent clinical-support gate. Local clinician notes are labelled as opinions, not evidence upgrades. **Reference comparisons retain their context.** Form age bands use age on the collection date, unknown demographics do not select a labelled range, and boundary flags use the measurement's own interval. Same-day records with incomplete clock precision no longer bypass the recorded method priority. Radar rows retain reference origin, and form defaults use the local date. **Local diagnostics retain their security checks.** TLS verification is not silently bypassed, a foreign server's text does not identify it as Scholion, and PRS constraints use private unique temporary files. The Hub data view honours the configured external profile. Medicine replacement and import conflicts/errors are visible in the web interface; CLI exit codes are documented. **Previously missing laboratory analytes are no longer silently omitted.** Recognition includes 31 additional analytes, with English and Russian names. These new rules are explicitly proposed: values and dates are retained, while norm judgements await the person's confirmation. Units come from the form or the table, not an assumed assay; missing or changed units produce a named refusal rather than mixing unlike numbers. No reference intervals, standard identifiers or conversion factors are invented. Earlier imports are unchanged; re-import source forms to recover previously omitted readings. **Unassessed laboratory readings no longer look reassuring.** A proposed rule or a missing corridor produces a neutral label rather than a green in-range badge. An unconfirmed rule does not print its corridor as a normal range. **Existing print views keep the radar together.** Large system cards can flow across pages instead of leaving a blank opening page; the on-screen layout is unchanged. This does not add a new consultation sheet. **An interrupted laboratory import could leave only part of a panel.** CSV imports now validate dates and prepare the complete panel before saving it. A rejected row leaves the previous laboratory file unchanged. Importing into a demonstration profile is refused without clearing that profile. **A bounded laboratory result could become an exact number.** FHIR observations such as “less than 5” are now explicitly skipped, rather than stored as exactly 5. Observations with multiple or conditional reference ranges are also skipped until their context can be represented without guessing. **A failed coverage measurement could look like no reads.** A failing alignment reader now stops the measurement without replacing the completed coverage table. Interrupted measurements are reused only when their local input identities and parameters match; obsolete progress is retained separately, not mixed into the new measurement. **Unresolved threshold comparisons could dilute the reported rate of flags.** Unresolved markers are counted separately, not as negative comparisons. A text-only result no longer crashes the next-draw checklist. **A slower tab could replace the screen selected afterwards.** Late tab replies and their errors no longer overwrite the current screen. A read started before cache invalidation cannot repopulate that cache with its old answer. **The note beside an assumed reference named two script files instead of a command.** When a position has no row in the variant file, `scholion genome` answers that this is either the reference or a lack of coverage, and says what tells the two apart. It pointed at two scripts inside the source tree, which a person who installed the package does not have. It names `scholion recompute` now, which reads the catalogue positions from the alignment where one is at hand. The answer itself is unchanged. ### What is checked now that was not - The linter of the public checks runs in the test runner before a publication, with the version and the command the public job names. Until now it answered only after a version was out. ### What is retracted **A saved secondary-findings table is not necessarily a result for the current genome.** A table whose input identity or selected sample cannot be confirmed is no longer presented as findings or as a clean negative result. Missing, unreadable or replaced input files and altered tables now require a fresh scan. ### What needs recomputing **Run `scholion genotype-sites` — when the alignment and matching reference are available.** The added panel expands the locus catalogue. An older scoring-site file cannot establish reference genotypes at its new positions. No laboratory values are changed by the catalogue update. **By hand — previously saved composite and genetic reports.** Recover collection dates for month-only PhenoAge inputs and regenerate reports assembled across draws. Recheck APOE assignments based on assumed references, selected-sample region reports, X-linked interpretations and previously accepted Moderate gene–disease findings. No stored result is silently corrected. **By hand — existing ACMG secondary-findings tables.** Run `scholion acmg-scan` after confirming the selected VCF and sample. Earlier tables do not contain the input-identity witness required to reuse them safely. The original VCF and stored table are not removed by a read-only check. **By hand — if an earlier CSV import failed partway through.** Check the original file and re-import the corrected complete panel. **By hand — if an earlier FHIR import contained bounded results or conditional ranges.** Imports that contained bounded results or conditional reference ranges must be checked against the original observations: a qualifier lost during import cannot be recovered from the stored number alone. **Run `scholion coverage` — if an earlier attempt failed or was resumed after changing its inputs.** The old table cannot establish whether either happened. ## v0.5.11 — 27.09.2026 ### What you can do now Nothing new: the program answers exactly as 0.5.10 does. This release repairs the public checks that stand behind it. ### What is fixed **The public check of types could not run.** Every push to the public repository starts a job that counts type errors per module and refuses a push that adds one. The job called a tool the published tree did not contain, so it failed before it measured anything, on every push since it was introduced. The tool and its recorded counts now travel with the package, and the job measures what it says it measures. Nothing a person runs was affected: `scholion` and every command of it behaved the same with the job red. ### What is checked now that was not - A workflow that ships names only tools that ship with it. The package's own suite reads the workflows and the test runner, and a tool one of them runs and the package lacks stops the publication before anything goes out. - A tool that compares against recorded numbers travels with those numbers. - Type errors are counted before a publication and not only after it. The test runner fetches the checker in the version the public job pins, without installing anything into the interpreter, and stops when a module has gained an error; where it cannot fetch the checker it says the count was not measured. The first count found three modules above their recorded number, and they are corrected: the recorded total falls from 258 to 173. ### What is retracted Nothing. ### What needs recomputing Nothing has to be recomputed and nothing has to be re-imported. A person who runs `scholion version` sees the new number and no step to take. ## v0.5.10 — 27.09.2026 ### What you can do now **Read the current regimen through an assistant.** An assistant connected through the tool server could check a new prescription against «the current prescriptions» and could not read that list: the overview it was given carries how many prescriptions there are, not which. The tool `sch_medications` prints the regimen as `scholion medications` does, so the assistant can say what the new drug was compared against — and no longer has to ask the person to recite it, which is an answer from memory and not from the record. **Ask about the genome file and the laboratory archive before a negative answer.** The safety rules tell an assistant to consult `scholion genome-status` before a statement that depends on the file, and to run `scholion selfcheck` before saying a test was never taken. Both are tools now: `sch_genome_status` and `sch_selfcheck`. The rules handed over by `sch_rules` end with a table that names, for every command they mention, the call that runs it. **Start the ACMG screen from an assistant when it has never been run.** `scholion recompute` and `sch_recompute` offer the screen as a step whenever a genome is connected and no result of the screen exists; it starts, as every step does, only after the person's yes. Before, the step was offered only when a release asked for a re-run, so a profile that had never been screened was told «not run» and given nothing that could run it. **Reach five more readings through the tools that already cover their subject.** `sch_selfcheck` with `full=true` prints the whole audit of forms against the profile (`scholion reconcile`); `sch_genome_status` with `updates=true` prints what the last check against a fresh ClinVar changed (`scholion genome-updates`); `sch_analyze_labs` with `catalogue=true` prints the marker keys its own `markers` parameter takes (`scholion markers`); `sch_rules` with `levels=true` prints what each evidence level A–E means (`scholion evidence-levels`); `sch_overview` with `snapshot=true` prints the bare snapshot (`scholion profile`). **Ask a build what it can do, and through which door.** `sch_capabilities` is `scholion capabilities` as a tool. The manifest now prints the call that answers for each command and, where no tool does, why not and which tools reach the same thing — so an assistant can answer «this exists, on the command line» where it used to have nothing to say. ### What is fixed **Coverage of a gene was never measured on an alignment that names its contigs `chr7`.** The depth reader looked a chromosome up by the exact name `7`, which is how the coordinate layer spells it. An alignment against a UCSC or GATK reference spells it `chr7`, so for every gene on such a file `scholion genome --gene` printed «coverage not measured — the alignment carries no contig 7» — about a file that holds the contig and was never asked for it under its own name. The name is now matched against the file's header in either spelling; the mitochondrion is found as `MT`, `M`, `chrM` or `chrMT`. A contig the file really does not hold is still refused, and `1` never finds `chr11`. **A measured coverage was replaced by «not measured» in the answer about a gene.** For a gene outside the curated catalogue the depth read from the alignment was written into the answer and then overwritten by the entry of the coverage table, which for most genes is empty. The report then said that coverage «was not computed for this answer» beside an alignment that had just been read. The measurement now stays, and the table's answer travels beside it. **Three commands that write were listed as «reads only — safe to call to answer a question».** `scholion capabilities` called `choose-genome`, `brief-reviewed` and `acmg-scan` reads. The first records which file is the person's genome, the second records the person's statement that a brief still holds, the third writes the table the ACMG screen reads. All three are listed as changes now. **The instruction counted thirty-two tools where the build registers more.** The paragraph an assistant reads about the tool server listed thirty-two tools and left out `sch_version`, `sch_update` and `sch_recompute`. It lists what the build registers. **An evidence level was a bare letter to an assistant that holds only tools.** The laboratory report prints «(B)» beside a genetic note, and the legend — which levels permit a conclusion at all — had no tool. See `sch_rules` with `levels=true` above. **A request to prepare for a visit to a physician did not lead to the page written for it.** A host that reads tool descriptions and no skill entry saw nothing in the description of `sch_second_opinion` that said when to use it. It says so now, in both languages. ### What is checked now that was not - What the build says about its tool door is held to what it does: every command without a tool states a reason of a kind a program can check; a command excused for writing must be listed as a write; a reading said to be reached through another tool is run through both and compared; and a command the safety rules name must be one the tool door answers. - A tool and the command it stands for print the same text and the same structure, compared on every run. - The alignment reader is tested on files written in both spellings of a contig name. - An import nobody reads, and a type named in an annotation and never imported, are found by the suite itself — until now only the public linter looked. - A test of a shell script starts the shell a probe found to run, never a bare name: on Windows the two are different programs. ### What is retracted Nothing. ### What needs recomputing Nothing has to be recomputed and nothing has to be re-imported. The coverage of a gene outside the catalogue is measured when it is asked for and is not stored, so the next `scholion genome --gene` on an alignment with `chr` names gives the measured answer. A profile whose ACMG screen was never run will see that step in `scholion recompute`; whether to run it is the person's decision. ## v0.5.9 — 27.09.2026 ### What was wrong and is now right **A failed read of the genome no longer answers «reference».** When the program that seeks into an indexed VCF failed at a position — it timed out on a large file, it could not load the index, it was missing, or the built-in index reader hit a damaged block — the position came back as having no row, and a position with no row is read as «the person carries the reference». The answer was labelled as assumed, but it was a sentence about the person made from an error. Such a position is now reported as not read, with what the reader said, and the advice to check that the index sits beside the file and matches it. A clean read that finds no row is still the reference, as before. **`scholion update` from an unpacked archive no longer offers `pip`.** A copy run from where a release archive or an sdist was unpacked — a source tree without `.git` — was treated as an installation, and the command offered `pip install --upgrade`. That updates the copy in site-packages and leaves the one that is running as it was. Such a copy now says what it is and how it is updated: a newer release unpacked in its place, or a clone updated with `git pull`. **A drug name typed in Russian goes to a translator only when nothing else found it.** The transliteration and the original spelling are looked up in RxNorm first; the translator is asked only if both found nothing — before, it was asked first, for every Cyrillic name, including the ones transliteration reaches on its own (metformin written in Cyrillic, for one). The second, undocumented translation endpoint (`translate.googleapis.com`) is gone: the only translator is MyMemory, as listed in `THREAT_MODEL.md`. **`scholion.format_system` imports on its own.** It worked only when `scholion.format` had been imported first; imported first itself, it failed with a circular import. Nothing in the package imported it that way, so no answer was affected; code built on the package could have been. **A failure is no longer printed as a fact about the person.** Every place where the program caught an error and went on in silence was read once, by hand; where the silence could reach an answer, the answer now says what could not be read. What somebody could have been told before, and is not told now: - *«Variants in the gene: 0»* — about a gene whose region the reader failed on, a file called against another genome build, or a chromosome the file does not hold. Each is now said by name, and nothing is counted. - *The reference at a position* — when the built-in reader of the index failed (index missing or damaged, file cut short), when the file's composition could not be measured, or when the kind of file could not be measured at all (it was then treated as a whole genome, opening ClinVar, ACMG and polygenic scores). - *«No coding exons»* for a gene, stored after a failed request to Ensembl. Such stored entries are asked again. - *«A published reference sample»* about a genome when the file that says whose genome it is could not be read. - *«A scan without OCR»* about a laboratory form or a study every PDF reader failed on; it was also recorded as read and never tried again. It is now reported as unreadable, with the readers' errors, and read again next time. - *No clinical threshold at all* for a value that could not be compared with one. It is now «could not be compared», and the prescription check lists it as unresolved instead of letting the verdict fall to «low». - *«Done recently»* for a monitoring test whose last date could not be read. - *No red flag on this drug*, when the prescriptions file could not be read; *no such drug*, when the drug database did not answer. Both now say so. - *The default folders and a different genome file* when the settings file that names the chosen folders and files was damaged. The program now stops with the reason (the command line exits with status 5). - *A reference range for the other sex*, when the file with sex and age sat in a chosen folder or could not be read. - *Points the person had removed from a wearable series* came back when the file of corrections could not be read; the rebuild now stops instead. Export files that could not be read are listed, instead of months averaged without them. - *A radar without its fitness domain*, and an overview without its wearable line, when the wearable data could not be read: both now say it. - *A request with a body that is not JSON* was read as an empty one by the local server — for the goal screen that meant adopting every proposal. It is refused. **The positions and rules added in 0.5.8 now carry the review that covers them.** The five positions, the carrier classes and the five «adds nothing» correction rules added in 0.5.8 were shown as reviewed on a date before some of their levels were assigned. The panel author has since approved them as a whole — sentences, levels, the corrected genes and the wording for a woman with an OTC variant — and the card shows that review, dated 27.09.2026. **The list of sources gives the locus catalogue the date it last grew** — 25.09.2026, not 18.09.2026. A positions file made by 0.5.8 stays current: nothing needs to be genotyped again. ### What is checked now that was not - Every module of the package is imported first, into an interpreter where nothing of the package is loaded yet. - The suite also runs in the tree a release archive unpacks into, without `.git`, on every push. - Two rules of the rendering are held by tests rather than comments: a value with no reference range never gets a verdict colour, and what the interaction comparison left out is printed whether or not anything was found. - Lint (pyflakes rules) runs on every push with zero findings; type errors are counted per module and may not grow. - A handler that catches every error and stays silent must say, in the code, why its silence cannot become an answer; the suite and the test runner refuse one that does not. - The package's own suite runs before publication in a copy without `.git` — the shape a release archive unpacks into — not in the repository's working tree. ### What needs recomputing Nothing is recomputed automatically. A genotype stored as «assumed reference» from an indexed VCF read before this version is correct when the reader worked; if the index was missing or damaged at the time, ask for the position again. **Run `scholion ingest-labs --force ` — if a form was reported as a scan without OCR by an earlier version.** It may have been a PDF the readers failed on; it is read again, and if it still fails the reason is printed. **Run `scholion ingest-garmin` — if a Garmin export was imported by an earlier version.** A file that could not be read was skipped without a word; the rebuild now lists such files, and the months are averaged over what was actually read. ## v0.5.8 — 25.09.2026 ### What you can do now **Five positions from the panel author's list are read.** Each carries its level of evidence, and only A and B print a conclusion. - **Gonads.** rs5934505 near FAM9B is read on the X chromosome and carries its association with testosterone in men (level B). rs1691053 in SRD5A1 is shown as a value only. Its author declined to grade its genotypes, so no ladder of genotypes is drawn for it and no verdict is printed. - **Glucose.** rs182052 in ADIPOQ is read (level C): a genotype and its source, without a conclusion. - **Behaviour.** The on-demand panel reads rs6356 in TH, Val81Met (level C), and rs34424986 in PRKN, p.Arg275Trp. The PRKN variant is recessive, and one copy is printed as carriership. **A position outside the pseudoautosomal regions of X is read by sex.** A man has one copy there, and his genotype is shown as one allele (hemizygous), not as two. A two-letter heterozygous call for a man is left unread, since it cannot be a real genotype. Without a sex in the profile, such a position is not read at all. **A carrier of a recessive amino acid gene is told what that carriership means.** - PAH, CBS and GLDC: carriership is silent. - SLC7A9: carriership may have an effect. - OTC sits on X. A likely pathogenic or pathogenic variant in a man is shown as clinically significant, with the variant named, not as carriership. In a woman it is not reduced to carriership either: following GeneReviews, the picture ranges from no symptoms to episodes of hyperammonaemia. **Two variants in one recessive gene are a result, not carriership.** With more than one heterozygous variant in a gene that needs both copies changed, the row no longer says «carrier». It says the result is clinically significant if the two lie on different copies, which short reads cannot tell; phasing, or testing the parents, settles it. **Three tests are offered under their author's condition.** They were withheld before. - **Fecal elastase:** when albumin or total protein is low, or when vitamin D is low together with essential amino acids, and it has never been measured. - **Methionine load:** when homocysteine is high while folate and B12 are not low. - **ADMA/SDMA:** when Lp(a), apoB or LDL is high, or arginine or citrulline is low, and they have never been measured. **Tryptophan to the large neutral amino acids (Trp:LNAA) is computed** from a draw that holds all six components. It is a value; no range is carried for it. **«If a decision to correct has been made» says outright, in five more situations, that carriership changes nothing.** Each statement has its source, and in each one heterozygous carriership is no reason for a different route: - high phenylalanine in a PAH carrier: restricting phenylalanine and aspartame is for confirmed PAH deficiency; - citrulline on the medication list of an ASS1 or ASL carrier: the allele does not predict the response, the measured concentrations do; - low tryptophan in an SLC6A19 carrier: nicotinamide is not a preventive measure for a carrier. This one is level C, so it is printed as «adds nothing» with the reason; - creatine on the medication list of a GATM carrier: there is no evidence of a higher need; - collagen or gelatin on the medication list of a carrier of a primary hyperoxaluria gene: there is no evidence they must be excluded. ### What is fixed **Two genes on the panel author's list were wrong.** - rs6356 was listed under the wrong gene; it is in TH. - rs3816873 was listed as an MC4R variant. It is in MTTP, so it is not taken into the panel it was proposed for. A SERPINE1 entry is an insertion, which this build cannot yet read from a genome file, so it stays waiting rather than being read as a single letter. ### A series break On unchanged input: - a man's genotype at an X position outside the pseudoautosomal regions is shown as one allele; - a recessive gene with two heterozygous variants is no longer shown as carriership; - the three tests above may now be offered. ### What is retracted Nothing. ### What needs recomputing **Run `scholion genotype-sites` — if a sites file was made by an earlier version and an alignment is at hand.** The five positions added in this version are not in a sites file made before it. Until they are read from the alignment, they are shown as not read. They are never shown as the reference. ## v0.5.7 — 22.09.2026 ### What you can do now Nothing new to learn: this release corrects answers. Every item below is a wrong answer somebody could have received from 0.5.6, and what they receive now. ### What is fixed **Laboratory forms** **A flag or a unit printed against the result cut its decimals off.** «6,5H» and «6,5ммоль/л» were stored as 6.0. The whole number is read now. **A result with a space as the thousands mark was cut at the space.** A ferritin of «1 250,0» was stored as 1. The two readings are told apart by the corridor printed on the same row (or the marker's physiological range); with neither, the row is left unread rather than guessed. **A digit of the analyte's name was taken for its result.** «ИФР-1» and «пиридоксаль-5-фосфат» gave 1 and 5. A number joined by a hyphen to a word is part of the name. **A longer analyte was stored as the shorter one it contains.** Prealbumin and microalbumin were read as albumin, macroprolactin as prolactin, non-HDL cholesterol as HDL, latent iron-binding capacity as total, folate in red cells as serum folate — each a different number filed under a marker it is not. **The unit printed on the row was ignored for most markers.** «Glucose 95 mg/dL» became 95 mmol/L, a catastrophic reading; an HbA1c of 48 mmol/mol became 48 %. The row's unit is now converted, value and corridor together — by the formula where the unit needs one — and a unit the marker cannot take (Lp(a) in mg/dL) leaves the row unread. **A one-sided corridor on the result row was not read.** «< 5,2», «до 5,2», «> 1,0» and «более 1,0» — the commonest layout for lipids and liver enzymes — arrived with no corridor at all, so a total cholesterol of 6.1 against a printed ceiling of 5.2 was not flagged. **A corridor the form did not print was stored as if it had.** It was filled in from the dictionary and then judged as the form's own, skipping the checks made before a reference range is lent — whose sex and which age band it belongs to. A woman's point could be judged by a man's ceiling with nothing on screen saying so. Such a point now carries no corridor of its own, and the reference is lent, labelled, or withheld with its reason. **The reconciliation reported every point as missing, and the provenance audit called every point hand-entered,** once points began to carry the day and the clock time of the draw. Both now match a draw at the resolution each side has. **Lab points written at the same moment could be lost.** Two writes from the page could each read the history, add their own point and write back the other's loss. Writes of a lab point are now serialised. **A local marker file that could not be read was overwritten.** The next proposal replaced it with an empty file holding one entry, and every marker, unit and rule the person had confirmed was gone. A write over such a file is now refused and the file is kept. **Pharmacogenetics and prescriptions** **An unread second gene disappeared from a two-gene drug.** With TPMT normal and NUDT15 never read, azathioprine was answered as a normal-dose drug. The unread gene is now named, and the answer does not read as reassuring until it is read. **A called phenotype the engine had no code for was answered as normal.** A caller's «Indeterminate», and SLCO1B1's «Poor Function», fell through to «nothing notable in the markers». «Indeterminate» is now unknown; SLCO1B1's function phenotypes reach the statin table; any other phrase is quoted and not taken for normal. **A genotype that was not a reading of the counted allele was counted as zero copies.** A strand-ambiguous chip call, a genotype written on the other strand, and a person carrying the other alternative allele of a multi-allelic site all produced «normal». Each is now unread. **A drug name was matched inside other words.** Nystatin was given the statin guideline, «Антигриппин» the proton pump inhibitor one, and heparin the omega-3 dose evidence. Names are now matched as whole words; a dose, a brand word or a Russian case ending still matches. **A non-normal phenotype with no guideline row read as low.** A CYP2C19 poor metaboliser asking about pantoprazole, and a CYP2C9 poor metaboliser asking about celecoxib, were told «low». Every gene the guideline database links to a drug now reaches the prescription verdict: a non-normal phenotype without a row, and an actionable gene that could not be read (HLA among them), are named and keep the verdict off green. **Genome files** **A half call («./0») was read as a call**, and a reference block with one allele unread as a confirmed reference. Both are now «no call». **A missing row on an exome was presumed to be the reference.** Outside an exome's targets nobody sequenced the position. The presumption is now made only on a file whose breadth shows it is a whole genome. **A chromosome the file does not hold was read as the reference at every locus on it** — a file without chrY, or one that names the mitochondrion `chrM` where the catalogue says `MT`. Both names are tried, and a contig the file does not declare is said to be absent. **A multi-sample file read in container form took the first sample as the person.** It now needs the sample named, as a multi-sample VCF already did. **A gene region was asked of a file in another build.** GRCh38 coordinates on a GRCh37 file returned the variants of some other stretch — usually none, printed as «0 variants in the gene». The region is now refused with the reason. **The page and the local server** **Another server on the same machine could write to the profile.** The origin check compared the host name and not the port, so a page served on `localhost:3000` could add a medication. The whole origin is compared now, and a browser's cross-site fetch is refused by its own fetch metadata as well. The drug, prescription and genome questions — which reach outside services on the caller's behalf — are gated the same way. **The prescription card never showed its verdict or the genetic context of the decision** — both were computed and left out. They are shown now. **The ACMG class chips did nothing when clicked, a ClinVar finding opened an empty gene card, and choosing a genome file also opened a folder dialog.** Each now does the one thing it says. **An error answer was drawn as if it were data**, and the state of a recompute was cached by the page. Neither happens now. **Other doors** **Under the Ouroboros Hub, `recompute` started work that could never finish.** The Hub runs every call in a fresh process, and a rebuild started on a thread died with the call. There it now shows the plan and names the command to run in a terminal. **A package registry that did not answer was asked again on every call**, each call paying the timeout. The failure is remembered for an hour. **A long-running process that started offline never mentioned a newer build.** The note was closed for the process before anything was said. **Two recomputes could run at once**, one from the page and one from a terminal, and a run that did not start left the job marked as running until the server restarted. The job is claimed atomically now, and always ends in a final state. **The MCP server stopped on one deeply nested message**, ending the session for every later call. It answers with a parse error and carries on. **A refused value exited with success.** `add-lab`, `add-med`, `remove-med`, `add-metric`, `target` and `lab-draw` printed the refusal and returned 0, so a script could not tell which values never arrived. They return 1 now. **The public package could carry files git ignores** — a spreadsheet, a log or an `.env` left beside the code. What git will not track is no longer copied. ### A series break Laboratory forms read with this version give different values on unchanged input wherever one of the defects above applied: decimals and thousands are read whole, a row's unit is converted, a one-sided corridor is kept, a prefixed analyte no longer lands on its stem, and a point no longer carries a corridor its form did not print. A value stored by an earlier version and the same form read again can differ; they should not be charted together without re-reading. ### What is retracted Nothing. ### What needs recomputing **Run `scholion ingest-labs --force ` — if forms were ingested by an earlier version.** Values, units and corridors are read again under the corrected rules; points of the same draw are replaced, not doubled. ## v0.5.6 — 22.09.2026 ### What you can do now Nothing new: this build is 0.5.5 with one test corrected. ### What is fixed **The test suite went red on Windows for a reason that had nothing to do with the product.** One test runs a shell script of the genome pipeline with its external programs stubbed by small shell scripts. Windows carries `bash` on its PATH and still cannot run such a stub, so on that system the four branches of the test failed before the script under test was reached — while every other test passed there. The test now skips where a stub is not a program. Nothing that runs for a person changed: `scholion version` reports 0.5.6, and every answer is the one 0.5.5 gave. ### What is retracted Nothing. ### What needs recomputing Nothing. ## v0.5.5 — 21.09.2026 ### What you can do now **The genes a «nothing found» cannot rest on can be handed to a laboratory in one step.** `scholion limits --bed` prints them as a BED — worst gene first, each interval carrying its percentage — `--out` writes the file, and `--panel` narrows it to one panel (ACMG, pharmacogenetics, HLA). The genome tab offers the same file under the screen, and an assistant can ask for it. The list existed before; no command, page or tool reached it. **The amino acid panel reads by pathway.** Branched-chain amino acids, the urea cycle, methionine metabolism, phenylalanine → tyrosine and the sulphur amino acids each show as a group, with how many of its markers were measured. A marker stands in every group it belongs to — methionine in both its own cycle and the sulphur chain. A group counts nothing: the panel's term in the score is what it was. **Five markers are shown as values only.** β-alanine, β-aminoisobutyric acid, aspartate, asparagine and pipecolic acid are printed with their values, say so on the line, and no longer enter the panel's share outside its corridor. **A marker names the ones to read beside it.** Under histidine when it is low, under cystathionine when it is high, under phenylalanine always, and so on for ten markers: the companions are listed with their values, or as not measured. Nothing more is said about what they mean together. **Hydroxyproline/proline is shown**, computed from a draw that holds both. No range is carried for it, so it is a value, not a deviation. ### What is fixed **Forms from one laboratory lost their dates.** Laboratory forms that print the draw as «Дата/время забора» had their markers found and then dropped, because no date was recognised. Studies that print their date and kind as one header line — a two-digit year, the clock time and the kind in capitals — were stored without a date and mostly without a kind. Both are read now. Where a study also prints when the examination was done, that is its date rather than the time it was written up; a birth date, the print time in a footer and a date inside a recommendation are never taken for it, and a two-digit year later than this one is refused rather than read as last century. **Three answers sent a person to a script the package does not carry.** When coverage had never been measured, or had been measured before the intervals were recorded, the answer said to run a shell script of the source tree — which a `pip install` does not have. It names `scholion coverage` now, which has done the same measurement from the package for some time. **A proposed marker name belongs to the profile it was proposed for.** It was written beside the data root — in a source checkout, the checkout itself — and shared by every profile the same installation was pointed at. It now lives in the profile. A proposal an earlier version wrote is read until the next one, and moves with it. **The journal's yes/no factors are the ones the person declared.** `scholion focus-log --factor NAME` records any yes/no field `profile/focus.json` declares for the journal, the page draws those fields, and the assistant's tool takes `factors`. The earlier flag named after one drug still works and is no longer listed. The journal analysis names the factor it separates instead of a drug. **Three tests are no longer offered to everyone with an amino acid panel.** Faecal elastase, the methionine load and the dimethylarginines were suggested whenever the panel was measured and they were not. They are held until the conditions that select the people they are for are written; urine urea nitrogen stays. **Under the Ouroboros Hub, `update` no longer installs into the host's Python.** The Hub runs a skill in a child of the host's interpreter, so the route «pip in the running interpreter» would have changed the host's environment rather than the skill's own. Under the Hub the answer now names the host, whose catalogue brings the newer build. Through the classic tools module, the MCP server, the page and `scholion update` on the command line, updating works as before. **The note about a newer build no longer ends every answer on a host that starts a process per call.** «Once» was kept in the memory of a process. It is now remembered beside the registry's answer, so it is said once a day for a given release, on every door. **The Hub manifest says what the package does.** It declares `subprocess` — the package starts `bcftools`, `pdftotext`, `pdftoppm` and `tesseract` when they are installed — and counts the once-a-day question to PyPI among what goes out. Both corrections came from the Hub's maintainer; a test now holds the first, so a later manifest cannot drop it. **No position of an authored list leaves it without a word.** Every position a curated list names is now either in the catalogue and on a panel, or waiting, with the reason and the work that closes it. The first check of the list of 29 June 2026 found twelve positions absent with no reason recorded anywhere, and thirteen covered only by a note about their whole class; each now carries its own. It also found one reason that had outlived its position — the position had arrived later — and removed it. **The ClinVar annotation script is run by the tests in every branch.** The branch for a genome with `chr`-named chromosomes stopped once on a variable that was never set; each branch is now executed end to end, with the external programs replaced by stand-ins. **A reviewed sentence names a role, never a person or a clinic.** This was already how a review was recorded; it is now a rule over everything that ships, with a test that fails the build when a clinician or a clinic is named. ### What needs recomputing **Run `scholion ingest-labs --force ` — if the folder holds forms that print the draw as «Дата/время забора».** Their markers were found and dropped for want of a date; they are stored with it now. **Run `scholion ingest-studies ` — if the folder holds studies whose header is one line of date, time and kind.** They were stored without a date and mostly without a kind. ## v0.5.4 — 17.09.2026 ### What you can do now **An assistant can run the recompute, not only describe it.** A new tool shows the plan by default and starts the ready steps when asked to with a confirmation, the same way the page does; the progress is a file, so nothing is lost if the call ends first. A system's answer also carries, as a field rather than prose, why rows were not read and what closes each reason. **The reports say how a command is typed on this machine.** Where the word `scholion` is not on the PATH, `version` and `recompute` add one line with the spelling that works here. **A release records the size of the catalogue it ships.** A catalogue that grew without the release notes naming the step that re-reads the new positions now stops the publication, instead of being noticed on somebody's profile. **The panel of authored positions grew from 120 to 214, in three kinds of row.** Thirty-four positions rest on a named study whose reference was checked against Europe PMC — every one of them carries the product's own sentence in both languages, a level from B to D by how well the source replicates, and the source itself. Twenty-two rare variants of immunity, the pancreas and the kidney are read through the gene–disease base: the classification and the inheritance come from GenCC, the sentence says what one copy and two copies mean, and a carrier is never printed as a finding. Twenty-nine positions the list named without any source are shipped at level E: the genotype is shown, and the row says that nothing stands behind it. One source on an older row pointed at a paper about another subject and was replaced by the paper about the variant. **Fifteen groups of positions print as one row each.** Linked variants of one locus — the FTO block, TCF7L2, the deiodinases, the estrogen and progesterone receptors, SHBG, the rare variants of SECISBP2 and TSHR — say one thing, and the card said it three or five times. A group prints its name, its count, one sentence, one level and one source, and opens into its members. **Two panels without a radar domain.** Behaviour and the stress response — seventeen candidate positions of neurotransmission, most at level C or D — open as a card of their own, with the reason at the top: there is no laboratory marker a behaviour is read by, and an empty half on the radar would teach a reader that an empty half means a clean one. The dental panel is declared and waits for its positions. **«The genetics of this marker».** A laboratory marker with positions behind it says so under its own row — gene, rsID, level, the direction the panel expects — and the row is a door to the system card, where the person's own state is. **A clinician's local note.** A note kept in the profile prints on the row it is about, in its own voice, and never reaches the package. **A system can now answer the question that comes after a deviation: if a decision to correct has been made, what does the genotype say about the route.** The amino acid system carries it first. Routes are grouped by what the genotype says — this one goes around your narrow place, this one probably works worse for you, this one makes the genotype beside the point, this one needs a different dose, this one the genotype says nothing about — and never ranked, because a ranking would mean an optimum, which needs cost, availability, the clinical picture and your own preference. A route is printed only at evidence level A or B, only with a named source, only in the conditional, and only with the other half of the answer: which marker to recheck, after how long, and what counts as a change rather than the scatter of the method. Where nothing reaches that bar, the product says the genotype adds nothing here, with the reason, instead of saying nothing at all. **Every position of the amino acid system says which link of the chain it sits on** — digestion and absorption, carriage into tissues, use inside the cell, renal losses — **and how it reads on the three routes protein can arrive by.** A drip removes the gut and the first pass through the liver; free amino acids remove the digestion of protein and the peptide path. So the same variant matters on one route and is beside the point on another, and the card says which, marking what is a mechanistic reading rather than a measured comparison. **Where a fasting corridor cannot answer, the position names the load that would.** Carriership of the transsulfuration is invisible at rest; the methionine load is what shows it. The same for the urea cycle under a protein load and for urate under a purine load. The product names the test and what it would reveal, and never says to do it. **The second half of the amino acid picture is now asked for.** Urine amino acids, cystine in a 24-hour collection, plasma ammonia, kynurenine with its ratio, vitamin B6 and fecal elastase appear in «what else to take» while they are missing, and disappear the day they are taken. **The genetic half of a system is read as a table.** One line per position or gene — level, state, chain link, what it means — grouped by link where the system has links. A line opens into the card it used to be, so the density is the reader's choice and not the page's. **Every statement about a genetic position now says what stands behind it.** A position on a system's card carries a level from A to E: A is a clinical guideline or a gene–disease link GenCC rates Definitive or Strong, B a replicated association with a measured effect size, C single or conflicting studies, D a candidate gene whose association was never replicated, E no source at all. A conclusion is printed at A and B only. Below them the card shows your genotype and says why nothing follows. The same letter appears on the page, in the command line and in what an assistant is told to relay. `scholion evidence-levels` prints the legend, and so does the Guide page. A position with no level assigned yet says so and draws no conclusion. **Three new body systems: immunity, the musculoskeletal system, and amino acids and protein.** The radar now has fifteen laboratory systems. Immunity is read through white cells, lymphocytes, IgA, ESR, C-reactive protein and two autoantibody screens. The musculoskeletal system is read through calcium, phosphorus, alkaline phosphatase, parathyroid hormone, osteocalcin and vitamin D. Amino acids and protein are scored on seven markers: total protein, albumin, urea, creatinine, uric acid, homocysteine and citrulline. Beside them the card shows the amino acid panel and its ratios. The share of the panel outside its corridor counts in the score as one term, so a drifting panel shows at the top level. A marker can now count in two systems, for example C-reactive protein in inflammation and in immunity. Each new system has a genetic half composed from the gene–disease base: 75, 366 and 97 genes. In front of that base stand 28 authored positions: seven for immunity, six for bone, twelve for amino acids (glycine, branched-chain amino acids, asparagine, uric acid, homocysteine and classic homocystinuria among them), and three more for the gonads. The panel now holds 120 positions across fourteen systems. **The gene–disease base is refreshed to the export of 13 September 2026.** The kidneys gain one gene. The systems now hold 1,469 different genes. **A common variant shows its three genotypes, with yours marked.** The card lists them from the base genotype to the one with two copies of the named allele. **An amino acid panel is read as a picture.** Nine ratios have their own values: phenylalanine/tyrosine, glycine/serine, the Fischer ratio, glutamine/glutamate (and glutamate/glutamine, as some laboratories print it), methionine/homocysteine, α-aminobutyrate/leucine, kynurenine/tryptophan and urea/creatinine. A ratio a laboratory prints is read and checked against the value its components give. No reference range was invented for them: until a source gives one, a ratio is shown as a value. ### What is fixed **A position written on the other strand could read as «the allele is absent».** When the named letter was not one of the two at the position, the genotype was searched for it, it was never there, and the card said the allele was not found. Such a position is now refused before any genotype is read. **The recompute panel took over the screen after the recompute was done.** It reappeared at full height on every reload, showing the report of a run the person had just watched. A finished run is no longer a reason to open it: what is ready to recompute is one line with the panel one click away, and a line dismissed stays dismissed until something new is ready. **A ratio refused to compute when one of its components had been measured again later.** Urea/creatinine — the only ratio of the amino acid panel with a published corridor — and methionine/homocysteine were both dropped although their components were drawn together: the engine compared each marker's newest point instead of looking for the last draw that held them all. Two values drawn months apart still do not make a ratio; a draw that exists is now found. **Two lines of one card contradicted each other**: the panel said it does not enter the score while the line above it said the share outside the corridor enters as one term. The panel is not scored marker by marker — that is what it says now. **A gene whose coverage was never measured was called «not read», which is false about the file.** The file did read the gene; what nobody measured is how deeply. That is now a state of its own — read, with the depth unmeasured — and the row says what the file holds in the gene, with the alignment named as what measures the rest. A coverage table that WAS made and found the gene thin is a different answer and still counts as not read. **A position a whole-genome file has no row for is answered, not left blank.** Such a file omits a position BECAUSE it matched the reference, so the answer is almost certainly there. With an alignment on the machine the position says it is not read yet and names the step that reads it. With none, it is taken by the reference — and the caveat stands inside the sentence, not beside it: «the risk allele is absent — taken by the reference: the file has no row here, and no alignment was checked». That is never allowed on a chip, a genotype table or a panel, where a missing row means the position is not on the chip; never where a coverage table calls the gene low; never for a position outside the catalogue. It is not a finding, it is counted apart from what was read, and it never lets a list be called read end to end. The other side of the same question: a position whose row IS in the file is read — and where the row states no depth and no alignment is on the machine to measure one, it says so: read from the file, the depth of reading not verified. **«Not read end to end» did not say what was in the way.** A reader who had just run every recompute the product offered was told that 28 rows of 373 were unread and left to guess the next step. The count now comes with the reason and with what closes it: read too thinly — deeper sequencing; no row in the genome file — genotyping those positions from the alignment; the depth never measured — a coverage table. The sentence no longer lists a dozen gene names or explains what «nothing found» means over an unread gene: the names are on the rows. **A system card could take three quarters of a minute to open, and could show a position as unread on a machine with no network.** A position whose coordinate the build did not carry was looked up online at every opening, once per position. Every position a panel ships now carries its coordinate in the build, confirmed against two independent sources, and a card opens without reaching the network at all. **A rare variant read off a genotyping chip could be printed as a finding.** Chips often report rare variants that are not there. Such a variant is now shown as a signal to confirm by sequencing, not as a finding. **A disease class could read as «every gene read, nothing found» with no coverage measured.** Without a coverage table, no gene now counts as read, and the answer says so gene by gene. **A ratio printed with full names was read as one of its components.** «Phenylalanine (Phe)/Tyrosine (Tyr) 1.13» was stored as tyrosine 1.13, a value far below its range, and ratios printed as «Phe:Tyr», «AABA:Leu» or «Glu:Gln» were not read at all. **A printed albumin/globulin ratio was never checked against its components**, and could be flagged as having nothing behind it. **The warning about calcium on high-dose vitamin D showed calcium as not taken** even when it had been measured. ### What needs recomputing **Run `scholion ingest-labs --force ` — if the profile holds a urine or an amino acid panel this version added markers for.** Ammonia, fecal elastase, the dimethylarginines, 3-methylhistidine, urea nitrogen in urine and six urine amino acids are new keys: a form that holds them was stored without them. **Run `scholion genotype-sites` — if a sites file was made by an earlier version and an alignment is at hand.** The positions added in this version are not in a sites file made before it, so a position the raw genome file has no row for is shown as not read rather than as the reference. Reading those positions from the alignment settles what is there. Without an alignment the step says so and is not offered as ready: a position stays honestly not read, which is what it is. **Run `scholion ingest-labs --force ` — if an amino acid panel whose form prints ratios was ingested by an earlier version.** A ratio line could have been stored as one of its components: tyrosine, glutamine and the other single values are read again from their own lines, and the printed ratios get values of their own. **Run `scholion coverage` — if a coverage table was computed by an earlier version.** The three new systems add 372 genes that a coverage table made before this version does not hold. Until the table is computed again, they are shown as not measured, never as read. **Run `scholion provenance` — if the profile holds an albumin/globulin ratio or an amino acid panel.** The printed ratios, and the albumin/globulin ratio that was never recomputed before, are checked against the values their components give. ## v0.5.3 — 16.09.2026 ### What you can do now **The tools and the instruction that governs them now travel as one package.** Scholion is packaged in the portable plugin format agreed in August 2026 — a manifest, the skill folder, and the description of the local tool server — which a client reads in a single import instead of three separate steps: install the package, register the server by hand, copy the skill folder beside it. The convenience is the smaller half. A model that reaches these tools through a bare server is handed a list of functions and no instruction with them, which is why the safety canon has to be a tool of its own; in this package the instruction sits in the same folder as the functions and cannot be installed without them. The launcher installs nothing: where the engine is absent it refuses and names the one command that installs it, because software that quietly put itself on a machine in order to read somebody's genome would be the wrong kind of quiet. It starts on the machine that holds the data and nowhere else — a client that cannot run a local process does not get this door, and that is the correct answer rather than a limitation. The launcher also looks where a per-user installation puts the `scholion` command. A desktop application started from the Dock does not get a terminal's search path, and without that look it would have refused on a machine where the engine was installed. **The tool server speaks the 2026 revision of the protocol and hands back structures.** `scholion mcp` now answers clients of the July 2026 revision, which has no handshake: a client can ask the server which revisions it speaks, and a revision the server does not know gets an error listing the ones it does. Clients that open with the older handshake are served as before, under the revision they ask for. From the June 2025 revision on, thirteen tools also return their answer as a structure, the same one their command prints with `--json`: overview, labs, suggested tests, second opinion, radar, metrics, goal, ClinVar, ACMG, polygenic scores, longevity, drug check and prescription check. An assistant no longer has to parse the report back into numbers. The rendered report stays in the answer, and a copy of it sits inside the structure, because it carries the qualifications an assistant has to pass on, and some clients show a model only the structure. `scholion capabilities --json` lists the revisions under `access.doors.mcp.protocols`. ### What is fixed **The catalogue entry in the plugin hub advertised an older build.** The row a person reads before installing said version 0.4.7 and thirty tools, while the package on the index had moved seven releases past it and registered thirty-four. That row is a shipped page like any other, and nothing compared its numbers with the build. The copy kept beside the source had also lost the field that declares which plugin contract the skill speaks, so publishing it would have dropped that field from the row as well. The numbers and the required fields of the entry are now read from the build and refused when they disagree, and the version the row offers is checked against the releases that are actually on the index — a row may name an older release, never one nobody can install. ### What needs recomputing Nothing has to be recomputed and nothing has to be re-imported. What changed is what the catalogue says about this skill, not anything the skill computes: a host installing it already received the tools of the published package, because the entry installs that package from the index instead of carrying its own copy. `scholion version` names the build you are on. ## v0.5.2 — 14.09.2026 ### What you can do now **An assistant tells a person that a newer version is out, and installs it on their word.** Somebody who uses Scholion through Claude or another assistant never sees the local page, so a newer version went unnoticed. `scholion update` says whether a newer build is out and how it installs in this environment — pip, pipx or uv as it was installed, or `git pull` for a source checkout — asking the package registry at most once a day, sending only the package's name, and never with `SCHOLION_OFFLINE=1`; `scholion update --yes` installs it. Through the tool interface the same is `sch_version` and `sch_update`: the first tool answer of a session mentions a newer build once, and `sch_update` installs nothing without `confirm=true`, which is reserved for the person's own yes. The skill's instruction starts every session with `scholion update`. **The menu checks for an update and opens the recompute.** The ☰ menu beside the language switch now carries «Check for an update», which asks the package registry on request and, when a newer version is out, prints the command that installs it, and «Recompute after an update», which opens the list of steps the releases since your data's version ask for. Both used to live only inside the note shown right after an upgrade, so once that note was dismissed there was no way back to either. The same answers come from `scholion version` and `scholion recompute`, and an update itself is `pip install --upgrade scholion` followed by a restart. ### What is fixed The engine's answers are the same; what changed is how they are shown. **The body figure and the radar no longer wait for the genetic rings.** On the Overview the figure appeared seconds after the rest of the page — about seven seconds on a server that had just started — because it waited for the listing of the systems' genetic halves, the slowest answer of the page, and asked for it only after the person's block was drawn. The figure and the radar are drawn at once now, a bar beside them shows the rings loading, and the rings are drawn in when they arrive. **A pharmacogenetic phenotype read from a diplotype in the profile is shown in the reader's language.** A star-allele call carried by the profile — from PyPGx, PharmCAT or a laboratory report — printed its phenotype as the English CPIC phrase («Intermediate Metabolizer», «Normal Function») on a Russian page. It is worded from the phenotype now, and a «likely» or «possible» qualifier is kept. **A ClinVar drug-response record says what kind of response it is.** A record named like «warfarin response - Dosage» reads «warfarin: response to the drug — dosage» in the reader's language; the drug keeps ClinVar's spelling, and disease names are still printed as ClinVar gives them rather than translated without a source. **A month without a day no longer reads wrongly after a preposition.** Dates that carry only a month are abbreviated like full dates, and a system's movement reads «was 80 (Nov 2025)». ### What needs recomputing Nothing. ## v0.5.1 — 13.09.2026 ### What you can do now **An update can now run what it asks, and shows how far it is.** `scholion recompute` lists every step the releases since your data's version ask for, together with the genome steps your data lacks, and says for each one whether it will run, what it still needs — a folder of forms not named, an alignment not found — or that it is yours to do by hand. `scholion recompute --yes` runs the ready steps one after another and prints the step, the item being read, the time spent and an estimate of the time left; `--status` shows the same from another terminal, and `--stop` stops after the item being read. Files a step rewrites are copied into the archive first, and the version is recorded as done only when nothing is left for you. The local web page offers the same from the update note, keeps the progress through a reload, and on the radar a panel with positions not read for this reason offers the step where the gap is seen. **The catalogue positions can be genotyped from the alignment without the source tree.** `scholion genotype-sites` reads every catalogue position from your BAM with `bcftools`, one chromosome at a time with progress, in the build of the alignment, and writes the file that lets a position missing from the VCF read as the reference. It records beside the file which catalogue it answers for, so a later release that grows the catalogue can tell that the file is behind. Until now this step needed two scripts that only the source tree carries. **Every file in a profile now records the build that wrote it.** `scholion version` names the files a later release asks to rebuild — laboratory results written by an older build before a release asked to re-read the forms — with the command to run. Files written before this release carry no such record; they are counted as such, and `scholion version --since` lists what to rebuild from a version you name. **A skill copied into a skills folder can say which build it came from.** `scholion skill --install` copies the entry into `~/.agents/skills/scholion/`, or a folder you name, records the build beside it and says what it replaced; `scholion selfcheck` reports a copy that differs from the installed build as an error and exits with a non-zero status until the copy is replaced. **An update now says what it asks of the data you already have.** `scholion version` names this build and its age, the version your data was last used with, and — for every release in between — what that release asks: a command to run and when it applies, or a step to take by hand. The same note stands under the header of the local web page until you press «Understood» (`scholion version --seen` on the command line). A profile that never recorded its version says so, and `scholion version --since 0.4.8` lists everything after a version you name. `scholion version --check`, or the button beside the note, asks PyPI whether a newer version exists — one request, only when you ask, refused when the network is switched off. The README has a new section, «Updating», with the command for each way of installing. **The panel of a radar segment is one picture of the system's genotype, and it says it was checked whole.** Each panel opens with the genotype of the system, assembled from the panel's own sentences and nothing else: in which positions the named allele was found and what the panel says of each, which alleles are not carried, which positions were not read. Every position is then a card by gene — found ones bright, the rest muted with the sentence that says what «not found» means for that one — until now the patient's view showed two rows of a seven-position panel and nothing about the other five. The polygenic scores mapped to the system fold into one line and open by a click. «Genotype against the measurements» puts, for every found position that names a marker, the expected direction beside the measured value with its place in the corridor and the number of the question it raises; whether they agree stays that question. Where no found position predicts a measured marker, the block says so: the panel's genotype does not account for those measurements and their cause lies outside it — and the expectations the panel holds for alleles not carried are listed as what did not apply, so the check is seen to have happened. Positions a guideline weighs when a drug is chosen are named last, as what they are: not a prescription, and no statement that a drug is taken. The same view is on the system's own card, both for the patient and for the clinician, and the command line prints the same conclusion. **The panel itself, with its studies, on a page of its own for the clinician.** Every radar block with a panel, and the system's card, link to a description of that panel as the catalogue holds it: each position with its locus and named allele, the sentence it prints for one copy and for two, the kind of link, the guideline or study it rests on with the effect size, what it expects of a marker, the gene–disease classification where there is one, and who signed the sentence and when. It reads no genome and no laboratory result, so it is the same page for anybody; the reading against a person's genome stays on the radar. `scholion panel ` prints the same, and `scholion panel` lists the panels. **One design across the application.** Every page is built the same way now: a title, the question the page answers, a strip of the few numbers worth seeing first, the sections, the details folded, and the sources in one line at the bottom. There are six tabs — Overview, Radar, Labs, Genome, Medicines, Lifestyle — with the Guide and the Assistant in a menu beside the language switch. Medicines joins the list of prescriptions and the check of a new drug, which used to be two tabs under two names. The body figure and the radar open both the Overview and the Radar; the Radar adds an index of the systems, and each system's block leads with a one-sentence verdict, the measurements as a table, the genotype as findings with whether it agrees with the measurements, and the questions for the doctor; every position of the panel, the scores and the notes are under «More». Labs shows what is out of range first and folds the values in range. Dates read «3 Sep 2026» instead of «2026-09-03T08:22», ClinVar significance is shown in the reader's language, long notes are cut at three lines until clicked, and a finding without a usable rsID is named by its position. The Russian interface addresses the reader formally throughout. On a phone the header takes one line, the tabs scroll sideways, the disclaimer sits in the footer instead of covering the tabs, and the page no longer scrolls sideways. A prescription that could not be removed now says so instead of doing nothing, a test's priority is shown in words instead of an English code, and the count of unread target genes agrees with its number. **The radar shows its head at once and fills the systems in as they arrive.** The tab used to wait for everything before showing anything. The head of the tab now appears as soon as the quick answers are back, well under a second; the blocks by system, which are the slow part, fill in under their own loader. The server also reads every system's card once right after it starts, on a thread of its own, so the first page of a session finds the genome folder, the catalogue positions and the coverage table already read. The two ways out of a block — the system's card, and the panel with its studies for the clinician — are labelled buttons with a hint each, no longer two blue links run together. **Switching tabs no longer fetches every answer again.** What a tab has fetched is kept for the session: coming back to the radar takes a tenth of a second instead of seconds. Every write made from the page, a finished recompute or ClinVar refresh, and a change of language forget what was kept; the answers that describe a running job are never kept. **A block of the lifestyle brief due for a review opens what changed.** The flag «needs a review» is a button: for every block due it shows, per watched marker, the value at the review date, every measurement since, where each stands against its range and whether that position moved — beside «the wording still holds» and a request for the assistant built from the same facts, carrying the block's live tokens. The flag on the Assistant page, in the menu, goes there. `scholion brief-review [block]` prints the same. **The coverage of the genes the panels read can be measured from the package.** `scholion coverage` reads, with `samtools`, how well the alignment covered every gene of the ACMG list, the CPIC genes and the composed genetic half of each body system, and writes the table the cards judge coverage by. Until now that step was a shell script of the source tree, and its table held 93 genes while the systems read 1203 — so most genes were honestly reported as not read, with nothing a pip install could do about it. `scholion recompute` plans this step when the table is missing or covers fewer genes than the panels read. **A loading view shows the project's helix and how much has arrived.** Every view that loads shows the DNA mark turning above a bar that fills as the view's requests come back, with a count of what has arrived; the motion stops under the system's reduced-motion setting. **Every segment of the radar has a panel of its own: the positions a clinician acts on, read on the genome and set beside the segment's laboratory markers.** Ninety-two authored positions across twelve systems, each of one of three kinds and saying which: a position that changes a prescription by a guideline (the statin, thiopurine, NSAID, warfarin, clopidogrel and allopurinol genes), a position that explains a laboratory marker with a measured effect from a named study (PCSK9 and LDL-C, the LPA positions and lipoprotein(a), PNPLA3 and liver fat, GC and vitamin D, FUT2 and B12, TMPRSS6 and ferritin, ABCG2 and urate, SHBG and testosterone, DIO1 and the T3/T4 ratio), and a position people ask about that decides nothing, printed with that note. Each row prints the person's genotype with its depth, the sentence written for that state, and — where the position bears on a marker of the segment — a question comparing the expected direction with the last value; when the marker was never taken, the positions waiting on it are named inside one question rather than asking for the same test once per position. The panel follows the verdict on the segment's card and in each block of the radar page; the broad disease list from the base stands after it as the background. The clinician's blank for the thyroid is in, sorted by kind rather than copied as a list. The locus catalogue grew from 61 to 113 positions, every new one verified against two independent sources; two whose second-build position the sources disagree on are held without it and say so. **A gene short reads cannot read is named as such, never as clear.** The 21-hydroxylase gene beside its pseudogene, the androgen-receptor repeat and the Gilbert promoter repeat are printed as «requires a separate method», with the method named, and are never counted as read; a «nothing found» over them would have been a statement about the method. **The radar page shows panels, not scores.** Polygenic scores stay on the genome tab; on the radar each system's block opens with its verdict and panel, and the scores mapped to it are folded under «More». **The radar has a thirteenth segment, «Heart and vessels».** Until now the body was laid out as eleven laboratory panels and one wearables segment, and the heart had no panel of its own — so the pharmacogenetics that matter most in a cardiology prescription (warfarin with VKORC1, CYP2C9 and CYP4F2; clopidogrel with CYP2C19; the beta-blockers; the anticoagulants and antiplatelets) and the cardiovascular polygenic scores had no card to stand on. The new segment is built from the five markers a laboratory issues beside the lipid panel rather than inside it — lipoprotein(a), apolipoprotein B, apolipoprotein A1, fibrinogen and D-dimer — and answers like every other system: `scholion system cardio` prints the laboratory now and its movement, a genetic half of 258 genes composed from the Gene Curation Coalition export (cardiomyopathies, channelopathies and inherited arrhythmias, aortic aneurysm and dissection, monogenic thrombophilia and hypertension, the monogenic forms of coronary disease and of cerebral small-vessel stroke; pulmonary arterial and portal hypertension are excluded by name because they belong to the lungs and the liver), the ten cardiovascular scores, the prescriptions acting on it, and the questions for a clinician. On the figure it is drawn at the heart. Lipids stay a separate segment and keep the lipid measurements; the pulse and heart-rate variability a wearable records stay in fitness, because a segment has one source. Nothing stored is rewritten: the five markers were already recorded under their own keys, and the segment reads them where they are. ### What is fixed **A click on a radar label opened nothing, and the dot beside it was hard to hit.** The dot's hit area was eighteen pixels across, and the label beside it — «Lipids 56» — was not a door at all, so a click on it did nothing and looked like a hang. The label opens the system like the dot does, and the dot answers within a wider circle around it. A click from the radar now opens the system's card at once and asks the server for nothing: the answer the radar already drew is the one the card shows. **The radar tab took about a minute to open on a real profile.** The list of systems and every card asked the same questions hundreds of times: the genome folder was searched and each file in it opened by content on every genome read, the genotyping program was started once per position for the list and again for every card, the laboratory results were analysed again for each card, and the coverage table was parsed again for every gene. What a file's content says is now remembered while the file is unchanged, the rows read at a position are remembered while the file and its index are unchanged, and inside one reading each of those questions is asked once. The list of systems opens in about four seconds after a start and under a second after that; the cards in about two. **The Ouroboros installation never loaded.** The instructions copied the tools module into Ouroboros's own tools package, where it could not import its neighbours, so the plugin failed on its first line and registered nothing. They now place one line that imports the installed package, which also means an upgrade of the package is the whole update of the plugin. **A reference file refreshed on this machine outranked a newer one the package carries.** An import kept answering for as long as the file existed, so after an upgrade a months-old local copy silently beat the newer copy the build brought. The newer of the two now answers, by the date each carries; `scholion sources` says which copy answers each file and why, and a local copy with no date keeps answering and says that it cannot be compared. **The button labelled «Check for updates» refreshed ClinVar, and after a pip install it answered with a file path.** It never updated the program: it ran a step of genome preparation that a pip install does not carry, and the reason it printed was a path inside the Python installation. It is now called «Refresh ClinVar», where it cannot run it is replaced by a sentence saying where that step is done, and updating the program itself is `scholion version`. **A cardiology prescription was printed as acting on nothing, and six polygenic scores appeared on no card.** An ACE inhibitor, a sartan, a beta-blocker, a calcium-channel blocker, a thiazide or loop diuretic, warfarin, a direct oral anticoagulant, clopidogrel or an antiarrhythmic was listed as «unmapped» on every system card, so `scholion prescription` could not reach its genes through a system; the scores for atrial fibrillation, heart failure, hypertension, systolic blood pressure, ischemic stroke and venous thromboembolism were computed and shown on the genome tab but placed on no system. All of them now stand on «Heart and vessels», and coronary artery disease, myocardial infarction, peripheral arterial disease and abdominal aortic aneurysm move there from the lipid card, where they had been the only outcomes among measurements. **Three lines of every form from one laboratory were read by nobody.** Total calcium, magnesium, zinc and serum copper — the biochemical rows, printed in mmol/L and µmol/L — were excluded from any form whose text or file name contained «исп», the three letters that stand for the elemental method. Those letters also stand inside «Исполнитель», the word that names the performing laboratory in the header of every form one Russian laboratory prints, so on those forms the four rows matched nothing at all: the elemental series does not answer to the plain printed names, and the biochemical series had excluded itself. Nothing failed — the other rows of the same form were read as usual, and the missing ones were simply absent. The rule now names the form it meant («ИСП-МС», «Ответ ИСП»), and a check refuses any form rule short enough to hide inside an ordinary word. **A form could be counted as already read without ever having been read.** The list of files a loader has taken remembered each one by the name it was given on the command line. Named by a path relative to the directory the command ran in — `ingest-labs ../forms` — that name meant a different folder from a different directory. Two folders reached by the same relative name, a file of the same name and the same modification time in each, and the second was passed over in silence: present, never refused, its rows simply absent from the history. Copying is what makes modification times equal, since `cp -p`, rsync and every cloud sync carry them over with the bytes. A file is now remembered by its full resolved path, so two files can no longer be one entry, and one file reached through two paths — a folder of forms opened through a symbolic link, say — is no longer read twice. Lists written by earlier versions go on working and are renamed as each file is met again, so nothing is re-read because of this change. ### What needs recomputing **Run `scholion genotype-sites` — if the catalogue positions were genotyped from an alignment before this version.** The locus catalogue grew from 61 to 113 positions. A position the genome file does not list counts as the reference only when a sites file genotyped from the alignment confirms it, and a sites file made before this version holds none of the new positions: in a segment panel they print as not read, never as the reference. `scholion recompute` finds this step by itself and runs it with the alignment and the reference it names. Everything else in the panels, the heart-and-vessels segment included, is read from the reference base at every run and needs nothing. **Run `scholion ingest-labs --force` over your folder of laboratory forms — if a form names its performing laboratory in its header.** Total calcium, magnesium, zinc and serum copper from those forms are in no series at all; re-reading adds them. Two cautions. A value converted by an earlier version out of the biochemical row into the elemental series — a calcium of 2.31 mmol/L stored as 92.58 mg/L, and the same shape for magnesium, zinc and copper — is NOT removed by re-reading: it stays beside the true elemental readings, where a change of method reads as a trend. Such a point carries no date beyond its month and no source, which is how to recognise it. Delete it by hand — but only AFTER the re-read has put the printed row where it belongs, or the value leaves the profile altogether. **Run `scholion ingest-labs --force` — if the folder of forms has ever been named by a path relative to the directory the command ran in.** A form passed over for that reason left nothing behind to find it by: it counted as unchanged, not as refused, so no report names it. One full pass over the folder is what rules it out, and it is the same pass the note above asks for. ## v0.5.0 — 13.09.2026 ### What you can do now **Every body system now has its genetic half, composed from a base with a version, without waiting for anyone's signature.** Eleven systems of the radar carry a gene list taken from the Gene Curation Coalition's export: which genes have an asserted link to the diseases of that system, who asserted it, how strongly, under which mode of inheritance, and on what date — 945 genes in all, from 31 for the growth axis to 218 for the kidneys, every submitter's row kept side by side. The disease groups behind each list are named per system and tuned title by title against the export; a term that matched the wrong things — a bare «gout» that reached a neurological syndrome, «short stature» that reached sixty syndromic genes — was vetoed by name, and the tool now carries such vetoes so a refresh keeps them. The primary analysis runs on every system at once: what was read, what was not, what ClinVar holds, and the questions that follow. A clinician's signature is still what the curated layer waits for — the positions, the phrases per genotype state, the signed exclusions — but a list composed from the base no longer waits for it. **COMT rs4680 stands on the radar, under the adrenal axis, and says what it does not decide.** The catalogue held the locus since the previous release; the adrenal system now carries it as an authored position: one or two copies of Met are printed with the sentence that CPIC names the gene in its opioid guideline and issues no recommendation by it, that the behavioural and cognitive associations of the 2000s do not reproduce, and that nothing is decided by the genotype — it is shown because it is asked about. It is printed, and it is not a finding: a position never read prints as unread, and the named allele not found prints as read and absent, with the depth. **Polygenic scores stand beside every body system, as scores.** Of the seventy pinned polygenic models, thirty-eight are placed with the system they measure — LDL and coronary disease with the lipids, type 2 diabetes and glycated haemoglobin with carbohydrate metabolism, Crohn's and lupus with inflammation, gout and kidney disease with the kidneys — and each system's card prints its scores under their own heading, never interleaved with the gene rows and never inside the 0–100 index or the verdict: a genotype cannot be refuted by the next blood draw. A reliable score at or above the eightieth percentile becomes one question for the clinician — whether a screening is worth discussing — with the caveats in the sentence: a percentile is not a probability, and the panels are mostly European. The thirty-two models that fit no system of the radar are named, with the reason, in the file that maps them. A profile with no scores says so, and says that scores are computed from a full genome. **The card says what a full genome would add.** For a person whose genome file is an array, an exome or a panel — or who has none yet — the «to read in the genome» basket names, per system, the genes of the list a full genome would read and the polygenic scores it would make possible. A full genome prints nothing there, because nothing is missing. **The «Second opinion» tab is now «Radar», and it carries the whole picture.** The figure and the radar with two rings per system, and one block per system below them: what is outside the corridor, the genetic half composed from a base with a version and how much of it was read, the polygenic scores, the prescriptions acting on it, what to test and the questions for the clinician — with the system's full card one click away. An explanation of what the rings and the genetic half mean stands on the page itself, and the page still prints as the sheet to take to an appointment. **A body system answers as one card on every face.** Click a segment of the radar or an organ on the figure, run `scholion system thyroid`, ask the local API or the assistant's tool, and the same card comes back: the laboratory now and its movement since the previous draw, the genetic half and how much of it was actually read, the prescriptions acting on the system, the target a clinician set, what to test, and the questions to bring to the appointment. The next step comes in three baskets — laboratory, genome, ask — and a basket that is empty says why it is empty. The card has two densities, one for the patient and one for the clinician; they differ in how much is said, never in the verdict, and no line in either is an instruction. **The second look is laid out by body system.** What is outside the corridor, what is prescribed for that system, what to test and what to ask now stand together under one heading per system, where before the thyroid appeared three times in three layers without being called one subject. Each block prints whole, and a system with nothing to say is named as silent rather than left out. **Every system carries two rings, never merged.** How much of its laboratory panel has been measured, and how much of its genetic half has been read, drawn as two rings on the figure and on the radar. A system whose genetic list is not composed shows that ring dotted — an absence, not a zero. **Markers, genes and prescriptions point to their system.** A marker card says which system it belongs to and opens that system's card; a gene says in which systems it is named; a prescription says which system it acts on. A prescription's genes now come from the system it acts on, and a clinician's own rows about that drug stand as exceptions on top — a sentence, a class of link, or a signed exclusion; an unsigned exclusion is refused and counted. Thirteen drug classes are mapped to a system; the classes whose target is a use rather than an organ — blood pressure, anticoagulation, pain — are left unmapped, and the file says why. **Three entries refuse in one voice.** A prescription, a class of disease and a body system all judge their gene list through one form: the same gate (printed only with a sentence and a source, the rest counted), the same pending row, the same four-state verdict with the number of unread genes inside it, and the same sentence. A list that was not read end to end is never called clear, whichever door was used. **The genetic half of a body system is composed from a base with a version, and a clinician signs exceptions.** For the thyroid the monogenic composition comes from GenCC — 38 genes, every submitter's assertion side by side with its classification, mode of inheritance and date, and the export the list came from printed with it. A clinician's own positions, phrases and exclusions are read from a curated file that ships empty and says why. Ten systems say plainly that their genetic half is not composed; the twelfth, built from wearables, says it has none by design — three different answers where an empty block used to be the only one. **What the engine says about genetics is bounded.** Every row carries its evidence mode and the three modes never print alike; an assertion classified Limited, Disputed or Refuted is never a finding; one copy of an allele in a recessive gene is printed as carriership and raised as a question, never as a risk line; a single common variant prints only with an effect size from a named study; and genetics does not enter the 0–100 score — a genotype cannot be refuted by the next blood draw, so it stands beside the score, not inside it. **A target your treating clinician set has a place of its own.** «TSH between 1 and 2», «free T3 5.0» — beside the reference interval printed on the form and separate from your personal goals. You enter it with who set it and when; a target without either is refused, and the product never proposes a figure of its own. The figures convert to the marker's standard unit the way a laboratory value does. The labs report, the marker cards and the marker charts show the target beside the corridor, drawn in a different stroke so the laboratory's range and the treatment's aim are never confused. When a value sits inside the corridor but outside the target, the product asks whether it is worth discussing at the next visit — a question, not an instruction. Standing outside a target is never counted as an abnormality: the flag and the out-of-range count are exactly what they were before. Targets live in their own file, so re-importing a folder of forms rewrites the series and leaves them in place; `scholion target` and a matching block on the labs page list, record and withdraw them, and name in one place the values that are in the corridor yet outside a target. **The monogenic half of a system's gene list can come from a curated base instead of from one person's memory.** A single command pulls the Gene Curation Coalition's weekly export, keeps only the genes whose diseases belong to a system, and writes them down with everything a reader needs to weigh each line: who asserted the link, how strongly, on what date, under which mode of inheritance, and which version of the base it came from. Every submitter's row is kept, so two groups disagreeing about one gene are shown side by side rather than averaged, and a weak or refuted assertion travels marked as such rather than being silently dropped or silently promoted. The thyroid system ships composed this way — thirty-eight genes with the recessive ones named recessive, which is the difference between «carrier» and «at risk» for a heterozygote. A freshness check says how far behind the base the shipped copy is, and the tool refuses to touch the network when told to work offline. The other ten systems remain unfilled on purpose: their terms are entered from a named panel or report, and none has been named yet, so each says so instead of looking like a system nobody asked about. **A gene answers with what has been decided about it, not only with genotypes.** Asked about a gene, the build now prints the curated verdict written about that gene before the findings and again under them — including the case where the verdict is that no clinical decision follows from it. Where such a sentence exists it is quoted with the file it came from: a statement about what does not follow from a gene is as much a claim as one about what does, and neither is printed unattributed. **How well a gene was read travels with the answer about a prescription.** The measure arrived with the gene question and was wired only there. It is now beside every gene on the prescription path as well, where «no pharmacogenetic finding» is read as permission to proceed and is worth exactly as much of the gene as was read. One state stays silent — a gene read like the rest of the file — and that is not a claim that the reading was sufficient: sufficiency depends on the question, which the product does not know. Without an alignment file the answer is «not measured», which is a fact about the input. **A second way in: a class of disease instead of a prescription.** `scholion screen` answers the question a person has when nothing has been prescribed and the examination says they are well — what to look at for the conditions where inheritance carries most of the weight. The gene list there is fixed in advance, which is what a panel is, and this build still invents none: the classes come from the published secondary-findings panel it already carries, with the panel named as the source, and every class that panel does not cover is printed as absent rather than left out. A curated file takes classes a clinician supplies, under the same rule as the prescription side — a list without a named source is dropped and counted. The answer for a class is gene by gene, and it will not call a class clear it did not read: something reportable was found; nothing was found and every gene of the class was read; nothing was found and part of the class was not read, with how many and which; or the screen has not been run. On a screening result «nothing found» is taken for health, and over a gene nobody read it is a statement about the file. A finding does not cancel the gap either — both travel in the same sentence. The entry is on every face at once: the command line, the page (a pane of the genome tab), the plugin, the model-facing tool list and the instruction the model reads. **Everybody has the gene; what can be missing is a variant, or knowledge of one.** The class of link that used to read as «this gene is not here» was making a claim about a person. It now reports what this build can say about variants in that gene, measured against the reader's own file and never defaulted: positions read and matching the reference, with how many of how many and at what depth — which is a measured absence of a finding; positions held and not read in this file — which is an absence of measurement; or no position held for the gene at all — which is a statement about the build. Positions with no row are counted beside the ones that were read, so two confirmed out of eight held no longer reads as «nothing here». **A gene somebody named, whose meaning nobody has written yet, is kept and printed as unwritten.** It used to be dropped with the unattributed entries. It is the most informative row on such a screen: it says a clinician put the gene on the list and that what follows from it is still to be written — and the build says plainly that it will not fill that in. The class of link is authored too: a gene arriving without one is printed as named and unclassified rather than filed under a class this program chose. **A prescription is answered with a judgement about the choice, not only with genotypes.** Asked to check a drug, the answer now opens with one of three statements, and each is about what this build holds rather than about what to do: a rule for this pair exists and the reading selects a row that is not the neutral one; a rule exists and the reading selects the neutral row, which is a statement about the rule and not clearance; or no rule could be reached, with the reason named. **The link between a prescription and a gene has a class, and the class decides what may be said.** Two classes are computed from this build's own tables — a pair with a guideline, and a pair recognised with no table, which travels with the date of the copy. Three cannot be computed: a gene that handles the substance with no dosing rule following, a gene asked about from which nothing follows, and a gene named in a list and unknown here. Which genes belong to a prescription is a medical statement and so is «nothing follows from this one», so those three are read from a curated file and never derived. An entry without a named source is not printed, and the number of entries dropped that way is printed instead — an entry that vanishes quietly is indistinguishable from one nobody wrote. The curated file ships empty, deliberately. Until a clinician writes a row, the honest answer to «what else bears on this prescription» is that this build holds no list for it — and that sentence is printed, because a silence there is completed by whoever is answering the reader, out of knowledge that never passed through this build. **Every locus stands on one of five named things, and two of them license nothing.** What permits saying anything about a position — a guideline rule, a curated note, a verdict about the gene, or the bare fact that the gene is a recognised pharmacogene this build holds no table for — is now decided from the files and printed. The fifth state is a position with none of those, and it no longer prints a genotype and stops: it says this build holds no reading for the position, the genotype is named and not interpreted. A check walks the whole catalogue and fails on a locus that stands on nothing silently. ### What is fixed **A gene the ACMG scan had put through ClinVar was still called «not put through ClinVar».** The system card counted a gene as read only when the wide ClinVar annotation — a separate pipeline step — had been run; on a full genome with only the product's own ACMG scan, APOB and LDLR on the lipid list said the annotation had not been run, although the scan had done exactly that for its 84 genes. For those genes the scan now counts; every other gene still says, in words, that its variants were not put through ClinVar, and what closes it. **Why a gene was not read printed as a code.** «not read (clinvar_not_run)» stood on every such row and once per gene in the genome basket — two hundred and eighteen lines for the kidneys, none of them saying what to do. A reason is now a sentence in the reader's language, the basket groups the genes by reason on one line each, and each line names the step that closes it: the ClinVar annotation of the file, or the coverage table computed from the reads. **A position whose author says nothing follows counted as a finding.** An authored position of the «asked about» kind — COMT Val/Met — was counted into the system's verdict as something reportable, while its own sentence said nothing is decided by it. Such rows are printed with their sentence and are not findings; the patient's register shows them, since the sentence was written to be read, and shows the genotype state on them instead of «not determined». **The demo-genome recipe ended in a refusal.** The fetcher printed «lay out a profile, then point the product at both», and the product then refused to read the reference genome beside a profile it took for the person's own. A profile can now be laid out for a reference sample — `scholion init --subject reference` — every file of it says so, the reference genome is read beside it, and the refusal that used to print a raw key names this way out. **The demo-genome fetcher crashed on its first request.** `fetch_demo_genome.py`, the one tool that downloads a public Genome in a Bottle sample so the product can be shown with a real genome, had `encoding=` pasted onto its HTTP calls to satisfy a text-file check, and every run since 0.4.9 stopped with a type error before listing anything. The three calls are HTTP openers again, and the check now knows them by line. **Three tools could not be called through the tool server.** A model asking to note an episode in the focus journal, to record why a day holds two draws, or to propose a marker name received a type error instead of an answer — on every call since those tools appeared, because the three read their arguments in a way no host passes them, and the test that covered them filled the arguments in by hand. All thirty-two tools now answer over the wire with the arguments their schemas declare, and a test calls every one of them that way. Nothing stored changes. **Five tools were served without a description.** The register of sources, the array report, the flag rate, the draw context and the marker proposal showed a raw key where a description belongs, in both languages. They are described now, and a raw key in a tool list fails the build. **Asked to transcribe laboratory forms with no folder named, the tool read the current directory.** An empty folder name resolves to wherever the process is standing, so a call that named nothing could transcribe every PDF under the working directory into the profile. A folder must now be named; an empty name is refused before anything is read, a refused call no longer copies the transcription manifest into the profile, and on success the tool prints the same report as the command line — it used to fail on a spreadsheet export after having written the profile. If such a call ever ran against your profile, the points it added carry the file names they came from and can be removed by re-importing the folder you meant. **An unknown register was served as the patient's.** Asked for a system card in a register that does not exist, the local API and the tool answered the patient's card labelled with the word given. The request is now refused by name, with the two registers listed; the command line always refused it. **Two writing commands were listed as reads.** Recording the context of a double draw and proposing a marker both write to the profile, and the capability manifest called them reads. They are listed as writes of the dictated kind — what the person said, never a value — and the skill text and the tool descriptions no longer claim that only one tool writes: four do, and each records what the person handed over. **The web's source badges had no command-line twin.** The route the page reads its «where this data comes from» badges from was mapped to the provenance audit, a different function. `scholion sources --json` now carries the same block under `data_sources`. **A Russian card explained an empty genetic list in English.** The sentence a system prints when its genetic list is not composed shipped in one language. Both now. **A wearables file in the oldest layout listed the years as kinds of workout.** A file that kept workouts as «kind, then year» was carried over to the current layout without being turned, so the summary printed «2024» as a type of training and «Walking» as the last active year. The file is now turned on read, once; nothing stored changes, and a file already in the current layout is read exactly as before. **A gene link typed into a person's own lab file no longer reads like a curated statement.** Such a line is free text without a source; on the second look it was printed in the same voice as a sentence that had passed the gate. It now travels marked as the person's note, so every page shows it as one. Nothing in the stored profile changes. **A deletion or duplication in a position list could be read as the reference — with an empty genotype.** Asked about a position whose event is not a single-base substitution — a frameshift deletion, a duplication, an insertion — the locus reader fetched the coordinate, found no row there, and answered «assumed reference» with an empty string where the genotype belongs. A variant of that kind is written one base upstream in a VCF as a pair of alleles of different length, so the reader was looking at the wrong base with the wrong instrument and calling the silence calm. Such a position is now refused by name before any file is opened: the coordinate is given, the event is named, no genotype is written and no reference is assumed, and the answer says what would read it. A position list that contained such an event now shows a refusal where it showed an empty reference. **A position with three alternative alleles can now be held, without anyone choosing one.** The catalogue holds one alternative nucleotide per position, which is right for comparing genotypes and left some studied positions unable to be entered at all. Such a position is now kept with its coordinate and the alleles observed there and no alternative chosen — which of them a study meant is not in the source, and this build does not pick. Every reader refuses on it by name, before reading anything: the coordinate is given, the observed set is named, no verdict is offered. An entry may hold a comparable pair or an observed set, never both — one carrying both would be compared against the chosen allele while looking as though it had declined to choose. **A catalogue refresh could rewrite the alleles it was only meant to check.** The documented update command refreshes the coordinates of every locus it already holds, and it was writing into the alternative-allele field whatever the public source returns — the full observed set, `A/T`, `C/G`. The catalogue holds one nucleotide on each side and the genotype comparison reads one character from each, so a run left thirty-five of fifty-five loci compound, among them the ones dose answers are drawn from. A refresh may move a coordinate and may not decide what the alleles are: a disagreement about alleles is now reported and never applied, since which is right is a curation decision with a source behind it. A position the source reports with more than one alternative allele is named and left out rather than guessed at, and the whole file is checked against its own invariant before anything is written — a run that would break it writes nothing and exits non-zero, instead of printing «Written to:» over a broken catalogue. Adding a locus no longer leaves an empty record behind when the source does not answer. **«Coverage not measured — » printed with nothing after the dash.** The value of that line is the reason: no alignment file, no index, or a gene outside the coverage table need opposite actions, and an empty one reads as text that broke off. A reason is always printed now, including the one that says nothing said why. **An annotation beside the data was not looked for there.** The gene-coordinate search consulted two declared folders, and on a machine where neither was the one holding the file — while the live source was unreachable in the same moment — three genes came back unresolvable with the file one directory away. The folder holding the alignment the profile already knows about, and its neighbours, are now part of the declared list. An explicit setting still switches the search off entirely. **A caveat vanished when a gene was listed rather than a position.** Each line of a gene listing is cut to its first line, and everything a position had to add about itself was on the lines after it. On one locus that discarded sentence was the one that decided the answer: the catalogue's own note saying the variant read there is the minor one for most readers, and that the main one is not a single substitution and cannot be read from a variant file at all. The curated note, the note about that particular read, and any refusal are now printed under their locus in a listing as well. **A verdict written about a gene reached nobody.** One gene carried a sentence, written and checked, saying that no guideline exists for it and that testing it is not recommended. The field was translated into both languages and covered by a check — a check that the field was in the file, which is a different claim from a reader seeing it. On the screen that gene printed two bare genotypes. Nine more curated sentences were in the same position, across four files: how an analyte is measured and why its published limit may be the wrong one for the reader, what a laboratory code means for that analyte, why a missing guideline row is missing, and which tools were examined, not taken, and what taking one would close. All of them now reach the reader, each where its own answer is. A check enumerates every curated field of this kind and fails on one that nothing renders, so that the next such sentence cannot be written for nobody. **«No meaningful pharmacogenetics for this drug» carried no date.** «Nothing is known about this pair» and «our copy of the guidelines is a release behind» are different facts, and the date of the copy is what tells them apart. The drug-by-name path said it; the prescription check — the path taken before a tablet — did not. It does now, in the command line and on the page alike. **A mineral measured by two methods stayed one series.** An elemental analysis (ICP) reports calcium, magnesium, zinc, copper and iron as mass per litre; a clinical-chemistry panel reports the same elements in molar units, and the two disagree by more than rounding — over ten per cent on the same sample. The dictionary converted the molar result by the atomic weight and appended it to the elemental series, so a change of method read as a change in the person; for iron the elemental row had no unit gate at all, and a molar value carrying the symbol was stored as micrograms unchanged. Every such element now has two markers, each declaring its method; the elemental one accepts no molar unit, and each is kept off the other method's forms. Serum copper by the biochemical method is a new marker with a standard adult interval by sex. A check refuses the next element that mixes. **Re-importing laboratory forms could add a second point for a draw the series already held.** When the earlier point was dated to the month — the shape older imports wrote — and the form carried the day and clock time, nothing was refused: the marker count stayed the same while the number of points grew, and a trend read the same draw twice. A point of the same period now stands in for the earlier one at every resolution: a draw replaces its day or month, a day replaces its month, and the finer date is kept whichever order the two arrived in, with what the earlier entry recorded about the draw carried over. A month or day that arrives against two distinct draws inside it still replaces neither and is reported, because choosing one would be a guess. ### What is retracted Nothing. ### What needs recomputing **Run `scholion ingest-labs --force ` — if forms were ingested by an earlier version.** A month point beside a dated point of the same draw is collapsed into one (the next write to that marker, of any shape, does the same), and biochemistry copper points move from the elemental series to their own marker. **By hand — if an elemental-iron point sits in the low tens.** It is a molar value stored as micrograms; remove it from the series. ## v0.4.11 — 12.09.2026 ### What you can do now **A variant file with no index is read.** Every reader of a genome file used to need an index beside it, and the tools that build one are not on a physician's machine. Such a file is now read once from beginning to end — the catalogue's positions in both builds, the counts that tell what kind of file it is, and the header — and no index is written, because one written subtly wrong would be trusted by every other tool. What that reading cannot answer is refused by name: a question about a whole region says it needs an index and names the two commands that make one, rather than answering with an empty list that reads as «this gene carries no variants». **The screen for what is worth acting on runs from an installed package.** The secondary-findings screen over the 84 ACMG genes does not compute when asked: it reads a table, and the pass that writes that table lived in the data-preparation directory, which does not travel with `pip install`. `scholion acmg-scan` runs it now, with no external tools and no index, from the person's variant file and the published ClinVar file; when that file is missing the command prints the one download it needs, for the build the person's file is in. Each file's build is established from the file itself, and a crossed pair — a GRCh37 genome against a GRCh38 ClinVar — is refused before a single position is compared, because a crossed pair does not fail: it finds nothing, or matches a position that belongs to a different base, and both look like an answer. **An exome is told apart from a panel.** Breadth used to be probed in windows that are gene-poor by construction, where an exome is empty, so an exome was classed as a sparse panel — and that class shuts the ClinVar and ACMG paths, on the one input where a screen for known pathogenic variants is most obviously worth running. A second set of probes in gene-dense windows tells the two apart by contrast. The two paths open on an exome and say what an exome can and cannot answer; polygenic scores stay shut, because a score summed over the coding two per cent of the genome has no distribution behind it. **The status says which questions this input can carry, before any finding.** Six paths — catalogue loci, pharmacogenetics, a whole region, ClinVar, the ACMG panel, polygenic scores — each marked open or shut for this file, with the reason; «this input cannot carry the answer» is told apart from «the annotation this path reads has not been produced», which used to be invisible because the narrower gate fired first and blamed the file for a missing table. **A gene is asked of every shelf that holds anything about one.** What this build knows about a gene sits in four places, each keyed differently — the curated catalogue by rsID, the ClinVar scan by coordinate, the shipped ACMG secondary findings panel by symbol, coverage by gene — and nothing joined them. Asking about a gene read the catalogue alone and answered «not in the coordinate reference», which is a statement about one shelf and reads as a statement about the genome. A clinician asked about the bile-acid transporters and was told there was nothing to say «even in general terms from your genome», while the ClinVar table on that machine held 386 findings. `scholion genome --gene X` now prints a frame before the findings: what each layer holds, that it holds nothing, or that it could not be asked and what would let it be. A gene in the ACMG panel is recognised with no network and no annotation file, because those 84 symbols travel inside the build — so a question about BRCA1 on a machine that cannot reach Ensembl gets an answer rather than a shrug. **ClinVar can be asked by gene.** `scholion clinvar --gene X` matches findings to a gene by COORDINATE — the scan table carries no gene column, and this needs no re-scan of the genome. A gene whose coordinates cannot be obtained is reported as unresolved, with what would resolve it, rather than as a gene with no findings. ### What is fixed **A row at the position was taken for the locus.** The reader took the first variant row at a catalogue position and built the genotype from that row's own alleles. A coordinate is not an identity: an insertion, a neighbouring substitution or a multi-allelic row can stand on the same base, and the genotype printed then belonged to somebody else's variant while carrying the locus's name and the label «called». On three clinical files the alleles at the Factor V Leiden position disagreed with the catalogue in every one. The row is now chosen, not taken — its alleles must be the catalogue's — and the two ways of failing to find one are named: another variant on this base, and a reference base that is not ours at all, which is a statement about the file's build, never about the person. **A genome file with no index that had been cut short was read up to the cut and answered «reference» at every position past it.** A copy or a download that did not finish leaves such a file; a heterozygous APOE ε4 carrier could be printed as a non-carrier under a status line saying the genome was connected. Such a file is now refused by name — the status says the file ends before its end — and a reading that dies part-way is answered as a failure rather than as an empty result. **Two shapes of a gVCF row printed the strongest label the layer has over stretches that were never read.** A reference block whose genotype is a no-call (`./.`, depth 0 — what a caller writes over an unread region) answered «confirmed reference»; and a row whose only alternative is a spanning deletion (`*`), left behind when a multi-allelic row is split and one half filtered, answered «confirmed reference» at a base that lies inside a deletion on one chromosome. The first is now a no-call; the second a refusal naming the allele found. **A deletion or multi-base change standing on a catalogue base was reported as «the file is in another build»** — a claim about the file made from a row that merely carried a different variant; it is now «another variant at this position». A multi-allelic row written with a shared trailing base rendered a heterozygote as a four-letter string labelled «called»; it now prints the locus's own allele pair, and a genotype naming an allele of another length beside ours is refused by name instead of concatenated. **An owner of a genotyping chip saw a status that said the array was connected and, six lines later, «no genome is connected» on every question** — closing the locus catalogue and the pharmacogenetics a chip answers as designed. The frame now belongs to the input that answered: the catalogue and pharmacogenetics are open, a gene region is closed because a chip reads chosen positions and a gene is a stretch nobody chose, and the screens are closed as too narrow; a gene query on a chip refuses with the same reason instead of proceeding without a file. **The secondary-findings screen could report a variant a person does not carry, and miss one they do.** A row whose genotype was not called at all — the ordinary shape of a family file or a gVCF — was written as a finding, because «not a reference» was the only test. A row with two alternate alleles was matched on the pathogenic one and judged from the other, so a person homozygous for a benign second allele was reported homozygous for the pathogenic first. And in a file holding several samples the first column was read as the person's: a mother's BRCA1 heterozygote, screened as her child's. Each is now a named step — the column is chosen, the allele index is required in the genotype, a no-call is counted and said with the result — and a call the caller itself flagged as not PASS is written as filtered rather than decided. **The refusal against matching two builds could be switched off by the one variable meant to arm it.** Declaring the build of a headerless personal file also declared it for the ClinVar file, so a declared GRCh37 against the downloaded GRCh38 ClinVar read as «GRCh37 against GRCh37» and ran. The ClinVar build is now read from the ClinVar file alone. And a personal file whose build could not be told at all went into the scan with a clean «ok»; it now refuses and names what closes it, the same way the ClinVar side already did. **The table was written beside the variant file while the screen read it from the genome folder**, so with the file anywhere else the command said «written» and the screen said «not run». It now lands where the screen looks, and carries a sidecar saying which builds it was matched in, which ClinVar release, when, and how many positions were unread — so a stale or crossed table can be told from a current one. **The advice about external tools described a product that needed them.** The first command a new person types offered four binaries and explained that without them «the genome layer does not work at all». True when written, false since a file without an index became readable and the ACMG screen became runnable. It now says what the four buy — seeking a region or a whole gene, and the wide ClinVar annotation — and what answers without them. **The gene frame said the wrong thing about a scan that had not run.** Asking about a gene prints, above its findings, which shelves of the build hold anything about it. When the ClinVar scan had simply never been run, that frame said the gene's coordinates could not be obtained — sending a person to fetch an annotation file they did not need — and `clinvar --gene` blamed a missing or broken index. Worse, the ACMG line printed «in it, 0 findings» for a panel nobody had scanned: a false «clean» on a hereditary-cancer gene. Each layer now says which of three things is true — what it holds, that the scan has not been run, or that it could not be asked and why — and a warning that only part of the findings table was read now reaches the frame instead of dying on the way. **The web page still showed a genotype for a position with no row.** A position absent from the variant file means either «reference» or «never read», and the command line had already stopped rendering that state as an answer. The page had not: the card printed the reference bases in large type with the caveat in small type beside it, and a gene listing did the same for every such locus. The page now prints a dash there, on both views. **A prescription's status could not be set, was erased on re-entry, and stopped drugs looked current.** No writer — command, page or API — could record whether a drug was current, so for anyone not editing the file by hand every entry was «no status recorded» while the wording claimed those entries predated the field. Re-adding a name to change its dose replaced the whole entry, so a stopped drug came back current with its start date and monitoring gone, silently. And every listing of the regimen drew a stopped drug like one taken this morning, while the interaction check had already excluded it. A status can now be written (`add-med --status`, a field on the page, `status` in the API); re-adding merges and says what it kept; and every listing — command, page, and the context handed to a model — marks entries that are not current. A pulse course counts as current everywhere, by one rule. **A gene filter over a truncated scan said the gene was clear.** The ClinVar scan is read a page at a time, and the gene filter took the page: on a profile with more findings than one read, «0 in this gene» would have been printed where the finding sat past the cut. The filter now compares what it read with what exists and says when it could not see everything. In the same pass: a gene frame that failed to build vanished without a trace and now reports itself unavailable with the reason; a single letter of a supplement's name matched a drug and earned it a pharmacogenetic tag, and a match now needs a whole name; a folded provenance note could still run to three hundred characters and now has a ceiling. **What the person is taking NOW is asked instead of assumed.** Every comparison — interactions, drug classes, the laboratory monitoring rules, the limitations — ran against every name in the prescriptions file, so a drug the physician stopped carried exactly as much weight as one taken this morning. A statin that left the scheme on the day an azole course was replaced still produced «↑ statin concentration, risk of myopathy», and beside it a warning about a probiotic paused six weeks earlier. A status decides now, and it decides by a WHITE list, so a value nobody taught the code about is not silently treated as current. An entry with no status at all stays current: those predate the field, and reading them as stopped would delete real prescriptions from every check at once — silence where a warning belongs is the worse mistake. And what was left out is named in the answer, because a red line that is absent for a reason and a red line nobody computed look identical on the page. **How well a gene was read comes with the answer about it.** Coverage was a footnote at the bottom of a report, and the question it qualifies is asked one gene at a time. It now travels beside the answer — and only when it is worth saying: a gene read in line with the rest of the file is left in silence, a gene far below it says so and says by how much. The ruler is the file's own middle, and it was measured before it was chosen. At 20× on a 30× whole genome the callable fraction runs to a median of 79.8 %, with the best gene of ninety-three at 92 % and none at 95 %: any clinical-looking threshold therefore fires on 89 of 93 and is measuring the shape of a depth curve rather than anything about a gene. Against the file's own median, eight fire — and they are the genes that are hard to sequence for known reasons, X-linked or carrying a pseudogene. The rule travels with the sequencing depth, so it says something useful at 30× and at 100×. Four states, one of them silent. «In line with the rest of this file» is never reported as «adequate»: what is adequate depends on the question, which the product does not know. A gene the coverage table does not cover, and a profile where coverage was never computed, both say so — a silence that means «nobody measured» is read as reassurance, which is the same mistake as a missing row printing as «reference». **A position with no row in the file was printed as a confirmed reference.** A VCF of variants has no line where the genome matches the reference, and none where nothing was read either; the layer calls that state «assumed», and every decision in the engine already excluded it. The last mile did not. It rendered the state in the shape of an ANSWER — «genotype TT (reference)» — with the honest note beneath. On a single locus a reader got both, a reassuring label and a warning contradicting it on the next line. In a GENE LISTING they got only the first, because a listing keeps one line per locus and the note is the second. A physician running this over DPYD — fluoropyrimidines, where the genotype is required before the first dose — met eight positions labelled reference, six of which have no row in the file at all. The state is now rendered as the refusal it is, in one line, so it survives being cut to one line. **A genotype with no curated reading behind it says so.** A variant outside the curated set reached the reader as a bare genotype, and the silence where a reading should be was filled by whoever was talking: asked about COMT, the product correctly said the variant is outside its set, and the conversation supplied «the low-activity variant» from general knowledge with no source inside the product. The sentence is a statement about this build's catalogue, not about medicine. **A refusal about a drug names which silence it is.** «We could not look», «we looked and there is no such drug» and «we looked, found it, and no guideline for it is in our copy» wore one sentence. The third now carries the date of the guideline copy it is a statement about — which is what tells «the literature has nothing» from «this build is behind». **The regimen says which entries the model can speak about at all.** A list of supplements produced «no interactions found», which is true and uninformative: most of them are outside pharmacogenetics and always were. Entries with a gene-drug pair are marked; the rest are named as outside the model rather than left to look unexamined. Long provenance notes fold to their first sentence — a list of forty-five entries carrying a paragraph each is not a list anybody reads. **The build says when it is old.** It reported a stale reference database and said nothing about its own age. It now reads its own release date out of the journal it ships and says so on the first screen once that date is old enough to matter. Nobody is contacted to find this out: a product whose claim is that the profile never leaves the disk does not acquire an outbound host to deliver a convenience. ### What is retracted Answers that labelled a position «reference» where the file has no row at it. No stored value changes — the engine never counted those positions — but a printed page or an exported summary made before this version may carry the old label, and for DPYD and CYP2D6 that label reads as permission to prescribe. A genotype printed at a catalogue locus whose row carried other alleles than the catalogue's. It was somebody else's variant under our locus's name; it is now withheld with the reason, so a report made before this version may carry a call that will not be repeated — the Factor V Leiden conclusion among them. An exome classed as a sparse panel, with ClinVar and the ACMG screen reported shut. The same file now opens both paths; the earlier statement about what that input could answer no longer stands. ### What needs recomputing **Run `scholion acmg-scan` — if an ACMG table was written by an earlier version.** That table was matched without checking that the personal file and the ClinVar file share a build; if the two were crossed, it is a silent zero or a match on a different base. The scan rebuilds it from the files and refuses a crossed pair; a table whose files were of one build comes out unchanged. ## v0.4.10 — 09.09.2026 ### What you can do now **The Genome tab is one page at a time, and it opens with findings.** Seven headings on one scroll — all open, the maintenance block above every conclusion, and the same gene appearing in four of them without any of them saying so — are now panes of one tab. The first is neither ClinVar nor polygenic nor longevity: it is one list, ordered by how much a line can change and marked with the source each came from. Clicking a gene anywhere on the tab gathers what every source holds about it into a single card: the loci and their genotypes with coverage, what ClinVar says at those positions, what the longevity layer and the lipid card say about that gene. Nothing was removed — the sections are still there, one at a time, and the service text each of them opened with is folded rather than first. **The documents inside the package open as pages, and the second opinion offers the one written for a clinician.** The product's own output names files — «see PREPARING-THE-GENOME.md» — and after `pip install` the only way to read one was `scholion doc ` at a terminal, which the person reading the interface may not have open. The local server now serves the same nine documents at `/doc/`, each as a self-contained page that prints on paper and pulls nothing from the network. «Second opinion» carries a button to the one-page description for clinicians and researchers: what the program is, what it computes, and where it refuses to answer — meant to be handed over at the appointment. **The first screen is the person.** It opened with the one experiment being run this month and reached the reader before anything about the reader. The order is now the order the questions come in: who the data belong to — sex, age, height, body-mass index, the reference panel — then their own indicators, then the targets they are aiming at, then the body systems and the figure, then what is out of range right now, then what is worth measuring, and last the focus of attention. Every block still names the tab that owns it and goes there on a click. **The Profile tab is retired into that first block.** It held four cards and two forms, and the four cards were the ones a watch measures every day; as a tab of its own it had become the emptiest page in the product. Nothing it could do was lost — the profile form, the manual measurement form and the list of what the profile is still missing all open at the top of the first screen. **The state of the genome is said in one place, including when it is not being read.** The tab used to show a grey badge and leave the reason unprinted; a person whose folder held more than one candidate file met «not read» in six places and the cause in none. The state now carries the file, the assembly, the sample, what was set aside and why — and, where a choice is open, the names with a button beside each. The choice is kept, so it is asked once (`scholion choose-genome` at the command line). **«What to test» is part of the second opinion.** Every row of it is a line for the same conversation, and it was a tab that handed half of its own list back to the tab beside it. The routine controls travel with it, folded. **The lifestyle brief's «needs review» can be answered.** The flag is raised when a marker a block watches is measured after the block's wording was last read, and until now nothing in the product could lower it: it went up once and stayed up, at the top of the tab, above the content. There is now one button — the wording still holds — which records the date that has always been what the flag compares against (`scholion brief-reviewed `). It also moves down the page to sit beside the wording it is about. ### What is fixed **Accepting the reach baseline moved the whole file to whichever machine ran it.** A number in `test_reach_baseline.json` is not a property of the code alone: it is what the suite reached on one interpreter, with one backend — the two do not count a line identically — and, for at least one module, only where a file the repository does not carry happens to sit. `--strict` had printed a warning whenever the run and the recorded stamp disagreed; `--accept` rewrote every number regardless, restamped the file, and reported it in one line. There are three writing modes now, and only one of them moves the file. `--accept` records the whole measurement and refuses when the baseline was taken elsewhere — before measuring, since the answer never depended on the ninety seconds. `--accept-new` records only modules that have no accepted number yet, which is what the suite's own guard asks for when a module is added; it changes no other number, not the overall, and not the stamp, and it does not run the suite at all when there is nothing to add. `--rebaseline` is the deliberate whole-file move and prints every number it lowers, old → new, before writing. **A document name was tolerant about spelling, which was free until a route handed it one.** `DATA_LAYOUT`, `data-layout.md` and `data-layout` are one request, and refusing two of them teaches nothing but the exact spelling. While the only caller was a person typing at their own prompt, that was the whole story; a URL is composed by whoever holds it, and `../../../etc/passwd` builds a path outside the package as readily as a name builds one inside it. The tolerance stays and the shape does not: a document name is one file name, or it is nothing. Anything else opens the list of documents instead. **Seven of the thirteen rows of the goal table said «—» while the numbers sat in the file.** Weight, body-mass index, body fat, muscle mass, VO₂max, resting heart rate and steps come from a wearable device, and five of the goal charts drew nothing at all. The lifestyle layer stores a measurement together with the device that made it — two watches do not measure resting heart rate the same way, and one series built out of both shows a step on the month the second export was loaded — and the goal reader had been written before that was true. It looked for the metrics where they used to sit, found nothing, and returned an empty series. Nothing failed and nothing was logged: «—» is what that table prints when there is no data, and there was a decade of it. Two things follow, and both are new behaviour rather than a repair. The reader goes through the accessor that knows the file's shape, so a file written by an older version answers exactly as a current one does. And a row with no number now says which of three things is the matter — nothing carries this series, the series is empty, or more than one device measures it and the goal has to say whose. The last of those is refused rather than averaged, and a goal may name the device (`wear:garmin:RestingHeartRate`) to answer it. **A number the watch already had was reported as missing, or as three weeks old.** Some indicators are kept twice: what a person types in, and what a device records every day. Only the first was read. Steps stood at a single figure entered in July and were called «below target», while the device series had the month just gone above it; sleep showed nothing at all beside seventy-five months of nightly data; a resting heart rate from December stood as the current one in September. The two are joined now: the newest measurement is the one shown, each card says which store it came from and on what date, and the other store is printed beside it rather than instead of it. A tie goes to the hand-entered reading — a monthly mean and a measurement taken on a day are not the same statement. Neither file is written to. The pairing is declared once, in the shipped wearable reference, and only where the two are the same quantity. Where they are merely similar it is left out and the row goes on saying it has nothing behind it: intensity minutes are not «minutes of activity», and a pairing that is nearly true prints a number nobody can act on. **The goal board was dated by one of the files behind it.** The heading read «data as of» the timestamp of the wearables file, while half the rows come from the laboratory — so a table carrying a draw from the 3rd was headed with the 23rd of the month before. Every row carries its own date now, and the heading carries the newest of them. **A stored result was deciding what language the product speaks.** Polygenic risks and longevity markers printed in Russian while the interface was English. Neither catalogue is missing a translation — both carry every name in both languages. The names were coming from `prs_results.json` and `longevity_findings.json`, which are stored RESULTS: each label is a copy of the catalogue made on the day of the run, in whatever language that run was speaking. The catalogue now decides what a thing is called and the file decides what the number is; a trait or a marker the catalogue does not carry keeps the name it was stored with, because a percentile with no name is worse than a name in one language. **The longevity layer showed genotypes and explained none of them.** Each row printed a gene, an rsID, a genotype — and an explanation line that was always empty, because the page asked for a field these rows do not carry. Everything that says what a marker MEANS was in the catalogue, in both languages, unread: what the allele is, what a second copy does, what it argues for, what population the direction holds in, and the papers behind it. Rows now carry all of it, sorted so that what was found comes first and what was checked-and-quiet folds away. The verdict and the strength of the sources are written out as sentences rather than as the internal words they are — and a word the catalogue does not recognise is never turned into a message key, which is how «⟦longevity.verdict.…⟧» used to reach a reader. **APOE says what APOE is.** The card led with «APOE — status» over two rsID numbers, which tells a reader nothing about the gene or about their own combination. It now opens with what the gene is and what this particular pair means, says plainly that it is a factor and not a diagnosis, and folds the two positions it was computed from underneath. **A file carved out of a genome was counted as a second genome, and the whole genomic layer went dark behind it.** Files called from the same reads at a chosen list of positions — the loci of the catalogue, the scoring sites of the polygenic models — sit beside the main file by design. They were recognised by NAME, from a list of four; an output written under a fifth name became a second candidate, the choice became ambiguous, and a refusal to guess between two files means no locus is read at all. Nothing failed and nothing was logged. Every genomic answer said «not read» — including a card that named two positions and said they had not been read while the reads sat in the file beside it. What a file is called no longer decides anything. bcftools records its command in the header, and a pileup restricted to a list of sites, or an annotation pass over another file's rows, is an extraction rather than the genome it came from; files this project writes stamp themselves besides. The rule may narrow the set of candidates and may never empty it — a folder holding nothing but extractions still holds the person's reads — and whatever it sets aside is named with its reason rather than dropped in silence. **A card no longer blames the positions when the genome is not being read.** «The PCSK9 positions have not been read» is a statement about two rows of a file that is open. With no genome connected there is no such file, and the sentence sent a reader to look at their sequencing when the answer was in the folder beside it. ### What is retracted No stored value changes. Answers that said «not read» for the reason above will now say what the reads say — which is not a retraction of a claim but the end of a refusal. One claim is retracted, and it was a claim about a person rather than a silence: an indicator whose hand-entered point was older than the device series was judged against its target on the old point. Where that judgement was «below target» and the device disagrees, the flag changes with this version. Nothing was stored, so nothing has to be undone — but a printed page or an exported summary made before this version may carry the old flag. ### What needs recomputing Nothing has to be recomputed and nothing has to be re-imported. A profile whose genome folder holds one file behaves exactly as before; one that holds several is asked, once, which file is the genome, and everything that was silent answers from it. The wearable series were on disk the whole time — they are read now, not rebuilt — so the goal charts fill in on the first run with no export and no ingest. ## v0.4.9 — 08.09.2026 ### What you can do now **The Overview draws the body beside the radar.** The same systems, twice: the radar as before, and a figure on which every system with a place on a body is lit by its score. Around each mark a ring shows how much of that system was actually measured, which is the number the radar has never been able to show. A liver scoring 100 out of 100 on one marker of three is a full point at the edge of the radar and a ring closed a third of the way on the body. Picking a system on either picture marks it on the other. **The endocrine system is four systems, and each is scored on the panel a laboratory actually issues.** One domain called «Hormones», averaged from testosterone, IGF-1 and TSH, is gone. In its place the radar carries the thyroid axis (TSH, free T4, free T3, anti-TPO), the adrenals (cortisol, DHEA-S), the gonads (testosterone, DHT, estradiol) and the growth axis (IGF-1, growth hormone). The ring beside each now says how much of that panel exists, instead of how much of a mixture of three markers nobody orders together; a system with nothing measured is not drawn at all rather than averaged into the picture. **A hormone is marked at the gland that makes it**, not at the organ it is read for: TSH and growth hormone at the pituitary that secretes them, free T4, free T3 and anti-TPO at the thyroid, cortisol and DHEA-S at the adrenals, IGF-1 at the liver that writes it, testosterone at the gonads. Each mark carries the score of what is made there — the average of a whole axis would print the same number in two places and mean it in neither. Two hormones answer instead that there is no one place to mark: DHT is converted in the tissues that respond to it, and estradiol comes from the ovary in one person and from adipose tissue in another. An antibody is the one named exception to the rule — anti-TPO is made by lymphocytes, which are everywhere and mean nothing as a place, so it is marked at the gland it is raised against. A reason is recorded beside every placement, and beside every refusal to place. **The pancreas is on the map.** It is a system of its own now — amylase, its pancreatic fraction, lipase and C-peptide, the acinar cell and the beta cell together — and insulin, which stays in the carbohydrate panel because HOMA-IR is computed from it, is marked at the gland that secretes it. Nothing else about carbohydrate metabolism moves: glucose, HbA1c and the index itself say plainly that they are made nowhere in particular. **A reference interval that belongs to one sex is no longer lent to the other.** Six markers carry both corridors and were already handled. Every other marker carries one, transcribed from the forms of one person — and it was borrowed by anybody whose own laboratory printed no range. HDL's floor of 1.0 mmol/L is a man's; so are AST's ceiling of 40 U/L, GGT's of 60, and the intervals for DHEA-S, DHT and estradiol. Borrowed for a woman, none of them failed: each produced a verdict, and the wrong one. Every marker a body system is scored on now declares whether its corridor depends on sex — silence was what made the class invisible, so silence now fails the build — and a corridor is withheld, with the reason printed, rather than lent across that boundary. A person's own form is unaffected: it is always preferred, and this only ever governed what fills a hole. **A number with no reference interval beside it no longer costs its system points.** Such a marker scored 55 out of 100 — the score for «a deviation whose size cannot be assessed» — when there is no deviation, because there is nothing to deviate from; and it was counted among the system's deviations, while the marker list on the same screen said, correctly, that it was not one. It is now left out of the score and out of that count, and still counted as measured: a system whose only measured markers carry no corridor reports no score rather than a mediocre one. The five systems with no place on a body say so rather than being drawn on an organ they do not belong to; which is which is recorded with a reason beside each entry, and a system added to the radar without one now fails the build. The figure is drawn male or female from the profile; where the profile does not say, it is not drawn at all and the radar answers alone. **Windows is a supported platform.** The package, the command line, the local web application and the assistant skill run there, and a cell of the test matrix now says so on every commit rather than leaving it to hope. The promise and the check are tied together: a platform named in the package metadata with no runner behind it, or a runner with no promise in front of it, now fails the suite. Two things stay Unix-only and both are optional. The `crossread` wrapper is a shell script — on Windows the installed `scholion` command is the same core, so nothing is lost but the second name. Building a genome from raw reads drives external alignment and coverage tools through shell scripts; every such tool is looked for before it is used, so where one is missing the answer names it instead of failing. The data directory on Windows is `%USERPROFILE%\.scholion`, and `SCHOLION_REPO_DIR` overrides it as everywhere else. **The suite can be started without a shell.** Until now the only way to run it was a bash script, including the run the release procedure requires INSIDE the unpacked package. There is now a second entry point written in Python that does the same run, so a person who received the package can check it on a machine that has no shell at all. The build carries it, and a check makes sure of that: every way of starting the suite that the project's own automation uses must be present in the package a recipient receives, or the build fails. **Any gene can be asked about, not only one in the curated catalogue.** `scholion genome --gene CASR` used to answer "Gene CASR is not in the coordinate reference" — true about `loci.json`, which is a book of pharmacogenetic loci, and easily read as a statement about the genome, whose reads were there the whole time. The gene name is now resolved to coordinates from a local Ensembl annotation (or from Ensembl live, cached afterwards), the interval is cut out of the personal VCF, coding variants are separated from the rest by the coding exons of the canonical transcript, and what each one does to the protein is computed against the same reference the genome was called against — no web service is asked, and no coordinate leaves the machine. **A gene report prints coverage before it prints findings.** Every reassuring thing such a report can say has the form "no such variant", and that sentence is empty until the region is known to have been read. Read depth is computed directly from the alignment, without samtools or pysam, and it reproduces the project's own native callability run exactly — mean and every threshold, to the last base, on four genes across three chromosomes. Where a piece is missing the report names it and what would supply it: coverage without an alignment prints as "not measured — because", and "changing the protein: 0" is replaced by "not computed" wherever the reference was absent, because a zero and an uncomputed number look identical on the page and mean opposite things. The two blind spots of short reads — large exon-level deletions, and deep intronic or regulatory variants — are printed on every answer, not only on the reassuring ones. **Three analytes the dictionary did not know are now recognised.** A urine albumin-to-creatinine ratio, a urine microalbumin and a **total** T3 went through `ingest-labs` without a word: an unrecognised line is skipped, and a skipped line is indistinguishable from a line that was never on the form. All three sit on real forms of an ordinary laboratory, and the ratio is one of the two axes of KDIGO staging — a value whose absence changes what may be said about the kidneys. Total T3 gets its own table rather than joining free T3: the two are reported in different units (ng/dL against pg/mL) and the same number under the wrong one is out by an order of magnitude. The unit table learned `mg/g`, so the ratio prints with a unit rather than a code. **A polygenic percentile is printed with two quantities beside it: how stable the number is, and how informative the model is.** A bare percentile looked equally convincing for coronary artery disease and for intelligence, and it is not: for the second, the choice of reference population moves the number by some fifty points while the model's whole range separates the outcome by a few. The two are different properties and are shown as such, under every percentile in the `prs` report and in its JSON. Stability belongs to the measurement — the spread of the percentile across the five reference populations, its spread across the models scored for the trait, and the share of the model your file actually covers. Informativeness belongs to the model — its discrimination (AUROC), and what the score's range from the 10th to the 90th percentile does to the outcome, read per standard deviation as the catalogue reports it. A figure that is not on the machine is named as «not recorded» rather than left out, because a line with one number and a line with three look alike only when the missing two are silent. The two are not folded into one «signal to method» ratio: the effect size cancels out of such a ratio, and what remains is the population spread written in other letters. ### What is fixed **A corridor transcribed at one age was lent at every age, and most corridors did not say whose they were.** The rule that keeps one sex's interval from the other covered only the markers the body systems score; the other three hundred and forty held a corridor and were silent about it, so a man's prolactin or LH interval could still be borrowed for a woman whose form printed no range. And age was not modelled at all: IGF-1 and DHEA-S depend on it more than on sex, every laboratory bands them, and the dictionary held one corridor each. Every corridor now declares both — whose it is, and whether age governs it — with three states for age: independent, banded with the band on record unknown (lent to nobody), or a band in years (lent inside it; an unrecorded age is not lent a band). A corridor that could not be checked against a form or a standard interval says so in a word — `unreviewed` — and is lent to nobody until it is; nine are marked so, the estrogen-metabolite panel among them, and that number may only shrink. PSA is a different case again: not a man's interval but a test that exists for the male sex only, and for a woman's profile the value is shown with that sentence rather than with a borrowed or withheld corridor. The reason is printed beside the value in each case, and it names what would supply the corridor: the range on the person's own form, or the birth year in the profile. **A value was judged by a range its own form did not print.** The rule that the range on the person's own form wins held only at the level of the marker, which keeps the first range it met and is not rewritten by later forms; no stored value carried the range printed beside it. A September draw was then judged by a corridor recorded in December, or by one entered by hand. An ionised calcium the form called a hair under its 1.10–1.35 read as deeply low against a 1.16–1.32 recorded months before; a DHT five per cent under one corridor was twenty-three per cent under the other. The range printed on the form now travels with the value and the verdict stands on it; where a value has none, the marker's recorded range answers and the report says so; and a series whose corridor changed between draws says that its values compare and its flags do not. **A bound printed instead of a number lost its sign.** A laboratory that cannot quantify a value prints the limit — «< 0.09 nmol/L» — and the ordinary form reader stored the number without the sign, as if 0.09 had been measured. The engine has carried a censoring mark for such values all along, and two special readers (flora, titres) supplied it; the common path did not, and a series of «below the limit» results read as a level that later rose. The sign is now read from the text immediately before the value — `<`, `>`, `≤`, `≥` and their spelled-out forms — so a bound in a reference column further along the row cannot be mistaken for it. **Calcium measured by two methods was filed as one series.** Elemental analysis (ICP) prints calcium in mg/L; a biochemistry panel prints total calcium in mmol/L; one marker caught both and converted the second into the first by molar mass. The two methods disagree beyond rounding — thirteen per cent on the same person two months apart — and a series that holds both presents a change of method as a trend. Total calcium is now a marker of its own, the elemental one no longer reads a biochemistry form, and the rule is written where the next mineral will meet it: a series belongs to a method, not to a substance — a unit conversion is legitimate within a method and not across methods. Magnesium and the other minerals have not been checked yet. **A form that printed «HOMA-IR» in the Cyrillic alphabet was not read.** The dictionary knew the index by its Latin spelling only; the file was refused with an honest reason and the value had to be entered by hand. Both alphabets are recognised now, and «insulin resistance index» as well. **A report could not be redirected to a file.** `scholion labs > labs.txt`, and every other command whose output was piped or saved rather than read on screen, ended in a crash partway through — thirteen of sixteen commands measured. The reports are written with arrows, dashes and guillemets, and the encoding a redirected stream inherits from the system cannot always carry them. Output is now written as UTF-8 whatever the system would have chosen, so a saved report is the report. A character that still cannot be written appears as its own escape rather than as a question mark, so what was lost is visible. **Files were read in whatever encoding the machine assumed.** A genotype table, a cached answer or an export written as UTF-8 and read back through a national code page does not fail — it decodes, wrongly, and where the read also asked for replacement characters the wrongness was silent by construction. Every text read and write in the package now states its encoding, and a check over the source refuses a new one that does not. Twenty-nine places were corrected; six of them were never reached by any test, which is why the check reads the source rather than watching a run. **Two programs writing the profile at once could lose a change.** Where the system offers no lock between processes — Windows has none — the local web application and a command line each read a file, changed one field and wrote it back; whoever finished last erased the other, with no error anywhere. A lock the program takes itself now covers that case, and where it is already held the second writer says who holds it and stops rather than writing over them. A lock left behind by a run that died is taken over after a minute. **The update button reached for a shell that need not exist.** On a machine without one it now says so in a sentence, instead of showing the failure of a program it tried to start. The search path it builds is also joined the way the platform joins it, rather than always with a colon. **The monthly reanalysis stopped at its first step.** The ClinVar annotation (`annotate_clinvar.sh`) died with «GEN: unbound variable» on the first scheduled run: the branch that looks for a reference FASTA named a variable that belongs to the orchestrator calling it, not to the script itself, and `set -u` stopped it at the first of three such places. All three now use the script's own `GENOME_DIR`. The annotation has not yet been re-run against the fresh ClinVar release; until it is, findings stand on the release recorded in the profile. **A profile that spelled its sex «f» or «m» could be measured against the wrong reference row.** Several spellings of sex are accepted when a profile is written — «f», «female», «woman» and their male counterparts — but the reader that picks the applicable row of a multi-row reference interval compared the raw spelling with «male»/«female», so for any other spelling the sex half of that choice was silently off: a men-only row could fit a woman, a women-only row a man. The reader now recognises sex the way everything else does. A profile created by `scholion init` was never affected — it can only write the two spellings; the demonstration profile and imported or hand-edited profiles were. **The version shown could be another installation's.** With any `scholion` distribution installed on the machine — an older one from the registry, kept for comparison — a copy run from its source tree reported the installed number rather than its own, in the server's header and in `--version`. The tree now answers for itself; an installed copy still answers from its metadata. **The polygenic-score layer could stop starting on a machine that had run it the week before.** The scoring sidecar is fetched into an isolated environment on first use, and its own dependency declaration left one library unbounded; a newer major version of that library reached the index in August, and any fresh cache took it — the sidecar then died at start-up, before the first request, and `prs`, its page in the web application and the monthly reanalysis all reported a server that «exited without an answer». The resolution is now pinned below that major version wherever the sidecar is launched, from the application and from the setup script alike; a constraint already set in the environment by whoever runs it is respected rather than overruled. The message for a server that exits without answering now carries its exit code and names this cause and the pin, so a recurrence reads as «the pin was not applied» rather than as silence. **Every polygenic-score computation went to the server twice.** Two optional settings of the report — a wider pool of candidate models, and the models of child traits — are not accepted by the pinned sidecar; they were sent with every trait, refused with every trait, and each trait was computed again without them, so a full panel cost double for a setting that was never in force. A refusal is now remembered for the rest of the run and the panel pays it once. The two settings are honoured only by a newer sidecar than the pinned one, and the help says so. **Three models were computed for a trait and one was shown.** The client asked the server to score several models per trait and then to return only its favourite, so the spread between them — for a trait whose score explains five per cent of the variance, half of the answer to «how much of a number is this» — never reached the page. Every scored model now comes back, the spread is stored with the panel and printed as part of the percentile's stability. And the count of candidate models left unscored — eighty-five of eighty-eight for coronary artery disease — travelled without its rule; the rule is the report's own `--models` limit, and the count now says so wherever it appears. **A panel entered by hand and then re-imported from its form stood twice in every series.** The form prints the draw hour, so the re-import arrived with a finer date than the day entered by hand; the two did not match as strings, and the second point joined the series beside the first — identical values, one draw, nothing reported. Trends were then computed against a «previous point» that was the same blood. A point now stands in for every point of the same day whatever precision either carries, the more precise date is the one kept whichever order the two arrived in, and what the earlier entry recorded about the draw — its context, its source — travels to the replacement instead of being erased on every re-import. The importer names each replacement. The one case that cannot be decided — a bare day arriving against two timed draws of that day — is still reported and left to the person. **One file the reader could not handle stopped `ingest-labs` for the whole folder.** A traceback, every form after it unread, and no indication of which file was at fault. Each file is now read on its own: a file that raises is listed among the files nothing was taken from, with the exception's type and text, the rest of the folder is processed, and the command exits with a non-zero status so that a partial run is not mistaken for a clean one. The failed file is tried again on the next run rather than remembered as done. **The list of files already read was shared by every profile on the machine.** `ingest-labs` and `ingest-studies` remembered what they had read in the application-wide cache rather than beside the profile, so anyone working with more than one profile — a family member's, a temporary one — shared a single list, and a form already read for one profile could be silently skipped for another. The list now lives in the profile directory, one per profile (`ingest_labs_manifest.json`, `ingest_studies_manifest.json`); on the first run after upgrading the old list is carried over from the cache and the run says so once, so no form is read as new because of the move. Both loaders report the carry-over as a `manifest_moved` field in `--json`. **The reach baseline could not be accepted for a commit that added a module.** The suite fails when a module of the package has no accepted reach, and its message says to run `--accept`; `--accept` in turn refused to record a suite that had failed. Adding any module therefore closed a circle the tool could not be talked out of, and the only exit was to seed the new module in `test_reach_baseline.json` by hand. `--accept` now enters missing modules at 0.0 before the suite is measured, drops entries whose file is gone, says which it did, and records the measured numbers over the seeds. When the suite fails anyway the file is put back exactly as it was. `--strict` is unchanged and still fails on a module nobody has reviewed. ### A series break `knowledge/lab_markers.json` now records whose reference interval each scored marker holds, and a corridor that belongs to one sex is no longer lent to another. On unchanged input that changes the verdict in one place: a profile whose sex differs from the corridor's, for a marker whose own laboratory form printed no range, used to receive a flag and now receives the value together with the reason no corridor is shown. Thirty-six markers can be affected — the six the body systems score (HDL, AST, GGT, DHEA-S, DHT, estradiol) and thirty more whose adult interval is sex-specific: the red cell count and ESR, iron and transferrin saturation, the gonadal and pituitary hormones, calcitonin, osteocalcin, leptin, PSA, urine creatinine and the estrogen metabolites — and only where the range was borrowed: a person's own form has always been preferred, and this governs nothing but the hole it filled. The remedy is the form itself, or a range recorded by hand. Age is the same line, one axis over. Four markers whose interval every laboratory bands by age — IGF-1, DHEA-S, inhibin B and total PSA — held one corridor each, transcribed at one age, and lent it to everybody. The band on record is not known, so the corridor is now lent to nobody and the reason is printed; a value that used to receive a verdict against it receives the value alone. The same remedy applies. Every corridor in the dictionary now states both — whose it is, and whether age governs it — and a marker that does not fails the suite, because absence was how the question used to be spelled. A verdict now stands on the range printed beside the value rather than on the range first recorded for the marker. For a value whose own form printed a different range from the one on record, the flag can change either way; the value does not. Which ruler each flag stands on is printed, so the two can be told apart on the page. The score of a body system also moves where a measured marker had no corridor: such a marker used to take 45 points off its system and no longer takes any. The overall health index is a mean over systems, so it moves with them. Values from either side of this line belong on one chart only with a note. ### What is retracted No stored value changes and no conclusion is revisited. One correction above — the spelling of sex — is to how a reference row is chosen for a profile spelled other than «male»/«female»; what that asks of such a profile is under the next heading. The rest are to how reports are written and how files are read. ### What needs recomputing **Run `scholion ingest-labs --force ` — if forms were ingested by an earlier version.** The folder is read again, and for each of these what an earlier version stored is corrected: reference intervals picked without the sex filter, for a profile whose sex is spelled other than «male»/«female»; urine albumin-to-creatinine ratio, urine microalbumin and total T3 lines that were left out; a hand-entered day beside the same draw's timed re-import, collapsed into one point that keeps the timed date, with every collapse named; the range each form printed, which replaces the marker's recorded range a value was judged by until then; and the sign of a bound («< 0.09»). Points entered by hand before the form was read keep whatever was typed. **By hand — if a biochemistry total calcium was ingested by an earlier version.** It sits in the elemental series converted to mg/L. Reading the folder again files it under total calcium, but the converted point is not removed from the elemental series; remove it, or the report shows a method change as a trend. **Run `python3 -m scholion.prs report` — if a polygenic panel was computed by an earlier version.** Such a panel carries no spread across models and no AUROC, and the report prints «not recorded» in their place. Recompute it, then rebuild the panel with the panel-build step of the genome guide; the spread across reference populations comes from the ancestry-sensitivity step of the same guide, run once per panel. **By hand — if a report saved from an earlier version stops short.** It may be truncated at its first unwritable character; running the same command again produces the whole report. ## v0.4.8 — 31.08.2026 ### What you can do now **`ingest-studies` names every file it took nothing from.** The report used to be four counts, and the number of files seen was never reconciled against them: a file that was read and then dropped moved no counter at all. Each such file is now listed with the reason it produced nothing — the PDF gave up no text, it is a laboratory form that the other loader owns, it reads like a study but no conclusion could be lifted out of it, or this loader cannot place it at all. The counts and the named files add up to the files that were looked at. **A PDF holding several studies is reported with what is inside it.** A discharge summary — several examinations, and often a laboratory panel, in one file — is listed section by section with each section's own date, together with the plain statement that this loader cannot split such a file yet and that nothing from it reached the profile. Until now such a file was declared a laboratory form and handed to the laboratory loader, which dropped it for a reason of its own, and neither report held a line about it. **A decision you made about a wearable series survives the next import.** A rebuild is written from the export every time, so an edit made inside `wearable_trends.json` was lost at the next run — a weight point removed by hand as physically impossible came back as soon as a fresh export was read. Put the decision in `profile/wearable_corrections.local.json` instead: `remove` or `replace` a month of one metric of one device, with a reason. The import applies them over the rebuilt series and says which it applied, which it refused and why, and which no longer match anything — a reason is required, and a correction without one is refused rather than applied. `LOADING-DATA.md` describes the file. **A monthly point from a wearable now says what it stands on, and a movement too small to see is no longer given a direction.** Each month carries the number of days that held a reading, the days the month had, the median beside the mean and the spread. From those the lifestyle answer works out the smallest difference the data can tell from its own sampling, and when a shift is smaller than that it says so instead of naming a direction — for a monthly sleep series measured on a typical month that threshold is on the order of several minutes, and movements under it were previously reported as improvement or decline. A month measured on part of itself — nineteen nights of thirty-one — says that too. A series written by an earlier version carries no sample description and behaves exactly as before: nothing is invented for it. **A laboratory arrow says whether the change can be told from the marker's own scatter.** «↑ 44 % since the previous measurement» is arithmetic on two numbers and it stays; what follows it now is whether this marker's own history has ever shown itself able to tell a change that size from its own wobble. The threshold is measured from that history — no outside table is required and no coefficient is assumed — and it is reported with it, so it can be argued with. A rule that suggests a test because a marker is trending no longer fires on a movement the series cannot distinguish: a suggested test costs a real draw of blood. Where the history is too short to support the statement, none is made. **A genotype read from a VCF says how well the reads actually supported it.** Depth said how many reads there were; nothing said how they were divided. A heterozygote whose reads split 15/85 — a mosaic, a duplicated region, an artefact — was presented in the same words as one that split evenly. The allele fraction and the caller's own quality score are now read, and everything worth confirming about a call arrives as one list with the measurement that raised each entry, instead of three separate flags a reader had to know to look for. These are quality-control heuristics and they say «have this confirmed by another method», never «this is wrong». **A gene a variant file cannot answer now says so, instead of showing a tag SNP.** CYP2D6 decides codeine, tramadol, tamoxifen and the tricyclics, and it is settled by the full diplotype — copy number and phase — not by any single position. Asked about codeine, the answer used to print the gene, the word «important», a bare genotype and the promise that a full genome would bring the rest, which for this gene is not true. It now states that a diplotype is what decides it, shows the tag SNPs under a name that says what they are, and names the two things that actually close the gap: a star-allele call over your reads, or a laboratory report that states the diplotype — the only route open to somebody who has no alignment file. A diplotype already on file is used as before, and the sentence beside it now follows where it came from instead of asserting the caller's name over both cases. **The genes a «nothing found» cannot rest on can be handed to a laboratory.** The coverage table knew which genes were under-read and could give the list to nobody: a percentage names a gene, and re-reading something needs coordinates. The intervals the measurement was made over are now recorded and the weak list exports as a BED, worst gene first, each interval carrying its own percentage. The track line states that these are gene loci with a margin rather than coding sequence — a small dropout inside an exon barely moves a locus-wide percentage, so the file is a worklist of genes to look at again, not a map of the bases that were missed. A coverage table written before the coordinates existed is refused with the run that would fill them in: a gene name is not an interval and none is invented from it. ### What is fixed **A conclusion written in mixed case produced an empty record, and an empty record was dropped without a word.** The recogniser accepts the heading whatever its case; the extractor beneath it demanded the word in capital letters and required the text to begin on the following line. A document that writes the heading in ordinary case, with the text after a colon on the same line, was therefore accepted as a study and then yielded nothing at all. Both shapes are read now, and the printed disclaimer that contains the same word is not mistaken for the finding. **A laboratory point is dated in one of three shapes, and anything else is refused.** A month, a day, or a day with the clock time of the draw — the last one because two draws can fall on one day and a key no finer than the day loses the second. Nothing checked the string before: a value that was not a date at all would have become a key of the series and been sorted and charted beside real dates. And when the same period is already present at another resolution — a month point and a dated point of that month, one measurement standing twice — the import now says so rather than quietly holding both. **A doubled measurement was found and then not mentioned, for anyone whose results arrive as a table.** The importer notices when one measurement ends up in a series twice at two resolutions — a month point and a dated point of that month. It said so for results read out of a PDF and stayed silent for results read out of a CSV or TSV export, because the two kinds of file reach the profile by different routes and only one of them carried the report. The doubling was detected in both cases and shown in one. Both now say it. ### What needs recomputing **Run `scholion ingest-studies --force ` — once, if studies were ingested by an earlier version.** Studies that were dropped in silence may now be read, and whatever is still not taken is named in the report. **By hand — if you want the BED of under-read genes.** Measure coverage once more: the measurement now records the intervals its percentages were taken over, and a table produced before it did does not carry them. **Run `scholion ingest-garmin` — once, if Garmin months were imported by an earlier version.** Existing months gain the description of their sample; the values themselves do not change, so charts and comparisons are unaffected. Until an import has run, a series simply has no sample description and no statement about what is distinguishable is made for it. ## v0.4.7 — 27.08.2026 ### What you can do now **The Ouroboros Hub skill now says where your files go, on a page of its own.** Installed there, Scholion arrives by a click into a machine whose paths you have never seen and cannot list. Until now it registered thirty tools and nothing else: no data directory was created, and the first thing the skill said was a tidy report — `markers: 0`, `genome: not connected` — about a person it had never been given anything about. A statement of absence, phrased as a finding. Enabling the skill now adds a **Scholion** tab to the Widgets page, and that tab is the first thing to read: - it names the data directory this installation actually uses, and the exact folders for laboratory forms and for the genome; - a button lays that directory out — empty templates, plus a README in every folder saying what belongs in it. Pressing it a second time writes nothing, and it never touches a file that already exists; - if your files already live in a folder of their own, a field points at that folder instead of copying them. The directory the skill creates is one the host keeps, so it survives restarts and is not scattered inside a container. To put it somewhere else, set `SCHOLION_REPO_DIR` (the whole layout) or `SCHOLION_PROFILE_DIR` (the profile alone) in the host environment before enabling — either is respected, and nothing is moved. Nothing changed for the command line or the local web application: the same layout is what `scholion init` has always written, and the tab creates it through the same command. ### What is fixed **A freshly created, empty directory was described as a connected genome.** The introduction the skill prints was reading the genome section of the report as «present» rather than reading its ready flag, so a person who had just pressed the button — with no VCF anywhere — was told the genome was connected and then handed a list of loci «still unread». Both halves were wrong and the second made the first look substantiated. It now says `not connected` until a VCF is actually there, which is what `scholion genome-status` said all along. **A profile that cannot be read is no longer reported as an empty one.** If a file in the profile is malformed, the tab now says so, with the reason, instead of showing zeroes that look like a person with no history. ### What is retracted Nothing. No stored value changes, and no conclusion drawn from a previous version needs revisiting: the corrections above are to what the skill said about its own state, never to a reading of anybody's data. ### What needs recomputing Nothing. ## v0.4.6 — 25.08.2026 ### What you can do now **A tool that installs skills from a repository can now find this one.** `npx skills add` and its kind read a repository rather than a package: the root if it holds an entry, then `skills/`, then the agent folders — the documented shape being `skills//SKILL.md`. None of those existed here, so the entry was reachable only by such a tool's fallback recursive search, at a path whose folder is called `skill` while the file inside calls itself `scholion` — and the format requires those two to agree. The published repository now carries `skills/scholion/`, and it holds the entry and nothing else. That is the licensing decision rather than economy: what is published as a skill is the entry; the long instruction and the canon of rules stay with the package they are printed from, under their own licence, and a folder carrying both would put one licence over two bodies of text. Unlike the standalone skill folder in the downloadable archive, this one is not excluded from the repository — a tool fetches it from there, and a folder kept out of version control does not exist for the purpose it was made for. **An assistant that reads skills from a folder can now be given this one, in one line.** Several hosts read the same path, with no registry, no account and nobody's moderation in between — and until now nothing in this project mentioned it: ```bash mkdir -p ~/.agents/skills/scholion cp "$(scholion skill --path)" ~/.agents/skills/scholion/SKILL.md ``` That single file is the whole installation, which is what makes it worth doing and also what it had to be repaired for. **The entry did not name the tool server.** A host with a plugin mechanism finds `scholion mcp` through its own means; a host that reads only this file had no way to learn the door existed — so the door built for exactly those runtimes was invisible to them. It names the tool server and the in-process module now, and says that neither writes to a profile. **And it pointed at reference texts that a one-file install does not have.** The full instruction and the canon of safety rules sit beside the entry in the downloadable bundle and nowhere else; the entry named them by path regardless, so a model following the pointer would report a missing file rather than ask for what it needed. Each is now named twice — the path, for the bundle, and the command that prints it out of the installed package, for every other way in. `scholion capabilities --json` lists this door beside the others, derived from the build: the size of the entry and whether it names the tool server are read off the file rather than asserted about it. `scholion doc connecting-an-agent` describes it in words, and the heading there has stopped counting the doors — it said «four» through the arrival of a fifth and a sixth. **The checks now run on the oldest Python the package promises, before a release rather than after one.** `pyproject.toml` says this runs on Python 3.10 and later. That sentence is a promise to everybody who installs it, and until now nothing checked it before a release: the matrix that runs every promised version lives with the published repository, so it answers about a version that is already out. It answered twice in two days, both times too late — once with a release build that failed and never reached the registry, once with a red matrix on a version that had. Both were the same shape: a repair verified on one interpreter and promised about four. `./run_tests.sh` now runs the whole suite a second time under the oldest promised version, taking that version from the promise itself rather than from a number written into the script — `scholion doc contributing` describes how to run the checks that come with the package. Where `uv` is available it fetches the interpreter and caches it; where it is not, the run says the check could not be made instead of passing over it in silence. `SCHOLION_SKIP_OLDEST=1` skips it while editing. Three places name that floor — the promise, the versions the matrix runs, and this step — and a test now compares them, so a version can no longer be promised and never run, or run and never promised. ## v0.4.5 — 24.08.2026 ### What was wrong **The README told you the plugin adds fourteen tools. It adds twenty-nine, and there are not four doors but six.** Neither sentence was wrong when it was written; both stopped being true and nothing was obliged to notice. They are corrected, and the numbers are gone rather than updated: a count kept by hand beside a count the build derives will go stale again, so the README no longer writes one and a check refuses a new one. The installation table for a skill folder was rewritten against each product's own documentation rather than from memory. `~/.agents/skills/` is the shared path and two runtimes read it as they are; the others keep their own folder and take the same single file. The table carries the date it was checked, because paths move and that is the kind of claim that goes stale quietly. The published format's own limits are now a test: the name a valid slug matching its directory, the description within its ceiling, the body within the recommended length, and no frontmatter field the format does not define. They are somebody else's rules, which is exactly why they are worth checking — a file that breaks one is refused with a message about YAML, and the person meets that instead of this product. **What the data cannot answer reached every way in except the one most people use.** `scholion limits` says what cannot be said from a profile and what would close it — and the page had never asked for it. Not a broken renderer: no screen called that route at all, so the whole layer existed for the command line and for an assistant and for nobody sitting in front of the interface. The Profile tab now opens with what the profile is still missing and what each absence costs — a reference interval withheld rather than guessed, the age-banded rows of a form that cannot be read — with the fields to fill them in directly below. And the caveat about which reference panel a percentile is a position inside is printed beside the percentiles, where the number is met. A check now holds the general rule: every route the server answers is asked for by the page, or is named with the reason it is not. Two writes are named — one superseded, and one an acknowledged gap: conclusions from a doctor can still only be loaded from the command line. **A percentile said which population it was a position in, and the answer could be wrong.** The reference panel a polygenic score is computed against decides what the percentile means: against another population it is not your position. The report carried a panel and, beside it, a claim that the panel had been settled — and those two came from different places. The claim asked the profile whether a panel was known; the number had been computed against whatever the scoring run was given, which fell back to Europe in a function signature. While a panel could only be typed in by hand the two rarely disagreed. Once it began to be determined from the genome, they came apart for everybody who had one: the screen said the panel was settled and showed percentiles computed against a different one. No error and no gap — an ordinary number with a wrong sentence attached. Three facts are reported now where one was: the panel the stored numbers used, whether it was chosen or fallen back on, and which panel applies today. Where they disagree the report says so beside the number and names both, rather than leaving it in a field. A result computed before any of this was recorded cannot say which panel was chosen, and that reads as «cannot say» rather than as «yes». `scholion prs` takes the panel from the genome unless told otherwise, and a run that had nothing to go on says it used a default. **Determining that panel is a step of preparing a genome now, and it can be asked what it does before it does it.** The tool that answers it existed and was reachable from nowhere — no command, no mention in the guide. Worse, it took no arguments and checked its inputs while being imported, so any invocation at all ran the whole job: about three hundred requests to a public database over somebody's real genome, from what was meant to be a question. It has an entry point now — `--help` explains, `--dry-run` names what it would read and write and fetches nothing, and a missing input is refused by name with where it comes from. The guide carries it in the sequence, at the point where the data it needs exists. **A check that travels with the package disagreed with itself between Python versions.** The tool that measures how much of the code the test suite actually runs asked the compiler whether a file has anything to execute. For a file with no statements in it at all — the vendored package carries an empty `__init__.py` — what compiles is an implicit return, and the line it is numbered at is 1 on Python 3.10 and 0 on 3.11 and later. The tool ignores line 0. So one empty file was measured on one interpreter and skipped on another, and the recorded baseline, taken on a newer Python, failed the suite on 3.10 over a module with no code in it. Anybody running the checks on the oldest Python this package supports would have met it — the checks travel beside the package and `scholion doc contributing` describes how to run them. The question is answered from the source now: no statements, nothing to reach, the same answer everywhere. ## v0.4.4 — 23.08.2026 This release is mostly about a medical record read from a file: what such a record holds that is not a laboratory number, and how the product names the codes it could not place. The rest is about statements being true — a number and the word after it, the version the local server gives out about itself, what a connectivity check is allowed to fetch, whether a download checked who answered it, and whether an index still describes the genome it was built from. ### What you can do now **The facts the application cannot derive can be given from the page at last.** Sex, year of birth, height and which wearable answers are preconditions: without them a dozen reference intervals are withheld rather than guessed, the age-banded rows of a laboratory form cannot be read, and there is no body-mass index. There is now a **Profile** tab, and it offers all four. The main wearable had no field anywhere but the command line. **The reference panel for percentiles is no longer a question.** It used to be one: `--ancestry EUR|AFR|EAS|SAS|AMR`, a superpopulation code nobody knows about themselves in those terms. What such a box collects is a guess, and a guess stored where a measurement goes is indistinguishable from one afterwards — while every polygenic percentile depends on it. Your own genome answers it. Comparing a few hundred of your genotypes against the five 1000 Genomes panels is a step of preparing a genome, and the result is used from where that step writes it; the Profile tab shows which panel applies and says whether it was measured from your DNA or set by hand. Until it has been determined, percentiles go on naming the default panel they used. The command-line flag remains as a deliberate override. Saying there is no wearable is an answer of its own. Until now «I do not own one» and «nobody asked» were one blank field, so anything listing what the profile still needs asked a person with no watch about their watch for ever. Choose «No wearable device» once, or `scholion profile --wearable none`, and it stops. **What the profile is still missing is now part of what the data cannot answer.** `scholion limits` already answered one question — what cannot be said here, and what would close it — and a missing precondition is exactly that. Each one now appears in that list with the sentence it prevents and the command that records it, and disappears from it once answered. An assistant reading the list at the start of a conversation therefore asks for what is actually absent, rather than from a list somebody typed into an instruction and then had to keep in step. **A body measurement in a bundle is now kept, not dropped.** This product has always taken a weight — from the command line and from the page — and an import that met one inside a medical record threw it away because it is not a laboratory analyte. It goes to the metrics layer where it belongs, with the same mark of whose measurement it is as everything else written today. Only where one of this product's own metrics holds the same quantity IN THE SAME UNIT: nothing here converts, so a weight in pounds is refused rather than joined to a series in kilograms. `scholion import-fhir ` reports the two layers separately, because one number for both would say nothing about either. **Height can be set from the command line at last.** The page has always had the field; the command had not, and the body-mass index needs it — `scholion profile --height-cm 178`. A bundle that states a height still does not apply it: a file may hold a relative or two people, and a height is one field of the profile rather than a series, so it is reported for you to set yourself. **A marker may now carry more than one LOINC code**, because a LOINC code is not one per analyte: the same substance has a different code by material and by method, and four of that bundle's unplaced observations were analytes this dictionary knows under a code it does not — glucose in whole blood beside glucose in plasma, LDL measured directly beside LDL calculated. Every additional code has to state why the two are the same measurement, and none ships: whole blood and plasma differ by about a tenth, so pairing them because the names match would put a systematic error into a series. The mechanism is here; the pairings wait for a source, and the refusal now prints the code, so each one can be looked up. **The suite now says how much of the code it runs, and will not quietly run less.** A thousand green tests is not a measurement, and this project had been reading it as one. The number, once taken, was 69.9% — with the module that implements provenance for every answer at 12.8%, and the VCF reader used whenever `pysam` is absent, which is most installations, at 35.4%. Nothing could have said so, because nothing was counting. `check_test_reach.py`, which travels with the package beside the other checking tools, counts — on the standard library alone and including the tests that run the command line in a real subprocess. It reports per module, `--strict` fails when a module falls below the reach recorded for it, and `--accept` records a new number deliberately. It is the last step of `./run_tests.sh`; `SCHOLION_SKIP_REACH=1` skips it while editing. No module is now under half. Seven that were — polygenic scoring, the provenance audit, the study loader, the watch import, every route of the local web interface, the online drug lookup and the VCF reader — carry tests for what they actually decide: which polygenic model is allowed to speak for a trait, that a score is not computed for an organ the person does not have, that a stored number which no report holds is told apart from one that a second method explains, that a rebuilt watch export cannot erase months it no longer mentions, that a judgement written about a study survives the file being read again, and that an empty answer from a database that was never reached is never printed as «nothing was found». Nothing about anyone's data changes, and no command behaves differently. ### What was wrong **A form the interface never showed.** The page holding sex, year of birth and height existed, worked, and was in no tab: it had never been reachable, in the whole history of the file, so in practice those facts could only be set from the command line — while the command's own help said the opposite. Every view the page defines is now checked for a way in, because a view nobody can open fails no test that calls its functions directly. **A recorded age and a recorded sex could both show as «—».** The age was computed from a year of birth alone, so a profile carrying a full birth date — which is what the demonstration writes, and what an imported medical record writes — reported no age at all while the file held one. And the page compared the stored sex against `male`, while the file is allowed to say `m`; a sex already given then showed as blank and was asked for again. Both are read through the one function that knows the spellings. **A value that cannot mean anything was stored rather than refused.** `--ancestry EURO` went in, and every polygenic percentile afterwards was computed against a reference population that does not exist — printed as an ordinary number, and without the caveat about a default one, because a value was set. Nothing downstream could tell. Populations, devices and spellings of a sex are now checked on the way in, by the same lists in every face; an unrecognised value is refused with what is accepted. A profile field a person did not touch is also no longer rewritten by a save of a different one. **«1 markers are printed without a reference range».** A number and the word after it did not agree — on the screen that says what the data cannot support, which is the screen this project argues from. The machinery for it existed and was in use; twenty-three messages simply did not go through it, and in Russian, where a noun after a number takes three different forms, the same lines read as carelessness about everything else on them. All twenty-three are repaired, and the rule that replaces them is mechanical: a message may not put a number immediately in front of a word unless it carries all the forms of that word. The page and the package now also agree on which form to choose, checked against each other rather than each against its author. **A body weight was reported as a laboratory code nobody knows.** Importing a FHIR bundle, ten of the twenty-three observations it could not place were not analytes at all — height, weight, body mass index, temperature, heart rate, respiratory rate, oxygen saturation. Calling them «a code this build does not know» sends the reader looking for a dictionary entry that should never exist. They are named for what they are now, each with the code and, where one of this product's own metrics holds the same quantity, with that named too — and where it does not, the reason is written down rather than left as silence: a heart rate at a visit is not the resting home pulse, and a body-mass-index percentile against an age-and-sex reference is not the index this product keeps. Nothing is written from them yet; what changed is that the list of what a bundle held is no longer misleading. On the same bundle the count of «not in the dictionary» falls from 23 to 13. **An observation the dictionary could not place named only its label.** Importing a FHIR bundle lists what it did not take and why; for a code the dictionary does not know it printed «Calcium» — while the code, which is the thing an entry is keyed by and the only way to look the analyte up, sat in the record one line short of the screen. It is printed now: «Calcium (49765-1)». `scholion import-fhir --dry-run` shows the list without writing anything. **A profile with no birth year stopped a whole batch of laboratory forms.** Reference ranges on a form are often given by age band — one row for 40 to 49, another for 50 and over — and choosing the row that applies needs the reader's age. A profile without a birth year is an ordinary state, one the setup warns about and proceeds from; the comparison was made anyway. `scholion ingest-labs` then failed on the first form carrying such a row, and the run stopped there rather than setting that one file aside and going on. An age that is unknown is not the same claim as an age that fits. An age-banded row is now neither confirmed nor excluded — the standing a row with no group label has always had — and where a block offers several of them, the rule this product already followed takes over: more than one row could apply, so none is taken and the marker arrives with no reference corridor at all. That is the safe direction rather than a gap: nothing without a corridor is ever called a deviation here. Sex is a separate question and still filters rows when the age is unknown. **The connectivity check accepted an address to fetch, and it should never have accepted one.** The local server answers a page in your own browser, and any other page open in that browser can ask it for something. The check that says «is there internet from here» took the address to try as a parameter. The first repair made it verify that address — https only, and only the handful of hosts this product itself talks to — which closed the hole it was written for and left the shape: a host that is allowed still accepts any path and any query, so an address could still carry something outward, and a check over a string somebody else composed is a race with whoever composes the string. It no longer takes an address. It takes the NAME of a probe, and the address behind that name is a constant in the source; a name that is not in the table is refused and nothing is opened. `scholion serve` shows the result in the same place as before, and the page asks for it the same way — only `?url=` is gone, replaced by `?target=`, and no caller can name an address again. **A reference download could stop checking who was answering, and said nothing about it.** Two of the catalogues this project builds are fetched from public reference databases: the collection of longevity variants, and the list of genome positions those variants are read at. When the certificate check failed, both downloads simply repeated the request without it. Whether a certificate was the problem at all was decided by reading the wording of the error — so a message that merely mentions SSL, such as a protocol mismatch or a proxy refusing the connection, was enough — and nobody was asked for permission. The note written beside it argued that the archive's own integrity check made this safe; it does not. That check catches a damaged transfer, not a substituted one. What could have come of it: a catalogue of variants, or a set of coordinates a genome is then read at, supplied by somebody other than the database named on it, and afterwards reported with exactly the confidence the real thing would carry. Nothing on screen would have looked unusual, which is the whole difficulty with this class of fault. Both now refuse. Verification is skipped only when the person allows it out loud (`SCHOLION_TLS_INSECURE=1`), only when the failure really was a certificate — decided by the kind of failure, not by its wording — and every request made that way prints a warning as it is made. This was already how the application's own lookups behaved; it is now how everything behaves, and a check that reads the whole source tree refuses any new place that goes back to deciding for itself. **A genome rebuilt while the application was running could report a variant as absent.** The reader that answers questions about a variant file without external tools keeps that file's index in memory. It kept it under the file NAME alone, and nothing ever discarded it — so a genome re-called and re-indexed while `scholion serve` was left open went on being read through the previous index. The offsets in it no longer describe the file, the read lands nowhere useful, and it comes back with no rows. No rows is the answer that matters. For a variant file produced the usual way, the absence of a row at a position means «the same as the reference» — so a stale index did not produce an error, and did not leave a gap. It produced an ordinary genotype, stated with the ordinary confidence, at a position that may carry a finding. The index is now remembered together with the modification time and size of the index file, so a rebuilt genome is read afresh. Anyone who never rebuilds a genome under a running interface was never affected, and a command-line run never was: it starts a new process each time and had nothing to remember. ### What needs recomputing Nothing, unless a warning about unverified certificates was seen while the reference catalogues were being built. They are rebuilt by rerunning the build; the data already in a profile is not affected, because those files are a shared public reference and hold nobody's measurements. ## v0.4.3 — 22.08.2026 This release is about files that arrive wrapped, laboratory forms printed for an American reader, and — the larger half — a report that stops claiming more than the file it was given can support. ### What you can bring it now **A consumer DNA test still inside whatever the provider wrapped it in.** A VCF compressed with bzip2, a VCF inside the provider's zip, a VCF whose file name carries URL-encoded brackets — all of them ordinary VCFs, and all of them now read. A file is identified by its bytes before its name, so an export that arrived zipped is no longer «an archive, not opened blind here»: looking inside is the opposite of blind. None of these can be seeked into, but the catalogue is fifty-four loci, so one cached pass over the file answers all of them — and the same pass measures the call set exactly rather than by probe. `scholion genome-status` names what it found. **A genotype table from a chip, with the ceiling of the chip attached.** It is read the same way, and a position nobody typed is reported as not read — never as the reference. **Three formats stay named and unread, each with its reason**, because a gap left unexplained is indistinguishable from an oversight: a VCF that went through a spreadsheet (its structure is gone rather than hidden — the honest answer is the original export), a Complete Genomics `var` table (the vendor ships a converter that is correct by construction, and a second implementation here would be a worse one), and an archive of the FTDNA era keyed by internal SNP numbers with no rsID anywhere in it. **Laboratory forms printed for an American reader.** A LabCorp report prints its dates as a table — the column headings on one line, the values on the next — and a reader expecting the label and the date side by side finds neither. The heading and the line under it are now read, and only while «collected» is the leftmost date column; where that cannot be established, nothing is read rather than the wrong column filed. A two-digit year is read. And a page carrying `12/15/2008` has already said which order it prints in, because there is no fifteenth month, so `12/10/2008` beside it is the tenth of December and not a coin toss — the evidence is on the same page, in the same table, from the same instrument. A page whose dates contradict each other is still refused, and so is a lone ambiguous date with nothing on the page to settle it. Two weaker witnesses are used only after the page and are named as what they are: «Ordered Date», which some lipid panels print instead of a draw date, and the file name, where a month spelled out is read and a numeric one is not. `scholion ingest-labs` reports what each file gave. **An exome or a panel whose header does not say which build it is in.** The build decides whether the genomic layer may answer at all, and it was established from the lengths of the chromosomes in the header, or — failing that — from a variant lying past the end of chromosome 1, which only a GRCh37 file can have. A capture panel has neither: no contig lengths, and no rows out at the telomere to probe with. Three providers stamp their own pipeline into the header and build against one reference only, so that signature is now read as well. Where a pipeline serves both builds, the provider's name alone settles nothing and is not used on its own: DRAGEN counts only together with the reference path in the same header. `scholion genome-status` prints which of the four witnesses answered — a build inferred from who made the file is a weaker claim than one measured off the file, and it no longer looks the same. **A WHOOP export.** The zip that arrives by email — or the folder it was unpacked into — is now read: recovery, day strain, resting heart rate, heart rate variability, respiration, blood oxygen, skin temperature, sleep with its stages, sleep debt, consistency and efficiency, and workouts. `scholion ingest-wearable ` takes either device and says which one it recognised; the Garmin command still does exactly what it did. WHOOP does not publish the layout of that export, and a layout nobody published is one that can change without telling anybody, so the column names are read rather than assumed. Each header is looked up in a table that ships as data, and a column that is not in the table is **listed by name** with nothing read from it — an export carrying a column this has never seen says so instead of quietly dropping a measurement. **The reader can be named.** Three readers can put a genome through: two external programs and the one that ships here. Until now whichever was installed won, which means the same file on two machines was read two different ways with nothing saying so. `SCHOLION_GENOME_ENGINE=tabixlite scholion genome-status` pins it — useful for anybody comparing two runs, and the only way to see what somebody with no external tools installed actually gets. A name that is not one of the three, or a reader that is not installed, stops the genomic layer and says which: falling back quietly would answer a different question from the one asked. **Two devices no longer become one line.** A Garmin and a WHOOP both report resting heart rate, heart rate variability, respiration and sleep, and they do not measure them the same way: different window, different algorithm, different place on the body. A measurement is now stored together with the device that made it. Where both measured the same thing both series are shown, neither is averaged into the other, and no conclusion is drawn from either until the question is answered — `scholion profile --wearable whoop` names the one that speaks. For anybody with a single device nothing changes at all. A lifestyle file written by an earlier version is brought to the new layout when it is read, and the device it is filed under is taken from what that file says about itself: a file naming no device is filed as unspecified rather than assigned one. **Every number says which device measured it.** The strip above the lifestyle section names the devices in the profile rather than the file they are kept in, and each metric card carries `measured by …` under the value. Where two devices report the same thing and none has been named to answer, the page says so at the top of the section and on each affected card, and offers to settle it in one click. A column the reader of a WHOOP export does not recognise is listed by name after every import, and can be named once in `profile/wearable_metrics.local.json` so the next import reads it — additions merge per column and cannot delete what already works. ### What was wrong **Your own measurement joined a fictional person's history without a word.** The demonstration profile marks itself as invented, and the mark was on the FILE. Adding a real value to it therefore worked: the point joined a series of generated numbers, the file went on declaring itself synthetic — by then untrue — and the overview counted the abnormalities of somebody half imaginary. Every datum now records whose it is, and one profile holds one person. The first real value written — by `scholion add-lab`, `scholion add-metric`, `scholion add-med`, an import, or the same actions on the page — erases the demonstration and says so, naming every file it removed. Nothing is lost by that: the demonstration is generated from a fixed seed, so `scholion init --demo --dir ` builds it again exactly as it was, while a series mixing invented values with measured ones could not be separated afterwards by anybody. Only data carrying the mark can be erased, so an ordinary profile — which carries no mark at all — is never touched. **A published reference genome could be read under somebody else's laboratory history.** A genome anybody may look at can be fetched to see the genomic layer work, and it belongs to a real, consented, published person — not to the reader, and not to the fictional one of the demonstration. Read beside either it produced one case out of two people: this genotype, that history, a single report. The fetched folder now says whose genome it is, and the genomic layer stays silent while the two are in one profile, naming both sides and the folder to give it instead. A genome folder that says nothing is still read as before: silence is not a claim, and every genome anybody already has is unmarked. **A chip stopped being a chip by arriving as a VCF.** ClinVar findings, the secondary-findings list and polygenic scores can only be answered from a broad call set, and the gate that closes them keyed on the CARRIER — a consumer array file — rather than on what the input actually holds. A genotyping panel distributed as a VCF (553 197 variants) and a table of chosen positions (48 838) both went the other way and were told «this has not been annotated yet — run the preparation», which is an invitation to do the exact thing the gate exists to prevent. Breadth now decides: a chip, a genotype table, a panel, a low-pass screen, a mostly-imputed file and half a call set all close those three paths, whatever they arrived in. An input whose breadth could not be measured closes them too — refusing a genome that could not be probed costs one command, and opening a screen that was never measured costs a finding somebody may act on. **An approximate date stopped looking approximate the moment it was stored.** A form printing no draw date is read from two weaker witnesses — an «Ordered Date», which some lipid panels print instead of one, and the file name. That caveat was printed once, while importing, and afterwards the point sat in the series indistinguishable from one dated by the draw itself. Every laboratory point now records which of the three answered for it. Points stored before this version say «not recorded» rather than claiming a form; `scholion limits` counts them in one line instead of marking each, and re-running `scholion ingest-labs` over the original forms fills them in. **«A whole genome» was printed above files that were not one.** Every readable VCF used to be described as «a whole genome — every base the sequencing reached, so both single variants and polygenic scores are computable». Breadth is now measured rather than assumed: observed variants per megabase through three fixed intergenic windows, the composition of substitutions against insertions and deletions, and whether the file carries reference blocks. The thresholds are calibrated against measured files rather than chosen for roundness — a 30× whole genome measures 1547–1616 variants per megabase, a genotyping chip shipped as a VCF 147–452, a low-pass screen 17–81. Where the measurement cannot be made, the report says so and promises nothing. Two consequences carry the weight. The FILTER column is now read: a file whose rows are almost entirely imputed used to have every one of them signed «called from the VCF» — the output of an imputation model handed over as a measurement. And a call set split by variant type now refuses a variant it cannot hold: a file containing only insertions and deletions used to answer `TT (reference)` for APOE, which is a statement about a person derived from a property of a file. **`Genome connected. File: None`** — twice wrong in eight words, and it was what every holder of a chip export saw, while the path to that export sat in the machine-readable status all along. The status of an array now names the vendor, the number of positions, the file itself and the ceiling of the chip. **A refusal printed its own internal label instead of a sentence.** The commonest question anybody asks of a chip came back as an unreadable string in brackets. The missing lines are written in both languages, the head of a refusal can no longer come back as a label, and every value able to reach it is now walked. That walk immediately turned up one more: a call ambiguous by strand fell through every branch and vanished, so two DPYD markers — the pair used to dose chemotherapy — answered «cannot be read». **«No genome found» on a file that was sitting right there.** Every refusal now names the file by what is inside it and says what to do with it. **An answer printed a position from the other build.** A GRCh37 file was read at GRCh37 positions and printed the catalogue's GRCh38 number, sending the reader to a base that is not the one that answered. Answer and refusal both name the coordinate set they were read in. **A page open in your browser on another site could ask your own Scholion instance to make an outbound request, and get nothing back but learn whether it succeeded.** Every route on this server that changes what is stored is already refused when a foreign page calls it — that is what stops a tab on another site from quietly editing a profile just because a browser can always reach `127.0.0.1`. The one route that does not store anything but does make its own request to the outside — the diagnostic check behind `scholion serve`'s connection page — was not covered by that refusal, because it answers a plain `GET` rather than a `POST`, and the check had only ever been wired to `POST`. It is now refused the same way. Separately, the list of addresses that check is allowed to reach was being read by scheme and host alone; an allowed address that itself answered with a redirect was followed wherever it pointed. It no longer is. ### What is retracted Anything in this list was produced by an earlier version and should not be relied on: - any ClinVar finding, secondary-findings answer or polygenic score computed from an input that is not a broad call set — a chip delivered as a VCF, a table of chosen positions, a genotyping panel, a low-pass or mostly-imputed file, or half a call set; - any description of an input as a whole genome, where the input was a chip export, a low-pass screen or a partial call set; - any reference genotype returned from a call set that cannot hold that kind of variant — most visibly `TT (reference)` for APOE out of a file containing only insertions and deletions; - any row signed «called from the VCF» that came from a file of imputed genotypes; - any position printed against a build other than the one the file was read in. ### What needs recomputing Nothing stored changes value and the knowledge base is untouched, so series already on a chart stay comparable. What changed is what can now be read. **Run `scholion genome-status` and `scholion overview` — to see what can now be read.** **Run `scholion ingest-labs ` — if laboratory PDFs were refused because no draw date could be found.** ### Measured On a fixed set of thirty-seven third-party inputs, kept unchanged between runs: | | before | after | |---|---|---| | files read and answering | 19 | 24 | | false «no genome found» | 8 | 0 | | «a whole genome» claimed on an input that is not one | 7 | 0 | | `File: None` on a chip export | 12 | 0 | | internal labels in user-facing text | 6 | 0 | | laboratory measurements extracted | 126 | 387 | ## v0.4.2 — 20.08.2026 The safety rules now travel with the tools, not only with the skill. ### What you can do now **`sch_rules` — the rules this product is operated under, as a tool.** A model that arrives through the skill is handed the instruction and the rules with it. A model that arrives through the tool interface was handed a list of tools and nothing else: it knew what it could call and nothing about what it must not say. Every answer already ends in the one-line disclaimer, and a disclaimer is a boundary, not an instruction. The rules are now a tool any host can call — 29 tools in total — and the Model Context Protocol handshake carries a digest of them in the protocol's own `instructions` field, for the hosts that pass it on. They are the same rules `scholion skill --rules` prints, through a second door. **A version that is already published cannot be re-published changed.** The build refuses instead of letting the registry skip the upload in silence: it reads what actually travels out of the build configuration, compares it against what went out last time, and stops with «bump the version» when the two differ. Where it cannot reach the registry to ask, it says so and refuses rather than guessing — the alternative is a tag pointing at code nobody can install. ### What needs recomputing Nothing. No stored value changes and no answer changes. ## v0.4.1 — 20.08.2026 An assistant can now ask Scholion how to reach it, and what it must not say — and gets an answer instead of inventing one. ### What you can do now **`scholion doc connecting-an-agent`** — how to wire an assistant to this, for every way in: the command line, the Model Context Protocol server, the Ouroboros tools module and the Ouroboros Hub skill. It carries the configuration block an MCP host expects, what to do when the executable is not on the host's `PATH`, how to point the server at a profile or genome elsewhere on disk, and a two-line exchange that tests the server by hand with no host at all. **`scholion capabilities --json` carries an `access` block.** Every way in, the protocol the server speaks, the number of tools behind it, and the environment variables this build reads — scanned from its own source, so the list cannot outlive the code that reads it. The first field is authentication, and it says there is none. **There is no account, key, token or credential for any surface of this product**, and nothing to authenticate against: the analysis runs on the machine that holds the data, and the Model Context Protocol server is a local process spoken to over standard input and output — no port, no host, nothing on the other end of the pipe but the program that started it. That was always true and was written nowhere a program could read, so a program asked for a credential instead. It is now the first thing the build answers, and the claim is checked against the code rather than repeated. ### What was wrong **The Ouroboros Hub skill described a build that no longer existed.** It declared version 0.3.2 against 0.4, 23 tools against 28, and did not mention the Model Context Protocol server at all — so a host that read only that file saw a Scholion without it, and an assistant working from that description had no way to reach a surface that was sitting there. The version and the tool count are now written from the build rather than typed beside it, the same way the shipped documents and the assistant rules already are. **The model's instruction named the protocol and not the way to speak it.** It listed `scholion mcp` and stopped. It now points at the connection guide. **The local web page was not marked as being for a person.** `scholion serve` opens a page on `127.0.0.1` for someone to read. It is listed among the ways in and listed as human: an agent that finds an undescribed local page will try to drive it, and a door that is not for you is a fact worth stating, like any other refusal. ### What needs recomputing Nothing. No stored value changes and no answer changes. ## v0.4.0 — 19.08.2026 This release is about the files people actually have. A consumer DNA test, a genome in the older GRCh37 build, a hospital portal export, laboratory results as a spreadsheet — all of them are now read, where most of them used to be answered with «no genome found» or «nothing importable here». The second half is what the answers say about themselves: how much of a gene was actually read before «no findings» was printed, which sample in a multi-sample file was used, and — where an answer cannot honestly be given — which of several different reasons is the one that applies. ### What you can bring it now **A consumer DNA test — 23andMe, AncestryDNA, MyHeritage, FamilyTreeDNA, Living DNA.** This is the most common genetic file in the world and Scholion used to answer «no genome» to all of it. It is now a first-class input, including when it is still inside the `.zip` or `.gz` the provider sent. On a 23andMe v5 export, 46 of the 54 catalogue positions are present — APOE ε-status included, so the headline genomic answers work without a sequenced genome. Every position answers one of three things: read, attempted and not called, or not on this chip at all — because «not on the chip» and «you do not carry it» are different sentences. Six positions whose two strands are ambiguous are named separately instead of being handed over with the rest, and the things a chip cannot support — ClinVar screening, secondary findings, polygenic scores — are refused with the reason. **A genome in the older GRCh37 build.** Most files people actually hold are GRCh37: consumer chips, several sequencing providers, and most whole genomes more than a few years old. Scholion used to switch its genomic layer off on every one of them. All 54 catalogue positions now carry coordinates in both builds — each one taken from two independent public sources that agreed — so a GRCh37 file is read at GRCh37 coordinates. Nothing is converted between builds: the offset is not constant even inside one chromosome, so arithmetic would return a plausible position pointing at the wrong base. **Laboratory results from a health portal or an app.** `scholion import-fhir` reads a FHIR R4 bundle — a patient-portal download, Apple Health clinical records, an EHR export. Analytes are matched by their LOINC code rather than by the name a hospital happened to print, and units go through the same gate as everything else. The bundle names a patient; Scholion reports who it says and does not act on it. **Lab results as a spreadsheet.** CSV, TSV and plain text exports are read, not just PDFs. In a table each row carries its own date, which is how these files actually work — and which is why they used to import as one date for the whole sheet, or not at all. **American forms.** Dates written `03/14/2015` or `March 14, 2015` are read. When a date is genuinely ambiguous — `07/12/2015` is either July 12th or December 7th — it is refused by name rather than guessed at, because a mis-dated point silently reorders a seven-year trend. ### What the answers tell you that they did not before **«No secondary findings» now says how much was read.** The ACMG list of 84 actionable genes is only as good as the coverage underneath it, and that number had been computed and printed elsewhere for months without reaching this sentence. A negative over a gene read at 62 % is now qualified as one. **Sex-specific thresholds.** «Three times the upper limit of normal» is published as a pair of numbers, one for each sex. Scholion had stored only the product, computed from the male bound — so a woman on a statin with ALT 110 or CK 520 got nothing, in both cases in the dangerous direction. The rule is stored now and the number computed from it. Where sex is not recorded, the more cautious bound is used and said so. **Star-allele diplotypes are used where they exist.** If a proper pharmacogenetic call is in your profile — from PyPGx over a BAM, with copy number and phasing — it now decides the phenotype, instead of the answer being reconstructed from individual tag SNPs. `CYP2C19 *2/*17` means something a pair of SNPs cannot. **Polygenic scores say what they cannot do**, and a percentile about an organ you do not have is withheld and named rather than quietly printed. A low-confidence ClinVar hit now carries how strongly it is reviewed — a single-submitter «Pathogenic» and an expert-panel one are not the same claim. **`scholion flag-rate`** answers a question the documentation had been describing for a while: how often does this tool raise this flag at all? A rule that fires on everybody is not information. ### What it now refuses to do Four situations used to produce a confident answer where no answer was available. Each is now a refusal that names the file and the reason. **A file it cannot actually open is no longer «Genome connected».** A `.vcf.gz` compressed with ordinary gzip rather than bgzip looks correct from the outside and cannot be read at all. Every position in it came back «reference» — including a heterozygous APOE ε4 carrier, reported as a non-carrier. The same applies to a missing, truncated, or previously unsupported `.csi` index. Scholion now names the file, prints the one command that fixes it, and answers nothing until it is. **A folder holding several genomes.** It used to read whichever filename sorted first. A genome split across `chr1.vcf.gz` … `chr22.vcf.gz` therefore answered APOE — which is on chromosome 19 — out of chromosome 1, as «reference», while the right file sat unopened beside it. A folder holding two people answered about whoever came earlier in the alphabet. Both now list the files and ask (`SCHOLION_GENOME_VCF`). **A file holding several samples.** A trio or a family file puts several people side by side, and the first column belongs to whoever the laboratory listed first — possibly a relative. `SCHOLION_GENOME_SAMPLE` says which one is yours, and until it does, nothing is read. **A file whose reference build cannot be established.** A position looked up in the wrong coordinate system comes back empty for exactly the same reason a position with no variant does. That silence is no longer reported as «reference». And when the folder holds a BAM, a CRAM, a FASTQ pair, a BCF, a gVCF or a provider archive, each is named by what it is with the next step for that kind of file — instead of the same «the full VCF is not connected» that eleven different formats used to print at people whose file was lying right there. ### Also new * **`scholion mcp`** — Scholion as a Model Context Protocol server over standard input and output, so any MCP-capable assistant can call the same tools the Ouroboros plugin exposes. * **`SCHOLION_GENOME_VCF` and `SCHOLION_GENOME_SAMPLE`** — which file, and which sample inside it, is yours. `scholion genome-status --json` reports the sample name, how many samples the file holds, how many genome files were found, and the reason it is refusing when it refuses. * **The numbers behind the flags are written down.** Four thresholds that no published guideline fixes — how large a change counts as movement, how close to a reference limit counts as near it, and two more — are now stated in the knowledge base with what would replace them and, more usefully, what they do not license anyone to conclude. ### A series break Values computed before and after this version can differ on unchanged input, in six places: sex-specific decision thresholds (ALT and CK on a statin), star-allele diplotypes that were on disk and unread, the ClinVar review-confidence note, polygenic traits withheld for sex, lab points imported from tables and FHIR bundles that were previously skipped, and every genomic answer from a GRCh37 file. Do not put values from either side of this line on one chart without a note. ### What is retracted * **Any earlier «no reportable findings» read as unqualified.** It never was: it was a statement about what had been read, and now it says so. * **Any lab flag on ALT or CK for a woman, or for a person whose sex was not recorded.** The thresholds were male. * **«This position is not on the array»** for a consumer export that had been opened and re-saved in a spreadsheet. The file parsed to zero rows, and our failure to read it was reported as a fact about the chip. * **Any report that claimed a folder held nothing importable** when it held CSV, TSV or TXT: the search looked only for PDFs. * **Any genomic answer out of a `.vcf.gz` that was not block-compressed**, or whose index was missing, truncated or `.csi`. It was not reference; it was unread. * **Any genomic answer from a folder holding several genomes**, or from a multi-sample file where the sample was never named. Which person it was about was decided by sort order. * **Any «reference» from a file whose build was never established.** ### What needs recomputing **Run `scholion labs` and `scholion overview` — if their output was saved with an earlier version.** The thresholds that depend on sex have changed. **Run `scholion drug ` — if a star-allele table or a called diplotype is in the profile.** For CYP2D6, CYP2C19, CYP2C9, DPYD, TPMT, NUDT15, SLCO1B1 and the rest of the eighteen. **Run `scholion acmg` and `scholion prs` — if their output was saved with an earlier version.** The ACMG report now carries coverage; sex-specific polygenic traits are withheld and the method caveats are printed. **Run `scholion ingest-labs ` — if the folder holds non-PDF files or an American form.** **Run `scholion genome` — on a GRCh37 file, a consumer array, a multi-sample file, a folder with several genomes, or a file that was never actually readable.** ### Measured Twenty-eight input shapes — the zoo of formats real providers hand out — run through the previous release and this one: | | v0.3.4 | this release | |---|---|---| | **Confidently wrong answers** | **8** | **0** | | False «no genome found» | 14 | 0 | | Correct | 6 | 27 | | Answers nothing, but still says «connected» | 0 | 1 | The remaining one is a genome whose reference build cannot be determined from the file: the first line still says «Genome connected» before it says the build is unknown, though every position in it now refuses. And on real files rather than invented ones — 31 sets of genomic and medical records from the Personal Genome Project, whose participants publish their data under open consent. No participant's data, findings or identifiers appear here or anywhere else in this repository; these are counts of what the software did. | | v0.3.4 | this release | |---|---|---| | **Confidently wrong answers** | 1 | **0** | | Consumer arrays read | 3 | **9** | | Genomes turned away over their reference build | 7 | **0** | | False «no genome found» | 11 | 7 | | **Laboratory results imported** | 0 | **61** | The last row is the one that matters most: the laboratory side now fills up from records it has never seen before, which is the precondition for everything this tool does across layers. On one of those sets a lipid panel showed dyslipidaemia, the tool concluded that statin therapy was likely, and named the one gene worth testing **before** it starts. On another, a single raised rheumatoid factor produced two suggested tests and a sentence saying why one result is not a diagnosis. ### Thanks * **Personal Genome Project (Harvard)** and its participants, whose open-consent data made it possible to test this on 27 real files from eleven providers without a single user. Almost everything in this release was found there. No participant's data, findings or identifiers are redistributed here in any form. * **Synthea** (synthetichealth/synthea, Apache-2.0) for the synthetic FHIR bundle the import is tested against — a parser tested only on input written by whoever wrote the parser passes its own tests and fails on the world. * **Genomi** (exon-research/genomi, Apache-2.0) for the input-format detector, vendored with a way to update it. ## v0.3.4 — 19.08.2026 The release for the person we are about to invite: someone with their own medical files and no terminal habits. Four changes, assembled under one tag (an earlier v0.3.4 tag existed locally and was never published; it was re-cut onto this state — nobody could have installed the intermediate one). No knowledge changes, no series break. ### The skill becomes a download, not a build step The published page used to explain the «no terminal» path with a terminal command — `python3 src/tools/make_skill_package.py` — which is the joke telling itself. Now the page CARRIES the skill: `scholion-skill.md` (the entry — one file to attach to a chat with Claude or ChatGPT and say «set this up») and `scholion.skill` (the full bundle with the reference texts and the safety rules) are generated by `make_shareable.py` at build time, from the same sources every other step uses, so the page can never serve a stale skill. Both presentation cards and the README's skill section now lead with the download and keep the build-it-yourself route for people with the source tree. What does not change: the model gets no access to the machine, the profile never leaves it, and the safety rules take precedence over every other instruction the model is given. ### A genome that cannot be read is named, not reported as missing Found by walking the invited person's path. A plain `.vcf` — the shape providers hand out routinely — was invisible to the reader, and the person with their genome sitting in the folder was told «The full VCF is not connected», word for word the message shown to someone with no genome at all. A `.vcf.gz` compressed with ordinary gzip instead of bgzip was quieter and worse: it looks right, is not, and the tools' own error explains nothing to someone who never heard there were two gzips. Now `genome.unusable_nearby()` names the file, names the reason, and prints the exact command that fixes it — in the status, in `limits`, and in the genome guide, with the model's instruction taught the same. One person, one folder, one command — instead of a wall described as an empty room. ### The easiest way in moves to the top of the page The download-and-attach path lived in the installation section — at the bottom of a long page, where a person with no terminal habits arrives already tired. Now it is the first thing after the title: one button (download the skill file), one sentence (attach it to Claude or ChatGPT, say «set this up»), one line of what never changes — the data stays on the machine, the safety rules outrank everything. The full installation section stays where it was, for the readers who want the whole inventory. ### The plugin gains its OuroborosHub form The Hub's contract is skills// with SKILL.md frontmatter and a plugin.py exposing register(api) against the frozen PluginAPI ABI — not the classic tools-module this project has carried. `ouroboros_plugin/hub/scholion/` now holds both files; plugin.py is an adapter over the pip package's own `get_tools()`, so the Hub skill cannot drift from the product. Verified against the host's own code: manifest parser, install_specs normalization, 23 tools inside the 24-character name limit, end-to-end answers on a demo profile, and a clean catalog entry from the Hub's build_catalog.py. Submission to the catalog is the owner's fork-and-PR; the reviewed source lives here. ## v0.3.3 — 19.08.2026 The release where the project explains itself twice over: to a visitor, and to a contributor. No knowledge changes, no series break; the analysis answers exactly what v0.3.2 answered. ### The presentation catches up with the product, and gets a front door `share/presentation.html` and `share/presentation.ru.html` are rebuilt against the living 0.3.2 — checked by running the product, not by memory: 553 tests, 48 commands, the Assistant tab's self-audit at 39 files / 18,194 lines (read from `_audit_core()` directly, not off a screenshot), six outbound hosts by name. All screenshots are fresh, a tenth was added (the Guide tab), and the Russian edition carries REAL Russian screenshots for the first time — the apologetic note about English ones is gone. An Installation section now mirrors the README's four ways in, and says plainly that the genome-preparation workshop lives in the source tree only. Two wordings were corrected on the way: «not one external dependency» (pdfplumber is required) and the locality card now names the translation services next to the reference APIs, because the application's own self-report does. The pages get an entry point: `share/index.html` redirects to the presentation (with a manual fallback naming both languages) and `share/.nojekyll` keeps GitHub Pages from mangling the folder. The publish gate carries both into the built package's `docs/`, so `crossread.github.io/scholion` starts answering at the root instead of 404. The screenshot allowlist in `make_shareable.py` was replaced wholesale — twenty hashes, ten per language — following the file's own precedent. One accepted line joins the language baseline: the «Russian» link label on the entry page is a language switch, which is the one place a language's own name belongs. ### The conventions get a file, and the split gets a gate `docs/DEVELOPMENT.md` — the architecture and the development rules, written for an outside contributor to assemble a structured commit without reading the tests first: the layer map, the engine-package rules, the four-face tick, language and i18n discipline, the gate order, what a commit message carries, and short checklists for the common additions. An early audit cited a DEVELOPMENT.md that did not exist; the citation is now true. `tests/test_engine_stays_split.py` holds the refactoring's shape the way the four faces are held — as a red test, not a memory: the facade defines nothing; domain imports stay acyclic at the top level (the one sanctioned back edge is lazy inside a function, which is exactly what makes it sanctioned); every module has a size budget recorded in the test, and exceeding it is a same-commit decision — split, or raise the budget with a reason about the capability; the facade may not shrink below the hundred names the flat file exposed. Seven mutations were tried against the gate; seven came back red. ## v0.3.2 — 18.08.2026 A refactoring release: not one answer changes on unchanged input. The full suite, the compatibility snapshots, both language catalogues and every command are byte-for-byte the same claims as v0.3.1 — that sameness IS the release gate, recorded here as the thing that was checked rather than assumed. ### engine.py becomes a package of eight domain modules The 2800-line flat file — five domains that had grown into one another for a year — is now `engine/`: `_helpers`, `labs`, `pgx`, `genomics`, `goals`, `lifestyle`, `sources`, `profile_view`, with `__init__.py` as a facade that re-exports EVERY name, private ones included, at the address the tree has always used. Six consumers (`__init__`, `cli`, `server`, `ouroboros_tools`, `assistant`, `limits`) and the tests needed zero edits: five of the six always called `engine.(...)`, and the sixth's explicit imports resolve through the facade identically. Two module names differ from the auditor's proposal for a reason recorded in the plan: `genomics` (a `genome.py` already exists — the VCF backend) and `sources` (a `provenance.py` already exists and means the reverse check; `engine.provenance()` keeps its public name, only its file changed). The mechanical work was governed by a call-graph analysis the original audit did not do, and the graph had teeth: two cycles (labs↔pgx and lifestyle↔pgx). They were broken by MOVING two leaves into `_helpers` — `_active_names_by_class` and `_brief_num` invoke nothing and belong to everyone — and by ONE deliberate lazy import: `pgx._dose_context` reaches for `lifestyle._brief_life` inside the function body, the same pattern the file already used for `drugsource`. Everything else imports one way, top-level, two dots up for the package neighbours exactly as the plan required. The extraction tool refused to write a module with an unresolved name, and that refusal caught three edges no analysis had listed: `DISCLAIMER` is called by `goals` and `genomics` (the call-graph pass had skipped the two constant- like functions), and `_OPS` — the comparison-operator table — is shared by `_helpers._match_count` and `labs._eval_condition`. All three now live in `_helpers`, imported by name where used. The audit's other prediction was confirmed the hard way: `_WATCHLIST` sits in `profile_view`'s line range and belongs to `lifestyle.second_opinion` — cutting by line ranges would have shipped a NameError that only fires when somebody asks for a second opinion. One collision existed and is now tested: `lifestyle` is both a submodule and a function. The facade binds the function last, and a test-adjacent check confirms `engine.lifestyle` stays callable even after a direct submodule import. ### Removed: the monitoring list nobody calls `_monitoring_for` returned monitoring hints for eight drug classes hard-coded in the source, through sixteen `monitor.*` keys in both catalogues. The only place needing such hints — `check_new_prescription` — has long read them from `knowledge/drug_lab_monitoring.json` via `core.drug_lab_monitoring()`. The code list lost that argument and stayed anyway; found by the refactoring audit, deleted with its keys (catalogues stay identical: 1334 = 1334). ### What was checked, where `run_tests.sh` end to end in the repository: 553 tests, 48 commands, 20 snapshots, docs and rules in sync, the language remainder unchanged at 352 (the seven accepted places that lived in `engine.py` moved to their new addresses; the baseline records the move, not growth). And — because a check that agrees only with the repository it was written in has now cost this project five times — the artefact itself: a wheel built from this tree, installed into a clean environment, imports the facade, resolves the private names the tests use, and answers `scholion capabilities` and `scholion second-opinion` from the installed package. All nine `engine/` files travel in the wheel. ## v0.3.1 — 18.08.2026 _MINOR by the letter of `docs/VERSIONING.md`: `lab_markers.json` changes what a value means on unchanged input — an HbA1c that used to be refused is now stored. Nothing already stored moves; a refusal has no stored value to move._ _Backlog 36 and 41, and the answer to both was mostly «check before you build»._ ### What this changes in the conclusions **HbA1c in mmol/mol is read now instead of refused.** The IFCC scale and the NGSP scale are related by the master equation `% = 0.09148 × mmol/mol + 2.152` — affine, not proportional — and the gateway had one law: a multiplier per spelling. Refusing was the honest answer while that was the only tool, and it was also a refusal of the commonest unit on a European report. A person met «unknown unit» about a unit that plainly exists, went looking for a typo, found none, and typed the number bare — the outcome the refusal existed to prevent. The gateway carries a second law: `convert_affine`, with the constants and the NGSP citation beside them. 48 mmol/mol comes out as 6.5 %, which is what the published table says, and the test checks the table rather than the arithmetic. **The corridor travels with the value, by the same law.** A range printed in mmol/mol beside a value converted to % is the mg/dL-glucose defect one level down, and with an affine law a bound multiplied instead of transformed lands somewhere else entirely. Both ends now go through the conversion, and a test adds a value inside its own corridor and checks it is not flagged. **Nothing converts on its own any more.** The arithmetic used to live in the caller. That was safe with one law and dangerous with two: a caller reading only `factor` would apply 1.0 to 48 mmol/mol and store 48 % — not an error, a catastrophic diabetic reading, silently. `core.convert_to_canonical` is the one place the law lives, both entry points go through it, and a test refuses any module that multiplies by the gateway's factor itself. Verified by putting the multiplication back and watching two tests go red. **Lp(a) in mg/dL stays refused,** and that is what keeps the refusal path honest. Mass and molar concentration there depend on the size of the person's apo(a) isoform: no factor, no formula, and a second law is not a licence to convert everything. ### What was already done, and is now closed by checking rather than by memory **Task 41 — the test matrix — was already in the tree.** `tests.yml` runs ubuntu × macOS against Python 3.10, 3.11, 3.12 and 3.13 with `fail-fast: false`, plus a Linux job with `TMPDIR` behind a symlink (the macOS `/var` → `/private/var` shape, reproduced where it is cheap), plus the package job. The backlog line said «not started» as of 16.08 and was two days stale. It is closed with what proves it: the file, the cells it declares, and a green run on GitHub. **Task 36 was five-sevenths done and the entry knew it.** The five factor conversions — free T4, free T3, TIBC, zinc, DHT — shipped earlier, each with its molar mass written next to the constant. What remained were the two the entry itself called hard, and they turned out to be one piece of work and one correct refusal, which is the paragraph above. ### The frame: one capability, four faces, one tick `contract.py` opens by naming three faces of one core and describes the defect it was written after — «Second opinion», the summary and the health index living in the web tabs alone for half a year, because a capability added quickly to one face stays there. It closed the first two faces against each other and left the others open, and both drifted. There are four doors, not two, and they are not equal: | | who walks through it | who notices it is shut | |---|---|---| | web interface | a person clicking | the person — a missing tab is visible | | command line | a MODEL with a shell, first; a person typing, second | the person — the model works from what it was told exists | | plugin tool list | a model deciding what it can call | **nobody** | | the model's instruction | a model deciding what exists at all | **nobody** | A person can see that something is absent and go looking. A model cannot see a capability it was never shown: it answers from what it has instead of saying it cannot, and that answer looks exactly like a good one. So the two model-facing doors now carry the higher bar — an omission must be written down with a reason, and the reason must be about the capability rather than about the week. **Measured before the guard was written**, which is the only reason to trust that it was needed: the plugin lacked nine capabilities (see above), and the shared instruction named 40 commands of 47. Three of the seven absent were real — `acmg`, `goal-suggest`, `lipid-genetics` — and two of the three had been added that same week. Each of those two drifts had been found by somebody noticing months later, not by a test. `check_all_faces()` answers for all four at once, and `tests/test_all_faces_move_ together.py` prints them in one message. That is deliberate: the question an author has after adding something is «what did I forget», and four separate red runs answer it a quarter at a time — a run per face, a fix per run, and the fourth found an hour later. The fourth face is CHECKED and not generated. The command block in the instruction carries curated invocations — `genome rs0000000`, `phenoage --panels`, `tools --set NAME` — worth more to a reader than a line per bare command, and a generator would flatten them. What must not happen is silent absence, and that is what is now impossible. A fifth check went in beside them, of a different kind: every phrase the interface reaches for exists in **both** catalogues. A missing key does not crash — the page prints `⟦web.some.key⟧` where a sentence should be, in front of the reader and nowhere else, in only the language that lacks it, so it is invisible to whoever wrote the other one. **The guard's first catch was its author.** `init` was excused as «not named in the instruction» and is named there; the excuse was wrong and the check said so on the first run. Its second was a stale sentence in both instruction editions telling a model that HbA1c in mmol/mol is refused — true until earlier the same day, and exactly the sort of statement that outlives the thing it describes. **The command line was misnamed in the first draft of this table.** It said «a person typing, every script» — and the owner corrected it: the CLI's completeness is noticed not only by a person but by the AI agent that works with Scholion as a tool, and the CLI is for that agent first. A person can browse `--help`; a model with a shell runs what its instruction names and nothing else — the instruction is the discovery mechanism of the main surface. That correction forced two things that were not in the plan. The first is a second route to the truth: `scholion capabilities` (also `--json`) — a manifest GENERATED from the command parser and the entry-point map, listing every command, what it does, whether it writes, and which faces carry it. The instruction now ends its command list with the rule «if this list and the build disagree, believe the build». A curated instruction can go stale — this release found it stale twice — so the model gets one door that cannot: the build describing itself. The second the new gate class found on its own first run (`TestTheManifestCannotFallBehindTheBuild`): the claim «no tool handed to a model writes», printed in the writes heading and asserted from a hand-written list, had been false from the beginning — `sch_ingest_labs` is a tool and writes `labs.json`. The earlier test happened not to contain the one command that broke its premise: a check agreeing with its author, the same class that has cost this project four times. The fix is a distinction, not a deletion: WRITES splits into AUTHORS (creates values from nobody's document — `add-lab`, `add-med`, `demo`… — never a model's tool) and TRANSCRIBES (moves the person's own documents into the profile — `ingest-labs`, `ingest-garmin`… — a model may hold these, because they invent nothing). The manifest marks every write with its kind, the gate holds AUTHORS out of the tool list by name, and every transcriber must be recorded as admitted or refused — silence is the one state the contract no longer allows. **And the manifest's own reader carried the disease it was built against.** `instruction_text()` read the instruction at its source-repository address alone, passed every test in the repository it was written in, and failed inside the built package on the owner's publish run — the package does not carry `share/`, it carries the identical copy beside the module. A check agreeing with the single environment its author sat in: the same class, caught by the publish gate doing exactly what it is for — nothing was committed or pushed to the public repository. The function now knows both homes and says so when it finds neither. ### The third face of the core had fallen behind, and nothing was watching it `contract.py` opens by naming three faces of one core — the web interface, the CLI and the Ouroboros plugin — and by describing the defect it was written after: «Second opinion», the summary and the health index by body system lived only in the web tabs for half a year, because a capability added quickly to one face stays there. The map it enforces covered two faces of the three. So the plugin drifted exactly the way the web had. Nine capabilities had a route and a command and no tool: the overview, the second opinion, the radar, the focus, the lifestyle brief, the ACMG scan, the two added this week — and `limits`, which is the answer to «what can this data NOT tell you». `limits` is the one that matters. The reader who needs it most is a language model about to make a negative statement, and it was the one face that could not ask for it. A missing tool is worse than a missing tab for a reason worth stating: a person looking at a page can see that something is absent. A model cannot see a capability it was never shown — it answers from what it has instead of saying it cannot. Fourteen tools became twenty-three, each checked by calling it. `PLUGIN` maps command → tool and `NO_PLUGIN` records a reason for every command that has none, and the bar there is deliberately higher than for the web. Every write command is in that list marked «a write», with a test that keeps it so: the canon handed to a model says it does not change the profile, and the absence of a write tool is what makes that more than a promise. ### The seven small things the stranger's run left behind None of them changes an answer; all seven were things a reader had to work around. **Labs listed twenty-seven markers alphabetically under a heading that said «8 out of range of 27».** The eight the sentence was about were scattered among the nineteen it was not, and counting them by eye was work the page could have done. Out of range first, furthest outside its corridor leading, then a divider and the rest in the order they had. **Overview came to 11 400 px on a 390 px screen** — the goal board is most of that, and the four numbers a reader opens the page for sat underneath it. The board folds on a narrow screen and stays open on a wide one: 5 200 px now, one tap instead of a minute of scrolling. Built with `
` rather than a script, so it survives a half-loaded page and a screen reader already knows how to announce it. **Polygenic scores sat a centimetre below «Full genome (VCF): no data».** Both true — a score is a stored result, computed once from a file that need not still be attached — and read as a contradiction with no way to tell which half to believe. The block now says when it was computed and that the file is not attached now. **The disclaimer was printed four times on one screen** and after the fourth it reads as legal cover rather than as care. It was repeated for a real reason: Overview is thousands of pixels tall and the header scrolled away. The header is pinned now, which answers the reason instead of arguing with it, and the copy inside the focus card is gone. **«P94», a phenotype code and a confidence mark carried no explanation** where a reader first meets them; the Guide has all three, a tab away. They now carry the Guide's own wording as a tooltip — the Guide's, not a second text written beside them, because two texts for one term drift and the one on the badge is the one nobody maintains. **A traceback in the middle of the release log.** `test_server_guard.py` provokes a failure to prove the server does not leak a filesystem path into an HTTP response; the server prints the details to the owner's console, which is the other half of that design. Read as a crash three times in one day. Swallowed in the test that causes it — and the test now also asserts the traceback still reaches the console, because silencing it in the server would delete half the guard. **One tool description still described the owner's shelf** («from Garmin wearables and smart scales») in text written for every profile. ### Also `LICENSE-DATA` attributed the knowledge base to `https://scholion.dev`, which does not exist. It points at the repository now. The edit arrived in the built package rather than the source tree, where the next publication would have wiped it without a word — the hazard the two-repository model carries, and worth recording next to the fix. #### Also, on the unit gateway `tests/test_unit_gate_second_law.py`. Among its assertions: that the published pairs do NOT share a ratio — which is the reason the second law has to exist at all, stated as something that fails if it ever stops being true. --- ## v0.3.0 — 18.08.2026 _Tagged three times before it left the building. `v0.3.0` and `v0.3.1` were pushed to GitHub and rejected by PyPI — see «the metadata line» below — so neither was ever installable, and both tags were withdrawn rather than left standing as versions nobody could get. What follows is everything those three tags contained, under the number the release was always meant to carry. If you have a checkout pinned to `v0.3.1`, it is gone; `v0.3.0` is the same code and more._ _MINOR, and a **series break** in two places. `test_rules.json` rewrote three rules, so a suggestion list printed before this version and one printed after it are not the same document even from an unchanged profile. `loci.json` and `longevity_directions.json` gained PCSK9, so a genome that resolved nothing there now resolves four positions. Separately, a profile carrying BOTH a laboratory report and a VCF can answer differently at a position where the two disagree: the reads win now, and the disagreement is printed._ _Written from a run of the product as a stranger: the package built by `make_shareable.py`, installed with `pip install` into a clean machine, opened in a browser as two people — one with the demo, one an hour after installing with nothing loaded — and read by three reviewers with no knowledge of the project. The findings are theirs; what follows is what was done about them._ ### What this changes in the conclusions **A new profile no longer says anything about the person who created it.** Every file `scholion init` laid down carried data: one triglyceride measurement dated 2024-01, one prescription, a sex, a year of birth and a height. A minute after installing — before loading anything — the first screen counted «0 red flags over the last 12 months», the labs tab showed a value flagged «within range» with a date on it, and the header counted a prescription. Both readers who met that state said they would have repeated those numbers to a doctor. The templates now ship empty, and each one says in `_meta.why_empty` that the emptiness is a decision. `metrics.json` no longer assigns a sex, a birth year or a height: the panels that need them refuse until they are filled in, which is the correct answer. **The suggestion rules stopped asserting facts about the reader.** The CYP2C9 rule fires for anyone whose CYP2C9 is unread — that is, for everyone new — and its reason read «Together with the **already known VKORC1**…». True of exactly one profile, the one it was written on. The APOE rule justified a test with «a goal of the project». Both rewritten. «Track 2», the project's internal name for the FASTQ→VCF pipeline, left the three rules it appeared in: every reviewer stopped on it, and a clinician read «The CYP2C9 genotype (\*2/\*3) — through Track 2» as the patient's genotype rather than as a test being suggested. **Direction stopped being read as severity.** The first screen split current abnormalities into «red flags» (above a ceiling) and «under observation» (below a floor), which made a ferritin of 13 against a floor of 20 the milder of the two — while the same screen's focus card was about that ferritin. The block now shows every current abnormality in one list; the two counts remain, labelled as the directions they are, with a line saying how the four numbers relate. **The pharmacogenetic section says how much of it is about you.** With no genotypes on file the section is not empty — every watch-list drug comes back with the general rule printed in place of a statement about the person — so it now opens with «K of N could be judged from the genotypes on file», and each row shows the phenotype LABEL (`ultrarapid metaboliser — ASSUMED, not every marker was read`) instead of the machine code (`UM`, `normal_sensitivity`). ### What is retracted **The command for installing the skill never worked for anybody but the owner, and would have leaked his key if it had.** The «Assistant» tab built the path as `REPO/src/skill/INSTRUCTION.owner.md`. `REPO` is the repository root only in a source checkout — after `pip install` it is the parent of site-packages, naming nothing — and `INSTRUCTION.owner.md` never ships, so every installed copy showed «not found» and then printed a symlink command into empty space. In a checkout the path did resolve, and it pointed at `src/skill/`, which holds the owner's 117 KB clinical key: following it would have symlinked that key into `~/.claude/skills` for a model to read. The entry now names the packaged skill directory, prints no command when there is nothing to point at, and a test asserts that whatever directory it names carries no `*.owner.*` file — the assertion that fails on the owner's own machine, which the path-exists version did not. **Four places sent the reader to scripts that are not in the package.** `call_full_vcf.sh`, `annotate_clinvar.sh`, `acmg_sf_scan.py`, `loci_sites_bed.py` — all of `src/ingest/`, none of it shipped. They now point at `scholion doc preparing-the-genome`, which travels with the package. **The version in the header was a changelog line frozen in 2026-07.** `server.py` kept `VERSION = "2026-07-30 · radar dynamics + tab freshness"` by hand while the package was 0.2.2. It reads the package version now, and a test ties the two together. ### What needs recomputing Nothing stored changes value. Suggestion lists and second-opinion sheets printed before this version carry the retracted wording and should be reprinted if they are still in circulation. ### Also - The demo announces itself on every screen — an amber band saying it is a fictional person. Before, the only sign was «DEMO-0001» in the header, which reads like a laboratory accession number. - «Guide» moved from tenth tab to second. It answers most of what a newcomer asks and was being handed over at the end of the journey. - A print stylesheet: the navigation, the source chips, the language switch and the cross-tab links are stripped, and the sheet gains a header with fields for a name, a date of birth and a date — the first thing a general practitioner said was missing — and a footer saying where the numbers came from. - Empty chart frames replaced by a line saying no series exists yet. Three framed rectangles with headings and nothing in them read as broken, not as empty. - `scholion init` names the first useful command for whatever the person actually has, and says out loud that the four external programs listed after it are for the genome track alone. - The owner's own goal left the product's vocabulary: the CLI help, the tool description, the chart legend («the optimum window 2021–2022») and the default heading («Goal — get back in shape») were one person's aim shown to everyone. - `_goal_num` used a decimal comma regardless of language, so the English page showed «TSH 6,4» in the goal table above a card reading «6.42». - «The internet is needed» on the drugs tab, three centimetres under a padlock reading «runs locally», now says what actually leaves: the drug name typed, and nothing else. - Three new test files — `test_fresh_profile_is_empty.py`, `test_entrypoints_are_reachable.py`, `test_empty_state_honesty.py` — named after the failures rather than the functions. ### From the other branch, in the same release **There is now an address.** `scholion.dev@proton.me`, in `README.md`, `SECURITY.md`, `CITATION.cff`, `pyproject.toml` and the issue-template config — with the instruction, in every one of them, to send no personal health data, and a pointer to `scholion redact` for anyone who wants to send a file anyway. This closes the last item of R1.1: until now a reader who found a defect, or wanted to offer de-identified data for validation, had nowhere to write. **A page for clinicians.** `docs/FOR-CLINICIANS.md` — what the program takes in, what it will and will not claim, and where it refuses. It ships inside the package like the other documents (`scholion doc for-clinicians`). **`scholion limits` names the cell it is answering from.** Input class (whole genome / exome / consumer array) × trait architecture (monogenic / oligogenic / polygenic): the pipeline differs in each, and so does what may be claimed. A percentile with no architecture beside it reads as a verdict; «no pathogenic variant found» in a gene the file never covered reads as reassurance. Where a polygenic number is on screen, a note on heritability goes with it. **Fact, not cause.** Raised by a clinical geneticist reviewing the project on 17.08.2026 and the sharpest technical point in that review: «ferritin rose after the course» is a fact, «the course raised ferritin» is a causal claim that a series of two points cannot support. Rule 7 of the skill entry now says so to the model, and `test_safety_rules.py` holds the message catalogue to it — the catalogue is where a causal habit would start, because the model imitates the wording it is given. ### Two capabilities, in the same release **The goal is proposed now, not shipped.** Removing one person's targets from `health_goals.json` left a hole where a goal used to be, and «write your own» is not an answer for somebody who has just installed a program. `scholion goal-suggest` reads what the person has actually measured and what the clinical associations publish, and proposes a target for each marker there is enough to propose one for. Three sources, and the whole design is in keeping them apart: - **a guideline**, quoted with its citation — the strongest and the rarest; - **the person's own best**, with the date and the number of readings behind it. Not a recommendation from anybody: a fact about them; - **the wall of the laboratory corridor**, offered last, because «inside the range» is where most people already are and is not an aim. A target already met is not offered as something to reach — it goes to a list of its own, which is a different and true statement. A target written for people with a condition (ADA's «under 7 %» is for somebody who HAS diabetes) is never adopted on a condition nobody confirmed. And what was passed over is listed with the reason, because five proposals with no account of the forty markers skipped read as «these five are what matter». `goal_targets.json` is new, and its hardest entries are the empty ones. The Endocrine Society looked at 25(OH)D in 2024 and withdrew the target it had once set; that refusal is recorded, quoted, and travels with anything else proposed for that marker — otherwise the laboratory corridor quietly supplies the number the society declined to write. Nothing is written to the profile without `--write` or a press of «Save», and a target the person set by hand is never replaced: it is the strongest source there is, because it is theirs. **The inherited side of the lipid profile (task 63).** PCSK9 carriage and Lp(a) in one card on «Genome», because each is misread alone. A low LDL-C with a loss-of-function variant behind it is a different fact from the same number reached on a statin; and Lp(a) is invisible to the rest of a lipid panel — set at birth, unmoved by what moves LDL-C, so a normal panel with a high Lp(a) is a normal panel that has missed the finding. Four PCSK9 positions entered `loci.json`, each coordinate read from the Ensembl REST API rather than from memory — the one lost variant in this project's history was lost to a coordinate written from memory. Two of them entered `longevity_directions.json` with primary PMIDs (Cohen 2005, Cohen 2006); the other two did not, and sit in `unresolved` saying why. The catalogue's own rule asks for a primary source naming the favourable allele, and review-level sourcing is not that. Two limits are printed rather than implied. «Not a carrier» of C679X says almost nothing outside populations of African descent, where the variant is close to absent — so the caveat travels with the answer. And a polygenic score for Lp(a) is an ESTIMATE: the level is driven mostly by the number of KIV-2 repeats inside LPA, a copy-number variant short reads see poorly, which is what the catalogue's «Moderate» mark on `PGS002101` has been saying in a place nobody looks. Where Lp(a) has not been measured the card says so and asks for the test — once in a lifetime, in nmol/L, and before a decision about therapy rather than after it. One correction to the analysis this was built from: `rs28362286` is **C679X**, not «near Y142X». Y142X is a different variant (`rs67608943`). The position Ensembl returns, 1:55063542, is at the 3′ end of the gene and fits residue 679, not 142. **A laboratory's summary sheet no longer overrules your own reads (task 64).** `core.genotype_status` returned the profile entry the moment it found one and never reached the VCF. So `rs4988235`, `rs1801133` and `rs429358` came back as `reported / profile / depth=None` — copied off an Evogen summary — while the person's own aligned reads sat unread in a file on the same disk. `scholion genome rs4988235` meanwhile DID read the VCF and answered «reference confirmed by a call (0/0), coverage 32». Two routes to one fact, disagreeing, and nothing in either saying so. A genuine read now wins: it carries a depth, it can be re-examined, and it is what the report was made from. But only a genuine one — `assumed_ref` means «the reference, OR nothing was looked at there», and letting that overrule a laboratory's positive finding would be this project's oldest defect wearing new clothes. A disagreement is never resolved silently: both values travel in `conflict`, and the CLI and the interface both print it, with the suggestion to take it back to whoever issued the report. Agreement is printed too — two independent routes to the same call is a stronger statement than either alone. Why the seventeen known disagreements between that report and the reads never tripped this: `genotype_status` answered `None` for every one of them, because those rsIDs are not in the coordinate catalogue. The priority was never exercised where it is dangerous, so the absence of an error there proved nothing — and the tests build the collision by hand rather than waiting for one. ### What is retracted — the CI job that could not run where it was sent **The job written to catch «a check that asks the artefact for something only the repository has» was one.** `tests.yml` has a `package` job whose first step runs `make_shareable.py` — the sanitiser, which builds the package FROM `share/`. But `share/` does not ship: it is the folder the package is built out of, not part of what is built. So on the public repository that step answered «Run this from the ORIGINAL repository (there is no share/ folder with the templates)» and the build went red on the first push after publication, six seconds in. The author never saw it, because the author is in the source tree, where it passes. That is the whole of the class: a check agreeing with the single environment it was written in. It has now cost this project four times, and this is the first time it cost a red badge on a public repository — the first thing a stranger sees. The two steps are conditional now, on the same predicate `tests/support.py` already uses and calls `IN_SOURCE_REPO`: does `share/` exist. Where they are skipped the job says so with a `::notice::`, because a green tick for having run nothing is worse than a red one, and the matrix job above has in any case already run the package's own suite on exactly the files a recipient gets. #### Also `tests/test_ci_runs_where_it_can.py` — a workflow step that runs a tool needing the source tree must be conditional, and the condition must be the same question Python asks. The check is textual, so it is narrow on purpose; its first version read the workflow's own explanation of the guard as an unguarded call, which is the failure mode of every textual check and is why it now drops comment lines before looking. Verified by removing the guard and watching it go red. ### What is retracted — the metadata line that kept this release off PyPI **Two releases reached GitHub and neither reached PyPI.** `v0.3.0` and `v0.3.1` both died on the same line, added when the project got an address to write to: Contact = "mailto:scholion.dev@proton.me" Every value under `[project.urls]` has to be a URL, and PyPI means it: `400 'mailto:scholion.dev@proton.me' is not a valid url`. The index published `0.2.2` and has been publishing it ever since; the tags on GitHub say otherwise. Anyone who ran `pip install scholion` after 0.3.0 was tagged got 0.2.2 and had no way to know. **When it failed is the part worth keeping.** The build succeeded. `twine check` printed PASSED on both artefacts — it validates the long description, not the URL schemes. The signing, the attestations and the transparency-log entries all completed. The refusal came from the index, in the last step of the last job, after everything that could have caught it had said yes. The address now lives in `authors`, which is the field the core-metadata specification has for an e-mail and which PyPI accepts; the built wheel carries it as `Author-email: CrossRead `. `Contact` points at the README's own contact section — the place that says what to write about and, more importantly, what not to send. #### Also `tests/test_packaging_metadata.py` asks the questions the index asks, when the suite runs rather than when the artefact is in flight: every `[project.urls]` value begins with `http`, an address to write to still exists and is not a `mailto:` in disguise, and the version still comes from the one file. Verified by putting the `mailto:` back and watching it go red, and by dropping the address and watching it go red for the other reason — the fix must not become «we removed the contact», which is the task the address was added for. It parses the file by hand instead of with `tomllib`. That library arrived in Python 3.11; this project supports 3.10, and 3.10 is the interpreter the mistake was authored on — so a test that skipped without a TOML reader would have skipped on the one machine where it mattered and reported OK for checking nothing. This project has been bitten by that shape four times. --- ## v0.2.2 — 17.08.2026 _PATCH, by the owner's judgement and against the first reading of this project's own rule — which is why the rule was rewritten in the same breath rather than quietly stepped over (`docs/VERSIONING.md`, «Accepting a new form of an input»). Everything here is the road in: nothing already stored changes value, and no answer already given reads differently. Five unit forms that used to be refused are now accepted, and one command was added that draws no new kind of conclusion._ ### What this changes in the conclusions **Five more of the units a US lab actually prints are accepted.** Free T4 in ng/dL, free T3 in pg/mL, TIBC and zinc in µg/dL, DHT in ng/dL. The lab import is transactional by design, so one unknown form used to drop a whole panel and leave the person with an import that did nothing. Each factor is stored with the molar mass it derives from, so a wrong one can be caught by reading rather than by trusting whoever typed it, and `hba1c` (mmol/mol) and `lpa` (mg/dL) stay refused on purpose — one relates by an affine formula rather than a multiplier, the other depends on the person's apo(a) isoform. **`scholion doc` prints the documents the output points at.** A `pip install` gets `src/scholion` and nothing else, while the output sends the reader to README nine times, to PREPARING-THE-GENOME four and to DATA-LAYOUT twice. Those files were not on a PyPI user's disk, and with the repository private there was no second place to read them: `limits` was advising people to open something they could not reach. Seven documents now travel inside the package. **The first screen works from an empty machine.** `scholion demo` writes into `/demo/profile` and `scholion overview` reads `/profile`, so the README's opening two commands produced an empty profile. The first line is now `init --demo`. `demo` is unchanged and still right: building the profile away from a real one is the correct default. **A bare `scholion` answers instead of erroring,** and `init --demo` no longer lists four external programs the demo does not use. After a real `init` the offer stays — there the genome layer needs them. **The skill loads a short entry instead of a thousand lines.** `SKILL.md` was the whole instruction — around seventeen thousand tokens on every trigger before the first useful word, and the one thing a newcomer needs, how to start, was not in it. It is now a 5.5 KB entry plus `INSTRUCTION.md`, which the model opens when the task calls for it, and `make_skill_package.py` builds the pair and its references into a single `dist/scholion.skill`: for somebody whose only tool is a language model, a folder is not a delivery and a file is. **The local web interface got a design.** It is styled with Pico CSS, vendored rather than fetched from a CDN: the interface binds to `127.0.0.1` and the claim is that using it sends nothing anywhere, which a stylesheet loaded from somebody else's server on every page would quietly make false. The bundled copy is now recorded in `ATTRIBUTION.md` under a section that did not exist — the legal layer covered tools the user installs and data redistributed here, but not code that travels inside the package. **A profile file now says which shape it is in.** Nothing to migrate yet; the direction that matters is that a build refuses a file written by a newer one rather than reading an unknown shape with rules that no longer apply. That direction cannot be added later — by the time there is a version 2, the builds that must refuse it are already installed. ### What is withdrawn Nothing. Every previous result stands; what changed is what the system will now accept as input and what it will now refuse. ### What needs recomputing Nothing. A panel that failed to import because of one of those five units can be imported again — it never wrote anything, so there is nothing to correct. ### Changes by file **Knowledge — a break in the series** - `src/scholion/knowledge/lab_markers.json` — five US unit forms, each with its derivation **Web interface** - `src/scholion/web/` — a design, styled with a vendored Pico CSS - `ATTRIBUTION.md` — a section for code bundled in this repository **Skill** - `share/skill/` — the instruction splits into a short `SKILL.md` and `INSTRUCTION.md` - `src/skill/INSTRUCTION.owner.md` — the owner's edition, renamed and still private - `src/tools/make_skill_package.py` — the bundle as one file **Engine and application** - `src/scholion/cli.py` — `doc`; a bare call answers; no tool offer after a demo - `src/scholion/core.py` — the profile schema layer: read, refuse, stamp - `src/scholion/docs.py`, `src/scholion/docs/` — seven documents carried inside the package - `src/scholion/contract.py`, `src/scholion/i18n/` — the new command and its wording - `src/scholion/templates/profile/` — a version number, and the layout prose moved off that key **Tools and build** - `src/tools/sync_docs.py` — keeps the carried documents equal to their sources - `src/tools/check_language.py` — copies are measured once, not twice **CI** - `.github/workflows/tests.yml` — ubuntu + macos × Python 3.10–3.13, a symlinked TMPDIR, and the package's own suite run inside the package ## v0.2.1 — 17.08.2026 _PATCH: nothing that ships behaves differently. `v0.2.0` could not be published — its own test suite failed on a public runner, and the workflow runs that suite before it publishes._ ### What this changes in the conclusions Nothing. This release exists because `v0.2.0` never reached anybody. A test read `src/tools/check_push.py` — the private repository's pre-push hook, which guards a history a recipient does not have and therefore does not ship. Inside the package the file is absent, the test raised `FileNotFoundError`, the suite went red and no version was published. That is the third time in three days for the same shape: `.personal_patterns` in v2.23.0, `share/` in v0.1.3, this one now. The first two were repaired by guarding the one test that had failed — which is how a class survives being fixed. The third is the one worth naming: `support.IN_SOURCE_REPO` already existed, written for the second, and simply was not used. So the repair is on the publication rather than on the test. `publish_share.sh` now **runs the package's own suite inside the package**, before anything is committed or pushed, and a red run stops the publication instead of announcing it. `pyproject.toml` has stated that rule in prose since the sdist was defined — «a green run at the author's end with a red one at the recipient's does not mean verified, it means the state of the artefact is unknown» — and nothing enforced it. The run leaves `__pycache__`, which the audit calls build junk, so the last word belongs to a clean rebuild and a second audit. A textual rule was tried first and abandoned, which is worth recording so nobody tries it again: «a test module reading a repository-only path must name IN_SOURCE_REPO» flagged `test_redact.py`, which correctly writes its own `.personal_patterns` into a temporary directory, then missed the real defect, and tightening it would have failed three modules that guard themselves differently and correctly. A check wrong in both directions is worse than no check. ### What is withdrawn Nothing. `v0.2.0` was tagged and pushed but never published — anyone who has it has it from the repository, not from an index. ### What needs recomputing Nothing. ### Changes by file **Tools and build** - `src/tools/publish_share.sh` — the artefact's own suite runs before the commit; a clean rebuild after it **Tests** - `tests/test_privacy_guard.py` — the push-gate test asks whether it is in the repository - `tests/test_build_audit.py` — the publication is checked for running the suite it declares ## v0.2.0 — 17.08.2026 _Comparison base: v0.1.3 → HEAD. 13 files changed. **MINOR, not PATCH: this is a break in the series** — `src/scholion/knowledge/` changed and the same input now yields a different answer._ ### What this changes in the conclusions **The DPYD panel goes from two variants to seven.** The 2024 joint consensus of AMP, ACMG, CPIC, CAP, DPWG, ESPT, PharmGKB and PharmVar names seven Tier 1 DPYD alleles — the set a clinical panel is expected to carry. This one carried two: `*2A` and `c.2846A>T`. Now it carries all seven: `*13`, HapB3 (both of its tags), `c.557A>G`, `c.868A>G` and `c.2279C>T` are added, with their coordinates taken one at a time from dbSNP and Ensembl rather than from anybody's memory. The gene is on the minus strand, so the cDNA notation and the allele written in a VCF are opposites; each was converted and checked. A person who ran `drug capecitabine` before this release and was told the DPYD markers looked normal was told that on the strength of two positions out of seven. The same command now says «read 2 of 8 markers» and names the six it could not read. **Nothing about that person changed; what the answer admits did.** **A haplotype carried by two variants counted as two alleles.** DPYD HapB3 is one allele described by `rs75017182` and `rs56038477`, which travel together. The counter added copies per marker, so a single heterozygous carrier would have produced two decreased-function alleles — CPIC activity score 1.0 read as 0.0, an intermediate metaboliser reported as fully deficient. The direction is towards caution, which is why it could have sat there; it is still a wrong statement about a person. Markers may now declare a `haplotype`, and one is counted once. **Five markers were outside the extraction target and nobody could have seen it.** `fastq_to_vcf.sh` carried its own table of regions to align against. Two CYP2C19 markers were written on chromosome 19 — the gene is on 10. Two DPYD intervals stood 373 kb and 899 kb from their loci. `rs1142345` — TPMT `*3C`, the commonest deficient allele in Europeans — was 31 bases outside the left edge. None of that surfaces as an error: `bcftools` finds no row outside the target and the marker comes out `./. (ref/not covered)`, exactly what a position the sequencing genuinely missed produces. Anyone following the documented route was told nothing was found where their genotype was. The table is gone: the target is generated from the catalogue at every run. ### What is withdrawn **Any previous «DPYD looks normal» rests on two positions out of seven and does not exclude a deficiency.** It was never phrased as an exclusion, but it was read as one, and at full-dose fluoropyrimidine the difference is not academic. **Any previous genotype for CYP2C19 `*2`/`*17`, TPMT `*3C` or the two DPYD markers, obtained through `fastq_to_vcf.sh`, is not a result.** Those positions were never in the alignment target; `./.` there means «not looked at», not «reference». ### What needs recomputing **By hand — if your VCF came through `fastq_to_vcf.sh`.** Re-run the extraction: the target now reaches five markers it did not before. Genotypes read from a full-genome VCF produced elsewhere are unaffected — only the targeted route was narrow. Nothing else needs recomputing. The wider DPYD panel does not change a stored value; it changes how much of the panel an answer admits to having read. ### Changes by file **Knowledge — a break in the series** - `src/scholion/knowledge/cpic_drug_gene.json` — DPYD: 2 markers → 8, `haplotype` on the HapB3 pair - `src/scholion/knowledge/loci.json` — 6 DPYD loci added; new `regions` section for whole-gene windows **Engine and application** - `src/scholion/engine.py` — a multi-tag haplotype counts once **Data preparation** - `src/ingest/fastq_to_vcf.sh` — the target BED is generated from the catalogue - `src/ingest/update_check.sh` — the version of ClinVar is read by the name it is written under **Tools and build** - `src/tools/check_staged.py` — `in_forbidden_dir`: a personal-data folder under a neighbouring name - `src/tools/check_push.py` — uses that predicate instead of its own copy **Tests** - `tests/test_answerability.py` — haplotype counting; the online drug route - `tests/test_catalogue_integrity.py` — the DPYD panel against the external consensus - `tests/test_pgx_script_coordinates.py` — BED-shaped coordinate tables; the margin invariant; note vs catalogue - `tests/test_privacy_guard.py` — a renamed personal folder ## v0.1.3 — 17.08.2026 _Comparison base: v0.1.2 → HEAD. Commits: 6. 21 files changed, 356 insertions(+), 19 deletions(-)._ ### What this changes in the conclusions One new layer: `prescription` can now surface a hand-curated fact from the patient's own record. `medications[].safety_flags[]` in `profile/medications.json` holds entries the engine never invents — it only reads one a person wrote down: a documented diagnosis, a documented event, a conflict with their own history. A flag marked `red_flag` lifts the overall verdict to `high`, anything else to `moderate`, and the renderer prints it first — above the genome, the labs and the interactions, because a flag read last is a flag not read. The web view carries the same block, and both skill editions now say when to raise one: as soon as a new document yields a diagnosis that changes how a current prescription reads, and in reverse — check any new document against the current drug list. Nobody who has not written a `safety_flags` entry into their own profile sees any difference in output; the layer is silent until a person fills it in. Everything else in this release is process and safety-net, not a change to any result: the shipped skill file is now checked against substitution at build time (the package ships two editions of `SKILL.md` and a build error used to be able to put the wrong one at a given path without anything noticing); the package's own test suite is now verified to pass from *inside* the package it built, not only inside the repository that builds it; a repository-hygiene test that mistakenly flagged the package's own shipped profile/genome templates as personal data is corrected; and a publication commit can now be signed with a real identity instead of the anonymous one the tooling used before. ### What is withdrawn Nothing. ### What needs recomputing Nothing computed from existing data changes. `safety_flags` is something a person adds by hand to their own `profile/medications.json`; it has no effect until they do. ### Changes by file **Tools and build** - `src/tools/check_language.py` — changed - `src/tools/check_staged.py` — changed - `src/tools/install_hooks.sh` — changed - `src/tools/make_shareable.py` — changed - `src/tools/publish_share.sh` — changed **Skill** - `share/SKILL.shared.md` — changed - `src/skill/SKILL.md` — changed **Engine and application** - `src/scholion/engine.py` — changed - `src/scholion/format.py` — changed - `src/scholion/i18n/en.py` — changed - `src/scholion/i18n/ru.py` — changed - `src/scholion/skill/SKILL.md` — changed - `src/scholion/web/index.html` — changed **Guides and documentation** - `CLAUDE.md` — changed - `README.md` — changed - `docs/DATA-LAYOUT.md` — changed **Tests** - `tests/support.py` — changed - `tests/test_build_audit.py` — changed - `tests/test_licensing.py` — changed - `tests/test_repo_hygiene.py` — changed **Other** - `.gitignore` — changed
Commits - a diagnosis of the patient's own can now outrank a computed verdict - the package's own template files no longer read as somebody's personal data - readme: trim the versioning explanation to one sentence - the publication commit can be signed by a person, and the setting that does it no longer kills the script - the suite stops being red inside the package it was built from - the shipped edition of the skill is checked, and the layout gains a slot for what was made to be read
## v0.1.2 — 17.08.2026 _Comparison base: v0.1.1 → the working tree (not committed yet). Commits: 0. 5 files changed, 79 insertions(+), 13 deletions(-)._ ### What this changes in the conclusions Nothing about anyone's health, and nothing in how a result is computed. `set-folder` used to accept only a fixed list of eight domain names (labs, medications, metrics, genome, labs_docs, med_docs, garmin, apple_health — the last one newly recognised in this release, though nothing parses it yet) and refused everything else outright. A source folder that is legitimately personal — a CGM app's screenshots, a specific sequencing provider's export folder, whatever the next such folder turns out to be called — had no way to be recorded at all. An unrecognised domain name is now accepted rather than refused, and filed under a new `external_sources` section of `profile/sources.json` instead of `folders`. `core.source_config()` — what every reader of a configured folder actually calls — merges both sections, so the split matters only at the moment a folder is set, never when one is read back. The trade-off is explicit, not accidental: the old refusal also caught a typo of one of the eight names as a side effect ("grmin" for "garmin"). Opening the domain up removes that — a near-miss is now just an ordinary new `external_sources` entry, and the intended domain is left untouched, not corrected. Nothing reads `external_sources` programmatically yet, so today that costs nothing silent. ### What is withdrawn Nothing. ### What needs recomputing Nothing. No stored value, no catalogue and no conclusion changed. ### Changes by file **Engine and application** - `src/scholion/cli.py` — changed - `src/scholion/core.py` — changed - `src/scholion/store.py` — changed **Tests** - `tests/test_external_sources.py` — added **Other** - `VERSION` — changed ## v0.1.1 — 16.08.2026 _Comparison base: v0.1.0 → the working tree (not committed yet). Commits: 0. 1 file changed, 53 insertions(+), 1 deletion(-)._ ### What this changes in the conclusions Nothing about anyone's health. Two defects in the gate that runs last — the pre-push hook — one of which would have stopped the first push of this repository anywhere. **The pre-push check refused the synthetic genome fixture.** `tests/fixtures/genome/ tiny.vcf.gz` is the one genome file this project allows into its history, and it is allowed by content rather than by name: the header has to declare it invented, and the call set has to be small enough that nothing real fits through. Four gates ask `synthetic_fixture` that question — the pre-commit hook, the build audit, the repository-hygiene test, the `.gitignore` negation. `check_push.py` never did. It saw a `.vcf.gz` in the history and blocked, which means every earlier gate could approve the repository and the last one would refuse it at the moment of pushing, over a file the project ships deliberately. The exception is now asked of the same module the other four ask, so it cannot drift between them. **The pre-publication tags could travel out with `git push --tags`.** 32 tags named `v1.0.0` … `v2.24.0` live under `pre-0.1.0/` because the numbering was reset at publication and those same numbers will be used again. `git push --tags` does not ask which tags — it sends every tag it has. The CI filter (`tags: ["v*"]`) would not have published them to PyPI, but by then they would already be in a public history, and a pushed tag is not taken back by deleting it locally. The hook is the only place that sees the refs and can still say no; it now refuses them by name and prints what to push instead. Neither would have fired today — the private repository has no remote, so there is nowhere to push. Both would have fired the first time one was added. ### What is retracted Nothing published. The claim being corrected is internal: that the five gates guarding personal data all asked one predicate. Four did. ### What needs recomputing Nothing. No stored value, no catalogue and no conclusion changed. ### Changes by file **Tools and build** - `src/tools/check_push.py` — changed ## v0.1.0 — 16.08.2026 _The first release anyone outside the project can install._ ### What this changes in the conclusions Nothing is retracted and nothing needs recomputing: there is no previous public version to compare against. What follows is what the release contains and, more importantly, what its number means. ### What the number means **Below `1.0.0` the public contract may break.** Command names, the top-level fields of `--json`, the file names inside a profile — the project's own rule is that these may grow and may not shrink, and `python3 src/tools/check_compat.py` enforces it on every run. Until `1.0.0` that rule is **internal discipline, not a promise made to anyone outside**: it is stated here so that a person who builds on `--json` knows precisely how much weight it carries, which is some, and not all. **`1.0.0` arrives by use, not by features.** The condition is a number of people who have run this on their own medical data and said what happened — not a count of finished capabilities. Everything in this release was verified on invented forms, an invented profile and one real record; the failure modes that matter for a system like this appear in the second record and in the tenth, not in the first, and no amount of building substitutes for that. ### What it does Reads a person's genome, laboratory history, prescriptions, clinical conclusions and wearable data against each other, locally, with the source shown behind every statement. One core with three entry points — a local web app, a command line, and a skill for a language model — and a rule, enforced by a test, that a capability appears in all of them at once. The part worth naming is what it refuses to do: - **A value without a reference corridor is never shown as normal.** Green means «inside the corridor»; with no corridor there is no such claim to make. - **A connected genome cannot make an answer less cautious.** A position with no row in a VCF is either the reference or no coverage at all, and the file does not say which — so it is carried as an explicit confidence level rather than collapsed into «reference». Before this was fixed, a person with a genome attached could get a calmer answer about a drug than the same person without one. - **Units are a gate before they are a conversion table.** A value is admitted into a series only in a unit the marker declares, and the reference range is converted with it — the two travelling apart is a defect this project found in itself. - **A source that was never reached makes no negative statement.** «No significant pharmacogenetics» and «the database did not answer» are different sentences. - **What is not known is printed.** `scholion limits` states the coverage behind the genomic conclusions and names what cannot be concluded from the data present. Everything it produces is material for a person's own decisions and for a conversation with their physician. It is not a medical device, it is not a clinical decision support system, and it does not diagnose. ### What is knowingly incomplete - **Nobody outside has run this yet.** That is the single largest gap in the evidence behind everything above. - **eGFR is not read from an English laboratory form.** Its value is printed on the line below its name, and relaxing that rule would take a wrong number on the very form the rule was written for. It needs a real English form to decide on, and there is not one — so it stays named rather than fitted. - **175 markers are recognised in Russian only** — urine organic acids, the coprogram, the dysbacteriosis culture, the element panels. They are barely ordered outside Russia; their labels are work an outside contributor can do without touching any logic. - **LOINC codes are absent.** The table is available from Regenstrief, and using it obliges the project to ship a verbatim notice in `NOTICE` in the same commit. Neither half could be done honestly on release day. - **Pharmacogenetics covers seven genes** — the core with the strongest evidence, not a full panel. CYP2D6 as a whole needs a dedicated tool: structural variants and phasing. ### What holds it together 393 tests, run offline against synthetic fixtures and executed inside the built package rather than only in the repository. A parsing baseline recorded *before* the dictionary migration that followed it — the timing is the value: a baseline recorded after a change states that the code equals itself and passes on any behaviour. Every test written for a defect was re-run against the un-fixed code to prove it catches it. A build audit that fails the package on any personal datum — in plain text or encoded — and three independent barriers keeping personal data out of git. --- - System insights show annual direction alongside emerging month and quarter changes, with compact charts. Recent reversals remain visible even when the annual direction differs. A single reading within its recorded range does not trigger requests for more tests merely to draw a trend.