Chronicle 1.13.0 opens up the package's public surface for host applications and downstream packages. It adds four capabilities - a configurable entry model, entry-range verification, reverse reference resolution, and a verification-preserving test seeder - and lays the groundwork for the upcoming laravel-chronicle/filament plugin.
This is a drop-in, backward-compatible minor. With the new config keys unset, behavior is byte-for-byte identical to 1.12.x. No breaking changes, no migrations.
What's new
Config-resolvable entry model
Point Chronicle at your own subclass of Chronicle\Entry\Entry to add accessors, relationships, or casts. The override is resolved through a single seam (Chronicle::entryModel() / Chronicle::newEntryQuery()) that is honored everywhere - the manager query, the ledger reader, all three verifiers, the storage drivers, and the entry-touching console commands.
// config/chronicle.php
'models' => [
'entry' => \App\Models\AuditEntry::class,
],The override must extend Chronicle\Entry\Entry; Chronicle validates this and throws InvalidEntryModelException otherwise, so immutability and the hash-chain contract are always preserved. Chronicle\Entry\Entry is now documented as stable, subclassable public API.
Entry-bounded range verification
Verify an arbitrary span of entries without working out checkpoint bounds yourself:
php artisan chronicle:verify --from={first-entry-ulid} --to={last-entry-ulid}app(\Chronicle\Verification\IntegrityVerifier::class)
->verifyEntryRange($fromSequence, $toSequence);Chronicle resolves the signed checkpoints that enclose the range, verifies their signatures, and recomputes the chain between them - so verification of the requested rows rides on the signed anchors, never on an entry's own stored hash. It fails closed if the derived anchors don't actually enclose the range. Ranges within a single checkpoint segment, spanning several, starting at genesis, and extending past the last checkpoint (recomputed to the head, same trust as --since-last-checkpoint) are all handled.
Reverse reference resolution
Turn a stored (type, id) actor/subject back into something displayable. Honors Relation::morphMap() and does not touch the database unless you opt in:
use Chronicle\Facades\Chronicle;
$ref = Chronicle::resolveReference($entry->subject_type, $entry->subject_id);
$ref->class; // resolved FQCN, or null when unknown
$ref->label; // "Order #123" - humanised basename + id
Chronicle::referenceLabel($entry->actor_type, $entry->actor_id); // no query
Chronicle::referenceModel($entry->subject_type, $entry->subject_id); // ?Model (queries)
Chronicle::referenceLabel($entry->subject_type, $entry->subject_id, hydrate: true);Hydration reads chronicle.references.label_attribute (default name). Bind your own Chronicle\Contracts\ReferenceLookup to fully customise resolution. The storage (write) direction is unchanged.
Verification-preserving test seeding
Seed realistic, verifiable ledger data in tests without one-at-a-time record()->commit() calls or invalid raw factory inserts:
use Chronicle\Testing\LedgerSeeder;
$seeded = LedgerSeeder::make()
->count(1000)
->checkpointEvery(100)
->action(fn (int $i) => "order.$i")
->subject(fn (int $i) => Order::factory()->create())
->seed();
$seeded->entries; // 1000
$seeded->checkpoints; // 10
$seeded->lastCheckpointId; // ?stringLedgerSeeder drives the real write path inside a single transaction and writes periodic signed checkpoints, so the result passes both IntegrityVerifier::verify() and CheckpointChainVerifier::verify(). Ships under the Testing namespace with no impact on production code paths.
Upgrading
composer update laravel-chronicle/coreNo breaking changes and no new migrations. The four config keys (chronicle.models.entry, chronicle.references.label_attribute) are optional with safe defaults; leaving them unset reproduces 1.12.x behavior exactly.
Compatibility
- PHP
^8.2 - Laravel
^12.0or^13.0 ext-sodium,ext-openssl
Quality
898 tests / 1750 assertions passing, PHPStan level 10 (no new baseline entries), Pint clean.
Full Changelog: 1.12.1...1.13.0