'v1', 'rules' => [[ 'id' => 'vip-whatsapp', 'enabled' => true, 'when' => [ 'platform' => 'whatsapp', 'contact.segment' => 'vip', ], 'actions' => [ ['type' => 'tag', 'value' => 'vip'], ['type' => 'assign', 'value' => 'support-team'], ['type' => 'snooze', 'value' => 'PT15M'], ], ]], ], JSON_THROW_ON_ERROR); $decision = bird_flow_policy_evaluator::evaluate($policy, [ 'platform' => 'whatsapp', 'contact' => ['segment' => 'vip'], ]); expect($decision)->toMatchArray([ 'decision' => 'allow', 'reason' => 'matched_rule', 'policyVersion' => 'v1', 'ruleId' => 'vip-whatsapp', ])->and($decision['actions'])->toHaveCount(3); }); it('fails closed for absent matches, invalid policy and any non-allowlisted action', function (): void { $unsafePolicy = json_encode([ 'version' => 'v1', 'rules' => [[ 'id' => 'unsafe', 'enabled' => true, 'when' => ['platform' => 'email'], 'actions' => [['type' => 'send_message', 'value' => 'hello']], ]], ], JSON_THROW_ON_ERROR); expect(bird_flow_policy_evaluator::evaluate('', ['platform' => 'email'])['decision'])->toBe('deny') ->and(bird_flow_policy_evaluator::evaluate($unsafePolicy, ['platform' => 'email'])) ->toMatchArray([ 'decision' => 'deny', 'reason' => 'invalid_rule_actions', 'actions' => [], ]) ->and(bird_flow_policy_evaluator::evaluate($unsafePolicy, ['platform' => 'sms'])['decision']) ->toBe('deny'); });