ksef_client 0.1.0.rc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 0e0a3a152f2257ff0f45b86a8027f6a48a8b1ff6612b940284c155bf1a9d6702
4
+ data.tar.gz: ffed0fa233572a528a77ffdcc0e0171a96f7a755ffff1dfc4561c955b15bc32b
5
+ SHA512:
6
+ metadata.gz: c3c6068d0d7bbdc5ee4aad882950e13b497da3633b1e832357ae42597952c197734229dab9cd5b7f20680dbb4f3028291e953028fcae3a7ac9fe10a0a9465c7c
7
+ data.tar.gz: e99942deca0206d28f0919d58ad94bf83c6e9e689868b76e7968518d343ca0ea10fdb47a537a817ba537b7b83137c1ff9b6ba1ba9da89eaab0d8ac22823b9fb9
data/CHANGELOG.md ADDED
@@ -0,0 +1,59 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here, following
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). This project adheres to
5
+ [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ Every release entry states the **KSeF API version** and **FA schema revision** it
8
+ targets. The Ministry ships changes continuously, so users must be able to answer "which
9
+ gem version for which API state".
10
+
11
+ ## [Unreleased]
12
+
13
+ ## [0.1.0.rc1] — 2026-08-22
14
+
15
+ **Targets:** KSeF API 2.0 · FA(3) `1-0E` (`kodSystemowy` `FA (3)`, variant 3)
16
+ · upstream `CIRFMF/ksef-api@1c34fe27` (2026-07-21)
17
+
18
+ > **Prerelease. Not usable for invoicing.** This release exists to verify the trusted
19
+ > publishing pipeline and to claim the gem name. It contains the transport foundations
20
+ > only — authentication, encryption, sessions and the FA(3) builder are not implemented,
21
+ > so no invoice can be sent yet. Prereleases are not installed by `gem install
22
+ > ksef_client`; you would have to ask for this version explicitly.
23
+
24
+ ### Added
25
+
26
+ - Pinned upstream artifacts with SHA-256 manifest: the OpenAPI 3.0.4 contract
27
+ (78 paths, 302 schemas) and the FA(3) XSD with its three base schemas. Verified on
28
+ every test run and by `rake verify:artifacts`.
29
+ - `docs/REFERENCE.md`, the verification ledger — every externally sourced fact with its
30
+ source URL and retrieval date.
31
+ - `Ksef::Environments` with TEST, DEMO and PROD base URLs verified against each
32
+ environment's own OpenAPI document, plus a `custom(base_url:)` escape hatch that
33
+ requires HTTPS.
34
+ - `Ksef::Configuration` — frozen at construction for thread safety, with a redacting
35
+ `#inspect`, duck-typed logger validation, and rejection of unknown options.
36
+ - `Ksef::RetryPolicy` — idempotent-only retries with capped exponential backoff, honouring
37
+ `Retry-After` unclamped. Invoice submission is never auto-retried.
38
+ - Full error hierarchy under `Ksef::Error`, including `AuthorizationError` (403, with
39
+ `reason_code` and the structured `security` payload) and `ResourceGoneError` (410).
40
+ - `Ksef::ProblemDetails` — normalises both KSeF error envelopes (`application/problem+json`
41
+ and the deprecated `application/json` shapes) and degrades gracefully on non-JSON bodies.
42
+ - `Ksef::HTTP::Connection` — Faraday connection factory with TLS 1.2 floor, verification
43
+ always on, and error mapping.
44
+ - `Ksef::HTTP::SystemWarning` — surfaces the `X-System-Warning` advisory header the API
45
+ sets on successful responses.
46
+ - CI: test matrix across Ruby 3.2/3.3/3.4/4.0/head, nightly TEST integration, and a
47
+ tag-triggered release workflow using RubyGems Trusted Publishing.
48
+
49
+ ### Notes
50
+
51
+ - The `ksef-docs` repository named in the design document does not exist; all upstream
52
+ documentation, the OpenAPI spec and the FA schemas live in `CIRFMF/ksef-api`.
53
+ - Endpoint paths carry no `/api` prefix — the base URL includes `/v2` and paths are
54
+ appended bare.
55
+ - The FA(3) schemas are MIT-licensed by Ministerstwo Finansów, so they are bundled with
56
+ the gem rather than fetched at first run.
57
+ - **The Ruby 3.2 floor is retained despite 3.2 reaching EOL upstream**, as a deliberate
58
+ exception to the EOL-drop policy (DESIGN.md §3). The full suite is verified green on
59
+ 3.2.11. The EOL-drop rule governs 3.3 and later series.
data/CONTRIBUTING.md ADDED
@@ -0,0 +1,103 @@
1
+ # Contributing
2
+
3
+ ## Getting set up
4
+
5
+ ```bash
6
+ bin/setup # bundle install + verify pinned artifacts
7
+ bundle exec rake # verify:artifacts, specs, RuboCop
8
+ ```
9
+
10
+ Development targets the current Ruby 4.0 patch (see `.ruby-version`) because the
11
+ strictest interpreter surfaces un-bundled-stdlib and deprecation issues locally. The
12
+ library itself supports MRI >= 3.2.
13
+
14
+ ## The one rule that matters most
15
+
16
+ **Never invent an endpoint path, XML element name, namespace URI, or cryptographic
17
+ parameter.**
18
+
19
+ This gem talks to a tax authority. A plausible-looking guess that happens to be wrong
20
+ produces rejected invoices, or worse, silently malformed ones. So:
21
+
22
+ 1. Every externally sourced fact goes in [`docs/REFERENCE.md`](docs/REFERENCE.md) with its
23
+ value, source URL and retrieval date, *before* the code depending on it is written.
24
+ 2. The authoritative sources, in order: the integrator documentation portal, the
25
+ `CIRFMF/ksef-api` repository, the pinned OpenAPI spec, the pinned FA(3) XSD.
26
+ 3. If a detail is not in the ledger and not verifiable from those sources, **stop and ask**
27
+ rather than guessing.
28
+ 4. When the pinned artifacts and the design document disagree, the artifacts win. Update
29
+ the ledger and note the divergence in your PR description.
30
+
31
+ ## Pinned artifacts
32
+
33
+ `spec/fixtures/openapi/open-api.json` and `lib/ksef/fa3/schema/**` are verbatim upstream
34
+ copies, checksummed in `docs/artifacts.sha256`. Do not edit them, not even whitespace.
35
+
36
+ If `rake verify:artifacts` fails, upstream changed. That is a finding to investigate and
37
+ ledger, not a checkout to repair. Refresh the files, update the manifest and the ledger,
38
+ and describe the behavioural impact in your PR.
39
+
40
+ ## Constraints that are decided, not open
41
+
42
+ Please raise these for discussion rather than changing them in a PR:
43
+
44
+ - **Runtime dependencies are exactly four**: `faraday`, `nokogiri`, `bigdecimal`,
45
+ `zeitwerk`. A fifth needs a discussion first.
46
+ - **No upper bound on `required_ruby_version`**, ever.
47
+ - **The Ruby 3.2 floor stays**, despite 3.2 being EOL upstream — see DESIGN.md §3. Please
48
+ don't open a PR raising it, and don't reach for a 3.3+ API on the grounds that 3.2 is
49
+ past EOL.
50
+ - **`BigDecimal` for every monetary amount.** `Float` is forbidden in any monetary path.
51
+ - **Invoice submission is never auto-retried.**
52
+ - **No `VERIFY_NONE`**, no way to disable TLS verification.
53
+ - `lib/ksef/fa3/generated/` is codegen output — regenerate with `rake fa3:generate`, never
54
+ hand-edit.
55
+
56
+ ## Style
57
+
58
+ - `# frozen_string_literal: true` in every file.
59
+ - `Data.define` for immutable value and response objects.
60
+ - UTF-8 throughout; XML serialised with an explicit `encoding="UTF-8"` declaration. Note
61
+ that file reads need an explicit encoding, since a non-UTF-8 locale otherwise yields
62
+ US-ASCII strings and breaks on Polish characters.
63
+ - No `RUBY_VERSION` string surgery.
64
+ - RuboCop enforces what it can; `bundle exec rubocop -a` for the mechanical parts.
65
+
66
+ ## Tests
67
+
68
+ | Tier | Runs |
69
+ |---|---|
70
+ | Unit + recorded (WebMock/VCR) | every push, full Ruby matrix |
71
+ | Golden files, round-trip, crypto vectors | every push |
72
+ | Live TEST integration | nightly and pre-release only, never per-PR |
73
+
74
+ Coverage gate is 90% lines, excluding `generated/`. Live integration specs are tagged
75
+ `:integration` and read credentials only from `KSEF_TEST_NIP` / `KSEF_TEST_TOKEN` with
76
+ `KSEF_ENV=test`.
77
+
78
+ Never point tests at production. The suite aborts on `KSEF_ENV=prod`.
79
+
80
+ Cassettes must be scrubbed of tokens, JWTs and keys before committing.
81
+
82
+ ## Releasing
83
+
84
+ 1. Confirm no metadata placeholders were reintroduced: `grep -r UNRESOLVED-DESIGN-12-1`.
85
+ The release gate checks this too, but it is cheaper to notice here.
86
+ 2. Update `CHANGELOG.md`, including the KSeF API version and FA schema revision targeted.
87
+ 3. Confirm a **green nightly on the release commit**.
88
+ 4. Do one deliberate local full-suite run on **Ruby 3.2** — the floor contract deserves a
89
+ look, not just a matrix tick. RuboCop cannot substitute for this: `TargetRubyVersion`
90
+ catches incompatible *syntax* but not post-3.2 *APIs*, so a method added in 3.3 or 3.4
91
+ passes lint and then `NoMethodError`s on a user's interpreter.
92
+
93
+ ```bash
94
+ mv Gemfile.lock /tmp/Gemfile.lock.dev
95
+ BUNDLE_PATH=/tmp/ksef-bundle-32 RBENV_VERSION=3.2.11 bundle install
96
+ BUNDLE_PATH=/tmp/ksef-bundle-32 RBENV_VERSION=3.2.11 bundle exec rake
97
+ mv /tmp/Gemfile.lock.dev Gemfile.lock
98
+ ```
99
+
100
+ The separate `BUNDLE_PATH` leaves your development gem set alone, and resolving without
101
+ the lockfile mirrors CI. Bundler on 3.2 cannot read a lockfile written by Bundler 4.
102
+ 5. Tag `vX.Y.Z`. The release workflow runs the suite with `KSEF_RELEASE_CHECK=1`, checks
103
+ the tag against `Ksef::VERSION`, and publishes via Trusted Publishing.
data/LICENSE ADDED
@@ -0,0 +1,27 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Tibor Molnár
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
23
+ ---
24
+
25
+ The FA(3) XML schemas bundled under `lib/ksef/fa3/schema/` are published by
26
+ Ministerstwo Finansów under the MIT licence; see
27
+ `lib/ksef/fa3/schema/LICENSE.upstream.txt` and docs/REFERENCE.md §1.
data/README.md ADDED
@@ -0,0 +1,150 @@
1
+ # ksef_client
2
+
3
+ Ruby client for **KSeF 2.0**, Poland's national e-invoicing system (Krajowy System
4
+ e-Faktur), with a standalone **FA(3)** invoice builder.
5
+
6
+ Invoicing through KSeF has been a legal obligation since 2026-02-01 for taxpayers with
7
+ 2024 gross sales above 200M PLN, and since 2026-04-01 for essentially everyone else. The
8
+ Ministry of Finance publishes official SDKs in C# and Java; this gem fills the Ruby gap.
9
+
10
+ > **Status: pre-release, under active development.** The transport foundations
11
+ > (configuration, environments, error model, HTTP layer) are in place. Authentication,
12
+ > encryption, sessions and the FA(3) builder are not implemented yet — see
13
+ > [Roadmap](#roadmap). The quickstart below is the **target** API for 0.1.0 and does not
14
+ > run today.
15
+
16
+ ## Installation
17
+
18
+ ```ruby
19
+ gem "ksef_client"
20
+ ```
21
+
22
+ The gem is named `ksef_client`; the namespace is `Ksef`.
23
+
24
+ ```ruby
25
+ require "ksef_client" # defines Ksef, Ksef::Client, Ksef::FA3, ...
26
+ ```
27
+
28
+ ## Quickstart (target API for 0.1.0 — not yet functional)
29
+
30
+ ```ruby
31
+ client = Ksef::Client.new(
32
+ env: :test,
33
+ auth: Ksef::Auth::Token.new(context_nip: "9999999999", token: ENV["KSEF_TOKEN"])
34
+ )
35
+
36
+ invoice = Ksef::FA3.build do |f|
37
+ f.seller nip: "9999999999", name: "ACME sp. z o.o.",
38
+ address: { street: "Prosta 1", city: "Warszawa", postal_code: "00-001", country: "PL" }
39
+ f.buyer nip: "1111111111", name: "Klient S.A.",
40
+ address: { street: "Długa 2", city: "Kraków", postal_code: "30-001", country: "PL" }
41
+ f.number "FV/2026/08/001"
42
+ f.issue_date Date.today
43
+ f.line name: "Consulting", qty: 10, unit: "godz.", net_unit_price: 150, vat: "23"
44
+ end
45
+
46
+ result = client.send_invoice(invoice) # validate! → encrypt → session → submit
47
+ status = client.wait_until_accepted(result.reference)
48
+ status.ksef_number # => "9999999999-2026…"
49
+ upo = client.upo(result.reference) # signed UPO XML — archive this verbatim
50
+ ```
51
+
52
+ ## What works today
53
+
54
+ ```ruby
55
+ config = Ksef::Configuration.new(env: :test, timeout: { read: 120 })
56
+ config.base_url # => "https://api-test.ksef.mf.gov.pl/v2"
57
+ config.inspect # credentials are redacted, never logged
58
+
59
+ conn = Ksef::HTTP::Connection.build(config)
60
+ conn.get("rate-limits")
61
+ ```
62
+
63
+ Errors from the API arrive as a typed hierarchy carrying the Ministry's own diagnostics:
64
+
65
+ ```ruby
66
+ begin
67
+ conn.get("sessions")
68
+ rescue Ksef::RateLimitedError => e
69
+ e.retry_after # => 30 — seconds, from the Retry-After header. Honour it.
70
+ rescue Ksef::AuthorizationError => e
71
+ e.reason_code # => "missing-permissions"
72
+ e.security # => {"requiredAnyOfPermissions" => ["InvoiceWrite"], ...}
73
+ rescue Ksef::ApiError => e
74
+ e.status # => 400
75
+ e.code # => 21405
76
+ e.details # => ["Wskazany kod formularza nie jest wspierany."]
77
+ e.trace_id # quote this to Ministry support
78
+ end
79
+ ```
80
+
81
+ ## Environments
82
+
83
+ | Env | Base URL |
84
+ |---|---|
85
+ | `:test` | `https://api-test.ksef.mf.gov.pl/v2` |
86
+ | `:demo` | `https://api-demo.ksef.mf.gov.pl/v2` |
87
+ | `:prod` | `https://api.ksef.mf.gov.pl/v2` |
88
+
89
+ Each URL was verified against that environment's own OpenAPI document; see
90
+ [`docs/REFERENCE.md`](docs/REFERENCE.md). Note the base URL already includes `/v2`.
91
+ `Ksef::Environments.custom(base_url:)` is available for non-public deployments.
92
+
93
+ TEST and DEMO are for integration testing only — never send real invoices or real
94
+ taxpayer data to them, and use random NIPs. TEST data is not isolated between
95
+ integrators.
96
+
97
+ ## Ruby support
98
+
99
+ - **MRI >= 3.2**, with **no upper bound, ever.** `required_ruby_version` resolves at
100
+ install time, so an upper bound strands users on each new Ruby release.
101
+ - CI covers 3.2, 3.3, 3.4, 4.0 and `head`, and the full suite is run on 3.2 at every
102
+ milestone — the floor is verified, not just declared.
103
+ - **The 3.2 floor is a commitment, not a default.** Ruby 3.2 is EOL upstream, and this gem
104
+ still supports it deliberately: Polish tax-compliance software upgrades slowly, and 3.2
105
+ is the Rails 8.0 floor. There is no plan to raise it.
106
+ - Later EOL series will be dropped in **minor** releases with a changelog note. Because
107
+ version constraints resolve at install time, users on an older Ruby keep receiving the
108
+ last compatible release.
109
+ - **MRI only.** JRuby and TruffleRuby are untested — the crypto layer needs precise
110
+ OpenSSL RSA-OAEP parameterisation. PRs welcome.
111
+
112
+ ## Design notes
113
+
114
+ - **Two decoupled subsystems.** The FA(3) builder has no HTTP dependency and is usable on
115
+ its own; the transport layer accepts anything responding to `#to_xml`, or a raw XML
116
+ string.
117
+ - **`BigDecimal` everywhere for money.** `Float` is forbidden in any monetary path.
118
+ - **Invoice submission is never auto-retried.** A duplicate invoice in KSeF is a real tax
119
+ problem. Idempotent GETs retry with backoff; a failed submission surfaces to you.
120
+ - **Thread-safe.** A single client is shareable across threads (Sidekiq is the expected
121
+ habitat); configuration is frozen at construction.
122
+ - **Secrets never logged.** Tokens, JWTs, symmetric keys and IVs are redacted from
123
+ `#inspect` output.
124
+
125
+ ## Roadmap
126
+
127
+ | Version | Scope |
128
+ |---|---|
129
+ | 0.1.0 | KSeF-token auth, crypto, online sessions, send/status/UPO/download, full FA(3) builder for all seven invoice types |
130
+ | 0.2 | Batch sessions, invoice query/search, package export, hardened error catalogue |
131
+ | 0.3 | XAdES authentication, certificate lifecycle, permissions API, offline QR codes |
132
+ | 1.0 | After sustained production use; API stability promise begins |
133
+
134
+ ## Development
135
+
136
+ ```bash
137
+ bin/setup # or: bundle install
138
+ bundle exec rake # verify pinned artifacts, specs, RuboCop
139
+ ```
140
+
141
+ Every externally sourced fact — endpoint paths, XML element names, namespace URIs,
142
+ crypto parameters — is recorded with its source and retrieval date in
143
+ [`docs/REFERENCE.md`](docs/REFERENCE.md). Nothing enters the code without an entry there.
144
+
145
+ ## Licence
146
+
147
+ MIT. The bundled FA(3) schemas are published by Ministerstwo Finansów, also under MIT;
148
+ see `lib/ksef/fa3/schema/LICENSE.upstream.txt`.
149
+
150
+ This project is not affiliated with or endorsed by the Polish Ministry of Finance.
data/SECURITY.md ADDED
@@ -0,0 +1,69 @@
1
+ # Security policy
2
+
3
+ ## Reporting a vulnerability
4
+
5
+ Please report security issues privately via GitHub's "Report a vulnerability" flow on
6
+ this repository, rather than opening a public issue. You can expect an acknowledgement
7
+ within a few working days.
8
+
9
+ This gem handles credentials that authenticate against a tax authority, and invoice
10
+ payloads that are legally binding documents. Reports about credential leakage, weakened
11
+ TLS, or incorrect cryptographic parameters are treated as high severity.
12
+
13
+ ## Supported versions
14
+
15
+ Security fixes land on the latest minor release.
16
+
17
+ The supported interpreter range is whatever `required_ruby_version` declares — currently
18
+ **MRI >= 3.2**. Note that this **includes Ruby 3.2 even though 3.2 is EOL upstream**: the
19
+ floor is a deliberate commitment (see DESIGN.md §3), so 3.2 users do receive fixes from
20
+ this gem.
21
+
22
+ That said, an EOL interpreter stops receiving fixes for CVEs *in Ruby itself*, which this
23
+ project cannot patch. If you are on 3.2 for compliance reasons, upgrading Ruby is still
24
+ the stronger position.
25
+
26
+ Once a series does fall below the floor in a future minor release, `required_ruby_version`
27
+ resolves at install time, so users on that interpreter stay on the last compatible release
28
+ and stop receiving fixes from us.
29
+
30
+ ## Handling of secrets
31
+
32
+ The following must never appear in logs, `#inspect` output, exception messages, or test
33
+ fixtures:
34
+
35
+ - KSeF tokens and JWTs (access and refresh)
36
+ - Session symmetric keys and IVs
37
+ - Full invoice payloads at default log level
38
+
39
+ Enforced by:
40
+
41
+ - A redacting `#inspect` on configuration and authentication objects.
42
+ - VCR cassette scrubbing, plus a spec that scans committed cassettes for `Bearer ` and
43
+ known secret environment values.
44
+ - Integration tests reading credentials only from environment variables
45
+ (`KSEF_TEST_NIP`, `KSEF_TEST_TOKEN`, `KSEF_ENV=test`).
46
+
47
+ ## Transport security
48
+
49
+ - TLS verification is always on. No code path may set `VERIFY_NONE`; there is no
50
+ configuration option to disable it.
51
+ - Minimum TLS version is 1.2.
52
+
53
+ ## Testing against production
54
+
55
+ The test suite aborts if `KSEF_ENV=prod`, and the nightly workflow fails the same way.
56
+ Never point automated tests at the production environment — invoices submitted there have
57
+ full legal force.
58
+
59
+ ## Rate limits
60
+
61
+ KSeF records and analyses rate-limit breaches, and explicitly treats spreading one
62
+ context across many IP addresses as an abuse pattern. This client honours `Retry-After`
63
+ and will never work around a 429 by rotating connections. Please do not add such a
64
+ mechanism.
65
+
66
+ ## Publishing
67
+
68
+ Releases use RubyGems Trusted Publishing (OIDC). No long-lived RubyGems API key exists
69
+ for this project. The gemspec sets `rubygems_mfa_required`.