create([ 'tenant_id' => (int) $tenant->getKey(), 'workspace_id' => (int) $tenant->workspace_id, 'user_id' => (int) $user->getKey(), 'type' => 'inventory_sync', 'status' => OperationRunStatus::Queued->value, 'outcome' => OperationRunOutcome::Pending->value, ]); $context = new QueuedExecutionContext( run: $run, operationType: 'inventory_sync', workspaceId: (int) $tenant->workspace_id, tenant: $tenant, initiator: $user, authorityMode: ExecutionAuthorityMode::ActorBound, requiredCapability: null, providerConnectionId: null, targetScope: [ 'workspace_id' => (int) $tenant->workspace_id, 'tenant_id' => (int) $tenant->getKey(), 'provider_connection_id' => null, ], ); $decision = QueuedExecutionLegitimacyDecision::deny( context: $context, checks: [ 'workspace_scope' => 'passed', 'tenant_scope' => 'passed', 'capability' => 'failed', 'tenant_operability' => 'not_applicable', 'execution_prerequisites' => 'not_applicable', ], reasonCode: ExecutionDenialReasonCode::MissingCapability, ); app()->instance(QueuedExecutionLegitimacyGate::class, new class($decision) { public function __construct(private readonly QueuedExecutionLegitimacyDecision $decision) {} public function evaluate(OperationRun $run): QueuedExecutionLegitimacyDecision { return $this->decision; } }); $ensure = new EnsureQueuedExecutionLegitimate; $track = new TrackOperationRun; $executed = false; $job = new class($run) { public function __construct(public OperationRun $operationRun) {} }; $response = $ensure->handle($job, function ($job) use (&$executed, $track) { return $track->handle($job, function () use (&$executed): string { $executed = true; return 'ran'; }); }); $run->refresh(); expect($response)->toBeNull() ->and($executed)->toBeFalse() ->and($run->status)->toBe(OperationRunStatus::Completed->value) ->and($run->outcome)->toBe(OperationRunOutcome::Blocked->value) ->and($run->started_at)->toBeNull() ->and($run->context['blocked_by'] ?? null)->toBe('queued_execution_legitimacy') ->and($run->context['execution_legitimacy']['reason_code'] ?? null)->toBe('missing_capability'); }); it('marks legitimate execution running before completing it', function (): void { [$user, $tenant] = createUserWithTenant(role: 'owner'); $run = OperationRun::factory()->create([ 'tenant_id' => (int) $tenant->getKey(), 'workspace_id' => (int) $tenant->workspace_id, 'user_id' => (int) $user->getKey(), 'type' => 'inventory_sync', 'status' => OperationRunStatus::Queued->value, 'outcome' => OperationRunOutcome::Pending->value, ]); $context = new QueuedExecutionContext( run: $run, operationType: 'inventory_sync', workspaceId: (int) $tenant->workspace_id, tenant: $tenant, initiator: $user, authorityMode: ExecutionAuthorityMode::ActorBound, requiredCapability: null, providerConnectionId: null, targetScope: [ 'workspace_id' => (int) $tenant->workspace_id, 'tenant_id' => (int) $tenant->getKey(), 'provider_connection_id' => null, ], ); $decision = QueuedExecutionLegitimacyDecision::allow( context: $context, checks: [ 'workspace_scope' => 'passed', 'tenant_scope' => 'passed', 'capability' => 'passed', 'tenant_operability' => 'passed', 'execution_prerequisites' => 'not_applicable', ], ); app()->instance(QueuedExecutionLegitimacyGate::class, new class($decision) { public function __construct(private readonly QueuedExecutionLegitimacyDecision $decision) {} public function evaluate(OperationRun $run): QueuedExecutionLegitimacyDecision { return $this->decision; } }); $ensure = new EnsureQueuedExecutionLegitimate; $track = new TrackOperationRun; $executed = false; $job = new class($run) { public function __construct(public OperationRun $operationRun) {} }; $response = $ensure->handle($job, function ($job) use (&$executed, $track) { return $track->handle($job, function () use (&$executed): string { $executed = true; return 'ran'; }); }); $run->refresh(); expect($response)->toBe('ran') ->and($executed)->toBeTrue() ->and($run->status)->toBe(OperationRunStatus::Completed->value) ->and($run->outcome)->toBe(OperationRunOutcome::Succeeded->value) ->and($run->started_at)->not->toBeNull() ->and($run->completed_at)->not->toBeNull(); }); it('persists write-gate denials as blocked before track middleware runs', function (): void { [$user, $tenant] = createUserWithTenant(role: 'owner'); $run = OperationRun::factory()->create([ 'tenant_id' => (int) $tenant->getKey(), 'workspace_id' => (int) $tenant->workspace_id, 'user_id' => (int) $user->getKey(), 'type' => 'restore.execute', 'status' => OperationRunStatus::Queued->value, 'outcome' => OperationRunOutcome::Pending->value, ]); $context = new QueuedExecutionContext( run: $run, operationType: 'restore.execute', workspaceId: (int) $tenant->workspace_id, tenant: $tenant, initiator: $user, authorityMode: ExecutionAuthorityMode::ActorBound, requiredCapability: null, providerConnectionId: null, targetScope: [ 'workspace_id' => (int) $tenant->workspace_id, 'tenant_id' => (int) $tenant->getKey(), 'provider_connection_id' => null, ], ); $decision = QueuedExecutionLegitimacyDecision::deny( context: $context, checks: [ 'workspace_scope' => 'passed', 'tenant_scope' => 'passed', 'capability' => 'passed', 'tenant_operability' => 'passed', 'execution_prerequisites' => 'failed', ], reasonCode: ExecutionDenialReasonCode::WriteGateBlocked, ); app()->instance(QueuedExecutionLegitimacyGate::class, new class($decision) { public function __construct(private readonly QueuedExecutionLegitimacyDecision $decision) {} public function evaluate(OperationRun $run): QueuedExecutionLegitimacyDecision { return $this->decision; } }); $ensure = new EnsureQueuedExecutionLegitimate; $track = new TrackOperationRun; $executed = false; $job = new class($run) { public function __construct(public OperationRun $operationRun) {} }; $response = $ensure->handle($job, function ($job) use (&$executed, $track) { return $track->handle($job, function () use (&$executed): string { $executed = true; return 'ran'; }); }); $run->refresh(); expect($response)->toBeNull() ->and($executed)->toBeFalse() ->and($run->status)->toBe(OperationRunStatus::Completed->value) ->and($run->outcome)->toBe(OperationRunOutcome::Blocked->value) ->and($run->started_at)->toBeNull() ->and($run->context['reason_code'] ?? null)->toBe('write_gate_blocked') ->and($run->context['execution_legitimacy']['reason_code'] ?? null)->toBe('write_gate_blocked'); });