Skip to content

feat(ksef): emit FA(3) Platnosc from connection payment config - #1317

Merged
norbert-kulus-blockydevs merged 27 commits into
mainfrom
1311-ksef-platnosc-plan
Jul 3, 2026
Merged

feat(ksef): emit FA(3) Platnosc from connection payment config#1317
norbert-kulus-blockydevs merged 27 commits into
mainfrom
1311-ksef-platnosc-plan

Conversation

@norbert-kulus-blockydevs

@norbert-kulus-blockydevs norbert-kulus-blockydevs commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator

Summary

What's included

  • Domain types: KsefPaymentConfig / KsefBankAccountConfig / KsefFormaPlatnosciValues on KsefConnectionConfig; Fa3PaymentInput / Fa3BankAccount / Fa3FormaPlatnosciValues for the builder.
  • Builder: platnoscNode() in fa3-xml.builder.ts, wired into faNode() right after FaWiersz; omits Platnosc entirely when nothing is configured (existing connections keep byte-identical output).
  • Wiring: KsefAdapterFactory.resolvePayment (mirrors resolveSeller/resolveDefaultTaxRate) threaded through the adapter constructor and Fa3MappingContext.
  • Validation: KsefConnectionConfigShapeValidatorAdapter rejects an unknown formaPlatnosci, an empty bankAccount.nrRb, a negative/non-integer paymentTermDays, or an incomplete skonto (added after the smoke-test bug below) at connection-save time.
  • Frontend: ksef-payment-config.ts assembly module (mirrors ksef-seller-config.ts) wired into edit-connection.schema.ts + EditConnectionForm.tsx; new fields in ksef-structured-section.tsx (payment method select + bank account/SWIFT/term/skonto inputs, each crediting its FA(3) target element in the description copy).
  • Docs: FA3_IMPLEMENTATION_NOTES.md updated with the Platnosc mapping table and emit order.
  • Design mockup: docs/plans/mockups/ksef-payment-platnosc.html (added in the plan commit; renamed from infakt-ksef-bank-account-payment-terms.html per review - the mockup is the KSeF payment section, the old prefix only credited the inFakt precedent).

Bug found via the live smoke test (fixed in this PR)

ksef-payment-config.ts's applyKsefPaymentToConfig originally deleted the whole bankAccount/skonto sub-object whenever a sibling field wasn't present in the same merge call. Since each field syncs to configText independently per keystroke, this silently discarded whatever the operator typed first — skonto could never actually be saved. Fixed: a sub-object is now dropped only when it's completely empty, never based on a missing sibling; the "required together" rules moved to the backend shape validator (save time) and the factory's resolvePayment (issuance time). Full writeup + before/after screenshots in the verification artifact.

Test plan

  • pnpm --filter @openlinker/integrations-ksef test - 305+ pass, including full XSD structural validation of the new Platnosc block for both configured and unconfigured connections, in the correct child order.
  • pnpm --filter @openlinker/web vitest - full suite pass, including new ksef-payment-config/ksef-structured-section coverage.
  • pnpm check:invariants - clean (cross-context imports, service interfaces, etc.).
  • pnpm --filter @openlinker/integrations-ksef lint / pnpm --filter @openlinker/web lint - no new errors or warnings in touched files.
  • Live smoke test (Phase 6) against a running dev-stack KSeF connection with real Playwright screenshots — verification artifact. Live KSeF sandbox submission was attempted with a real sandbox token but blocked at the auth-handshake layer (unrelated to this PR — see the artifact's "Live sandbox attempt" section); the identical builder/mapper code path is covered by XSD-validated unit tests instead.

Closes #1311
Closes #1330 (assembly-relocation refactor merged into this branch via PR #1334; it targets the default branch through this PR)

Also included (bundled changes surfaced by tech review)

Two live-E2E bug fixes and one drive-by CI fix ride along on this branch; they are documented here so the trade-offs are auditable from the PR, not only from the diff:

  • AES session-IV reuse (ksef-session-crypto.service.ts, ksef-crypto.types.ts): encryptDocument now reuses the session-declared IV instead of generating a fresh per-document IV. The KSeF v2 wire shape has no per-document IV field and the CIRFMF reference client confirms session-IV reuse is the protocol's intended model - a fresh IV made every submission fail with status 430. Trade-off: reusing one IV across documents within a session yields deterministic ciphertext for identical plaintexts in that session (a known CBC property). Accepted because the protocol mandates it, the session key is short-lived, and invoice payloads are practically never byte-identical (timestamps, invoice numbers).
  • Required JST/GV elements in Podmiot2 (fa3-xml.builder.ts buyerNode): the FA(3) XSD declares these without minOccurs="0", so omitting them failed live clearance with status 450. Emitted with the 2 ("no") values for regular buyers.
  • /v1/listings int-spec fix (fix(api): version listings invalid-path-id int-spec paths under /v1 #1328, merged into this branch): main's Integration Tests job was red due to a semantic merge conflict between feat(api): version the HTTP API under /v1 + runtime version surface (#1133) #1316 (global /v1 URI versioning) and fix(listings): reject non-UUID path ids with 400 instead of 500 #1313 (a new int-spec calling unprefixed /listings/...); the spec's paths are now versioned. Unrelated to Platnosc but required for this PR's CI to be green.

Adds the full implementation plan for emitting payment method, bank
account, payment term, and skonto in KSeF FA(3) invoices as a
per-connection config value (no live bank-accounts API on the KSeF
side, unlike inFakt's #1303/#1308). Includes the schema-audited XSD
child order for Platnosc (TerminPlatnosci -> FormaPlatnosci ->
RachunekBankowy -> Skonto) and the design mockup referenced by the
issue.

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
Adds a per-connection, manually-entered payment configuration (default
payment method, bank account, payment term, early-payment discount)
and emits it into the FA(3) Platnosc element whenever configured.
Unlike inFakt (#1303/#1308), KSeF has no live bank-accounts API, so
this is a plain config value the operator types in once, mapped
straight through the existing seller/defaultTaxRate resolution chain
(factory -> adapter -> mapper -> pure builder).

- KsefPaymentConfig / KsefBankAccountConfig / KsefFormaPlatnosciValues
  on KsefConnectionConfig (domain types)
- Fa3PaymentInput / Fa3BankAccount / Fa3FormaPlatnosciValues (FA(3)
  builder-internal types)
- platnoscNode() in fa3-xml.builder.ts, emitted as a sibling of
  FaWiersz in the XSD-mandated child order: TerminPlatnosci ->
  FormaPlatnosci -> RachunekBankowy -> Skonto (confirmed against the
  vendored FA(3) v1-0E XSD - not payment-method-first)
- KsefAdapterFactory.resolvePayment + shape-validator checks for
  formaPlatnosci / bankAccount.nrRb / paymentTermDays
- FE: ksef-payment-config.ts assembly module (mirrors
  ksef-seller-config.ts) wired into edit-connection.schema.ts, new
  fields in ksef-structured-section.tsx
- FA3_IMPLEMENTATION_NOTES.md updated with the Platnosc mapping table

Backend: 305/305 ksef unit tests pass, including full XSD structural
validation of the new Platnosc block for both configured and
unconfigured connections. Frontend: 1887/1887 web unit tests pass.
check:invariants clean.

Closes #1311

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
@norbert-kulus-blockydevs norbert-kulus-blockydevs changed the title docs(plans): implementation plan for KSeF FA(3) Platnosc feat(ksef): emit FA(3) Platnosc from connection payment config Jul 2, 2026
@norbert-kulus-blockydevs
norbert-kulus-blockydevs marked this pull request as draft July 2, 2026 12:33
Live Playwright smoke test against a real dev stack + KSeF sandbox
connection (Phase 6 of the implementation plan) surfaced a real bug:
applyKsefPaymentToConfig deleted the whole bankAccount/skonto
sub-object whenever a sibling field (nrRb, or the other of
conditions/amount) wasn't present in the SAME merge call. Since each
field syncs to configText independently per keystroke, this silently
discarded whatever the operator typed first - skonto could never
actually be saved, and a bankAccount sub-field typed before nrRb was
lost.

Fix: only drop a sub-object when it is completely empty (no keys at
all), never based on a missing sibling. The "nrRb required if
bankAccount is set" and "skonto needs both conditions+amount" rules
now live where they belong - the backend shape validator (save time,
new skonto check added) and the factory's resolvePayment (issuance
time) - so a violation surfaces as a clear error instead of silent
data loss.

Adds the e2e smoke-test script (apps/web/e2e/ksef-payment-config.mjs)
and its screenshots (docs/assets/ksef-1311-smoke/) used to find this
and verify the fix + full field set against the design mockup.

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

@norbert-kulus-blockydevs norbert-kulus-blockydevs left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tech Lead Review — PR #1317 (KSeF FA(3) Platnosc)

Summary

Solid, well-scoped PR. Everything stays inside libs/integrations/ksef and its FE plugin slice (ADR-026 compliant — no CORE changes), the factory→adapter→mapper→builder wiring mirrors the existing resolveSeller/resolveDefaultTaxRate chain exactly, naming/typing conventions (as const unions, *.types.ts, file headers) are followed, and test coverage is thorough (builder XSD-order cases, mapper, adapter, factory, shape validator, FE assembly + component). No architecture or CORE/Integration boundary violations found. Two SUGGESTION-level gaps below, neither blocking.

Issues

[SUGGESTION]libs/integrations/ksef/src/infrastructure/adapters/ksef-connection-config-shape-validator.adapter.ts (payment.bankAccount.nrRb check) / apps/web/src/features/connections/components/edit-connection.schema.ts (paymentBankAccountNrRb schema)

The implementation plan documents NrRB as TNrRB, 10–34 chars per the XSD pattern (§4), but neither the shape validator nor the FE Zod schema enforce a minimum length — only non-emptiness (BE) / a max of 34 (FE). An operator could save an obviously truncated account number (e.g. 3 digits) that passes both client and server validation, only to fail at actual KSeF submission time with an opaque XSD error far removed from the config-save screen where the mistake was made. Since engineering-standards.md's validation guidance is to reject bad input at the boundary rather than let it surface downstream, consider adding a length/pattern check mirroring the XSD (10–34 chars) to the shape validator, with the FE schema following suit (.min(10)).

[SUGGESTION]libs/integrations/ksef/src/application/factories/ksef-adapter.factory.ts:168-193 (resolvePayment)

The method's doc comment claims a defensive posture "mirroring resolveDefaultTaxRate's defensive posture," and it does re-validate bankAccount (drops it if nrRb is falsy) and skonto (drops it unless both sub-fields are present) — but formaPlatnosci and paymentTermDays are passed through unchecked (result.formaPlatnosci = payment.formaPlatnosci; / result.paymentTermDays = payment.paymentTermDays;). In the normal flow the shape validator already rejects an out-of-range formaPlatnosci or a negative/non-integer paymentTermDays at save time, so this is low-impact — but for a connection whose config predates the validator (or was written through another path), an invalid code/negative day-count would be emitted verbatim into FormaPlatnosci/TerminOpis/Ilosc rather than being defensively dropped like the other two fields. Minor inconsistency worth a one-line guard (or a code-comment caveat) if the "defensive" framing is meant to be complete.

Verdict

Approve — both findings are non-blocking robustness suggestions; nothing here violates hexagonal boundaries, naming conventions, or the documented ADR-026 country-agnostic-core rule.

All acceptance criteria satisfied and Phase 6 (live smoke test +
verification artifact) done - link to the published artifact and
note the persistence bug found/fixed during the smoke test.

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
- Shape validator + FE Zod schema now enforce the FA(3) TNrRB length
  bound (10-34 chars) on payment.bankAccount.nrRb instead of only
  non-emptiness, so a truncated account number is rejected at
  connection-save time rather than surfacing as an opaque KSeF XSD
  error at issuance.
- resolvePayment now defensively drops an unknown formaPlatnosci code
  or a negative/non-integer paymentTermDays (mirroring the existing
  bankAccount/skonto guards) for connections whose config predates the
  ksef.publicapi.v2 shape validator.

Addresses both SUGGESTION findings from the tech-lead review on #1317.

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
@norbert-kulus-blockydevs

Copy link
Copy Markdown
Collaborator Author

Addressed both SUGGESTION findings from the tech-lead review in 6d29c91:

  • payment.bankAccount.nrRb now enforces the FA(3) TNrRB length bound (10-34 chars) in both the shape validator and the FE Zod schema, not just non-emptiness.
  • resolvePayment now defensively drops an unknown formaPlatnosci code or a negative/non-integer paymentTermDays, mirroring the existing bankAccount/skonto guards, for connections whose config predates the ksef.publicapi.v2 shape validator.

Added corresponding unit tests in both the KSeF integration package and the web edit-connection schema. pnpm --filter @openlinker/integrations-ksef test (315 passed), pnpm --filter @openlinker/integrations-ksef lint/type-check, and pnpm --filter @openlinker/web lint/type-check all clean.

…NazwaBanku)

Confirmed live against the KSeF sandbox: the FA(3) XSD mandates NrRB
before SWIFT before NazwaBanku inside RachunekBankowy. Emitting
NazwaBanku before SWIFT caused KSeF to reject the invoice (status 450,
"invalid child element SWIFT... expected OpisRachunku").

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
Two E2E-verified prerequisites for live KSeF acceptance, confirmed
against the KSeF test environment while doing live sandbox
verification of #1311 (the Platnosc feature was silently blocked
from ever actually being accepted end-to-end without these):

1. AES session IV: encryptDocument was generating a fresh random IV
   per document, but KSeF's SendInvoiceRequest wire shape has no
   per-document IV field — it decrypts every document in a session
   using the session IV declared once in
   OpenOnlineSessionRequest.encryption.initializationVector. A
   per-document IV caused a SHA-256 hash mismatch (status 430)
   because KSeF's decryption produced garbage. Fixed: encryptDocument
   now reuses context.symmetricKey.iv (the session IV) for every
   document.

2. JST and GV are REQUIRED by the FA(3) XSD on Podmiot2 (no
   minOccurs="0") but were never emitted, causing a semantic
   validation rejection. Fixed: buyerNode now always emits JST=2,
   GV=2 ("nie dotyczy" — not a JST subsidiary unit / not a VAT group
   member).

Cherry-picked from 634c27a5 (previously only on the unmerged branch
1228-ksef-fa3-full-visualization-new, which was never pushed to
origin) — without this fix no live KSeF invoice submission from this
codebase can be genuinely accepted, regardless of Platnosc content.

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

@piotrswierzy piotrswierzy left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed against the vendored schemat_fa3_v1-0e.xsd in the repo. The wiring is clean and idiomatic — mirrors resolveSeller/resolveDefaultTaxRate end-to-end, no CORE changes (ADR-026 clean), as const unions, types in *.types.ts, no any, and the config-merge fix + validator prototype-safety both check out. I verified most XSD claims and they hold: the Platnosc position under Fa (after FaWiersz/Rozliczenie, both optional), the TerminPlatnosci → FormaPlatnosci → RachunekBankowy → Skonto order, the TerminOpis complex {Ilosc, Jednostka, ZdarzeniePoczatkowe} shape, and the TFormaPlatnosci 1–7 values are all correct.

One blocking issue and one process gap.

🔴 BLOCKING — RachunekBankowy emits NazwaBanku before SWIFT (XSD-invalid)

rachunekBankowyNode in fa3-xml.builder.ts builds keys as NrRB → NazwaBanku → SWIFT, but the vendored XSD TRachunekBankowy sequence is:

NrRB, SWIFT        (inner sequence)
RachunekWlasnyBanku?
NazwaBanku?
OpisRachunku?

SWIFT must come before NazwaBanku. When a connection has both bankName and swift set, the emitted document is XSD-invalid and KSeF clearance would reject it. Single-field paths (NrRB only, NrRB+swift, NrRB+bankName) are fine — only the both-set path breaks.

Fix: emit SWIFT before NazwaBanku in rachunekBankowyNode, and update fa3-xml.builder.spec.ts:485 — it currently asserts the wrong order (<NrRB><NazwaBanku><SWIFT>), which is why the bug reads as "passing."

🟠 IMPORTANT — the structural validator doesn't cover Platnosc, so the "validator passes" tests are false comfort

validateFa3Xml only checks root/namespace/Naglowek/Podmiot/Fa-required-children/FaWiersz — it never inspects Platnosc or child ordering. The two should pass the structural FA(3) validator … tests would (and do) pass even with the bug above. Combined with the blocked live sandbox submission, element-order correctness rests entirely on manual XSD reading plus tautological "emits-what-it-emits" regex assertions.

Please add a real order check for the Platnosc block (a targeted structural assertion, or an opt-in xmllint/libxmljs conformance pass in CI) so ordering regressions like the RachunekBankowy one are actually caught rather than co-signed by the test suite. This is the reason the bug slipped through.

🟡 SUGGESTIONS (non-blocking)

  • The new payment constructor arg on KsefInvoicingAdapter is inserted before the pre-existing defaulted now param. Both internal call sites were updated so nothing breaks, but a mid-list positional insert is fragile — appending or an options object would be safer.
  • The "required-together" invariants now live in FE assembly + shape validator + factory resolvePayment. Fine as defense-in-depth (matches the resolveSeller precedent), just noting the triplication.

Everything else — the merge-bug fix (sub-objects dropped only when fully empty, required-together moved to validator + factory), neutrality, and typing — looks good. Fixing the RachunekBankowy order + its test, and closing the validator coverage gap, clears this for merge.

norbert-kulus-blockydevs and others added 3 commits July 2, 2026 19:29
… + ctor options bag

- validateFa3Xml now enforces the XSD-mandated child order of Fa/Platnosc
  (TerminPlatnosci -> FormaPlatnosci -> RachunekBankowy -> Skonto) and of each
  RachunekBankowy (NrRB required, SWIFT before NazwaBanku), so an ordering
  regression like the pre-review RachunekBankowy bug is caught by the
  'passes the structural validator' tests instead of co-signed by them
- KsefInvoicingAdapter trailing optional params (payment, now) move into a
  KsefInvoicingAdapterOptions bag so future additions never shift positional
  call sites
- resolvePayment doc comment now names the intentional three-layer
  (FE assembly / shape validator / factory) defense-in-depth explicitly

The blocking SWIFT/NazwaBanku emit-order finding was already fixed in
c315e4d; this adds the validator coverage that would have caught it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
…c fix

The merge helper intentionally persists an incomplete skonto (conditions
without amount) so independent per-field config sync never drops the
first-typed field; completeness is enforced at save/issuance time (#1311
smoke-test finding). The test still asserted the old drop-on-incomplete
behaviour.

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
…1328)

