Repository navigation
Releases: nowo-tech/VerifactuBundle
Release list
Release v1.4.0
v1.4.0
Changelog
SEC-004 hardening: PEM loader fix, submit-sandbox refuses production, production requires persistent hash chain, CIF check digit, strict dates/amounts, explicit cURL TLS policy, dist export-ignore See UPGRADING.
Security hardening from the independent SEC-004 review of 1.3.0 (Pass (good) / Low, Appendix BI). See UPGRADING.
Security
- L1 — PEM certificates:
CertificateLoaderreturned the public key asprivate_keyand the whole PEM file (private key block included) ascertificate, so PEM signing failed and the key travelled with the certificate. It now exports the private key and the X.509 certificate separately, refuses a PEM file without a certificate or with a certificate that does not match the key, and no longer returns the password in the key material. - L2 —
nowo:verifactu:submit-sandbox:--submitoutside the sandbox environment is refused unless--allow-production, explicit--nif/--numserieand an interactive confirmation are given (it used to only print a warning). - L3 — production chain storage: the container refuses
aeat.environment: productionwith an AEAT client (certificate_pathorsubmission_client) whilehash_chain.storageismemoryand nohash_chain.repositoryis set. - L4 — dist archives:
.gitattributesexport-ignoreandcomposer.jsonarchive.excludekeeptests/(throwawaytest.p12),demo/,scripts/,specs/,.specify/,.cursor/and tooling out of dist archives. #[\SensitiveParameter]on the certificate password ofSoapAeatSubmissionClientandXadesBillingRecordSigner;scripts/aeat-sandbox-submit.phpreadsAEAT_CERT_PASSWORDand warns when--cert-passwordis used.CurlSoapTransportsets an explicit TLS policy: HTTPS only, no redirects,CURLOPT_SSL_VERIFYPEER/CURLOPT_SSL_VERIFYHOST = 2, 10 MiB response cap (MAX_RESPONSE_BYTES).twig/twigfloor raised to^3.19(patched line).- Demo: published port bound to
127.0.0.1; the audit log masks the issuer NIF.
Fixed
SpanishTaxIdValidatornow checks the CIF control character (digit or letter, by entity type); before, any CIF with a valid shape was accepted although the docs claimed a check digit.AeatBusinessRulesValidatorrejects impossible calendar dates (31-02-2026) and amounts outside the AEAT format (exponents, more than 2 decimals, more than 12 integer digits).- XML text values use
ENT_SUBSTITUTE, so invalid UTF-8 no longer produces an empty element. doctrine.table_prefixvalidation regex is anchored withD(a trailing newline is rejected).
Documentation
- SECURITY.md: AI audit record 2026-10-30 (Pass (good) / Low, L1–L4 fixed), PEM file requirements, business rule details, host transaction around
BillingRecordProcessor::process()andBillingRecordmutability. CONFIGURATION.md: production storage requirement and host transaction. COMMANDS.md:--allow-production.
Release v1.3.0
v1.3.0
Changelog
doctrine.table_prefix (TablePrefixListener), SECURITY.md threat model, SensitiveParameter on certificate passwords, PHP 8.5 curl_close deprecation See UPGRADING.
Added
doctrine.table_prefix(REQ-ORM-002). New optionnowo_verifactu.doctrine.table_prefix(default'', letters / digits / underscores only). A DoctrineloadClassMetadatalistener (Nowo\VerifactuBundle\Doctrine\TablePrefixListener) prepends it to theverifactu_hash_chaintable, its unique constraint and any many-to-many join table of bundle entities (e.g.app_yieldsapp_verifactu_hash_chain). Host and third-party entities are never touched; the default keeps the historical name.
Security
- Certificate passwords are
#[\SensitiveParameter]inCertificateLoader::load()andCurlSoapTransport::send()(redacted from stack traces).
Fixed
CurlSoapTransportno longer callscurl_close()(no effect since PHP 8.0, deprecated in PHP 8.5).
Deprecated
nowo_verifactu.hash_chain.table_prefixwas never applied; setting it now triggers a deprecation pointing todoctrine.table_prefix. It is still accepted and still has no effect.
Changed
- Developer tooling: Rector targets PHP 8.2, the declared
phpfloor (REQ-CS-003, REQ-SF-001).
Added
- Developer tooling:
make check-no-raw-html-formfails on raw<form>/<input>/<textarea>/<select>in bundle views and demo templates, and runs inrelease-check(REQ-TWIG-005).
Changed
- Demo: the invoice generator renders through a Symfony Form Type (
App\Form\InvoiceType; flat field names unchanged, CSRF token iddemo_invoice) withform_start/form_row/ child loop /form_end; the controller validates withhandleRequest+isValid(); the demo now requiressymfony/formandsymfony/security-csrf(REQ-TWIG-005). - Developer tooling:
make release-checkruns the full REQ-MAKE-002 chain in order (ensure-upfirst, thencheck-no-cursor-coauthor,check-open-prs,composer-sync,cs-fix,cs-check,rector-dry,phpstan,igor,test-coverage-100, demo release-check);check-open-prsalso fails on conflicted pull requests (REQ-REL-003). - Dependencies refreshed (Composer and pnpm/npm lock files of the bundle and its demos, campaign 2026-10-19).
.php-cs-fixer.dist.php:new ParallelConfig(2)(REQ-CS-001).
Documentation
- PHPDoc descriptions on every public method in
src/(REQ-CS-001). docs/SECURITY.mdrestructured as a security analysis: scope, attack surface, threat model (threat → mitigation), secrets and cryptography, logging policy, dependency policy, exposure (REQ-SEC-001).docs/CONFIGURATION.mddocumentsdoctrine.table_prefixwith the resulting table names.- Documentation: wave-1 REQ compliance — README documentation list order (canonical labels, PSR under Additional documentation), Symfony badge lists 7.4 explicitly and GitHub stars image uses the package name, canonical issue templates, demo README links DEMO-FRANKENPHP, per-variable comments in the demo
.env.example, CONTRIBUTING Cursor Agent settings, TOCs for long docs, Spec Kit inventory refresh (55/55, LockableHashChainRepositoryInterface).
Release v1.2.0
Release v1.2.0: TaxBreakdown tax type emitted as DetalleDesglose/Impuesto; Symfony >=6.4.
Changelog
Fixed
TaxBreakdown::$taxTypeis now emitted as<Impuesto>(first child ofDetalleDesglose, as required by the AEATDetalleTypesequence) when it is not empty. Previously it was ignored, so IGIC (03), IPSI (02) and other (05) lines were sent without their tax type and AEAT assumed IVA. Generated XML withImpuestois XSD-validated against the bundledSuministroInformacion.xsdin tests.
Changed
- Requirements: all
symfony/*constraints are now^6.4 || ^7.0 || ^8.0(was^6.0 || ...) so the lowest allowed version is the one tested in CI. README badge and INSTALLATION aligned (PHP requirement in INSTALLATION corrected to 8.2+).
Release v1.1.0
Release v1.1.0: AEAT compliance fixes (see CHANGELOG/UPGRADING).
Changelog
Security
nowo:verifactu:submit-sandboxwithout--submit(or with--dry-run) no longer stores the generated record as the new hash chain head, so smoke tests cannot rewrite the real persistent chain.BillingRecordProcessor::process()gains apersistChainStateargument (defaulttrue).- AEAT SOAP responses are only reported as successful when the HTTP status is 2xx and
EstadoRegistroisCorrecto. A missingEstadoRegistro(e.g. HTML error pages) or a non-2xx status is now a failure; namespace-prefixed AEAT responses (<tikR:EstadoRegistro>) are parsed andDescripcionErrorRegistrois reported. - The chain head read-then-write is now serialized per issuer NIF: new
LockableHashChainRepositoryInterface; the Doctrine repository runs it in a transaction withSELECT … FOR UPDATE, preventing forked chains under concurrent requests.
Fixed
- Apps without
doctrine/doctrine-bundleno longer fail at container build: the extension checkshash_chain.storagefirst and probesServiceEntityRepositoryinstead of autoloading the Doctrine repository.storage: doctrinewithout Doctrine now throws a clearLogicException. - Anulación fingerprints now use
IDEmisorFacturaAnulada/NumSerieFacturaAnulada/FechaExpedicionFacturaAnulada, as required by the AEAT hash specification (verified with the official test vector). - Hash amounts are normalized as strings (insignificant zeros only) instead of through
float, so large amounts such as999999999999.99are no longer altered. Desgloseemits oneDetalleDesgloseperTaxBreakdownentry instead of a single line with the first rate and the invoice totals.- QR URLs use
ValidarQRNoVerifactuinno_verifactumode (QrUrlBuildertakes the configured mode;buildUrl()accepts an optional per-call$mode).
Changed
- Documented that
XadesBillingRecordSignerproduces plain enveloped XML-DSig, not full XAdES, and thathash_chain.table_prefixandaeat_spec_versionare reserved options with no effect.
Release v1.0.12
Release v1.0.12: dependency refresh and repository maintenance.
Changelog
Changed
- Development:
composer.jsonpinsconfig.platform.phpto 8.2.0 so the committed lock stays installable on the minimum PHP; CI overrides the platform per matrix cell.
Dependencies
doctrine/orm3.7.4 → 3.7.5 (development lock; #12).
Release v1.0.11
v1.0.11
Changelog
Dependencies
- Dev tooling (Dependabot + lockfile refresh):
igor-php/igor-php^0.10.0,doctrine/orm3.7.3,phpstan/phpstan2.3.1,phpstan/phpstan-phpunit2.1.1,phpstan/phpstan-symfony2.1.0,nowo-tech/phpstan-frankenphp1.2.3,rector/rector2.7.0. The bundle lockfile stays resolved for the PHP 8.2 floor (Symfony 7.4). - Demo (
demo/symfony8): Symfony 8.1.8,doctrine/orm3.7.4,doctrine/dbal4.5.0,twig/twig3.30.0; regeneratedconfig/reference.php.
Release v1.0.10
v1.0.10
Changelog
Added
- REQ-CS-008:
igor-php/igor-php(require-dev only), rootigor.json, Composer/Makefileigortarget, andrelease-checkwiring for FrankenPHP worker-state audit.
Changed
- Worker safety (Igor): justified
// @igor-ignoreannotations and/orResetInterface/ request-scoped fixes somake igorpasses on packagesrc/.
Release v1.0.9
Release v1.0.9
FrankenPHP worker mode hardening (kernel not reset between requests).
See docs/CHANGELOG.md ## [1.0.9] and docs/FRANKENPHP-WORKER-AUDIT.md.
Changelog
Fixed
hash_chain.repositorynow also aliasesHashChainRepositoryInterface(previously onlynowo_verifactu.hash_chain_repository, soBillingRecordProcessorkept using the in-memory storage).- FrankenPHP worker mode (no kernel reset between requests): see
docs/FRANKENPHP-WORKER-AUDIT.md.InMemoryHashChainRepositoryis request-scoped: newclear()method, taggedkernel.reset, and called at the start of every main request by the newEventSubscriber\WorkerStateResetSubscriber(priority 4096). Workers no longer build per-worker hash chains. Whenkernel.debugis false it logs a warning on every store (the chain is not persistent).DoctrineHashChainRepositoryreads the last state fresh from the database (Query::HINT_REFRESH), detaches the entity after each read/write, deletes with a DQLDELETE, and resets a closed EntityManager when a flush/query fails before rethrowing.XsdValidatorrestores the previouslibxml_use_internal_errors()value infinally.
Release v1.0.8
v1.0.8
Changelog
Fixed
- FrankenPHP / PHPStan: replace mutable static
$translationswith immutable class constant in unit helper. - CI: drop obsolete PHP 8.1 from the test matrix (composer requires PHP ≥8.2).
- Demo: install
pdo_mysqlin FrankenPHP image (demo was HTTP 500: could not find driver).
Changed
- Deps (dev): refresh Composer lockfiles (
xmlseclibsand demo lock alignment).
Release v1.0.7
Release 1.0.7
See docs/CHANGELOG.md for details.
Changelog
Changed
- Raise minimum PHP to 8.2 and sync README badge (REQ-SF-001).
- Demos: MySQL env policy in FrankenPHP stack (REQ-DEMO-011).
- Makefile: detect Docker Compose V2 (REQ-MAKE-010).
- QA: add
phpstan-frankenphpextension (REQ-CS-005). - README: FrankenPHP-friendly worker-mode banner (REQ-DOCS-017).
- Docs: PHP-FIG PSR evaluation (REQ-CS-007).
- Style: PHP CS Fixer alignment.
Notes
- No API or configuration changes for integrators unless noted above.