Two standards, 26 cantons · Part 10 of 14
10 · A chapter for implementers — the drift, and the gap we know
The previous chapter measured the standard from the outside. This one opens the files.
This chapter is for the people who actually build the channel. It carries two findings. The first: three eCH-0276 schemas are in circulation at once; two of them carry the same namespace, the same version designation, and the same mandatory attribute declaration — and differ in 88 silent data-type changes that no change log records. The second: the swissdec guidelines require a test marker in two places, and it exists in none of the schemas shipped with them. A gap of that kind has exactly one failure direction, and we learned in Germany, expensively, what that direction looks like.
Three files, one name — and who supplies them
Anyone who says «eCH-0276» today has to say which file they mean. We opened all three on 04.08.2026 and counted.
| File (SHA-256, truncated) | Origin | Namespace | version | Element names |
|---|---|---|---|---|
eCH-0276-1-0.xsd (14a0d6cb…) | ech.ch, version 1.0.0; decided 02.12.2024, published 05.12.2024 | …/eCH-0276/1 | "0" | 890 |
eCH-0276-1-0-4_20260115.xsd (032a3449…) | from the swissdec Transmitter package, edition 06.03.2026; file dated 15.01.2026 | …/eCH-0276/1 | "0" | 892 |
eCH-0276-2-0-0.xsd (df4cd47a…) | Annex to the eCH draft V2.0.0; published 23.07.2026 | …/eCH-0276/2 | "0" | 934 |
The middle file is worth naming twice: it is not published on ech.ch. It sits in the schema/ folder of the swissdec package of 06.03.2026, and that package's container binds it by name: <xs:import namespace="http://www.ech.ch/xmlns/eCH-0276/1" schemaLocation="eCH-0276-1-0-4_20260115.xsd"/>. Anyone transmitting through the Distributor therefore validates against a different file from the one eCH publishes under the same version number. That is not an ambiguity but a binding, and it sits in a single line of a single file — it comes to light only when the package is unpacked.
And a correction to a wording that is often repeated, ours included: the two files do not carry «dieselbe Versionsangabe 1.0» (the same version designation 1.0). The version attribute in the xs:schema tag reads "0" in all three files. The standard sets this out itself, in §2.4 of the main document: «Die eCH-Standard Major-Version, die dem XML zugrunde liegt ist im Namespace aufgeführt» (the eCH standard major version underlying the XML is stated in the namespace) — and: «Die eCH-Standard Minor-Version wird in jedem Fall im XML-Schema im xs:schema Tag mittels „version = X" gesetzt.» (the eCH standard minor version is in every case set in the XML schema, in the xs:schema tag, by means of „version = X"). The published file and the swissdec file therefore share the namespace http://www.ech.ch/xmlns/eCH-0276/1, the schema version designation version="0", and the same mandatory attribute declaration minorVersion. A validator that set out to tell them apart has nothing to go on.
The name diff, and why typo repairs break things
Between the published file and the swissdec file, 13 element names are removed and 15 are new. Among those removed are annualProfitOrAnnualLoss, disclosedHiddenReserves, participationInPercent, primeCostsNumber, freeCapitalReservesTaxLaw, and tiedCapitalFreeFundsTaxLaw; among the new ones are disclosedHiddenReservesInformativ, disclosedHiddenReservesTaxableCapital, generalParticipationsDetails, balanceSheetDateBusinessYear, and unpaidShareCapitalTaxableEquity.
Some of these are pure spelling corrections: incomTaxValueOfTotalAssetsAtTheEndOfTheTaxPeriod is now called incomeTaxValueOfTotalAssetsAtTheEndOfTheTaxPeriod; otherReasontaxAdjustmentsDecreasingTaxableResult — with a lower-case t — disappears without replacement. A rename that heals a typo breaks every conforming instance just as hard as a substantive change. The parser knows no intentions; it knows character strings.
One entry, though, is not a repair but an addition: taxExemptLegalEntity, of type xs:boolean. A new declarable fact about the taxpayer — tax-exempt or not — introduced within an unchanged major version, in a file a vendor obtains from a transmission package rather than from the standards body.
The finding the name diff misses
Names can be compared by eye. Types cannot. 88 of the 877 shared element names silently change data type between the two files, in 84 cases from xs:long to xs:decimal. The changes reach the balance sheet and the income statement at their core: totalAssets, totalEquity, cashAndCashEquivalents, machinery, goodwill, currentProvisions, accruedExpenses, fixedTangibleAssets.
That is concrete and falsifiable, and we did not assert it; we ran it. One local copy of each of the two original files, each with a test root element of type assetsType; the same instance against both, under xmllint --schema:
<totalAssets>1234567.89</totalAssets>
against the ech.ch file: Element 'totalAssets': '1234567.89' is not a
valid value of the atomic type 'xs:long'. → fails
against the swissdec file: validates
Same namespace. Same version designation. Rappen count as an error in one file and are permitted in the other, and nothing in either file says which one is in hand.
For the build, that means two things, and neither direction is comfortable. Anyone developing against the published file rounds the Rappen away before writing — correct against that schema, but against the swissdec file a silent loss of precision that nobody reports, since a rounded figure is valid there as well. Anyone developing against the swissdec file and delivering Rappen produces a file that breaks against the published version in 84 places — and breaks visibly. A rename shows up at the first validation. A type change shows up only when both schemas are held and both are run.
V1 → V2: the shift that no change log records
From the published V1.0.0 to the V2.0.0 draft: 751 shared names, 139 removed, 183 new — and 225 of the 751 shared names change data type. Of those, 149 go from xs:long to twoDecimalDigitsType and 28 to taxAmountType. The new type is defined as
<xs:simpleType name="twoDecimalDigitsType">
<xs:restriction base="xs:decimal">
<xs:fractionDigits value="2"/>
</xs:restriction>
</xs:simpleType>
— two decimal places, and no bounds. With xs:long, the upper limit of 9'223'372'036'854'775'807 goes as well. In practice no Jahresrechnung will exceed it; the two changes are documented differently: the value-range limit falls away without an entry of its own, while the same change log lists the alphabetical sorting of an overview table as a change.
Nor does the movement point in one direction. In the same version, type safety is gained in one place and given up in another. Gained: country goes from xs:string to eCH-0010:countryType, cantonOfBusinessPremises from xs:string to eCH-0007:cantonAbbreviationType. Given up: zipCodeHeadOffice goes from eCH-0010:swissZipCodeType — four digits, checked — to eCH-0276:textField254Type, i.e. 254 arbitrary characters; townHeadOffice takes the same route. Country and canton narrow; postal code and town become free text. This is not an oversight anyone could diagnose from the outside, and it is not a criticism either — it is an observation that anyone with the two files reproduces in two minutes. But anyone who relied on swissZipCodeType in their outbound check loses that check at the version change, and nothing anywhere advises against it.
Annex D of the V2.0.0 main document, «Änderungen gegenüber Vorversion» (changes compared with the previous version), lists five lines: the SimpleType overview sorted alphabetically; references on the title page and in Annex A adjusted; figures updated; titles added for the annexes; and naming conventions for the XML file and the package supplemented. None of the 225 type changes appears there. The change log describes the document; the breaking changes sit in the annex.
One figure as a guide through the whole drift
annualProfitOrAnnualLoss, the annual profit, traces the movement in a single value.
- V1.0.0: one name, two declaration sites — on the liabilities side
minOccurs="0", in the income statementminOccurs="1". Bothxs:long. - swissdec 1-0-4: two names.
annualProfitOrAnnualLossEquityAndLiabilities(xs:decimal, optional) andannualProfitOrAnnualLossIncomeStatement(xs:long, mandatory). - V2.0.0 draft: three names across three registers. The two accounting variants are now
twoDecimalDigitsTypeand both optional. The only mandatory one is calledannualProfitOrAnnualLossFiscalCorrections, sits in the fiscal corrections, and is the only one that stays an integer.
Rule-set line D9200 of the SSK closes the sequence, action: «= C9200, gerundet auf Ganzzahl» (= C9200, rounded to an integer), with the addition «Die Art der Rundung wird durch Kanton festgelegt.» (the canton determines the type of rounding).
One figure, typed four ways, in three files all called eCH-0276. The accounting figure is precise and optional; the tax figure is rounded and mandatory. Anyone looking for the point in this standard where bookkeeping ends and assessment begins finds the border here, in the data type.
Migration, run rather than asserted
Both official example instances validate cleanly against their own schema, so the checking chain holds. Then the obvious shortcut: take the V1 instance, s/eCH-0276\/1/eCH-0276\/2/g, validate against V2. Result: errors in at least eight places, verbatim:
'statuatoryCapitalReserveLimitedCompanies': This element is not expected.
Expected is one of ( … statutoryCapitalReserveLimitedCompanies … )
'TotalOtherOperatingExpenses': This element is not expected.
Expected is ( totalOtherOperatingExpenses )
'profitOrLossBroughtForward': Character content other than whitespace is not
allowed because the content type is 'element-only'
Two typo repairs and a scalar that has become a complex type — in precisely the messages a migrator will see. Rewriting the namespace is not a migration.
The TestCase gap
The guidelines require a test marker twice. §8.1, alternative flow: «Die Meldung wird als Testmeldung gekennzeichnet. (Ein Element TestCase wird im Job Element eingefügt).» (the message is marked as a test message. (An element TestCase is inserted in the Job element).) §8.3, UC003: «Das TestCase Flag ist jeweils auf dem Job bei der initialen Meldung oder im CaseContext bei der Synchronisation gesetzt» (the TestCase flag is set on the Job for the initial message or in the CaseContext during synchronization), postcondition: «Die Meldung wurde vom Endreceiver empfangen. Sie darf nicht produktiv weiterverarbeitet werden.» (the message has been received by the end receiver. It must not be processed further in production.) Annex F, AB-11, requirement 4: «Jede Meldung, die in Zusammenhang mit dem Geschäftsvorfall versendet wird, muss die Testfall-Markierung tragen.» (every message sent in connection with the business transaction must carry the test-case marking).
Measured on 04.08.2026 against the package of 06.03.2026: grep -r "TestCase" across all 35 XSD files shipped with it → 0. Across all 11 SOAP examples shipped with it → 0. Testfall across schema/ → 0.
Nor is there a place left open to put one. DeclareBalanceSheetJobType and DeclareRawBalanceSheetJobType are closed xs:sequence with two children each — Addressees and Addressee respectively, plus an optional Substitution — and xs:any occurs zero times in the container schema. A message with a TestCase child would be schema-invalid; a schema-valid message cannot carry one.
The limits of this finding belong with it: Annex A references a [CONTAINERXSD] Container Schema. Swissdec. without a URL, and that schema is not included in the package. And the finding itself reads «0 über die Schemata und die Beispiele» (0 across the schemas and the examples), not «0 über das ganze Paket» (0 across the whole package) — the PDF streams are compressed, and in the German running text TestCase appears twice, in the two places quoted above.
And then the sentence that comes from our own record and judges no one: We know this pattern. In Germany we lost six returns because a missing test marker did not count as an error but as a live case. A test marker that the specification requires and the schema does not know has exactly one failure direction: everything is live. Anyone implementing this channel would want that settled before the first call — not after.
An open tension around UC002
§3.2 of the guidelines says: «Die Use Cases Abschnitt 8.2, ‹UC002 Status abholen› und Abschnitt 8.12, ‹UC012 Dialog Message abwickeln› werden von eBilanz nicht unterstützt.» (the use cases in section 8.2, ‹UC002 fetch status› and section 8.12, ‹UC012 handle dialog message›, are not supported by eBilanz). At the same time, table 3.1 maps UC002 onto concrete operations (GetStatusFromDeclareBalanceSheet and the corresponding response), §§7.6 and 7.7 specify it in full, the WSDL binds it as a SOAP operation, two example XML files are included for it, and §8.1 lists UC002 among the Included Use Cases of the initial message. §7.2 is titled «Initiale asynchrone Meldung» (initial asynchronous message) — and in §8.1 asynchronous is expressly defined: «Asynchroner Aufruf, bei dem der Distributor mit einem JobKey antwortet und der Transmitter mittels <GetStatus> Polling die Antworten der Endreceiver erhält.» (an asynchronous call in which the Distributor answers with a JobKey and the Transmitter receives the end receivers' answers by <GetStatus> polling). The eCH-0276 route, which is exactly the route a Deklarationslösung takes, cannot be completed without a status query. We record this as an open tension and do not resolve it — both sentences stand in the same 126-page document, and neither is a typo. Anyone implementing the channel would want to know before certification which of the two statements applies, because UC002 is the one operation a private Deklarationslösung would have to build in any case.
The smaller items, all checkable
Annex G renders the payload line of both operations as <null>bsc:blubber</null>, while the XSD correctly references eCH-0276:eBalanceSheetETaxLegalEntity and xbrl:xbrl respectively; grep -rn "blubber" schema/ returns 0. The one line that distinguishes the two tracks is a placeholder in the human-readable annex.
Namespace capitalization: the main documents V1 and V2 write http://www.ech.ch/xmlns/ech-0276/1 and …/ech-0276/2 respectively in §2.4; all three schemas write eCH-0276. URIs are character-exact. Anyone following the prose instead of the XSD produces an instance that does not validate.
Five enum spellings appear in the V2 main document and are rejected by the V2 XSD: bookprofitsFromHigherValuation…, …OfPeridiodicity (twice), othertypeOfSecurity, singleFamilyHousesflat…. Counter-check: in V1, document and schema agree in all five places — both carry the old spelling. The V2 XSD has repaired them; the V2 prose has not. That is a regression in the consultation draft, not a long-standing property of the standard.
attachments carries minOccurs="2" in V2 — the only minOccurs="2" in the whole 2'226-line schema — on a self-recursive type. The official reference example therefore emits the block twice, byte-identically, with an empty <documentTitle/>.
The error that can be reported before 03.09.2026, and that costs five characters
textFieldAttachmentTitleType, new in V2 and the type of documentTitle:
<xs:pattern value="[a-zA-Z0-9äöüÄÖÜ.\-_ ]*"/>
German umlauts — but no accent, no apostrophe, no comma, no bracket. «Bericht der Revisionsstelle, 2025» (report of the Revisionsstelle, 2025) is invalid (comma). «Rapport de l'organe de révision» (report of the auditing body) is invalid twice over: apostrophe and é. In a standard whose annexes are named d_f. If this text brings one item into the Vernehmlassung, it is this one. The Vernehmlassung is open through a public form with a button «RFC erstellen» (create RFC), with no membership barrier, until 3. September 2026.
The governance contradiction that legitimizes the whole drift
An approved standard may move like this because it permits itself to — and disputes it at the same time. Two clauses, twenty pages apart, in the same document:
§1.1: «Genehmigt: Das Dokument wurde vom Expertenausschuss genehmigt. Es hat für das definierte Einsatzgebiet im festgelegten Gültigkeitsbereich normative Kraft.» (approved: the expert committee has approved the document. Within the defined scope, for the defined field of application, it has normative force.)
§4: «eCH-Standards … haben nur den Status von Empfehlungen. … Der Inhalt von eCH-Standards kann jederzeit und ohne Ankündigung geändert werden.» (eCH standards … have only the status of recommendations. … The content of eCH standards may be changed at any time and without notice.)
Alongside that, from one canton, comes a third description of the same legal position — minutes of the Steueramt Solothurn with EXPERTsuisse and TREUHAND|SUISSE of 25.11.2025, Traktandum 8: «Der Standard ‹eCH-0276› wurde per 01. Januar 2025 in Kraft gesetzt. Gegenwärtig sind noch Anpassungen und Korrekturen am Standard in Arbeit.» (the standard ‹eCH-0276› was brought into force as of 01. Januar 2025. Adjustments and corrections to the standard are currently still in progress.)
Normative force, mere recommendation, brought into force. Three descriptions, all published, all dated. We put them side by side and do not simplify them — the earlier wording, that eCH standards are «ihrer eigenen Bestimmung nach Empfehlungen» (recommendations by their own definition), captures only one of the three.
The recommendation, with swissdec's own authority behind it
It stands on page iv of the Transmitter guidelines, ahead of every diagram:
«Verbindlich sind ausschliesslich die jeweils veröffentlichten offiziellen XML-Schemata.» (only the officially published XML schemas in force at any given time are binding.)
One rule follows from that, and it is what makes this chapter practical: validate against the schema, not against the version number — and note at the point of download which package the file comes from. In this standard, a version number today does not say which file is meant. A SHA-256 and a package date do.
Anyone building the channel can turn that into a short list. First: hash every XSD obtained and record where it came from, not just the version. Second: run the regression check against all versions in circulation, not against the newest — the difference between 890, 892, and 934 element names is not cosmetic, and the 88 and 225 type changes are visible only to whoever measures them. Third: before the first call in production, settle in writing how a test message is marked on this channel, and what the end receiver does when the marking is absent. In AB-12 the guidelines describe three checking stages — «Validierung, Plausibilisierung, Akzeptierung» (validation, plausibility check, acceptance) — and record: «Nur der Endreceiver kann die Akzeptanzstufe durchführen.» (only the end receiver can carry out the acceptance stage.) Whoever makes the first call without having settled that question learns the answer on a real tax return.