feat: add resource policy authorization proof matrix
Some checks failed
PR Fast Feedback / fast-feedback (pull_request) Failing after 1m7s

This commit is contained in:
Ahmed Darrazi 2026-06-23 09:44:21 +02:00
parent e1a7752f40
commit eaf28229e0
8 changed files with 1451 additions and 16 deletions

View File

@ -1235,7 +1235,7 @@ public static function makeEditNavigationAction(): Actions\Action
Actions\Action::make('edit') Actions\Action::make('edit')
->label('Edit') ->label('Edit')
->icon('heroicon-o-pencil-square') ->icon('heroicon-o-pencil-square')
->visible(fn (ProviderConnection $record): bool => static::recordAllowsProviderCapability($record, Capabilities::PROVIDER_MANAGE)) ->visible(fn (ProviderConnection $record): bool => static::recordAllowsProviderMemberVisibility($record))
->url(fn (ProviderConnection $record): string => static::getUrl('edit', ['record' => $record], tenant: static::resolveTenantForRecord($record))) ->url(fn (ProviderConnection $record): string => static::getUrl('edit', ['record' => $record], tenant: static::resolveTenantForRecord($record)))
) )
->preserveVisibility() ->preserveVisibility()
@ -1250,7 +1250,7 @@ public static function makeCheckConnectionAction(): Actions\Action
->label('Check connection') ->label('Check connection')
->icon('heroicon-o-check-badge') ->icon('heroicon-o-check-badge')
->color('success') ->color('success')
->visible(fn (ProviderConnection $record): bool => static::recordAllowsProviderExecution($record)) ->visible(fn (ProviderConnection $record): bool => static::recordAllowsProviderMemberVisibility($record, requiresEnabled: true))
->action(function (ProviderConnection $record, StartVerification $verification, $livewire = null): void { ->action(function (ProviderConnection $record, StartVerification $verification, $livewire = null): void {
static::handleCheckConnectionAction($record, $verification, $livewire); static::handleCheckConnectionAction($record, $verification, $livewire);
}) })
@ -1267,7 +1267,7 @@ public static function makeInventorySyncAction(): Actions\Action
->label('Inventory sync') ->label('Inventory sync')
->icon('heroicon-o-arrow-path') ->icon('heroicon-o-arrow-path')
->color('info') ->color('info')
->visible(fn (ProviderConnection $record): bool => static::recordAllowsProviderExecution($record)) ->visible(fn (ProviderConnection $record): bool => static::recordAllowsProviderMemberVisibility($record, requiresEnabled: true))
->action(function (ProviderConnection $record, ProviderOperationStartGate $gate, $livewire = null): void { ->action(function (ProviderConnection $record, ProviderOperationStartGate $gate, $livewire = null): void {
static::handleProviderOperationAction( static::handleProviderOperationAction(
record: $record, record: $record,
@ -1298,7 +1298,7 @@ public static function makeComplianceSnapshotAction(): Actions\Action
->label('Compliance snapshot') ->label('Compliance snapshot')
->icon('heroicon-o-shield-check') ->icon('heroicon-o-shield-check')
->color('info') ->color('info')
->visible(fn (ProviderConnection $record): bool => static::recordAllowsProviderExecution($record)) ->visible(fn (ProviderConnection $record): bool => static::recordAllowsProviderMemberVisibility($record, requiresEnabled: true))
->action(function (ProviderConnection $record, ProviderOperationStartGate $gate, $livewire = null): void { ->action(function (ProviderConnection $record, ProviderOperationStartGate $gate, $livewire = null): void {
static::handleProviderOperationAction( static::handleProviderOperationAction(
record: $record, record: $record,
@ -1332,7 +1332,7 @@ public static function makeSetDefaultAction(): Actions\Action
->requiresConfirmation() ->requiresConfirmation()
->visible(fn (ProviderConnection $record): bool => (bool) $record->is_enabled ->visible(fn (ProviderConnection $record): bool => (bool) $record->is_enabled
&& ! $record->is_default && ! $record->is_default
&& static::recordAllowsProviderCapability($record, Capabilities::PROVIDER_MANAGE)) && static::recordAllowsProviderMemberVisibility($record))
->action(function (ProviderConnection $record, AuditLogger $auditLogger): void { ->action(function (ProviderConnection $record, AuditLogger $auditLogger): void {
$tenant = static::resolveTenantForRecord($record); $tenant = static::resolveTenantForRecord($record);
@ -1380,7 +1380,7 @@ public static function makeEnableDedicatedOverrideAction(string $source, ?string
->color('primary') ->color('primary')
->requiresConfirmation() ->requiresConfirmation()
->visible(fn (ProviderConnection $record): bool => $record->connection_type !== ProviderConnectionType::Dedicated ->visible(fn (ProviderConnection $record): bool => $record->connection_type !== ProviderConnectionType::Dedicated
&& static::recordAllowsProviderCapability($record, Capabilities::PROVIDER_MANAGE_DEDICATED)) && static::recordAllowsProviderMemberVisibility($record))
->form([ ->form([
TextInput::make('client_id') TextInput::make('client_id')
->label('Dedicated app (client) ID') ->label('Dedicated app (client) ID')
@ -1453,7 +1453,7 @@ public static function makeRotateDedicatedCredentialAction(?string $modalDescrip
->color('primary') ->color('primary')
->requiresConfirmation() ->requiresConfirmation()
->visible(fn (ProviderConnection $record): bool => $record->connection_type === ProviderConnectionType::Dedicated ->visible(fn (ProviderConnection $record): bool => $record->connection_type === ProviderConnectionType::Dedicated
&& static::recordAllowsProviderCapability($record, Capabilities::PROVIDER_MANAGE_DEDICATED)) && static::recordAllowsProviderMemberVisibility($record))
->form([ ->form([
TextInput::make('client_id') TextInput::make('client_id')
->label('Dedicated app (client) ID') ->label('Dedicated app (client) ID')
@ -1508,7 +1508,7 @@ public static function makeDeleteDedicatedCredentialAction(?string $modalDescrip
->requiresConfirmation() ->requiresConfirmation()
->visible(fn (ProviderConnection $record): bool => $record->connection_type === ProviderConnectionType::Dedicated ->visible(fn (ProviderConnection $record): bool => $record->connection_type === ProviderConnectionType::Dedicated
&& $record->credential()->exists() && $record->credential()->exists()
&& static::recordAllowsProviderCapability($record, Capabilities::PROVIDER_MANAGE_DEDICATED)) && static::recordAllowsProviderMemberVisibility($record))
->action(function (ProviderConnection $record, ProviderConnectionMutationService $mutations): void { ->action(function (ProviderConnection $record, ProviderConnectionMutationService $mutations): void {
$tenant = static::resolveTenantForRecord($record); $tenant = static::resolveTenantForRecord($record);
@ -1542,7 +1542,7 @@ public static function makeRevertToPlatformAction(string $source, ?string $modal
->color('gray') ->color('gray')
->requiresConfirmation() ->requiresConfirmation()
->visible(fn (ProviderConnection $record): bool => $record->connection_type === ProviderConnectionType::Dedicated ->visible(fn (ProviderConnection $record): bool => $record->connection_type === ProviderConnectionType::Dedicated
&& static::recordAllowsProviderCapability($record, Capabilities::PROVIDER_MANAGE_DEDICATED)) && static::recordAllowsProviderMemberVisibility($record))
->action(function (ProviderConnection $record, ProviderConnectionMutationService $mutations, AuditLogger $auditLogger) use ($source): void { ->action(function (ProviderConnection $record, ProviderConnectionMutationService $mutations, AuditLogger $auditLogger) use ($source): void {
$tenant = static::resolveTenantForRecord($record); $tenant = static::resolveTenantForRecord($record);
@ -1600,7 +1600,7 @@ public static function makeEnableConnectionAction(): Actions\Action
->color('success') ->color('success')
->requiresConfirmation() ->requiresConfirmation()
->visible(fn (ProviderConnection $record): bool => ! (bool) $record->is_enabled ->visible(fn (ProviderConnection $record): bool => ! (bool) $record->is_enabled
&& static::recordAllowsProviderCapability($record, Capabilities::PROVIDER_MANAGE)) && static::recordAllowsProviderMemberVisibility($record))
->action(function (ProviderConnection $record, AuditLogger $auditLogger): void { ->action(function (ProviderConnection $record, AuditLogger $auditLogger): void {
$tenant = static::resolveTenantForRecord($record); $tenant = static::resolveTenantForRecord($record);
@ -1676,7 +1676,7 @@ public static function makeDisableConnectionAction(): Actions\Action
->color('danger') ->color('danger')
->requiresConfirmation() ->requiresConfirmation()
->visible(fn (ProviderConnection $record): bool => (bool) $record->is_enabled ->visible(fn (ProviderConnection $record): bool => (bool) $record->is_enabled
&& static::recordAllowsProviderCapability($record, Capabilities::PROVIDER_MANAGE)) && static::recordAllowsProviderMemberVisibility($record))
->action(function (ProviderConnection $record, AuditLogger $auditLogger): void { ->action(function (ProviderConnection $record, AuditLogger $auditLogger): void {
$tenant = static::resolveTenantForRecord($record); $tenant = static::resolveTenantForRecord($record);
@ -1723,17 +1723,27 @@ public static function makeDisableConnectionAction(): Actions\Action
->apply(); ->apply();
} }
private static function recordAllowsProviderExecution(ProviderConnection $record): bool private static function recordAllowsProviderMemberVisibility(ProviderConnection $record, bool $requiresEnabled = false): bool
{ {
return static::recordAllowsProviderCapability($record, Capabilities::PROVIDER_RUN, requiresEnabled: true); $tenant = static::resolveTenantForRecord($record);
$user = auth()->user();
if (! $tenant instanceof ManagedEnvironment || ! $user instanceof User || ! $user->canAccessTenant($tenant)) {
return false;
}
if ($requiresEnabled && ! (bool) $record->is_enabled) {
return false;
}
return true;
} }
private static function recordAllowsProviderCapability( private static function recordAllowsProviderCapability(
ProviderConnection $record, ProviderConnection $record,
string $capability, string $capability,
bool $requiresEnabled = false, bool $requiresEnabled = false,
): bool ): bool {
{
$tenant = static::resolveTenantForRecord($record); $tenant = static::resolveTenantForRecord($record);
$user = auth()->user(); $user = auth()->user();

View File

@ -0,0 +1,161 @@
<?php
declare(strict_types=1);
use App\Filament\Resources\BackupScheduleResource;
use App\Filament\Resources\ProviderConnectionResource;
use App\Models\BackupSchedule;
use App\Models\ManagedEnvironment;
use App\Models\ProviderConnection;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
pest()->browser()->timeout(60_000);
uses(RefreshDatabase::class);
function spec402BrowserLoginUrl(User $user, ManagedEnvironment $tenant, string $redirect): string
{
return route('admin.local.smoke-login', [
'email' => $user->email,
'tenant' => $tenant->external_id,
'workspace' => $tenant->workspace->slug,
'redirect' => $redirect,
]);
}
function spec402BrowserPath(string $url): string
{
$parts = parse_url($url);
return ($parts['path'] ?? '/admin').(isset($parts['query']) ? '?'.$parts['query'] : '');
}
function spec402BrowserVisibleActionsDisabledScript(array $labels): string
{
$encodedLabels = json_encode(array_values($labels), JSON_THROW_ON_ERROR);
return <<<JS
(() => {
const labels = {$encodedLabels};
const candidates = Array.from(document.querySelectorAll('button, a, [role="button"], [role="menuitem"]'));
const visible = (element) => {
const style = window.getComputedStyle(element);
return style.display !== 'none'
&& style.visibility !== 'hidden'
&& ! element.hidden
&& Boolean(element.offsetWidth || element.offsetHeight || element.getClientRects().length);
};
const normalizedText = (element) => [
element.textContent || '',
element.getAttribute('aria-label') || '',
element.getAttribute('title') || '',
].join(' ').replace(/\\s+/g, ' ').trim();
const disabled = (element) => {
const disabledOwner = element.closest('[disabled], [aria-disabled="true"]');
const href = element.tagName === 'A' ? element.getAttribute('href') : null;
return Boolean(disabledOwner)
|| element.disabled === true
|| element.getAttribute('aria-disabled') === 'true'
|| (element.tagName === 'A' && (href === null || href === '' || href === '#'));
};
return labels.every((label) => candidates.some((element) => (
visible(element)
&& normalizedText(element).includes(label)
&& disabled(element)
)));
})()
JS;
}
it('Spec402 smokes provider resource authorization boundaries in the browser', function (): void {
[$owner, $tenant] = createUserWithTenant(role: 'owner', workspaceRole: 'owner');
$connection = ProviderConnection::factory()
->platform()
->verifiedHealthy()
->create([
'managed_environment_id' => (int) $tenant->getKey(),
'workspace_id' => (int) $tenant->workspace_id,
'provider' => 'microsoft',
'display_name' => 'Spec 402 Browser Provider',
'is_enabled' => true,
]);
$backupSchedule = BackupSchedule::query()->create([
'managed_environment_id' => (int) $tenant->getKey(),
'name' => 'Spec 402 Browser Backup Schedule',
'is_enabled' => true,
'timezone' => 'UTC',
'frequency' => 'daily',
'time_of_day' => '01:00:00',
'days_of_week' => null,
'policy_types' => ['deviceConfiguration'],
'include_foundations' => true,
'retention_keep_last' => 30,
]);
$providerPath = spec402BrowserPath(ProviderConnectionResource::getUrl('view', [
'record' => $connection,
'environment_id' => (int) $tenant->getKey(),
], panel: 'admin'));
$backupPath = spec402BrowserPath(BackupScheduleResource::getUrl('index', panel: 'admin', tenant: $tenant));
visit(spec402BrowserLoginUrl($owner, $tenant, $providerPath))
->resize(1440, 1000)
->waitForText('Spec 402 Browser Provider')
->assertNoJavaScriptErrors()
->assertNoConsoleLogs();
[$readonly] = createUserWithTenant(tenant: $tenant, role: 'readonly', workspaceRole: 'readonly');
$readonlyPage = visit(spec402BrowserLoginUrl($readonly, $tenant, $backupPath))
->resize(1440, 1000)
->waitForText($backupSchedule->name)
->assertDontSee('Run now')
->assertNoJavaScriptErrors()
->assertNoConsoleLogs();
expect(\App\Models\OperationRun::query()
->where('managed_environment_id', (int) $tenant->getKey())
->where('type', 'backup.schedule.execute')
->count())->toBe(0);
visit(spec402BrowserLoginUrl($readonly, $tenant, $providerPath))
->resize(1440, 1000)
->waitForText('Spec 402 Browser Provider')
->assertSee('Run provider verification')
->click('[aria-label="More"]')
->wait(1)
->assertSee('Edit')
->assertSee('Inventory sync')
->assertSee('Compliance snapshot')
->assertScript(spec402BrowserVisibleActionsDisabledScript([
'Run provider verification',
'Edit',
'Inventory sync',
'Compliance snapshot',
]), true)
->assertNoJavaScriptErrors()
->assertNoConsoleLogs();
[$otherUser, $otherTenant] = createUserWithTenant(role: 'owner', workspaceRole: 'owner');
visit(spec402BrowserLoginUrl($otherUser, $otherTenant, $backupPath))
->resize(1440, 1000)
->assertScript('document.body.innerText.includes("404") || document.body.innerText.includes("Not Found") || document.body.innerText.includes("No access")', true)
->assertNoJavaScriptErrors()
->assertNoConsoleLogs();
visit('/system')
->resize(1440, 1000)
->assertScript('document.body.innerText.includes("404") || document.body.innerText.includes("Not Found") || document.body.innerText.includes("No access")', true)
->assertNoJavaScriptErrors()
->assertNoConsoleLogs();
});

View File

@ -3,12 +3,17 @@
use App\Filament\Resources\ProviderConnectionResource; use App\Filament\Resources\ProviderConnectionResource;
use App\Filament\Resources\ProviderConnectionResource\Pages\ListProviderConnections; use App\Filament\Resources\ProviderConnectionResource\Pages\ListProviderConnections;
use App\Filament\Resources\ProviderConnectionResource\Pages\ViewProviderConnection; use App\Filament\Resources\ProviderConnectionResource\Pages\ViewProviderConnection;
use App\Models\ProviderConnection; use App\Jobs\ProviderComplianceSnapshotJob;
use App\Jobs\ProviderConnectionHealthCheckJob;
use App\Jobs\ProviderInventorySyncJob;
use App\Models\ManagedEnvironment; use App\Models\ManagedEnvironment;
use App\Models\OperationRun;
use App\Models\ProviderConnection;
use App\Support\Auth\UiTooltips; use App\Support\Auth\UiTooltips;
use Filament\Facades\Filament; use Filament\Facades\Filament;
use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Queue;
use Livewire\Livewire; use Livewire\Livewire;
uses(RefreshDatabase::class); uses(RefreshDatabase::class);
@ -117,6 +122,74 @@
->assertActionEnabled('edit'); ->assertActionEnabled('edit');
}); });
test('members without dedicated provider capability see dedicated override action disabled', function (): void {
$tenant = ManagedEnvironment::factory()->create();
[$user] = createUserWithTenant($tenant, role: 'manager');
$connection = ProviderConnection::factory()
->platform()
->create([
'managed_environment_id' => $tenant->getKey(),
'workspace_id' => (int) $tenant->workspace_id,
'consent_status' => 'required',
'is_enabled' => true,
'provider' => 'microsoft',
]);
$tenant->makeCurrent();
Filament::setTenant($tenant, true);
Livewire::actingAs($user)
->test(ViewProviderConnection::class, ['record' => $connection->getKey()])
->assertActionVisible('enable_dedicated_override')
->assertActionDisabled('enable_dedicated_override')
->assertActionExists('enable_dedicated_override', fn ($action): bool => $action->getTooltip() === UiTooltips::insufficientPermission());
});
test('members without provider run capability cannot directly execute disabled provider operations', function (): void {
Queue::fake();
$tenant = ManagedEnvironment::factory()->create();
[$user] = createUserWithTenant($tenant, role: 'readonly');
$connection = ProviderConnection::factory()
->verifiedHealthy()
->create([
'managed_environment_id' => $tenant->getKey(),
'workspace_id' => (int) $tenant->workspace_id,
'consent_status' => 'granted',
'is_enabled' => true,
'provider' => 'microsoft',
]);
$tenant->makeCurrent();
Filament::setTenant($tenant, true);
Livewire::actingAs($user)
->test(ListProviderConnections::class)
->assertTableActionVisible('check_connection', $connection)
->assertTableActionDisabled('check_connection', $connection)
->callTableAction('check_connection', $connection)
->assertTableActionVisible('inventory_sync', $connection)
->assertTableActionDisabled('inventory_sync', $connection)
->callTableAction('inventory_sync', $connection);
Livewire::actingAs($user)
->test(ViewProviderConnection::class, ['record' => $connection->getKey()])
->assertActionVisible('compliance_snapshot')
->assertActionDisabled('compliance_snapshot')
->callAction('compliance_snapshot');
Queue::assertNotPushed(ProviderConnectionHealthCheckJob::class);
Queue::assertNotPushed(ProviderInventorySyncJob::class);
Queue::assertNotPushed(ProviderComplianceSnapshotJob::class);
expect(OperationRun::query()
->where('managed_environment_id', (int) $tenant->getKey())
->whereIn('type', ['provider.connection.check', 'inventory.sync', 'compliance.snapshot'])
->exists())->toBeFalse();
});
test('sensitive provider connection mutations remain confirmation and capability gated', function (): void { test('sensitive provider connection mutations remain confirmation and capability gated', function (): void {
$source = (string) file_get_contents(repo_path('apps/platform/app/Filament/Resources/ProviderConnectionResource.php')); $source = (string) file_get_contents(repo_path('apps/platform/app/Filament/Resources/ProviderConnectionResource.php'));

View File

@ -0,0 +1,76 @@
# Requirements Checklist: Spec 402 - Resource Policy & Authorization Proof Matrix
**Purpose**: Validate preparation quality for Spec 402 before implementation starts.
**Created**: 2026-06-23
**Feature**: `specs/402-resource-policy-authorization-proof-matrix/spec.md`
## Candidate Selection
- [x] The selected candidate was directly provided by the operator.
- [x] The candidate is linked to the Spec 400 P1 resource-policy matrix condition.
- [x] `docs/product/spec-candidates.md` was reviewed and currently reports no safe automatic next-best-prep target.
- [x] Close alternatives are deferred instead of hidden inside the primary scope.
- [x] The target does not reopen completed Specs 400 or 401.
- [x] No existing `specs/402-resource-policy-authorization-proof-matrix/` package existed before preparation.
- [x] Existing unrelated `402-screwfast-website-rebuild` branch collision is documented as context.
## Scope Quality
- [x] The spec is bounded to existing resource authorization proof and minimal hardening.
- [x] No new roles, permission product model, product surfaces, navigation, migrations, or broad RBAC redesign are included.
- [x] Admin and system panels are both explicitly in scope.
- [x] Workspace/environment isolation is explicitly in scope.
- [x] System/admin separation is explicitly in scope.
- [x] Global search, bulk actions, relation managers, controller-backed downloads/exports, and direct invocation are explicitly in scope.
- [x] Customer/reviewer boundary proof is included only where existing surfaces/tests represent that access.
- [x] Evidence currentness, management PDF staging validation, governance lifecycle, JSONB migration, and full browser audit are deferred.
## Constitution And Product Surface
- [x] Spec Candidate Check is filled out.
- [x] Approval class is exactly one class: Core Enterprise.
- [x] Score is recorded and above the minimum threshold.
- [x] Proportionality Review is completed because the matrix is a review artifact.
- [x] No runtime source of truth, persisted table, status family, enum, taxonomy, or framework is introduced.
- [x] Product Surface Contract is referenced because existing rendered authorization behavior may change.
- [x] UI Surface Impact is classified as existing-surface authorization hardening only.
- [x] Browser proof is required for representative rendered authorization behavior.
- [x] Human Product Sanity is required for changed rendered authorization behavior.
- [x] Completed-spec rewrite guardrail is explicit.
## Plan Quality
- [x] Plan identifies Laravel, Filament, Livewire, Pest, and Sail versions from repo context.
- [x] Plan names panel provider registration location.
- [x] Plan names likely affected repository surfaces.
- [x] Plan requires matrix-first work before adding policies or hardening code.
- [x] Plan distinguishes policies, gates/capabilities, scoped queries, global search, bulk actions, relation managers, controller routes, and system-panel capability middleware.
- [x] Plan requires existing capability services to remain authoritative where they already define product semantics.
- [x] Plan forbids cosmetic policy generation.
- [x] Plan includes rollout/deployment impact and expects no migrations/env/assets/queues/storage changes.
## Task Quality
- [x] Tasks are ordered by preparation, inventory, matrix, gap classification, tests, hardening, browser proof, and report close-out.
- [x] Tasks require negative tests for every fixed authorization gap.
- [x] Tasks include direct route/resource access tests.
- [x] Tasks include cross-workspace denial tests.
- [x] Tasks include system/admin separation tests.
- [x] Tasks include Filament action execution authorization tests.
- [x] Tasks include relation manager, bulk action, global search, and controller/download/export proof tasks.
- [x] Tasks include focused browser proof and explicitly forbid claiming full browser audit.
- [x] Tasks include dirty-state protocol before and after implementation.
- [x] Tasks include final implementation report sections A through M.
## Open Questions And Readiness
- [x] No open question blocks implementation preparation.
- [x] Product-ambiguous authorization decisions are required to be deferred rather than invented.
- [x] Spec Readiness Gate can pass after artifact analysis.
- [x] Candidate Selection Gate can pass as a manual operator-promoted candidate.
## Review Outcome
- [x] Review outcome class: `acceptable-special-case` for a bounded authorization proof matrix.
- [x] Workflow outcome: `keep`.
- [x] Final note location: future implementation report `specs/402-resource-policy-authorization-proof-matrix/implementation-report.md`.

View File

@ -0,0 +1,200 @@
# Spec 402 Implementation Report - Resource Policy & Authorization Proof Matrix
## A. Candidate Gate Result
**Result: PASS.**
No P0 or P1 authorization blocker remains in the inspected high-risk proof set. The confirmed Spec 401 residual around provider action visibility was fixed and covered by focused Livewire/Filament UI and direct execution tests plus browser smoke proof for representative rendered authorization behavior.
## B. Scope
Included:
- Repository-truth authorization inventory across admin resources, system surfaces, controller-backed exports/downloads, relation managers, global search posture, and representative custom pages.
- Minimal hardening in `ProviderConnectionResource` to separate business visibility from capability authorization for provider high-impact actions.
- Focused test and browser proof for provider action UI enforcement, provider disabled-action UX, provider direct execution blocking, backup hidden mutation behavior, admin global-search safety, and system/admin separation.
Not included:
- New roles, capabilities, permission model, migrations, persisted entities, JSONB work, product taxonomy/status families, new pages, navigation changes, or broad RBAC redesign.
- Completed historical spec rewrites.
- Full browser audit of every page.
## C. Dirty State
- Starting branch: `402-resource-policy-authorization-proof-matrix`
- Starting HEAD: `e1a7752f chore: finalize high risk admin action proof pack (#472)`
- Starting dirty state: active untracked `specs/402-resource-policy-authorization-proof-matrix/`; no tracked runtime changes.
- Starting `git diff --check`: passed.
- Ending changed files:
- `apps/platform/app/Filament/Resources/ProviderConnectionResource.php`
- `apps/platform/tests/Feature/Filament/ProviderConnectionsUiEnforcementTest.php`
- `apps/platform/tests/Browser/Spec402ResourcePolicyAuthorizationSmokeTest.php`
- `specs/402-resource-policy-authorization-proof-matrix/spec.md`
- `specs/402-resource-policy-authorization-proof-matrix/plan.md`
- `specs/402-resource-policy-authorization-proof-matrix/implementation-report.md`
- `specs/402-resource-policy-authorization-proof-matrix/tasks.md`
- No unrelated dirty files were reset, deleted, or cleaned.
## D. Resource Policy & Authorization Matrix
| Resource / Surface | Panel | Model | Category | Scope | Policy | Gate / Capability | CRUD | Custom Actions | Bulk | Relations | Global Search | Query Scope Proof | Direct Access Proof | Tests | Decision |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| Provider connections | Admin | `ProviderConnection` | B | Workspace + environment | `ProviderConnectionPolicy` | `provider.view`, `provider.manage`, `provider.manage_dedicated`, `provider.run` | View/create/edit gated | Check, sync, snapshot, default, lifecycle, dedicated credential actions | None | N/A | Disabled | `applyMembershipScope()` | Non-member 404 route tests | Provider UI enforcement + direct execution denial + Spec 402 browser | PASS |
| Backup schedules | Admin | `BackupSchedule` | B | Environment | `BackupSchedulePolicy` | tenant view/manage/run/delete | View allowed; mutations gated | Run, retry, archive, restore, force delete | Run/retry | Operation runs | Disabled | tenant-owned query trait | Non-member 404 + forged-key tests | Backup lifecycle/run tests + browser | PASS |
| Backup sets | Admin | `BackupSet` | B | Environment | Query/gate path | tenant view/restore capabilities | Read/detail scoped | Add policies via relation | None | Backup items | Disabled | tenant-owned query | Existing parity tests | Backup set/RBAC tests | PASS |
| Restore runs | Admin | `RestoreRun` | B | Environment | Query/gate path | restore/tenant capabilities | Read/create gated | Execute/confirm safety flow | None | N/A | Disabled | tenant-owned query | Restore UI tests | Restore run enforcement | PASS |
| Policies | Admin | `Policy` | B | Environment | Query/gate path | tenant view/sync/restore | Read scoped | Sync/restore actions gated | None | Versions | Disabled | tenant-owned query | Existing route/search tests | Policy resource/search tests | PASS |
| Policy versions | Admin | `PolicyVersion` | B | Environment | Query/gate path | tenant view/restore | Read scoped | Restore gated | None | N/A | Disabled | tenant-owned query | Existing tests | Policy version RBAC/search | PASS |
| Inventory items | Admin | `InventoryItem` | A | Environment | Query/gate path | tenant view | Read scoped | None | None | N/A | Existing scoped posture | scoped query | Existing tests | Inventory item tests | PASS |
| Entra groups | Admin | `EntraGroup` | A/B | Environment | `EntraGroupPolicy` | Entra roles view/manage | Read/mutate gated | Sync/manage actions gated | None | N/A | Existing scoped URL | scoped query | Existing tests | Directory group tests | PASS |
| Findings | Admin | `Finding` | B | Environment/workspace | `FindingPolicy` | finding view/triage/assign/resolve | Read/mutate gated | Workflow actions gated | Bulk triage | Exceptions | Existing scoped posture | scoped query | Existing workflow tests | Findings tests | PASS |
| Finding exceptions | Admin | `FindingException` | B | Environment/workspace | `FindingExceptionPolicy` | exception view/manage/approve | Read/mutate gated | Approval/renewal/decision actions | None | Decisions | Disabled | scoped query | Existing lifecycle tests | Exception RBAC tests | PASS |
| Environment reviews | Admin | `EnvironmentReview` | B/C | Environment/customer-safe output | `EnvironmentReviewPolicy` | review view/manage/acknowledge | Read/mutate gated | Export/acknowledge gated | None | Sections | Disabled | scoped query | Existing tests | Review UI/export tests | PASS |
| Review packs | Admin | `ReviewPack` | C | Environment/customer output | `ReviewPackPolicy` + `CustomerOutputGate` | review pack view/manage | Read/mutate gated | Download/publish/resolve gated | None | Reports | Disabled | scoped query | Controller tests | Review pack/customer output tests | PASS |
| Stored reports | Admin | `StoredReport` | C | Environment/customer output | Query/gate path | review/report capabilities | Read/create/edit/delete gated | Download gated | None | Review pack | Disabled | scoped query | Existing tests | Stored report tests | PASS |
| Evidence snapshots | Admin | `EvidenceSnapshot` | C | Environment/evidence | `EvidenceSnapshotPolicy` | evidence view/manage | Read/mutate gated | Capture/publish gated | None | N/A | Disabled | scoped query | Existing tests | Evidence tests | PASS |
| Baseline profiles | Admin | `BaselineProfile` | B | Workspace/environment assignment | Query/gate path | baseline/manage capabilities | Read/mutate gated | Compare/capture gated | None | Assignments | Disabled | scoped query | Existing tests | Baseline action tests | PASS |
| Baseline snapshots | Admin | `BaselineSnapshot` | B | Environment | Query/gate path | tenant view/baseline | Read scoped | Compare/details | None | N/A | Disabled | scoped query | Existing tests | Snapshot auth tests | PASS |
| Alerts | Admin | `AlertRule`, `AlertDestination`, `AlertDelivery` | B | Workspace/environment | Alert policies | alert view/manage | Read/mutate gated | Test/send/ack gated | None | Deliveries | Disabled | scoped queries | Existing access tests | Alert access/viewer tests | PASS |
| Operation runs | Admin | `OperationRun` | A/C | Environment/workspace | `OperationRunPolicy` | ops/tenant view | Read scoped | Retry/view links gated by source | None | N/A | Disabled | scoped query | Existing tests | Ops/run tests | PASS |
| Managed environments | Admin | `ManagedEnvironment` | D | Workspace | Query/gate path | tenant/workspace capabilities | Read/manage gated | Onboarding/verify/setup gated | None | Memberships | Disabled by query | workspace scope | Existing tests | Tenant action tests | PASS |
| Workspaces | Admin | `Workspace` | D | Workspace | `WorkspacePolicy` | workspace view/manage | Read/manage gated | Membership/settings | None | Memberships | Existing scoped posture | workspace query | Existing tests | Workspace RBAC tests | PASS |
| Custom admin pages | Admin | Multiple/read models | A-D | Workspace/environment | Page-level gates/resolvers | Existing tenant/workspace capabilities | Page access scoped | Page actions gated | N/A | N/A | N/A | context middleware + page queries | Existing page tests | Surface guard tests | PASS |
| System panel pages | System | `PlatformUser`, ops/directory models | D/E | System only | Platform guard + page capabilities | `PlatformCapabilities::*` | Page access gated | Repair/ops controls gated | N/A | N/A | System-only | `platform` guard + middleware | Tenant session 404; missing capability 403 | System boundary tests + browser | PASS |
| Review/download controllers | HTTP | Review/report models | C | Environment/customer output | Controller authorization + output gates | review pack view/customer output gate | Download only | Stream/export | N/A | N/A | N/A | tenant/workspace checks | Controller tests | Review/report tests | PASS |
| Provider callbacks/context routes | HTTP | Provider/workspace context | D | Workspace/environment | Middleware + controller checks | provider/manage/context | Callback/context only | Consent/RBAC callbacks | N/A | N/A | N/A | guarded routes | Existing route tests | Provider/onboarding tests | PASS |
| Relation managers | Admin | Related models | B/C/D | Owner record scope | Owner resource + relation checks | owner capabilities | Related CRUD/attach gated | Add/remove/update gated | N/A | Owner scoped | N/A | relation owner scoping | Existing relation tests | Relation-manager RBAC tests | PASS |
## E. Runtime Changes
- `ProviderConnectionResource` now uses `recordAllowsProviderMemberVisibility()` for action visibility and leaves capability decisions to `UiEnforcement::requireCapability()`.
- The change covers provider edit, run/check/sync/snapshot, default, lifecycle, and dedicated credential actions.
- Result: non-members remain hidden/404, members missing capability get visible-disabled or non-exposed rendered behavior depending on Filament action-group rendering, and server-side guards remain capability-backed.
## F. Policies / Gates / Capabilities
- Added policies: none.
- Added capabilities: none.
- Updated gates: none.
- Existing provider capabilities remain authoritative: `provider.manage`, `provider.manage_dedicated`, `provider.run`.
- Documented exception: models with sufficient existing scoped query + capability paths were not given cosmetic policies.
## G. Tests Added / Updated
- Updated `ProviderConnectionsUiEnforcementTest` with manager-without-dedicated-capability proof and direct Livewire execution denial for disabled provider operations.
- Added `Spec402ResourcePolicyAuthorizationSmokeTest` browser smoke.
- Reused existing focused tests for global search, system/admin separation, backup hidden/disabled mutation behavior, and direct resource denial.
## H. Proof Summary
- Direct access: provider non-member route tests and backup schedule non-member tests prove deny-as-not-found behavior.
- UI visibility/action execution: provider actions are disabled for members missing capabilities; direct Livewire calls to disabled provider check/sync/snapshot actions do not queue jobs or create OperationRuns; backup readonly cannot dispatch run/retry.
- Workspace/environment isolation: backup browser smoke proves cross-workspace denied path; feature tests cover provider guessed URLs.
- System/admin separation: feature tests prove tenant users get 404 on `/system`; browser smoke covers rendered denial.
- Customer/internal boundary: review/report controllers retain existing authorization and customer output gates; no runtime changes.
- Global search: representative high-risk admin resources remain disabled; global-search safety tests passed.
- Relation managers and bulk actions: existing relation/bulk tests remain the proof source; no relation or bulk runtime changes.
## I. Focused Browser Proof
Command:
```bash
cd apps/platform && ./vendor/bin/sail artisan test tests/Browser/Spec402ResourcePolicyAuthorizationSmokeTest.php --compact
```
Result: passed, 1 test / 22 assertions.
Covered:
- Authorized admin provider detail for owner.
- Readonly provider detail renders provider verification/edit/sync/snapshot actions as visible but disabled, with no JavaScript or console errors.
- Readonly backup schedule list renders without exposing `Run now` and creates no operation run; this is the browser high-impact hidden-action proof.
- Cross-workspace backup schedule path denied.
- Workspace web session denied on `/system`.
- No JavaScript or console errors on checked pages.
Not covered by the focused browser smoke:
- Provider direct disabled-action execution is covered by `ProviderConnectionsUiEnforcementTest`.
- Global-search disabled/scoped posture is covered by `AdminGlobalSearchContextSafetyTest`; the browser smoke intentionally does not claim global-search UI coverage.
Note: stale Playwright run-server processes from previous browser work initially caused `SIGKILL`; those orphaned runner processes were stopped, and the final browser proof passed. No runner processes remained after cleanup.
## J. Remaining Findings
- P0: none.
- P1: none.
- P2: full browser audit across every matrix row and global-search browser UI proof remain intentionally out of scope.
- P3: some low-risk/read-only resources rely on scoped query + capability paths rather than dedicated policy classes; documented as sufficient existing architecture.
## K. Deferred Items
- Evidence currentness closure: deferred to the evidence/review feature stream.
- Management PDF staging validation: deferred; no PDF behavior changed.
- Governance lifecycle expansion: deferred.
- JSONB migration/index work: deferred.
- Full browser audit: deferred.
- Product decision changes for roles/capabilities: none requested and none added.
## L. Validation Commands
```bash
cd apps/platform && ./vendor/bin/sail pint app/Filament/Resources/ProviderConnectionResource.php tests/Feature/Filament/ProviderConnectionsUiEnforcementTest.php tests/Browser/Spec402ResourcePolicyAuthorizationSmokeTest.php
```
Result: passed.
```bash
cd apps/platform && ./vendor/bin/sail artisan test tests/Feature/Filament/ProviderConnectionsUiEnforcementTest.php --compact
```
Result: passed, 7 tests / 97 assertions.
```bash
cd apps/platform && ./vendor/bin/sail artisan test tests/Feature/Filament/ProviderConnectionsUiEnforcementTest.php tests/Feature/Rbac/AdminGlobalSearchContextSafetyTest.php tests/Feature/Rbac/SystemPanelAccessBoundaryTest.php --compact
```
Result: passed, 13 tests / 112 assertions.
```bash
cd apps/platform && ./vendor/bin/sail artisan test tests/Feature/Filament/ProviderConnectionsUiEnforcementTest.php tests/Feature/Rbac/AdminGlobalSearchContextSafetyTest.php tests/Feature/Rbac/SystemPanelAccessBoundaryTest.php tests/Feature/BackupScheduling/BackupScheduleLifecycleAuthorizationTest.php tests/Feature/BackupScheduling/RunNowRetryActionsTest.php --filter='provider|global search|system panel|readonly|members without manage capability|dedicated' --compact
```
Result: passed, 14 tests / 134 assertions.
```bash
cd apps/platform && ./vendor/bin/sail artisan test tests/Browser/Spec402ResourcePolicyAuthorizationSmokeTest.php --compact
```
Result: passed, 1 test / 22 assertions.
```bash
git diff --check
```
Result: passed.
## M. Recommended Next Action
Proceed to Spec 403 only after review of this proof matrix and runtime hardening. The next spec should not introduce new roles/capabilities unless a product decision explicitly requires it.
Product Surface Contract close-out:
- No-legacy posture: no legacy surface introduced.
- Product Surface Impact: existing authorization hardening only.
- UI Surface Impact: existing Filament action visibility/disabled posture only; no new page, navigation, widget, or copy-heavy surface.
- Page archetype: standard native Filament resources.
- Surface budgets: unchanged.
- Technical Annex / deep-link demotion: N/A.
- Canonical status vocabulary: unchanged.
- Product Surface exceptions: none.
- Focused browser proof: completed via `Spec402ResourcePolicyAuthorizationSmokeTest`.
- Human Product Sanity: visible disabled affordances increased intentionally inside existing provider More/action surfaces to satisfy RBAC-UX; the focused browser smoke verified the readonly provider actions are rendered disabled. No new page, navigation, widget, or copy-heavy surface was added, and readonly users are not offered executable high-impact controls.
- Livewire v4: compliant with installed Livewire 4.1.4.
- Provider registration: panels remain registered in `apps/platform/bootstrap/providers.php`.
- Global search: no globally searchable resource was enabled; high-risk resources remain disabled or existing scoped.
- Destructive/high-impact actions: existing `->requiresConfirmation()` retained; authorization remains enforced by `UiEnforcement` and policies/gates.
- Asset strategy: no assets added; `filament:assets` deployment step unchanged and not newly required by this spec.
- Deployment impact: no migrations, env vars, queues, storage, or asset build changes.

View File

@ -0,0 +1,284 @@
# Implementation Plan: Spec 402 - Resource Policy & Authorization Proof Matrix
**Branch**: `402-resource-policy-authorization-proof-matrix` | **Date**: 2026-06-23 | **Spec**: `specs/402-resource-policy-authorization-proof-matrix/spec.md`
**Input**: Feature specification from `specs/402-resource-policy-authorization-proof-matrix/spec.md`
## Summary
Spec 402 converts TenantPilot resource authorization from implicit/scattered proof into an auditable matrix and targeted hardening package. The implementation must inventory existing admin and system resource-backed surfaces, classify each policy/gate/capability/scoped-query decision, add focused negative tests for high-risk authorization paths, and apply only minimal hardening for confirmed gaps.
Do not start by adding policies. Build the matrix first, classify gaps, then fix only the smallest confirmed in-scope authorization defects.
## Technical Context
**Language/Version**: PHP 8.4.15, Laravel 12.52.0
**Primary Dependencies**: Filament 5.2.1, Livewire 4.1.4, Laravel Sail, Pest 4.3.1, PHPUnit 12.5.4
**Storage**: PostgreSQL; no schema changes allowed
**Testing**: Pest Feature/Filament/Policy tests, focused Pest Browser smoke where available
**Validation Lanes**: confidence for Feature/Filament authorization tests; browser for representative UI proof; heavy-governance only if a matrix/discovery guard is implemented
**Target Platform**: TenantPilot Laravel monolith under `apps/platform`
**Project Type**: Laravel + Filament application
**Performance Goals**: No material runtime or suite-cost increase; avoid broad browser/full-suite expansion
**Constraints**: No new roles, permission product model, persisted truth, migrations, product surfaces, navigation, or broad refactor
**Scale/Scope**: Existing `/admin` resources/pages/actions and `/system` pages/actions with resource-backed authorization impact
## Preparation Context
- Dirty state before preparation: clean on `platform-dev` at `e1a7752f chore: finalize high risk admin action proof pack (#472)`.
- Spec Kit script used: `.specify/scripts/bash/create-new-feature.sh --json --number 402 --short-name resource-policy-authorization-proof-matrix "Resource Policy & Authorization Proof Matrix"`.
- Generated branch/path: `402-resource-policy-authorization-proof-matrix`, `specs/402-resource-policy-authorization-proof-matrix/`.
- Existing conflict note: local and remote branch `402-screwfast-website-rebuild` existed before preparation, but no `specs/402-*` package existed.
- Candidate source: direct user-provided Spec 402 draft, promoted from Spec 400 P1 resource-policy matrix condition.
- Completed-spec guardrail: Specs 400 and 401 are read-only context. Do not rewrite their close-out, validation, completed tasks, implementation reports, or browser evidence.
## Existing Repository Surfaces
### Panels and routes
- Admin panel provider: `apps/platform/app/Providers/Filament/AdminPanelProvider.php`, registered in `apps/platform/bootstrap/providers.php`, path `/admin`, discovers `App\Filament\Resources`.
- System panel provider: `apps/platform/app/Providers/Filament/SystemPanelProvider.php`, registered in `apps/platform/bootstrap/providers.php`, path `/system`, discovers `App\Filament\System\Pages`.
- Admin route families include workspace/environment resource routes under `/admin/workspaces/{workspace}/environments/{environment}/...`.
- System route families include `/system/directory/...`, `/system/ops/...`, `/system/security/access-logs`, and `/system/repair-workspace-owners`.
### Authorization foundations
- Tenant/admin capabilities are registered in `apps/platform/app/Providers/AuthServiceProvider.php` from `App\Support\Auth\Capabilities`.
- Platform/system capabilities are registered in `AuthServiceProvider` from `App\Support\Auth\PlatformCapabilities`.
- Some policies are registered via `$policies` in `AuthServiceProvider`.
- Additional policies are manually bound in `apps/platform/app/Providers/AppServiceProvider.php` for `BackupSchedule`, `Finding`, `EntraGroup`, and `OperationRun`.
- Existing central paths include `App\Services\Auth\CapabilityResolver`, `App\Services\Auth\WorkspaceCapabilityResolver`, `App\Support\Rbac\UiEnforcement`, and `App\Support\Rbac\WorkspaceUiEnforcement`.
### Resource inventory seed
The implementation must verify this list rather than treating it as complete:
- Admin resources: `AlertDeliveryResource`, `AlertDestinationResource`, `AlertRuleResource`, `BackupScheduleResource`, `BackupSetResource`, `BaselineProfileResource`, `BaselineSnapshotResource`, `EntraGroupResource`, `EnvironmentReviewResource`, `EvidenceSnapshotResource`, `FindingExceptionResource`, `FindingResource`, `InventoryItemResource`, `ManagedEnvironmentResource`, `OperationRunResource`, `PolicyResource`, `PolicyVersionResource`, `ProviderConnectionResource`, `RestoreRunResource`, `ReviewPackResource`, `StoredReportResource`, `Workspaces\WorkspaceResource`.
- Admin model-backed pages/custom flows: `BaselineSubjectResolution`, `EnvironmentDashboard`, `EnvironmentDiagnostics`, `EnvironmentRequiredPermissions`, `FindingsIntakeQueue`, `MyFindingsInbox`, `DecisionRegister`, `GovernanceInbox`, `AuditLog`, `EvidenceOverview`, `Operations`, `TenantlessOperationRunViewer`, `CustomerReviewWorkspace`, `ReviewRegister`, `WorkspaceSettings`, `ManagedEnvironmentOnboardingWizard`.
- Relation managers: backup schedule operation runs, backup items, baseline tenant assignments, managed environment memberships, policy versions, workspace memberships, and any relation managers discovered during implementation.
- System pages: dashboard, directory tenants/workspaces/detail pages, ops controls/failures/runbooks/runs/stuck/view run, repair workspace owners, security access logs.
- Controller-backed routes: review-pack downloads/rendered reports, management-report PDF downloads, provider consent/RBAC callbacks, finding exception queue deep links, workspace/environment context switching.
## UI / Surface Guardrail Plan
- **Guardrail scope**: existing operator/admin/system surfaces may be authorization-hardened; no new surfaces.
- **Affected routes/pages/actions/states/navigation/panel/provider surfaces**: exact touched surfaces named in implementation report after matrix inventory.
- **No-impact class, if applicable**: N/A because rendered authorization behavior may change.
- **Native vs custom classification summary**: native Filament resources/pages plus existing Laravel controllers and shared authorization helpers.
- **Shared-family relevance**: authorization, global search, high-impact actions, relation managers, downloads/exports, system panel access.
- **State layers in scope**: page, table/action, relation manager, route/query, global search, controller route.
- **Audience modes in scope**: operator-MSP, readonly/customer where existing tests/surfaces represent it, support-platform/system.
- **Decision/diagnostic/raw hierarchy plan**: unchanged; do not expose new raw technical evidence.
- **Raw/support gating plan**: preserve or tighten existing capability gating.
- **One-primary-action / duplicate-truth control**: unchanged; do not add UI layers.
- **Handling modes by drift class or surface**: review-mandatory for high-risk authorization paths; exception-required for any policy/gate exception; hard-stop for P0 unsafe access.
- **Repository-signal treatment**: review-mandatory for missing policy proof, direct invocation gaps, relation/bulk/search uncertainty, and system/admin boundary gaps.
- **Special surface test profiles**: standard-native-filament, global-context-shell, system-admin, browser representative proof.
- **Required tests or manual smoke**: Feature/Filament/policy tests plus focused browser smoke.
- **Exception path and spread control**: documented exception in matrix and implementation report only; no runtime exception framework.
- **Active feature PR close-out entry**: `Guardrail / Exception / Smoke Coverage`.
- **UI/Productization coverage decision**: existing-surface authorization hardening only; no route-inventory/design matrix update unless implementation materially changes a reachable surface beyond authorization.
- **Coverage artifacts to update**: none planned.
- **No-impact rationale**: N/A.
- **Navigation / Filament provider-panel handling**: no provider registration changes; system/admin provider locations must be stated in close-out.
- **Screenshot or page-report need**: no page-report set; focused browser proof is sufficient.
## Product Surface Contract Plan
- **Product Surface Contract reference**: `docs/product/standards/product-surface-contract.md`
- **No-legacy posture**: canonical current authorization, no compatibility exception.
- **Page archetype and surface budget plan**: existing archetypes only; neutral budget impact expected.
- **Technical Annex and deep-link demotion plan**: unchanged; do not promote OperationRun/evidence/raw IDs/source keys/payloads.
- **Canonical status vocabulary plan**: unchanged.
- **Product Surface exceptions**: none planned.
- **Browser verification plan**: focused representative authorization paths.
- **Human Product Sanity plan**: focused authorization sanity over changed rendered behavior.
- **Visible complexity outcome target**: neutral or decreased.
- **Implementation report target**: `specs/402-resource-policy-authorization-proof-matrix/implementation-report.md`.
## Filament / Livewire / Deployment Posture
- **Livewire v4 compliance**: Livewire 4.1.4 confirmed by Laravel Boost; no Livewire v3 APIs allowed.
- **Panel provider registration location**: unchanged; Laravel 12 providers are registered in `apps/platform/bootstrap/providers.php`.
- **Global search posture**: every resource must be classified; do not enable global search merely for completeness. Globally searchable resources need View/Edit pages, `$recordTitleAttribute`, scoped queries, and tests.
- **Destructive/high-impact action posture**: all touched destructive/high-impact actions must be action-backed, `->requiresConfirmation()` where destructive/high-impact, server-authorized, audited when mutating, and tested.
- **Asset strategy**: no new assets. `filament:assets` is not newly required unless implementation unexpectedly registers assets, which is out of scope by default.
- **Testing plan**: policy/gate tests, Filament page/action tests, relation manager tests, direct route access tests, global search posture tests, system/admin separation tests, focused browser smoke.
- **Deployment impact**: no env vars, migrations, queues, scheduler, storage, assets, routes, panels, or navigation changes expected. If authorization hardening changes runtime behavior, deployment notes must call out safer access enforcement only.
## Shared Pattern & System Fit
- **Cross-cutting feature marker**: yes.
- **Systems touched**: policies, gates, capability resolvers, UI enforcement helpers, scoped Filament resource queries, relation managers, system capability middleware, controller authorization.
- **Shared abstractions reused**: `CapabilityResolver`, `WorkspaceCapabilityResolver`, policy classes, `UiEnforcement`, `WorkspaceUiEnforcement`, platform capability middleware, existing scoped query helpers.
- **New abstraction introduced? why?**: none planned.
- **Why the existing abstraction was sufficient or insufficient**: existing abstractions are the correct sources of product authorization semantics; the gap is proof consistency and any missing direct/access tests.
- **Bounded deviation / spread control**: gate/capability exception acceptable only when matrix documents why adding a policy would duplicate existing tested contract without increasing safety.
## OperationRun UX Impact
- **Touches OperationRun start/completion/link UX?**: proof only, possible if OperationRun-linked actions/resources are hardened.
- **Central contract reused**: existing OperationRun policies and links.
- **Delegated UX behaviors**: unchanged.
- **Surface-owned behavior kept local**: existing initiation inputs only.
- **Queued DB-notification policy**: unchanged.
- **Terminal notification path**: unchanged.
- **Exception path**: none planned.
## Provider Boundary & Portability Fit
- **Shared provider/platform boundary touched?**: yes, for provider connection/resource/readiness authorization proof.
- **Provider-owned seams**: provider connection credentials/readiness/permissions details.
- **Platform-core seams**: workspace/environment authorization, operations, evidence, audit, capabilities, system/admin planes.
- **Neutral platform terms / contracts preserved**: workspace, managed environment, provider connection, operation, evidence, audit, capability.
- **Retained provider-specific semantics and why**: Microsoft/Graph/Entra terms remain only where existing provider-owned implementation uses them.
- **Bounded extraction or follow-up path**: none; provider productization is a later candidate if proof reveals UX friction rather than auth defect.
## Constitution Check
- Inventory-first: PASS planned. Authorization proof inspects existing inventory/resource truth and does not alter external truth.
- Read/write separation: PASS planned. Any touched mutating action keeps server authorization, confirmation where high-impact/destructive, audit, and tests.
- Graph contract path: PASS planned. No new Graph calls.
- Deterministic capabilities: PASS planned. Use existing capability resolvers and registries.
- RBAC-UX: PASS planned. Admin `/admin` and system `/system` planes remain separate; non-member/wrong-scope access deny-as-not-found; missing capability forbidden at execution level.
- Workspace isolation: PASS planned. Cross-workspace direct access tests are required.
- Tenant isolation: PASS planned. ManagedEnvironment-scoped resources must remain workspace/environment safe.
- Run observability: PASS planned. Existing OperationRun behavior only.
- OperationRun start UX: PASS planned. No new local start UX.
- Data minimization: PASS planned. No secrets/raw provider payloads in reports/tests.
- Test governance: PASS planned. Confidence/browser lanes are explicit; heavy-governance cost remains visible if used.
- Proportionality: PASS planned. Matrix/report is review evidence, not runtime framework.
- No premature abstraction: PASS planned. No new helper unless spec/plan is updated.
- Persisted truth: PASS. No new persistence.
- Behavioral state: PASS. No new states.
- UI semantics: PASS. No new presentation taxonomy.
- Shared pattern first: PASS. Existing authorization helpers preferred.
- Provider boundary: PASS. Provider-specific semantics stay bounded.
- V1 explicitness / few layers: PASS. Direct tests and targeted hardening only.
- Spec discipline / bloat check: PASS. One coherent authorization proof spec rather than many micro-specs.
- Filament-native UI: PASS planned. Existing Filament authorization/action patterns preserved.
- UI/Productization coverage: PASS planned. Existing-surface authorization hardening only.
- Product Surface Contract Gate: PASS planned. Spec/plan/tasks name no-legacy, UI impact, browser proof, human sanity, global search, destructive/high-impact actions, and close-out fields.
## Test Governance Check
- **Test purpose / classification by changed surface**: Policy/Gate for authorization decisions; Feature/Filament for resources/actions/relation managers; Browser for representative rendered proof; Heavy-Governance only for broad matrix/discovery guard.
- **Affected validation lanes**: confidence, browser, optional heavy-governance.
- **Why this lane mix is the narrowest sufficient proof**: resource authorization cannot be proven by unit tests only; browser proof is limited to representative high-risk paths.
- **Narrowest proving command(s)**:
- `cd apps/platform && ./vendor/bin/sail artisan test --filter=Policy`
- `cd apps/platform && ./vendor/bin/sail artisan test --filter=Authorization`
- `cd apps/platform && ./vendor/bin/sail artisan test --filter=Capability`
- `cd apps/platform && ./vendor/bin/sail artisan test --filter=Filament`
- `cd apps/platform && ./vendor/bin/sail artisan test --filter=Resource`
- `cd apps/platform && ./vendor/bin/sail artisan test --filter=System`
- targeted Spec 402 test files
- focused Spec 402 browser smoke if browser test support is available
- `git diff --check`
- conditional project formatter check for changed PHP runtime/test files
- report/test/log redaction check for secrets, tokens, raw credential payloads, and sensitive raw provider payloads
- **Fixture / helper / factory / seed / context cost risks**: workspace/environment/user/platform-user setup; keep helpers existing or opt-in.
- **Expensive defaults or shared helper growth introduced?**: no by default.
- **Heavy-family additions, promotions, or visibility changes**: none planned except explicit matrix/discovery guard if implementation chooses it.
- **Surface-class relief / special coverage rule**: standard-native-filament for ordinary resource pages; special coverage for global-context-shell and system-admin boundary.
- **Closing validation and reviewer handoff**: implementation report records command results, lane fit, browser proof, and residual findings.
- **Budget / baseline / trend follow-up**: none expected; document any material test runtime growth.
- **Review-stop questions**: Does every fixed gap have a negative test? Did any policy duplicate capability logic? Did relation/search/bulk proof cover direct access? Did `/system` remain platform-only?
- **Escalation path**: document-in-feature by default; follow-up-spec for structural unresolved authorization decisions; reject-or-split for scope creep.
- **Active feature PR close-out entry**: `Guardrail / Exception / Smoke Coverage`.
- **Why no dedicated follow-up spec is needed**: this spec itself is the bounded authorization proof package; only remaining P0/P1 blockers require a remediation spec.
## Project Structure
### Documentation (this feature)
```text
specs/402-resource-policy-authorization-proof-matrix/
├── spec.md
├── plan.md
├── tasks.md
├── checklists/
│ └── requirements.md
└── implementation-report.md # created during implementation
```
### Source Code (repository root)
Implementation may inspect and, if required by confirmed gaps, minimally edit:
```text
apps/platform/app/Filament/Resources/
apps/platform/app/Filament/Pages/
apps/platform/app/Filament/System/Pages/
apps/platform/app/Http/Controllers/
apps/platform/app/Http/Middleware/
apps/platform/app/Policies/
apps/platform/app/Providers/AuthServiceProvider.php
apps/platform/app/Providers/AppServiceProvider.php
apps/platform/app/Services/Auth/
apps/platform/app/Support/Auth/
apps/platform/app/Support/Rbac/
apps/platform/routes/web.php
apps/platform/tests/
```
**Structure Decision**: Use the existing Laravel monolith structure. Do not create new base folders or new runtime packages. Add tests under existing domain folders or focused `Spec402...` files.
## Complexity Tracking
| Violation | Why Needed | Simpler Alternative Rejected Because |
|---|---|---|
| Spec-local authorization matrix/report | Required to prove fragmented resource authorization across admin/system panels | Blind policy generation or local fixes would not prove relation/search/bulk/direct/system boundaries |
| Focused browser proof | Required because UI-hidden/disabled state and panel separation are rendered behavior | Feature tests alone cannot prove representative browser/Filament runtime behavior |
No runtime abstraction, persisted truth, enum/status family, or UI framework is planned.
## Implementation Phases
### Phase 1 - Baseline and inventory
Run required dirty-state commands, inspect panels/resources/routes/policies/gates/tests, and build the internal inventory. No edits before this phase is complete.
### Phase 2 - Resource authorization matrix
Create the implementation-report matrix before adding policies or hardening code. Include all columns required by the spec.
### Phase 3 - Gap classification
Classify each gap as missing proof only, sufficient gate/capability without policy, missing policy and insufficient path, UI/execution inconsistency, scope risk, system/admin boundary risk, global-search risk, relation/bulk risk, or product-decision required.
### Phase 4 - Tests first for high-risk gaps
Add or update negative tests for direct access, action execution, cross-workspace denial, system/admin separation, global search, relation managers, and bulk actions before runtime hardening when feasible.
### Phase 5 - Minimal hardening
Only after matrix and tests, apply the smallest safe fix: policy class, policy registration, action-level authorization, search disable/restrict, bulk/relation scoping, controller authorization, or documented exception.
### Phase 6 - Focused browser proof
Run a representative browser smoke for allowed admin access, cross-workspace denial, system-only denial to workspace admin, and unauthorized high-impact action visibility/execution. Global-search posture may remain feature-level proof when exercising the search UI would turn the focused smoke into a broader browser audit.
### Phase 7 - Implementation report and validation
Complete `implementation-report.md`, classify remaining findings, run targeted validation, record dirty state, and recommend PASS/PASS WITH CONDITIONS/FAIL.
## Risk Controls
- Stop and update spec/plan before introducing new roles, capabilities, product surfaces, database changes, or a shared authorization framework.
- Treat missing product semantics as `DEFERRED: product decision required`.
- Preserve completed specs and historical close-out evidence.
- Do not conflate UI visibility with execution authorization.
- Do not treat global search as safe unless scoped and tested.
- Do not count route middleware alone as record-level authorization proof.
## Rollout Considerations
- Staging: run targeted authorization tests and browser proof before merge.
- Production: no migrations or env changes expected. If authorization hardening changes access outcomes, release notes should mention stricter authorization enforcement.
- Queues/scheduler/storage/assets: no expected impact.
- `filament:assets`: no new requirement unless unexpected asset registration occurs, which is out of scope.
## Candidate Readiness
- Candidate Selection Gate: planned PASS because the operator explicitly promoted Spec 402, no existing spec package existed, and the scope is bounded to authorization proof/hardening.
- Spec Readiness Gate: should pass when `spec.md`, `plan.md`, `tasks.md`, and `checklists/requirements.md` are complete, placeholders are removed, and preparation analysis finds no blocking artifact drift.

View File

@ -0,0 +1,468 @@
# Feature Specification: Spec 402 - Resource Policy & Authorization Proof Matrix
**Feature Branch**: `402-resource-policy-authorization-proof-matrix`
**Created**: 2026-06-23
**Status**: Draft / Ready for implementation preparation review
**Type**: Targeted authorization hardening / proof / policy matrix spec
**Runtime posture**: Existing authorization proof and minimal hardening only. No new product surfaces, no new product concepts, no broad RBAC redesign.
**Input**: User-provided Spec 402 draft, current repo truth, Product Surface Contract, Spec 400 product-contract audit context, Spec 401 high-risk action proof context, roadmap/spec-candidate queue, and admin/system Filament authorization surfaces.
## Candidate Selection Context
- **Selected candidate**: Resource Policy & Authorization Proof Matrix.
- **Source location**: Direct user-provided Spec 402 draft in the 2026-06-23 request, promoted from the Spec 400 P1 resource-policy matrix condition.
- **Why selected**: `docs/product/spec-candidates.md` currently reports no safe automatic next-best-prep target, but the operator supplied a direct manual candidate. The candidate closes an explicit Spec 400 risk: several resource-backed models and surfaces rely on a mix of policies, gates, capabilities, Filament static authorization, route middleware, and scoped queries, but the proof is not organized as an auditable resource matrix.
- **Roadmap relationship**: Supports the current governance and architecture hardening lane by proving RBAC, workspace/environment isolation, system/admin separation, global search posture, relation-manager scoping, and direct invocation denial before Evidence Anchor & Currentness Runtime Closure or broader browser/runtime audit work proceeds.
- **Close alternatives deferred**:
- Spec 403 - Evidence Anchor & Currentness Runtime Closure is deferred until Spec 402 reaches at least `PASS WITH CONDITIONS` with no P0 authorization findings.
- Management Report PDF staging validation remains manual follow-through for Specs 378-380 and is unrelated to resource authorization proof.
- Governance artifact lifecycle/retention runtime is broader product runtime work and must not be hidden inside this authorization proof.
- Provider readiness onboarding productization is optional productization work; this spec proves existing authorization posture first.
- Full browser/UX/runtime audit is deferred while resource authorization proof remains incomplete.
- **Completed-spec guardrail result**:
- No `specs/402-resource-policy-authorization-proof-matrix/` package existed before this preparation.
- A local and remote branch named `402-screwfast-website-rebuild` existed before preparation, but no TenantPilot `specs/402-*` package existed. The operator explicitly requested Spec ID 402, so this package uses the requested number and records the branch-prefix collision as preparation context.
- Specs 400 and 401 are related context only. Their close-out notes, validation results, completed task markers, browser proof, smoke history, and implementation reports must not be rewritten, normalized, unchecked, or removed.
- Spec 401 implementation-report evidence shows no unresolved P0 high-risk action blocker in the selected backup-schedule hardening slice. It records an independently reproducible provider UI enforcement residual; Spec 402 must inventory that as proof debt if still present, not silently treat it as solved.
- **Smallest viable implementation slice**: Build the Resource Policy & Authorization Matrix first; classify gaps; add targeted tests and only minimal runtime hardening for confirmed unsafe or unproven high-risk resource authorization paths; record any policy/gate/capability exceptions in the implementation report.
- **Feature description for Spec Kit**: Prove and minimally harden existing resource-backed authorization across TenantPilot admin and system panels by inventorying every relevant Filament resource/page/action/relation/search path, documenting policy/gate/capability decisions, adding focused tests for direct access, workspace/environment isolation, system/admin separation, global search, relation managers, and high-risk action execution, and applying only bounded fixes required to make the authorization posture evidence-backed.
## Spec Candidate Check *(mandatory - SPEC-GATE-001)*
- **Problem**: TenantPilot has many resource-backed admin and system surfaces, but authorization proof is fragmented across policies, gates, capability checks, static Filament `can*` methods, route middleware, scoped queries, UI visibility callbacks, and tests. Future agents can mistake hidden UI buttons or resource-level helpers for complete authorization.
- **Today's failure**: A reviewer cannot quickly prove which actor may list, view, create, update, delete, search, bulk-act, or trigger custom actions for every resource-backed surface. Missing policies may be safe because a capability path exists, or unsafe because direct record/action access is untested. Both cases currently look too similar.
- **User-visible improvement**: Operators and reviewers get evidence-backed confidence that admin/system resources enforce workspace/environment scope, system/admin separation, customer/internal boundaries, direct invocation denial, global search posture, relation-manager scoping, and action authorization.
- **Smallest enterprise-capable version**: One implementation report with a matrix over existing admin/system resources, focused negative tests for high-risk resource paths, minimal hardening only where proof reveals an unsafe or unproven authorization path, and documented exceptions where an existing gate/capability service is intentionally sufficient.
- **Explicit non-goals**: No new roles, no new role hierarchy, no new permission product model, no new capability vocabulary unless an existing contract already requires it, no new admin/system/customer surfaces, no navigation changes, no onboarding or evidence/reporting behavior, no management PDF behavior, no JSON-to-JSONB migration, no governance lifecycle/retention work, no broad UI redesign, no completed-spec rewrite.
- **Permanent complexity imported**: A spec-local implementation report/matrix, focused policy/feature/Filament/browser tests, and possibly a small number of targeted policy classes or authorization checks if existing proof is insufficient. No new persisted truth, table, enum/status family, cross-domain framework, or broad RBAC model is expected.
- **Why now**: Spec 400 recorded resource policy coverage as a P1 condition, and Spec 401 proved selected high-risk action behavior. Before proceeding to evidence/currentness or broader runtime audit, TenantPilot needs the authorization map that proves resource-backed surfaces are not relying on UI visibility or assumptions.
- **Why not local**: A local policy fix for one model would not answer cross-panel ownership, direct route access, global search, relation managers, bulk actions, system/admin boundary, or existing gate/capability exceptions. The proof must be matrix-first so fixes are targeted rather than cosmetic.
- **Approval class**: Core Enterprise.
- **Red flags triggered**: Matrix/proof artifact and many surfaces. Defense: the matrix is spec-local evidence, not a runtime registry or framework; scope is existing authorization only; no new roles, concepts, persisted truth, or surfaces are introduced.
- **Score**: Nutzen: 2 | Dringlichkeit: 2 | Scope: 2 | Komplexitaet: 1 | Produktnaehe: 2 | Wiederverwendung: 2 | **Gesamt: 11/12**
- **Decision**: approve as a bounded authorization proof and hardening package.
## Problem Statement
Spec 400 concluded that TenantPilot passes product-contract review with conditions, including a P1 need for a resource-policy matrix. Several resource-backed models do not have obvious explicit policy classes, while many paths use gates, capabilities, Filament authorization methods, or scoped query constraints.
This spec answers:
```text
Can TenantPilot prove that every resource-backed admin/system surface has an explicit, consistent, and tested authorization contract?
```
A surface is authorization-safe only when the implementation can prove who may access it, in which panel, under which workspace/environment/system scope, through which policy/gate/capability path, with which list/view/create/update/delete/custom/bulk/search/relation decisions, and with which tests proving unauthorized direct access is blocked.
## Product / Business Value
Authorization proof is a trust feature for an Intune management product. It prevents false confidence in UI-hidden controls, reduces cross-workspace leakage risk, clarifies system/admin separation, and gives future agents a concrete map instead of implicit assumptions.
## Primary Users / Operators
- Workspace owners/managers who expect workspace and managed-environment isolation.
- Tenant/MSP operators who use admin resources but must be blocked from system-only functions.
- Readonly/customer/reviewer actors where tests or surfaces represent customer-safe access.
- Platform/system operators who use `/system` without leaking cross-workspace views into `/admin`.
- Engineering reviewers and future implementation agents validating authorization safety.
## Spec Scope Fields *(mandatory)*
- **Scope**: Admin panel resources/pages/actions, system panel pages/actions, relation managers, table/header/record/bulk actions, global search posture, resource-backed routes/controllers, and workspace/environment/system scoped model access.
- **Primary Routes / Surfaces**:
- Admin panel `/admin`, discovered resources under `apps/platform/app/Filament/Resources`.
- System panel `/system`, discovered pages under `apps/platform/app/Filament/System/Pages`.
- Resource-backed admin routes under `/admin/workspaces/{workspace}/environments/{environment}/...`.
- Workspace resource routes under `/admin/workspaces/...`.
- System directory and operations routes under `/system/directory/...`, `/system/ops/...`, `/system/security/access-logs`, and `/system/repair-workspace-owners`.
- Controller-backed admin routes for review-pack downloads/rendered reports, management-report PDF downloads, provider consent/RBAC callbacks, finding-exception queue deep links, workspace switching, and environment selection where they touch resource authorization.
- **Required domains**:
- Workspace and workspace membership.
- ManagedEnvironment and environment memberships/access scopes.
- Provider connections, provider credentials/readiness/freshness/permissions.
- Backup schedules, backup sets, backup items, backup runs, restore runs.
- Evidence snapshots/items, evidence anchors where repo-real, findings, finding exceptions/decisions/evidence references.
- Environment reviews, review publication resolution, review packs, stored reports/exports.
- Operation runs, audit logs, governance inbox/register pages, operational controls, system operations, system directory, user/team/access management surfaces.
- **Data Ownership**:
- Workspace-owned records remain workspace scoped.
- ManagedEnvironment-owned records remain workspace plus managed-environment scoped.
- OperationRun may be workspace-scoped or managed-environment scoped depending on run type, but detail links must enforce entitlement before revealing tenant-bound records.
- System-only records and system pages remain platform-plane scoped.
- No new persisted entity/table/artifact is allowed by default.
- **RBAC**:
- Admin plane (`/admin`) uses authenticated `User` plus workspace/environment membership and capability gates.
- System plane (`/system`) uses authenticated `PlatformUser` plus `PlatformCapabilities`.
- Non-member or wrong workspace/environment scope remains deny-as-not-found.
- Member missing capability remains forbidden at execution level.
- Cross-plane access remains denied and non-enumerable.
- UI visibility is never sufficient authorization proof.
For canonical-view or mixed-scope specs:
- **Default filter behavior when environment-context is active**: Existing route-owned workspace/environment context must remain authoritative. Missing or stale session context must not expose records.
- **Explicit entitlement checks preventing cross-tenant leakage**: List queries, record resolution, action execution, relation managers, exports/downloads, global search, and system cross-workspace views must each prove scope and capability checks.
## No Legacy / No Backward Compatibility Constraint *(mandatory)*
TenantPilot is pre-production for this authorization contract.
- **Compatibility posture**: canonical authorization proof and hardening over current behavior.
- **Legacy aliases, fallback readers, hidden routes, duplicate UI, old labels, or historical fixtures kept?**: no new compatibility path is allowed.
- **Why clean replacement is safe now**: Authorization gaps must be fixed or explicitly documented as product-decision debt. Pre-production status means unsafe or ambiguous authorization paths should not be preserved for historical compatibility.
## UI Surface Impact *(mandatory - UI-COV-001)*
Does this spec add, remove, rename, or materially change any reachable UI surface?
- [ ] No UI surface impact
- [x] Existing page changed
- [ ] New page/route added
- [ ] Navigation changed
- [ ] Filament panel/provider surface changed
- [ ] New modal/drawer/wizard/action added
- [ ] New table/form/state added
- [ ] Customer-facing surface changed
- [x] Dangerous action changed
- [ ] Status/evidence/review presentation changed
- [ ] Workspace/environment context presentation changed
Rationale: Spec 402 does not add surfaces or redesign presentation, but implementation may harden visibility, disabled state, global search posture, direct action execution, relation-manager access, or high-impact action authorization on existing rendered pages. Treat these as existing-surface authorization changes requiring focused browser proof.
## UI/Productization Coverage *(mandatory when UI Surface Impact is not "No UI surface impact")*
- **Route/page/surface**: Existing admin/system resources and pages listed in the authorization matrix; exact touched surfaces must be named in `implementation-report.md`.
- **Current or new page archetype**: Existing Search/Index, Settings, Decision, Receipt, Technical Annex, and System Admin pages only. No new archetype may be introduced.
- **Design depth**: Domain Pattern Surface / System Admin / Technical Annex depending on existing surface. This spec is authorization proof, not a product redesign.
- **Repo-truth level**: repo-verified existing surfaces.
- **Existing pattern reused**: native Filament resource/page/action authorization, policies, gates, `UiEnforcement`, `WorkspaceUiEnforcement`, scoped queries, platform capability middleware, and existing browser fixture patterns.
- **New pattern required**: none by default. If implementation finds a recurring structural authorization defect, stop and update spec/plan before introducing any shared helper or abstraction.
- **Screenshot required**: no full screenshot set by default. Focused browser proof is required for representative high-risk paths.
- **Page audit required**: no broad page audit. Matrix and focused proof only.
- **Customer-safe review required**: required only where existing customer/reviewer tests or customer-safe surfaces are included in the matrix.
- **Dangerous-action review required**: yes for existing destructive/high-impact actions touched or classified.
- **Coverage files updated or explicitly not needed**:
- [ ] `docs/ui-ux-enterprise-audit/route-inventory.md`
- [ ] `docs/ui-ux-enterprise-audit/design-coverage-matrix.md`
- [ ] `docs/ui-ux-enterprise-audit/page-reports/...`
- [ ] `docs/ui-ux-enterprise-audit/strategic-surfaces.md`
- [ ] `docs/ui-ux-enterprise-audit/grouped-follow-up-candidates.md`
- [ ] `docs/ui-ux-enterprise-audit/unresolved-pages.md`
- [x] `N/A - no new reachable UI surface; existing-surface authorization hardening only`
- **No-impact rationale when applicable**: N/A because existing rendered authorization behavior may change.
## Product Surface Impact *(mandatory for UI-affecting specs)*
Reference: `docs/product/standards/product-surface-contract.md`.
- **Product Surface Contract applies?**: yes. Existing actions, global search, route access, downloads/exports, and system/admin boundaries can affect rendered product access.
- **Page archetype**: existing archetypes only; exact touched pages recorded in implementation report.
- **Primary user question**: "Am I allowed to see or perform this action for this workspace/environment/system scope?"
- **Primary action**: no new primary action. Existing primary actions must remain authorization-correct.
- **Surface budget result**: neutral by default; any UI complexity increase requires an implementation-report exception.
- **Technical Annex / deep-link demotion**: unchanged. OperationRun, raw evidence, IDs, payloads, source keys, detectors, fingerprints, and technical logs must not become more visible.
- **Canonical status vocabulary**: unchanged. No new status vocabulary is allowed.
- **Visible complexity impact**: neutral or decreased. Hidden/disabled actions may become more accurate; no new UI layers.
- **Product Surface exceptions**: none planned.
## Browser Verification Plan *(mandatory)*
- **Browser proof required?**: yes for representative high-risk authorization surfaces.
- **No-browser rationale**: N/A.
- **Focused path when required**:
1. One authorized admin workspace-scoped resource allowed path.
2. One admin cross-workspace denied resource path.
3. One `/system` resource/page denied to a normal workspace admin.
4. One high-impact action hidden, disabled, or execution-blocked for unauthorized actor.
5. One globally unsearchable or scoped-search sensitive resource posture by the narrowest honest proof path; use feature-level proof when the focused browser smoke does not exercise the global-search UI.
- **Primary interaction to execute**: route navigation, table/resource action visibility, direct denied URL, one high-impact action proof, and global search only when the focused browser path is available without turning the smoke into a broad UI audit.
- **Console, Livewire, Filament, network, and 500-error checks**: planned.
- **Full-suite failure triage**: unrelated browser failures are documented separately; do not claim full browser audit.
## Human Product Sanity Check *(mandatory)*
- **Required?**: yes, as a focused authorization sanity check for changed rendered behavior.
- **No-human-sanity rationale**: N/A.
- **Reviewer questions**: Are allowed/denied states understandable? Does forbidden access avoid leaking record existence? Are high-impact actions separated and confirmation-protected? Is the system/admin boundary clear? Did visible complexity stay neutral or decrease?
- **Planned result location**: `specs/402-resource-policy-authorization-proof-matrix/implementation-report.md`.
## Product Surface Merge Gate Checklist *(mandatory)*
- [x] No-legacy posture or approved exception recorded.
- [x] Product Surface Impact is completed for existing-surface authorization hardening.
- [x] Browser proof is required for representative authorization surfaces during implementation.
- [x] Human Product Sanity is required for changed rendered behavior.
- [x] Product Surface exceptions are documented as `none planned`.
- [x] Implementation report will state Livewire v4 compliance, provider registration location, global search posture, destructive/high-impact action posture, asset strategy, tests/browser result, deployment impact, visible complexity outcome, and no completed-spec rewrite assertion.
## Cross-Cutting / Shared Pattern Reuse
- **Cross-cutting feature?**: yes.
- **Interaction class(es)**: authorization, action visibility/disabled state, global search, relation managers, downloads/exports, system access.
- **Systems touched**: policies, gates, capability resolvers, `UiEnforcement`, `WorkspaceUiEnforcement`, scoped resource queries, platform capability middleware, Filament resources/pages/relation managers, controller-backed downloads/deep links.
- **Existing pattern(s) to extend**: existing policies/gates/capabilities and UI enforcement paths.
- **Shared contract / presenter / builder / renderer to reuse**: existing authorization services and UI enforcement helpers; no new shared pattern by default.
- **Why the existing shared path is sufficient or insufficient**: sufficient until the matrix proves a concrete gap. Any new helper requires spec/plan update and proportionality review.
- **Allowed deviation and why**: documented policy/gate/capability exception only when it is safer and clearer than adding a cosmetic policy.
- **Consistency impact**: direct execution, UI visibility, scoped queries, search, relations, and bulk actions must agree.
- **Review focus**: no hidden UI-only authorization, no duplicate role logic, no raw role strings in new code, no unscoped relation/search/export path, no completed-spec rewrite.
## OperationRun UX Impact
- **Touches OperationRun start/completion/link UX?**: possibly, only as authorization proof for existing OperationRun-linked resources/actions.
- **Shared OperationRun UX contract/layer reused**: existing OperationRun policies, links, and start UX paths only.
- **Delegated start/completion UX behaviors**: unchanged.
- **Local surface-owned behavior that remains**: existing initiation inputs only.
- **Queued DB-notification policy**: unchanged.
- **Terminal notification path**: unchanged.
- **Exception required?**: none planned.
## Provider Boundary / Platform Core Check
- **Shared provider/platform boundary touched?**: yes, by inspection of provider connections/readiness/permissions and provider-backed resources.
- **Boundary classification**: mixed; provider connection details are provider-owned, while workspace/environment authorization, capability gates, operations, evidence, and audit are platform-core.
- **Seams affected**: provider connections, provider credentials, managed-environment permissions, provider readiness/freshness actions, OperationRun proof links.
- **Neutral platform terms preserved or introduced**: workspace, managed environment, provider connection, operation, evidence, audit, capability.
- **Provider-specific semantics retained and why**: Microsoft/Graph/Entra terms remain only where existing provider-owned surfaces require them.
- **Why this does not deepen provider coupling accidentally**: the spec changes authorization proof only and does not create provider taxonomy or new provider-facing product concepts.
- **Follow-up path**: provider productization remains outside scope.
## UI / Surface Guardrail Impact
| Surface / Change | Operator-facing surface change? | Native vs Custom | Shared-Family Relevance | State Layers Touched | Exception Needed? | Low-Impact / `N/A` Note |
|---|---|---|---|---|---|---|
| Admin Filament resources and relation managers | yes, if hardening changes visible authorization | Native Filament + existing helpers | authorization, actions, search, relations | page, table, relation, modal/action, route/query | none planned | Existing-surface authorization only |
| System Filament pages | yes, if capability access changes | Native Filament pages/widgets | platform capabilities | page, route | none planned | System/admin boundary proof |
| Controller-backed downloads/deep links | no rendered surface change unless link visibility changes | Laravel controllers + existing links | downloads/exports | route/action | none planned | Direct authorization proof |
## Decision-First Surface Role
| Surface | Decision Role | Human-in-the-loop Moment | Immediately Visible for First Decision | On-Demand Detail / Evidence | Why This Is Primary or Why Not | Workflow Alignment | Attention-load Reduction |
|---|---|---|---|---|---|---|---|
| Existing admin resource index/detail pages | Primary or secondary depending on existing page | Decide whether actor can inspect or act on scoped resource | Allowed actions and denied/disabled state | Audit/OperationRun/evidence links where already present | Not changed by this spec | Existing workflows only | Prevents false clickable affordances |
| Existing system pages | System Admin | Platform operator decides or inspects system state | System-only access and capability-gated actions | System diagnostics and access logs | System plane only | Existing `/system` workflows | Blocks workspace-admin confusion |
## Audience-Aware Disclosure
| Surface | Audience Modes In Scope | Decision-First Default-Visible Content | Operator Diagnostics | Support / Raw Evidence | One Dominant Next Action | Hidden / Gated By Default | Duplicate-Truth Prevention |
|---|---|---|---|---|---|---|---|
| Admin resources | operator-MSP, readonly/customer where already represented | Safe action availability and scoped records | Existing audit/evidence/run links | Capability-gated only | Existing primary action | Unauthorized records and raw internals | Matrix records one authorization decision per action |
| System pages | support-platform | System capability access | System diagnostics | Platform capability-gated | Existing system action | Workspace-admin access | Separate admin/system decisions |
## UI/UX Surface Classification
| Surface | Action Surface Class | Surface Type | Likely Next Operator Action | Primary Inspect/Open Model | Row Click | Secondary Actions Placement | Destructive Actions Placement | Canonical Collection Route | Canonical Detail Route | Scope Signals | Canonical Noun | Critical Truth Visible by Default | Exception Type / Justification |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Admin resources | Existing list/detail/action classes | CRUD / Registry / Technical Annex / Decision as already defined | Inspect, manage, restore, export, run, verify, or resolve according to existing resource | Existing resource route | Existing behavior | Existing More/detail placement | Confirmation-protected, server-authorized | Existing `/admin/...` routes | Existing detail routes | Workspace/environment | Existing nouns | Authorized action state | none planned |
| System pages | System Admin | System operations/directory/security | Inspect or operate platform controls | Existing page route | Existing behavior | Existing page actions | Capability-gated | `/system/...` | `/system/...` | Platform/system | Existing nouns | Platform access state | none planned |
## Operator Surface Contract
| Surface | Primary Persona | Decision / Operator Action Supported | Surface Type | Primary Operator Question | Default-visible Information | Diagnostics-only Information | Status Dimensions Used | Mutation Scope | Primary Actions | Dangerous Actions |
|---|---|---|---|---|---|---|---|---|---|---|
| Existing admin resources | Workspace operator | Access scoped records and execute allowed actions | Existing | Can I access or act on this scoped record? | Allowed records/actions | Raw proof, payloads, audit internals where existing | Existing | TenantPilot / provider / simulation as existing | Existing | Must be confirmed and execution-authorized |
| Existing system pages | Platform operator | Inspect or operate platform/system state | System Admin | Can I access or act on this system resource? | System capability-allowed surfaces | System diagnostics | Existing | Platform/system | Existing | Must be platform-capability gated |
## Proportionality Review *(mandatory when structural complexity is introduced)*
- **New source of truth?**: no runtime source of truth. The implementation report matrix is review evidence only.
- **New persisted entity/table/artifact?**: no.
- **New abstraction?**: no by default. Any proposed helper/policy abstraction requires updated proportionality review before implementation continues.
- **New enum/state/reason family?**: no.
- **New cross-domain UI framework/taxonomy?**: no.
- **Current operator problem**: authorization posture is too implicit to audit consistently across admin/system resources.
- **Existing structure is insufficient because**: scattered proof makes it hard to distinguish safe gate/capability exceptions from missing policy coverage or direct-access gaps.
- **Narrowest correct implementation**: matrix-first proof, targeted tests, minimal hardening only when evidence shows a gap.
- **Ownership cost**: one implementation report/matrix plus focused tests and any targeted policies/checks added.
- **Alternative intentionally rejected**: blindly generating policies for every model or creating a new authorization framework.
- **Release truth**: current-release trust and safety requirement.
### Compatibility posture
This feature assumes a pre-production environment. Backward compatibility, legacy aliases, migration shims, historical fixtures, and compatibility-specific tests are out of scope unless explicitly required by an existing product contract.
## Testing / Lane / Runtime Impact *(mandatory for runtime behavior changes)*
- **Test purpose / classification**: Feature, Filament/Livewire, policy/gate, Heavy-Governance for matrix/proof if grouped, Browser for representative authorization smoke.
- **Validation lane(s)**: confidence plus focused browser; fast-feedback for small policy/gate tests where possible.
- **Why this classification and these lanes are sufficient**: authorization behavior spans HTTP routes, Filament resources/actions/relation managers, policy/gate decisions, and rendered browser visibility; no full browser audit is needed.
- **New or expanded test families**: `Spec402` focused authorization tests only.
- **Fixture / helper cost impact**: use existing factories/helpers; any new helper must stay cheap and explicit.
- **Heavy-family visibility / justification**: matrix-style discovery may be heavy-governance if implemented as broad guard; keep it explicit and not in narrow lanes accidentally.
- **Special surface test profile**: global-context-shell for workspace/environment scope; standard-native-filament for ordinary resources; system-admin for `/system`; browser for representative proof.
- **Standard-native relief or required special coverage**: standard resources need focused direct/access/action tests; high-risk actions and system boundary require browser proof.
- **Reviewer handoff**: reviewers verify lane fit, fixture cost, negative tests for every fixed gap, and no broad helper drift.
- **Budget / baseline / trend impact**: none expected; document if test runtime materially increases.
- **Escalation needed**: document-in-feature by default; follow-up-spec if a structural authorization framework decision is required.
- **Active feature PR close-out entry**: `Guardrail / Exception / Smoke Coverage`.
- **Planned validation commands**:
- `cd apps/platform && ./vendor/bin/sail artisan test --filter=Policy`
- `cd apps/platform && ./vendor/bin/sail artisan test --filter=Authorization`
- `cd apps/platform && ./vendor/bin/sail artisan test --filter=Capability`
- `cd apps/platform && ./vendor/bin/sail artisan test --filter=Filament`
- `cd apps/platform && ./vendor/bin/sail artisan test --filter=Resource`
- `cd apps/platform && ./vendor/bin/sail artisan test --filter=System`
- targeted Spec 402 test files created during implementation
- focused browser command for the Spec 402 browser smoke if available
- `git diff --check`
## User Scenarios & Testing *(mandatory)*
### User Story 1 - Build the resource authorization matrix (Priority: P1)
As an engineering reviewer, I want a complete matrix of admin/system resource-backed surfaces, authorization mechanisms, scope decisions, search posture, relation/bulk posture, and tests, so I can tell which resources are explicitly safe, fixed, deferred, or failed.
**Independent Test**: Inspect the implementation report. Every inspected resource has a decision of `OK: explicit policy`, `OK: explicit gate/capability`, `OK: documented exception`, `FIXED: policy added`, `FIXED: authorization hardened`, `DEFERRED: product decision required`, or `FAIL: unsafe or unproven`.
### User Story 2 - Prove admin workspace/environment isolation (Priority: P1)
As a workspace operator, I want admin resources to show and mutate only records in my authorized workspace/environment, so direct URLs, action calls, relation managers, exports, and global search cannot leak another workspace's data.
**Independent Test**: Focused tests prove allowed user can list/view an authorized record, cross-workspace user receives deny-as-not-found on guessed URLs and action targets, relation managers do not expose unrelated records, and sensitive global search is disabled or scoped.
### User Story 3 - Prove system/admin separation (Priority: P1)
As a platform operator, I want `/system` to stay separate from `/admin`, so normal workspace admins cannot access system-only directory, operations, repair, or access-log surfaces, while authorized platform users can access intended system pages.
**Independent Test**: Focused tests prove workspace admins are denied system pages/actions, platform users with required capabilities can access intended system surfaces, and mutation/repair actions require stronger platform capabilities than read-only diagnostics.
### User Story 4 - Harden only confirmed authorization gaps (Priority: P1)
As a maintainer, I want minimal fixes for confirmed unsafe or unproven high-risk paths, so authorization becomes safer without inventing a new RBAC model or adding cosmetic policies that duplicate existing capability logic.
**Independent Test**: Every fixed gap has at least one negative test proving unauthorized direct access/action/search/relation/bulk path is blocked and no unauthorized mutation/audit/evidence side effect occurs.
### User Story 5 - Record implementation proof and residual findings (Priority: P1)
As a future agent, I want the final report to record dirty state, matrix decisions, changes, tests, browser proof, P0/P1/P2/P3 residuals, and next-step recommendation, so later specs do not assume UI visibility equals authorization.
**Independent Test**: The implementation report follows the required Spec 402 structure and classifies remaining findings; `PASS` is used only when no P0/P1 high-risk proof gaps remain.
## Functional Requirements
- **FR-402-001**: Implementation MUST record dirty state before and after work, including branch, tracked changes, untracked files, and `git diff --check`.
- **FR-402-002**: Implementation MUST inventory existing admin panel resources, system panel pages, model-backed Filament pages, relation managers, controller-backed admin/system routes, policies, gates, capability checks, route middleware, scoped query helpers, global search posture, and relevant tests.
- **FR-402-003**: The authorization matrix MUST include columns for resource/surface, panel, model, category, workspace/environment/system scope, policy, gate/capability, list/view/create/update/delete, custom actions, bulk actions, relations, global search, query-scope proof, direct-access proof, tests, and decision.
- **FR-402-004**: Every inspected resource-backed surface MUST receive exactly one explicit decision: `OK: explicit policy`, `OK: explicit gate/capability`, `OK: documented exception`, `FIXED: policy added`, `FIXED: authorization hardened`, `DEFERRED: product decision required`, or `FAIL: unsafe or unproven`.
- **FR-402-005**: High-impact operational resources MUST have policy/gate/capability proof and direct unauthorized execution tests.
- **FR-402-006**: Sensitive workspace/environment data resources MUST have workspace/environment scoping proof and customer/internal boundary proof where applicable.
- **FR-402-007**: Governance/audit/proof resources MUST separately prove read access and action access.
- **FR-402-008**: System-only resources MUST deny normal workspace admins and explicitly authorize platform users.
- **FR-402-009**: Low-risk/read-only/internal resources MAY use documented exceptions only when no unsafe access path exists.
- **FR-402-010**: For Eloquent model-backed resources, policies are preferred unless an existing central gate/capability/scoped-query contract is explicitly documented and tested as sufficient.
- **FR-402-011**: New policies MUST delegate to existing capability/scope services where those services already define product authorization semantics.
- **FR-402-012**: Implementation MUST NOT invent new product roles, permission hierarchy, capability vocabulary, or independent role-string logic.
- **FR-402-013**: Static Filament `can*` methods MAY remain only if they delegate to explicit gates/capabilities/policies or are documented as an exception and direct execution remains blocked.
- **FR-402-014**: Route middleware alone MUST NOT count as record-level proof unless scoped record resolution, actor entitlement, capability, and action checks are also proven.
- **FR-402-015**: Every resource with global search enabled or potentially enabled MUST be classified as globally searchable and authorized, not globally searchable by design, system-only searchable, scoped-search only, or unsafe/missing proof.
- **FR-402-016**: Bulk actions MUST be disabled, authorized, or documented with tests proving they cannot bypass per-record authorization.
- **FR-402-017**: Relation managers MUST prove parent and related record scoping and must not expose or mutate unrelated records.
- **FR-402-018**: Controller-backed downloads, exports, rendered reports, and signed links MUST prove actor authorization to the underlying workspace/environment/customer-safe artifact.
- **FR-402-019**: Every fixed authorization gap MUST include a negative test.
- **FR-402-020**: Focused browser proof MUST cover representative allowed admin access, cross-workspace denial, system-only denial to workspace admin, one high-impact action hidden/blocked for unauthorized actor, and one global-search posture check where applicable.
- **FR-402-021**: Implementation MUST produce the required Spec 402 implementation report with matrix, runtime changes, tests, browser proof, findings, deferred items, validation commands, and recommended next step.
## Non-Functional Requirements
- **NFR-402-001**: Keep changes narrowly scoped to authorization proof and minimal hardening.
- **NFR-402-002**: Do not introduce schema changes, migrations, new persistent entities, or new product surfaces.
- **NFR-402-003**: Preserve completed-spec history and validation artifacts.
- **NFR-402-004**: Use existing Laravel 12, Filament v5, Livewire v4, and Pest 4 conventions.
- **NFR-402-005**: Avoid broad test-suite expansion; keep heavy-governance/browser cost explicit.
- **NFR-402-006**: No secrets, tokens, raw credential payloads, or sensitive raw provider payloads may be logged or added to reports.
## Out of Scope
- New roles, new role hierarchy, new permission product model, or new customer-visible permission UX.
- Broad RBAC redesign.
- New admin, system, customer, or navigation surfaces.
- New restore/backup/provider feature behavior beyond authorization hardening.
- Evidence anchor/currentness runtime closure.
- Management Report PDF staging validation.
- Governance artifact lifecycle/retention.
- UI audit registry reconciliation.
- JSON-to-JSONB data-layer hardening.
- Full browser/UX/runtime audit.
- Broad service/model/Filament refactor.
- Rewriting historical completed specs.
## Acceptance Criteria
- Existing admin and system resource-backed surfaces are inventoried.
- A Resource Policy & Authorization Matrix is produced.
- Every inspected resource has an explicit authorization decision.
- Missing policy classes are either added or justified with documented exception.
- Existing gates/capabilities are proven where used instead of policies.
- Workspace/environment isolation is tested for high-risk resources.
- System/admin separation is tested.
- Direct unauthorized access is tested for high-risk resources.
- Custom actions are execution-authorized and tested where relevant.
- Bulk actions are disabled, authorized, or explicitly documented.
- Relation manager scoping is tested where relevant.
- Global search posture is disabled, scoped, or tested where relevant.
- No new product roles/surfaces/concepts are introduced.
- Remaining findings are classified P0/P1/P2/P3.
- Validation commands and results are included.
## Candidate Gate Rules
- **PASS**: no P0 findings remain; no P1 authorization proof gaps remain for high-risk resources; every inspected resource has an explicit matrix decision; targeted tests pass; focused browser proof passes or is not applicable with reason; no new product/authorization model drift is introduced.
- **PASS WITH CONDITIONS**: no P0 findings remain; remaining P1 gaps are explicitly bounded and safely deferred; critical high-risk resource authorization is proven; exceptions are documented; remaining work belongs to later specs or lower-risk productization.
- **FAIL**: any P0 remains; unsafe cross-workspace access exists; unauthorized high-impact action execution is possible; system-only resources are accessible to workspace admins; customer/reviewer can access internal-only resources; global search leaks sensitive records; authorization behavior requires unresolved product decisions; fixes exceed bounded scope.
## Required Final Implementation Report Structure
The future implementation report must use this top-level structure:
```markdown
# Spec 402 Implementation Report - Resource Policy & Authorization Proof Matrix
## A. Candidate Gate Result
## B. Scope Confirmation
## C. Dirty State
## D. Resource Policy & Authorization Matrix
## E. Runtime Changes Made
## F. Policies / Gates / Capabilities
## G. Tests Added or Updated
## H. Authorization Proof Summary
## I. Browser Proof
## J. Remaining Findings
## K. Deferred Items
## L. Validation Commands
## M. Recommended Next Step
```
## Success Criteria
- **SC-402-001**: 100% of inspected resource-backed surfaces have a matrix row and decision.
- **SC-402-002**: 100% of fixed authorization gaps include a negative test.
- **SC-402-003**: High-impact resources prove direct access/action denial.
- **SC-402-004**: Cross-workspace, system/admin, and global search boundaries are proven for representative high-risk surfaces.
- **SC-402-005**: The final report can recommend Spec 403 only when no P0 remains and any P1 conditions are bounded.
- **SC-402-006**: No runtime code outside authorization proof/hardening scope is changed.
## Assumptions
- The operator-provided Spec 402 draft is an explicit manual promotion despite the automatic candidate queue being empty.
- The existing branch prefix collision with `402-screwfast-website-rebuild` is unrelated to TenantPilot spec artifacts; this package uses the requested Spec ID 402.
- `ManagedEnvironment` is the current repo model for managed tenant/environment context; existing tests and some route/copy names may still use tenant terminology.
- Spec 401 has no unresolved P0 blocker in its implementation report, but its provider UI residual must be treated as related proof context.
- Implementation may create or update `specs/402-resource-policy-authorization-proof-matrix/implementation-report.md`.
## Risks
- The matrix scope is broad; implementation must avoid turning the work into a full RBAC redesign or broad browser audit.
- Existing gate/capability paths may be safe but under-documented; the report must distinguish missing proof from unsafe behavior.
- Adding policies without understanding existing capability services could create contradictory authorization logic.
- Focused browser proof may require fixtures; any fixture expansion must be explicit and bounded.
- Product decisions may be required for genuinely ambiguous system/admin/customer boundaries; those must be deferred rather than invented.
## Open Questions
None blocking preparation. During implementation, if a resource's intended authorization decision cannot be derived from existing contracts, record `DEFERRED: product decision required` and stop before inventing semantics.
## Follow-Up Spec Candidates
- Spec 403 - Evidence Anchor & Currentness Runtime Closure, only after Spec 402 reaches at least `PASS WITH CONDITIONS` without P0.
- Provider readiness/onboarding productization if provider proof reveals UX friction but no authorization defect.
- Dedicated authorization remediation spec if Spec 402 returns `FAIL` or leaves P0/P1 blockers too large for bounded fixes.

View File

@ -0,0 +1,163 @@
# Tasks: Spec 402 - Resource Policy & Authorization Proof Matrix
**Input**: `specs/402-resource-policy-authorization-proof-matrix/spec.md`, `plan.md`, `checklists/requirements.md`, user-provided Spec 402 draft, Spec 400 and Spec 401 context, Product Surface Contract, and repo truth.
**Tests**: Required. This spec changes or verifies authorization behavior and must include focused Pest policy/gate/Feature/Filament tests plus focused browser proof for representative high-risk rendered paths.
## Test Governance Checklist
- [x] Lane assignment is named and is the narrowest sufficient proof for the changed authorization behavior.
- [x] New or changed tests stay in the smallest honest family, and any heavy-governance or browser addition is explicit.
- [x] Shared helpers, factories, seeds, fixtures, and context defaults stay cheap by default; any widening is isolated or documented.
- [x] Planned validation commands cover resource authorization without pulling in unrelated lane cost.
- [x] The declared surface test profile or `standard-native-filament` relief is explicit.
- [x] Browser proof covers representative rendered authorization behavior and does not claim full browser audit.
- [x] Human Product Sanity and Product Surface implementation-report close-out are planned.
- [x] Any material budget, baseline, trend, or escalation note is recorded in the implementation report.
## Phase 1: Preparation And Dirty-State Baseline
**Purpose**: Establish safe starting conditions and read all governing context before runtime edits.
- [x] T001 Read `specs/402-resource-policy-authorization-proof-matrix/spec.md`, `plan.md`, `tasks.md`, and `checklists/requirements.md`.
- [x] T002 Record current branch, HEAD, dirty state, tracked changed files, untracked files, and `git diff --check` in `specs/402-resource-policy-authorization-proof-matrix/implementation-report.md`.
- [x] T003 Re-read `AGENTS.md`, `.specify/memory/constitution.md`, `.specify/README.md`, `docs/ai-coding-rules.md`, `docs/security-guidelines.md`, `docs/testing-guidelines.md`, `docs/architecture-guidelines.md`, `docs/filament-guidelines.md`, and `docs/product/standards/product-surface-contract.md`.
- [x] T004 Re-read `specs/400-product-contract-spec-completeness-audit/` and `specs/401-high-risk-admin-action-proof-pack/implementation-report.md` as read-only context; preserve completed-spec history.
- [x] T005 Confirm Spec 401 has no unresolved P0 high-risk action blocker before making Spec 402 runtime changes; record any residual proof debt as input to the matrix.
- [x] T006 Confirm no new roles, new capability vocabulary, new product surfaces, migrations, JSON-to-JSONB work, governance lifecycle work, management PDF behavior, or broad UI redesign will be included.
## Phase 2: Baseline Inventory
**Purpose**: Build repo-truth inventory before adding policies or hardening code.
- [x] T007 Inventory panel provider registration and access middleware in `apps/platform/bootstrap/providers.php`, `apps/platform/app/Providers/Filament/AdminPanelProvider.php`, and `apps/platform/app/Providers/Filament/SystemPanelProvider.php`.
- [x] T008 Inventory admin/system routes with Laravel route tooling or Boost route listing, including `/admin`, `/admin/workspaces/{workspace}/environments/{environment}/...`, `/system`, controller-backed downloads/exports, and signed links.
- [x] T009 Inventory Filament resources under `apps/platform/app/Filament/Resources/`, including model, pages, global search posture, `can*` methods, `getEloquentQuery()`, `resolveRecordRouteBinding()`, actions, bulk actions, and relation managers.
- [x] T010 Inventory model-backed admin/custom pages under `apps/platform/app/Filament/Pages/`, including governance, monitoring, evidence, operations, review, onboarding, workspace settings, and context pages.
- [x] T011 Inventory system pages/widgets under `apps/platform/app/Filament/System/` and classify read-only diagnostics versus mutation/repair actions.
- [x] T012 Inventory policies under `apps/platform/app/Policies/`, policy registration in `AuthServiceProvider` and `AppServiceProvider`, and any model-backed resource without an explicit policy.
- [x] T013 Inventory gates/capabilities in `apps/platform/app/Providers/AuthServiceProvider.php`, `apps/platform/app/Services/Auth/`, `apps/platform/app/Support/Auth/`, and existing `UiEnforcement` / `WorkspaceUiEnforcement` paths.
- [x] T014 Inventory authorization-relevant controllers and middleware under `apps/platform/app/Http/`, especially review-pack/report downloads, management-report PDF downloads, provider consent/RBAC callbacks, finding-exception queue deep links, workspace/environment switching, and platform capability middleware.
- [x] T015 Inventory existing tests under `apps/platform/tests/` for policy, authorization, capability, Filament resource, relation manager, global search, admin/system boundary, backup/restore/provider/evidence/review/report/operation/audit coverage.
## Phase 3: Resource Policy & Authorization Matrix
**Purpose**: Create the matrix first; do not fix runtime behavior until every inspected surface has a classification target.
- [x] T016 Create `specs/402-resource-policy-authorization-proof-matrix/implementation-report.md` with the required report headings A through M from `spec.md`.
- [x] T017 Add the matrix table with columns: Resource / Surface, Panel, Model, Category, Workspace/Environment/System Scope, Policy, Gate/Capability, List, View, Create, Update, Delete, Custom Actions, Bulk Actions, Relations, Global Search, Query Scope Proof, Direct Access Proof, Tests, Decision.
- [x] T018 Classify each admin Filament resource as category A/B/C/D/E and record workspace/environment/system scope, model, pages, global search posture, and authorization mechanism.
- [x] T019 Classify each system page/action as category D or E and record platform capability path, read-only versus mutation/repair decision, and system/admin boundary proof.
- [x] T020 Classify controller-backed downloads/exports/deep links and signed routes by underlying model/resource, scope, capability path, and customer/internal boundary.
- [x] T021 Classify every relation manager by owner record, related model, parent scope, allowed actions, and direct invocation proof need.
- [x] T022 Classify every bulk action discovered in resources by action name, affected model(s), per-record authorization posture, confirmation posture, and tests.
- [x] T023 Classify global search for every resource as authorized/scoped, disabled by design, system-only, unsafe/missing proof, or not applicable.
- [x] T024 Record existing tests proving each matrix decision; mark missing proof as P1 until proven or explicitly deferred.
## Phase 4: Gap Classification
**Purpose**: Decide whether a gap is missing proof, missing policy, unsafe access, or product-decision debt.
- [x] T025 Classify missing proof only gaps where implementation looks safe but direct/negative tests do not exist.
- [x] T026 Classify missing policy but sufficient gate/capability paths where existing resolvers are authoritative and tested.
- [x] T027 Classify missing policy and insufficient authorization paths where no explicit server-side check is proven.
- [x] T028 Classify UI visibility versus execution inconsistencies, especially static Filament `can*` methods, hidden actions, disabled actions, and direct Livewire action calls.
- [x] T029 Classify workspace/environment scope risks for list, view, action, relation manager, export/download, and global search paths.
- [x] T030 Classify system/admin boundary risks for `/system` access, system pages/actions, platform capabilities, and workspace-admin denial.
- [x] T031 Classify customer/reviewer/internal boundary risks where customer-safe review/report/evidence surfaces are represented.
- [x] T032 Classify relation manager, bulk action, and global search risks separately rather than folding them into page-level authorization.
- [x] T033 Mark product-ambiguous authorization decisions as `DEFERRED: product decision required` instead of inventing roles or capabilities.
## Phase 5: Tests Before Hardening
**Purpose**: Add focused negative tests for high-risk proof gaps before runtime changes where feasible.
- [x] T034 Add or update policy/gate tests proving allowed and denied decisions for every new or modified policy in `apps/platform/tests/Feature/...`.
- [x] T035 Add or update direct route/resource access tests proving authorized users can list/view authorized workspace/environment records and unauthorized users cannot access guessed record URLs.
- [x] T036 Add or update cross-workspace denial tests for at least one high-risk admin resource and every fixed cross-workspace gap.
- [x] T037 Add or update system/admin separation tests proving workspace admins cannot access `/system` resources and platform users require appropriate `PlatformCapabilities`.
- [x] T038 Add or update Filament action tests proving unauthorized actors cannot execute hidden/disabled high-impact actions directly and no unauthorized mutation/audit/evidence/operation side effect occurs; provider check/sync/snapshot disabled direct calls are covered by `ProviderConnectionsUiEnforcementTest`.
- [x] T039 Add or update bulk action tests proving disabled/authorized/per-record behavior for any fixed or high-risk bulk actions.
- [x] T040 Add or update relation manager tests proving parent and related records stay scoped and relation actions cannot affect unrelated records.
- [x] T041 Add or update global search tests proving sensitive resources are disabled or scoped and unauthorized/cross-workspace/system-only records do not appear.
- [x] T042 Add or update controller/download/export tests proving actor authorization to underlying workspace/environment/customer-safe artifacts before serving output.
- [x] T043 Add or update customer/reviewer boundary tests where existing customer-safe surfaces are in matrix scope.
## Phase 6: Minimal Hardening
**Purpose**: Fix only confirmed unsafe or unproven authorization gaps using existing architecture.
- [x] T044 Add a policy class only when the model is user-accessible, no sufficient explicit authorization path exists, behavior is derivable from existing product contracts, and tests can prove it.
- [x] T045 Register any newly required policy according to current repository convention without duplicating existing capability semantics.
- [x] T046 Update existing policies only when they lack required list/view/create/update/delete/custom action decisions or wrong-scope denial semantics.
- [x] T047 Update static Filament `can*` methods only when they do not delegate to explicit policies/gates/capabilities or contradict direct execution behavior.
- [x] T048 Add missing action-level server authorization checks for high-impact or destructive actions while preserving `->action(...)`, `->requiresConfirmation()`, audit behavior, and notifications where applicable.
- [x] T049 Disable or restrict unsafe global search rather than enabling search for completeness.
- [x] T050 Restrict unsafe bulk actions or add per-record authorization checks where bulk operations could bypass record authorization.
- [x] T051 Scope relation manager queries/actions to the owner workspace/environment and block unrelated related records.
- [x] T052 Harden controller-backed downloads/exports/deep links to authorize the underlying resource before output.
- [x] T053 Document any low-risk/read-only/internal exception in the matrix instead of adding cosmetic policies.
- [x] T054 Stop and update spec/plan before introducing any new authorization helper, new abstraction, new capability, or broader RBAC model.
## Phase 7: Focused Browser Proof
**Purpose**: Verify representative rendered authorization behavior without turning Spec 402 into a full browser audit.
- [x] T055 Add or update a focused browser smoke test such as `apps/platform/tests/Browser/Spec402ResourcePolicyAuthorizationSmokeTest.php` if browser support is available.
- [x] T056 Browser-proof one authorized admin workspace-scoped resource allowed path.
- [x] T057 Browser-proof one admin cross-workspace denied resource path.
- [x] T058 Browser-proof one system-only resource/page denied to a workspace admin.
- [x] T059 Browser-proof one high-impact action hidden for an unauthorized actor with no operation side effect; provider disabled-action UX is browser-tested, and direct disabled provider operation execution is feature-tested.
- [x] T060 Feature-proof one sensitive global-search disabled/scoped posture; browser global-search UI proof is intentionally not claimed by the focused smoke.
- [x] T061 Record route/surface, actor, panel, workspace/environment, expected result, observed result, and console/runtime errors in the implementation report.
- [x] T062 If browser tests are unavailable, record the exact blocker and do not claim browser proof.
## Phase 8: Implementation Report And Final Validation
**Purpose**: Close the proof loop with explicit results, residual severity, and next-step recommendation.
- [x] T063 Complete implementation report section A with Candidate Gate Result: PASS, PASS WITH CONDITIONS, or FAIL.
- [x] T064 Complete section B with included and explicitly not included scope.
- [x] T065 Complete section C with dirty state before/after, tracked files changed, and untracked files.
- [x] T066 Complete section D with the Resource Policy & Authorization Matrix.
- [x] T067 Complete section E with runtime changes made, why needed, and scope risk.
- [x] T068 Complete section F with policies/gates/capabilities added, updated, and documented exceptions.
- [x] T069 Complete section G with tests added/updated, positive/negative classification, and result.
- [x] T070 Complete section H with direct access, UI visibility, action execution, workspace/environment isolation, system/admin separation, customer/internal boundary, global search, relation manager, and bulk-action proof summary.
- [x] T071 Complete section I with focused browser proof or exact no-browser limitation.
- [x] T072 Complete section J with remaining findings by P0/P1/P2/P3.
- [x] T073 Complete section K with deferred items: evidence currentness closure, management PDF staging validation, governance lifecycle, JSONB migration, full browser audit, and other items.
- [x] T074 Complete section L with validation commands and exact results.
- [x] T075 Complete section M with recommended next action: Spec 403 only if no P0 remains and P1 conditions are bounded.
- [x] T076 Run `git diff --check` after implementation and record result.
- [x] T077 If PHP runtime or test files changed, run the project formatter for changed PHP files according to repository convention and record the result.
- [x] T078 Verify changed reports, tests, logs, and fixtures do not include secrets, tokens, raw credential payloads, or sensitive raw provider payloads.
- [x] T079 Run targeted Pest validation commands from `plan.md` and record pass/fail/output summary.
- [x] T080 Run focused browser validation command if available and record pass/fail/output summary.
- [x] T081 Run final dirty-state commands and confirm no unrelated dirty files were reset, deleted, or cleaned.
## Non-Goals Checklist
- [x] NT001 Do not add new product roles, role hierarchy, permission model, or capability vocabulary unless an existing contract already requires it.
- [x] NT002 Do not add new admin, system, customer, navigation, onboarding, evidence/reporting, backup, restore, provider, or governance lifecycle surfaces.
- [x] NT003 Do not add migrations, JSON-to-JSONB changes, new persisted truth, new status family, or new taxonomy.
- [x] NT004 Do not perform broad service/model/Filament refactors.
- [x] NT005 Do not rewrite completed specs or remove historical close-out, validation, smoke, browser, or task history.
- [x] NT006 Do not claim UI visibility is authorization proof.
- [x] NT007 Do not enable global search merely to satisfy matrix completeness.
- [x] NT008 Do not claim full browser/UX/runtime audit completion.
## Dependencies And Execution Order
- Phase 1 must complete before runtime edits.
- Phase 2 inventory must complete before Phase 3 matrix decisions.
- Phase 3 matrix must exist before Phase 4 gap classification.
- Phase 4 must classify gaps before tests/hardening.
- Phase 5 negative tests should precede Phase 6 hardening wherever feasible.
- Phase 6 fixes must stay bounded to confirmed authorization gaps.
- Phase 7 browser proof follows focused hardening and tests.
- Phase 8 closes with report, validation, dirty state, and recommended next step.
## Recommended Implementation Strategy
Treat implementation as an authorization proof loop, not a policy-generation pass. Prefer existing policies, gates, capabilities, scoped queries, and UI enforcement helpers. Add policy classes only when they improve safety and can be derived from current contracts. Every fixed authorization gap needs a negative test, and any ambiguous product decision must be deferred instead of invented.