feat!: add opt-in strict transactional audit mode - #7
Merged
Merged
Conversation
Lock the historical public API, Symfony configuration, Doctrine mapping, persistence pipeline, listener behavior, and known legacy semantics before introducing opt-in transactional auditing.
Canonicalize equivalent reflection types across PHP versions and adapt Doctrine test fixtures for DoctrineBundle 2/3 and PHP 8.4 native lazy objects without changing the 1.0.0 API snapshot.
Exercise PHP 8.3 through 8.5, Symfony 7.4 and 8.x, DoctrineBundle 2/3 and Doctrine ORM 3 across lowest and latest dependencies, with separate static analysis, coding style, platform checks, and security audit gates.
Raise the DoctrineBundle minimum to 2.19, drop Doctrine ORM 2 support, declare symfony/polyfill-mbstring as a direct runtime dependency, and prepare the main branch for the 2.x series. BREAKING CHANGE: Doctrine ORM 2 is no longer supported. DoctrineBundle 2.19 or 3.x and Doctrine ORM 3.x are now required.
Exercise UTF-8 truncation with and without the native mbstring extension, assert the direct polyfill dependency and platform state, and keep the supported compatibility matrix green across PHP 8.3 through 8.5.
Document PHP 8.3+, Symfony 7.4 and 8.x, Doctrine ORM 3, DoctrineBundle 2.19/3.x, and the native-or-polyfilled mbstring runtime policy.
Define strict fail-closed extension contracts and readonly subject, actor, event, and record models while keeping the legacy runtime, Doctrine mapping, service aliases, and database schema unchanged.
Resolve mapped entity types and scalar, BackedEnum, Stringable, and primitive composite identifiers through Doctrine metadata, with a private default extractor alias and explicit failures for unavailable or unsupported identifiers. The extractor performs no persistence, flush, transaction management, repository lookup, or explicit database query.
Resolve authenticated Symfony users and immediate SwitchUserToken impersonation through TokenStorageInterface, with a private default resolver alias and no implicit role, token metadata, HTTP context, or Doctrine access. Invalid users and identifiers fail explicitly with ActorResolutionException while preserving the original exception and excluding raw identifiers from error messages.
Resolve explicit or strategy-provided subjects, actors, and timestamps into an AuditRecord before delegating entry creation and storage, with deterministic ordering and exact exception propagation. The recorder remains manually composable and performs no flush, transaction control, logging, Doctrine access, Messenger dispatch, or implicit Symfony service wiring.
Register the strict transactional recorder and its private interface alias only when transactional.enabled is explicitly enabled, while requiring the application container to supply the entry factory, storage, and PSR-20 clock. The default-disabled path preserves the legacy container and schema, and no factory, storage, clock, flush, transaction control, or legacy fallback is provided by the bundle.
Add an application-owned UUID v7 business entity, audit entity, entry factory, no-flush Doctrine storage, and dedicated PostgreSQL tests proving shared commit, shared rollback, and fail-closed rollback after a flushed business mutation. The executable example remains isolated to test fixtures and does not add a production entity, mapping, migration, storage, or PostgreSQL runtime requirement to the bundle.
Execute the dedicated PostgreSQL 16 atomicity suite on the minimum PHP 8.3 and latest PHP 8.5 supported stacks, covering Doctrine DBAL 3 and 4 without duplicating the general compatibility and QA gates.
Document application-owned audit entities, no-flush Doctrine storage, shared EntityManager and connection requirements, explicit transaction boundaries, and the executable PostgreSQL commit, rollback, and fail-closed proof.
Add a guarded legacy_mapping.enabled option that retains the AuditEntry mapping by default and conditionally omits it only after legacy auditing has been disabled. The opt-out changes Doctrine metadata registration only: it performs no table deletion, SQL, migration, queue draining, service removal, or automatic storage replacement.
Document coexistence and transactional-only configurations, clarify that disabling the mapping never removes an existing table, and require legacy Messenger queues and workers to be drained before opting out.
Add a deterministic full public API baseline for the 2.0 series while preserving the immutable 1.0 contract, canonicalizing equivalent reflection types across PHP 8.3 through 8.5, and allowing future additive public types. The contract rejects removals and changes to signatures, interfaces, enums, public properties, constructors, finality, and readonly semantics.
Upgrade static analysis to PHPStan 2 across the supported PHP 8.3 through 8.5 platform and tighten configuration, Doctrine, Reflection, generic and fixture types without changing runtime behavior or public native signatures. No baseline, global ignoreErrors, bleeding edge ruleset, functional cast, configuration change, mapping change, or exception handling was introduced.
Require PHPStan 2 in the static-analysis gates and validate the Composer archive as a self-contained runtime package with bundled versioned documentation, relative-link integrity, development-file exclusions, no-dev installation, platform checks, and runtime autoload verification.
Document the 1.0-to-2.0 upgrade path, preserved legacy semantics, strict transactional Doctrine integration, security and privacy responsibilities, and the supported compatibility and deprecation policy. The guides distinguish fail-open legacy behavior from the opt-in fail-closed recorder and do not mark 2.0 as released.
Rewrite the project overview, configuration comments, and unreleased changelog around the future 2.x platform, default-preserved legacy mode, opt-in strict recorder, migration paths, and explicit security and durability non-guarantees. The documentation does not mark 2.0 as published and preserves the historical 1.0 changelog entry.
Require the upgrade, legacy, transactional, security, privacy and compatibility guides in the Composer archive and validate every distributed Markdown document for safe, resolvable relative links.
Validate all latest-compatible Symfony 8 matrix entries against the supported major instead of pinning a stale minor, while preserving the explicit Symfony 8.0 compatibility boundary.
Run CI once for pull requests targeting develop or main, and on pushes reaching the integration, stable or tag refs, so feature, release and Dependabot branches no longer execute duplicate push and pull-request matrices.
Alias the develop branch to the future 2.0 development line and prepare monthly Composer and GitHub Actions version updates targeting develop, with compatible minor and patch updates grouped.
Document feature integration through develop, release branches toward main, explicit tag and publication authorization, security-fix synchronization, and main-to-develop reconciliation after every release.
Adopt the reviewed checkout v7.0.1 Node 24 release and pin every checkout and setup-php invocation to its verified full commit SHA while retaining version comments for Dependabot updates.
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
Add an opt-in strict transactional audit mode while preserving the existing
legacy audit behavior by default.
The application owns its transactional audit entity, entry factory, storage,
PSR-20 clock and transaction boundary. The bundle supplies deterministic
Doctrine identifier extraction, Symfony Security actor resolution, strict audit
orchestration and conditional Symfony container wiring.
Why
The legacy listener remains useful for automatic fail-open audit histories, but
its fail-open behavior and persister-owned flush cannot guarantee that business
and audit writes share the same commit or rollback.
The new recorder supports application flows where an audit failure must prevent
the business commit without imposing an audit schema or persistence model.
Breaking changes and platform baseline
Dropping Doctrine ORM 2 and raising the DoctrineBundle minimum are the reasons
this work targets the future 2.x series.
Legacy compatibility
The default configuration remains:
enabled: truelegacy_mapping.enabled: truetransactional.enabled: falseThe historical
AuditEntrymapping,audit_entryschema, integer identifier,columns, indexes, automatic listener, service aliases, sync/async writers and
fail-open behavior remain unchanged by default.
No SQL migration is mandatory merely to retain the default legacy mode.
Transactional architecture
The strict recorder is enabled explicitly with:
Applications must provide:
AuditEntryFactoryInterfaceAuditStorageInterfacePsr\Clock\ClockInterfaceThe bundle does not impose a factory, storage, audit entity, table or migration.
It provides default, replaceable aliases for:
IdentifierExtractorInterfaceAuditActorResolverInterfaceThe recorder:
best_effortmode.Identifier and actor resolution
The default Doctrine extractor supports:
The stable composite format is
doctrine-composite-v1.Identifier associations are not handled by the default extractor. Applications
can replace the extractor or provide an explicit
AuditSubject.The default Symfony Security resolver uses
getUserIdentifier()and immediateSwitchUserTokenimpersonation context. System and technical actors should beprovided explicitly.
PostgreSQL guarantees
A dedicated PostgreSQL 16 suite proves that an application-owned business
mutation and audit entry can:
fails before commit.
These guarantees require the same EntityManager and underlying connection. The
application storage only calls
persist()and never flushes.Transactional-only applications
Applications no longer using the legacy path can explicitly configure:
Disabling the mapping:
audit_entrytable;PersistAuditEntryMessagemessages to be processed ordrained;
Security and privacy
Applications remain responsible for:
The bundle does not automatically provide cryptographic signatures, hash
chaining, append-only storage, tamper evidence, archival, purge, anonymization
or legal compliance.
getUserIdentifier()may itself be personal data. Applications needing anopaque actor identifier can replace the actor resolver or provide an explicit
AuditActor.Supported matrix
The CI executes:
PHPStan 2 runs at maximum level across PHP 8.3 through 8.5 without a baseline or
global ignored errors.
Test results
General runtime matrix, six lines:
PostgreSQL matrix, two lines:
Additional gates:
Migration and operating documentation
The branch includes:
UPGRADE-2.0.mddocs/legacy-mode.mddocs/transactional-doctrine.mddocs/security-privacy.mddocs/compatibility.mdBoth the 1.0.0 and future 2.0.0 public APIs are protected by deterministic
contract snapshots.
Release workflow
This draft pull request targets
develop, the integration branch.It must not be merged into
maindirectly.After review and green CI on
develop, the release process will use a separaterelease/2.0.0branch created fromdevelop. That release branch will:2.0.0changelog version and release date;main.Tagging
2.0.0, creating a GitHub Release and publishing to Packagist remainseparate operations requiring explicit maintainer authorization.
After the release,
mainmust be synchronized back intodevelop.Out of scope
best_effortmode;Release status
This is a draft integration branch for the future 2.x series.
It has not been tagged, released or published.