'Spec422 Redaction Meeting', 'AllowTranscription' => true, 'clientSecret' => 'spec422-client-secret', 'privateKey' => 'spec422-private-key', 'headers' => ['Authorization' => 'Bearer spec422-auth-token'], 'cookies' => ['session' => 'spec422-cookie'], 'rawPayload' => ['secret' => 'spec422-raw-payload'], 'providerResponse' => ['body' => 'spec422-provider-response-body'], 'messageBody' => 'spec422-mail-message-body', 'chatContent' => 'spec422-chat-content', 'fileContent' => 'spec422-file-content', 'recordingTranscript' => 'spec422-recording-transcript', 'operationRunContext' => ['access_token' => 'spec422-run-token'], 'auditMetadata' => ['raw_payload' => ['secret' => 'spec422-audit-secret']], ]; $summary = app(ExchangeTeamsRenderableSummaryBuilder::class)->build('meetingPolicy', $payload); $compare = app(ExchangeTeamsCoverageComparator::class)->compare('meetingPolicy', $payload, [ ...$payload, 'modifiedDateTime' => '2026-06-28T12:00:00Z', ]); $encoded = json_encode([$summary, $compare], JSON_THROW_ON_ERROR); expect($encoded)->not->toContain('spec422-client-secret') ->and($encoded)->not->toContain('spec422-private-key') ->and($encoded)->not->toContain('spec422-auth-token') ->and($encoded)->not->toContain('spec422-cookie') ->and($encoded)->not->toContain('spec422-raw-payload') ->and($encoded)->not->toContain('spec422-provider-response-body') ->and($encoded)->not->toContain('spec422-mail-message-body') ->and($encoded)->not->toContain('spec422-chat-content') ->and($encoded)->not->toContain('spec422-file-content') ->and($encoded)->not->toContain('spec422-recording-transcript') ->and($encoded)->not->toContain('spec422-run-token') ->and($encoded)->not->toContain('spec422-audit-secret') ->and($summary['redacted_fields'])->toContain( 'clientSecret', 'privateKey', 'headers.Authorization', 'cookies', 'rawPayload', 'providerResponse', 'messageBody', 'chatContent', 'fileContent', 'recordingTranscript', 'operationRunContext', 'auditMetadata.raw_payload', ); }); it('Spec422 keeps content-bearing transport rule condition values out of render and compare output', function (): void { $before = [ 'DisplayName' => 'Spec422 Content Rule', 'Enabled' => true, ]; $after = [ 'DisplayName' => 'Spec422 Content Rule', 'Enabled' => true, 'SubjectContainsWords' => ['spec422-root-subject-secret'], 'Conditions' => [ 'SubjectOrBodyContainsWords' => ['spec422-mail-body-secret'], 'AttachmentContainsWords' => ['spec422-file-attachment-secret'], 'HeaderContainsWords' => ['spec422-header-secret'], 'HeaderMatchesPatterns' => ['spec422-header-pattern-secret'], ], 'Actions' => [ 'ApplyHtmlDisclaimerText' => 'spec422-disclaimer-secret', 'PrependSubject' => 'spec422-prepend-subject-secret', 'SetHeaderValue' => 'spec422-set-header-secret', ], ]; $summary = app(ExchangeTeamsRenderableSummaryBuilder::class)->build('transportRule', $after); $compare = app(ExchangeTeamsCoverageComparator::class)->compare('transportRule', $before, $after); $encoded = json_encode([$summary, $compare], JSON_THROW_ON_ERROR); $materialClassifications = collect($compare['changes']) ->filter(fn (array $change): bool => in_array($change['classification'] ?? null, ['added', 'removed', 'changed'], true)) ->pluck('classification') ->values(); expect($encoded)->not->toContain('spec422-root-subject-secret') ->and($encoded)->not->toContain('spec422-mail-body-secret') ->and($encoded)->not->toContain('spec422-file-attachment-secret') ->and($encoded)->not->toContain('spec422-header-secret') ->and($encoded)->not->toContain('spec422-header-pattern-secret') ->and($encoded)->not->toContain('spec422-disclaimer-secret') ->and($encoded)->not->toContain('spec422-prepend-subject-secret') ->and($encoded)->not->toContain('spec422-set-header-secret') ->and($encoded)->not->toContain('[redacted]') ->and($summary['redacted_fields'])->toContain( 'SubjectContainsWords', 'Conditions.SubjectOrBodyContainsWords', 'Conditions.AttachmentContainsWords', 'Conditions.HeaderContainsWords', 'Conditions.HeaderMatchesPatterns', 'Actions.ApplyHtmlDisclaimerText', 'Actions.PrependSubject', 'Actions.SetHeaderValue', ) ->and(collect($compare['changes'])->pluck('classification'))->toContain('redacted') ->and($compare['changed'])->toBeFalse() ->and($materialClassifications)->toBeEmpty(); });