create([ 'tenant_id' => (int) $tenant->getKey(), 'policy_type' => 'settingsCatalogPolicy', 'external_id' => 'policy-a', 'platform' => 'windows10', ]); $policyVersion = PolicyVersion::factory()->create([ 'tenant_id' => (int) $tenant->getKey(), 'policy_id' => (int) $policy->getKey(), 'policy_type' => (string) $policy->policy_type, 'platform' => (string) $policy->platform, 'captured_at' => CarbonImmutable::parse('2026-03-01 10:00:00'), 'snapshot' => [ 'settings' => [ ['name' => 'settingA', 'value' => 1], ], ], ]); $inventory = InventoryItem::factory()->create([ 'tenant_id' => (int) $tenant->getKey(), 'policy_type' => (string) $policy->policy_type, 'external_id' => (string) $policy->external_id, 'meta_jsonb' => [ 'odata_type' => '#microsoft.graph.deviceManagementConfigurationPolicy', 'etag' => 'W/"meta-etag"', 'scope_tag_ids' => [], 'assignment_target_count' => 1, ], 'last_seen_at' => CarbonImmutable::parse('2026-03-01 11:00:00'), 'last_seen_operation_run_id' => null, ]); $expectedContentHash = app(DriftHasher::class)->hashNormalized( app(SettingsNormalizer::class)->normalizeForDiff( is_array($policyVersion->snapshot) ? $policyVersion->snapshot : [], (string) $policyVersion->policy_type, is_string($policyVersion->platform) ? $policyVersion->platform : null, ), ); $expectedMetaHash = app(BaselineSnapshotIdentity::class)->hashItemContent( policyType: (string) $inventory->policy_type, subjectExternalId: (string) $inventory->external_id, metaJsonb: is_array($inventory->meta_jsonb) ? $inventory->meta_jsonb : [], ); $resolver = app(CurrentStateHashResolver::class); $result = $resolver->resolveForSubjects( tenant: $tenant, subjects: [ ['policy_type' => (string) $policy->policy_type, 'subject_external_id' => (string) $policy->external_id], ], since: null, latestInventorySyncRunId: null, ); expect($result)->toHaveKey((string) $policy->policy_type.'|'.(string) $policy->external_id); $evidence = $result[(string) $policy->policy_type.'|'.(string) $policy->external_id]; expect($evidence)->not->toBeNull(); expect($evidence?->hash)->toBe($expectedContentHash); expect($evidence?->hash)->not->toBe($expectedMetaHash); expect($evidence?->provenance()['fidelity'])->toBe('content'); expect($evidence?->provenance()['source'])->toBe('policy_version'); }); it('Baseline resolver obeys since rule and falls back to meta evidence when content is too old', function () { [, $tenant] = createUserWithTenant(); $policy = Policy::factory()->create([ 'tenant_id' => (int) $tenant->getKey(), 'policy_type' => 'settingsCatalogPolicy', 'external_id' => 'policy-b', 'platform' => 'windows10', ]); PolicyVersion::factory()->create([ 'tenant_id' => (int) $tenant->getKey(), 'policy_id' => (int) $policy->getKey(), 'policy_type' => (string) $policy->policy_type, 'platform' => (string) $policy->platform, 'captured_at' => CarbonImmutable::parse('2026-03-01 10:00:00'), 'snapshot' => [ 'settings' => [ ['name' => 'settingB', 'value' => 2], ], ], ]); $inventory = InventoryItem::factory()->create([ 'tenant_id' => (int) $tenant->getKey(), 'policy_type' => (string) $policy->policy_type, 'external_id' => (string) $policy->external_id, 'meta_jsonb' => [ 'odata_type' => '#microsoft.graph.deviceManagementConfigurationPolicy', 'etag' => 'W/"meta-etag-b"', 'scope_tag_ids' => [], 'assignment_target_count' => 1, ], 'last_seen_at' => CarbonImmutable::parse('2026-03-02 10:00:00'), 'last_seen_operation_run_id' => null, ]); $expectedMetaHash = app(BaselineSnapshotIdentity::class)->hashItemContent( policyType: (string) $inventory->policy_type, subjectExternalId: (string) $inventory->external_id, metaJsonb: is_array($inventory->meta_jsonb) ? $inventory->meta_jsonb : [], ); $resolver = app(CurrentStateHashResolver::class); $result = $resolver->resolveForSubjects( tenant: $tenant, subjects: [ ['policy_type' => (string) $policy->policy_type, 'subject_external_id' => (string) $policy->external_id], ], since: CarbonImmutable::parse('2026-03-02 00:00:00'), latestInventorySyncRunId: null, ); $evidence = $result[(string) $policy->policy_type.'|'.(string) $policy->external_id] ?? null; expect($evidence)->not->toBeNull(); expect($evidence?->hash)->toBe($expectedMetaHash); expect($evidence?->provenance()['fidelity'])->toBe('meta'); expect($evidence?->provenance()['source'])->toBe('inventory'); });