A stocktake of the machine-readable Jahresrechnung in Switzerland · 255 public documents, every statement with its source and its date

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 the same time, 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 names. 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 like that 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 saying «eCH-0276» today has to say which file they mean. We opened all three on 04.08.2026 and counted.

File (SHA-256, shortened)OriginNamespaceversionElement names
eCH-0276-1-0.xsd (14a0d6cb…)ech.ch, version 1.0.0, decision 02.12.2024, publication 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, publication 23.07.2026…/eCH-0276/2"0"934

The middle file is important enough to name 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 via 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 one line of a single file — it surfaces only when the package is unpacked.

And a correction to a formulation that is often read, 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 explains this itself, in the main document §2.4: «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 wanted to tell them apart has nothing to do it by.

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. Removed are, among others, annualProfitOrAnnualLoss, disclosedHiddenReserves, participationInPercent, primeCostsNumber, freeCapitalReservesTaxLaw and tiedCapitalFreeFundsTaxLaw; new are, among others, disclosedHiddenReservesInformativ, disclosedHiddenReservesTaxableCapital, generalParticipationsDetails, balanceSheetDateBusinessYear and unpaidShareCapitalTaxableEquity.

Among them 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 exactly as hard as a substantive change. The parser knows no intentions; it knows character strings.

Among them, though, is also one entry that is not a repair but an addition: taxExemptLegalEntity, type xs:boolean. A new declarable fact about the taxpayer — tax-exempt yes or no — introduced within an unchanged major version, in a file a vendor obtains from a transmission package and not from the standards body.

The actual find that 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. It hits 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. From each of the two original files one local copy with a test root element of type assetsType, the same instance against both, 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 are an error in one file and permitted in the other, and nothing in the files 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, because a rounded figure is valid there too. Anyone developing against the swissdec file and delivering Rappen produces a file that breaks against the published version in 84 places — and does so 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. 149 of them from xs:long to twoDecimalDigitsType, 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, its upper limit of 9'223'372'036'854'775'807 disappears 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.

The movement does not point in one direction either. 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 get narrower, 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, without anyone advising against it.

Annex D of the V2.0.0 main document, «Änderungen gegenüber Vorversion» (changes compared with the previous version), lists five lines: SimpleType overview sorted alphabetically, references on the title page and in Annex A adjusted, figures updated, titles added for the annexes, naming conventions for XML file and 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, shows the movement in a single value.

  • V1.0.0: one name, two declaration sites — on the liabilities side minOccurs="0", in the income statement minOccurs="1". Both xs:long.
  • swissdec 1-0-4: two names. annualProfitOrAnnualLossEquityAndLiabilities (xs:decimal, optional) and annualProfitOrAnnualLossIncomeStatement (xs:long, mandatory).
  • V2.0.0 draft: three names across three registers. The two accounting variants are now twoDecimalDigitsType and both optional. The only mandatory one is called annualProfitOrAnnualLossFiscalCorrections, sits in the fiscal corrections and is the only one that stays an integer.

This is closed by rule-set line D9200 of the SSK, action: «= C9200, gerundet auf Ganzzahl» (= C9200, rounded to an integer), with the addition «Die Art der Rundung wird durch Kanton festgelegt.» (the type of rounding is determined by the canton).

One figure, four typings, 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 where bookkeeping ends and assessment begins in this standard finds the border here, in the data type.

Migration, driven 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 exactly the messages the 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 synchronisation), 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 across the package of 06.03.2026: grep -r "TestCase" across all 35 XSD shipped with it → 0. Across all 11 SOAP examples shipped with it → 0. Testfall across schema/0.

Nor is a place to put one left open. 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 limit of this finding belongs 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 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 nobody: We know this constellation. 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 productive. 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 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 asynchronous, in §8.1, means expressly: «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 present this as an open tension and do not resolve it — both sentences stand in the same 126-page document, and neither of the two 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 capitalisation: 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 stand in the V2 main document that the V2 XSD rejects: 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 runs through a public form with a button «RFC erstellen» (create RFC), with no membership barrier, until 3. September 2026.

The governance contradiction that legitimises the whole drift

Why may an approved standard 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 document has been approved by the expert committee. For the defined field of application within the defined scope 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, 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 formulation, that eCH standards are «ihrer eigenen Bestimmung nach Empfehlungen» (recommendations by their own definition), renders 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 all the diagrams:

«Verbindlich sind ausschliesslich die jeweils veröffentlichten offiziellen XML-Schemata.» (only the officially published XML schemas in force at any given time are binding.)

From that follows the one rule that 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 does not answer today which file is meant. A SHA-256 and a package date answer it.

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 seen only by whoever measures them. Third: before the first productive call, 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 sends the first call without having settled that question learns the answer on a real tax return.