# Document Lifecycle — Improvement & Growth Plan

> Package: capell-app/document-lifecycle · Kind: package · Tier: premium · Product group: Capell Operations · Bundle: operations · Status: Draft

## 1. Snapshot

Document Lifecycle is an admin/database package that turns "controlled documents" (terms, privacy, policy, operational docs) into evidence-grade records: a registry (`Document`), immutable versioned publications with SHA-256 content hashes (`DocumentPublication`), and acceptance records that extend the existing `legal_acceptances` table (`DocumentAcceptance`). Behaviour lives entirely in Actions (`RegisterDocumentAction`, `PublishDocumentAction`, `PublishDocumentFromPublishingRevisionAction`, `ResolveLatestDocumentPublicationAction`, `ComputeDocumentContentHashAction`, `RecordDocumentAcceptanceAction`, plus status/health helpers); the admin surface (`DocumentResource` + two read-only relation managers) can register documents, publish manual versions, archive/restore records, and record authenticated-admin acceptances. A `PublishingRevision::created` listener auto-publishes when a registered document's underlying model is published in Publishing Studio. Requires `capell-app/core`, `capell-app/admin`, `capell-app/publishing-studio`; supports `capell-app/customer-portal` via a portal self-service feed (`DocumentLifecyclePortalSelfServiceItemProvider`). Current marketplace summary: _"Version-pinned acceptance evidence for controlled documents in Capell: register documents, publish hashed versions from admin or Publishing Studio, and record who accepted which version and when."_ Screenshots: `capell.json` now declares the extension card plus **4** committed admin PNG captures from `docs/screenshots.json`.

## 2. Improvements (existing functionality)

- **Done/Shipped: model factories are real.** `DocumentFactory`, `DocumentPublicationFactory`, and `DocumentAcceptanceFactory` exist under `database/factories`, model `factory()` methods resolve them, and coverage exercises state helpers such as `draft`, `active`, `archived`, `metadata`, `document`, `fromRevision`, `acceptor`, `subject`, and `forPublication`. — `database/factories/*`, `tests/Feature/DocumentLifecycleFactoryTest.php`
- **Done/Shipped: health check has a real diagnostic report.** `DocumentLifecycleHealthCheck::report()` delegates to `BuildDocumentLifecycleHealthReportAction`, which verifies the three required tables, `legal_acceptances` extension columns, protected audit-table registration, morph map aliases, and the Publishing Studio revision listener. Coverage locks both the passing report and a missing-table failure. — `src/Health/DocumentLifecycleHealthCheck.php`, `src/Actions/BuildDocumentLifecycleHealthReportAction.php`, `tests/Feature/DocumentLifecycleHealthReportTest.php`
- **Done/Shipped: document status labels and badge colours are enum-driven.** `DocumentStatusEnum` implements Filament `HasLabel` and `HasColor`, `DocumentResource` uses the enum for Select options and badge colours, and coverage verifies translated labels plus `gray`/`success`/`warning` colours. — `src/Enums/DocumentStatusEnum.php`, `src/Filament/Resources/Documents/DocumentResource.php`, `tests/Feature/DocumentLifecycleStatusActionTest.php`
- **Done/Shipped: acceptance evidence is request-context-pure.** `RecordDocumentAcceptanceAction` accepts explicit `ipAddress` and `userAgent` parameters and hashes those values independently of the live request; when callers omit them, the Action does not silently read request state. — `src/Actions/RecordDocumentAcceptanceAction.php`, `tests/Feature/RecordDocumentAcceptanceTest.php`
- **Done/Shipped: `legal_acceptances` rollback only reverses package-owned additions.** The migration now tracks whether it added the publication/hash columns and package-named indexes, guards drops with `Schema::hasIndex(...)`, and preserves legacy-owned columns/indexes with matching or differently named indexes. — `database/migrations/2026_05_10_190868_03_extend_legal_acceptances_for_document_lifecycle.php`, `tests/Feature/ExtendLegalAcceptancesMigrationTest.php`
- **Done/Shipped: `DocumentAcceptance` mass-assignment is explicit.** The evidence table model now declares a field-level `$fillable` list instead of `$guarded = []`, matching the sibling models and keeping writes constrained even outside Actions. — `src/Models/DocumentAcceptance.php`
- **Index `Document.documentable` for reverse lookups used by the listener.** `PublishDocumentFromPublishingRevisionAction::documentForRevision()` queries `whereIn('documentable_type', ...)->whereIn('documentable_id', ...)`. The migration indexes `documentable` via `nullableMorphs(...idx)` (good), but the hot path also `->first()` with no ordering — fine, yet confirm the composite type+id morph index is actually hit (it is, via the generated index). No change needed if verified; otherwise add `index(['documentable_type','documentable_id'])`. — `database/migrations/2026_05_10_190868_01_create_document_lifecycle_documents_table.php:22` — **S**
- **Document the auto-publish coupling.** The `PublishingRevision::created` global listener fires `PublishDocumentFromPublishingRevisionAction` on _every_ revision row app-wide and early-returns when no registered `Document` matches. That's correct but invisible; one extra query per revision create. Note it in `docs/overview.md` and consider a config flag to disable, for hosts that don't use auto-publish. — `src/Providers/DocumentLifecycleServiceProvider.php:146-153` — **S**

