docs(infakt): ADR-030 KSeF indirection model + operator setup guide#1307
docs(infakt): ADR-030 KSeF indirection model + operator setup guide#1307norbert-kulus-blockydevs wants to merge 16 commits into
Conversation
piotrswierzy
left a comment
There was a problem hiding this comment.
Tech-lead review (docs-only, reviewed against stacked base 1282-infakt-fe-plugin; diff = origin/1282...origin/1283).
Overall a well-structured docs set: ADR-030 is the next-free number, conforms to the ADR template (all required sections present), the ADR-README index row is added, all 20 referenced screenshots are committed and their paths resolve, relative links resolve, and no secrets are leaked. Adapter facts largely check out against the merged code (adapterKey infakt.accounting.v1, platformType infakt, displayName Infakt Accounting API v3, InvoicingPort + RegulatoryStatusReader + CorrectionIssuer with RegulatoryTransmitter deliberately absent, the ksef_data.status → RegulatoryStatus mapping, document types, gross→net + before/after correction rows).
One blocking accuracy issue plus a merge-ordering constraint:
🔴 BLOCKING — ADR-030's "no submit primitive" premise is contradicted by the shipped adapter and the POC it cites.
The ADR states "There is nothing for OL to submit … OL has no submit primitive to call at all" and that inFakt "auto-triggers KSeF submission the moment POST /invoices.json succeeds (confirmed live on sandbox, #1274: … zero further OL action)." But:
infakt-invoicing.adapter.tsships a publicsendToKsef(invoiceUuid)→POST invoices/{uuid}/send_to_ksef.json.- The cited POC (
scripts/poc-sandbox-test.ts) runsissueInvoice → getInvoice → getClearanceStatus ("pre-submit clearance") → sendToKsef ("STEP 5 — trigger KSeF submission") → cleared— it explicitly invokes a submit primitive and checks a pre-submit status, rather than proving "zero further OL action." - The Alternatives section says the OL-driven-submission fallback is "not modeled in code," yet
sendToKsefmodels exactly that. (Note: #1293's rebased adapter now callssendToKsefinline at issuance — so OL does drive submission.)
The decision (don't expose RegulatoryTransmitter) is probably still right, but the justification as written will mislead a maintainer who greps and finds sendToKsef. Please reword: OL retains an out-of-port sendToKsef trigger and does not surface it as a RegulatoryTransmitter method because clearance timing/status ownership stays with inFakt — rather than "there is no submit primitive."
🟠 IMPORTANT — merge ordering. ADR-030 and the README reference InfaktInboundWebhookDecoderAdapter, which does not exist on this PR's base (1282) — it lives on 1281-infakt-plugin-registration-webhook (#1293), and 1281 is not an ancestor of 1282. The docs correctly describe the merged end-state, but this PR can't land until #1293 and the 1282 base are on main. Confirm the class names still match after the stack collapses.
🟡 SUGGESTION — setup-guide §3 step 3 says the invoice "updates to accepted". The adapter historically mapped success → cleared; #1293 remaps it to accepted. Reconcile the guide with whichever value ships, and keep it consistent with ADR-030.
🟡 SUGGESTION — README names both InfaktWebhookTranslator and InfaktInboundWebhookDecoderAdapter; the final (#1293) code has three distinct webhook classes. Tighten the prose to name which class does what.
Draft looks close — main ask is the ADR premise reword; the rest is post-stack verification.
0012af0 to
d441dbe
Compare
…hots Part of the E2E evidence trail for PR #1300 / issue #1282: inFakt sandbox dashboard login, API key generation page, and the webhook creation flow (list + new-webhook form). Captured manually against the real inFakt sandbox dashboard, no secrets visible in frame. Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
…KOR, list) Captured against the real inFakt sandbox on a temporary paired stack (this branch's web build + the 1281-infakt-plugin-registration-webhook backend branch), confirming the full connect -> issue -> KSeF-clearance -> correction flow works end to end through OpenLinker: - 00-05: guided connection setup, Test connection (passing, after the ConnectionTesterPort fix landed on PR #1293), connections list - 12: invoice accepted with real KSeF clearance number (8201194127-20260701-A5797F400000-ED) - 14-16: KOR correction flow, also cleared by the real sandbox - 17: invoices list showing the original + correction, both accepted - if1-if4: manual inFakt-dashboard screenshots (API key, webhooks) Known gaps (tracked, not blocking): - 13-invoice-detail-page was captured via the /invoices/:invoiceId page, but that GET route doesn't exist yet on the 1281 backend branch's base main snapshot — will be trivial to recapture once #1292/#1293 land on current main. - The not-issued / submitted (pending) order-detail states aren't captured cleanly yet — the seeded test order is now terminal (accepted) for this connection; a second seeded order would be needed for a clean before/during capture. Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
…ng on this backend branch, not blocking) Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
…rified Re-ran the full sandbox walkthrough on a fresh order (PLN 189.00) after the groszy/decimal-string fix landed on PR #1293 (651cac2). Confirmed against inFakt's raw API response that amounts now round-trip exactly (gross_price: 18900 groszy = PLN 189.00) and the KOR correction combines original + corrected lines correctly (189.00 + 99.99 = 288.99 PLN). Also adds the invoice-detail-page screenshot (13) that 404'd on a stale branch snapshot last time — the route works fine now. Replaces the earlier PLN 349.00 test order's screenshots, which (correctly, at the time) showed the ~100x-low amounts that led to the money-format bug report on PR #1293. Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
…ssets/ Aligns with the convention established in PR #1284 (KSeF/Subiekt tutorials) — screenshot assets live inside the integration package's own docs/assets/, not a root-level docs/assets/<provider>/ directory. Updates the two e2e scripts' output path accordingly. Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
… 6 confirmation - 08-orders-list.png: clean, filtered (sourceConnectionId + createdFrom) view of just the seeded test orders — the unfiltered list is too cluttered with other sessions' dev-DB fixtures to be tutorial-usable. - 10/11: genuinely clean not-issued -> submitted transition, captured without a page reload wiping the connection-picker selection (fixed infakt-invoice.mjs to shoot the submitted state before any reload). - if5-infakt-invoice-confirmed.png: fresh manual inFakt-dashboard screenshot confirming the money-format fix (9/07/2026 = PLN 189.00, 10/07/2026 correction = PLN 288.99), replacing the pre-fix evidence. - 13/17 updated: also documents the KSeF cleared-vs-accepted mapping bug found this run (flagged on PR #1293) — two rows show "KSeF: CLEARING" (the real reconcile job's output, unpatched) alongside two "KSeF: ACCEPTED" rows from earlier runs where I'd manually corrected the DB while chasing the money bug. Will re-capture once the accepted-mapping fix lands. Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
…erified Fourth fresh sandbox order (PLN 259.00), confirming both backend fixes together: gross_price round-trips exactly (25900 groszy = PLN 259.00), and the invoice now shows "KSeF: ACCEPTED" with a real clearance reference chip instead of getting stuck on "KSeF: CLEARING" forever. Correction verified too (259.00 + 99.99 = 358.99 PLN exact, gross_price 35899 groszy). Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
Documents why InfaktInvoicingAdapter implements RegulatoryStatusReader (not RegulatoryTransmitter) — inFakt auto-submits to KSeF on its own, so OL only ever reads clearance status back. Adds the operator setup guide (connection creation, webhook configuration, troubleshooting), the package README the infakt adapter was missing, and the architecture-overview.md provider entry. Part of #1279. Closes #1283. Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
9de602f to
b0d61a9
Compare
…tup guide Live-E2E-verified against the inFakt sandbox (2026-07-03): - section 1: Default payment method wizard field + Transfer bank-account behaviour (live picker on the Edit form, eager persist, inFakt default sync) with fresh wizard + edit-form screenshots - section 3: Download the invoice PDF step (rendered PDF via RegulatoryDocumentReader, #1321) - corrections: issuance-time line snapshot note (#1297) - README: BankAccountsReader / BankAccountDefaultSetter / RegulatoryDocumentReader notes + implementation details - capability-panel screenshot (post-#1320) Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
piotrswierzy
left a comment
There was a problem hiding this comment.
/pr-review — systematic review
Verdict: ❌ Request changes (consistent with draft status). Well-researched docs PR — ADR structure, link hygiene, and most technical claims check out against the code — but four accuracy defects need fixing before merge. All are small and mechanical.
Stacking status verified: the PR now targets main and the diff contains only docs (6 markdown files + 12 PNGs, no stacked code); the prereqs (#1293/#1300/#1309) have merged. Branch is behind main (#1297/#1329 landed since) — needs an update, no conflict expected.
🟡 IMPORTANT
-
docs/architecture/adrs/030-infakt-ksef-indirection.md:43— wrong terminal status mapping. The Decision section mapsksef_data.statusontoRegulatoryStatusassubmitted | submitted | cleared | rejected. The shipped adapter (infakt-invoicing.adapter.ts:73-81) mapssuccess → 'accepted'— and its own comment documents that aclearedmapping was a past bug. The ADR is the durable record and would re-canonize the exact mistake the code warns against; it also contradicts the setup guide's own verification step 3 ("updates toaccepted"). One-word fix:submitted | submitted | accepted | rejected. -
Setup guide (Capabilities table :23, bank-picker paragraphs ~:76-90) + README (capabilities row, #1310 bullet) — documents unmerged functionality as shipped.
BankAccountsReader/BankAccountDefaultSetterdon't exist on main — the adapter implementsInvoicingPort, RegulatoryStatusReader, CorrectionIssuer, RegulatoryDocumentReaderonly, andInfaktConnectionConfighas nobankAccountfield. The bank-picker walkthrough (with screenshots 06/07) describes UI an operator on main cannot find. Either sequence this PR after #1310 merges, or strip the bank-account rows/paragraphs/screenshots into #1310's own docs. -
Setup guide § 2, step 5 (~:128) — the OL-side "Rotate webhook secret" affordance does not exist in the FE. The backend endpoint exists (
POST /connections/:id/webhooks/secret/rotate, secret shown once — that part is accurate), but a repo-wide search ofapps/web/srcfinds no caller and no such label; the inFakt credentials-panel rotate affordance rotates the API key, not the webhook secret. An operator following the step hits a dead end. Document the API call (curl/HTTP snippet) until an FE affordance ships, or land that affordance first. -
Setup guide § 2, step 5 vs its own screenshot — the primary secret-exchange instruction is contradicted by
if4-infakt-webhook-form.png. The embedded screenshot of inFakt's "Nowy webhook" form shows no secret field at all (URL, description, payload option, event checkboxes, account password). The guide instructs "Paste that same value into inFakt's webhook subscription secret field" as the primary path. The PR body honestly flags this as unverified — good — but the guide text asserts as fact a UI element its own screenshot disproves. Invert the framing: lead with what the screenshot shows, state the exchange direction is unverified, keep the fallback as the likely path (or verify against inFakt's webhook docs first).
🟢 SUGGESTIONS
libs/integrations/infakt/README.md(~:40) — the Config JSON block omitsdefaultPaymentMethod, whichInfaktConnectionConfigcarries since #1309; add it so the documented shape matches the type.docs/plans/implementation-plan-infakt-docs-adr.md— plan constraints are stale (still says the PR targets1282-infakt-fe-plugin, that #1281/#1282 are open, "no new screenshots" while four are added). A one-paragraph retarget addendum would prevent confusion.
✅ Verified-correct
- ADR-030 matches the ADR template exactly (header, Context, Decision, 3 Alternatives with rejection rationale, Consequences, References) and is indexed in the ADR README.
- All 24 screenshot references resolve (19 pre-existing from #1300, 4 new, + platform picker); all relative links and anchors resolve, including ADR-026's
#amendments. - Manifest facts accurate:
infakt.accounting.v1, platformTypeinfakt, capabilityInvoicing. - Core claims coherent:
RegulatoryStatusReader-not-RegulatoryTransmittermatches the code; document types,X-Infakt-SignatureHMAC +verification_codehandshake +send_to_ksef_*allowlist all match;RegulatoryDocumentReader(#1321) is on main; thenot-applicable-forever consequence matchestoRegulatoryStatus(null). - No credentials leaked — placeholders only; screenshots show masked fields.
- The known-gap honesty (webhook-secret direction) is the right posture — only the primary-path framing overreaches (finding 4).
… reality check - ADR-030: fix ksef_data.status mapping (success -> accepted, not cleared) - the ADR was re-canonizing exactly the bug the shipped adapter comment warns against - bank-picker docs (BankAccountsReader/BankAccountDefaultSetter, #1310) now describe a MERGED feature - #1310 landed to main 2026-07-03, after Piotr's review; no doc changes needed, kept as-is post branch update - setup-guide step 5: replace the nonexistent "Rotate webhook secret" FE button with the actual API call (curl snippet against POST /v1/connections/:id/webhooks/secret/rotate); invert the secret-exchange framing to lead with what if4's screenshot actually shows (no secret field) and mark the paste-into-inFakt direction explicitly unverified - README: add defaultPaymentMethod + bankAccount to the Config JSON example (InfaktConnectionConfig carries both since #1309/#1310) - plan: retarget addendum noting the branch now targets main with all prereqs merged - merged origin/main (branch was behind #1297/#1329/#1310/#1320/#1331) Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
|
Pushed 999dda5 addressing all four IMPORTANT findings + both suggestions:
Also: README config example now includes |
The earlier review-fix commit (999dda5) addressed the /pr-review's four numbered findings but missed the tech-lead draft review's BLOCKING finding: ADR-030, the setup guide, and the README all claimed OL "has no submit primitive to call" and that inFakt "auto-triggers" KSeF submission on its own. The shipped adapter's own docstring says otherwise: an inFakt draft does NOT auto-submit on its own, so issueInvoice/issueCorrection call send_to_ksef.json explicitly and inline - verified live 2026-07-01. Reworded throughout to the framing the reviewer suggested: OL retains an out-of-port sendToKsef trigger, not surfaced as RegulatoryTransmitter, because clearance timing and status ownership stay with inFakt - not because there is nothing for OL to call. Also disambiguates the three webhook classes in the README (InfaktWebhookTranslator, InfaktInboundWebhookDecoderAdapter, InfaktWebhookEventTranslatorAdapter), per the same draft review's suggestion, which the prior commit also left unaddressed. Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
|
Pushed e304219, addressing the tech-lead draft review's BLOCKING finding that the earlier fix commit (999dda5) missed. The draft review flagged that ADR-030's premise — "there is nothing for OL to submit ... zero further OL action" — is contradicted by the shipped adapter: Reworded throughout, following the reviewer's suggested framing verbatim: OL retains an out-of-port
Everything from the second (/pr-review) pass — the |
Combined review:
|
piotrswierzy
left a comment
There was a problem hiding this comment.
/pr-review — delta re-review (161ab4af → e30421988, docs-only)
All four findings from my earlier Request-changes are resolved. Verified against the actual files, not the PR body.
- ADR-030 wrong terminal status mapping → RESOLVED. Now reads
submitted | submitted | accepted | rejectedwith explicit prose "successmaps to the terminalacceptedstate, notcleared" — matches the shipped adapter (infakt-invoicing.adapter.tscase 'success': return 'accepted'). - #1310 bank-account functionality documented as shipped → MOOT (now merged). #1310 landed on main, the branch merged it, and the adapter implements the six capabilities the README table lists (
InvoicingPort, RegulatoryStatusReader, CorrectionIssuer, RegulatoryDocumentReader, BankAccountsReader, BankAccountDefaultSetter). Docs match main. - Nonexistent "Rotate webhook secret" FE affordance → RESOLVED. Replaced with the real API call
POST /v1/connections/{id}/webhooks/secret/rotate(confirmed atconnection.controller.ts:223; response DTO matches). The doc's claim that there's no inFakt-connection FE webhook-secret affordance (unlike PrestaShop/Erli) is accurate. - Secret-exchange framing contradicted by its own screenshot → RESOLVED. Step 5 now leads with "inFakt's 'Nowy webhook' form has no secret field" (matching
if4-…) and the Known Gap honestly discloses OL can't set its secret to inFakt's auto-generated value.
Also verified: README config JSON (baseUrl/defaultPaymentMethod/bankAccount{id,accountNumber,bankName}) matches InfaktConnectionConfig exactly and correctly excludes apiKey (a credential) — no config/secret drift; ADR-030 indexed as Accepted; asset links resolve; no unredacted secrets in text.
🟢 One minor wording nit (non-blocking)
Setup guide step 5 (~lines 129-130) still carries an actionable imperative — "set it as OL's webhook secret so X-Infakt-Signature verification matches" — while the Known Gap block immediately below says there's currently no mechanism to do that. The gap is disclosed adjacently, but soften the imperative (e.g. "you would set it — but note there's currently no mechanism; see Known Gap") so it doesn't tell the operator to do something the next paragraph calls impossible.
Verdict: ✅ Approve (docs-only). All Request-changes findings addressed; the wording nit is optional.
PR #1307 already claims ADR-030 for the inFakt/KSeF indirection model. Renumbering ours to the next free slot before it merges. Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
…ible signal connection.supportedCapabilities turned out to be a static, per-adapterKey manifest value rather than computed per-connection-instance, so it can't distinguish an Erli connection with configured Allegro app credentials from one without. Add a non-secret ErliConnectionConfig.allegroCategoryAccessEnabled boolean for the frontend to read instead (see ADR-031 "Correction"); the write path that sets allegroClientId/allegroClientSecret must set/clear this flag atomically (tracked in #1384's scope). Also fixes stale ADR-030 references (renumbered to ADR-031 to avoid a collision with #1307) in allegro-category-catalog-client.ts and erli-connection.types.ts. Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
…lient-credentials token (#1380) * docs(plan): Erli category/parameter browsing via Erli-owned Allegro client-credentials token Implementation plan + ADR-030 for letting the Erli offer wizard browse Allegro's public category/parameter catalog (client_credentials grant) without requiring the operator to connect a real Allegro seller account. Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com> * docs(plan): renumber ADR-030 to ADR-031 to avoid collision with PR #1307 PR #1307 already claims ADR-030 for the inFakt/KSeF indirection model. Renumbering ours to the next free slot before it merges. Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com> * docs(plan): correct FE gating signal — supportedCapabilities is static, use config flag #1383 implementation discovered connection.supportedCapabilities is a static, per-adapterKey manifest value, not computed per-connection instance — it can't distinguish configured vs unconfigured Erli connections. Corrected ADR-031 and the plan to use a new non-secret ErliConnectionConfig.allegroCategoryAccessEnabled boolean instead, written/cleared alongside the Allegro credential pair. Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com> --------- Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
… credential/config validation (#1399) * feat(erli): wire per-connection Allegro category-catalog capability + credential/config validation (#1383) ErliOfferManagerAdapter now wires fetchCategories/fetchCategoryParameters as optional instance properties (never a static implements clause) so isCategoryBrowser/isCategoryParametersReader reflect whether a specific Erli connection has configured a valid Allegro app credential pair, per ADR-031. ErliAdapterFactory constructs the shared AllegroCategoryCatalogClient only when both allegroClientId and allegroClientSecret resolve to non-empty strings, resolving config.allegroEnvironment (default 'production'). The credentials shape validator now enforces "both or neither" for the Allegro credential pair, and the config shape validator restricts allegroEnvironment to 'sandbox' | 'production'. Verified CategoriesCacheService and ListingsController need no changes: both already resolve capabilities generically via getCapabilityAdapter + is* type guards, so a misconfigured Erli connection behaves like today's "adapter doesn't implement this capability" case. Added an integration test exercising the real ErliAdapterFactory + ErliOfferManagerAdapter + AllegroCategoryCatalogClient (fetch stubbed) through the production adapter-resolution seam and the real HTTP endpoint, covering configured / unconfigured / partially-configured connections. Confirmed the existing #1367 bulk-wizard capability-gate test passes unmodified. Part of #1381, sub-task 2 of 4 (depends on #1382, merged; see also #1384, #1387). Signed-off-by: Norbert Kulus <42zeroo@gmail.com> Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com> * fix(erli): add allegroCategoryAccessEnabled config flag as the FE-visible signal connection.supportedCapabilities turned out to be a static, per-adapterKey manifest value rather than computed per-connection-instance, so it can't distinguish an Erli connection with configured Allegro app credentials from one without. Add a non-secret ErliConnectionConfig.allegroCategoryAccessEnabled boolean for the frontend to read instead (see ADR-031 "Correction"); the write path that sets allegroClientId/allegroClientSecret must set/clear this flag atomically (tracked in #1384's scope). Also fixes stale ADR-030 references (renumbered to ADR-031 to avoid a collision with #1307) in allegro-category-catalog-client.ts and erli-connection.types.ts. Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com> * fix(erli): hoist Allegro category-token cache to host.cache + dedupe credential resolve (#1399 review) AllegroCategoryCatalogClient built a fresh in-memory token cache per adapter instance, but ErliAdapterFactory builds a fresh instance per getCapabilityAdapter call — so fetchCategoryParameters paid a full client_credentials OAuth round-trip on every request. The client now persists the acquired token in the optional CachePort (host.cache), keyed by clientId, with a TTL matching the token's remaining lifetime, so it survives across per-request instances and processes. Also collapses ErliAdapterFactory.createAdapters's two credentialsResolver.get calls (one for apiKey, one for the Allegro pair) into one shared resolve. Addresses both IMPORTANT findings from the PR #1399 review round (Piotr's token-cache note and the self-review's double-resolve finding). Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com> --------- Signed-off-by: Norbert Kulus <42zeroo@gmail.com> Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
… credential/config validation (#1399) * feat(erli): wire per-connection Allegro category-catalog capability + credential/config validation (#1383) ErliOfferManagerAdapter now wires fetchCategories/fetchCategoryParameters as optional instance properties (never a static implements clause) so isCategoryBrowser/isCategoryParametersReader reflect whether a specific Erli connection has configured a valid Allegro app credential pair, per ADR-031. ErliAdapterFactory constructs the shared AllegroCategoryCatalogClient only when both allegroClientId and allegroClientSecret resolve to non-empty strings, resolving config.allegroEnvironment (default 'production'). The credentials shape validator now enforces "both or neither" for the Allegro credential pair, and the config shape validator restricts allegroEnvironment to 'sandbox' | 'production'. Verified CategoriesCacheService and ListingsController need no changes: both already resolve capabilities generically via getCapabilityAdapter + is* type guards, so a misconfigured Erli connection behaves like today's "adapter doesn't implement this capability" case. Added an integration test exercising the real ErliAdapterFactory + ErliOfferManagerAdapter + AllegroCategoryCatalogClient (fetch stubbed) through the production adapter-resolution seam and the real HTTP endpoint, covering configured / unconfigured / partially-configured connections. Confirmed the existing #1367 bulk-wizard capability-gate test passes unmodified. Part of #1381, sub-task 2 of 4 (depends on #1382, merged; see also #1384, #1387). Signed-off-by: Norbert Kulus <42zeroo@gmail.com> Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com> * fix(erli): add allegroCategoryAccessEnabled config flag as the FE-visible signal connection.supportedCapabilities turned out to be a static, per-adapterKey manifest value rather than computed per-connection-instance, so it can't distinguish an Erli connection with configured Allegro app credentials from one without. Add a non-secret ErliConnectionConfig.allegroCategoryAccessEnabled boolean for the frontend to read instead (see ADR-031 "Correction"); the write path that sets allegroClientId/allegroClientSecret must set/clear this flag atomically (tracked in #1384's scope). Also fixes stale ADR-030 references (renumbered to ADR-031 to avoid a collision with #1307) in allegro-category-catalog-client.ts and erli-connection.types.ts. Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com> * fix(erli): hoist Allegro category-token cache to host.cache + dedupe credential resolve (#1399 review) AllegroCategoryCatalogClient built a fresh in-memory token cache per adapter instance, but ErliAdapterFactory builds a fresh instance per getCapabilityAdapter call — so fetchCategoryParameters paid a full client_credentials OAuth round-trip on every request. The client now persists the acquired token in the optional CachePort (host.cache), keyed by clientId, with a TTL matching the token's remaining lifetime, so it survives across per-request instances and processes. Also collapses ErliAdapterFactory.createAdapters's two credentialsResolver.get calls (one for apiKey, one for the Allegro pair) into one shared resolve. Addresses both IMPORTANT findings from the PR #1399 review round (Piotr's token-cache note and the self-review's double-resolve finding). Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com> --------- Signed-off-by: Norbert Kulus <42zeroo@gmail.com> Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
… credential/config validation (#1399) * feat(erli): wire per-connection Allegro category-catalog capability + credential/config validation (#1383) ErliOfferManagerAdapter now wires fetchCategories/fetchCategoryParameters as optional instance properties (never a static implements clause) so isCategoryBrowser/isCategoryParametersReader reflect whether a specific Erli connection has configured a valid Allegro app credential pair, per ADR-031. ErliAdapterFactory constructs the shared AllegroCategoryCatalogClient only when both allegroClientId and allegroClientSecret resolve to non-empty strings, resolving config.allegroEnvironment (default 'production'). The credentials shape validator now enforces "both or neither" for the Allegro credential pair, and the config shape validator restricts allegroEnvironment to 'sandbox' | 'production'. Verified CategoriesCacheService and ListingsController need no changes: both already resolve capabilities generically via getCapabilityAdapter + is* type guards, so a misconfigured Erli connection behaves like today's "adapter doesn't implement this capability" case. Added an integration test exercising the real ErliAdapterFactory + ErliOfferManagerAdapter + AllegroCategoryCatalogClient (fetch stubbed) through the production adapter-resolution seam and the real HTTP endpoint, covering configured / unconfigured / partially-configured connections. Confirmed the existing #1367 bulk-wizard capability-gate test passes unmodified. Part of #1381, sub-task 2 of 4 (depends on #1382, merged; see also #1384, #1387). Signed-off-by: Norbert Kulus <42zeroo@gmail.com> Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com> * fix(erli): add allegroCategoryAccessEnabled config flag as the FE-visible signal connection.supportedCapabilities turned out to be a static, per-adapterKey manifest value rather than computed per-connection-instance, so it can't distinguish an Erli connection with configured Allegro app credentials from one without. Add a non-secret ErliConnectionConfig.allegroCategoryAccessEnabled boolean for the frontend to read instead (see ADR-031 "Correction"); the write path that sets allegroClientId/allegroClientSecret must set/clear this flag atomically (tracked in #1384's scope). Also fixes stale ADR-030 references (renumbered to ADR-031 to avoid a collision with #1307) in allegro-category-catalog-client.ts and erli-connection.types.ts. Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com> * fix(erli): hoist Allegro category-token cache to host.cache + dedupe credential resolve (#1399 review) AllegroCategoryCatalogClient built a fresh in-memory token cache per adapter instance, but ErliAdapterFactory builds a fresh instance per getCapabilityAdapter call — so fetchCategoryParameters paid a full client_credentials OAuth round-trip on every request. The client now persists the acquired token in the optional CachePort (host.cache), keyed by clientId, with a TTL matching the token's remaining lifetime, so it survives across per-request instances and processes. Also collapses ErliAdapterFactory.createAdapters's two credentialsResolver.get calls (one for apiKey, one for the Allegro pair) into one shared resolve. Addresses both IMPORTANT findings from the PR #1399 review round (Piotr's token-cache note and the self-review's double-resolve finding). Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com> --------- Signed-off-by: Norbert Kulus <42zeroo@gmail.com> Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
…t a required Allegro connection (#1407) * feat(erli): Erli-owned Allegro client_credentials category-catalog client (#1388) * feat(erli): add Erli-owned Allegro client_credentials category-catalog client Adds AllegroCategoryCatalogClient, a self-contained HTTP client that lets an Erli connection browse Allegro's public /sale/categories and /sale/categories/{id}/parameters catalog via an Allegro app's grant_type=client_credentials token, with no dependency on @openlinker/integrations-allegro (per ADR-030) and no requirement for the operator to own a real Allegro seller connection. Extends ErliConnectionConfig with an optional allegroEnvironment field and ErliCredentials with optional allegroClientId/allegroClientSecret fields to carry the Allegro app credentials alongside Erli's own apiKey. Part of #1381, sub-task 1 of 3 (see #1383, #1384). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RpsVFTpKZ1HoowEUKzbnWu Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com> * fix(erli): address PR #1388 review findings on AllegroCategoryCatalogClient - Extract inline wire-shape types to allegro-category-catalog-client.types.ts, matching the erli-http-client.ts / erli-http-client.types.ts split. - Add a single-flight guard around token acquisition so two concurrent fetchCategories/fetchCategoryParameters calls on a cold cache issue one grant_type=client_credentials request, not two. - Treat a token response missing expires_in as already-expired instead of caching it indefinitely. - Add tests for the 401/403 branch on a data call (categories/parameters), distinct from the token-endpoint rejection branch already covered. - Add a cross-plugin mapper parity test suite that runs Allegro's real sandbox category-parameters fixture through this client's copy of toNeutralCategoryParameter, mirroring assertions from allegro-category-parameter.mapper.spec.ts, with cross-referencing comments in both spec files so a future mapper change prompts updating both. - Replace the inline 'sandbox' | 'production' union with an as const + runtime-array AllegroCatalogEnvironment type in erli-connection.types.ts, matching AllegroConnectionConfig's own convention. 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 5 <noreply@anthropic.com> * feat(erli): wire per-connection Allegro category-catalog capability + credential/config validation (#1399) * feat(erli): wire per-connection Allegro category-catalog capability + credential/config validation (#1383) ErliOfferManagerAdapter now wires fetchCategories/fetchCategoryParameters as optional instance properties (never a static implements clause) so isCategoryBrowser/isCategoryParametersReader reflect whether a specific Erli connection has configured a valid Allegro app credential pair, per ADR-031. ErliAdapterFactory constructs the shared AllegroCategoryCatalogClient only when both allegroClientId and allegroClientSecret resolve to non-empty strings, resolving config.allegroEnvironment (default 'production'). The credentials shape validator now enforces "both or neither" for the Allegro credential pair, and the config shape validator restricts allegroEnvironment to 'sandbox' | 'production'. Verified CategoriesCacheService and ListingsController need no changes: both already resolve capabilities generically via getCapabilityAdapter + is* type guards, so a misconfigured Erli connection behaves like today's "adapter doesn't implement this capability" case. Added an integration test exercising the real ErliAdapterFactory + ErliOfferManagerAdapter + AllegroCategoryCatalogClient (fetch stubbed) through the production adapter-resolution seam and the real HTTP endpoint, covering configured / unconfigured / partially-configured connections. Confirmed the existing #1367 bulk-wizard capability-gate test passes unmodified. Part of #1381, sub-task 2 of 4 (depends on #1382, merged; see also #1384, #1387). Signed-off-by: Norbert Kulus <42zeroo@gmail.com> Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com> * fix(erli): add allegroCategoryAccessEnabled config flag as the FE-visible signal connection.supportedCapabilities turned out to be a static, per-adapterKey manifest value rather than computed per-connection-instance, so it can't distinguish an Erli connection with configured Allegro app credentials from one without. Add a non-secret ErliConnectionConfig.allegroCategoryAccessEnabled boolean for the frontend to read instead (see ADR-031 "Correction"); the write path that sets allegroClientId/allegroClientSecret must set/clear this flag atomically (tracked in #1384's scope). Also fixes stale ADR-030 references (renumbered to ADR-031 to avoid a collision with #1307) in allegro-category-catalog-client.ts and erli-connection.types.ts. Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com> * fix(erli): hoist Allegro category-token cache to host.cache + dedupe credential resolve (#1399 review) AllegroCategoryCatalogClient built a fresh in-memory token cache per adapter instance, but ErliAdapterFactory builds a fresh instance per getCapabilityAdapter call — so fetchCategoryParameters paid a full client_credentials OAuth round-trip on every request. The client now persists the acquired token in the optional CachePort (host.cache), keyed by clientId, with a TTL matching the token's remaining lifetime, so it survives across per-request instances and processes. Also collapses ErliAdapterFactory.createAdapters's two credentialsResolver.get calls (one for apiKey, one for the Allegro pair) into one shared resolve. Addresses both IMPORTANT findings from the PR #1399 review round (Piotr's token-cache note and the self-review's double-resolve finding). Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com> --------- Signed-off-by: Norbert Kulus <42zeroo@gmail.com> Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com> * feat(erli,web): Allegro credentials checkbox + offer wizard category/parameters steps (#1401) * feat(erli,web): checkbox-reveal Allegro credentials panel + offer wizard category/parameters steps Adds the operator-facing half of the Erli-owned Allegro category-catalog feature (#1384, part of epic #1381, depends on #1382/#1383): - ErliCredentialsPanel gains a "Browse Allegro categories when creating Erli offers" checkbox that reveals Client ID / Client Secret fields (masked, show/hide toggle). Saving sequences two existing mutations from one click: useUpdateConnectionCredentialsMutation (credentials pair, merged with apiKey) then useUpdateConnectionMutation (config patch for allegroCategoryAccessEnabled) — credentials first so a failure never leaves the flag advertising access the backend can't serve. - ErliCreateOfferWizard renders the reused Allegro CategoryPicker + CategoryParametersStep (fed by the existing useCategoryParametersQuery) as dedicated Category / Category-parameters steps when connection.config.allegroCategoryAccessEnabled is true — per ADR-031's correction, this per-connection-instance config flag is the FE gating signal, not the static, per-adapterKey connection.supportedCapabilities. Falls back to today's plain-text field + a link to the connection's edit page otherwise. Stepper labels and needsProductParameters become capability-conditional; erliCreateOfferSchema gains a parameters slice serialized into overrides.parameters on submit (no BE change needed). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RpsVFTpKZ1HoowEUKzbnWu Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com> * fix(erli,web): address PR #1401 review findings - Gate `canSubmit` in ErliCredentialsPanel on `allegroEnabled` so unchecking the Allegro-access box after typing (but not saving) Client ID/Secret disables Save instead of showing a false-positive "Credentials saved" toast while discarding the typed secret. - Drop the checkbox's invalid inline `accentColor: var(--accent)` (token doesn't exist) — `index.css` already applies `accent-color: var(--accent-primary)` globally. - Restore category-parameter values on Erli offer retry by reusing the Allegro retry mapper's `readParameters` heuristic (exported) instead of always prefilling an empty `parameters` object — both platforms persist the same neutral `overrides.parameters` shape. Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com> * fix(erli,connections): merge credential rotation + enforce category pick (#1401 second review) - ConnectionService.updateCredentials now merges the submitted payload onto the existing stored credentialsJson instead of replacing it wholesale, so rotating the plain Erli apiKey no longer silently wipes a previously configured allegroClientId/allegroClientSecret pair (and enabling Allegro access without touching apiKey no longer fails shape validation). - ErliCreateOfferWizard now blocks Next on the Category step until a category is actually selected, mirroring AllegroCreateOfferWizard's required categoryId field, instead of silently falling through to Category-parameters/Review with an empty category. - Carried over the approved mockup's helper copy under the Allegro Client ID / Client Secret fields and restored the checkbox's original hint text. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com> * test(erli,docs): cover mutation-sequencing failure paths + close ADR-031 documentation gaps (#1401 third review) - erli-credentials-panel.test.tsx: add regression tests asserting the config patch never fires when the credentials write rejects, and that a config-patch rejection after a successful credentials write leaves the flag/fields intact with a retryable inline error - the two claims the PR description makes about "the trickiest part of this issue" were previously unverified by the suite. - ADR-031: add a second Correction noting the sequencing is two FE-orchestrated mutations, not a single backend write, since no endpoint accepts both the credential pair and the config flag together; also record the wizard's 3-step-vs-5-step topology and the deferred credential-verification indicator as explicit, confirmed scope cuts rather than implicit deviations from the approved mockup. 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 Sonnet 5 <noreply@anthropic.com> * feat(erli): reuse an existing Allegro connection's credentials for category access (#1387) (#1405) Extends the #1384 Erli credentials panel with a reuse-vs-manual choice: when the operator already has an Allegro connection, its clientId/clientSecret can be copied server-side into the Erli connection instead of registering a second Allegro app. The raw clientSecret never round-trips through the browser. The credential-copy logic is dispatched through a new, platform-neutral ConnectionCredentialsRewriterPort + registry (mirroring the existing ConnectionConfigShapeValidatorPort/ConnectionCredentialsShapeValidatorPort pair) so the generic ConnectionService.updateCredentials stays unaware that Allegro or Erli exist. The Erli-specific resolution lives entirely in ErliAllegroCredentialsRewriterAdapter, registered from a companion ErliCredentialsRewriterModule (mirrors ErliWebhookProvisioningModule) since it needs ConnectionPort, which is intentionally outside the plugin-neutral HostServices bag. Closes #1387 Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com> --------- Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com> Signed-off-by: Norbert Kulus <42zeroo@gmail.com> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
…or convention parity Every other integration (allegro, dpd-polska, erli, inpost, ksef, subiekt, woocommerce) already lives at libs/integrations/<name>/docs/setup-guide.md after the recent doc-location convention change on main. inFakt's guide had already moved its screenshots there but left the markdown file behind at the old top-level docs/integrations/infakt/ location. Move the file alongside its assets and fix every cross-reference (ADR-030, package README, architecture-overview.md). Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
…the 4 sub-capabilities shipped since #1307's last update InfaktInvoicingAdapter now implements 10 Invoicing sub-capabilities; this branch's docs only covered 6 (missing RegulatoryResubmitter #1356, PaymentStatusReader #1354, PaymentMarker #1362, InvoiceEmailSender #1353). Also fixes a since-stale claim that invoice_marked_as_paid webhooks are ignored (they now drive PaymentStatusReader via #1354/#1361), documents the dedicated corrective_invoices.json endpoint (#1342), and adds the per-connection shipping-line label override (#1517/#1562). docs/capabilities.md and architecture-overview.md's InvoicingPort sub-capability list had the same 6-of-10 staleness independent of this branch - fixed alongside since they're the same underlying gap. Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
Summary
docs/architecture/adrs/030-infakt-ksef-indirection.md) documenting whyInfaktInvoicingAdapterimplementsRegulatoryStatusReader(notRegulatoryTransmitter): inFakt auto-submits to KSeF on its own timing via its own native integration, so OL has no submit primitive to call — it only ever reads clearance status back viaGET /invoices/{uuid}.json.docs/integrations/infakt/setup-guide.md): prerequisites, guided connection creation, webhook configuration (URL pattern, HMAC secret, verification handshake), a full verification walkthrough, and a troubleshooting table — illustrated with the 20 real screenshots already captured on this branch for feat(web): inFakt FE plugin — connection setup + invoice detail + KOR correction #1300's E2E walkthrough (libs/integrations/infakt/docs/assets/), including the inFakt-dashboard side (login, API key page, webhooks list, webhook subscription form, invoice-confirmed).libs/integrations/infakt/README.md— the package README this adapter was missing, mirroring the shape of the other integration READMEs added in docs(integrations): package READMEs + operator tutorials for KSeF and Subiekt nexo #1284 (adapter key, capabilities, credentials/config shape, notable implementation details).docs/architecture-overview.md§ 14 Invoicing with a "Second provider" paragraph for inFakt, alongside the existing KSeF entry.docs/plans/implementation-plan-infakt-docs-adr.md) per the repo's/planworkflow.Why this PR targets
1282-infakt-fe-plugin, notmainThe setup guide documents connection-wizard fields and webhook wiring that only exist on
1282-infakt-fe-plugin(which carries #1280's hardened adapter, #1281's registration + webhook routing, and #1282's FE plugin) — none of that has merged tomainyet. Stacking this PR keeps its diff docs-only; it'll retarget tomainautomatically once1282-infakt-fe-pluginmerges.Known gap, documented not hidden
The webhook-secret exchange direction (OL generates → paste into inFakt, vs. inFakt generates → paste into OL) couldn't be verified against inFakt's live dashboard UI in this session. The setup guide documents the more common HMAC-webhook UX (OL generates via Rotate webhook secret) and calls out the fallback + the fact that OL currently has no endpoint to set the webhook secret to an arbitrary caller-supplied value — only to rotate to a new random one.
Test plan
scripts/check-repo-urls.mjspassesdocs/architecture/adrs/README.md, Status: AcceptedPart of #1279. Closes #1283.
🤖 Generated with Claude Code
https://claude.ai/code/session_01JvfZGWAWWqVeJsFZbH1atL