'baseline', 'subject_class' => SubjectClass::PolicyBacked, 'subject_type_key' => 'deviceConfiguration', 'operator_note' => 'Operator confirmed this provider resource binding after reviewing duplicate labels.', ], $overrides); } it('creates manual fake-provider bindings with scoped references and safe audit metadata', function (): void { [$actor, $tenant] = createUserWithTenant(role: 'owner'); $connection = ProviderConnection::factory()->create([ 'workspace_id' => (int) $tenant->workspace_id, 'managed_environment_id' => (int) $tenant->getKey(), 'provider' => 'fake-provider', ]); $run = OperationRun::factory()->forTenant($tenant)->create(); $inventory = InventoryItem::factory()->create([ 'managed_environment_id' => (int) $tenant->getKey(), ]); $policyVersion = PolicyVersion::factory()->create([ 'managed_environment_id' => (int) $tenant->getKey(), ]); [, $snapshot] = seedActiveBaselineForTenant($tenant); $binding = app(ProviderResourceBindingService::class)->createManualBinding( actor: $actor, environment: $tenant, identity: ResourceIdentity::providerResource('fake-provider', 'policy', 'fake-policy-1'), attributes: providerResourceBindingAttributes([ 'provider_connection_id' => (int) $connection->getKey(), 'source_operation_run_id' => (int) $run->getKey(), 'source_inventory_item_id' => (int) $inventory->getKey(), 'source_policy_version_id' => (int) $policyVersion->getKey(), 'source_baseline_snapshot_id' => (int) $snapshot->getKey(), 'display_label' => 'Duplicate policy label', 'resolution_reason' => 'duplicate-provider-resource', ]), ); expect((int) $binding->workspace_id)->toBe((int) $tenant->workspace_id) ->and((int) $binding->managed_environment_id)->toBe((int) $tenant->getKey()) ->and($binding->provider_key)->toBe('fake-provider') ->and($binding->resolution_mode)->toBe(ProviderResourceResolutionMode::ManualBinding) ->and($binding->binding_status)->toBe(ProviderResourceBindingStatus::Active) ->and((int) $binding->provider_connection_id)->toBe((int) $connection->getKey()) ->and((int) $binding->source_operation_run_id)->toBe((int) $run->getKey()) ->and((int) $binding->source_inventory_item_id)->toBe((int) $inventory->getKey()) ->and((int) $binding->source_policy_version_id)->toBe((int) $policyVersion->getKey()) ->and((int) $binding->source_baseline_snapshot_id)->toBe((int) $snapshot->getKey()); $audit = AuditLog::query() ->where('action', AuditActionId::ProviderResourceBindingCreated->value) ->where('resource_type', 'provider_resource_binding') ->firstOrFail(); expect((int) $audit->workspace_id)->toBe((int) $tenant->workspace_id) ->and((int) $audit->managed_environment_id)->toBe((int) $tenant->getKey()) ->and(data_get($audit->metadata, 'provider_key'))->toBe('fake-provider') ->and(data_get($audit->metadata, 'resolution_mode'))->toBe(ProviderResourceResolutionMode::ManualBinding->value) ->and(data_get($audit->metadata, 'source_operation_run_id'))->toBe((int) $run->getKey()) ->and(data_get($audit->metadata, 'operator_note'))->toBeNull() ->and(data_get($audit->metadata, 'operator_note_sha256'))->toBe(hash('sha256', providerResourceBindingAttributes()['operator_note'])) ->and(data_get($audit->metadata, 'operator_note_length'))->toBe(mb_strlen(providerResourceBindingAttributes()['operator_note'])); }); it('supersedes an existing active binding and keeps exactly one active row', function (): void { [$actor, $tenant] = createUserWithTenant(role: 'owner'); $service = app(ProviderResourceBindingService::class); $identity = ResourceIdentity::providerResource('fake-provider', 'policy', 'same-subject'); $first = $service->createExactProviderIdentity($actor, $tenant, $identity, providerResourceBindingAttributes([ 'operator_note' => 'Initial identity binding.', ])); $second = $service->createManualBinding($actor, $tenant, $identity, providerResourceBindingAttributes([ 'operator_note' => 'Replacement identity binding after manual review.', ])); expect($first->refresh()->binding_status)->toBe(ProviderResourceBindingStatus::Superseded) ->and($second->binding_status)->toBe(ProviderResourceBindingStatus::Active) ->and(ProviderResourceBinding::query() ->where('workspace_id', (int) $tenant->workspace_id) ->where('managed_environment_id', (int) $tenant->getKey()) ->where('provider_key', 'fake-provider') ->where('canonical_subject_key', $second->canonical_subject_key) ->active() ->count())->toBe(1); $audit = AuditLog::query() ->where('action', AuditActionId::ProviderResourceBindingSuperseded->value) ->firstOrFail(); expect(data_get($audit->metadata, 'old_binding_id'))->toBe((int) $first->getKey()) ->and(data_get($audit->metadata, 'new_binding_id'))->toBe((int) $second->getKey()) ->and(data_get($audit->metadata, 'resolution_mode'))->toBe(ProviderResourceResolutionMode::ManualBinding->value); }); it('revokes active bindings with audit metadata', function (): void { [$actor, $tenant] = createUserWithTenant(role: 'owner'); $binding = app(ProviderResourceBindingService::class)->createAcceptedLimitation( actor: $actor, environment: $tenant, identity: ResourceIdentity::unsupported('fake-provider', 'foundation-resource', 'unsupported-subject'), attributes: providerResourceBindingAttributes([ 'operator_note' => 'Accepted limitation for unsupported coverage.', 'subject_class' => SubjectClass::FoundationBacked, 'subject_type_key' => 'foundationResource', ]), ); $revoked = app(ProviderResourceBindingService::class)->revoke( actor: $actor, binding: $binding, operatorNote: 'Revoked because the limitation is no longer accepted.', ); expect($revoked->binding_status)->toBe(ProviderResourceBindingStatus::Revoked) ->and($revoked->ended_at)->not->toBeNull(); $audit = AuditLog::query() ->where('action', AuditActionId::ProviderResourceBindingRevoked->value) ->firstOrFail(); expect(data_get($audit->metadata, 'old_binding_id'))->toBe((int) $binding->getKey()) ->and(data_get($audit->metadata, 'binding_status'))->toBe(ProviderResourceBindingStatus::Revoked->value) ->and(data_get($audit->metadata, 'operator_note'))->toBeNull(); }); it('requires operator notes for binding decisions', function (): void { [$actor, $tenant] = createUserWithTenant(role: 'owner'); expect(fn () => app(ProviderResourceBindingService::class)->createManualBinding( actor: $actor, environment: $tenant, identity: ResourceIdentity::providerResource('fake-provider', 'policy', 'missing-note'), attributes: providerResourceBindingAttributes(['operator_note' => '']), ))->toThrow(InvalidArgumentException::class, 'operator note'); }); it('persists every v1 resolution mode without Microsoft literals', function (string $method, ResourceIdentity $identity, ProviderResourceResolutionMode $mode): void { [$actor, $tenant] = createUserWithTenant(role: 'owner'); $binding = app(ProviderResourceBindingService::class)->{$method}( actor: $actor, environment: $tenant, identity: $identity, attributes: providerResourceBindingAttributes([ 'subject_class' => SubjectClass::FoundationBacked, 'subject_type_key' => 'fakeSubjectType', 'operator_note' => 'Provider-neutral decision for '.$mode->value, ]), ); expect($binding->provider_key)->toBe('fake-provider') ->and($binding->resolution_mode)->toBe($mode) ->and($binding->canonical_subject_key)->toContain('fake-provider') ->and($binding->canonical_subject_key)->not->toContain('microsoft'); })->with([ 'exact provider identity' => ['createExactProviderIdentity', ResourceIdentity::providerResource('fake-provider', 'policy', 'exact-1'), ProviderResourceResolutionMode::ExactProviderIdentity], 'provider default as canonical builtin' => ['createCanonicalBuiltin', ResourceIdentity::canonicalDefault('fake-provider', 'default', 'provider-default'), ProviderResourceResolutionMode::CanonicalBuiltin], 'canonical virtual target' => ['createCanonicalVirtualTarget', ResourceIdentity::virtualTarget('fake-provider', 'target', 'virtual-target'), ProviderResourceResolutionMode::CanonicalVirtualTarget], 'manual binding' => ['createManualBinding', ResourceIdentity::providerResource('fake-provider', 'policy', 'manual-1'), ProviderResourceResolutionMode::ManualBinding], 'excluded non governed' => ['createExclusion', ResourceIdentity::unsupported('fake-provider', 'object', 'excluded-1'), ProviderResourceResolutionMode::ExcludedNonGoverned], 'accepted limitation' => ['createAcceptedLimitation', ResourceIdentity::unsupported('fake-provider', 'object', 'limited-1'), ProviderResourceResolutionMode::AcceptedLimitation], 'unsupported coverage' => ['markUnsupported', ResourceIdentity::unsupported('fake-provider', 'object', 'unsupported-1'), ProviderResourceResolutionMode::UnsupportedCoverage], 'missing expected' => ['markMissingExpected', ResourceIdentity::unknown('fake-provider', 'object', 'missing-1'), ProviderResourceResolutionMode::MissingExpected], ]);