middleware()), fn (Closure $next, object $middleware): Closure => fn (object $job): mixed => $middleware->handle($job, $next), $terminal, ); return $pipeline($job); } it('stores actor-bound execution metadata when bulk policy delete is queued', function (): void { [$user, $tenant] = createUserWithTenant(role: 'owner'); $policies = Policy::factory()->count(2)->create([ 'tenant_id' => (int) $tenant->getKey(), ]); $selection = app(BulkSelectionIdentity::class)->fromIds($policies->pluck('id')->all()); $run = app(OperationRunService::class)->enqueueBulkOperation( tenant: $tenant, type: 'policy.delete', targetScope: [ 'entra_tenant_id' => (string) ($tenant->tenant_id ?? $tenant->external_id), ], selectionIdentity: $selection, dispatcher: static fn (): null => null, initiator: $user, extraContext: [ 'policy_count' => $policies->count(), ], ); expect($run->context)->toMatchArray([ 'execution_authority_mode' => 'actor_bound', 'required_capability' => 'tenant.manage', 'policy_count' => 2, ]) ->and($run->context['selection']['kind'] ?? null)->toBe('ids') ->and($run->context['selection']['ids_count'] ?? null)->toBe(2) ->and($run->context['selection']['ids_hash'] ?? null)->toBeString(); }); it('blocks bulk policy delete before worker fan-out when the initiator loses capability', function (): void { Queue::fake(); [$user, $tenant] = createUserWithTenant(role: 'owner'); $policies = Policy::factory()->count(2)->create([ 'tenant_id' => (int) $tenant->getKey(), ]); $selection = app(BulkSelectionIdentity::class)->fromIds($policies->pluck('id')->all()); $run = app(OperationRunService::class)->enqueueBulkOperation( tenant: $tenant, type: 'policy.delete', targetScope: [ 'entra_tenant_id' => (string) ($tenant->tenant_id ?? $tenant->external_id), ], selectionIdentity: $selection, dispatcher: static fn (): null => null, initiator: $user, extraContext: [ 'policy_count' => $policies->count(), ], ); $job = new BulkPolicyDeleteJob( tenantId: (int) $tenant->getKey(), userId: (int) $user->getKey(), policyIds: $policies->pluck('id')->all(), operationRun: $run, ); $user->tenantMemberships()->where('tenant_id', $tenant->getKey())->update(['role' => 'readonly']); app(CapabilityResolver::class)->clearCache(); $terminalInvoked = false; runQueuedBulkJobThroughMiddleware( $job, function (BulkPolicyDeleteJob $job) use (&$terminalInvoked): void { $terminalInvoked = true; $job->handle(app(OperationRunService::class)); }, ); $run->refresh(); expect($terminalInvoked)->toBeFalse() ->and($run->status)->toBe('completed') ->and($run->outcome)->toBe('blocked') ->and($run->context['reason_code'] ?? null)->toBe('missing_capability') ->and($run->context['execution_legitimacy']['target_scope']['tenant_id'] ?? null)->toBe((int) $tenant->getKey()); Queue::assertNothingPushed(); });