Skip to content

1.11.0

Latest

Choose a tag to compare

@ntoufoudis ntoufoudis released this 09 Jun 13:47
· 5 commits to main since this release
f48c53d

v1.11.0 — Scalable Verification & External Anchoring

This release roots checkpoint trust outside the application and makes verifying a large ledger cheap, while hardening the write path against concurrency and tampering. It is fully backward compatible: a 1.10 ledger with anchoring disabled and no incremental flags verifies identically to 1.10, with no artifact-format change and no re-export needed.

Highlights

  • External anchoring (opt-in). Copy a per-checkpoint digest — sha256(id . chain_hash . created_at) — into an independent trust domain so a full internal compromise is detectable. Ships an RFC 3161 timestamp anchor in core (offline verify, no cloud SDK) and a NullAnchor for dev/tests. A checkpoint that's been rewritten and re-signed with a valid key still fails chronicle:verify --anchors.
  • Scalable verification. New chronicle:verify modes — --checkpoints-only, --from-checkpoint/--to-checkpoint, --since-last-checkpoint, --resume, and --anchors — trade scope for cost without losing rigor. Each falls back to full verify (with a warning) until checkpoints are backfilled.
  • Range-aware checkpoints. Checkpoints now record head_id, entry_count, and previous_checkpoint_id, and checkpoint_id is populated on covered entries at creation — fixing a 1.10 gap where the checkpoint-verification branch never ran. checkpoint_id is unhashed; no signatures change.
  • Concurrency-safe ordering. A monotonic sequence column (assigned under the chain row-lock, with unique(sequence)/unique(chain_hash)) replaces ULID-sort ordering everywhere — a concurrent chain fork now fails loudly instead of corrupting the ledger.
  • Companion package: the new laravel-chronicle/anchor-s3 reference adapter anchors to an S3 Object Lock (WORM) bucket.

Added

  • Monotonic sequence column on chronicle_entries (assigned under the chain row-lock; unique(sequence) + unique(chain_hash)), with a backfilling migration that's a no-op on fresh installs.
  • IntegrityVerifier::verifyFrom(Checkpoint) — verify from a known-good checkpoint instead of genesis (signature verified before its chain_hash seeds the walk), so pruned-history ledgers stay verifiable.
  • Range-aware checkpoints: head_id, entry_count, previous_checkpoint_id columns; new chronicle_checkpoint_anchors and optional chronicle_verification_runs tables; chronicle.tables.* keys for both. Checkpoint gains previousCheckpoint() and anchors() relations.
  • CheckpointCreator::create() records head/count/linkage and populates checkpoint_id on covered entries (unhashed — no payload_hash/chain_hash change).
  • chronicle:checkpoints:backfill — chunked, idempotent backfill of the range columns + checkpoint_id for pre-1.11 ledgers (--dry-run supported).
  • IntegrityVerifier::verifySegment() and CheckpointChainVerifier (fast O(checkpoints) attestation; signature path shared via the VerifiesCheckpointSignature trait).
  • New failure reasons: checkpoint_chain_broken, checkpoint_head_mismatch, segment_discontinuous, anchor_invalid.
  • chronicle:verify incremental modes: --checkpoints-only, --from-checkpoint=/--to-checkpoint=, --since-last-checkpoint, --resume.
  • External anchoring: the AnchorProvider contract, AnchorReceipt, CheckpointDigest, and AnchorManager (opt-in chronicle.anchoring, enabled defaults false); NullAnchor; and Rfc3161TimestampAnchor (offline openssl ts -verify; adds symfony/process).
  • Anchoring pipeline: a queued, retryable AnchorCheckpointJob dispatched after the checkpoint commits (anchor failure never rolls a checkpoint back); the shared CheckpointAnchorer writes pending → anchored/failed.
  • Anchor commands: chronicle:checkpoint --anchor, chronicle:anchor:retry {--status=failed} (pending/failed), chronicle:anchor:verify {--checkpoint=}, and chronicle:verify --anchors.
  • Ledger order is derived from sequence everywhere (ChainHashEntry, IntegrityVerifier, EntryVerifier, EntryExporter), eliminating false chain_hash_mismatch/chain_invalid when entries share a millisecond across processes.
  • payload, payload_hash, chain_hash are NOT NULL on fresh installs.
  • CheckpointCreator signs a canonical object (id, chain_hash, algorithm, key_id, created_at, mirroring ExportSigner); verification falls back to the legacy bare-hash format, so older checkpoints still verify.
  • chronicle:prune warns that from-genesis verify won't pass post-prune and points to verifyFrom().
  • RateLimitPolicy logs a warning before rejecting an over-limit entry (audit suppression is now observable).
  • Genesis seed unified on ChainHasher::GENESIS; chronicle:install publishes migrations under their dated filenames (idempotent re-runs); checkpoint head resolved by sequence.

Fixed

  • Export ordering matches chain/verification ordering — fixes export-verification false failures under clock skew.
  • Corrected author email in composer.json and a PendingEntry docblock typo.

Security

  • Verification now detects divergence between an entry's denormalized columns (action, actor_id, metadata, diff, …) and its hash-covered payload — new code column_payload_divergence (shared ComparesEntryColumns trait).
  • Model diffs redact $hidden attributes and any $chronicleRedact/$redactedFields entries (records "[redacted]"), so secrets never enter the immutable, exportable audit diff.
  • External anchoring defeats a full internal compromise: even if an attacker rewrites the ledger and re-signs every checkpoint with a valid key (offline verify passes), chronicle:verify --anchors fails at the first anchored checkpoint.

Upgrading from 1.10

  1. php artisan migrate (additive: checkpoint range columns, an index on the existing checkpoint_id entries column, and the two new tables).
  2. php artisan chronicle:checkpoints:backfill — populates the range columns + checkpoint_id for pre-1.11 checkpoints (chunked, idempotent; --dry-run to preview). Incremental verify modes fall back to full verify until this runs.
  3. Anchoring is opt-in via chronicle.anchoring.enabled; no behavior change without it.
  4. New runtime dependency: symfony/process (a standard component, not a cloud SDK).

No artifact-format change; no re-export needed. See the Upgrade Guide.

Full Changelog: 1.10.0...1.11.0