## 3. Missing Features (gaps)

Mapped to `capabilities[]`: `document-lifecycle`, `document-lifecycle-admin`, `document-lifecycle-acceptance`, `document-lifecycle-customer-portal-document-feed`.

- **Admin lifecycle actions (closed for the feasible Now slice).** The admin now uses Action-backed create, publish-version, archive, restore, and record-authenticated-admin-acceptance flows, all gated through the existing `DocumentPolicy` update/create checks and backed by package Actions rather than inline lifecycle writes. Void acceptance, bulk acceptance operations, and richer acceptor targeting remain part of the export/re-acceptance work below rather than this completed row. — ties to `document-lifecycle-admin`.
- **Done/Shipped: retention / expiry policy.** Controlled documents now have `review_due_at` and `expires_at` timestamps, the admin resource exposes both dates, and the package registers/schedules `capell:document-lifecycle:archive-expired` daily through `ArchiveExpiredDocumentsAction` so active expired documents automatically move to Archived. Portal reminder surfacing remains optional future UX polish.
- **Done/Shipped: re-acceptance enforcement and outstanding report foundation.** `RequiresDocumentReAcceptanceAction` answers whether a subject is missing the latest publication/hash, and the acceptances relation manager now exports an outstanding-acceptances CSV grouped by the latest known subject acceptance. Reminder scheduling remains Later depth.
- **Done/Shipped: acceptance evidence CSV export.** `BuildDocumentAcceptanceEvidenceCsvAction` exports a document acceptance ledger as CSV, and the acceptances relation manager exposes an `Export evidence CSV` header action with an optional publication/version selector. Bulk publish/archive and PDF certificates remain separate Later depth.
- **Done/Shipped: signed JSON acceptance certificate.** `BuildDocumentAcceptanceCertificateAction` bundles the acceptance, document, publication hash, acceptor/subject, timestamp, IP/user-agent hashes, legal bundle values, and metadata into a JSON payload signed with an HMAC-SHA256 signature derived from the host app key. The acceptances relation manager exposes a row-level `Download signed JSON` action for exportable proof bundles. PDF certificates remain optional future polish rather than a remaining roadmap requirement.
- **Done/Shipped: public frontend surface is reconciled.** The package manifest intentionally declares only `admin` and `console` surfaces; the Customer Portal self-service feed remains an authenticated supported integration, not package-owned anonymous public output. Public consent widgets can still be built by host apps using package Actions.
- **Done/Shipped: diagnostics health report (see §2).** `DocumentLifecycleHealthCheck::report()` now exposes a real install/registration report for Diagnostics consumers.
- **Done/Shipped: per-version diff / content snapshot.** New publications now store a normalized `content_snapshot` and `content_snapshot_format` in publication metadata, including Publishing Studio `after_payload` snapshots. `BuildDocumentPublicationDiffAction` exports a JSON diff against the previous publication, and the publications relation manager exposes a `Download diff JSON` row action. Existing historical publications without snapshots honestly report `snapshot_available: false` in the export.

## 4. Issues / Risks

- **Done/Shipped: manifest frontend surface is reconciled.** `surfaces` is now `["admin","console"]`, `providers.frontend` is empty, and overview docs state that the Customer Portal feed is authenticated self-service data rather than package-owned public frontend output. — `capell.json`, `docs/overview.md`, `tests/Feature/DocumentLifecycleManifestTest.php`
- **Done/Shipped: critical health check asserts install and wiring state.** `DocumentLifecycleHealthCheck::report()` returns a typed report for table, column, protected-table, morph-map, and revision-listener wiring; package tests cover success and failure cases. — `src/Health/DocumentLifecycleHealthCheck.php`, `src/Actions/BuildDocumentLifecycleHealthReportAction.php`, `tests/Feature/DocumentLifecycleHealthReportTest.php`
- **Done/Shipped: factories resolve from models.** `database/factories` contains factories for all three models and tests prove `Document::factory()`, `DocumentPublication::factory()`, and `DocumentAcceptance::factory()` resolve correctly. — `composer.json`, `database/factories/*`, `tests/Feature/DocumentLifecycleFactoryTest.php`
- **Done/Shipped: off-request acceptance evidence can be explicit.** Queue/console callers can pass `ipAddress` and `userAgent`; the Action no longer reaches into the current request behind the caller's back. — `src/Actions/RecordDocumentAcceptanceAction.php`, `tests/Feature/RecordDocumentAcceptanceTest.php`
- **Done/Shipped: asymmetric migration rollback is fixed.** The rollback path now guards and ownership-checks package-added columns/indexes, including legacy pre-existing table cases. — `migrations/...03_extend_legal_acceptances...php`, `tests/Feature/ExtendLegalAcceptancesMigrationTest.php`
- **PHP constraint drift.** `composer.json` requires `php: ^8.3` while the Capell baseline is PHP 8.4. Harmless (broader floor) but inconsistent with siblings; confirm intentional. — `composer.json`
- **Done/Shipped: portal feed is scoped to the current portal account.** The Customer Portal self-service test creates a second acceptance for another portal account id and verifies only the current account's document item is returned. The feed still exposes acceptance/publication ids to the owner of the record, which is acceptable for authenticated account history. — `src/Support/CustomerPortal/DocumentLifecyclePortalSelfServiceItemProvider.php`, `tests/Feature/CustomerPortalDocumentFeedTest.php`
- **Done/Shipped: admin query budget is defended.** The admin index now has regression coverage that renders `ListDocuments` with publication counts and asserts select queries stay within `performance.adminQueryBudget`. — `capell.json` (performance), `tests/Feature/DocumentLifecycleAdminTest.php`
- **Done/Shipped: locale coverage extends beyond English.** `resources/lang/es/navigation.php` mirrors the package navigation/action/message keys so admin and portal strings have a second bundled locale. Portal feed and admin strings remain translation-keyed.
- **Test gaps (covered vs not):** Covered — publish/hash/immutability (`DocumentPublicationTest`), revision listener + documentable repointing, acceptance recording + config fallback and explicit IP/UA hashing (`RecordDocumentAcceptanceTest`), portal feed scoping + manifest (`CustomerPortalDocumentFeedTest`), admin visibility/forbidden/permitted/query budget (`DocumentLifecycleAdminTest`), schema + legacy-table adoption + protected-table/morph registration (`DocumentLifecycleSchemaTest`), nav + manifest contribution, health report behavior, factory usage, migration rollback, and enum labels/colours. **Not covered** — bulk evidence export, re-acceptance reporting, retention/expiry command execution, signed evidence JSON download, and content snapshot/diff export.

## 5. Marketplace & Selling

**Critique.** The manifest `summary` and composer `description` are identical-ish and feature-list-flat ("registry, publication metadata, hashes, and acceptance evidence"). They state _what is stored_, not _what the buyer gets_ (defensible compliance evidence). Neither mentions the actual differentiator: tamper-evident, version-pinned proof of _which_ document version a user accepted, wired automatically to Publishing Studio. Composer `description` is also truncated vs the manifest (drops "for Capell CMS").

**Improved 1-sentence summary:**

> Tamper-evident proof of which document version was published and who accepted it — controlled documents, immutable hashed versions, and a court-ready acceptance ledger for Capell.

**Improved 3–4 sentence description:**

> Document Lifecycle turns terms, privacy, and policy documents into auditable records: every publication is version-pinned and content-hashed, and every acceptance captures the exact version, hash, actor, and timestamp. It hooks into Publishing Studio so a published revision automatically becomes an immutable document publication — no custom audit layer required. Acceptance evidence surfaces in the admin and (with Customer Portal) in each account's self-service history, and the tables are registered as protected so the audit trail can't be casually deleted. Built for compliance-sensitive Capell sites that must answer "which version did this user agree to, and when?"

**Screenshot/media status.** Manifest declares the extension card plus **4** committed admin captures from `docs/screenshots.json` (index, edit, publications RM, acceptances RM). Remaining optional media depth is a video/GIF of the auto-publish-on-revision flow, which is the most compelling demo.

**Pricing / tier / bundle positioning.** `premium` in the `operations` bundle, `Capell Operations` group — appropriate; compliance/governance is a willingness-to-pay feature. Strengthen by cross-selling on dependencies: hard-requires `publishing-studio` (anchor the "auto-publish" story to it), `supports` `customer-portal` (the acceptance-history feed is a portal upsell). Natural Extension-Suite framing: an "Operations / Compliance" suite = Document Lifecycle + Publishing Studio + Diagnostics + Password Policy (already the README "Best Used With" set). The retention/reminder/e-signature features in §3 justify the premium tier and a possible add-on tier.

