fix(listings): reject non-UUID path ids with 400 instead of 500 - #1313
Merged
Conversation
Three listings routes forwarded a raw path param straight into a Postgres uuid-column lookup, so a malformed id surfaced as an uncaught QueryFailedError -> 500. Add ParseUUIDPipe at the controller boundary and guard OfferMappingRepository/OfferCreationRecordRepository against driver code 22P02 as defense-in-depth, mirroring the existing ConnectionRepository pattern. Closes #1213 Signed-off-by: jakubret <jakub.retajczyk@blockydevs.com>
piotrswierzy
reviewed
Jul 2, 2026
piotrswierzy
left a comment
Collaborator
There was a problem hiding this comment.
Tech Lead Review — Approve (polish only)
Clean, tightly-scoped fix for the 500-on-malformed-uuid class. Two layers, both correct:
- Interface:
ParseUUIDPipeon the three UUID path params — verified against the route decorators (@Get(':id'),@Get(':id/offer'), andofferCreationRecordIdon the two-param creation-status route). Correctly leavesconnectionIdunguarded on the two-param route, since it's a JS string comparison, never a DB query. - Infrastructure (defense-in-depth): repository guards mirror the
ConnectionRepositoryprecedent verbatim — sameerror instanceof QueryFailedError && 'code' in error && error.code === '22P02'check,findById→null,updateStatus→OfferCreationRecordNotFoundException(domain error thrown from the infra layer, noQueryFailedErrorescapes the port — matches engineering-standards § Error Handling).
Checks I ran:
- Precedent exists and matches (
connection.repository.ts:48-60). - Over-catch is bounded — both guarded
whereclauses filter oneuuidcolumn (id);entityTypeon the offer-mapping query is a hardcoded constant, not user input, so22P02can only come fromid. - Scope is complete: the other
:connectionIdlistings routes resolve throughConnectionRepository, which already carries the22P02guard, so they were never in the 500 class. - No
any. Tests cover both the22P02→ null/throw path and a re-throw-on-different-code regression guard, plus an int-spec exercising the real Nest pipe pipeline (400 on all three routes + 404 regressions).
Suggestions (non-blocking)
- Add
@ApiResponse({ status: 400, ... })to the three fixed routes — they can now return 400 but Swagger doesn't document it (the plan's §8 already flagged this as worth including). GET /listings/:id/offerint-spec asserts only the 400 case;getOfferMappinggets a matching 404 regression butgetMarketplaceOfferdoesn't. A one-line 404-on-valid-absent-UUID assertion would symmetrically lock the unchanged happy path.- Nit (negligible): option-less
ParseUUIDPiperejects UUIDs outside v1-5 (e.g. the nil UUID) that Postgres would accept, shifting such nonsense input 404 → 400. Theoretical only — real ids are all v4.
Verdict: approve once out of draft and the quality gate (pnpm lint && pnpm type-check && pnpm test + the int-spec) is green in CI. Suggestions are optional polish.
jakubretajczykBD
marked this pull request as ready for review
July 2, 2026 11:53
piotrswierzy
approved these changes
Jul 2, 2026
piotrswierzy
left a comment
Collaborator
There was a problem hiding this comment.
Re-review (head 10fc1c8) — Approve
Re-reviewed after the branch went non-draft. The head moved fed8cdaf → 10fc1c8c only via a merge of main — git diff fed8cdaf..HEAD over the listings files is empty, so the controller and both repositories are byte-identical to the version I approved. No regression from the merge.
Verified
- ParseUUIDPipe placement — correct on
getOfferMapping.id,getMarketplaceOffer.id, andgetOfferCreationStatus.offerCreationRecordId. LeavingconnectionIdunguarded is right: it's only used for an in-memory equality check, never a DB lookup, so it can't raise22P02. The int-spec documents this with an explicit 404-fall-through case. - Repository
22P02guards — no over-catch. The narrowerror instanceof QueryFailedError && error.code === '22P02'check, plus the paired "re-throw a QueryFailedError with a different code" tests in both specs, positively prove non-target driver errors still propagate. Null-vs-throw split (read →null, mutate →OfferCreationRecordNotFoundException) matches theConnectionRepositoryprecedent. - Tests — int-spec now also asserts the symmetric 404-on-valid-absent-UUID case for
GET /listings/:id; unit specs cover bothfindByIdandupdateStatuspaths.
Non-blocking polish (carried over, optional)
- The three UUID-guarded GET routes still lack
@ApiResponse({ status: 400 })(the two existing400s are onupdateOfferFields/createOffer). Doc-only. GET /listings/:id/offerint-spec asserts only 400 — the symmetric 404 path is now covered by route 1.ParseUUIDPipeaccepts the nil UUID; harmless given the 400-not-500 goal.
Verdict: Approve. Fix is correct and unchanged; coverage is solid. None of the above blocks merge.
norbert-kulus-blockydevs
added a commit
that referenced
this pull request
Jul 2, 2026
…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>
5 tasks
norbert-kulus-blockydevs
added a commit
that referenced
this pull request
Jul 3, 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>
5 tasks
norbert-kulus-blockydevs
added a commit
that referenced
this pull request
Jul 3, 2026
review) Address the latest #1310 review: - Add a `connectionIdPipe()` (ParseUUIDPipe, 400 on malformed UUID) to the two new bank-account endpoints so a non-UUID path id returns 400 instead of surfacing as a 500 from the DB uuid cast (#1313 bug class). - `encodeURIComponent(accountId)` in `setDefaultBankAccount` so an unexpected id can't inject slashes/`..` into the provider URL. The prior review's edit-path eager-persist and surfaced-sync-failure items were already addressed in an earlier commit on this branch. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
piotrswierzy
pushed a commit
that referenced
this pull request
Jul 3, 2026
…follow-up) (#1310) * feat(infakt): bank-account picker with live inFakt default sync (#1303 follow-up) Adds a BankAccountsReader/BankAccountDefaultSetter sub-capability pair to InvoicingPort so a provider that requires a named bank account on 'transfer' invoices (inFakt) can surface a live picker instead of the operator guessing which account to configure. Backend: - New GET /connections/:id/bank-accounts and POST /connections/:id/bank-accounts/:accountId/default routes, both 501 when the adapter doesn't implement the capability. - InfaktInvoicingAdapter.listBankAccounts() maps inFakt's bank_accounts.json (including inFakt's own `default` flag) to the neutral shape; a new IInfaktHttpClient.put() backs setDefaultBankAccount(), which PUTs { bank_account: { default: true } } to keep inFakt's own "default account" setting in sync whenever the operator picks a non-default one. - issueInvoice/issueCorrection stamp bank_account/bank_name on 'transfer' invoices only when a bankAccount snapshot is configured. Frontend: - Wizard: a post-create step (locked until the connection exists) fetches live accounts and auto-applies whichever inFakt itself marks default; zero accounts forces the payment method back to Cash. - Edit screen: the same live picker inside the existing InlineDisclosure, requiring an explicit operator pick. - Both surfaces call the new default-setter mutation when the operator picks a non-default account, and show a "(default in inFakt)" marker. Verified end-to-end against the real inFakt sandbox API with 2 live bank accounts: switching accounts in OpenLinker's UI genuinely flips inFakt's own default flag. Closes #1303 (follow-up) Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com> * fix(infakt): address #1310 review - eager edit-path persist, surfaced sync failures, string account id, aligned picker surfaces Review follow-ups on the bank-account picker: - IMPORTANT: the edit screen now persists config.bankAccount eagerly on pick (from the server-side config snapshot) and flips inFakt's own default only after that persist succeeds - abandoning the edit form or a failed Save can no longer leave inFakt flipped while OL stamps the old account. The wizard pick path is gated the same way. - IMPORTANT: sync mutations are no longer silent - the default-setter hook surfaces failures via an error toast, and every eager updateConnection call site toasts on rejection. New tests cover the rejecting-mutation paths on both surfaces. - BankAccount id is a string end-to-end now (config snapshot, Zod schema, form state); legacy numeric ids are coerced on read. - The wizard's picker is gated on Transfer like the edit screen, and the duplicated "Payment method" label block is gone. - setDefaultBankAccount documents that inFakt clears the previous default server-side (single PUT, no second call). - Seeded the send_to_ksef fixture in the #1303 spec groups that were missing it after the rebase onto main's inline-KSeF-submit flow. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com> * chore(api): fix import-type lint warning in demo-mode.service.spec Cleanup surfaced by eslint --fix while validating the rebase. Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com> * fix(infakt): validate connectionId as UUID + URL-encode accountId (#1310 review) Address the latest #1310 review: - Add a `connectionIdPipe()` (ParseUUIDPipe, 400 on malformed UUID) to the two new bank-account endpoints so a non-UUID path id returns 400 instead of surfacing as a 500 from the DB uuid cast (#1313 bug class). - `encodeURIComponent(accountId)` in `setDefaultBankAccount` so an unexpected id can't inject slashes/`..` into the provider URL. The prior review's edit-path eager-persist and surfaced-sync-failure items were already addressed in an earlier commit on this branch. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com> * fix(infakt): address #1310 pr-review - wizard payment-method lock, docs sync, 502 mapping - Lock the wizard's payment-method select once the connection is created (post-create picks were never persisted, so both drift directions were reachable); the zero-accounts fallback now also syncs the form control to Cash and keeps its explanation visible outside the Transfer gate. - Sync docs/capabilities.md + architecture-overview.md §14 with the InvoicingPort sub-capabilities: add BankAccountsReader and BankAccountDefaultSetter rows, backfill RegulatoryDocumentReader (#1224), bump the inventory count 31 -> 34. - Map live provider failures on the two bank-account proxy endpoints to 502 (AdapterNotFoundException + live-call errors; provider text logged, never echoed), matching the issuance path's mapping. - Document listBankAccounts' accepted first-page-only v1 scope in the adapter JSDoc. - Surface a hint on the edit screen when the saved bank-account snapshot no longer exists in the live inFakt list. - Make BankAccountDefaultSetter extend BankAccountsReader (genuine is-a, mirroring RegulatoryTransmitter) with a both-methods runtime guard. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com> * fix(infakt): address #1310 review round 2 - misleading copy, pick race, config validation, coverage Addresses all 12 findings from the latest #1310 review: IMPORTANT - Wizard fetch-error copy no longer promises a Cash fallback it never persists; it now states invoices stay on Transfer and may be rejected until an account is picked (finding 1). - Edit-screen zero-accounts copy no longer claims "invoices will use Cash" (that surface never auto-persists); it states the saved method is still Transfer and points to the fix (finding 2). - Zero-accounts forced-Cash downgrade now reverts the optimistic UI flip and shows a dedicated toast when its persist fails, instead of leaving a locked Cash UI contradicting the server (finding 3). - Both picker Selects disable while a persist/flip is in flight, closing the double-pick race that could diverge OL vs inFakt defaults (finding 4). - The inFakt config-shape validator now validates config.bankAccount (object with id string|number + non-empty accountNumber/bankName), so a malformed snapshot 400s at save time instead of a 422 at issuance (finding 5). - The edit-path seam is now tested: readInfaktBankAccount hydration (incl. legacy numeric-id coercion + shape rejection) and the mergeStructuredIntoConfig infaktBankAccount clause (finding 6). SUGGESTION - The wizard auto-apply fallback (no account flagged default) now also flips inFakt's default instead of only persisting OL's snapshot (finding 7). - Documented the accepted unsaved-form-state pick behavior in the edit section header (finding 8). - Extracted the duplicated persist-then-flip choreography into a shared usePickBankAccount hook used by both surfaces (finding 9). - Added a placeholder option to the wizard Select (finding 10). - Added the missing edit-screen fetch-error test (finding 11). - Added an accountId non-empty param pipe on the default-setter endpoint (finding 12). 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
* 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
…follow-up) (#1310) * feat(infakt): bank-account picker with live inFakt default sync (#1303 follow-up) Adds a BankAccountsReader/BankAccountDefaultSetter sub-capability pair to InvoicingPort so a provider that requires a named bank account on 'transfer' invoices (inFakt) can surface a live picker instead of the operator guessing which account to configure. Backend: - New GET /connections/:id/bank-accounts and POST /connections/:id/bank-accounts/:accountId/default routes, both 501 when the adapter doesn't implement the capability. - InfaktInvoicingAdapter.listBankAccounts() maps inFakt's bank_accounts.json (including inFakt's own `default` flag) to the neutral shape; a new IInfaktHttpClient.put() backs setDefaultBankAccount(), which PUTs { bank_account: { default: true } } to keep inFakt's own "default account" setting in sync whenever the operator picks a non-default one. - issueInvoice/issueCorrection stamp bank_account/bank_name on 'transfer' invoices only when a bankAccount snapshot is configured. Frontend: - Wizard: a post-create step (locked until the connection exists) fetches live accounts and auto-applies whichever inFakt itself marks default; zero accounts forces the payment method back to Cash. - Edit screen: the same live picker inside the existing InlineDisclosure, requiring an explicit operator pick. - Both surfaces call the new default-setter mutation when the operator picks a non-default account, and show a "(default in inFakt)" marker. Verified end-to-end against the real inFakt sandbox API with 2 live bank accounts: switching accounts in OpenLinker's UI genuinely flips inFakt's own default flag. Closes #1303 (follow-up) Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com> * fix(infakt): address #1310 review - eager edit-path persist, surfaced sync failures, string account id, aligned picker surfaces Review follow-ups on the bank-account picker: - IMPORTANT: the edit screen now persists config.bankAccount eagerly on pick (from the server-side config snapshot) and flips inFakt's own default only after that persist succeeds - abandoning the edit form or a failed Save can no longer leave inFakt flipped while OL stamps the old account. The wizard pick path is gated the same way. - IMPORTANT: sync mutations are no longer silent - the default-setter hook surfaces failures via an error toast, and every eager updateConnection call site toasts on rejection. New tests cover the rejecting-mutation paths on both surfaces. - BankAccount id is a string end-to-end now (config snapshot, Zod schema, form state); legacy numeric ids are coerced on read. - The wizard's picker is gated on Transfer like the edit screen, and the duplicated "Payment method" label block is gone. - setDefaultBankAccount documents that inFakt clears the previous default server-side (single PUT, no second call). - Seeded the send_to_ksef fixture in the #1303 spec groups that were missing it after the rebase onto main's inline-KSeF-submit flow. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com> * chore(api): fix import-type lint warning in demo-mode.service.spec Cleanup surfaced by eslint --fix while validating the rebase. Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com> * fix(infakt): validate connectionId as UUID + URL-encode accountId (#1310 review) Address the latest #1310 review: - Add a `connectionIdPipe()` (ParseUUIDPipe, 400 on malformed UUID) to the two new bank-account endpoints so a non-UUID path id returns 400 instead of surfacing as a 500 from the DB uuid cast (#1313 bug class). - `encodeURIComponent(accountId)` in `setDefaultBankAccount` so an unexpected id can't inject slashes/`..` into the provider URL. The prior review's edit-path eager-persist and surfaced-sync-failure items were already addressed in an earlier commit on this branch. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com> * fix(infakt): address #1310 pr-review - wizard payment-method lock, docs sync, 502 mapping - Lock the wizard's payment-method select once the connection is created (post-create picks were never persisted, so both drift directions were reachable); the zero-accounts fallback now also syncs the form control to Cash and keeps its explanation visible outside the Transfer gate. - Sync docs/capabilities.md + architecture-overview.md §14 with the InvoicingPort sub-capabilities: add BankAccountsReader and BankAccountDefaultSetter rows, backfill RegulatoryDocumentReader (#1224), bump the inventory count 31 -> 34. - Map live provider failures on the two bank-account proxy endpoints to 502 (AdapterNotFoundException + live-call errors; provider text logged, never echoed), matching the issuance path's mapping. - Document listBankAccounts' accepted first-page-only v1 scope in the adapter JSDoc. - Surface a hint on the edit screen when the saved bank-account snapshot no longer exists in the live inFakt list. - Make BankAccountDefaultSetter extend BankAccountsReader (genuine is-a, mirroring RegulatoryTransmitter) with a both-methods runtime guard. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com> * fix(infakt): address #1310 review round 2 - misleading copy, pick race, config validation, coverage Addresses all 12 findings from the latest #1310 review: IMPORTANT - Wizard fetch-error copy no longer promises a Cash fallback it never persists; it now states invoices stay on Transfer and may be rejected until an account is picked (finding 1). - Edit-screen zero-accounts copy no longer claims "invoices will use Cash" (that surface never auto-persists); it states the saved method is still Transfer and points to the fix (finding 2). - Zero-accounts forced-Cash downgrade now reverts the optimistic UI flip and shows a dedicated toast when its persist fails, instead of leaving a locked Cash UI contradicting the server (finding 3). - Both picker Selects disable while a persist/flip is in flight, closing the double-pick race that could diverge OL vs inFakt defaults (finding 4). - The inFakt config-shape validator now validates config.bankAccount (object with id string|number + non-empty accountNumber/bankName), so a malformed snapshot 400s at save time instead of a 422 at issuance (finding 5). - The edit-path seam is now tested: readInfaktBankAccount hydration (incl. legacy numeric-id coercion + shape rejection) and the mergeStructuredIntoConfig infaktBankAccount clause (finding 6). SUGGESTION - The wizard auto-apply fallback (no account flagged default) now also flips inFakt's default instead of only persisting OL's snapshot (finding 7). - Documented the accepted unsaved-form-state pick behavior in the edit section header (finding 8). - Extracted the duplicated persist-then-flip choreography into a shared usePickBankAccount hook used by both surfaces (finding 9). - Added a placeholder option to the wizard Select (finding 10). - Added the missing edit-screen fetch-error test (finding 11). - Added an accountId non-empty param pipe on the default-setter endpoint (finding 12). 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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Related issues
Closes #1213
Test plan
Quality gate
pnpm lintpasses (zero errors)pnpm type-checkpasses (zero errors)pnpm testpasses (all unit tests green)pnpm test:integrationpasses — neededonly if you touched
apps/api/test/integration/**or any plugin'sinfrastructure/adapters/.By submitting this pull request, I confirm that my contributions
are made under the terms of the Apache License 2.0, and I
certify the Developer Certificate of Origin
by signing off my commits.