{"id":51385625,"url":"https://github.com/laravel-chronicle/filament","last_synced_at":"2026-07-03T20:02:46.411Z","repository":{"id":366386643,"uuid":"1273637937","full_name":"laravel-chronicle/filament","owner":"laravel-chronicle","description":"Read-only Filament panel plugin for Chronicle","archived":false,"fork":false,"pushed_at":"2026-06-30T14:53:24.000Z","size":232,"stargazers_count":2,"open_issues_count":7,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-06-30T16:27:33.061Z","etag":null,"topics":["audit-log","cryptography","event-log","filament","filament-plugin","laravel","security"],"latest_commit_sha":null,"homepage":"https://laravel-chronicle.github.io/docs/filament-installation","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/laravel-chronicle.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-18T18:09:58.000Z","updated_at":"2026-06-30T14:53:29.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/laravel-chronicle/filament","commit_stats":null,"previous_names":["laravel-chronicle/filament"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/laravel-chronicle/filament","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/laravel-chronicle%2Ffilament","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/laravel-chronicle%2Ffilament/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/laravel-chronicle%2Ffilament/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/laravel-chronicle%2Ffilament/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/laravel-chronicle","download_url":"https://codeload.github.com/laravel-chronicle/filament/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/laravel-chronicle%2Ffilament/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35099548,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-03T02:00:05.635Z","response_time":110,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["audit-log","cryptography","event-log","filament","filament-plugin","laravel","security"],"created_at":"2026-07-03T20:02:45.762Z","updated_at":"2026-07-03T20:02:46.403Z","avatar_url":"https://github.com/laravel-chronicle.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Chronicle for Filament\n\nA **read-only** Filament panel plugin for [`laravel-chronicle/core`](https://github.com/laravel-chronicle/core):\nbrowse your tamper-evident audit ledger and **cryptographically verify it - across the\nwhole chain, a single entry, or a selected segment - without ever being able to rewrite\nhistory**. It is the only Filament audit plugin with cryptographic verification at chain,\nentry, and segment granularity.\n\nThe panel cannot create, edit, or delete entries: every mutation ability is denied at the\nresource and the Gate, there are no create/edit/delete routes, and Chronicle's `Entry`\nmodel is immutable at the data layer. The UI is defence in depth on top of that.\n\n## Screenshots\n\n\u003c!-- TODO: add before tagging 1.0.0 --\u003e\n- ![Entry list with verification badges](docs/screenshots/list.png) \u003c!-- placeholder --\u003e\n- ![Entry detail (infolist)](docs/screenshots/view.png) \u003c!-- placeholder --\u003e\n- ![Verify actions: chain, entry, segment](docs/screenshots/verify.png) \u003c!-- placeholder --\u003e\n- ![Verification health widget](docs/screenshots/widget.png) \u003c!-- placeholder --\u003e\n- ![Anchor detail + coverage widget](docs/screenshots/anchoring.png) \u003c!-- placeholder --\u003e\n- ![Anchor column and Verify-anchor action](docs/screenshots/anchor-verify.png) \u003c!-- placeholder --\u003e\n- ![Signing-key column and Active/Retired badge](docs/screenshots/signing-key.png) \u003c!-- placeholder --\u003e\n- ![Key-ring summary widget](docs/screenshots/key-ring-widget.png) \u003c!-- placeholder --\u003e\n- ![Erasure column with On-hold indicator](docs/screenshots/erasure-column.png) \u003c!-- placeholder --\u003e\n- ![Subject erasure detail (state, KEK, erased_at, hold)](docs/screenshots/erasure-detail.png) \u003c!-- placeholder --\u003e\n- ![Erase subject (GDPR) confirmation modal](docs/screenshots/erase-modal.png) \u003c!-- placeholder --\u003e\n- ![Crypto-shredding stats widget](docs/screenshots/crypto-shredding-widget.png) \u003c!-- placeholder --\u003e\n\n## Requirements\n\n| Requirement              | Supported                              |\n|--------------------------|----------------------------------------|\n| PHP                      | 8.2, 8.3, 8.4, 8.5                     |\n| Laravel                  | 12, 13                                 |\n| Filament                 | 4, 5                                   |\n| `laravel-chronicle/core` | 1.13+                                  |\n| PHP extensions           | `ext-sodium`, `ext-openssl` (required) |\n\n## Installation\n\n```bash\ncomposer require laravel-chronicle/filament\nphp artisan vendor:publish --tag=chronicle-filament-migrations # publish migrations\nphp artisan migrate   # run migrations\n```\n\nOptionally publish the config:\n\n```bash\nphp artisan vendor:publish --tag=chronicle-filament-config\n```\n\n## Panel registration\n\nRegister the plugin on your Filament panel provider:\n\n```php\nuse Chronicle\\Filament\\ChronicleFilamentPlugin;\nuse Filament\\Panel;\n\npublic function panel(Panel $panel): Panel\n{\n    return $panel\n        -\u003eplugin(\n            ChronicleFilamentPlugin::make()\n                -\u003enavigationGroup('Audit')\n                -\u003enavigationSort(99)\n                -\u003eslug('chronicle')\n                -\u003everification(true)\n                -\u003eanchoring(true) // enable the external-anchor surfaces (defaults to following core)\n                -\u003esigningKeys(true) // enable the signing-key surfaces (column/filter, detail badge, key-ring widget)\n                // Gate the verify actions independently of read access:\n                -\u003eauthorize(fn (): bool =\u003e auth()-\u003euser()?-\u003ecan('verify-chronicle') ?? false)\n                // Optional: override actor/subject display labels (falls back to core's resolver):\n                -\u003elabelResolver(fn (string $type, string $id): ?string =\u003e null),\n        );\n}\n```\n\nRead (view) access is governed by your panel's normal authorization; the `-\u003eauthorize()`\nclosure gates only the chain/entry/segment **verify** actions.\n\n## Configuration reference\n\n`config/chronicle-filament.php`:\n\n| Key                                    | Default                      | Purpose                                                                                                                                                                                                                                                                                             |\n|----------------------------------------|------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| `entry_model`                          | `\\Chronicle\\Entry\\Entry`     | The Eloquent model the resource reads. Point at a subclass to add accessors/relations. With core \u003e= 1.13 the override is honored end-to-end by core's reader and verifiers when `chronicle.models.entry` matches.                                                                                   |\n| `navigation.group`                     | `'Chronicle'`                | Navigation group label.                                                                                                                                                                                                                                                                             |\n| `navigation.sort`                      | `null`                       | Navigation sort order.                                                                                                                                                                                                                                                                              |\n| `slug`                                 | `'chronicle-entries'`        | Resource route slug.                                                                                                                                                                                                                                                                                |\n| `verification.enabled`                 | `true`                       | Master toggle for badges, verify actions, and the health widget.                                                                                                                                                                                                                                    |\n| `verification.queue_threshold`         | `1000`                       | Chain/segment verifies covering more than this many entries are dispatched to the queue instead of running synchronously.                                                                                                                                                                           |\n| `verification.store.connection`        | `null`                       | Database connection for the plugin-owned verification result store. `null` = the app's default connection.                                                                                                                                                                                          |\n| `anchoring.enabled`                    | `null`                       | Master toggle for the anchor surfaces (detail section, Verify-anchor, anchor column/filter, coverage widget). `null` follows core's `chronicle.anchoring.enabled`; set `true`/`false` to force. Hidden everywhere when core anchoring is off.                                                       |\n| `anchoring.verify_all_queue_threshold` | `1000`                       | The \"Verify all anchors\" action runs synchronously at or below this many in-scope checkpoints, and is dispatched to the queue above it.                                                                                                                                                             |\n| `signing_keys.enabled`                 | `true`                       | Master toggle for the signing-key surfaces - the \"Signing key\" column + filter, the ViewEntry Active/Retired badge, and the key-ring widget. Display-only: signature verification stays inside core's chain/entry verifiers; this surface only shows which key signed each entry.                   |\n| `crypto_shredding.enabled`             | `null`                       | Master toggle for the read-only crypto-shredding surfaces (erasure column/filter, ViewEntry erasure detail, `subject.erased` trail, the `CryptoShreddingWidget`). `null` follows core's `chronicle.encryption.enabled`; set `true`/`false` to force. Hidden everywhere when core encryption is off. |\n| `erasure.enabled`                      | `false`                      | Master toggle for the **Erase subject (GDPR)** action - the panel's only write. **Off by default**; the action is absent and non-routable unless this is `true`, independent of the visibility toggle.                                                                                              |\n| `erasure.allow_hold_override`          | `false`                      | Whether a legal hold may be overridden during an erase. **Off by default**; when off, an active `LegalHold` always blocks the erase. When on, the confirmation modal adds a distinct, required override checkbox.                                                                                   |\n\nThe fluent plugin methods (`navigationGroup`, `navigationSort`, `slug`, `cluster`,\n`verification`, `anchoring`, `signingKeys`, `authorize`, `labelResolver`) override the matching config values per panel.\n\n## Verification\n\nVerification is always **deliberate** - nothing verifies on a read or render path. From the\npanel you can:\n\n- **Verify chain** (header action) - the full ledger from genesis.\n- **Verify entry** (row action) - a single entry.\n- **Verify segment** (bulk action) - the selected span, anchored on the enclosing signed\n  checkpoints via core's `verifyEntryRange` (never on a selected row's stored hash).\n\nResults are written to a plugin-owned, DB-backed store and surfaced as status badges\n(`Verified` / `Failed` / `Unverified` / `Stale`) and a health widget. Verifies covering more\nthan `verification.queue_threshold` entries run on the queue and notify you on completion.\n\n## External anchoring\n\nWhen `laravel-chronicle/core` is configured to anchor its signed checkpoints to an\nexternal service - an RFC 3161 timestamp authority, or the\n[`laravel-chronicle/anchor-s3`](https://github.com/laravel-chronicle/anchor-s3) adapter -\nthe panel surfaces that anchoring **read-only**:\n\n- **Detail view** - the entry-detail \"External anchoring\" section lists the entry's\n  checkpoint's anchors (provider, status badge, `anchored_at`, reference, and a copyable,\n  truncated proof) from stored status only.\n- **Verify anchor** - a deliberate row/header action that runs core's\n  `AnchorVerifier::checkpointHasValidAnchor()` for the entry's checkpoint and records the\n  outcome. Never runs on render.\n- **Verify all anchors** - a deliberate list-page header action that runs\n  `AnchorVerifier::verify()` over the in-scope checkpoints, synchronously below\n  `anchoring.verify_all_queue_threshold` and on the queue above it (notifying you on\n  completion).\n- **Anchor column + filter** - an \"Anchor\" badge column and a state filter derived from\n  the stored anchor status.\n- **Coverage widget** - a stats widget summarising anchored-vs-total checkpoints, pending\n  and failed counts, and the latest anchored time, from cheap table aggregates.\n\nAll of these are **deliberate and read-only** - no anchor is ever written, and no provider\nverification runs on a read or render path.\n\nAnchoring **requires core anchoring to be configured** and producing `anchored` rows. Enable\nthe surfaces with `-\u003eanchoring(true)` (or `anchoring.enabled` in config); by default they\nfollow core's `chronicle.anchoring.enabled` and stay hidden when it is off, showing entries\nas `Unanchored`.\n\n## Signing keys (key rotation)\n\nCore signs each checkpoint with the active key from its signing **key ring**, and keeps\nretired keys in the ring so historical artifacts still verify. When you rotate keys (core's\n`chronicle:key:rotate`), the panel surfaces *which key signed each entry* - **read-only**:\n\n- **Signing-key column** - a \"Signing key\" column showing the entry's `checkpoint.key_id`\n  as a state-colored badge: `Active` (the current key), `Retired` (a superseded key that\n  still verifies), or `Unsigned` (an entry with no checkpoint). The algorithm and a\n  retired-key reassurance show in the tooltip.\n- **Filter by signing key** - narrow the audit log to a specific key; options come from\n  core's `KeyRing::all()`, labelled `algorithm:keyId` with the active key marked `(active)`.\n- **Detail badge** - the entry-detail \"Signature\" section shows an `Active`/`Retired` badge\n  beside the key id, with a hint that a retired key still verifies the entries it signed.\n- **Key-ring widget** - a stats widget summarising the active key, the number of keys in the\n  ring, how many are retired, and the active key's checkpoint coverage, from cheap aggregates.\n\nThis surface is **display-only** - signature verification is already part of chain/entry\nverification (core's verifiers call `KeyRing::resolve`), so v1.2 adds **no** new verify\naction and nothing signs or verifies on a read or render path. Retired keys deliberately\nstay in the ring to verify historical artifacts; see core's\n[Signing \u0026 Keys](https://github.com/laravel-chronicle/core/blob/main/docs/signing-and-keys.md)\nguide on key rotation.\n\nEnable the surfaces with `-\u003esigningKeys(true)` (or `signing_keys.enabled` in config); they\nare on by default.\n\n## Crypto-shredding \u0026 GDPR erasure\n\nWhen `laravel-chronicle/core` encrypts entry payloads per subject (crypto-shredding), the\npanel surfaces that state **read-only** - it reads key/hold *status* only and never unwraps a\nDEK, decrypts, or erases on a render path:\n\n- **Erasure column + filters** - an \"Erasure\" badge column (`Encrypted` / `Erased` /\n  `Not encrypted`) with an \"On hold\" indicator and a KEK / `erased_at` / hold tooltip, plus\n  filters by erasure state and by legal hold. Primed once per page in a flat two queries -\n  no per-row lookup, no DEK unwrap.\n- **ViewEntry erasure detail** - the subject's erasure state, wrapping `kek_id`, `erased_at`,\n  and active legal-hold status (reason + placed-at). For an erased subject it states plainly\n  that the personal data is permanently unreadable **while the entry stays intact and still\n  verifies**.\n- **`subject.erased` trail** - an \"Erasure proofs only\" table preset filtering to\n  `action = 'subject.erased'`, surfacing the requester and reason from the proof's metadata.\n- **Crypto-shredding widget** - a stats widget summarising encrypted subjects, erased\n  subjects, subjects on active legal hold, and the active KEK id, from cheap aggregates.\n\nEnable with `-\u003ecryptoShredding(true)` (or `crypto_shredding.enabled` in config); by default it\nfollows core's `chronicle.encryption.enabled` and stays hidden when encryption is off (every\nsubject reads `Not encrypted`).\n\n### Erasing a subject (GDPR Article 17)\n\nThe panel has exactly **one** write: an opt-in, **off-by-default**, separately-authorized\n**Erase subject** action for fulfilling a GDPR Article 17 erasure request.\n\n**The reframed invariant - read this first.** Erasing a subject does **not** modify or delete\nany ledger entry. It calls core's `Chronicle::eraseSubject()`, which destroys the subject's\ndata-encryption key (crypto-shredding) and **appends** a hash-chained, signed `subject.erased`\nproof. Existing entries - and their hashes and signatures - are never touched and still verify.\nThe subject's personal data simply becomes permanently unreadable. The ledger stays immutable.\n\nThe action ships **disabled**, and it is impossible to enable or trigger by accident:\n\n- **Off by default.** The action is absent and non-routable unless you turn erasure on with\n  `-\u003eerasure(true)` (or `erasure.enabled` in config). This is independent of the read-only\n  crypto-shredding visibility toggle.\n- **Separately authorized.** Even when enabled, the action stays hidden until you grant it\n  with `-\u003eeraseAuthorize(fn (Model $record) =\u003e /* your policy */)`. This gate **defaults to\n  deny** and is never the verify/read gate - unset, the action can never run.\n- **Confirmation + reason.** The modal requires typing the exact `subject_type:subject_id`\n  and a mandatory free-text reason. It is single-subject only - there is no bulk erase.\n- **Legal hold.** If the subject is under an active `LegalHold`, the erase is blocked. You can\n  permit an override only by turning on `-\u003eeraseAllowHoldOverride(true)` **and** accepting a\n  distinct override checkbox in the modal; the override is then recorded in the proof metadata.\n- **Idempotent.** Re-erasing an already-shredded subject is a friendly no-op, not an error.\n\nRegister the gates on the plugin:\n\n```php\nuse Illuminate\\Database\\Eloquent\\Model;\n\nChronicleFilamentPlugin::make()\n    -\u003eerasure() // enable the action (off by default)\n    -\u003eeraseAuthorize(fn (Model $record): bool =\u003e auth()-\u003euser()?-\u003ecan('erase-subjects') ?? false)\n    -\u003eeraseAllowHoldOverride(); // optional: permit a doubly-confirmed hold override\n```\n\nSee core's crypto-shredding, GDPR-erasure, and legal-hold guides for the underlying mechanics:\n\u003c!-- TODO: cross-link the exact core doc URLs (Crypto-Shredding / GDPR erasure / Legal hold). --\u003e\n\n## Theming\n\nThe panel uses Filament's native CSS variables and utility classes only - no npm, no asset\ncompilation, and no required custom theme. It adopts your panel's primary color and dark-mode\nsettings automatically.\n\n## License\n\nThe MIT License (MIT). See [LICENSE](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flaravel-chronicle%2Ffilament","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flaravel-chronicle%2Ffilament","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flaravel-chronicle%2Ffilament/lists"}