TenantAtlas/specs/265-decision-register-approval/plan.md
ahmido 23ef20f86d feat(decision-register): implement Decision Register (spec 265) (#321)
This PR contains the committed changes for specs/265-decision-register-approval.

Commit: b5671cbf

Automated PR created by Copilot at user's request.

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #321
2026-05-02 19:02:04 +00:00

20 KiB

Implementation Plan: Decision Register & Approval Workflow v1

Branch: 265-decision-register-approval | Date: 2026-05-02 | Spec: spec.md Input: Feature specification from /specs/265-decision-register-approval/spec.md

Summary

This plan prepares one bounded operator follow-through slice over the repo's existing exception-decision truth. The implementation path is to add one new native Filament workspace page that hosts a Filament-native table or list surface, derives a decision register from FindingException, FindingExceptionDecision, current governance validity, owner or due context, and existing proof links, then launches into the current ViewFindingException detail surface for proof and action. The slice must stay on the existing exception decision domain, existing detail actions, and current audit semantics with no new persistence, no new approval engine, and no new queue or OperationRun family.

Filament remains on Livewire v4, no panel-provider registration changes are required (apps/platform/bootstrap/providers.php remains authoritative), no globally searchable resource is added, and no asset registration change is expected.

Inherited Baseline / Explicit Delta

Inherited baseline

  • FindingException and FindingExceptionDecision already provide append-only accepted-risk governance truth.
  • FindingExceptionService already owns request, approve, reject, renew, and revoke lifecycle behavior.
  • FindingExceptionsQueue already acts as the current queue-review workbench.
  • ViewFindingException already owns proof and mutation actions for the exception lifecycle.
  • CanonicalNavigationContext, OperationRunLinks, and BadgeRenderer already provide the relevant shared navigation, proof-link, and badge semantics.

Explicit delta in this plan

  • add one derived workspace Decision register page over current exception-decision truth
  • add one bounded derived row or builder seam for owner, due-date, next-action, and proof-link assembly
  • preserve the existing exception detail page as the only approval and closure surface
  • keep any review or run link optional and derived only when current repo truth already exposes it

Technical Context

Language/Version: PHP 8.4, Laravel 12, Filament v5, Livewire v4
Primary Dependencies: existing finding-exception models and services, native Filament pages, CanonicalNavigationContext, OperationRunLinks, BadgeRenderer, Pest v4
Storage: PostgreSQL via existing finding_exceptions, finding_exception_decisions, finding_exception_evidence_references, findings, audit_logs, and optional linked tenant_reviews, review_packs, and operation_runs only
Testing: Pest Unit plus Feature coverage, plus one narrow manual smoke in the later implementation loop
Validation Lanes: fast-feedback, confidence
Target Platform: existing Laravel monolith in apps/platform, admin plane only (/admin)
Project Type: Web application (Laravel monolith with Filament pages)
Performance Goals: derived DB-backed register rendering, no new Graph calls, no queue-start path, and no new cached projection
Constraints: no new persisted decision entity, no generic workflow engine, no new customer-facing register, no inline register mutations, no new run type, no duplicate truth between register and detail
Scale/Scope: one new workspace page, one bounded builder seam, one existing detail page continuity path

Likely Affected Repo Surfaces

  • apps/platform/app/Filament/Pages/Governance/DecisionRegister.php
  • apps/platform/resources/views/filament/pages/governance/decision-register.blade.php
  • apps/platform/app/Support/GovernanceDecisions/GovernanceDecisionRegisterBuilder.php
  • apps/platform/app/Models/FindingException.php
  • apps/platform/app/Models/FindingExceptionDecision.php
  • apps/platform/app/Services/Findings/FindingExceptionService.php
  • apps/platform/app/Support/Navigation/CanonicalNavigationContext.php
  • apps/platform/app/Support/OperationRunLinks.php
  • apps/platform/app/Filament/Resources/FindingExceptionResource.php
  • apps/platform/app/Filament/Resources/FindingExceptionResource/Pages/ViewFindingException.php
  • apps/platform/tests/Unit/Support/GovernanceDecisions/...
  • apps/platform/tests/Feature/Governance/...
  • apps/platform/tests/Feature/Findings/...

UI / Filament & Livewire Fit

  • Add one native Filament page under the existing admin governance navigation. The register itself must use Filament-native table or list primitives. Do not add a new resource, new detail shell, or second queue page.
  • Keep the new page read-first. It may show one dominant Open decision row action and bounded proof-link indicators, but it must not host approval, rejection, renewal, or revocation actions directly.
  • If decision-register.blade.php exists, keep it as a thin host for the native Filament surface rather than bespoke decision-row markup.
  • Keep the exception detail page as the action owner. Any new context or summary added there must deepen the chosen decision rather than restating the workspace register.
  • Keep filter state query-backed and public. Avoid private Livewire-only state for tenant and register-state filters.
  • Clearing a tenant prefilter must return the actor to the workspace-wide open register instead of leaving the page stranded on a tenant-scoped zero state.
  • Use existing badge semantics and existing action-surface rules. Do not introduce page-local semantic color logic or bespoke workflow cards if current Filament primitives and shared helpers are sufficient.
  • The finished implementation must pass docs/product/standards/list-surface-review-checklist.md, or explicitly document a narrow exception.
  • No new asset registration, panel setup, or provider registration change is planned.

RBAC / Policy Fit

  • Workspace membership remains the first gate for the register page.
  • Register rows and counts must derive only from decisions the actor can already view under the existing exception-visibility contract.
  • Existing decision actions remain on the current detail surface and keep their existing authorization paths.
  • Non-members and explicit out-of-scope tenant or record targets stay 404; in-scope members with no visible decisions in the default unfiltered register stay 403; user-applied filters that narrow an already-authorized register view to zero rows show a truthful filtered empty state.
  • Any optional related review or pack proof link must reuse current review visibility checks instead of inventing a new capability family.

Audit / Logging Fit

  • Existing append-only FindingExceptionDecision history remains the system of record.
  • Existing AuditLog entries remain authoritative for decision actions; do not create a new page-view audit stream for the register itself.
  • Existing exception detail actions keep their current audit IDs and proof semantics.

Data & Query Fit

  • Prefer one bounded builder or query helper that assembles register rows from the existing exception domain instead of storing a second decision summary.
  • Define recently closed as only current terminal exception states (rejected, revoked, superseded) whose current terminal decision timestamp is within the last 30 calendar days.
  • Derive register-state filters from current status, current decision type, validity, and bounded recent-history windows. Do not add a persisted register_state field.
  • Eager-load the current decision, tenant, finding, owner, and evidence references needed for honest rows and avoid N+1 lookups.
  • If a later implementation wants to expose related review or run links, derive them from current review or run truth only when already available. No new relation table or backfill process is allowed.

UI / Surface Guardrail Plan

  • Guardrail scope: changed surfaces
  • Native vs custom classification summary: native Filament
  • Shared-family relevance: governance decision home, proof drill-through, badge reuse, navigation continuity
  • State layers in scope: page, URL-query, detail launch context
  • Audience modes in scope: operator-MSP
  • Decision/diagnostic/raw hierarchy plan: decision-first on the register, diagnostics-second on the detail page, raw/support detail third
  • Raw/support gating plan: raw and support detail stay on the detail surface and remain secondary or capability-gated
  • One-primary-action / duplicate-truth control: register rows keep one dominant Open decision action; detail pages keep one current state-appropriate lifecycle action and must not duplicate the workspace summary
  • Handling modes by drift class or surface: review-mandatory
  • Repository-signal treatment: review-mandatory
  • Special surface test profiles: global-context-shell
  • Required tests or manual smoke: functional-core, state-contract, manual-smoke
  • Decision-first proof obligations carried into implementation: one dominant next action per row, diagnostics-secondary treatment, raw or support detail excluded from the register, and no duplicate visible decision summary between register and detail
  • List-surface review requirement: pass docs/product/standards/list-surface-review-checklist.md and record any exception explicitly
  • Exception path and spread control: none planned; any new decision engine or inline mutation surface is a scope split
  • Active feature PR close-out entry: Guardrail / Smoke Coverage

Shared Pattern & System Fit

  • Cross-cutting feature marker: yes
  • Systems touched: current finding-exception detail and queue surfaces, governance navigation, proof links, badges, and audit-aware decision disclosure
  • Shared abstractions reused: CanonicalNavigationContext, OperationRunLinks, BadgeRenderer, current exception-detail action surface, and current FindingExceptionService lifecycle rules
  • New abstraction introduced? why?: one bounded register builder is acceptable if needed to assemble row state, next action, and proof links without bloating the page class
  • Why the existing abstraction was sufficient or insufficient: the repo already has the truth and actions, but it lacks one bounded register view that uses them as a decision workflow instead of separate queue and detail fragments
  • Bounded deviation / spread control: the new builder, if introduced, must stay under Support/GovernanceDecisions/ and remain specific to this register rather than becoming a shared workflow framework

OperationRun UX Impact

  • Touches OperationRun start/completion/link UX?: yes, deep-link only when current proof already has a related run
  • Central contract reused: OperationRunLinks
  • Delegated UX behaviors: existing Open operation URL resolution only; no new toasts, notifications, or dedupe messaging
  • Surface-owned behavior kept local: the register may indicate proof-link availability, but it must not start or queue anything
  • Queued DB-notification policy: N/A
  • Terminal notification path: N/A
  • Exception path: none

Provider Boundary & Portability Fit

  • Shared provider/platform boundary touched?: no
  • Provider-owned seams: N/A
  • Platform-core seams: governance decision wording, owner or due context, proof-link semantics, and action labels
  • Neutral platform terms / contracts preserved: decision register, open decision, recently closed, due review, follow-up needed
  • Retained provider-specific semantics and why: none new
  • Bounded extraction or follow-up path: N/A

Constitution Check

GATE: Must pass before implementation begins and again before merge.

  • Inventory-first: PASS. Register rows stay derived from existing exception and finding truth.
  • Read/write separation: PASS. The register remains read-only and current detail surfaces keep all mutations.
  • Graph contract path: PASS. No Graph or provider call is introduced.
  • Deterministic capabilities: PASS. Existing capability checks remain authoritative.
  • Workspace and tenant isolation: PASS. Counts and rows derive after tenant and capability filtering.
  • RBAC-UX plane separation: PASS. Everything stays in /admin; no /system expansion.
  • Destructive action discipline: PASS. Existing risky actions stay on current detail pages and keep their current confirmation rules.
  • Global search: PASS. No new resource or search result is introduced.
  • OperationRun / Ops-UX: PASS by non-use. Existing run links stay optional proof only.
  • Data minimization: PASS. The register shows only decision-first row content.
  • Test governance: PASS. Proof stays in focused Unit and Feature lanes plus later manual smoke.
  • Proportionality / no premature abstraction: PASS. The plan adds one page and at most one bounded builder instead of a workflow platform.
  • Persisted truth: PASS. No new table or projection store is allowed.
  • Behavioral state: PASS. Any register-state key stays derived and local.
  • Shared pattern first / UI semantics / Filament-native UI: PASS. Existing detail actions, badges, and navigation helpers are reused.
  • Provider boundary: PASS. No provider/platform seam widens.
  • Filament/Laravel panel safety: PASS. Filament v5 stays on Livewire v4, provider registration remains in apps/platform/bootstrap/providers.php, and no new assets are planned.

Gate evaluation: PASS.

Test Governance Check

  • Test purpose / classification by changed surface: Unit for row assembly and state mapping, Feature for register visibility, filters, launch continuity, and detail-surface interaction boundaries
  • Affected validation lanes: fast-feedback, confidence
  • Why this lane mix is the narrowest sufficient proof: unit coverage proves the derived contract cheaply; feature coverage proves tenant-safe visibility and action-surface continuity on native Filament pages without introducing a dedicated browser family
  • 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/GovernanceDecisions/GovernanceDecisionRegisterBuilderTest.php
    • export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Governance/DecisionRegisterPageTest.php tests/Feature/Governance/DecisionRegisterAuthorizationTest.php tests/Feature/Findings/FindingExceptionDecisionRegisterNavigationTest.php tests/Feature/Findings/FindingExceptionDetailDecisionSummaryTest.php tests/Feature/Findings/FindingExceptionDecisionRegisterBoundariesTest.php
  • Fixture / helper / factory / seed / context cost risks: moderate; reuse current finding-exception factories and visible or hidden tenant context without widening to queue, review-pack, or provider-heavy defaults
  • Expensive defaults or shared helper growth introduced?: no
  • Heavy-family additions, promotions, or visibility changes: none
  • Surface-class relief / special coverage rule: global-context-shell
  • Closing validation and reviewer handoff: reviewers should re-run the focused commands above, then manually smoke the new page by opening the register, applying a tenant filter, opening one decision, confirming one dominant next action and diagnostics-secondary treatment, and returning to the same register scope. Reviewers must also verify the implementation against docs/product/standards/list-surface-review-checklist.md.
  • Budget / baseline / trend follow-up: none expected beyond a small feature-local increase
  • Review-stop questions: lane fit, hidden tenant leakage, accidental inline mutation surface, accidental second decision store, and proof-link honesty
  • Escalation path: reject-or-split for any new decision engine or persistence; otherwise none
  • Active feature PR close-out entry: Guardrail / Smoke Coverage
  • Test-governance outcome: keep

Project Structure

Documentation (this feature)

specs/265-decision-register-approval/
├── spec.md
├── plan.md
├── tasks.md
└── checklists/
    └── requirements.md

This preparation package intentionally stays on the core artifacts plus the readiness checklist. The repo already contains the relevant code, truth model, and adjacent specs, so no extra research, data-model, or contract package is required for a bounded implementation handoff.

Source Code (expected implementation surfaces)

apps/platform/app/Filament/Pages/Governance/DecisionRegister.php
apps/platform/resources/views/filament/pages/governance/decision-register.blade.php
apps/platform/app/Support/GovernanceDecisions/GovernanceDecisionRegisterBuilder.php
apps/platform/app/Models/FindingException.php
apps/platform/app/Models/FindingExceptionDecision.php
apps/platform/app/Services/Findings/FindingExceptionService.php
apps/platform/app/Support/Navigation/CanonicalNavigationContext.php
apps/platform/app/Support/OperationRunLinks.php
apps/platform/app/Filament/Resources/FindingExceptionResource.php
apps/platform/app/Filament/Resources/FindingExceptionResource/Pages/ViewFindingException.php
apps/platform/tests/Unit/Support/GovernanceDecisions/...
apps/platform/tests/Feature/Governance/...
apps/platform/tests/Feature/Findings/...

Structure Decision: keep implementation inside the existing admin-plane governance and findings seams. If a dedicated builder is needed, place it under Support/GovernanceDecisions/ and keep it local to this register.

Data / Migration Implications

  • No migration or new table is planned.
  • Prefer deriving row state, closure reason for recently closed rows, and next action from current exception-decision history and current exception fields.
  • If implementation discovers a missing piece for display, prefer a bounded derived helper or existing metadata enrichment over a new decision entity or projection store.
  • No new cache layer, backfill, or index migration should be required for v1.

Rollout Considerations

  • Filament remains v5 on Livewire v4. No panel-provider change is required, and provider registration remains in apps/platform/bootstrap/providers.php.
  • No global search change is required because the slice adds one page, not a new searchable resource.
  • No destructive action is added to the new page. Existing detail actions remain the only state-changing path and keep their current authorization and confirmation rules.
  • No new asset registration is expected.

Risk Controls

  • Reject any implementation that introduces a new GovernanceDecision model, register cache, or generic workflow engine.
  • Reject any implementation that adds inline register mutations or a second approval surface.
  • Reject any implementation that widens the slice into alerts, operations, or customer-facing decision pages.
  • Reject any implementation that invents proof by generating new reviews or runs from the register.

Implementation Phases

Phase 0 - Confirm Current Decision Truth

  • Verify the current exception lifecycle, current decision history, and current detail-surface actions in the existing models, services, pages, and tests.
  • Verify which proof links already exist and stay truthful in the current repo.

Phase 1 - Add The Derived Register Surface

  • Create the new page class and bounded register builder.
  • Derive open and recently-closed row states from current exception and decision truth only.
  • Render one dominant Open decision action per row with honest filter and empty-state behavior.

Phase 2 - Wire Detail Continuity

  • Launch into the existing ViewFindingException detail page with preserved tenant and return context.
  • Keep the detail page as the only approval and closure surface.

Phase 3 - Harden Proof And Boundary Rules

  • Add optional evidence, review, or run links only when current proof already exists.
  • Confirm hidden-tenant omission, 404 vs 403, and no second decision state.

Phase 4 - Validate And Stop

  • Run the focused Unit and Feature proof.
  • Perform one narrow register-to-detail smoke path.
  • Confirm no new panel, no new asset strategy, no new queue or run family, and no new persistence.

Why This Plan Is Narrow Enough

The repo already has append-only exception-decision history, existing detail-page approval actions, and the shared helpers needed for navigation, proof links, and badge semantics. This plan adds only the missing workspace register over that truth and preserves the current detail page as the owning action surface. Everything broader remains explicitly deferred.