laravel-chronicle/core
Cryptographically verifiable audit ledger for Laravel. Append-only entries are hash-chained so tampering, deletion, or reordering is detectable—built for security logging, compliance, and forensic audit trails.
Architecture fit. Chronicle is a single-purpose, framework-native package: one Eloquent-backed append-only ledger table plus checkpoints, signed with a key ring, verified by artisan commands and a verifier service. It fits cleanly where an audit trail is a compliance artifact rather than a UI feature — financial postings, access/security events, order lifecycle, admin actions on regulated records. Where a team only needs "who changed this record," a conventional activity-log package is cheaper. The decisive architectural question is whether your auditors actually require tamper evidence, because that requirement is what justifies the hash chain, the key management surface, and the verification runbook. Chronicle does not attempt to replace your primary database or event bus; it sits alongside as a write-path concern with a commit() per entry, which keeps blast radius small and makes it adoptable incrementally rather than as a platform migration.
The design choices a TPM should note as mature: pluggable signing providers so the private key can live in KMS/HSM rather than app env; a key ring with an active key plus retired public keys, so rotation doesn't invalidate historical artifacts; boundary checkpoints created automatically on rotation; anchoring pushed to a separate trust domain (RFC 3161 TSA in core, S3 Object Lock as a companion) to close the "attacker has DB + signing key" gap; and tiered verification (full, --since-last-checkpoint, --from-checkpoint, --checkpoints-only, --resume) that acknowledges O(n) full verification is untenable on a growing ledger. The LedgerSeeder that drives the real write path is a genuine delivery accelerant — teams routinely discover late that fixture-based logs fail verification.
Integration feasibility. Low-to-moderate. Requirements are narrow but non-negotiable: PHP 8.2+, Laravel 12 or 13 only, ext-sodium and ext-openssl. The Laravel 12/13 floor is the most likely blocker — it excludes any application not yet on the current major, and forces an upgrade dependency between Chronicle adoption and framework upgrades. The extension requirements are usually satisfiable locally but need confirming against managed runtimes and container base images. Installation is a single composer require plus chronicle:install, and the README's upgrade note (1.9.x flat signing config still works, auto-adapted to a single-key ring) suggests the maintainers take backward compatibility seriously — a positive signal.
Technical risk. Concentrated in four areas:
commit() reads the current head and appends within a transaction. Under high write concurrency this becomes a contention point (row lock or advisory lock on the head) and caps throughput. This needs a load test before it goes anywhere near a hot path.Key questions for the maintainer / internal review.
Stack fit. Native Laravel: service provider, published config, migrations, artisan commands, facade, Eloquent trait/observer, container-bound contracts (ReferenceLookup, signing providers). It composes with existing queue, scheduler, and event infrastructure rather than competing with it. The HasChronicle trait and Chronicle::observe() cover owned and vendor models, so coverage can start with two or three high-value models and expand without schema churn.
Migration path. Greenfield ledgers are trivial: install, configure signing, record. Existing ledgers require chronicle:checkpoints:backfill so historical checkpoints gain head/count/link metadata; until that runs, incremental verification modes safely degrade to full verify — a well-behaved fallback. There is no migration path from an existing activity-log table, and I would not build one: importing legacy rows into a hash chain only attests to data you already know is mutable. Run Chronicle in parallel with the incumbent log for a transition period, then decommission.
Compatibility. Pin the package version explicitly and wrap access behind your own thin service/facade rather than sprinkling Chronicle::record() through domain code — you want the option to swap or fork. The Laravel 12/13 constraint should be sequenced after any framework upgrade completes and soaks. Confirm ext-sodium/ext-openssl in CI, all container images, and every managed environment (staging, DR, and developer onboarding docs) — extension drift between environments is a classic silent failure.
Sequencing.
chronicle:checkpoint and all chronicle:verify modes, deliberately corrupt a row and confirm detection fails closed. Validate the extension and Laravel-version prerequisites.--anchors --checkpoints-only verification on a cron, alerting on failure.chronicle:anchor:retry) is scheduled and monitored.Maintenance. Ongoing cost is modest but real and cryptographic in nature: key rotation (generate → add to ring → chronicle:key:rotate → flip CHRONICLE_ACTIVE_KEY → deploy → retire private key, keeping the public key), scheduled checkpoints, scheduled verification, and anchor retry. Treat each of these as a runbook with an owner, not a wiki page. Upgrades should be pinned and staged; the 1.9.x compatibility story suggests low breakage risk, but with a small maintainer base, assume you may need to patch the code yourself.
Support. No commercial support tier is evident. Supportability therefore comes from: MIT license (fork rights), a compact auditable codebase, and the fact that failures are verifiable rather than silent. The counterweight is 0 dependents — you should expect to be the one discovering edge cases, and should staff accordingly rather than assuming community answers will exist.
Scaling. Three distinct scaling dimensions, each with a different answer:
chronicle:verify is O(n) and grows forever. The supported answer is incremental verification — run --checkpoints-only --anchors frequently (fast, externally rooted) and reserve full verification for periodic or post-incident runs. Query paths are fine: cursor pagination and Entry::stream() are constant-memory, and reference resolution avoids DB hits unless hydrate: true.Failure modes and mitigations.
chronicle:anchor:retry scheduled and monitored, or anchors silently lag.ext-sodium/ext-openssl → install-time hard failure; catch it in CI, not in production deploy.Ramp-up. Budget roughly: one sprint for the spike and threat model, one sprint for key management setup plus the pilot domain, and 2–3 weeks for runbooks, dashboards, verification scheduling, and load testing. The engineering learning curve is small — the API is pleasant — but the organizational ramp is the real work: security owns key custody, legal/records owns retention and erasure, SRE owns verification alerting, and audit liaison owns explaining the chain to an external auditor. Assign those owners before the pilot, because the first external audit question ("prove this log wasn't altered") is the point of the whole exercise, and answering it ad hoc is expensive.
How can I help you explore Laravel packages today?