Semantic merge conflict between #1316 (URI versioning, global /v1 default)
and #1313 (new listings-invalid-path-id.int-spec.ts written pre-versioning):
the spec's unprefixed /listings/... requests stopped matching any route, so
every request returned the router-level 404 ("Cannot GET ...") - the three
expect(400) ParseUUIDPipe assertions failed and the expect(404) ones passed
vacuously. Main has been red on Integration Tests since the two merged.

Prefix the five request paths with /v1, matching every other listings
int-spec. Verified against the Testcontainers harness: 5/5 pass.

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
@norbert-kulus-blockydevs

Copy link
Copy Markdown
Collaborator Author

@piotrswierzy re: your review (#1317 (review)) - all findings addressed:

  • BLOCKING (RachunekBankowy emits NazwaBanku before SWIFT): fixed in c315e4d. rachunekBankowyNode now emits NrRB -> SWIFT -> NazwaBanku per the XSD TRachunekBankowy sequence, and the builder spec asserts the correct order (<NrRB><SWIFT><NazwaBanku>) instead of co-signing the wrong one.
  • IMPORTANT (structural validator does not cover Platnosc): addressed in f11ea72. validateFa3Xml now enforces the XSD-mandated child order of Fa/Platnosc (TerminPlatnosci -> FormaPlatnosci -> RachunekBankowy -> Skonto) and of each RachunekBankowy (NrRB required, SWIFT before NazwaBanku). Negative tests include one that reproduces exactly the blocker shape (should reject a RachunekBankowy that emits NazwaBanku before SWIFT), so an ordering regression now fails the 'passes the structural validator' tests instead of sailing through.
  • SUGGESTION (positional ctor insert): also in f11ea72 - the trailing optional params (payment, now) moved into a KsefInvoicingAdapterOptions bag, so future additions never shift positional call sites.
  • SUGGESTION (required-together triplication): kept as intentional defense-in-depth (matches the resolveSeller precedent); the resolvePayment doc comment now names the three layers (FE assembly / shape validator / factory) explicitly.

Why PR #1328 was merged into this branch instead of main: main's Integration Tests job has been red since #1316 (URI versioning, global /v1 default) and #1313 (new listings-invalid-path-id.int-spec.ts written pre-versioning, calling unprefixed /listings/...) merged next to each other - a semantic merge conflict. Every request in that spec hit the router-level 404 ("Cannot GET"), so the three expect(400) ParseUUIDPipe assertions failed on main and on every PR based on it, including this one. The fix (#1328, prefixing the five spec paths with /v1) could not be merged to main directly - branch protection requires an approving review - so it was rebased onto this branch and squash-merged here (0d49d3f) to unblock this PR's CI. It will reach main together with this PR; the fix is a 5-line test-path change with no runtime surface.

CI is fully green on the current head (including Integration Tests). Ready for re-review.

@norbert-kulus-blockydevs

Copy link
Copy Markdown
Collaborator Author

Tech review (Claude, /tech-review)

Summary

Solid, well-layered implementation of #1311. The Platnosc chain (FE assembly -> shape validator -> factory resolvePayment -> mapper -> pure builder) mirrors the existing resolveSeller/resolveDefaultTaxRate seams exactly, stays entirely inside libs/integrations/ksef + its FE plugin section (no CORE changes, ADR-026 compliant), and the XSD child-order work is verified against the vendored schema with regression tests in the structural validator. The single most important concern is that the PR quietly bundles two significant changes that are not mentioned in the description.

Issues

[IMPORTANT] - libs/integrations/ksef/src/infrastructure/crypto/ksef-session-crypto.service.ts, ksef-crypto.types.ts, fa3-xml.builder.ts (buyerNode JST/GV)

The PR carries two live-E2E bug fixes that the description does not list: (1) the AES session-IV change - encryptDocument now reuses the session-declared IV instead of a fresh per-document IV, reversing an explicitly documented CBC IV-reuse security warning; and (2) the required JST/GV elements added to Podmiot2. Both look correct (the KSeF wire shape has no per-document IV field, and the CIRFMF reference confirms session-IV reuse; JST/GV have no minOccurs="0" in the XSD), but the IV change accepts deterministic ciphertext for identical plaintexts within a session - that trade-off deserves an explicit line in the PR body so it is auditable from the PR, not only from the test diff. Please add a "also included" section covering both, plus the unrelated /v1/listings int-spec drive-by fix (post-#1133 URI versioning).

[IMPORTANT] - apps/web/src/features/connections/components/ksef-payment-config.ts / ksef-structured-section.tsx

The FE placeholder actively suggests the spaced NRB format (61 1090 1014 0000 0000 9999 9999), and the value is persisted and emitted into NrRB verbatim, inner spaces included. The vendored XSD's TNrRB is length-only (10-34, no pattern facet), so it passes structural validation - but a spaced account number on the wire is fragile for downstream consumers and eats into the 34-char budget. Recommend normalizing (strip whitespace) at assembly time in applyKsefPaymentToConfig, following the normalizeNip precedent, and aligning the FE zod length check with the BE validator (FE counts raw length incl. spaces, BE counts trimmed length - after stripping they converge for free).

[SUGGESTION] - libs/integrations/ksef/src/infrastructure/adapters/ksef-connection-config-shape-validator.adapter.ts

A non-object payment (e.g. payment: "foo"), bankAccount, or skonto silently passes validation and is then silently dropped by resolvePayment at issuance. Since this validator is the strict save-time gate, pushing a must be an object issue for wrong-typed sub-objects would be more consistent than falling through.

[SUGGESTION] - apps/web/src/features/connections/components/ksef-setup.schema.ts vs ksef-connection.types.ts vs fa3-schema.types.ts

['1'..'7'] is now declared three times (FE, plugin connection-config layer, FA3 schema layer). FE/BE duplication is unavoidable; the two BE copies are intentionally layered but a one-line cross-reference comment already exists - fine as is, just flagging so a future 8th code gets added in all three places.

[SUGGESTION] - apps/web/e2e/ksef-payment-config.mjs

Hardcoded admin/admin123 dev-stack credentials; acceptable for a local smoke script, but reading them from env (with these as defaults) would match the WEB_BASE/KSEF_CONN_ID pattern already used.

What's good

  • Correct XSD emit order (TerminPlatnosci -> FormaPlatnosci -> RachunekBankowy -> Skonto; SWIFT before NazwaBanku) with negative-order regression tests in fa3-xsd.validator.spec.ts - exactly the kind of check that keeps a future builder refactor honest.
  • The per-keystroke merge-drop bug fix is the right call: persistence never gates on sibling completeness; required-together rules live in the shape validator (save time) and resolvePayment (issuance time), with the three-layer rationale documented at each layer.
  • KsefInvoicingAdapterOptions options-bag refactor removes the positional-optional-arg trap before it bites.
  • Byte-identical output for unconfigured connections is asserted, not just claimed.
  • Naming, as const unions, *.types.ts placement, file headers, and the feature-barrel export all follow the documented standards.

Verdict

🔄 Approve with changes - mergeable after the two IMPORTANT items (document the bundled IV/JST-GV/int-spec changes in the PR body; normalize the NRB input).

- NrRB input is whitespace-stripped at FE assembly time (new normalizeNrRb,
  mirroring the normalizeNip precedent) so a conventionally-spaced NRB paste
  never reaches config.payment.bankAccount.nrRb or the FA(3) wire with inner
  spaces; the FE zod length check now counts the stripped value, converging
  with the BE shape validator's 10-34 bound
- shape validator rejects a wrong-typed payment / bankAccount / skonto with
  an explicit 'must be an object' issue instead of silently falling through
  to issuance-time drop
- the tripled '1'..'7' TFormaPlatnosci list now carries a cross-reference
  comment at all three declaration sites (FE schema, plugin connection-config
  types, FA3 schema types) so a future 8th code is added everywhere
- ksef-payment-config.mjs smoke script reads login credentials from
  WEB_USER / WEB_PASSWORD env vars (dev-stack defaults preserved), matching
  the WEB_BASE / KSEF_CONN_ID pattern

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
@norbert-kulus-blockydevs

Copy link
Copy Markdown
Collaborator Author

Re: the second tech-review round (2 IMPORTANT + 3 SUGGESTION) - all findings addressed in 3a960dc + a PR-body update:

  • IMPORTANT (undocumented bundled changes): the PR body now has an "Also included" section covering all three ride-alongs: the AES session-IV reuse change (with the deterministic-ciphertext trade-off spelled out and why the protocol mandates it), the required JST/GV elements in Podmiot2, and the /v1/listings int-spec drive-by fix (fix(api): version listings invalid-path-id int-spec paths under /v1 #1328) with the reason it had to land on this branch.
  • IMPORTANT (spaced NRB persisted verbatim): fixed in 3a960dc. New normalizeNrRb helper (mirrors the normalizeNip precedent) strips all whitespace at assembly time in applyKsefPaymentToConfig, so the persisted config.payment.bankAccount.nrRb and the FA(3) NrRB wire value never carry inner spaces; the FE zod check now transforms through the same helper and counts the stripped length, converging with the BE validator's 10-34 bound. Tests cover the spaced-paste path on both the schema and the assembly (the old test that co-signed the spaced persistence now asserts the stripped value).
  • SUGGESTION (non-object payment/bankAccount/skonto passes silently): the shape validator now pushes an explicit must be an object issue for a wrong-typed (or null) payment, payment.bankAccount, or payment.skonto instead of falling through to the issuance-time silent drop. Negative tests added for all three paths.
  • SUGGESTION (tripled '1'..'7' list): all three declaration sites (ksef-setup.schema.ts, ksef-connection.types.ts, fa3-schema.types.ts) now carry a symmetrical cross-reference comment naming the other two, so a future 8th code gets added everywhere.
  • SUGGESTION (hardcoded e2e credentials): ksef-payment-config.mjs reads WEB_USER / WEB_PASSWORD env vars with the dev-stack defaults preserved, matching the existing WEB_BASE / KSEF_CONN_ID pattern.

Verified: pnpm --filter @openlinker/integrations-ksef test (324 passed), KSeF lint + type-check clean, web vitest for the touched suites (edit-connection schema 76 passed, ksef plugin 54 passed), web lint (0 errors) and type-check clean.

@norbert-kulus-blockydevs

Copy link
Copy Markdown
Collaborator Author

Tech review summary

Summary

Well-executed, self-contained KSeF feature. The FA(3) Platnosc element is sourced from manually-entered per-connection config and threaded through the established resolveSeller-style chain (factory -> adapter -> mapper -> pure builder) with no CORE changes (ADR-026 respected). Test coverage is strong: XSD child-order regression tests for Platnosc and RachunekBankowy, shape-validator tests, FE sync-routing tests, plus a live Playwright smoke test that caught a real per-keystroke config-sync bug (incomplete sub-objects being silently discarded) - and the fix (persist partial, enforce required-together at save/issuance time) is the right layering.

Issues

[IMPORTANT] ksef-connection-config-shape-validator.adapter.ts - nrRb whitespace asymmetry between FE and BE
The FE (normalizeNrRb) strips all whitespace before the 10-34 length check and before persisting, but the BE validator counts nrRb.trim().length - inner spaces count toward the bound and are not rejected. A config written directly via the API (bypassing the FE) with a conventionally-spaced NRB ("61 1090 1014 ...") passes the validator, and resolvePayment + platnoscNode emit it verbatim, so spaces reach the FA(3) wire and will fail KSeF's TNrRB pattern at clearance. Since the validator's own header calls it "the strict save-time gate", it should either reject inner whitespace or apply the same normalization as the FE.

[IMPORTANT] fa3-xml.builder.ts buyerNode - hardcoded JST: 2 / GV: 2 limitation is undocumented
Correct for the common case and required by the XSD, but a buyer that actually is a JST subsidiary unit or a VAT-group member would get a false declaration on a fiscal document. Please record this in FA3_IMPLEMENTATION_NOTES.md under "Known limitations / deferred work" so it doesn't silently become "the behavior".

[SUGGESTION] Bundled changes - the session-IV fix and JST/GV fix were found by this PR's own live E2E and are well-documented in the body, so bundling is acceptable; the /v1/listings int-spec CI fix (#1328) would have been cleaner as its own fast-merge PR.

[SUGGESTION] KSeF-specific assembly keeps accumulating in the shared features/connections (ksef-payment-config.ts, ksef-nrb.ts, values in ksef-setup.schema.ts), following the ksef-seller-config.ts precedent. Consistent with the existing pattern, so fine here - but worth a follow-up issue to move plugin-specific config assembly behind a plugin slot before a third platform needs the same treatment.

[SUGGESTION] KsefFormaPlatnosciValues is declared three times by design with cross-references in all three places - honest trade-off. A small drift test asserting the three arrays are equal would make the "add the 8th code in all three places" instruction self-enforcing.

Notable positives

  • The session-IV reuse change is correctly justified against the protocol (no per-document IV field on SendInvoiceRequest; CIRFMF reference client confirms session-IV reuse), with the CBC determinism trade-off documented in both the code and the PR body.
  • The hand-rolled Platnosc / RachunekBankowy child-order checks in fa3-xsd.validator.ts guard exactly the regression class (element ordering) that the presence-only checks would miss, including the RachunekBankowyFaktora prefix-collision and the nested-FormaPlatnosci caveat.
  • KsefInvoicingAdapterOptions bag for trailing optional constructor inputs is the right call to keep positional call sites stable.
  • Byte-identical output guarantee for existing connections (omit Platnosc entirely when unconfigured) is explicitly tested.

Verdict

🔄 Approve with changes - fix the nrRb whitespace gap in the shape validator and record the JST/GV limitation; everything else is optional.

- shape validator now rejects an nrRb containing inner whitespace (the FE
  strips via normalizeNrRb, but a direct API write bypassed that and a
  spaced NRB would fail KSeF's TNrRB pattern at clearance); the factory's
  resolvePayment additionally strips whitespace defensively for
  pre-validator config rows, and drops a whitespace-only value instead of
  emitting an empty NrRB
- the hardcoded Podmiot2 JST/GV = 2 limitation is now recorded in
  FA3_IMPLEMENTATION_NOTES.md under Known limitations, with a pointer
  comment at the buyerNode declaration site
- new drift spec makes the tripled TFormaPlatnosci '1'..'7' list
  self-enforcing: the two in-package arrays are compared by import and the
  FE array is extracted from its source file, so a one-sided edit fails
  the suite
- follow-up issue #1330 filed for moving plugin-specific connection-config
  assembly behind a plugin slot

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
@norbert-kulus-blockydevs

Copy link
Copy Markdown
Collaborator Author

Heads-up: stacked PR #1334 (refactor(web): move plugin-specific connection-config assembly behind a plugin slot) has been merged into this branch, so its diff is now part of this PR.

Why it lands here rather than as a separate PR to main: the refactor extracts the KSeF payment/seller connection-config assembly (introduced by this PR) out of the shared connections feature and behind a generic plugin slot. It builds directly on files added here (ksef-payment-config.ts, ksef-seller-config.ts, edit-connection schema wiring), so it could not target main independently without conflicting with this branch.

Verification after the merge (stacked branches do not trigger CI, so this was checked locally on the merged tree): web type-check clean, all 1977 web unit tests green, web lint 0 errors, and the updated check-ksef-forma-platnosci-drift.mjs passes against the moved file path. The branch still merges cleanly into main.

@norbert-kulus-blockydevs

Copy link
Copy Markdown
Collaborator Author

Tech Lead Review — #1317

Summary

High-quality, well-documented PR. The KSeF Platnosc feature (#1311) follows the established resolve → adapter → mapper → builder chain exactly like resolveSeller/resolveDefaultTaxRate, stays fully inside libs/integrations/ksef (ADR-026 compliant, no CORE changes), and ships defense-in-depth validation at three independent layers (FE Zod, BE shape validator, adapter-factory resolvePayment) with matching bounds (0-999 days, TNrRB 10-34 chars) — each layer's rationale is explicit in comments, including why each is still needed. The bundled #1330 connection-config plugin-slot refactor is a genuine architectural improvement: it moves KSeF-specific fields out of the shared edit-connection.schema.ts into the plugin package via a ConnectionConfigContribution seam, with a compiler-enforced link between the Zod fragment and the declaration-merged form fields, and a runtime cross-plugin field-name collision guard. Test coverage is thorough at every layer (XSD child order, drift-guard, factory defensive-dropping, shape-validator rejection paths, FE per-keystroke sync). The main open concern is scope: this single PR bundles three separable changes (the KSeF payment feature, the FE plugin-slot architectural refactor, and an unrelated CI int-spec path fix), which makes it a heavy review unit — acceptable given the PR body's explicit rationale, but worth flagging.

Issues

[SUGGESTION] — PR scope (whole PR)

Three logically independent changes ride on one branch: the #1311 KSeF Platnosc feature, the #1330 FE connection-config plugin-slot refactor (touches every plugin's edit-connection wiring), and the unrelated #1328 /v1/listings int-spec path fix needed only because main's CI was red. The PR body justifies this well (avoiding a second tech-debt PR, unblocking CI), and each piece is independently well-tested, so this isn't blocking — but for a change this size, splitting the #1330 refactor into its own PR would have made the diff reviewable in isolation and given it a rollback boundary independent of the KSeF feature.

[SUGGESTION]libs/integrations/ksef/src/infrastructure/crypto/ksef-session-crypto.service.ts

The AES-256-CBC session-IV reuse is well-justified (protocol-mandated, three independent sources cited) and the trade-off is documented in both the PR body and the type doc comment. Still, this is a real security-relevant property change (deterministic ciphertext for identical plaintexts within a session) — worth a one-line callout in FA3_IMPLEMENTATION_NOTES.md's "Known limitations" section alongside the JST/GV hardcoding, since that's where a future security reviewer would look first, rather than only in the crypto service's own docstring and the PR description.

[SUGGESTION]scripts/check-ksef-forma-platnosci-drift.mjs

The regex-based extraction (export const ${constName} = \[([^\]]*)\] as const;) is brittle to reformatting (e.g. Prettier wrapping the array across multiple lines once an 8th code is added) — at that point the script would fail with the intended "declaration not found" error rather than silently passing, so this degrades safely, but consider parsing via the TS compiler API or a simpler line-based scan if the array ever wraps.

[SUGGESTION]apps/web/src/plugins/ksef/ksef-connection-config.ts and edit-connection.schema.ts

readPatchString/readOptionalPatchString in ksef-connection-config.ts duplicate the same tiny helpers' shape used elsewhere in the file family (e.g. readString in EditConnectionForm.tsx). Not worth blocking on — the duplication is small and each copy is scoped to a different untyped bag shape — but if a fourth plugin contribution needs the same pattern, it's worth promoting to a shared helper exported from shared/plugins.

Nothing else surfaced: the CORE/Integration boundary is respected (no libs/core changes), naming conventions match (ksefConnectionConfig, KsefPaymentConfig, Fa3PaymentInput, applyKsefPaymentToConfig), the frontend dependency direction is correct (plugins/ksef owns its schema and lib, features/connections no longer re-exports KSeF-specific enums), state ownership rules are followed (form state via RHF, no raw fetch), and no secrets or credentials are touched by this change.

Verdict

Approve

- Record the AES-256-CBC session-IV determinism trade-off under Known
  limitations in FA3_IMPLEMENTATION_NOTES.md, next to the JST/GV entry,
  so security reviewers find it where they look first.
- Harden the TFormaPlatnosci drift script's extraction: tolerate an
  optional type annotation, Prettier multi-line wraps, and inline
  comments inside the array body.
- Promote the untyped-bag string readers (readConfigString /
  readOptionalConfigString) to shared/plugins; ksef-connection-config
  and EditConnectionForm now share one definition instead of per-file
  copies.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
@norbert-kulus-blockydevs

Copy link
Copy Markdown
Collaborator Author

Re: the fifth tech-review round (4 SUGGESTION, 0 IMPORTANT) - all findings addressed in 36211ee:

  • SUGGESTION (PR scope / bundled web: move plugin-specific connection-config assembly behind a plugin slot #1330 refactor): acknowledged as a process note - no code change possible at this point without unwinding the stack. The rationale for landing web: move plugin-specific connection-config assembly behind a plugin slot #1330 on this branch (it edits files this PR introduces, so it could not target main without conflicting) stays documented in the PR body and in the heads-up comment above; each ride-along remains independently well-tested, and the squash-merge message will carry the dedicated session-IV bullet for git-log findability.
  • SUGGESTION (session-IV callout in FA3_IMPLEMENTATION_NOTES.md): added a dedicated entry under "Known limitations / deferred work", right next to the JST/GV hardcoding item, covering the protocol-mandated single-IV-per-session model, the deterministic-ciphertext trade-off (identical documents in one session produce identical ciphertext; shared prefixes observable at CBC block granularity), the bounded exposure (TLS-only transport to MF, public FA(3) header prefix), and why it cannot be fixed client-side (the status-430 rejection of per-document IVs). A future security reviewer now finds it where they would look first, not only in the crypto service docstring.
  • SUGGESTION (brittle regex in check-ksef-forma-platnosci-drift.mjs): the extraction now tolerates an optional type annotation, arbitrary whitespace, a Prettier multi-line wrap of the array, and inline // / /* */ comments inside the array body - so an 8th code that makes Prettier wrap the declaration no longer trips the "declaration not found" exit. Verified against all three current declaration sites (script passes) and the full check:invariants chain is green.
  • SUGGESTION (duplicated readPatchString/readString helpers): promoted to shared/plugins as readConfigString / readOptionalConfigString (new config-readers.ts, exported from the barrel). Both plugins/ksef/ksef-connection-config.ts and the host EditConnectionForm.tsx now consume the shared definition; the per-file copies are deleted. The differently-shaped readString in create-offer-request-to-form-values.ts (takes Record | undefined, different bag family) was left as is.

Verified on the new head: web type-check clean, web lint 0 errors, the touched vitest suites green (ksef-connection-config 127 tests across the 4 affected files), and pnpm check:invariants fully green including the hardened drift script.

norbert-kulus-blockydevs and others added 8 commits July 3, 2026 08:25
…ld-cache

Ryuk is disabled on the persistent self-hosted runner, so the manual
orphan-sweep step is the only cleanup for Testcontainers resources.
docker rm -f (no -v) left anonymous volumes behind forever, and the
sweep never touched dangling images or build cache — both accumulate
indefinitely and were the actual cause of "no space left on device"
integration-test failures unrelated to any test's own code.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
The prior sweep (docker rm -fv + filtered volume/image/builder prune)
still failed with the exact same "no space left on device" error, and
logged 0B reclaimed across all three prune calls. Two concrete bugs:

- The volume-prune label filter never matches: anonymous volumes don't
  inherit the labels of the container that created them, so
  `--filter "label=org.testcontainers=true"` matches nothing. Dropped
  the filter — an unfiltered `docker volume prune` is still safe,
  since Docker refuses to remove a volume still attached to any
  container regardless of label.
- Plain `docker image prune -f` only removes dangling (untagged)
  images; 0B reclaimed means this runner has none, so the real disk
  pressure is old-but-tagged image versions across many past
  integration-test pulls. Widened to `-af` (all unused images) — still
  safe under concurrency since Docker never removes an image a live
  container references, and a concurrent job needing a pruned image
  just re-pulls it rather than failing.

Also logs `docker system df -v` before/after cleanup so the next
failure (if any) can be diagnosed from CI output alone — there is no
shell access to this runner.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
docker system df on the failing run revealed this self-hosted runner
is shared with unrelated third-party workloads (linux-runner-1..4,
hedera-payment-db, hawser-dockhand-agent — none of it OpenLinker's).
The runner's disk pressure is caused by that other tenancy's live
containers, not by anything OpenLinker's CI leaves behind, so a wider
prune (image -af, unfiltered volume prune, builder prune) only ever
reclaims OpenLinker's own garbage and can't fix a permanently
oversubscribed shared host — while carrying real risk of touching
image/volume state a co-tenant relies on. Reverting to the original,
narrowly-scoped cleanup: remove only OpenLinker's own orphaned,
Testcontainers-labeled containers whose owning CI run has finished.
Disk capacity itself needs to be resolved with whoever operates this
runner, not from this repo's workflow.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
…#1321)

A full apps/api integration run boots 4 suite-scoped PrestaShop+MySQL
Testcontainer pairs sequentially (maxWorkers: 1), and Ryuk is disabled
on the self-hosted CI runner — so a suite that times out before
reaching its own cleanup() leaks disk for the rest of that run with no
way to tell, from CI output alone, which suite did it (a prior
"no space left on device" mid-run failure had exactly this shape).

Logs `docker system df` before each boot and after each cleanup() in
the shared prestashop-container.helper.ts, so the next such failure
can be attributed to a specific suite without runner shell access.

Considered also converting the two PS suites that don't install the OL
module (prestashop-harness-smoke, prestashop-webhook-provisioning) to
mock the port instead of booting a real container, to cut boot count
from 4 to 2. Both are deliberately real-PS regression guards — the
smoke test exists to prove the Testcontainer harness itself works
end-to-end, and the webhook-provisioning spec guards #541, a bug that
was green at the unit-test/mock layer and only caught against the real
WS API. Mocking either would remove the exact coverage they exist for,
so this is diagnostics-only; reducing real PS boot count needs a
different approach (e.g. sharing one boot between the two suites that
already require installOlModule: true).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
…nt dirs (#1321)

The PS test harness bind-mounts a mkdtemp dir onto /var/www/html and PS
writes ~1GB into it as root; the unprivileged rmSync cleanup leaks it on
the persistent self-hosted host, invisible to docker system df and the
container sweep. Temporary read-only probe to confirm before fixing the
cleanup path.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
…1321)

PS writes ~1GB into the ol-ps-data-* bind-mount as root, and on the
runner-in-docker host the source path resolves on the HOST daemon's
/tmp - the unprivileged rmSync cleanup could never delete it, so every
PS suite leaked ~1GB onto the host root fs until containerd hit
"no space left on device".

Two-part fix:
- removePsDataDir empties the dir through a root alpine container
  (works in both host topologies), then rmSync's the local dir.
- The CI orphan-sweep step reaps host /tmp/ol-ps-data-* dirs older
  than 90 min (same age gate as the container sweep), covering
  SIGKILLed/cancelled runs that never reach in-process cleanup.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
…1321)

Restores ci.yml to main's version. With the host root fs at 100% every
docker-cli invocation in these steps hangs or fails, blocking the job
before tests even start. The in-process removePsDataDir fix in the PS
container helper stays - it adds no CI step.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
…ner boot (#1321)"

This reverts commit eea68ac. Diagnostics no longer needed - the root
cause (leaked root-owned ol-ps-data-* bind-mount dirs on the host) is
identified and fixed by removePsDataDir.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
@norbert-kulus-blockydevs
norbert-kulus-blockydevs merged commit b40902e into main Jul 3, 2026
7 checks passed
norbert-kulus-blockydevs added a commit that referenced this pull request Jul 3, 2026
…rrections (#1297) (#1329)

* docs(invoicing): implementation plan for issuance-time line snapshot (#1297)

Plan for persisting a neutral issuance-time line snapshot on InvoiceRecord
so KSeF (and any complete-resubmit) corrections diff against the lines as
issued, not the order's current state. Branch cut from main (not #1317 -
zero file overlap, snapshot is pure-core in InvoiceService).

Refs #1297

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* feat(invoicing): persist issuance-time line snapshot for safe corrections (#1297)

Persist a neutral issuedLineSnapshot ({ buyer, currency, lines }) on
InvoiceRecord whenever a document is issued (from the issue command) or
corrected (the correction's own post-correction lines), captured in the core
InvoiceService - no adapter change. The correction endpoint now assembles
originalDocument from that snapshot on the document being corrected, so a KOR's
originalLineNumber-indexed deltas diff against the lines AS ISSUED (and, for a
correction-of-correction, against the prior correction's own lines) even if the
order changed since issuance. Records issued before the column existed fall back
to the pre-#1297 order-derived reconstruction.

- New IssuedLineSnapshot type + nullable jsonb column + migration 1818000000003.
- InvoiceService.issueInvoice/issueCorrection populate it via InvoiceOutcomePatch;
  applyCorrectionDeltas computes the correction's after-lines from the original
  snapshot + per-line deltas.
- Controller prefers the persisted snapshot (buildSnapshotFromRecord), skipping
  the order fetch; falls back to order-derived only when absent.
- Not exposed via any response DTO (carries buyer PII), mirroring
  documentContent/sourceDocument.

Tests: service snapshot on issue + 4 correction cases; controller snapshot-
preference + correction-of-correction; repository jsonb round-trip int-spec.

Closes #1297

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* refactor(invoicing): address tech-review suggestions on line snapshot (#1297)

- Reject duplicate originalLineNumber correction lines at the HTTP
  boundary (new UniqueOriginalLineNumbersConstraint + DTO spec) so the
  persisted "after" snapshot can never silently last-write-win against
  what the provider computed; applyCorrectionDeltas doc notes the
  residual duplicate semantics for non-API callers.
- Type IssuedLineSnapshot.buyer as the structural IssuedSnapshotBuyer
  shape instead of the BuyerProfile class - the field round-trips
  through jsonb without the class prototype, so the type now says so.
- Document the transitional taxId: null caveat on
  buildSnapshotFromRecord for snapshots seeded by a correction of a
  pre-#1297 record (one-hop degradation that self-heals).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* refactor(invoicing): non-null-assert guarded fields in correction snapshot

Per PR review: buildSnapshotFromRecord's `?? ''` / ternary `''` fallbacks
for documentNumber/issueDate were dead branches masking the caller's
existing non-null guard (providerInvoiceNumber/issuedAt asserted before
this path runs). A future guard regression would now surface as a type
error / crash instead of silently producing an invalid empty string.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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 4.8 (1M context) <noreply@anthropic.com>
piotrswierzy added a commit that referenced this pull request Jul 3, 2026
… tag recipe (#1339)

Pre-merge fixes for the 0.1.0 release-please baseline, from the PR #1332
review:

- release-please-config.json: bump bootstrap-sha to the current main tip
  (e629837) so the first post-merge run stays dormant instead of opening
  a 0.2.0 Release PR that double-credits work the curated 0.1.0 section
  already describes (#1309/#1317/#1329/#1320/#1331).
- CHANGELOG.md + RELEASING.md § Versioning policy: reword the pre-1.0
  bump semantics to match the shipped config (minor = features +
  breaking, patch = fixes; bump-patch-for-minor-pre-major is false, so
  feat: bumps minor). RELEASING.md now cites the config keys so the two
  can't silently drift apart again. PUBLIC_API.md's "patch = additive"
  convention is intentionally untouched — that is the separate npm
  package axis (Changesets, deferred), not the product line.
- RELEASING.md v0.1.0 recipe: tag the bootstrap-sha commit explicitly
  instead of the main tip at tag time — once a v0.1.0 tag + Release
  exist, release-please parses from that tag's commit, so tagging later
  silently drops commits from the generated 0.2.0 changelog.
- release-please.yml: pin googleapis/release-please-action by commit SHA
  (v4.4.1) — the workflow holds contents+pull-requests write.

Also merges origin/main (e629837) to bring the branch current.

Refs #1332
Closes #1339

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Piotr Swierzy <piotr.swierzy@blockydevs.com>
norbert-kulus-blockydevs added a commit that referenced this pull request Jul 3, 2026
- merge origin/main so the branch carries the shipped FA(3) Platnosc
  feature (#1317, b40902e) that the payment docs describe - the docs
  no longer reference an unmerged feature
- repoint 4 inbound links broken by the docs/integrations -> package
  docs/ move (architecture-overview, getting-started, user-guide 02/04)
- Erli README: capability row now matches ErliOfferManagerAdapter's
  implements list (drop OfferLister/OfferQuantityBatchUpdater, add
  OfferStatusReader/OfferStockRestorer/TaxonomyBorrower)
- align the Part 2a heading dash with sibling headings
- woocommerce-walkthrough.mjs: admin password from OL_ADMIN_PASSWORD
  env instead of a hardcoded shared-instance value

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
norbert-kulus-blockydevs added a commit that referenced this pull request Jul 6, 2026
Repairs the BLOCKING merge-state defect and all IMPORTANT/SUGGESTION
findings from the /pr-review on #1335.

BLOCKING — the branch had merged an older main and, via a bad conflict
resolution, silently reverted three shipped features (#1297 issued-line
snapshot, #1317 KSeF FA(3) Płatność, #1330 connection-config plugin slot,
plus release-please plumbing). Reset the branch to origin/main and
re-applied only the Subiekt-scoped delta on top, so the diff is now the
Subiekt-only change and no merged work is reverted.

IMPORTANT
1. Payment/bank/cash-register config now rides the #1330
   ConnectionConfigContribution plugin slot (new
   plugins/subiekt/subiekt-connection-config.ts) instead of growing the
   host edit-connection.schema.ts.
2. Payment method is a real tri-state: an explicit "Not set (Subiekt
   default)" option, and the summary derives from the actual state (no
   false "Cash" for unset).
3. use-set-default-bank-account-mutation now invalidates the owner-aware
   subiektBankAccounts key too, so isDefault flags don't go stale.
4. The section fires the default-sync via .mutate() (not void mutateAsync)
   to avoid an unhandled promise rejection on failure.
5. The set-default error toast copy is now provider-neutral (was inFakt).
6. Removed the stale "does not type-check (3-arg constructor)" comment in
   subiekt-adapter.factory.ts — the 4-arg constructor ships here.

SUGGESTIONS
- paymentFields() warns when transfer is configured without a bankAccountId.
- setDefaultBankAccount guards the Number(accountId) coercion, throwing the
  config domain error on a non-numeric id.
- Payment labels routed through t(); the cash-register help default is English.
- Bank accounts group by the stable ownerPodmiotId, not the display name.

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
norbert-kulus-blockydevs added a commit that referenced this pull request Jul 6, 2026
Repairs the BLOCKING merge-state defect and all IMPORTANT/SUGGESTION
findings from the /pr-review on #1335.

BLOCKING — the branch had merged an older main and, via a bad conflict
resolution, silently reverted three shipped features (#1297 issued-line
snapshot, #1317 KSeF FA(3) Płatność, #1330 connection-config plugin slot,
plus release-please plumbing). Reset the branch to origin/main and
re-applied only the Subiekt-scoped delta on top, so the diff is now the
Subiekt-only change and no merged work is reverted.

IMPORTANT
1. Payment/bank/cash-register config now rides the #1330
   ConnectionConfigContribution plugin slot (new
   plugins/subiekt/subiekt-connection-config.ts) instead of growing the
   host edit-connection.schema.ts.
2. Payment method is a real tri-state: an explicit "Not set (Subiekt
   default)" option, and the summary derives from the actual state (no
   false "Cash" for unset).
3. use-set-default-bank-account-mutation now invalidates the owner-aware
   subiektBankAccounts key too, so isDefault flags don't go stale.
4. The section fires the default-sync via .mutate() (not void mutateAsync)
   to avoid an unhandled promise rejection on failure.
5. The set-default error toast copy is now provider-neutral (was inFakt).
6. Removed the stale "does not type-check (3-arg constructor)" comment in
   subiekt-adapter.factory.ts — the 4-arg constructor ships here.

SUGGESTIONS
- paymentFields() warns when transfer is configured without a bankAccountId.
- setDefaultBankAccount guards the Number(accountId) coercion, throwing the
  config domain error on a non-numeric id.
- Payment labels routed through t(); the cash-register help default is English.
- Bank accounts group by the stable ownerPodmiotId, not the display name.

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
piotrswierzy pushed a commit that referenced this pull request Jul 6, 2026
… Subiekt nexo (#1284)

* docs(integrations): add README + operator tutorials for all 8 adapter packages

Add per-package README.md to every integration package that was missing one:
ai, allegro, dpd-polska, erli, inpost, ksef, subiekt, woocommerce.
Each README covers adapter key + capabilities, credentials/config shape,
and links to further operator docs.

Add full A-to-Z operator tutorials for KSeF and Subiekt nexo with per-step
screenshot placeholders (ksef/tutorial.md, subiekt/tutorial.md). The Subiekt
tutorial covers bridge setup via PowerShell/WSL (without-exe-packaging branch),
wizard, B2B faktura, B2C paragon, and idempotency. The KSeF tutorial covers
token generation on the MF portal, connection wizard, B2B order, issuance, and
UPO download.

Create libs/integrations/ksef/assets/ and libs/integrations/subiekt/assets/
directories for future screenshot captures.

Update root README Integrations table to link the tutorial.md files for KSeF
and Subiekt nexo.

Closes #1265
Closes #1266

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014NQB4zBWSrneR71TRKkx1t
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(integrations): add real Playwright screenshots + rewrite KSeF and Subiekt tutorials

Replaces all placeholder screenshot references in KSeF and Subiekt nexo
tutorials with actual Playwright captures taken against a live OpenLinker
instance (preview build with all in-flight FE PRs merged).

KSeF assets (14 PNGs):
  01-02: connections list + platform picker
  03-09: wizard fields (name, env, NIP, address, auth-type, secret)
  10-12: connection created, list, detail page
  13-14: invoices list with regulatory badges, invoice detail

Subiekt assets (18 PNGs):
  06-15: connection wizard (empty -> filled -> created -> test -> list -> detail)
  20-26: invoice flow (orders list, order detail, connection picker,
          ready-to-issue, issue clicked, issued state, invoices list)

Tutorial rewrites:
  - ksef/tutorial.md: reordered around actual wizard fields; Part 1 (KSeF portal)
    marked manual; Parts 2-5 use real screenshot refs
  - subiekt/tutorial.md: full rewrite to match connection-picker flow (multiple
    Invoicing connections), real bridge startup instructions, TLS note, idempotency

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014S9kgctitB75BfHEDejJ6B
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(subiekt): add real Subiekt nexo FS document screenshot to tutorial

Part 5 (verify in Subiekt nexo) was the only remaining manual-step
placeholder without a real capture. Replaces it with an actual screenshot
of an issued FS document open in Subiekt nexo desktop.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014S9kgctitB75BfHEDejJ6B
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(ksef): add real KSeF 2.0 test-portal screenshots for token generation

Part 1 (get a KSeF authorisation token) was the last manual-step
placeholder. Captures the full flow on the live ap-test.ksef.mf.gov.pl
portal: test-auth login, NIP + test certificate, dashboard, token list,
generate-token form, and the revealed token (value redacted before commit
since it's a live, usable test-environment credential).

Also corrects the test-portal URL: ksef-test.mf.gov.pl (KSeF 1.0) was
decommissioned 2025-09-01; the current test environment is
ap-test.ksef.mf.gov.pl (KSeF 2.0).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014S9kgctitB75BfHEDejJ6B
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(subiekt): point to the openlinker-subiekt-bridge repo

The bridge is being moved to its own repo under the openlinker-project
org (not yet published). Update README + tutorial repo links and clone
paths accordingly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014S9kgctitB75BfHEDejJ6B
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(integrations): move all integration docs + assets under libs/integrations/<pkg>/docs/

Establishes one consistent convention across all 8 integration packages:
docs (setup guides, runbooks, tutorials) and their image assets live at
libs/integrations/<pkg>/docs/ and libs/integrations/<pkg>/docs/assets/,
co-located with the code they document instead of split across a separate
docs/integrations/<pkg>/ tree.

Moves (via git mv, history preserved):
- docs/integrations/<pkg>/*.md -> libs/integrations/<pkg>/docs/*.md
  (allegro, dpd-polska, erli, inpost, ksef, prestashop, subiekt, woocommerce)
- docs/integrations/woocommerce/screenshots/ -> .../woocommerce/docs/assets/
- docs/assets/erli/ -> libs/integrations/erli/docs/assets/
- docs/assets/subiekt/ -> libs/integrations/subiekt/docs/assets/
  (merged with the tutorial screenshots already at .../subiekt/assets/,
  no filename collisions)
- libs/integrations/{ksef,subiekt}/tutorial.md -> .../docs/tutorial.md
- libs/integrations/{ksef,subiekt}/assets/ -> .../docs/assets/

Rewrites every relative link and image reference in the moved files
(directory depth changed by one level), updates the 8 package READMEs'
documentation sections, the root README's integration table, the
new-integration issue template, ADR-025's doc links, and the e2e
Playwright capture scripts' output paths. Verified all 251 local links
in the changed docs resolve.

Historical docs/plans/*.md archive entries are intentionally left
untouched — they're frozen snapshots per docs/plans/README.md's own
convention, not live documentation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014S9kgctitB75BfHEDejJ6B
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(subiekt): drop old personal repo link, switch bridge instructions to plain PowerShell

- Remove remaining references to the old norbert-kulus-blockydevs/openlinker-subiekt
  repo and its without-exe-packaging branch — the bridge now lives directly on
  openlinker-project/openlinker-subiekt-bridge.
- Rewrite "running the bridge" / smoke-test steps as plain Windows PowerShell
  (Invoke-RestMethod) instead of WSL-flavored bash+curl — the bridge always
  runs on Windows; WSL was an artifact of how screenshots were captured for
  this PR, not an operator requirement.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014S9kgctitB75BfHEDejJ6B
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(integrations): declutter, crop, and fix tutorial screenshots

Re-captures the KSeF and Subiekt tutorial screenshots against the fully
merged dev branch (all 9 in-flight FE PRs) with three fixes:

1. Wizard field-by-field captures consolidated into one "all fields
   filled" screenshot per wizard (KSeF: 6 shots -> 1; Subiekt: 3 shots -> 1).

2. Noisy/irrelevant data cropped instead of shown in full:
   - Connections list: crop to header + Add-connection button, hiding
     unrelated existing connections.
   - Invoices list: filter by the tutorial's own connection + issued
     status before capturing, instead of showing the full unfiltered
     list (which mixed in dozens of unrelated test rows).
   - Order detail / invoice-issued state: crop above the Sync
     status/Activity/Order Snapshot sections, which aren't part of the
     tutorial narrative.
   - Connection detail page: crop above the "Capabilities" panel, which
     has an unrelated pre-existing bug (renders literal `’` and
     "adapter not recognized" for working connections) surfaced by one
     of the merged branches -- out of scope for this docs PR.

3. Fixed two broken captures:
   - ksef/docs/assets/14-ol-invoice-detail-ksef.png was an "Invoice not
     found" error -- GET /invoices/:invoiceId didn't exist on main yet
     (ships in open PR #1231). Temporarily merged that branch locally to
     capture the real working page, then reverted the merge.
   - subiekt order-detail flow (21-25) used a test order with a
     dangling customerId (no matching customer_projections row) that
     rendered "Couldn't load customer details. Retry". Backed a real
     order (ol_order_ksef_e2e_test_001) with a customer_projections row
     matching its embedded billing address, then drove the actual
     Issue-invoice flow through the live Subiekt bridge end-to-end.

Also drops one redundant intermediate screenshot
(24-ol-invoice-issuing-or-issued.png, identical to 25 once the bridge
issue was fixed) and removes now-orphaned per-field screenshot files.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014S9kgctitB75BfHEDejJ6B
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(integrations): redact KSeF portal cert/token data, fix platform-picker screenshots

- p3/p4 (KSeF portal — NIP + test cert / sign test request): the test
  certificate's SHA256 fingerprint and ID were shown in plaintext. Redact
  both (they're live, usable values on the shared public test environment,
  same category as the token value already redacted in p8).
- p6 (KSeF portal token list): was showing the full historical token list
  (9+ unrelated tokens: "kolejny", "henha", "teest", ...) instead of just
  the tutorial's own token. Generate a fresh, clearly-named token
  immediately before capturing so it sorts to the top, crop to header +
  that one row.
- 02-ol-platform-picker.png / 07-ol-platform-picker.png: these were
  byte-identical between the KSeF and Subiekt tutorials, but their alt
  text claimed each showed its own platform's card specifically ("KSeF
  card" / "Subiekt nexo card") — neither image actually highlighted
  anything. Hover the relevant card before capturing so each tutorial's
  screenshot genuinely shows what its alt text says.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014S9kgctitB75BfHEDejJ6B
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(ksef): add missing Part 3/4 screenshots, drop unimplemented KOR section

Part 3 (get a B2B order in), Part 4 (issue the invoice), and the Subiekt
cross-reference in "Next steps" had no corresponding OpenLinker screenshots
- three (correction, orders-list, order-detail) were simply never captured.
Fills the gap with a real order (ol_order_e2e_tutorial_001, B2B, company
buyer) driven through orders-list -> order-detail (not issued) ->
connection picker -> ready-to-issue -> issued+accepted, calling the real
KSeF test API.

Also:
- Corrected an inaccurate claim: the tutorial said OpenLinker "pre-selects
  Invoice (faktura VAT) when a NIP is present" — checked
  order-invoice-panel.tsx and the document-type select always defaults to
  'invoice' with no NIP-aware logic. Removed the false claim.
- Removed the entire "Part 6 — Correction invoices (KOR)" section. The
  Issue-correction dialog is real, working FE (KSeF has an
  invoiceCorrectionFlow slot), but actually submitting fails with "Provider
  does not support correction issuance" — KsefInvoicingAdapter doesn't
  implement the CorrectionIssuer capability. Checked all 11 open PRs; none
  add it either, so this isn't a merge-order gap, it's an unbuilt backend
  capability. Replaced with a one-line "coming soon" note in Next steps.
- Removed the "Pair with Subiekt nexo" bullet from Next steps per review —
  the KSeF tutorial should carry zero Subiekt references.
- Renamed/consolidated the local (gitignored) capture scripts used to
  produce these screenshots for future reuse.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014S9kgctitB75BfHEDejJ6B
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(ksef): restore Part 6 (KOR corrections) tutorial with real screenshots

CorrectionIssuer shipped on KsefInvoicingAdapter (#1289), so the correction
flow that was previously removed from the tutorial (backend didn't support
it yet) now works end to end. Captured 4 real screenshots against a live
KSeF sandbox correction: the Issue correction button, the filled-in
correction dialog, the post-submit confirmation, and the invoices list
showing the original + correction rows side by side.

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* chore(docs): address tech-review findings - remove orphaned assets and stray .gitkeep files

Delete 34 Subiekt screenshots never referenced by tutorial.md or
README.md (an earlier numbering scheme plus a cut PrestaShop-order
walkthrough section), and two redundant .gitkeep files - one in a
stray top-level libs/integrations/subiekt/assets/ directory
confusable with the real docs/assets/, and one in
libs/integrations/ksef/docs/assets/ now that it holds 22 real
screenshots.

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* test(erli): cover #1146 cancellation stock-restore hook (S7)

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(woocommerce): recapture master-shop screenshots in light mode

The 9 WooCommerce master-shop-setup-guide screenshots were dark-mode
(captured before this branch existed, pre-dating the docs relocation).
Recaptured all of them against a live dev stack in light mode, with the
same red-box/arrow callouts drawn programmatically instead of by hand.

Adds two reusable Playwright e2e scripts:
- apps/web/e2e/annotate.mjs: canvas-overlay helper to draw red
  rectangle/ellipse annotations (with optional arrow) onto a page before
  screenshotting, so future tutorial captures don't need a manual
  image-editor pass.
- apps/web/e2e/woocommerce-walkthrough.mjs: drives the WooCommerce
  connection wizard end-to-end and captures all 9 assets referenced from
  master-shop-setup-guide.md, forcing light theme via
  localStorage['openlinker.theme'] regardless of host prefers-color-scheme.

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(integrations): fix Piotr's review findings on PR #1284

- ksef/README.md: fix wrong KSeF API base URLs (was api-test.ksef.mf.gov.pl/api/v2
  and ksef.mf.gov.pl/api/v2; authoritative ksef-hosts.ts uses a bare /v2 path with
  prod host api.ksef.mf.gov.pl). Also documents the demo tier that was missing.
- ksef/docs/setup-guide.md: refresh from the stale C2-stub narrative (issuance
  throws, RegulatoryTransmitter unimplemented, no seller-profile config) to the
  shipped reality — full FA(3)+KOR issuance, RegulatoryTransmitter and
  CorrectionIssuer implemented, seller block present on KsefConnectionConfig.
- subiekt/docs/tutorial.md: the appsettings.json sample had a fictional
  "HeaderName": "X-Api-Key" field — the real bridge (AuthOptions) only has
  Enabled/ApiKey and hardcodes Authorization: Bearer. Removed the field and
  documented that the client's extra x-bridge-token header is sent but ignored
  by the bridge.
- subiekt/docs/runbook.md: same x-bridge-token clarification.
- woocommerce/README.md: capability table was missing CategoryProvisioner
  (manifest declares 6 capabilities, README listed 5).
- allegro/README.md: ADR-024 link pointed at the adrs/ directory instead of the
  file; repointed to the actual architecture-overview.md#824 section plus the
  correct ADR-024 file (which covers the related OfferManager/ProductPublisher
  split, not #824 itself).

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(subiekt): add Windows+WSL2 dev quick-setup guide (Presta + OpenLinker + bridge)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(subiekt): expand PrestaShop run steps + link canonical Getting Started

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(subiekt): use {{USER_NAME}} placeholder instead of a real Windows username

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(subiekt): add optional 'launch bridge from WSL' command box

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(subiekt): document payment-method / bank-account / cash-register per-invoice feature (#1324)

Adds setup-guide Part B2 subsection + tutorial Part 2b with live screenshots; FV-only requirement, multi-payer warning, fixed-Centrala note, advanced config keys.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(subiekt): fix broken image links in setup-guide (remap to existing assets, drop unbacked shots)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(ksef): payment config (FA(3) Platnosc) tutorial + KOR snapshot semantics

Live-E2E-verified on the KSeF test environment (2026-07-03):
- new tutorial Part 2a - per-connection payment config (method, term,
  bank account, SWIFT, skonto) with fresh screenshots
- setup-guide: config.payment field reference + issuance-time line
  snapshot semantics in the Corrections section (#1297)
- README: Platnosc feature bullet
- re-captured 12-ol-ksef-detail.png with the post-#1320 capability panel

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(integrations): address Piotr review round 3 on PR #1284

- strip unmerged #1335 content (Subiekt Part 2b + payment/cash-register
  sections + shots 28-29 + WSL-guide proxy-endpoint step) - rides with
  PR #1335 instead
- fix fabricated credentials/config examples against the real DTOs:
  KSeF (authType+secret, env+nested seller), DPD (login+password,
  payerFid string + required senderAddress), WooCommerce (siteUrl,
  inventory/orders blocks), Allegro (authorization-code flow,
  environment enum + optional keys), InPost (organizationId string,
  senderAddress, -pl sandbox host), Erli (real validator keys,
  ADR-025-correct taxonomy preference, decoder-accurate webhook body),
  AI (OL_AI_DEFAULT_MODEL / OL_AI_OPENAI_MODEL, gpt-4o-mini)
- Subiekt tutorial: manual document-type selection (FE does not
  NIP-preselect; Part 7 no longer instructs relying on auto-Receipt);
  README scopes the NIP rule to the auto-issue path
- sync KSeF/Subiekt capability tables with the adapters' implements
  lists; drop the stale Source layout trees
- align Subiekt /health response shape with the real bridge response
- revert the unrelated Erli S7 int-spec (0c1d07b) - moves to its own PR

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(integrations): post-review consolidation fixes on PR #1284

- merge origin/main so the branch carries the shipped FA(3) Platnosc
  feature (#1317, b40902e) that the payment docs describe - the docs
  no longer reference an unmerged feature
- repoint 4 inbound links broken by the docs/integrations -> package
  docs/ move (architecture-overview, getting-started, user-guide 02/04)
- Erli README: capability row now matches ErliOfferManagerAdapter's
  implements list (drop OfferLister/OfferQuantityBatchUpdater, add
  OfferStatusReader/OfferStockRestorer/TaxonomyBorrower)
- align the Part 2a heading dash with sibling headings
- woocommerce-walkthrough.mjs: admin password from OL_ADMIN_PASSWORD
  env instead of a hardcoded shared-instance value

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(plans): repoint dangling links after integrations docs move

The docs/integrations -> libs/integrations/<pkg>/docs/ relocation on
PR #1284 left two frozen implementation-plan docs pointing at the old
paths. Repoint them to the current locations.

Addresses the residual N1 finding from the PR #1284 delta re-review.

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(integrations): reconcile README/setup-guide shapes with DTOs; e2e cleanup

Address the consolidated /pr-review + /tech-review findings on PR #1284.

IMPORTANT:
- dpd-polska setup-guide: document payerFid as the required numeric id and
  masterFid as optional, matching DpdConnectionConfigDto.
- ksef setup-guide credentials table: the operator submits { authType, secret }
  (raw token); secretRef is platform-assigned, kept in prose only.
- woocommerce README: OrderProcessorManager supports only OrderFulfillmentUpdater
  (drop FulfillmentStatusReader / DestinationOptionsReader over-claim).
- prestashop README: add ProductPublisher + CategoryProvisioner to prose and
  capability table (manifest declares 6).

Minor:
- woocommerce setup-guide: note HTTPS is required (config validator rejects http).
- ksef README: clarify RegulatoryStatusReader is covered via RegulatoryTransmitter.
- apps/web/e2e: parameterize admin credentials via OL_ADMIN_USERNAME/PASSWORD,
  mark author-local defaults, drop dead VARIANT const, fix stale localStorage log
  message, add apps/web/e2e/README.md documenting these as manual capture scripts.
- erli setup-guide: wire in 00-dashboard/01-connections-list; drop redundant
  orphaned 12-order-detail.png.

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

---------

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
norbert-kulus-blockydevs added a commit that referenced this pull request Jul 22, 2026
* docs(plans): implementation plan for KSeF FA(3) Platnosc (#1311)

Adds the full implementation plan for emitting payment method, bank
account, payment term, and skonto in KSeF FA(3) invoices as a
per-connection config value (no live bank-accounts API on the KSeF
side, unlike inFakt's #1303/#1308). Includes the schema-audited XSD
child order for Platnosc (TerminPlatnosci -> FormaPlatnosci ->
RachunekBankowy -> Skonto) and the design mockup referenced by the
issue.

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* feat(ksef): emit FA(3) Platnosc from connection payment config (#1311)

Adds a per-connection, manually-entered payment configuration (default
payment method, bank account, payment term, early-payment discount)
and emits it into the FA(3) Platnosc element whenever configured.
Unlike inFakt (#1303/#1308), KSeF has no live bank-accounts API, so
this is a plain config value the operator types in once, mapped
straight through the existing seller/defaultTaxRate resolution chain
(factory -> adapter -> mapper -> pure builder).

- KsefPaymentConfig / KsefBankAccountConfig / KsefFormaPlatnosciValues
  on KsefConnectionConfig (domain types)
- Fa3PaymentInput / Fa3BankAccount / Fa3FormaPlatnosciValues (FA(3)
  builder-internal types)
- platnoscNode() in fa3-xml.builder.ts, emitted as a sibling of
  FaWiersz in the XSD-mandated child order: TerminPlatnosci ->
  FormaPlatnosci -> RachunekBankowy -> Skonto (confirmed against the
  vendored FA(3) v1-0E XSD - not payment-method-first)
- KsefAdapterFactory.resolvePayment + shape-validator checks for
  formaPlatnosci / bankAccount.nrRb / paymentTermDays
- FE: ksef-payment-config.ts assembly module (mirrors
  ksef-seller-config.ts) wired into edit-connection.schema.ts, new
  fields in ksef-structured-section.tsx
- FA3_IMPLEMENTATION_NOTES.md updated with the Platnosc mapping table

Backend: 305/305 ksef unit tests pass, including full XSD structural
validation of the new Platnosc block for both configured and
unconfigured connections. Frontend: 1887/1887 web unit tests pass.
check:invariants clean.

Closes #1311

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* fix(ksef): stop dropping payment sub-fields based on fill order (#1311)

Live Playwright smoke test against a real dev stack + KSeF sandbox
connection (Phase 6 of the implementation plan) surfaced a real bug:
applyKsefPaymentToConfig deleted the whole bankAccount/skonto
sub-object whenever a sibling field (nrRb, or the other of
conditions/amount) wasn't present in the SAME merge call. Since each
field syncs to configText independently per keystroke, this silently
discarded whatever the operator typed first - skonto could never
actually be saved, and a bankAccount sub-field typed before nrRb was
lost.

Fix: only drop a sub-object when it is completely empty (no keys at
all), never based on a missing sibling. The "nrRb required if
bankAccount is set" and "skonto needs both conditions+amount" rules
now live where they belong - the backend shape validator (save time,
new skonto check added) and the factory's resolvePayment (issuance
time) - so a violation surfaces as a clear error instead of silent
data loss.

Adds the e2e smoke-test script (apps/web/e2e/ksef-payment-config.mjs)
and its screenshots (docs/assets/ksef-1311-smoke/) used to find this
and verify the fix + full field set against the design mockup.

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(plans): mark #1311 acceptance criteria + Phase 6 complete

All acceptance criteria satisfied and Phase 6 (live smoke test +
verification artifact) done - link to the published artifact and
note the persistence bug found/fixed during the smoke test.

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* fix(ksef): address tech-review suggestions on PR #1317

- Shape validator + FE Zod schema now enforce the FA(3) TNrRB length
  bound (10-34 chars) on payment.bankAccount.nrRb instead of only
  non-emptiness, so a truncated account number is rejected at
  connection-save time rather than surfacing as an opaque KSeF XSD
  error at issuance.
- resolvePayment now defensively drops an unknown formaPlatnosci code
  or a negative/non-integer paymentTermDays (mirroring the existing
  bankAccount/skonto guards) for connections whose config predates the
  ksef.publicapi.v2 shape validator.

Addresses both SUGGESTION findings from the tech-lead review on #1317.

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* fix(ksef): correct RachunekBankowy child element order (NrRB, SWIFT, NazwaBanku)

Confirmed live against the KSeF sandbox: the FA(3) XSD mandates NrRB
before SWIFT before NazwaBanku inside RachunekBankowy. Emitting
NazwaBanku before SWIFT caused KSeF to reject the invoice (status 450,
"invalid child element SWIFT... expected OpisRachunku").

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* fix(ksef): session IV reuse + FA(3) Podmiot2 required JST/GV fields

Two E2E-verified prerequisites for live KSeF acceptance, confirmed
against the KSeF test environment while doing live sandbox
verification of #1311 (the Platnosc feature was silently blocked
from ever actually being accepted end-to-end without these):

1. AES session IV: encryptDocument was generating a fresh random IV
   per document, but KSeF's SendInvoiceRequest wire shape has no
   per-document IV field — it decrypts every document in a session
   using the session IV declared once in
   OpenOnlineSessionRequest.encryption.initializationVector. A
   per-document IV caused a SHA-256 hash mismatch (status 430)
   because KSeF's decryption produced garbage. Fixed: encryptDocument
   now reuses context.symmetricKey.iv (the session IV) for every
   document.

2. JST and GV are REQUIRED by the FA(3) XSD on Podmiot2 (no
   minOccurs="0") but were never emitted, causing a semantic
   validation rejection. Fixed: buyerNode now always emits JST=2,
   GV=2 ("nie dotyczy" — not a JST subsidiary unit / not a VAT group
   member).

Cherry-picked from 634c27a5 (previously only on the unmerged branch
1228-ksef-fa3-full-visualization-new, which was never pushed to
origin) — without this fix no live KSeF invoice submission from this
codebase can be genuinely accepted, regardless of Platnosc content.

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* fix(ksef): address PR #1317 review - Platnosc structural order checks + ctor options bag

- validateFa3Xml now enforces the XSD-mandated child order of Fa/Platnosc
  (TerminPlatnosci -> FormaPlatnosci -> RachunekBankowy -> Skonto) and of each
  RachunekBankowy (NrRB required, SWIFT before NazwaBanku), so an ordering
  regression like the pre-review RachunekBankowy bug is caught by the
  'passes the structural validator' tests instead of co-signed by them
- KsefInvoicingAdapter trailing optional params (payment, now) move into a
  KsefInvoicingAdapterOptions bag so future additions never shift positional
  call sites
- resolvePayment doc comment now names the intentional three-layer
  (FE assembly / shape validator / factory) defense-in-depth explicitly

The blocking SWIFT/NazwaBanku emit-order finding was already fixed in
c315e4d; this adds the validator coverage that would have caught it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* test(web): align stale skonto persistence test with per-keystroke sync fix

The merge helper intentionally persists an incomplete skonto (conditions
without amount) so independent per-field config sync never drops the
first-typed field; completeness is enforced at save/issuance time (#1311
smoke-test finding). The test still asserted the old drop-on-incomplete
behaviour.

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* fix(api): version listings invalid-path-id int-spec paths under /v1 (#1328)

Semantic merge conflict between #1316 (URI versioning, global /v1 default)
and #1313 (new listings-invalid-path-id.int-spec.ts written pre-versioning):
the spec's unprefixed /listings/... requests stopped matching any route, so
every request returned the router-level 404 ("Cannot GET ...") - the three
expect(400) ParseUUIDPipe assertions failed and the expect(404) ones passed
vacuously. Main has been red on Integration Tests since the two merged.

Prefix the five request paths with /v1, matching every other listings
int-spec. Verified against the Testcontainers harness: 5/5 pass.

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* fix(ksef): address second tech-review round on PR #1317

- NrRB input is whitespace-stripped at FE assembly time (new normalizeNrRb,
  mirroring the normalizeNip precedent) so a conventionally-spaced NRB paste
  never reaches config.payment.bankAccount.nrRb or the FA(3) wire with inner
  spaces; the FE zod length check now counts the stripped value, converging
  with the BE shape validator's 10-34 bound
- shape validator rejects a wrong-typed payment / bankAccount / skonto with
  an explicit 'must be an object' issue instead of silently falling through
  to issuance-time drop
- the tripled '1'..'7' TFormaPlatnosci list now carries a cross-reference
  comment at all three declaration sites (FE schema, plugin connection-config
  types, FA3 schema types) so a future 8th code is added everywhere
- ksef-payment-config.mjs smoke script reads login credentials from
  WEB_USER / WEB_PASSWORD env vars (dev-stack defaults preserved), matching
  the WEB_BASE / KSEF_CONN_ID pattern

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* fix(ksef): address third tech-review round on PR #1317

- shape validator now rejects an nrRb containing inner whitespace (the FE
  strips via normalizeNrRb, but a direct API write bypassed that and a
  spaced NRB would fail KSeF's TNrRB pattern at clearance); the factory's
  resolvePayment additionally strips whitespace defensively for
  pre-validator config rows, and drops a whitespace-only value instead of
  emitting an empty NrRB
- the hardcoded Podmiot2 JST/GV = 2 limitation is now recorded in
  FA3_IMPLEMENTATION_NOTES.md under Known limitations, with a pointer
  comment at the buyerNode declaration site
- new drift spec makes the tripled TFormaPlatnosci '1'..'7' list
  self-enforcing: the two in-package arrays are compared by import and the
  FE array is extracted from its source file, so a one-sided edit fails
  the suite
- follow-up issue #1330 filed for moving plugin-specific connection-config
  assembly behind a plugin slot

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* fix(ksef): address fourth tech-review round on PR #1317

- IMPORTANT (skonto error locality): editConnectionSchema gains a
  both-or-neither superRefine on the skonto pair, anchoring the error on
  the missing field at submit time instead of surfacing the BE shape
  validator's form-level 400; per-keystroke persistence of a partial pair
  stays untouched. Field descriptions now say the pair is required
  together.
- SUGGESTION (drift-guard monorepo coupling): the cross-package FE half
  of the TFormaPlatnosci drift guard moved out of the plugin jest suite
  (7-level relative path into apps/web) into a repo-level invariant,
  scripts/check-ksef-forma-platnosci-drift.mjs, wired into
  check:invariants; the in-package spec keeps the import-level comparison.
- SUGGESTION (paymentTermDays unbounded): sanity cap at 999 days on both
  sides (FE zod refine + BE shape validator 0-999), with tests at and
  above the bound.
- SUGGESTION (misleading mockup filename): docs/plans/mockups/
  infakt-ksef-bank-account-payment-terms.html renamed to
  ksef-payment-platnosc.html; all references updated.
- SUGGESTION (PR scope / merge-commit visibility): handled in the PR body
  (dedicated line for the session-IV crypto fix so it survives into the
  squash-merge message).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(ksef): cite spec sections confirming single-IV-per-session AES model

Review nit on PR #1317 (approve round): the session-IV reuse in
encryptDocument was justified only via the CIRFMF C# reference client.
The doc comments in ksef-session-crypto.service.ts and
ksef-crypto.types.ts now also cite the KSeF 2.0 OpenAPI
(OpenOnlineSessionRequest.encryption.initializationVector is the only IV
on the wire; SendInvoiceRequest has no per-document IV field) and the
official CIRFMF session guide (sesja-interaktywna.md: one 256-bit key +
128-bit IV generated at session open, used for the session's documents).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* refactor(web): move plugin-specific connection-config assembly behind a plugin slot (#1334)

* refactor(web): move plugin-specific connection-config assembly behind a plugin slot

Introduce PlatformContribution.connectionConfig (#1330) - the non-render
half of a platform's structured connection-config editing: a Zod schema
fragment composed into the edit-connection resolver at render time, a
read-side readConfigToForm hydrator, and a write-side applyToConfig
partial-patch assembler. Field names are declaration-merged into the new
PluginEditConnectionFields interface (mirrors the PluginApiNamespaces
precedent) so plugin sections stay statically typed.

Migrate KSeF as the first platform: every ksef-named module leaves the
shared features/connections feature (seller/payment assembly, NIP/NRB
normalizers, setup wizard schema+form+page, value sets) and lands in the
plugins/ksef slice; the KSeF Zod fields, superRefine checks, merge
clauses, and hydration readers move verbatim into
plugins/ksef/ksef-connection-config.ts, preserving the #1311
per-keystroke partial-patch semantics (relocated tests pin them).
Other platforms' inline fields migrate as follow-ups now that the seam
exists.

Closes #1330

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* refactor(web): tighten the connection-config plugin-slot contract per tech review

Address the tech-review round on PR #1334 (1 IMPORTANT + 2 SUGGESTIONS):

- IMPORTANT: constrain ConnectionConfigContribution.schemaShape to a
  keyof-PluginEditConnectionFields mapped type
  (PluginConnectionConfigSchemaShape) so a schema key with no matching
  declaration-merged form field is a compile error. The synthetic
  acmeToken test fixture now declaration-merges its field (same seam
  real plugins use), which also drops the patch-type casts it needed.
- SUGGESTION: extend assertUniquePluginInvariants with invariant 4 -
  cross-plugin connectionConfig.schemaShape field-name collisions throw
  at module load (TS silently accepts same-type declaration merges), with
  a registry test pinning the failure path.
- SUGGESTION: dev-mode console.warn in syncStructuredToJson when a synced
  field has neither a host merge clause nor a connectionConfig
  contribution (the silent-drop misconfiguration #1330 exists to
  prevent); StructuredField is now derived from a runtime as-const array.
  Also drop the redundant defensive spread before applyToConfig.

Doc row for connectionConfig updated with the compiler constraint, the
boot-time collision guard, and the platform-prefix naming convention.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* refactor(web): annotate ksefSchemaShape + widen the schemaShape dev-warn per second-pass review

Second-pass tech-review SUGGESTION: the excess-property check from the
PluginConnectionConfigSchemaShape mapped type only fires on a fresh or
annotated literal, so the separate un-annotated ksefSchemaShape const
silently accepted unmerged keys. Annotating it with
ConnectionConfigContribution['schemaShape'] restores the check at the
declaration (verified: a bogus key now fails with TS2353).

Also closes the related note: syncStructuredToJson's dev warn now fires
not only when a platform ships no connectionConfig contribution, but
also when the contribution exists and the synced field is absent from
its schemaShape - the other runtime-detectable silent-drop shape.

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

---------

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* fix(ksef): address fifth tech-review round on PR #1317

- Record the AES-256-CBC session-IV determinism trade-off under Known
  limitations in FA3_IMPLEMENTATION_NOTES.md, next to the JST/GV entry,
  so security reviewers find it where they look first.
- Harden the TFormaPlatnosci drift script's extraction: tolerate an
  optional type annotation, Prettier multi-line wraps, and inline
  comments inside the array body.
- Promote the untyped-bag string readers (readConfigString /
  readOptionalConfigString) to shared/plugins; ksef-connection-config
  and EditConnectionForm now share one definition instead of per-file
  copies.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* fix(ci): reclaim disk from orphaned Testcontainers volumes/images/build-cache

Ryuk is disabled on the persistent self-hosted runner, so the manual
orphan-sweep step is the only cleanup for Testcontainers resources.
docker rm -f (no -v) left anonymous volumes behind forever, and the
sweep never touched dangling images or build cache — both accumulate
indefinitely and were the actual cause of "no space left on device"
integration-test failures unrelated to any test's own code.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* fix(ci): widen disk cleanup after first attempt reclaimed 0B (#1321)

The prior sweep (docker rm -fv + filtered volume/image/builder prune)
still failed with the exact same "no space left on device" error, and
logged 0B reclaimed across all three prune calls. Two concrete bugs:

- The volume-prune label filter never matches: anonymous volumes don't
  inherit the labels of the container that created them, so
  `--filter "label=org.testcontainers=true"` matches nothing. Dropped
  the filter — an unfiltered `docker volume prune` is still safe,
  since Docker refuses to remove a volume still attached to any
  container regardless of label.
- Plain `docker image prune -f` only removes dangling (untagged)
  images; 0B reclaimed means this runner has none, so the real disk
  pressure is old-but-tagged image versions across many past
  integration-test pulls. Widened to `-af` (all unused images) — still
  safe under concurrency since Docker never removes an image a live
  container references, and a concurrent job needing a pruned image
  just re-pulls it rather than failing.

Also logs `docker system df -v` before/after cleanup so the next
failure (if any) can be diagnosed from CI output alone — there is no
shell access to this runner.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* revert(ci): back out disk-cleanup experiment (#1321)

docker system df on the failing run revealed this self-hosted runner
is shared with unrelated third-party workloads (linux-runner-1..4,
hedera-payment-db, hawser-dockhand-agent — none of it OpenLinker's).
The runner's disk pressure is caused by that other tenancy's live
containers, not by anything OpenLinker's CI leaves behind, so a wider
prune (image -af, unfiltered volume prune, builder prune) only ever
reclaims OpenLinker's own garbage and can't fix a permanently
oversubscribed shared host — while carrying real risk of touching
image/volume state a co-tenant relies on. Reverting to the original,
narrowly-scoped cleanup: remove only OpenLinker's own orphaned,
Testcontainers-labeled containers whose owning CI run has finished.
Disk capacity itself needs to be resolved with whoever operates this
runner, not from this repo's workflow.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* diag(test): log docker disk usage around each PS+MySQL container boot (#1321)

A full apps/api integration run boots 4 suite-scoped PrestaShop+MySQL
Testcontainer pairs sequentially (maxWorkers: 1), and Ryuk is disabled
on the self-hosted CI runner — so a suite that times out before
reaching its own cleanup() leaks disk for the rest of that run with no
way to tell, from CI output alone, which suite did it (a prior
"no space left on device" mid-run failure had exactly this shape).

Logs `docker system df` before each boot and after each cleanup() in
the shared prestashop-container.helper.ts, so the next such failure
can be attributed to a specific suite without runner shell access.

Considered also converting the two PS suites that don't install the OL
module (prestashop-harness-smoke, prestashop-webhook-provisioning) to
mock the port instead of booting a real container, to cut boot count
from 4 to 2. Both are deliberately real-PS regression guards — the
smoke test exists to prove the Testcontainer harness itself works
end-to-end, and the webhook-provisioning spec guards #541, a bug that
was green at the unit-test/mock layer and only caught against the real
WS API. Mocking either would remove the exact coverage they exist for,
so this is diagnostics-only; reducing real PS boot count needs a
different approach (e.g. sharing one boot between the two suites that
already require installOlModule: true).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* diag(ci): probe host /tmp for leaked ol-ps-data-* PrestaShop bind-mount dirs (#1321)

The PS test harness bind-mounts a mkdtemp dir onto /var/www/html and PS
writes ~1GB into it as root; the unprivileged rmSync cleanup leaks it on
the persistent self-hosted host, invisible to docker system df and the
container sweep. Temporary read-only probe to confirm before fixing the
cleanup path.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* fix(test): stop leaking root-owned PS bind-mount dirs on the CI host (#1321)

PS writes ~1GB into the ol-ps-data-* bind-mount as root, and on the
runner-in-docker host the source path resolves on the HOST daemon's
/tmp - the unprivileged rmSync cleanup could never delete it, so every
PS suite leaked ~1GB onto the host root fs until containerd hit
"no space left on device".

Two-part fix:
- removePsDataDir empties the dir through a root alpine container
  (works in both host topologies), then rmSync's the local dir.
- The CI orphan-sweep step reaps host /tmp/ol-ps-data-* dirs older
  than 90 min (same age gate as the container sweep), covering
  SIGKILLed/cancelled runs that never reach in-process cleanup.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* revert(ci): drop temporary disk diagnostics + host-tmp sweep steps (#1321)

Restores ci.yml to main's version. With the host root fs at 100% every
docker-cli invocation in these steps hangs or fails, blocking the job
before tests even start. The in-process removePsDataDir fix in the PS
container helper stays - it adds no CI step.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* Revert "diag(test): log docker disk usage around each PS+MySQL container boot (#1321)"

This reverts commit eea68ac. Diagnostics no longer needed - the root
cause (leaked root-owned ol-ps-data-* bind-mount dirs on the host) is
identified and fixed by removePsDataDir.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

---------

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
norbert-kulus-blockydevs added a commit that referenced this pull request Jul 22, 2026
…rrections (#1297) (#1329)

* docs(invoicing): implementation plan for issuance-time line snapshot (#1297)

Plan for persisting a neutral issuance-time line snapshot on InvoiceRecord
so KSeF (and any complete-resubmit) corrections diff against the lines as
issued, not the order's current state. Branch cut from main (not #1317 -
zero file overlap, snapshot is pure-core in InvoiceService).

Refs #1297

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* feat(invoicing): persist issuance-time line snapshot for safe corrections (#1297)

Persist a neutral issuedLineSnapshot ({ buyer, currency, lines }) on
InvoiceRecord whenever a document is issued (from the issue command) or
corrected (the correction's own post-correction lines), captured in the core
InvoiceService - no adapter change. The correction endpoint now assembles
originalDocument from that snapshot on the document being corrected, so a KOR's
originalLineNumber-indexed deltas diff against the lines AS ISSUED (and, for a
correction-of-correction, against the prior correction's own lines) even if the
order changed since issuance. Records issued before the column existed fall back
to the pre-#1297 order-derived reconstruction.

- New IssuedLineSnapshot type + nullable jsonb column + migration 1818000000003.
- InvoiceService.issueInvoice/issueCorrection populate it via InvoiceOutcomePatch;
  applyCorrectionDeltas computes the correction's after-lines from the original
  snapshot + per-line deltas.
- Controller prefers the persisted snapshot (buildSnapshotFromRecord), skipping
  the order fetch; falls back to order-derived only when absent.
- Not exposed via any response DTO (carries buyer PII), mirroring
  documentContent/sourceDocument.

Tests: service snapshot on issue + 4 correction cases; controller snapshot-
preference + correction-of-correction; repository jsonb round-trip int-spec.

Closes #1297

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* refactor(invoicing): address tech-review suggestions on line snapshot (#1297)

- Reject duplicate originalLineNumber correction lines at the HTTP
  boundary (new UniqueOriginalLineNumbersConstraint + DTO spec) so the
  persisted "after" snapshot can never silently last-write-win against
  what the provider computed; applyCorrectionDeltas doc notes the
  residual duplicate semantics for non-API callers.
- Type IssuedLineSnapshot.buyer as the structural IssuedSnapshotBuyer
  shape instead of the BuyerProfile class - the field round-trips
  through jsonb without the class prototype, so the type now says so.
- Document the transitional taxId: null caveat on
  buildSnapshotFromRecord for snapshots seeded by a correction of a
  pre-#1297 record (one-hop degradation that self-heals).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* refactor(invoicing): non-null-assert guarded fields in correction snapshot

Per PR review: buildSnapshotFromRecord's `?? ''` / ternary `''` fallbacks
for documentNumber/issueDate were dead branches masking the caller's
existing non-null guard (providerInvoiceNumber/issuedAt asserted before
this path runs). A future guard regression would now surface as a type
error / crash instead of silently producing an invalid empty string.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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 4.8 (1M context) <noreply@anthropic.com>
norbert-kulus-blockydevs added a commit that referenced this pull request Jul 22, 2026
Repairs the BLOCKING merge-state defect and all IMPORTANT/SUGGESTION
findings from the /pr-review on #1335.

BLOCKING — the branch had merged an older main and, via a bad conflict
resolution, silently reverted three shipped features (#1297 issued-line
snapshot, #1317 KSeF FA(3) Płatność, #1330 connection-config plugin slot,
plus release-please plumbing). Reset the branch to origin/main and
re-applied only the Subiekt-scoped delta on top, so the diff is now the
Subiekt-only change and no merged work is reverted.

IMPORTANT
1. Payment/bank/cash-register config now rides the #1330
   ConnectionConfigContribution plugin slot (new
   plugins/subiekt/subiekt-connection-config.ts) instead of growing the
   host edit-connection.schema.ts.
2. Payment method is a real tri-state: an explicit "Not set (Subiekt
   default)" option, and the summary derives from the actual state (no
   false "Cash" for unset).
3. use-set-default-bank-account-mutation now invalidates the owner-aware
   subiektBankAccounts key too, so isDefault flags don't go stale.
4. The section fires the default-sync via .mutate() (not void mutateAsync)
   to avoid an unhandled promise rejection on failure.
5. The set-default error toast copy is now provider-neutral (was inFakt).
6. Removed the stale "does not type-check (3-arg constructor)" comment in
   subiekt-adapter.factory.ts — the 4-arg constructor ships here.

SUGGESTIONS
- paymentFields() warns when transfer is configured without a bankAccountId.
- setDefaultBankAccount guards the Number(accountId) coercion, throwing the
  config domain error on a non-numeric id.
- Payment labels routed through t(); the cash-register help default is English.
- Bank accounts group by the stable ownerPodmiotId, not the display name.

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
norbert-kulus-blockydevs added a commit that referenced this pull request Jul 22, 2026
… Subiekt nexo (#1284)

* docs(integrations): add README + operator tutorials for all 8 adapter packages

Add per-package README.md to every integration package that was missing one:
ai, allegro, dpd-polska, erli, inpost, ksef, subiekt, woocommerce.
Each README covers adapter key + capabilities, credentials/config shape,
and links to further operator docs.

Add full A-to-Z operator tutorials for KSeF and Subiekt nexo with per-step
screenshot placeholders (ksef/tutorial.md, subiekt/tutorial.md). The Subiekt
tutorial covers bridge setup via PowerShell/WSL (without-exe-packaging branch),
wizard, B2B faktura, B2C paragon, and idempotency. The KSeF tutorial covers
token generation on the MF portal, connection wizard, B2B order, issuance, and
UPO download.

Create libs/integrations/ksef/assets/ and libs/integrations/subiekt/assets/
directories for future screenshot captures.

Update root README Integrations table to link the tutorial.md files for KSeF
and Subiekt nexo.

Closes #1265
Closes #1266

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014NQB4zBWSrneR71TRKkx1t
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(integrations): add real Playwright screenshots + rewrite KSeF and Subiekt tutorials

Replaces all placeholder screenshot references in KSeF and Subiekt nexo
tutorials with actual Playwright captures taken against a live OpenLinker
instance (preview build with all in-flight FE PRs merged).

KSeF assets (14 PNGs):
  01-02: connections list + platform picker
  03-09: wizard fields (name, env, NIP, address, auth-type, secret)
  10-12: connection created, list, detail page
  13-14: invoices list with regulatory badges, invoice detail

Subiekt assets (18 PNGs):
  06-15: connection wizard (empty -> filled -> created -> test -> list -> detail)
  20-26: invoice flow (orders list, order detail, connection picker,
          ready-to-issue, issue clicked, issued state, invoices list)

Tutorial rewrites:
  - ksef/tutorial.md: reordered around actual wizard fields; Part 1 (KSeF portal)
    marked manual; Parts 2-5 use real screenshot refs
  - subiekt/tutorial.md: full rewrite to match connection-picker flow (multiple
    Invoicing connections), real bridge startup instructions, TLS note, idempotency

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014S9kgctitB75BfHEDejJ6B
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(subiekt): add real Subiekt nexo FS document screenshot to tutorial

Part 5 (verify in Subiekt nexo) was the only remaining manual-step
placeholder without a real capture. Replaces it with an actual screenshot
of an issued FS document open in Subiekt nexo desktop.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014S9kgctitB75BfHEDejJ6B
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(ksef): add real KSeF 2.0 test-portal screenshots for token generation

Part 1 (get a KSeF authorisation token) was the last manual-step
placeholder. Captures the full flow on the live ap-test.ksef.mf.gov.pl
portal: test-auth login, NIP + test certificate, dashboard, token list,
generate-token form, and the revealed token (value redacted before commit
since it's a live, usable test-environment credential).

Also corrects the test-portal URL: ksef-test.mf.gov.pl (KSeF 1.0) was
decommissioned 2025-09-01; the current test environment is
ap-test.ksef.mf.gov.pl (KSeF 2.0).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014S9kgctitB75BfHEDejJ6B
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(subiekt): point to the openlinker-subiekt-bridge repo

The bridge is being moved to its own repo under the openlinker-project
org (not yet published). Update README + tutorial repo links and clone
paths accordingly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014S9kgctitB75BfHEDejJ6B
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(integrations): move all integration docs + assets under libs/integrations/<pkg>/docs/

Establishes one consistent convention across all 8 integration packages:
docs (setup guides, runbooks, tutorials) and their image assets live at
libs/integrations/<pkg>/docs/ and libs/integrations/<pkg>/docs/assets/,
co-located with the code they document instead of split across a separate
docs/integrations/<pkg>/ tree.

Moves (via git mv, history preserved):
- docs/integrations/<pkg>/*.md -> libs/integrations/<pkg>/docs/*.md
  (allegro, dpd-polska, erli, inpost, ksef, prestashop, subiekt, woocommerce)
- docs/integrations/woocommerce/screenshots/ -> .../woocommerce/docs/assets/
- docs/assets/erli/ -> libs/integrations/erli/docs/assets/
- docs/assets/subiekt/ -> libs/integrations/subiekt/docs/assets/
  (merged with the tutorial screenshots already at .../subiekt/assets/,
  no filename collisions)
- libs/integrations/{ksef,subiekt}/tutorial.md -> .../docs/tutorial.md
- libs/integrations/{ksef,subiekt}/assets/ -> .../docs/assets/

Rewrites every relative link and image reference in the moved files
(directory depth changed by one level), updates the 8 package READMEs'
documentation sections, the root README's integration table, the
new-integration issue template, ADR-025's doc links, and the e2e
Playwright capture scripts' output paths. Verified all 251 local links
in the changed docs resolve.

Historical docs/plans/*.md archive entries are intentionally left
untouched — they're frozen snapshots per docs/plans/README.md's own
convention, not live documentation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014S9kgctitB75BfHEDejJ6B
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(subiekt): drop old personal repo link, switch bridge instructions to plain PowerShell

- Remove remaining references to the old norbert-kulus-blockydevs/openlinker-subiekt
  repo and its without-exe-packaging branch — the bridge now lives directly on
  openlinker-project/openlinker-subiekt-bridge.
- Rewrite "running the bridge" / smoke-test steps as plain Windows PowerShell
  (Invoke-RestMethod) instead of WSL-flavored bash+curl — the bridge always
  runs on Windows; WSL was an artifact of how screenshots were captured for
  this PR, not an operator requirement.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014S9kgctitB75BfHEDejJ6B
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(integrations): declutter, crop, and fix tutorial screenshots

Re-captures the KSeF and Subiekt tutorial screenshots against the fully
merged dev branch (all 9 in-flight FE PRs) with three fixes:

1. Wizard field-by-field captures consolidated into one "all fields
   filled" screenshot per wizard (KSeF: 6 shots -> 1; Subiekt: 3 shots -> 1).

2. Noisy/irrelevant data cropped instead of shown in full:
   - Connections list: crop to header + Add-connection button, hiding
     unrelated existing connections.
   - Invoices list: filter by the tutorial's own connection + issued
     status before capturing, instead of showing the full unfiltered
     list (which mixed in dozens of unrelated test rows).
   - Order detail / invoice-issued state: crop above the Sync
     status/Activity/Order Snapshot sections, which aren't part of the
     tutorial narrative.
   - Connection detail page: crop above the "Capabilities" panel, which
     has an unrelated pre-existing bug (renders literal `’` and
     "adapter not recognized" for working connections) surfaced by one
     of the merged branches -- out of scope for this docs PR.

3. Fixed two broken captures:
   - ksef/docs/assets/14-ol-invoice-detail-ksef.png was an "Invoice not
     found" error -- GET /invoices/:invoiceId didn't exist on main yet
     (ships in open PR #1231). Temporarily merged that branch locally to
     capture the real working page, then reverted the merge.
   - subiekt order-detail flow (21-25) used a test order with a
     dangling customerId (no matching customer_projections row) that
     rendered "Couldn't load customer details. Retry". Backed a real
     order (ol_order_ksef_e2e_test_001) with a customer_projections row
     matching its embedded billing address, then drove the actual
     Issue-invoice flow through the live Subiekt bridge end-to-end.

Also drops one redundant intermediate screenshot
(24-ol-invoice-issuing-or-issued.png, identical to 25 once the bridge
issue was fixed) and removes now-orphaned per-field screenshot files.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014S9kgctitB75BfHEDejJ6B
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(integrations): redact KSeF portal cert/token data, fix platform-picker screenshots

- p3/p4 (KSeF portal — NIP + test cert / sign test request): the test
  certificate's SHA256 fingerprint and ID were shown in plaintext. Redact
  both (they're live, usable values on the shared public test environment,
  same category as the token value already redacted in p8).
- p6 (KSeF portal token list): was showing the full historical token list
  (9+ unrelated tokens: "kolejny", "henha", "teest", ...) instead of just
  the tutorial's own token. Generate a fresh, clearly-named token
  immediately before capturing so it sorts to the top, crop to header +
  that one row.
- 02-ol-platform-picker.png / 07-ol-platform-picker.png: these were
  byte-identical between the KSeF and Subiekt tutorials, but their alt
  text claimed each showed its own platform's card specifically ("KSeF
  card" / "Subiekt nexo card") — neither image actually highlighted
  anything. Hover the relevant card before capturing so each tutorial's
  screenshot genuinely shows what its alt text says.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014S9kgctitB75BfHEDejJ6B
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(ksef): add missing Part 3/4 screenshots, drop unimplemented KOR section

Part 3 (get a B2B order in), Part 4 (issue the invoice), and the Subiekt
cross-reference in "Next steps" had no corresponding OpenLinker screenshots
- three (correction, orders-list, order-detail) were simply never captured.
Fills the gap with a real order (ol_order_e2e_tutorial_001, B2B, company
buyer) driven through orders-list -> order-detail (not issued) ->
connection picker -> ready-to-issue -> issued+accepted, calling the real
KSeF test API.

Also:
- Corrected an inaccurate claim: the tutorial said OpenLinker "pre-selects
  Invoice (faktura VAT) when a NIP is present" — checked
  order-invoice-panel.tsx and the document-type select always defaults to
  'invoice' with no NIP-aware logic. Removed the false claim.
- Removed the entire "Part 6 — Correction invoices (KOR)" section. The
  Issue-correction dialog is real, working FE (KSeF has an
  invoiceCorrectionFlow slot), but actually submitting fails with "Provider
  does not support correction issuance" — KsefInvoicingAdapter doesn't
  implement the CorrectionIssuer capability. Checked all 11 open PRs; none
  add it either, so this isn't a merge-order gap, it's an unbuilt backend
  capability. Replaced with a one-line "coming soon" note in Next steps.
- Removed the "Pair with Subiekt nexo" bullet from Next steps per review —
  the KSeF tutorial should carry zero Subiekt references.
- Renamed/consolidated the local (gitignored) capture scripts used to
  produce these screenshots for future reuse.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014S9kgctitB75BfHEDejJ6B
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(ksef): restore Part 6 (KOR corrections) tutorial with real screenshots

CorrectionIssuer shipped on KsefInvoicingAdapter (#1289), so the correction
flow that was previously removed from the tutorial (backend didn't support
it yet) now works end to end. Captured 4 real screenshots against a live
KSeF sandbox correction: the Issue correction button, the filled-in
correction dialog, the post-submit confirmation, and the invoices list
showing the original + correction rows side by side.

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* chore(docs): address tech-review findings - remove orphaned assets and stray .gitkeep files

Delete 34 Subiekt screenshots never referenced by tutorial.md or
README.md (an earlier numbering scheme plus a cut PrestaShop-order
walkthrough section), and two redundant .gitkeep files - one in a
stray top-level libs/integrations/subiekt/assets/ directory
confusable with the real docs/assets/, and one in
libs/integrations/ksef/docs/assets/ now that it holds 22 real
screenshots.

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* test(erli): cover #1146 cancellation stock-restore hook (S7)

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(woocommerce): recapture master-shop screenshots in light mode

The 9 WooCommerce master-shop-setup-guide screenshots were dark-mode
(captured before this branch existed, pre-dating the docs relocation).
Recaptured all of them against a live dev stack in light mode, with the
same red-box/arrow callouts drawn programmatically instead of by hand.

Adds two reusable Playwright e2e scripts:
- apps/web/e2e/annotate.mjs: canvas-overlay helper to draw red
  rectangle/ellipse annotations (with optional arrow) onto a page before
  screenshotting, so future tutorial captures don't need a manual
  image-editor pass.
- apps/web/e2e/woocommerce-walkthrough.mjs: drives the WooCommerce
  connection wizard end-to-end and captures all 9 assets referenced from
  master-shop-setup-guide.md, forcing light theme via
  localStorage['openlinker.theme'] regardless of host prefers-color-scheme.

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(integrations): fix Piotr's review findings on PR #1284

- ksef/README.md: fix wrong KSeF API base URLs (was api-test.ksef.mf.gov.pl/api/v2
  and ksef.mf.gov.pl/api/v2; authoritative ksef-hosts.ts uses a bare /v2 path with
  prod host api.ksef.mf.gov.pl). Also documents the demo tier that was missing.
- ksef/docs/setup-guide.md: refresh from the stale C2-stub narrative (issuance
  throws, RegulatoryTransmitter unimplemented, no seller-profile config) to the
  shipped reality — full FA(3)+KOR issuance, RegulatoryTransmitter and
  CorrectionIssuer implemented, seller block present on KsefConnectionConfig.
- subiekt/docs/tutorial.md: the appsettings.json sample had a fictional
  "HeaderName": "X-Api-Key" field — the real bridge (AuthOptions) only has
  Enabled/ApiKey and hardcodes Authorization: Bearer. Removed the field and
  documented that the client's extra x-bridge-token header is sent but ignored
  by the bridge.
- subiekt/docs/runbook.md: same x-bridge-token clarification.
- woocommerce/README.md: capability table was missing CategoryProvisioner
  (manifest declares 6 capabilities, README listed 5).
- allegro/README.md: ADR-024 link pointed at the adrs/ directory instead of the
  file; repointed to the actual architecture-overview.md#824 section plus the
  correct ADR-024 file (which covers the related OfferManager/ProductPublisher
  split, not #824 itself).

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(subiekt): add Windows+WSL2 dev quick-setup guide (Presta + OpenLinker + bridge)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(subiekt): expand PrestaShop run steps + link canonical Getting Started

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(subiekt): use {{USER_NAME}} placeholder instead of a real Windows username

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(subiekt): add optional 'launch bridge from WSL' command box

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(subiekt): document payment-method / bank-account / cash-register per-invoice feature (#1324)

Adds setup-guide Part B2 subsection + tutorial Part 2b with live screenshots; FV-only requirement, multi-payer warning, fixed-Centrala note, advanced config keys.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(subiekt): fix broken image links in setup-guide (remap to existing assets, drop unbacked shots)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(ksef): payment config (FA(3) Platnosc) tutorial + KOR snapshot semantics

Live-E2E-verified on the KSeF test environment (2026-07-03):
- new tutorial Part 2a - per-connection payment config (method, term,
  bank account, SWIFT, skonto) with fresh screenshots
- setup-guide: config.payment field reference + issuance-time line
  snapshot semantics in the Corrections section (#1297)
- README: Platnosc feature bullet
- re-captured 12-ol-ksef-detail.png with the post-#1320 capability panel

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(integrations): address Piotr review round 3 on PR #1284

- strip unmerged #1335 content (Subiekt Part 2b + payment/cash-register
  sections + shots 28-29 + WSL-guide proxy-endpoint step) - rides with
  PR #1335 instead
- fix fabricated credentials/config examples against the real DTOs:
  KSeF (authType+secret, env+nested seller), DPD (login+password,
  payerFid string + required senderAddress), WooCommerce (siteUrl,
  inventory/orders blocks), Allegro (authorization-code flow,
  environment enum + optional keys), InPost (organizationId string,
  senderAddress, -pl sandbox host), Erli (real validator keys,
  ADR-025-correct taxonomy preference, decoder-accurate webhook body),
  AI (OL_AI_DEFAULT_MODEL / OL_AI_OPENAI_MODEL, gpt-4o-mini)
- Subiekt tutorial: manual document-type selection (FE does not
  NIP-preselect; Part 7 no longer instructs relying on auto-Receipt);
  README scopes the NIP rule to the auto-issue path
- sync KSeF/Subiekt capability tables with the adapters' implements
  lists; drop the stale Source layout trees
- align Subiekt /health response shape with the real bridge response
- revert the unrelated Erli S7 int-spec (0c1d07b) - moves to its own PR

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(integrations): post-review consolidation fixes on PR #1284

- merge origin/main so the branch carries the shipped FA(3) Platnosc
  feature (#1317, b40902e) that the payment docs describe - the docs
  no longer reference an unmerged feature
- repoint 4 inbound links broken by the docs/integrations -> package
  docs/ move (architecture-overview, getting-started, user-guide 02/04)
- Erli README: capability row now matches ErliOfferManagerAdapter's
  implements list (drop OfferLister/OfferQuantityBatchUpdater, add
  OfferStatusReader/OfferStockRestorer/TaxonomyBorrower)
- align the Part 2a heading dash with sibling headings
- woocommerce-walkthrough.mjs: admin password from OL_ADMIN_PASSWORD
  env instead of a hardcoded shared-instance value

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(plans): repoint dangling links after integrations docs move

The docs/integrations -> libs/integrations/<pkg>/docs/ relocation on
PR #1284 left two frozen implementation-plan docs pointing at the old
paths. Repoint them to the current locations.

Addresses the residual N1 finding from the PR #1284 delta re-review.

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(integrations): reconcile README/setup-guide shapes with DTOs; e2e cleanup

Address the consolidated /pr-review + /tech-review findings on PR #1284.

IMPORTANT:
- dpd-polska setup-guide: document payerFid as the required numeric id and
  masterFid as optional, matching DpdConnectionConfigDto.
- ksef setup-guide credentials table: the operator submits { authType, secret }
  (raw token); secretRef is platform-assigned, kept in prose only.
- woocommerce README: OrderProcessorManager supports only OrderFulfillmentUpdater
  (drop FulfillmentStatusReader / DestinationOptionsReader over-claim).
- prestashop README: add ProductPublisher + CategoryProvisioner to prose and
  capability table (manifest declares 6).

Minor:
- woocommerce setup-guide: note HTTPS is required (config validator rejects http).
- ksef README: clarify RegulatoryStatusReader is covered via RegulatoryTransmitter.
- apps/web/e2e: parameterize admin credentials via OL_ADMIN_USERNAME/PASSWORD,
  mark author-local defaults, drop dead VARIANT const, fix stale localStorage log
  message, add apps/web/e2e/README.md documenting these as manual capture scripts.
- erli setup-guide: wire in 00-dashboard/01-connections-list; drop redundant
  orphaned 12-order-detail.png.

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

---------

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants