Automated PR to merge `267-artifact-lifecycle-retention` into `platform-dev`. Created by Copilot. Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #323
39 KiB
Implementation Plan: Governance Artifact Lifecycle & Retention v1
Branch: 267-artifact-lifecycle-retention | Date: 2026-05-03 | Spec: spec.md
Input: Feature specification from /specs/267-artifact-lifecycle-retention/spec.md
Note: This template is filled in by the /speckit.plan command. See .specify/scripts/ for helper scripts.
Summary
Prepare one bounded shared lifecycle and retention contract over existing governance artifacts that already exist in repo truth: evidence snapshots, review packs, stored reports, and accepted-risk decision history. The implementation path is to extend the current governance-artifact truth path rather than invent a generic artifact registry, then adopt that contract on the current evidence, review, review-pack, customer-workspace, and signed-download surfaces while keeping stored-report and decision-history adoption headless at the model or aggregate seam in this slice.
This slice stays explicitly narrow. Filament remains v5 on Livewire v4, panel-provider registration stays in apps/platform/bootstrap/providers.php, no new globally searchable resource is introduced, and no asset registration change is expected. The plan does not reopen Spec 262, does not widen billing or closure truth, does not introduce a purge engine or workflow console, and does not force a new Stored Reports or decision-record browsing UI. If hold or deletion-request persistence widens beyond current owning records, the workflow outcome flips to split and the shipped slice stops at read-only lifecycle truth plus existing download audit.
Inherited Baseline / Explicit Delta
Inherited baseline
ArtifactTruthPresenteralready owns shared artifact-truth envelopes and compressed outcomes for evidence and review-pack truth rendered acrossEvidenceSnapshot,TenantReview, andReviewPacksurfaces.EvidenceSnapshotResource,TenantReviewResource,ReviewPackResource, andCustomerReviewWorkspacealready render current operator-facing governance artifact truth or linked artifact context on native Filament surfaces.ReviewPackServicealready distinguishes future start blocking from retained-history access throughWorkspaceCommercialLifecycleResolver, andReviewPackDownloadControlleralready preserves ready-pack downloads while a workspace is suspended read-only.TenantReviewLifecycleServicealready owns publish, archive, and successor-cycle transitions and invalidates derived artifact-truth cache entries.EvidenceSnapshotServicealready owns direct expiration and audit logging for evidence snapshots.StoredReportalready persists report fingerprints and prior fingerprints, andPruneStoredReportsCommandalready applies age-based retention without a dedicated operator UI.FindingExceptionplus append-onlyFindingExceptionDecisionalready provide accepted-risk decision history, with existing Spec 265 surfaces remaining unchanged in this slice.
Explicit delta in this plan
- Extend the current governance-artifact truth contract so every in-scope artifact can answer immutable reference, lifecycle role, retention state, and allowed or blocked next action.
- Keep the first visible adoption on existing evidence, tenant-review, review-pack, customer-workspace, and signed-download surfaces only.
- Add stored-report and decision-history adoption through current services, query seams, and aggregate-level findings seams only.
- Keep any new hold, release-hold, or deletion-request mutation scoped to existing detail surfaces and current-table persistence only. If that cannot be done without widening into a registry or purge workflow, split those mutations and ship read-only lifecycle truth first.
Technical Context
Language/Version: PHP 8.4, Laravel 12
Primary Dependencies: Filament v5, Livewire v4, Pest v4, ArtifactTruthPresenter, WorkspaceCommercialLifecycleResolver, ReviewPackService, TenantReviewLifecycleService, FindingExceptionService, shared badge and RBAC helpers
Storage: PostgreSQL via existing evidence_snapshots, tenant_reviews, review_packs, stored_reports, finding_exceptions, finding_exception_decisions, workspace and tenant membership tables, and audit_logs; no generic artifact table planned
Testing: Pest v4 Unit plus focused Feature coverage; browser proof is exception-only if native Filament and controller tests cannot prove the UI or action state cheaply
Validation Lanes: fast-feedback, confidence
Target Platform: Laravel monolith in apps/platform, using the existing admin plane, tenant-scoped Filament resources, and the existing signed review-pack download route
Project Type: Web application (Laravel monolith with Filament panels)
Performance Goals: DB-only render for the affected read surfaces, no new Graph calls, no new queue or OperationRun family, and reuse of the existing request-scoped derived-state cache
Constraints: no purge engine, no workspace or tenant closure flow, no billing or subscription truth work, no support-access governance package, no generic artifact registry UI, no workflow engine, no portal rewrite, no reopening Spec 262, no new asset registration, and no prep-step edits outside specs/267-artifact-lifecycle-retention/
Scale/Scope: 3 existing operator-facing artifact families plus 1 canonical read-only workspace surface and 1 signed download route, with headless contract adoption on 2 additional persisted artifact families and focused extensions to existing aggregate and current-surface test suites
Likely Affected Repo Surfaces
apps/platform/app/Support/Ui/GovernanceArtifactTruth/ArtifactTruthPresenter.phpas the existing shared contract that already covers evidence, reviews, and review packs but not stored reports or decision records.apps/platform/app/Models/EvidenceSnapshot.php,apps/platform/app/Models/ReviewPack.php,apps/platform/app/Models/StoredReport.php,apps/platform/app/Models/FindingException.php, andapps/platform/app/Models/FindingExceptionDecision.phpas the artifact-owning records and current lifecycle anchors.apps/platform/app/Models/TenantReview.phpplusapps/platform/app/Services/TenantReviews/TenantReviewLifecycleService.phpas the review-owned context surface that points to the current export artifact without becoming a retention family.apps/platform/app/Services/Evidence/EvidenceSnapshotService.php,apps/platform/app/Services/ReviewPackService.php,apps/platform/app/Services/TenantReviews/TenantReviewLifecycleService.php, andapps/platform/app/Services/Findings/FindingExceptionService.phpas the current artifact mutation owners, review-context owners, and audit boundaries.apps/platform/app/Filament/Resources/EvidenceSnapshotResource.php,apps/platform/app/Filament/Resources/TenantReviewResource.php,apps/platform/app/Filament/Resources/TenantReviewResource/Pages/ViewTenantReview.php, andapps/platform/app/Filament/Resources/ReviewPackResource.phpas the current detail and registry surfaces that already render outcome summaries.apps/platform/app/Filament/Pages/Reviews/CustomerReviewWorkspace.phpas the current canonical read-only consumption surface that already distinguishes scan-first availability from deeper detail actions.apps/platform/app/Models/FindingException.php,apps/platform/app/Models/FindingExceptionDecision.php, andapps/platform/app/Services/Findings/FindingExceptionService.phpas the accepted-risk aggregate seams that may adopt the shared contract without reopening current Spec 265 surfaces.apps/platform/app/Http/Controllers/ReviewPackDownloadController.phpas the existing ready-pack, signed-download, and download-audit seam.apps/platform/app/Console/Commands/PruneStoredReportsCommand.phpplus the stored-report-producing services underServices/PermissionPosture/andServices/EntraAdminRoles/as the current retention path for stored reports.- Existing related tests under
apps/platform/tests/Feature/Evidence/,apps/platform/tests/Feature/ReviewPack/,apps/platform/tests/Feature/Reviews/,apps/platform/tests/Feature/TenantReview/,apps/platform/tests/Feature/Governance/,apps/platform/tests/Feature/Findings/,apps/platform/tests/Feature/PermissionPosture/, andapps/platform/tests/Feature/EntraAdminRoles/, plusapps/platform/tests/Feature/Concerns/BuildsGovernanceArtifactTruthFixtures.php.
UI / Filament & Livewire Fit
- Evidence snapshots, tenant-review detail summaries, review packs, and the customer review workspace already sit on native Filament v5 surfaces under Livewire v4. This slice should stay inside those surfaces instead of adding a new panel, portal shell, or artifact-management console.
EvidenceSnapshotResource,TenantReviewResource, andReviewPackResourcealready declare read-only registry-report action surfaces and already setprotected static bool $isGloballySearchable = false;. That posture stays unchanged for this slice.- The visible lifecycle contract should continue to flow through the existing
Outcome summary, compressed outcome badges, and current context-link sections. Do not create a second page-local lifecycle badge map or duplicate summary block. - Existing destructive-like actions already show the expected Filament pattern: evidence snapshot
expire, review-packexpire, and tenant-reviewarchive_reviewalready live on existing surfaces with->action(...),->requiresConfirmation(), and server-side authorization. Any newPlace hold,Release hold, orRequest deletionaction must follow that same pattern and must stay offCustomerReviewWorkspaceandDecisionRegister. - Stored reports and accepted-risk decisions do not justify a new operator browsing surface in this slice. Stored reports should stay headless, and decision-history adoption should stay inside the current aggregate and findings-service seams without changing current Spec 265 surfaces.
- No new panel-provider, global-search, or asset-registration work is planned. If implementation later proves a shared asset is necessary anyway, deployment remains the normal
cd apps/platform && php artisan filament:assetspath.
RBAC / Policy Fit
- Workspace and tenant membership remain isolation boundaries. Non-members or wrong-scope actors must keep receiving
404, while in-scope members missing the relevant capability keep receiving403. - Existing controller and resource behavior already demonstrates the intended boundary:
ReviewPackDownloadControllerchecks tenant access first, capability second, and only then artifact state. - The slice should reuse the current capability registry and policy/service owners rather than inventing a new lifecycle capability family. Existing
REVIEW_PACK_VIEW,REVIEW_PACK_MANAGE,TENANT_REVIEW_VIEW,TENANT_REVIEW_MANAGE,EVIDENCE_VIEW, andFINDING_EXCEPTION_*checks remain authoritative. - Suspended read-only posture remains a business-state overlay, not a retention-state proxy.
ReviewPackServicealready blocks new pack starts throughWorkspaceCommercialLifecycleResolverwhileReviewPackDownloadTestproves that ready-pack downloads remain allowed. - Decision-record adoption must preserve the current Spec 265 audience boundary by leaving
DecisionRegisterandViewFindingExceptionunchanged in this slice. Accepted-risk lifecycle mapping stays on the aggregate and current findings-service seams.
Audit / Logging Fit
- Existing artifact-sensitive actions already have audit anchors that this slice should reuse rather than replace:
ReviewPackDownloaded,EvidenceSnapshotExpired, tenant-review publish or archive events, and finding-exception request, approval, renewal, rejection, and revocation events. ReviewPackDownloadControlleralready records artifact, actor, workspace, tenant, source surface, and operation-run context. The lifecycle contract should extend that audit language rather than creating a second download ledger.EvidenceSnapshotServiceandReviewPackServicealready own audit-backed lifecycle mutation paths, whileTenantReviewLifecycleServiceremains review-owned context. If hold or deletion-request actions are added in v1, they should stay on the current artifact owners with stable action IDs and without widening into cross-family orchestration.- Stored-report creation already has telemetry and pruning already has command coverage, but there is no dedicated operator mutation surface today. This slice should not create a new stored-report audit subsystem just to compensate for the absence of a browsing UI.
- Passive page renders stay non-audited. Audit remains tied to explicit artifact opens, downloads, or lifecycle mutations.
Data & Query Fit
- The shared contract must stay derived-first. Extend the current artifact-truth envelope and compression path before considering any new persistence, and do not create a generic artifact super-table or shadow projection.
- Current repo-real retention anchors already exist and should be reused:
EvidenceSnapshot.expires_at,ReviewPack.expires_at,ReviewPack.status,StoredReport.created_atplusstored_reports.retention_days, and append-onlyFindingExceptionDecision.decided_atplusFindingException.current_decision_idand status or validity on the parent exception. - Current immutable or historical anchors already exist per family: evidence snapshot fingerprints, review fingerprints, review-pack fingerprint and SHA-256, stored-report fingerprint plus
previous_fingerprint, and decision-record ID plusdecided_athistory on append-only rows. ReviewPackServicealready distinguishes reusable current packs from expired direct access through fingerprint lookup andexpires_at > now(). The new contract should preserve that query truth instead of duplicating it.CustomerReviewWorkspaceand current review-pack or review-detail surfaces already derive “what remains readable now” from existing relations and capabilities. The new contract should feed those surfaces, not replace them with a new cross-artifact query model.- Stored-report adoption should stay in current service and command seams.
PruneStoredReportsCommandis the current retention executor, so stored-report lifecycle adoption in this slice should clarify truth and historical identity but must not turn pruning into a generic purge engine. - Accepted-risk decision adoption should attach to
FindingExceptionplusFindingExceptionDecisionand the current findings-service or aggregate seams. Do not remodel decision history into a second artifact aggregate or a decision-surface rewrite. - If hold or deletion-request markers must become persistent in v1, they should be added only to the current owning tables or aggregate roots for the concrete families involved. If that work starts to require a cross-family artifact table, a decision-surface rewrite, or shared orchestration, split the mutation slice.
UI / Surface Guardrail Plan
- Guardrail scope: changed surfaces
- Native vs custom classification summary: native Filament
- Shared-family relevance: evidence and report viewers, status messaging, lifecycle badges, download actions, canonical read-only availability, aggregate-level decision-history continuity
- State layers in scope: page, detail, URL-query
- Audience modes in scope: customer/read-only, operator-MSP, support-platform
- Decision/diagnostic/raw hierarchy plan: decision-first, diagnostics-second, support-raw-third
- Raw/support gating plan: capability-gated and hidden by default through existing detail surfaces or support diagnostics only
- One-primary-action / duplicate-truth control: evidence, review, and review-pack surfaces keep one dominant current action and one summary statement of lifecycle truth;
CustomerReviewWorkspacekeepsOpen reviewas the dominant row affordance and does not become a download console - Handling modes by drift class or surface: review-mandatory
- Repository-signal treatment: review-mandatory now, future hard-stop candidate if implementation adds a new artifact console or local lifecycle taxonomy
- Special surface test profiles: standard-native-filament, shared-detail-family
- Required tests or manual smoke: functional-core, state-contract
- Exception path and spread control: none planned; any new registry UI, page-local badge map, or browser-heavy proof demand is exception-required drift
- Active feature PR close-out entry: Guardrail
Shared Pattern & System Fit
- Cross-cutting feature marker: yes
- Systems touched:
ArtifactTruthPresenter,BadgeCatalogandBadgeRenderer, Filament action-surface declarations,WorkspaceCommercialLifecycleResolver,ReviewPackService,ReviewPackDownloadController,CustomerReviewWorkspace,FindingException,FindingExceptionDecision,FindingExceptionService,CanonicalNavigationContext, and existing audit loggers - Shared abstractions reused: current
ArtifactTruthPresenterand derived-state cache, current badge domains, current Filament resource and page action-surface contracts, current commercial lifecycle resolver, current audit paths, and existing decision register continuity helpers - New abstraction introduced? why?: none by default. The narrowest acceptable addition is a bounded lifecycle or retention mapper inside the existing governance-artifact-truth support namespace if the presenter becomes unreadable without decomposition
- Why the existing abstraction was sufficient or insufficient: the current presenter is sufficient for evidence, reviews, and review packs, but insufficient because it does not yet carry immutable reference plus retention semantics and does not support stored reports or decision records
- Bounded deviation / spread control: stored-report and decision-record adoption must extend the shared artifact-truth path or a bounded helper beneath it. They must not create a parallel report-truth or decision-truth presenter family
OperationRun UX Impact
- Touches OperationRun start/completion/link UX?: yes, by reuse only
- Central contract reused: existing review-pack start and completion UX through
OperationRunServiceandOperationUxPresenter - Delegated UX behaviors: existing queued toast, dedupe behavior, and run-link semantics stay unchanged when review-pack generation is allowed; blocked starts remain pre-run business-state blocks with no new run created
- Surface-owned behavior kept local: detail surfaces may explain lifecycle or retention truth, but they must not create a second operation-start UX language for future export or deletion flows
- Queued DB-notification policy: unchanged
- Terminal notification path: unchanged central lifecycle mechanism for existing review-pack runs only
- Exception path: none planned. Any future long-running export-before-delete or purge work belongs to a named follow-up slice
Provider Boundary & Portability Fit
- Shared provider/platform boundary touched?: yes
- Provider-owned seams: provider content inside evidence snapshots and stored-report payloads stays provider-owned evidence
- Platform-core seams: governance artifact reference, lifecycle state, retention state, historical readability, hold semantics, deletion-request semantics, and suspended read-only access rules
- Neutral platform terms / contracts preserved:
governance artifact,artifact reference,lifecycle state,retention state,historical,superseded,retained,hold,deletion requested, anddownload allowed - Retained provider-specific semantics and why: evidence completeness and provider-derived content remain inside the existing evidence or report truth summaries because this slice governs TenantPilot-owned artifacts, not provider object lifecycle
- Bounded extraction or follow-up path: follow-up-spec for provider-lifecycle expansion beyond the current evidence and report truth already in repo
Constitution Check
GATE: Must pass before implementation begins and again before merge.
- Inventory-first / snapshot truth: PASS. The slice governs TenantPilot-owned persisted artifacts and their existing historical truth, not provider live state.
- Read/write separation: PASS. The dominant rollout work is read-surface truth adoption. Any new lifecycle mutation remains local-only, confirmation-backed, audit-backed, and on existing detail surfaces.
- Graph contract path: PASS. No new Graph call path is introduced.
- Deterministic capabilities: PASS. Existing capability registries, policies, and controller checks stay authoritative.
- Workspace and tenant isolation: PASS. All current surfaces remain workspace- and tenant-safe, and canonical read-only views keep current entitlement boundaries.
- RBAC-UX plane separation: PASS. Everything stays inside current
/adminand tenant-scoped surfaces; no/systemor second artifact portal is introduced. - Destructive action discipline: PASS. Existing evidence, review-pack, and tenant-review destructive-like actions already use confirmation, and any new hold or deletion-request action must follow the same
->action(...)plus->requiresConfirmation()rule. - Global search safety: PASS.
EvidenceSnapshotResource,TenantReviewResource, andReviewPackResourcealready disable global search, no new searchable resource is added, and current pages do not become searchable. - OperationRun / Ops-UX: PASS by reuse only. Existing review-pack generation keeps the shared start UX; blocked starts create no run; no new run family is introduced.
- Data minimization: PASS. Default-visible lifecycle truth stays concise, while raw report payloads, fingerprints, support diagnostics, and detailed reasons remain secondary.
- Test governance: PASS. Proof stays in focused unit plus feature suites, with browser proof explicitly excluded from the default plan.
- Proportionality / no premature abstraction: PASS. The narrowest correct path is to extend the current presenter and existing tables or aggregates rather than adding a registry or workflow engine.
- Persisted truth: PASS. No new generic table or shadow artifact entity is planned.
- Behavioral state: PASS. Lifecycle and retention states change allowed actions, read-only interpretation, audit responsibilities, and retention semantics.
- Shared pattern first / UI semantics / Filament-native UI: PASS. Existing badges, artifact-truth presentation, detail pages, and canonical read-only workspace surface remain the shared path.
- Provider boundary: PASS. Provider evidence stays provider-owned and does not become lifecycle truth.
- Filament / Laravel planning contract: PASS. Filament v5 stays on Livewire v4, provider registration remains in
apps/platform/bootstrap/providers.php, no new globally searchable resource is added, and no asset registration change is planned.
Gate evaluation: PASS.
Post-design re-check: PASS (design artifacts: research.md, data-model.md, quickstart.md; no contracts/ directory by design because the slice reuses existing Filament resources and the existing signed download route rather than introducing a new HTTP contract).
Agent-context refresh note: intentionally not applied in this preparation run because the task is explicitly limited to specs/267-artifact-lifecycle-retention/ and the plan introduces no new language, framework, storage, or deployment technology.
Test Governance Check
- Test purpose / classification by changed surface:
Unitfor shared lifecycle and retention mapping in the artifact-truth support layer;Featurefor resource, detail, controller, command, and read-only workspace behavior - Affected validation lanes: fast-feedback, confidence
- Why this lane mix is the narrowest sufficient proof: the contract is already consumed through native Filament resources, controllers, and existing commands, so focused unit and feature coverage can prove lifecycle mapping, authorization, audit, and read-only behavior without a browser lane by default
- Narrowest proving command(s):
export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Unit/Support/GovernanceArtifactTruth/GovernanceArtifactLifecycleContractTest.phpexport PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Evidence/EvidenceSnapshotResourceTest.php tests/Feature/Evidence/EvidenceSnapshotAuditLogTest.phpexport PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/ReviewPack/ReviewPackResourceTest.php tests/Feature/ReviewPack/ReviewPackDownloadTest.php tests/Feature/ReviewPack/ReviewPackEntitlementEnforcementTest.phpexport PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/TenantReview/TenantReviewLifecycleTest.php tests/Feature/TenantReview/TenantReviewUiContractTest.php tests/Feature/Reviews/CustomerReviewWorkspacePackAccessTest.php tests/Feature/Reviews/CustomerReviewWorkspaceLaunchLinksTest.phpexport PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Findings/FindingExceptionRenewalTest.php tests/Feature/Findings/FindingExceptionRevocationTest.phpexport PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/PermissionPosture/StoredReportModelTest.php tests/Feature/PermissionPosture/PruneStoredReportsCommandTest.php tests/Feature/EntraAdminRoles/StoredReportFingerprintTest.phpexport PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail bin pint --dirty --format agent
- Fixture / helper / factory / seed / context cost risks: moderate but contained; reuse
BuildsGovernanceArtifactTruthFixtures, existing review-pack and customer-workspace fixtures, and current finding-exception setup helpers instead of inventing a new governance matrix harness - Expensive defaults or shared helper growth introduced?: no; any new helper should stay in the existing governance-artifact-truth support layer or current family fixtures and remain opt-in
- Heavy-family additions, promotions, or visibility changes: none planned; browser proof stays out of the default plan
- Surface-class relief / special coverage rule: standard-native-filament relief for evidence, review, and review-pack surfaces; shared-detail-family coverage for customer-workspace launch and aggregate decision-history continuity; controller and command tests cover signed download and stored-report pruning
- Closing validation and reviewer handoff: reviewers should rely on the exact commands above, confirm that no new searchable resource or asset change slipped in, verify that
CustomerReviewWorkspacestays read-only, verify that stored-report and decision-history adoption stayed headless, and verify that any mutation work that widened beyond current owners resolved assplit - Budget / baseline / trend follow-up: none expected beyond a modest feature-local increase in unit and feature coverage
- Review-stop questions: did the implementation add a registry or workflow layer, did browser-only assertions sneak into the default proof, did stored-report or decision-record work create a new UI, and did hold or deletion-request persistence widen beyond current tables
- Escalation path:
document-in-featurefor contained mapping or audit wording drift;reject-or-splitif implementation introduces a new artifact console, generic table, or browser-heavy default proof - Active feature PR close-out entry: Guardrail
- Why no dedicated follow-up spec is needed: routine lifecycle-truth and retention-truth upkeep should stay inside this feature unless implementation proves a structural need for purge, closure, export-before-delete, or a new artifact family surface
- Test-governance outcome: keep
Review Checklist Status
- Review checklist artifact: checklists/requirements.md
- Review outcome class:
acceptable-special-case - Workflow outcome:
keep - Test-governance outcome:
keep - Escalation rule: if mutation persistence widens beyond current owners or a current decision-surface edit becomes necessary, flip the workflow outcome to
splitbefore implementation continues
Rollout Considerations
- Roll out the shared contract first, then the current evidence, review, review-pack, customer-workspace, and signed-download surfaces, and only then the headless stored-report and aggregate-level decision-history adoption.
- Keep
CustomerReviewWorkspacescan-first, and keep current Spec 265 surfaces unchanged in this slice. Neither should become the mutation surface for lifecycle actions. - Preserve the current suspended-read-only split that the repo already proves: generation or mutation can block while retained history and already-generated downloads remain readable when the retention state allows it.
- Keep Filament v5 on Livewire v4, keep panel providers in
apps/platform/bootstrap/providers.php, keepEvidenceSnapshotResource,TenantReviewResource, andReviewPackResourcenon-searchable, and keep the current asset strategy unchanged. - Do not add a contracts package or new route in v1. Existing route names, resource URLs, and controller entry points remain the canonical surface inventory.
Risk Controls
- Reject any implementation that adds a generic artifact registry table, artifact console, workflow engine, or customer artifact portal.
- Reject any implementation that creates a second lifecycle presenter family outside the current governance-artifact-truth support layer.
- Reject any implementation that uses workspace commercial state, provider absence, review publication status, or page-local copy as a proxy for retention truth.
- Reject any implementation that places hold or deletion-request actions on
CustomerReviewWorkspace, current Spec 265 surfaces, or other list-only shells. - Reject browser-heavy proof as the default lane. Escalate to a browser smoke only if grouped actions or read-only detail behavior cannot be proven through native feature tests.
- If hold or deletion-request persistence cannot stay on current family tables or aggregate roots without widening scope, split those mutations and ship the shared read-only contract first.
Research & Design Outputs
- research.md resolves the key design choices: extend the current artifact-truth presenter, keep stored reports headless, keep decision-history adoption on current aggregate seams, and preserve suspended-read-only download semantics.
- data-model.md records the bounded derived contract, current lifecycle and retention anchors per family, likely current-table persistence touch points, and precedence rules such as hold over deletion request.
- quickstart.md provides the recommended implementation order, focused validation commands, and stop conditions for keeping the slice bounded.
- checklists/requirements.md records the preparation review outcome, workflow outcome, and test-governance outcome that implementation must preserve unless the slice is explicitly split.
- No
contracts/directory is created because this slice introduces no new HTTP route, controller family, or external integration contract.
Project Structure
Documentation (this feature)
specs/267-artifact-lifecycle-retention/
├── checklists/
│ └── requirements.md
├── plan.md
├── research.md
├── data-model.md
├── quickstart.md
└── tasks.md # Created later by /speckit.tasks, not by this plan step
This preparation package intentionally stays small. The plan, research, data-model, and quickstart artifacts are enough to drive tasks generation for a bounded implementation. A separate contracts/ package would duplicate existing route and resource truth.
Source Code (expected implementation surfaces)
apps/platform/
├── app/
│ ├── Filament/
│ │ ├── Pages/
│ │ │ └── Reviews/
│ │ │ └── CustomerReviewWorkspace.php
│ │ └── Resources/
│ │ ├── EvidenceSnapshotResource.php
│ │ ├── ReviewPackResource.php
│ │ ├── TenantReviewResource.php
│ │ └── TenantReviewResource/Pages/ViewTenantReview.php
│ ├── Http/Controllers/ReviewPackDownloadController.php
│ ├── Models/
│ │ ├── EvidenceSnapshot.php
│ │ ├── ReviewPack.php
│ │ ├── StoredReport.php
│ │ ├── TenantReview.php
│ │ ├── FindingException.php
│ │ └── FindingExceptionDecision.php
│ ├── Services/
│ │ ├── Evidence/EvidenceSnapshotService.php
│ │ ├── Findings/FindingExceptionService.php
│ │ ├── ReviewPackService.php
│ │ └── TenantReviews/TenantReviewLifecycleService.php
│ ├── Support/
│ │ └── Ui/GovernanceArtifactTruth/ArtifactTruthPresenter.php
│ └── Console/Commands/PruneStoredReportsCommand.php
├── bootstrap/providers.php
└── tests/
├── Feature/Evidence/
├── Feature/ReviewPack/
├── Feature/Reviews/
├── Feature/TenantReview/
├── Feature/Findings/
├── Feature/PermissionPosture/
├── Feature/EntraAdminRoles/
└── Unit/Support/GovernanceArtifactTruth/
Structure Decision: Laravel monolith. Implementation should stay inside the existing artifact-truth support layer plus the named current model, service, resource, page, controller, and test seams. No new panel, artifact sub-app, or generic registry package is justified.
Complexity Tracking
| Violation | Why Needed | Simpler Alternative Rejected Because |
|---|---|---|
| BLOAT-001 - bounded lifecycle and retention contract extension | The repo already has three operator-facing artifact families plus stored-report and decision-record truth that need one shared meaning for reference, lifecycle role, retention posture, and next action | Local page labels would drift immediately, while a generic artifact registry would import persistence and UI scope the current release does not need |
Proportionality Review
- Current operator problem: current artifact surfaces still make operators infer whether an artifact is current, historical, expired from direct access, still downloadable, or merely blocked by read-only workspace posture.
- Existing structure is insufficient because: the current presenter answers outcome and readiness truth for evidence, reviews, and review packs, but not immutable reference plus retention semantics, and it does not support stored reports or accepted-risk decision records.
- Narrowest correct implementation: extend the existing artifact-truth path, keep persistence local to current tables or aggregates only where needed, adopt the contract on current surfaces first, and leave stored reports headless plus decision history on current aggregates without a decision-surface rewrite.
- Ownership cost created: one bounded lifecycle vocabulary, one bounded retention vocabulary, one presenter extension, current-table persistence only where mutation is required, and focused test expansion across existing suites.
- Alternative intentionally rejected: a generic artifact registry table, artifact-management console, workflow engine, customer artifact portal, or broad purge framework was rejected as wider than current release truth.
- Release truth: current-release truth grounded in already-persisted artifacts, already-shipped suspended-read-only behavior, and already-existing accepted-risk decision history.
Implementation Phases
Phase 1 - Extend the shared contract
- Extend the governance-artifact truth support layer so
EvidenceSnapshotandReviewPackcan emit immutable reference, lifecycle state, retention state, and allowed or blocked next-action semantics from one path, whileTenantReviewconsumes linked artifact truth without becoming its own retention family. - Keep lifecycle and retention mapping bounded and family-aware. Do not create a new generic registry or polymorphic persistence layer.
- Decide the smallest acceptable current-table persistence for hold or deletion-request markers, and stop if that work starts to require a cross-family artifact store.
Phase 2 - Adopt the current operator surfaces
- Apply the contract to
EvidenceSnapshotResource,TenantReviewResource,ViewTenantReview,ReviewPackResource,CustomerReviewWorkspace, andReviewPackDownloadController. - Preserve the existing scan-first and read-first surface contracts: one dominant next action, no new console, and no duplicate lifecycle summary blocks.
- Keep the current commercial-lifecycle split: future generation or mutation may block, while retained history and already-generated downloads remain available when the retention state allows it.
Phase 3 - Add headless stored-report and decision-record adoption
- Extend the shared contract to
StoredReportand accepted-risk decision history through current services, aggregates, and tests. - Keep stored reports without a new browsing UI. Use the existing creation, fingerprint, evidence-source, and prune seams instead.
- Keep decision-history adoption inside
FindingException,FindingExceptionDecision, andFindingExceptionServicewithout reopening Spec 265 into a new console or workflow.
Phase 4 - Harden audit, RBAC, and proof
- Extend focused unit plus feature suites for lifecycle mapping, current vs historical truth, retention-state gating, signed download preservation, audit coverage, and 404 or 403 semantics.
- Confirm no new searchable resources, no provider-registration changes, no asset-registration changes, and no browser lane by default.
- Stop after the bounded contract and current-surface adoption are proven. If mutation persistence widens beyond current owners, split that work and defer it along with purge, closure, export-before-delete, or broader support-access work.
Deferred Follow-ups
- Retention & Purge Governance v1 owns irreversible deletion, purge scheduling, purge proof, and any true hard-delete execution.
- Workspace & Tenant Closure Lifecycle v1 owns closure-class behavior for broader workspace and tenant truth.
- Data Export Before Deletion v1 owns export-request workflow, completion proof, and delete preconditions for customer-owned data bundles.
- Stored Reports Surface v1 owns any future dedicated stored-report browsing and operator workflow.
- Enterprise Access Boundary & Support Access Governance v1 owns support-access approval, TTL, and customer-visible support access audit rather than artifact retention truth.
Why This Plan Is Narrow Enough
The repo already has the hard parts needed for a truthful first slice: persisted artifact families, current evidence or review or pack surfaces, a shared artifact-truth presenter for the visible families, commercial read-only gating for review-pack starts versus downloads, append-only accepted-risk decision history, and current audit paths. The plan therefore adds only the missing shared lifecycle and retention contract over those real seams, keeps stored-report and decision-history adoption tied to their current owners, leaves current Spec 265 surfaces unchanged, and leaves everything broader to explicit follow-up work.