Compliance - v1.0.0

Advanced

The compliance package is built around small interface contracts so that organizations can plug in their own implementations without forking. Every orchestrator service iterates over container-registered implementations of its corresponding interface.

Custom erasure handlers

Implement ErasureHandlerInterface:

use ArtisanPackUI\Compliance\Compliance\Contracts\ErasureHandlerInterface;
use ArtisanPackUI\Compliance\Compliance\Erasure\ErasureHandlerResult;
use Illuminate\Support\Collection;

class OrderHistoryHandler implements ErasureHandlerInterface
{
    public function getName(): string
    {
        return 'orders';
    }

    public function getDescription(): string
    {
        return 'Removes the user\'s order history from the local store.';
    }

    public function canHandle( int $userId ): bool
    {
        return \App\Models\Order::where( 'user_id', $userId )->exists();
    }

    public function findUserData( int $userId ): Collection
    {
        return \App\Models\Order::where( 'user_id', $userId )->get();
    }

    public function erase( int $userId, array $options = [] ): ErasureHandlerResult
    {
        $count = \App\Models\Order::where( 'user_id', $userId )->delete();

        return new ErasureHandlerResult(
            status:         'success',
            recordsFound:   $count,
            recordsErased:  $count,
        );
    }

    public function isReversible(): bool
    {
        return false;
    }

    public function rollback( int $userId, array $backupData ): bool
    {
        return false;
    }

    public function getEstimatedTime(): int
    {
        return 5;
    }

    public function getDataCategories(): array
    {
        return ['orders', 'transactional'];
    }
}

Register it in your service provider:

$this->app->tag( OrderHistoryHandler::class, 'compliance.erasure_handlers' );

ErasureService resolves all tagged implementations at runtime.

Custom data exporters

Implement DataExporterInterface to surface a data store in portability exports:

use ArtisanPackUI\Compliance\Compliance\Contracts\DataExporterInterface;
use Illuminate\Support\Collection;

class OrderHistoryExporter implements DataExporterInterface
{
    public function getName(): string
    {
        return 'orders';
    }

    public function getCategory(): string
    {
        return 'transactional';
    }

    public function getData( int $userId ): Collection
    {
        return \App\Models\Order::where( 'user_id', $userId )->get();
    }

    public function getSchema(): array
    {
        return [
            'id'         => 'integer',
            'created_at' => 'iso8601',
            'total'      => 'decimal',
        ];
    }

    public function transform( Collection $data ): array
    {
        return $data->map( fn ( $order ) => [
            'id'         => $order->id,
            'created_at' => $order->created_at->toIso8601String(),
            'total'      => (string) $order->total,
        ] )->all();
    }

    public function getSupportedFormats(): array
    {
        return ['json', 'csv'];
    }

    public function getRecordCount( int $userId ): int
    {
        return \App\Models\Order::where( 'user_id', $userId )->count();
    }
}

Tag it the same way:

$this->app->tag( OrderHistoryExporter::class, 'compliance.data_exporters' );

Custom compliance checks

Implement ComplianceCheckInterface:

use ArtisanPackUI\Compliance\Compliance\Contracts\ComplianceCheckInterface;
use ArtisanPackUI\Compliance\Compliance\Monitoring\CheckResult;

class ConsentExpiryCheck implements ComplianceCheckInterface
{
    public function getName(): string
    {
        return 'consent.expiry';
    }

    public function getDescription(): string
    {
        return 'Flags consent records past their expiry date that have not been renewed.';
    }

    public function getCategory(): string
    {
        return 'consent';
    }

    public function getRegulations(): array
    {
        return ['gdpr'];
    }

    public function run(): CheckResult
    {
        // …
    }

    public function isEnabled(): bool
    {
        return true;
    }

    public function getRecommendedSchedule(): string
    {
        return 'daily';
    }

    public function getSeverity(): string
    {
        return 'medium';
    }

    public function getRemediation(): string
    {
        return 'Trigger a re-consent flow for affected users.';
    }
}

Tag with compliance.checks.

Custom report types

Implement ReportTypeInterface, tag with compliance.report_types, and reference it by name in compliance:generate-report --type=… or in a ScheduledComplianceReport row.

Implement ConsentStorageInterface and bind it in your service provider:

$this->app->bind(
    \ArtisanPackUI\Compliance\Compliance\Contracts\ConsentStorageInterface::class,
    \App\Compliance\AlternativeConsentStorage::class,
);

ConsentManager resolves the interface from the container, so the override flows everywhere.

Compliance dashboard

The package ships a default-deny Gate so that ComplianceDashboardController is locked down out of the box. Override in your AuthServiceProvider:

Gate::define( 'viewComplianceDashboard', fn ( $user ) => $user->hasRole( 'compliance-officer' ) );

When pairing with artisanpack-ui/rbac, the Gate integration ensures viewComplianceDashboard resolves against an RBAC permission of the same slug if one exists.