docs(architecture): ADR for the fiscalisation capability + VAT-rate annex on ADR-026 - #2056
Conversation
…nnex on ADR-026 Records, ahead of #1908's code, why fiscalisation is a capability of its own rather than an InvoicingPort document type, and where each of its contract-level guarantees lives. - ADR-040 (new): fiscalisation as a capability distinct from invoicing. Base contract taken from the published fiskaltrust / efsta shape (register a transaction, receive back what must be printed), trust anchor confined to the adapter, the device dependency expressed as a sub-capability (#1910), and exactly-once registration owned by core because a double fiscal registration is a legal event for the seller. Journal export, fiscal corrections, a second adapter and the legal-obligation determination are recorded as deferred with reasons. - ADR-026: annex recording where a line's tax rate comes from - the master, not a computation - with the rejected TaxCalculationPort alternative and the tax-rate-conflict state that blocks both invoice issue and fiscalisation. The ADR-014 supersession it requires is noted and left to #2054. - ADR index + architecture-overview: register ADR-040 and describe the planned fiscalisation capability where a reader first meets it. Numbering note: 038 was taken by the rate-limiting ADR (#2019, merged today), so fiscalisation is 040 and the sibling routing ADR (#2051) keeps 039. Refs #2009 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HpwFwSVZYF7nopZ5S3Peet Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
…nt routing The sibling sales-document routing ADR is now numbered (039, #2051), so refer to it by number and link it, instead of citing the issue alone. Refs #2009 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HpwFwSVZYF7nopZ5S3Peet Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
Tech lead review: ADR-040 fiscalisation capability + ADR-026 VAT-rate annexScope: 4 files, +188/-0, documentation only. SummaryThis is a well-constructed ADR and the central decision is right. Fiscalisation genuinely is not an The problems cluster in three places, none of which is the core decision:
Plus a handful of legal-citation corrections that matter because a compliance ADR gets quoted at sellers. Below is the consolidated set. Line references are to the files as they stand on this branch. BlockingB1.
|
…ate amendment Addresses the tech-lead review on PR #2056. Blocking: - eparagony.pl is a private e-receipt distribution hub, not a certified fiscaliser; say so, reword the capability to "performs or brokers the fiscal registration", and state that no v1 adapter implements FiscalDeviceOperator (the printer is driven by the vendor's software). - Rewrite decision 6's outcome clause: a repeat resumes status-aware (issued verbatim / live lease / in-doubt surfaced / terminal rejected re-attemptable) and exactly-once needs the unique index AND an atomic in-flight lease, matching InvoiceService.resumeExisting / claimForIssue. - Drop the five links to ADR-039, which does not exist yet (PR #2055), down to plain #2051 references, and record 039 as reserved in the index so the next contributor does not collide with it. - "The carrier is a code" -> "The tax rate is a code". Important: - Migration path now names the CoreCapabilityValues + FE-mirror additions #1908 needs, and Consequences says "no core *domain* PR". - ADR-005's delete-on-publish-failure step is explicitly NOT adopted. - Mandatory idempotencyKey means a plain, not partial, unique index. - Add a FiscalRegistrationLocator sub-capability and state that in-doubt is non-terminal, per ADR-035; declare an own failure-mode union instead of value-importing invoicing's. - Scope the amendment's "implies no migration" to the invoicing side; put the conflict flag on the Order projection AutoIssueTriggerService already receives, preserving its F3 one-way edge. - Legal accuracy: art. 111 ust. 6a + 6b and "potwierdzenie Prezesa GUM" (not homologation); carry the spec's not-legal-advice caveat into both documents; Czechia 2027 is pending legislation; the tax-code list adds np and drops intra-EU 0% as a separate code; fix three code pointers; the PL corrections deferral rests on paragony not being device-correctable, which makes receipt number + numer unikatowy a #1909 requirement. Also: Annex -> Amendment run-in, section vocabulary aligned with 12-14, the duplicated FiscalisationPort description trimmed to a pointer, the two invoicing policy bullets moved above the adapter roster, manifest posture for FiscalDeviceOperator, and the spelling choice recorded. Part of #2009 Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
Review addressed — all 4 blocking, all 16 important, all 15 suggestionsPushed as Thanks in particular for B2 and I4 — the composition bug in decisions 6+7 was real and would have cost a double registration if #1908 had implemented the ADR literally. BlockingB1 — eparagony.pl is not a certified fiscal provider. Correct, and the spec said so at
B2 — decisions 6 + 7 burned the idempotency key on a terminal B3 — five dead ADR-039 links and the index gap. Confirmed: PR #2055 is still open and 039 is not on
B4 — Important
SuggestionsAll 15 applied.
Not changed, and whyNothing was left unaddressed. Two judgement calls worth naming:
Your Verified correct section is much appreciated — I only re-verified the pointers I was changing (I15's three, the |
piotrswierzy
left a comment
There was a problem hiding this comment.
Tech-lead review — ❌ Request changes (numbering, not the decision)
Substantively this is a strong ADR. Keeping fiscalisation out of InvoicingPort is well argued (four independent reasons, each sufficient), the sub-capability shape matches the ADR-002 pattern precisely, the litmus test deliberately mirrors ADR-026's country-agnostic commitment, and every file:line claim I spot-checked against main is accurate. The blocker is the ADR number and a reservation note that is wrong in both directions.
BLOCKING
1. ADR-040 is claimed twice, off the same base commit. This PR adds 040-fiscalisation-capability.md; #2050 adds 040-order-time-fx-stamping-…md. Both are based on 30cb4799, both open. I verified this independently: main's highest ADR is 038.
The nasty part is that the two filenames differ, so git merges both cleanly with no conflict marker and you end up with two ADR-040s. That's the migration-timestamp-collision failure mode exactly, and nothing in pnpm check:invariants catches it.
Per the coordination table in #2055's body, the allocation the cluster has settled on is 039 → #2014 analytics, 040 → #2050 FX, 041 → #2055 routing, 042 → this PR. Renumbering means: the filename, the # ADR-042: title line, the README index row, the docs/architecture-overview.md § 15 pointer and § Future Capability Ports bullet, and the new **ADR:** line in docs/specs/product-spec-1902-eparagony-e-receipts.md:5.
2. The new README reservation note is wrong on both facts it asserts.
039 is reserved, not free: the sales-document routing ADR is authored in #2051 / PR #2055 and claims that number when it lands. Allocate 041 for the next new ADR.
- 039 is not the routing ADR — it's the order-analytics read model (#1985 / #2014), already referenced by filename on
mainatdocs/plans/implementation-plan-order-cancellation-record-state.md:164and:745, plus four prose references. - #2055 claims 041, not 039 (its own title says so).
As written, the note steers the next author into 041 — straight into #2055. It should reserve 039 (analytics), 040 (FX), 041 (routing) and point at 043 as next-free once this lands at 042. Given this note is the cluster's coordination mechanism, getting it right matters more than the renumber itself.
3. #2009's acceptance criterion ("040 fiscalisation + 039 routing") no longer holds — correct it on the issue in the same pass, or the next reader re-derives the wrong pairing from it.
IMPORTANT
4. Decision 8 and the ADR-026 annex record as settled a rule whose enabling supersession is still a draft that may be refused. The annex is honest that "supersession required, conducted elsewhere" — but that supersession is #2058, a draft, whose own body says "refuting the argument is a legitimate outcome and kills that work." Meanwhile ADR-014 on main explicitly rejects the premise (014-source-authoritative-order-pricing.md:47-48: "Per-line tax-inclusivity / tax rate on OrderItem. Rejected: … tax rate is destination-catalog knowledge that must not leak into the source").
Merging this puts a rule into ADR-026 that a live, unreversed ADR contradicts, and architecture-overview.md § 14 gains a bullet asserting it as current architecture. Either land #2058 first, or soften decision 8 / the annex / the overview bullet to "proposed, pending the ADR-014 reversal in #2054" so the record can't read as decided while its prerequisite is open.
5. Name the neutral shape for the two PL fields decision 9 makes a v1 requirement. Decision 9 says #1909 "must persist and surface both" the receipt number and the numer unikatowy for the PL correction register. Those are the only concrete field-level requirements in the ADR, and they're Polish. The decision-4 litmus test (zero paragon/kasa/printer/eparagony strings in libs/core/src/fiscalisation) doesn't cover them — so as written, #1909 could put numerUnikatowy on the neutral registration record, which is the exact breach this ADR is otherwise careful to prevent. One sentence fixing them as a neutral provider reference plus a regime-extras bag closes it. This is the single place the country-agnostic boundary is actually at risk.
SUGGESTION
-isationin a code identifier. Decision 1 puts'Fiscalisation'intoCoreCapabilityValues, whose eight existing members are all American-spelled. The ADR acknowledges this and justifies it by spec/issue-title consistency — defensible, but it makes this the one British-spelled identifier in the registry, permanently, and it's a wire-visible string inenabledCapabilities. Worth an explicit maintainer yes/no now rather than at #1908 review time.- Length (~2,300 words vs the README's "under 500") — universally broken since ADR-026 and #2055 amends the rule. Noting only.
Positive observations
- Every code claim I checked is CONFIRMED, which is unusual for a design-only ADR this size:
taxRate: ''atorder-to-issue-invoice-command.mapper.ts:228/:249;InvoiceFailureModeValues = ['rejected','in-doubt'];resumeExisting/claimForIssueatinvoice.service.ts:284/:336; the'receipt'documentType value;CoreCapabilityValuesclosed atadapter.types.ts:23-37with the strict@IsInon both DTOs and the FE mirror — which is what makes the Migration path's "without those, the adapter is unreachable" exactly right. - The capability design matches the shipped pattern, including naming the trap that
getCapabilityAdapter(connectionId, 'FiscalDeviceOperator')passes the manifest gate then throws a genericErrorinsidedispatchCapability. - The near-miss alternative is the best part of the document. Recording that
receiptalready exists as an open-worlddocumentTypeand would type-check, then explaining why folding the two is a category error, is precisely the "why didn't we just do X" the ADR practice exists to answer. - The VAT annex doesn't breach country-agnosticism: the PL codes appear as justification for keeping the existing neutral
taxRate: stringa string, not as a proposed core enum. - Supersession protocol followed correctly — ADR-026 is amended under its existing amendments header (matching the ADR-009/026 precedent) rather than superseded, and the ADR-014 reversal is delegated rather than restated so the two can't drift.
Merge readiness: ❌ Blocked on the renumber to 042 and the corrected reservation note (1–3) — both mechanical, and both must land before either this or #2050 merges. Finding 4 needs a maintainer decision on ordering against #2058; finding 5 wants one clarifying sentence. The decision itself I'd approve as-is.
Research: ADR-040 benchmarked against industry practiceI ran a research pass across the field before signing off on the architecture — open-source middleware (fiskaltrust, source-read), commercial vendors (efsta, fiskaly), platform/ERP layers (Odoo source at 18.0, Dynamics 365, SAP DRC, Oracle, Shopify, Lightspeed), and the 2026 regulatory landscape. Summary: the design holds up well. Eight of the nine decisions match what the industry converged on, one exceeds it, and my findings are wording and omissions rather than architecture. The framing I'd add up frontOpenLinker is not building fiskaltrust — it is building the cash register that calls it. fiskaltrust holds its neutral contract at the POS boundary ( OL sits on the caller side of that boundary; eparagony.pl is the middleware. That means the hardest problem in this domain — abstracting over heterogeneous national signing hardware — isn't ours, and several decisions become self-evident rather than needing defence. Worth stating in Context; it would let the ADR assert more and argue less. What the field validates
Where you're ahead of the reference implementationsDecision 6. The mandatory idempotency key + unique index written before the outbound call + atomic in-flight lease is stronger than anything I found:
None of them documents the concurrent-same-key race your lease closes. Worth keeping the reasoning in the ADR verbatim. Three things I'd change1. Decision 2 contradicts decision 4. D4 says the base contract must not assume a fiscal printer exists; D2 describes the return value as "what must be printed". The assumption D4 forbids is reintroduced in the phrasing of the operation it governs. Delivery channel is a variable, not a constant — fiskaltrust's contract enumerates PDF, ESC/POS, QR, SMS and e-mail as interchangeable outputs of one operation, and Czechia 2027, Hungary's eNyugta and Germany's proposed 2028 2. No degraded mode, and it's universal in the field. This is distinct from 3. Journal export is the most likely thing to force a port change. The deferral reasoning is right for PL (the online kasa transmits to CRK itself). But a standardised audit export is otherwise universal — DSFinV-K, SAF-T, NF525, LROE, DEP7 — and it is periodic, not per-transaction, so it won't fit the base port's shape. Worth recording as the known first extension point rather than an open deferral. Fact-check
One forward-planning noteFrom 1 January 2027 Poland's low-value NIP receipt (a faktura uproszczona) is pulled into KSeF — it sits outside only until 31 Dec 2026. So in the one market this work targets, roughly five months out, the same order may need both a fiscal registration and a KSeF clearance. That isn't an argument for merging the contexts — it's an argument for exactly what you have: separate ports, with one adapter free to implement both. Worth a line in Consequences, since it's the first concrete case where the two regimes touch the same document. None of this changes the architecture, and the numbering/reservation items from my review above are still the only things blocking. I have a fuller write-up with sources if useful. |
…scalisation ADR Addresses the tech-lead review + industry-research pass on PR #2056. Numbering is handled in a separate commit. Contract corrections: - Decision 2 no longer defines the base operation as returning "what must be printed", which is the assumption decision 4 forbids 18 lines later. The result now carries a possibly-empty list of customer artefacts, each with an adapter-declared medium and disposition hint; an empty list is a successful registration, not a failure. - Decision 9 names the neutral shape for the two PL fields it makes a v1 requirement, so #1909 cannot put `numerUnikatowy` on the neutral record: a small neutral identity set plus one adapter-owned extras bag core never indexes. Decision 4's litmus test is extended to cover field names and core reads, not just prose. New decisions: - Decision 10 records why no degraded/offline mode ships, contrasting it with `in-doubt` - OL is never in the buyer-blocking path and can never mint the legally-recognised substitute, since it is categorically not the trust anchor. - Decision 11 promotes journal/audit export from an open deferral to the named first extension point, with the reasons a periodic export cannot sit on a per-transaction base port. Tax-rate rule hedged: - Decision 8 splits its settled negative half (fiscalisation never recomputes a rate) from the positive ProductMaster rule, which is marked proposed pending the ADR-014 reversal in #2058 - a draft that may be refused. The ADR-026 annex and the architecture-overview bullets carry the same caveat, so the record cannot read as decided while a live ADR contradicts it. Framing and facts: - Context states the boundary explicitly (OL is the cash register calling the middleware, not the middleware), which lets decisions 3 and 5 and two Alternatives bullets shorten. - Italy corrected: certified software was added alongside hardware RT, with no sunset in any instrument - which strengthens the anchor argument rather than weakening it. - Czechia date-stamped, with the Ministry of Finance statement that it mandates neither printing nor new cash equipment. - Consequences notes that from 1 Jan 2027 a PL low-value NIP receipt enters KSeF, so one order can need both a fiscal registration and a clearance. Naming: code identifiers, the capability value and the context path use the `-ization` spelling, matching the repository convention and the two implementation plans. The value is wire-visible and strict-`@IsIn`-gated, so choosing now is free and revisiting later is a breaking change; prose, spec and issue titles keep `-isation`. Refs #2009 Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
… reservation note Three open PRs were authored off the same base commit and 040 was claimed twice: this PR and #2050 (order-time FX stamping) both added an `040-*.md`. Because the filenames differ, git merges both cleanly with no conflict marker and `pnpm check:invariants` does not catch it, so the collision only surfaces as two ADR-040s on `main`. Verified independently against `origin/main` and the open PRs: the highest ADR on `main` is 038, and 039 is the order analytics read model (#2014, already referenced by filename from `docs/plans/implementation-plan-order-cancellation-record-state.md`), 040 is FX (#2050), 041 is sales-document routing (#2055). This PR therefore takes 042 - filename, title line, README index row, both `docs/architecture-overview.md` pointers, the ADR-026 annex cross-link and the spec's `**ADR:**` line. The README reservation note was wrong on both facts it asserted: it named 039 as the routing ADR (it is analytics) and told the next author to allocate 041 (which #2055 already claims), steering them straight into a second collision. It now lists all three reserved numbers with their claiming PRs, points at 043 as next-free, and names the failure mode so the next author checks the open-PR list rather than the file listing. Refs #2009 Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
Two conflicts, both from ADRs that landed on `main` while this branch was in review: - `docs/architecture/adrs/README.md` - ADR-041 (sales-document routing, #2055) took its index row and rewrote the reservation note. Both rows are kept and the two notes are reconciled: 041 is no longer reserved (it merged), 039 (#2014 analytics) and 040 (#2050 FX) still are, 042 is this PR, and 043 is next-free. - `docs/architecture-overview.md` - `main` added `### 15. Analytics Trust` while this branch added `### 15. Fiscalisation (planned)`. Analytics Trust keeps 15 (it is merged and describes shipped code); fiscalisation moves to **16**, with its two inbound pointers (§ 14 Invoicing "see § 15 below", the `FiscalizationPort` bullet under Future Capability Ports) renumbered. The invoicing section keeps all three bullets - `main`'s routing-policy bullet plus this branch's "not fiscalisation" and tax-rate ones. Now that the routing ADR has merged, the placeholder `#2051` text references become real `ADR-041` links, as the PR description said they would. Refs #2009 Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
Review addressed - all findings from both passesThanks for both passes; the research one in particular changed the document more than the review did. Everything is in three commits, all pushed after your comments:
Tech-lead review1. ADR-040 claimed twice - renumbered to 042. Verified the same way you did: highest on 2. Reservation note - rewritten, and you were right on both facts. It named 039 as routing (it is analytics) and pointed the next author at 041 (already #2055's), i.e. straight into a second collision. It now lists each reserved number with its claiming PR, points at 043 as next-free, and names the failure mode explicitly - two PRs claiming one number merge cleanly when their filenames differ, so the check is the open-PR list, not the file listing. Post-merge it reads: 039 and 040 still reserved, 041 landed, 042 is this PR, 043 next. 3. #2009's acceptance criterion - corrected on the issue, with the 13 Aug "040 fiscalisation + 039 routing" pairing explicitly marked dead rather than silently overwritten, so the next reader cannot re-derive it. 4. Decision 8 + the ADR-026 annex - hedged, not landed. Split into its two halves. The negative half (fiscalisation never recomputes a rate) is settled regardless of #2058's outcome and stays unqualified. The positive ProductMaster rule is now marked proposed, pending the ADR-014 reversal in #2058, in all three places: decision 8, the ADR-026 annex, and the 5. The two PL fields - neutral shape now named. Decision 9 fixes them as a small neutral identity set (provider-assigned reference, the document's own reference, a flat signing identity, a timestamp) plus one adapter-owned jsonb extras bag that core never indexes; a key appearing in a second adapter gets promoted to a neutral field. Decision 4's litmus test was extended to cover field names and core reads, not just prose, which is what closes the Suggestion: Research passFraming. Context now states the boundary outright - OL is on the caller side of the POS seam; it is not building the middleware, it is calling one. Decisions 3 and 5 and two Alternatives bullets got shorter as a result, exactly as you predicted. 1. Decision 2 vs decision 4 - real contradiction, fixed. The base operation no longer returns "what must be printed". The result carries a possibly-empty list of customer artefacts, each pairing content with an adapter-declared medium (document / markup / code / link / text) and a disposition hint (print / display / send / retain). An empty list is a successful registration, not a failure - which is what a pure reporting regime returns. 2. Degraded mode - now recorded as a decision, not an omission. New decision 10 contrasts it with 3. Journal export - promoted from open deferral to named first extension point. New decision 11: Fact-check. Italy corrected to "certified software added alongside hardware RT, no sunset in any instrument" - which strengthens the trust-anchor argument rather than weakening it. Czechia date-stamped, with the MF statement that it mandates neither printing nor new cash equipment. 2027 note added to Consequences: from 1 Jan 2027 the PL low-value NIP receipt enters KSeF, so the same order can need both a fiscal registration and a clearance - the first concrete case where the two regimes touch one document, and an argument for exactly this shape rather than against it. Conflicts resolved (
|
Rewrites the proposed amendment after review of PR #2058. - Argument 4 corrected: PrestashopTaxRateResolver resolves per (product, delivery country) and deliberately DE-prefers state rows; it is injected only into the OrderProcessorManager adapter, never the ProductMaster, and ProductMasterPort exposes no rate. So the master read is a new port method with new code + unknown semantics, not a rewiring - and WooCommerce's ProductMaster reads no tax at all. - Defect restated as reachability rather than absence, and the three cheaper routes it admits are now weighed with a rejection rationale each (issuance-time read in the invoicing context, per-connection config, a mappings rule table), plus the withdrawn TaxCalculationPort. - Two arguments added that actually select the order contract: chronology (ADR-014 predates ADR-026, so it could not have reasoned about the invoice-only path) and point-in-time fidelity of a snapshotted rate. - Decision 3 secured by a binding clause (the order-creation path MUST keep resolving destination-side and MUST ignore an OrderItem-carried rate), and the rule scoped to the invoice-only path so the issuance gate cannot fire on an order whose destination already holds the rate. - Inline forward pointer added to Decision 2, the sentence a cold reader quotes; the append-only self-description corrected to match the diff. - The restated rule is deleted in favour of the pointer to the ADR-026 annex (#2009 / PR #2056), removing the zero-code and rate-domain drift between the two documents; merge order phrased conditionally. - Precedent survey corrected (five amendment sections across four ADRs; ADR-037 carries an in-place `Corrected by`, ADR-031 an inline `Correction`), so the standalone-superseding-ADR hedge is dropped. - Limitations recorded: EN 16931's category/rate/exemption-reason triple, the Art 14a deemed-supplier case where the channel is authoritative, and the open question of which vocabulary a core taxRate carries. - Downstream site list completed (7 code sites + the implementation plan) and a Consequences addendum added for the historical-backlog gate. - OrderTotals.tax aside trimmed to a pointer at the recorded deferral. Part of #2054 (step 1 of 7). Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
…use style Follow-up to the review on PR #2056, suggestion 11. The ADR previously kept `-isation` in prose while using `-ization` for code identifiers, which left the two halves of one vocabulary disagreeing. Settle on `-ization` everywhere: - rename 042-fiscalisation-capability.md -> 042-fiscalization-capability.md and update every inbound link (ADR index, architecture-overview, ADR-026, the #1902 spec header) - flip prose in ADR-042, ADR-026's tax-rate amendment, architecture-overview sections 14 and 16, and the #1902 spec - flip the four prose mentions in ADR-041, so the sibling routing ADR names the capability the way the capability is named. No decision text changed Matches the repository house style by a wide margin (normalize 464:40, authorize 244:3) and removes what would have been the tree's only -isation identifier. The GitHub issue titles (#1902, #1908, #1910, #1911) and this branch name keep the old spelling - renaming them would break inbound links for no gain, and the ADR now records that explicitly. The capability value is the reason to settle this before code: it is wire-visible in connections.enabledCapabilities behind a strict @isin DTO, so changing it once a connection exists is a breaking change. Part of #2009 Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
|
Both remaining items done, pushed as Suggestion 11 (spelling) — flipped to B3 (stacking on #2055) — moot: One thing that changed underneath this PR and is worth a second look: ADR-026's supersession note for ADR-014 now points at #2058 (opened separately, still open) rather than the general "#2054" it said before — that's a PR description updated to match current state (numbering + spelling) rather than describing what was true two revisions ago. |
piotrswierzy
left a comment
There was a problem hiding this comment.
Tech-lead re-review — ✅ Approve
Three commits since the last pass. All five prior findings and all four research-comment items are addressed — most properly rather than cosmetically — and the two new decisions that came out of the research pass (10 degraded mode, 11 journal export) are the strongest additions in the diff.
Prior findings
✅ 1 — ADR number. 042 confirmed free. main tops out at 041-sales-document-routing-policy.md; open-PR claims re-verified branch by branch: #2014 → 039, #2050 → 040, #2058 → amends 014 only, #2066 → 043/044/045. Nothing else claims 042.
✅ 2 — reservation note. Corrected in both directions, and the added failure-mode sentence — "two PRs claiming one number merge cleanly when their filenames differ, and pnpm check:invariants does not catch it" — is the right lesson to leave behind. One residual, below.
✅ 3 — issue #2009. Fixed well: scope item 1 now reads "number settled 14 Aug 2026: 042. Every earlier guess in this issue is dead, including the 040 recorded on 13 Aug", with a criterion that explicitly kills the old pairing rather than silently overwriting it. Correcting a stale AC by naming what it replaces is the right habit.
✅ 4 — decision 8 / ADR-026 annex. Honestly stated in all three places now. Decision 8 splits the halves: the negative ("fiscalization never computes a rate of its own") is unqualified; the positive ProductMaster rule is "proposed, not settled … proposed in #2058, which is open and may be refused", with a fallback naming what re-opens if it is. The annex carries "Status: PROPOSED, pending the ADR-014 reversal in #2058" plus "neither document is normative until #2058 lands", and architecture-overview.md adds "This is neither current behaviour nor yet a settled decision".
Since ADR-014 is only Proposed (I had that wrong last round), this is now over-cautious rather than under. Acceptable — and the hedge is #2058's to remove, not yours.
✅ 5 — PL identifiers vs the litmus test. Closed on both sides, which is more than I asked for. Decision 9 fixes a neutral identity set (providerReference, documentReference, flat signingIdentity, registeredAt) plus one jsonb regimeExtras, with PL/IT/DE examples per field and a promotion rule ("a key that shows up in a second adapter is promoted to a neutral field"). Decision 4's litmus widened from prose to field names and core reads: a regime value "may legitimately appear as a key inside adapter-written extras data but never as a column, a TypeScript property, or a key any code under libs/core/src/fiscalization indexes". That is precisely the hole.
✅ spelling. Flipped to -ization in identifier position throughout (FiscalizationPort, 'Fiscalization', libs/core/src/fiscalization/), prose keeps -isation, and the rationale for deciding now (wire-visible, strict-@IsIn-gated, breaking to revisit) is recorded so #1908 can't reopen it.
Research-comment items
✅ (a) D2/D4 contradiction resolved. "What must be printed" is gone. Decision 2 returns "a possibly-empty list of customer artefacts, each pairing content with an adapter-declared medium and disposition hint — a hint, never an instruction, because core neither renders nor delivers", and an empty list is a successful registration. That last clause is the part that actually makes Czechia 2027 and the 2028 Belegbereitstellungspflicht representable.
✅ (b) Degraded mode — argued, not asserted. New decision 10 opens by naming the four reference systems so the absence reads as considered, distinguishes it from in-doubt in one line ("we do not know whether it landed" vs "we know it did not land and we are proceeding anyway under a legally-recognised substitute"), gives three reasons, and closes with a "What would force one" trigger list separating the two lifecycle-changing triggers from the additive #OFFLINE-flag case. The ADR-035 contrast — degraded mode is legitimate there because OL issues — is the sharpest paragraph in the document.
✅ (c) Journal export promoted to decision 11 as the named first extension point, with a five-axis argument for why it can't sit on the base port (period-keyed not sale-keyed; freely repeatable, so the (connectionId, idempotencyKey) index + lease would mint non-registrations; opaque country-schema'd blob; scheduled/operator-initiated; and a base-port method would force no-ops on export-free regimes). The second axis is the one I hadn't thought through.
✅ Fact-check. Italy now reads "added certified software alongside hardware RT … with no sunset for hardware in any instrument and roughly 91% of merchants still hardware-only, so it now runs two classes at once" — which is stronger evidence for decision 3 than "migrating" was. Czechia is date-stamped twice, including an explicit "(Legislative status as at 2026-08-13; a reader after that date should re-check it.)". The 2027 KSeF overlap landed in Consequences, and the POS-boundary framing is now in Context.
New (both minor, non-blocking)
- The reservation note reproduces the failure it warns about. It ends "Allocate 043 for the next new ADR" — but #2066 already claims 043, 044 and 045. Self-limiting, since the same sentence tells the reader to check the open-PR list rather than the file listing, and #2066 postdates this branch. Either drop the "allocate 043" clause or extend the list with "043–045 by #2066". Whoever merges #2066 should reconcile it.
- Decision 4's example contradicts decision 9's assignment. D4 uses
numerUnikatowyas its example of a value that may live as a key inside extras, while D9 assigns the numer unikatowy to the neutralsigningIdentityfield (using numer ewidencyjny as its extras example). Consistent in rule, confusing in illustration — swapping D4's example tonumerEwidencyjnymakes them agree.
Link hygiene re-checked: the five previously-dead ADR-039 links now point at a real ./041-sales-document-routing-policy.md on main, the product-spec path resolves, and the spec gained a reciprocal **ADR:** header. No broken cross-link introduced.
CI
Test, Build, Type Check, Integration Tests, Docker smoke, PHP unit all green. Lint red is inherited — main carries two migrations at 1833000000002, failing check:invariants on the trunk itself. This PR touches five markdown files and nothing under apps/.
Merge readiness: ✅ Approve — all three blockers resolved, both IMPORTANTs addressed at the level asked, and the research pass produced two genuinely new decisions rather than hedging text. Merge order against #2058 is free: the hedge means this can't contradict ADR-014 whichever way #2058 goes. Keep #2009 open — scope item 2 is deliberately excluded and blocked on #1907.
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
Two small corrections to the ADR index note, both consequences of #2056 landing as ADR-042: - 042 is no longer reserved. The note still listed it as claimed-but-pending while the row for it sits in the table directly above, so a reader checking the note against the table saw a contradiction. 040 was in the same state after #2050. Both move to the claimed-and-carried list, leaving 039 (#2014) as the only genuine gap, and the note now states the next free number outright ("Allocate 046") the way it did before. - "fiscalisation" -> "fiscalization". #2056 normalised this vocabulary to the repository's -ization house style, and this line was the sole remaining occurrence of the old spelling anywhere in the tree. Docs only. No production code, no schema, no runtime behaviour change. Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
…2058) * docs(adr): propose reversing ADR-014's per-line tax-rate rejection ADR-014 rejected a per-line tax rate on `OrderItem` on the grounds that the rate is destination-catalog knowledge. That reasoning holds for the path the ADR was written about (creating an order into a destination shop, where the shop is the tax authority) but not for the invoice-only path, which has no destination shop at all: the rate ends up held nowhere, and the inFakt, Subiekt and KSeF adapters each substitute their own guess. ADR-014 set out to prevent fiscally-wrong VAT on invoices and, on that path, is what produces it. Appends a `## Proposed amendment (#2054)` section carrying the argument, plus a forward pointer on the rejected bullet. The append-only rule is respected: no existing text is rewritten and the Status line is unchanged, since the taxonomy has no per-alternative value and only one entry under "Alternatives considered" is in question. The section is explicitly framed as a proposal needing a team accept, not as settled record. Scope is exactly the tax-rate half of that one alternative. Decision 1 (source-authoritative price) is the foundation the new rule builds on, the tax-inclusivity half of the rejection stands, and Decision 3 (PrestaShop stays the tax and rounding authority on the order-creation path) is untouched. Docs only. No production code, no schema, no test changes. Every cited file:line was verified against 30cb479; two claims from the issue were corrected in the process (`OrderTotals.tax` is required, not optional, and the core mapper's empty-rate comments do not cite ADR-026). The normative rate rule stays with the ADR-026 annex in #2009 / PR #2056, which this does not touch, so the two documents cannot drift. Part of #2054 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HpwFwSVZYF7nopZ5S3Peet Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com> * docs(adr): address review on the ADR-014 per-line tax-rate amendment Rewrites the proposed amendment after review of PR #2058. - Argument 4 corrected: PrestashopTaxRateResolver resolves per (product, delivery country) and deliberately DE-prefers state rows; it is injected only into the OrderProcessorManager adapter, never the ProductMaster, and ProductMasterPort exposes no rate. So the master read is a new port method with new code + unknown semantics, not a rewiring - and WooCommerce's ProductMaster reads no tax at all. - Defect restated as reachability rather than absence, and the three cheaper routes it admits are now weighed with a rejection rationale each (issuance-time read in the invoicing context, per-connection config, a mappings rule table), plus the withdrawn TaxCalculationPort. - Two arguments added that actually select the order contract: chronology (ADR-014 predates ADR-026, so it could not have reasoned about the invoice-only path) and point-in-time fidelity of a snapshotted rate. - Decision 3 secured by a binding clause (the order-creation path MUST keep resolving destination-side and MUST ignore an OrderItem-carried rate), and the rule scoped to the invoice-only path so the issuance gate cannot fire on an order whose destination already holds the rate. - Inline forward pointer added to Decision 2, the sentence a cold reader quotes; the append-only self-description corrected to match the diff. - The restated rule is deleted in favour of the pointer to the ADR-026 annex (#2009 / PR #2056), removing the zero-code and rate-domain drift between the two documents; merge order phrased conditionally. - Precedent survey corrected (five amendment sections across four ADRs; ADR-037 carries an in-place `Corrected by`, ADR-031 an inline `Correction`), so the standalone-superseding-ADR hedge is dropped. - Limitations recorded: EN 16931's category/rate/exemption-reason triple, the Art 14a deemed-supplier case where the channel is authoritative, and the open question of which vocabulary a core taxRate carries. - Downstream site list completed (7 code sites + the implementation plan) and a Consequences addendum added for the historical-backlog gate. - OrderTotals.tax aside trimmed to a pointer at the recorded deferral. Part of #2054 (step 1 of 7). Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com> * docs(adr): bind the neutral-vocabulary constraint and cite #2057 Addresses the tech-lead review on PR #2058. - State the vocabulary constraint for a core OrderItem tax rate instead of flagging it open: a neutral string code that maps losslessly onto UNCL 5305 inside the adapter, with FA(3) keys explicitly inadmissible on the orders contract. Accepting the reversal without it would license importing a jurisdiction's model one layer further out than ADR-026 permits. - Cite #2057 as a prerequisite of any master rate read (unknown vs resolved-zero) in the evidence bullet and in References. - Record the settled answer on where the section lives: it stays here, and the accepting PR must both drop the proposal preamble and resolve ADR-014's own Proposed-while-shipped status. - Correct the amendment-precedent count to six across four ADRs (ADR-009 x3). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gxkqwi2VMY3G16MuCpHxDz Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com> * docs(adr): #2057 merged, so the prerequisite it named is satisfied #2057 merged 2026-08-14 and reached this branch with the main merge in a474145, so three present-tense claims about PrestashopTaxRateResolver went stale between writing them and now. The resolver no longer returns a number fraction conflating untaxed with unresolvable - it reports { kind: 'resolved' | 'unknown', reason }. The amendment now says the prerequisite is met rather than pending, and anchors the old behaviour to dec889a where it was true. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gxkqwi2VMY3G16MuCpHxDz Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com> --------- Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Documentation only. No production code, no schema, no runtime behaviour change.
What this adds
1.
docs/architecture/adrs/042-fiscalization-capability.md(new)Records the pre-code decisions #1908 depends on, taken from product spec #1902 §4a/§5 rather than re-derived:
InvoicingPortdocument type: different issuer, device dependency, legal basis, retry semantics. The near-miss is named explicitly, because ADR-026 already carries areceiptdocument type for a provider-issued document, which is not a fiscal registration.Sign, efsta/register). efsta's second mandatory endpoint/cfgis connection setup, not a per-sale operation, so it is named as such rather than silently dropped.PL = devicenever becomes a type.in-doubtis surfaced, and only a terminalrejectedre-crosses the boundary - mirroringInvoiceService.resumeExisting/claimForIssue(invoicing: close R2/R3 exactly-once gaps in InvoiceService (retryable discriminator + pending lease) #1200). ADR-005 is cited only for the durable-dedup precedent; its delete-on-failure step is explicitly not adopted, because the row is thein-doubtevidence.FiscalRegistrationLocatorsub-capability to confirm it - both artefacts taken from ADR-035, which solved the same problem for clearance. The failure-mode union is declared in the fiscalization context, mirroring invoicing's shape by design rather than value-importing it.-izationeverywhere - code, capability value, context path, and prose - matching the repository's house style by a wide margin. The GitHub issue titles keep-isation; renaming them would break inbound links for no gain.The legal paragraph cites art. 111 ust. 6a (what a
kasamust do) plus ust. 6b (who must obtain the potwierdzenie Prezesa GUM), applied odpowiednio by art. 111b ust. 2, and carries the spec's not-legal-advice caveat. Both new documents also note that most PL e-commerce is exempt and has no receipt to issue at all.2. VAT-rate amendment on
docs/architecture/adrs/026-country-agnostic-invoicing-domain.mdRecords the 13 Aug 2026 decision, appended as an amendment rather than an edit to the accepted body: the rate arrives from the ProductMaster, the withdrawn
TaxCalculationPortis recorded as the rejected alternative, the rate is a code and not a number (PL invoice markers23/8/5/0/zw/np/oo, and paragon rates carried as letters A-G), a missing rate blocks issuance, a channel-reported rate is a cross-check that can raise a tax-rate conflict blocking both invoice issue and fiscalization, and the order snapshot persists net and gross per line. The no-migration claim is scoped to the invoicing side; the product-side field, the conflict state and the per-line columns are named as #2054's. The conflict flag rides theOrderprojectionAutoIssueTriggerServicealready receives, preserving its documented F3 one-way edge.3. Index and overview
ADR index row, plus a new
16. Fiscalization (planned)section indocs/architecture-overview.mdwhere the capability is described once (with the standard italic ADR pointer), a one-lineFiscalizationPortpointer under Future Capability Ports, and two policy bullets on the Invoicing section - placed above the adapter roster, as in the other contexts.Numbering
038 was taken on
mainin the meantime, so this ADR is not 038 as the issue's acceptance criterion once said. It landed as 040 at review time, then main moved again: #2055 (the sibling sales-document routing ADR) merged as ADR-041, and two more numbers (039, 040) were claimed by unrelated in-flight PRs before this branch could rebase past them. This branch is now merged forward past all of that and the fiscalization ADR is ADR-042, cross-linked to the real, now-mergedADR-041-sales-document-routing-policy.md. No dead links remain anywhere in this diff.Deliberately not in this PR
docs/architecture/adrs/014-source-authoritative-order-pricing.mdis untouched by this PR. ADR-014's rejected per-line-tax-rate alternative does have to be superseded, but the argument and the supersession note belong to [TASK] CORE — per-line tax rate on the order contract + master reads (PrestaShop, WooCommerce) #2054 - now tracked as its own open PR (docs(adr): propose reversing ADR-014's per-line tax-rate rejection #2058) onmain, with ADR-026's amendment stating explicitly that neither document is normative until docs(adr): propose reversing ADR-014's per-line tax-rate rejection #2058 lands.Notes for the reviewer
-izationin prose as well as code (previously code alone used it, leaving one vocabulary split in two spellings).042-fiscalisation-capability.mdwas renamed to042-fiscalization-capability.md; every inbound link (index, overview, ADR-026, the [PRODUCT-DESIGN] Polish e-receipt (e-paragon) support — eparagony.pl connector #1902 spec header) was updated. Deliberately not renamed: the GitHub issue titles ([PRODUCT-DESIGN] Polish e-receipt (e-paragon) support — eparagony.pl connector #1902/[IMPL] Fiscalisation capability + eparagony.pl adapter — register a fiscal transaction #1908/[IMPL] Device/peripheral sub-capability for fiscal-printer regimes #1910/[IMPL] Fiscalisation provider connection setup + preconditions UI #1911) and this branch/PR name, since renaming those breaks inbound links for no gain - the ADR records this explicitly so it isn't re-litigated later.PricingAuthorityPortdoes.## Decision, and every addition traces to a review finding (the status-aware resume, the locator sub-capability, the capability-value migration step, the legal corrections).Part of #2009. Deliberately not
Closes- scope item 2 of that issue (the #1908 implementation plan) is excluded here, because #1907 has not settled who builds the connector, so merging this must not auto-close the issue while that acceptance criterion is unmet.🤖 Generated with Claude Code