**Differentiators / value props / target buyer.** Differentiator: content-hash immutability + automatic version pinning from Publishing Studio + protected audit tables — most CMS "terms acceptance" is an unversioned boolean. Target buyer: ops/compliance/legal owners of regulated or contract-heavy Capell sites (memberships, healthcare, finance, B2B SaaS portals) who need defensible "who accepted what version when" evidence.

**Keywords/tags (8–12):** `document-lifecycle`, `compliance`, `audit-trail`, `terms-of-service`, `consent-management`, `content-hash`, `versioning`, `legal`, `governance`, `acceptance-evidence`, `customer-portal`, `publishing`.

## 6. Prioritized Roadmap

| Item                                                                                                                                                                                                                                                 | Bucket | Effort | Impact | Section ref                                                                                                                                                                                          |
| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | ------ | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Ship model factories; fix/remove `database/factories` autoload                                                                                                                                                                                       | Done   | S      | High   | §2, §4                                                                                                                                                                                               |
| Real health-check `report()` Action + health test                                                                                                                                                                                                    | Done   | M      | High   | §2, §3, §4                                                                                                                                                                                           |
| Reconcile `frontend` surface: drop it or ship safe consent component                                                                                                                                                                                 | Done   | S→L    | High   | §3, §4                                                                                                                                                                                               |
| Capture 4 admin screenshots; sync `marketplace.screenshots[]` to manifest                                                                                                                                                                            | Done   | S      | High   | §1, §5 — closed 2026-06-06: four Capell runner PNG captures are committed and promoted into marketplace media.                                                                                       |
| Make `RecordDocumentAcceptanceAction` request-context-pure (ip/UA params)                                                                                                                                                                            | Done   | S      | Med    | §2, §4                                                                                                                                                                                               |
| Fix asymmetric `legal_acceptances` migration `down()` + rollback test                                                                                                                                                                                | Done   | S      | Med    | §2, §4                                                                                                                                                                                               |
| Rewrite manifest `summary` + composer `description`                                                                                                                                                                                                  | Done   | S      | Med    | §5 — 2026-06-04: replaced court-ready/immutable phrasing with shipped admin, Publishing Studio, and acceptance-evidence wording; manifest test now locks composer/marketplace copy.                  |
| Admin lifecycle Actions (Publish / Archive / Record acceptance buttons)                                                                                                                                                                              | Done   | M      | High   | §3                                                                                                                                                                                                   |
| Done/Shipped: Bulk export of acceptance evidence (CSV) per document/version. Evidence: `BuildDocumentAcceptanceEvidenceCsvAction`, acceptances relation manager header action, and `document-lifecycle-evidence-export`.                             | Done   | M      | High   | §3                                                                                                                                                                                                   |
| Done/Shipped: Re-acceptance enforcement + outstanding-acceptances report. Evidence: `RequiresDocumentReAcceptanceAction`, `BuildOutstandingDocumentAcceptancesCsvAction`, relation manager CSV action, and `document-lifecycle-reacceptance-report`. | Done   | M      | High   | §3                                                                                                                                                                                                   |
| Portal-feed cross-account isolation negative test                                                                                                                                                                                                    | Done   | S      | Med    | §4                                                                                                                                                                                                   |
| `DocumentStatusEnum` → `HasLabels` + badge colours                                                                                                                                                                                                   | Done   | S      | Med    | §2, §4                                                                                                                                                                                               |
| Retention / expiry + review-due scheduled transition                                                                                                                                                                                                 | Done   | M      | High   | §3 — 2026-06-06: added review/expiry timestamps, admin fields/table columns, `ArchiveExpiredDocumentsAction`, a registered command, and a daily schedule.                                            |
| Signed acceptance certificate (PDF/JSON proof bundle)                                                                                                                                                                                                | Done   | L      | High   | §3 — 2026-06-06: shipped HMAC-signed JSON proof bundles through `BuildDocumentAcceptanceCertificateAction` and a row-level admin download action. PDF certificate rendering remains optional polish. |
| Per-version content snapshot + diff view                                                                                                                                                                                                             | Done   | L      | Med    | §3 — 2026-06-06: new publications store normalized snapshots in metadata, and admins can download JSON diffs between publication versions.                                                           |
| Locale coverage beyond `en`; admin query-budget regression test                                                                                                                                                                                      | Done   | S      | Low    | §4 — 2026-06-06: added bundled Spanish translations and a manifest-budgeted admin index query regression test.                                                                                       |

Row movement in the 2026-06-04 follow-up: **Admin lifecycle Actions (Publish / Archive / Record acceptance buttons)** and **Fix asymmetric `legal_acceptances` migration `down()` + rollback test** moved to `Done`. The remaining screenshot captures, exports, re-acceptance, retention/expiry, signed evidence, and content-diff rows stay outside the closed Now slice.