olyx-guardrails 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +117 -0
- data/CODE_OF_CONDUCT.md +28 -0
- data/CONTRIBUTING.md +63 -0
- data/LICENSE +134 -0
- data/README.md +611 -0
- data/SECURITY.md +35 -0
- data/docs/API.md +460 -0
- data/examples/claude_analyzer.rb +53 -0
- data/examples/custom_policy.rb +41 -0
- data/examples/notifier.rb +37 -0
- data/examples/openai_analyzer.rb +45 -0
- data/examples/rails_opt_in.rb +99 -0
- data/examples/ruby_only.rb +53 -0
- data/lib/generators/olyx_guardrails/install_generator.rb +22 -0
- data/lib/generators/olyx_guardrails/templates/initializer.rb.tt +16 -0
- data/lib/generators/olyx_guardrails/templates/policy.yml.tt +41 -0
- data/lib/olyx/guardrails/ai/analysis_normalizer.rb +26 -0
- data/lib/olyx/guardrails/ai/analysis_pipeline.rb +26 -0
- data/lib/olyx/guardrails/ai/boolean_validator.rb +18 -0
- data/lib/olyx/guardrails/ai/flag_finding_merger.rb +16 -0
- data/lib/olyx/guardrails/ai/result_sanitizer.rb +29 -0
- data/lib/olyx/guardrails/ai/secret_finding_merger.rb +19 -0
- data/lib/olyx/guardrails/ai/standard_finding_merger.rb +24 -0
- data/lib/olyx/guardrails/ai_analysis.rb +29 -0
- data/lib/olyx/guardrails/ai_context_builder.rb +32 -0
- data/lib/olyx/guardrails/ai_failure_handler.rb +17 -0
- data/lib/olyx/guardrails/ai_finding_merger.rb +30 -0
- data/lib/olyx/guardrails/check_analyzer.rb +26 -0
- data/lib/olyx/guardrails/check_base_result.rb +32 -0
- data/lib/olyx/guardrails/check_pipeline.rb +17 -0
- data/lib/olyx/guardrails/check_result_builder.rb +24 -0
- data/lib/olyx/guardrails/check_runner.rb +42 -0
- data/lib/olyx/guardrails/check_set.rb +33 -0
- data/lib/olyx/guardrails/checks/injection_check.rb +20 -0
- data/lib/olyx/guardrails/checks/injection_result.rb +22 -0
- data/lib/olyx/guardrails/checks/length_check.rb +18 -0
- data/lib/olyx/guardrails/checks/message_injection_check.rb +19 -0
- data/lib/olyx/guardrails/checks/pii_check.rb +19 -0
- data/lib/olyx/guardrails/checks/policy_check.rb +26 -0
- data/lib/olyx/guardrails/checks/secret_check.rb +30 -0
- data/lib/olyx/guardrails/checks/skipped_checks.rb +26 -0
- data/lib/olyx/guardrails/enum_value.rb +20 -0
- data/lib/olyx/guardrails/errors.rb +22 -0
- data/lib/olyx/guardrails/injection_detector.rb +27 -0
- data/lib/olyx/guardrails/injection_patterns.rb +44 -0
- data/lib/olyx/guardrails/integrations/openai/analyzer_setup.rb +21 -0
- data/lib/olyx/guardrails/integrations/openai/configuration_resolver.rb +28 -0
- data/lib/olyx/guardrails/integrations/openai/dependency_contracts.rb +28 -0
- data/lib/olyx/guardrails/integrations/openai/input_builder.rb +39 -0
- data/lib/olyx/guardrails/integrations/openai/member_reader.rb +21 -0
- data/lib/olyx/guardrails/integrations/openai/model_identifier.rb +28 -0
- data/lib/olyx/guardrails/integrations/openai/refusal_guard.rb +28 -0
- data/lib/olyx/guardrails/integrations/openai/request_builder.rb +36 -0
- data/lib/olyx/guardrails/integrations/openai/request_configuration.rb +25 -0
- data/lib/olyx/guardrails/integrations/openai/request_values.rb +33 -0
- data/lib/olyx/guardrails/integrations/openai/response_contents.rb +22 -0
- data/lib/olyx/guardrails/integrations/openai/response_option_keys.rb +32 -0
- data/lib/olyx/guardrails/integrations/openai/response_options.rb +23 -0
- data/lib/olyx/guardrails/integrations/openai/schema_registry.rb +46 -0
- data/lib/olyx/guardrails/integrations/openai/sdk.rb +26 -0
- data/lib/olyx/guardrails/integrations/openai/signal_summary.rb +40 -0
- data/lib/olyx/guardrails/integrations/openai_analyzer.rb +107 -0
- data/lib/olyx/guardrails/integrations/openai_analyzer_configuration.rb +38 -0
- data/lib/olyx/guardrails/integrations/openai_response_parser.rb +34 -0
- data/lib/olyx/guardrails/message_check_runner.rb +24 -0
- data/lib/olyx/guardrails/message_check_set.rb +20 -0
- data/lib/olyx/guardrails/message_content.rb +27 -0
- data/lib/olyx/guardrails/message_source.rb +16 -0
- data/lib/olyx/guardrails/multi_turn_injection_detector.rb +18 -0
- data/lib/olyx/guardrails/multi_turn_pair_scanner.rb +35 -0
- data/lib/olyx/guardrails/multi_turn_variant_pairs.rb +25 -0
- data/lib/olyx/guardrails/notification/base_event.rb +35 -0
- data/lib/olyx/guardrails/notification/deep_freezer.rb +25 -0
- data/lib/olyx/guardrails/notification/delivery_dispatcher.rb +38 -0
- data/lib/olyx/guardrails/notification/delivery_summary.rb +17 -0
- data/lib/olyx/guardrails/notification/handler.rb +33 -0
- data/lib/olyx/guardrails/notification/handler_collection.rb +25 -0
- data/lib/olyx/guardrails/notification/handler_collection_validator.rb +23 -0
- data/lib/olyx/guardrails/notification/key.rb +19 -0
- data/lib/olyx/guardrails/notification/metadata.rb +34 -0
- data/lib/olyx/guardrails/notification/notifier_setup.rb +19 -0
- data/lib/olyx/guardrails/notification/preview.rb +27 -0
- data/lib/olyx/guardrails/notification/scrubber.rb +22 -0
- data/lib/olyx/guardrails/notification/violation_labels.rb +19 -0
- data/lib/olyx/guardrails/notification_event_builder.rb +41 -0
- data/lib/olyx/guardrails/notification_sanitizer.rb +33 -0
- data/lib/olyx/guardrails/notifier.rb +53 -0
- data/lib/olyx/guardrails/notifier_configuration.rb +30 -0
- data/lib/olyx/guardrails/pii/ambiguous_numeric_date.rb +32 -0
- data/lib/olyx/guardrails/pii/block_scrubber.rb +31 -0
- data/lib/olyx/guardrails/pii/content_scrubber.rb +33 -0
- data/lib/olyx/guardrails/pii/date_validator.rb +25 -0
- data/lib/olyx/guardrails/pii/hash_key.rb +19 -0
- data/lib/olyx/guardrails/pii/iban_remainder.rb +25 -0
- data/lib/olyx/guardrails/pii/iban_validator.rb +34 -0
- data/lib/olyx/guardrails/pii/ipv4_validator.rb +17 -0
- data/lib/olyx/guardrails/pii/ipv6_validator.rb +20 -0
- data/lib/olyx/guardrails/pii/luhn_checksum.rb +29 -0
- data/lib/olyx/guardrails/pii/luhn_validator.rb +21 -0
- data/lib/olyx/guardrails/pii/message_scrubber.rb +31 -0
- data/lib/olyx/guardrails/pii/named_date_validator.rb +28 -0
- data/lib/olyx/guardrails/pii/numeric_date_validator.rb +26 -0
- data/lib/olyx/guardrails/pii/pattern_catalog.rb +41 -0
- data/lib/olyx/guardrails/pii/sin_validator.rb +24 -0
- data/lib/olyx/guardrails/pii/ssn_validator.rb +21 -0
- data/lib/olyx/guardrails/pii/text_scrubber.rb +28 -0
- data/lib/olyx/guardrails/pii/year_first_date.rb +18 -0
- data/lib/olyx/guardrails/pii_scrubber.rb +23 -0
- data/lib/olyx/guardrails/pii_validators.rb +26 -0
- data/lib/olyx/guardrails/policy/ai_failure_mode.rb +15 -0
- data/lib/olyx/guardrails/policy/configuration.rb +62 -0
- data/lib/olyx/guardrails/policy/configuration_hash.rb +20 -0
- data/lib/olyx/guardrails/policy/finding_order.rb +20 -0
- data/lib/olyx/guardrails/policy/finding_presenter.rb +41 -0
- data/lib/olyx/guardrails/policy/match_collector.rb +20 -0
- data/lib/olyx/guardrails/policy/name.rb +23 -0
- data/lib/olyx/guardrails/policy/normalized_pattern_matcher.rb +34 -0
- data/lib/olyx/guardrails/policy/pattern_matcher.rb +19 -0
- data/lib/olyx/guardrails/policy/redaction_result.rb +20 -0
- data/lib/olyx/guardrails/policy/redaction_spans.rb +32 -0
- data/lib/olyx/guardrails/policy/rule_collection.rb +34 -0
- data/lib/olyx/guardrails/policy/rule_matcher.rb +29 -0
- data/lib/olyx/guardrails/policy/rule_normalizer.rb +21 -0
- data/lib/olyx/guardrails/policy/scan_result.rb +21 -0
- data/lib/olyx/guardrails/policy/secret_pattern_collection.rb +22 -0
- data/lib/olyx/guardrails/policy/unmatched_segment_builder.rb +28 -0
- data/lib/olyx/guardrails/policy/unmatched_transformer.rb +20 -0
- data/lib/olyx/guardrails/policy.rb +46 -0
- data/lib/olyx/guardrails/policy_aware_redactor.rb +42 -0
- data/lib/olyx/guardrails/policy_redactor.rb +28 -0
- data/lib/olyx/guardrails/policy_rule/configuration.rb +46 -0
- data/lib/olyx/guardrails/policy_rule/description_value.rb +24 -0
- data/lib/olyx/guardrails/policy_rule/match_mode.rb +15 -0
- data/lib/olyx/guardrails/policy_rule/name_value.rb +22 -0
- data/lib/olyx/guardrails/policy_rule/pattern_compiler.rb +28 -0
- data/lib/olyx/guardrails/policy_rule/regexp_compiler.rb +31 -0
- data/lib/olyx/guardrails/policy_rule/replacement_value.rb +26 -0
- data/lib/olyx/guardrails/policy_rule/term_compiler.rb +33 -0
- data/lib/olyx/guardrails/policy_rule/text_values.rb +23 -0
- data/lib/olyx/guardrails/policy_rule/values.rb +33 -0
- data/lib/olyx/guardrails/policy_rule.rb +30 -0
- data/lib/olyx/guardrails/policy_scanner.rb +44 -0
- data/lib/olyx/guardrails/rails/action_cable.rb +21 -0
- data/lib/olyx/guardrails/rails/active_job_handler.rb +27 -0
- data/lib/olyx/guardrails/rails/active_model_validator.rb +23 -0
- data/lib/olyx/guardrails/rails/check_telemetry.rb +19 -0
- data/lib/olyx/guardrails/rails/configuration.rb +68 -0
- data/lib/olyx/guardrails/rails/configuration_finalizer.rb +30 -0
- data/lib/olyx/guardrails/rails/configuration_registry.rb +41 -0
- data/lib/olyx/guardrails/rails/configuration_values.rb +30 -0
- data/lib/olyx/guardrails/rails/constant_resolver.rb +18 -0
- data/lib/olyx/guardrails/rails/controller.rb +34 -0
- data/lib/olyx/guardrails/rails/controller_metadata.rb +22 -0
- data/lib/olyx/guardrails/rails/decision_service.rb +26 -0
- data/lib/olyx/guardrails/rails/enforcer.rb +33 -0
- data/lib/olyx/guardrails/rails/evaluation_lifecycle.rb +23 -0
- data/lib/olyx/guardrails/rails/evaluation_service.rb +12 -0
- data/lib/olyx/guardrails/rails/filter_parameters.rb +26 -0
- data/lib/olyx/guardrails/rails/graphql.rb +21 -0
- data/lib/olyx/guardrails/rails/ingress.rb +26 -0
- data/lib/olyx/guardrails/rails/input_runtime.rb +25 -0
- data/lib/olyx/guardrails/rails/instrumentation.rb +39 -0
- data/lib/olyx/guardrails/rails/instrumentation_payloads.rb +25 -0
- data/lib/olyx/guardrails/rails/instrumentation_publisher.rb +24 -0
- data/lib/olyx/guardrails/rails/integration_configuration.rb +27 -0
- data/lib/olyx/guardrails/rails/job.rb +37 -0
- data/lib/olyx/guardrails/rails/job_argument.rb +41 -0
- data/lib/olyx/guardrails/rails/job_reference.rb +36 -0
- data/lib/olyx/guardrails/rails/message_evaluation_service.rb +13 -0
- data/lib/olyx/guardrails/rails/message_runtime.rb +20 -0
- data/lib/olyx/guardrails/rails/notification_dispatcher.rb +22 -0
- data/lib/olyx/guardrails/rails/output_runtime.rb +26 -0
- data/lib/olyx/guardrails/rails/path_value.rb +30 -0
- data/lib/olyx/guardrails/rails/policy_configuration.rb +25 -0
- data/lib/olyx/guardrails/rails/policy_document.rb +40 -0
- data/lib/olyx/guardrails/rails/policy_file.rb +29 -0
- data/lib/olyx/guardrails/rails/policy_file_error.rb +25 -0
- data/lib/olyx/guardrails/rails/policy_rule_names.rb +19 -0
- data/lib/olyx/guardrails/rails/policy_yaml.rb +22 -0
- data/lib/olyx/guardrails/rails/queue_name.rb +21 -0
- data/lib/olyx/guardrails/rails/redaction_payload.rb +36 -0
- data/lib/olyx/guardrails/rails/redaction_service.rb +21 -0
- data/lib/olyx/guardrails/rails/result_summary.rb +36 -0
- data/lib/olyx/guardrails/rails/runtime.rb +30 -0
- data/lib/olyx/guardrails/rails/timer.rb +27 -0
- data/lib/olyx/guardrails/rails/upload.rb +34 -0
- data/lib/olyx/guardrails/rails.rb +35 -0
- data/lib/olyx/guardrails/railtie.rb +19 -0
- data/lib/olyx/guardrails/redaction/content_result.rb +32 -0
- data/lib/olyx/guardrails/redaction/input_validator.rb +17 -0
- data/lib/olyx/guardrails/redaction/public_result.rb +23 -0
- data/lib/olyx/guardrails/redaction/unrestricted_text.rb +17 -0
- data/lib/olyx/guardrails/redactor.rb +29 -0
- data/lib/olyx/guardrails/risk/ai_score.rb +19 -0
- data/lib/olyx/guardrails/risk/check_weights.rb +18 -0
- data/lib/olyx/guardrails/risk/deterministic_score.rb +20 -0
- data/lib/olyx/guardrails/risk/weights.rb +18 -0
- data/lib/olyx/guardrails/risk_scorer.rb +28 -0
- data/lib/olyx/guardrails/secret_finding_collector.rb +31 -0
- data/lib/olyx/guardrails/secret_scanner.rb +42 -0
- data/lib/olyx/guardrails/secrets/blocked.rb +17 -0
- data/lib/olyx/guardrails/secrets/catalog_source.rb +28 -0
- data/lib/olyx/guardrails/secrets/cloud_credential_patterns.rb +16 -0
- data/lib/olyx/guardrails/secrets/confidentiality_source.rb +21 -0
- data/lib/olyx/guardrails/secrets/custom_pattern_compiler.rb +23 -0
- data/lib/olyx/guardrails/secrets/custom_pattern_source.rb +30 -0
- data/lib/olyx/guardrails/secrets/finding_order.rb +17 -0
- data/lib/olyx/guardrails/secrets/finding_presenter.rb +34 -0
- data/lib/olyx/guardrails/secrets/generic_credential_patterns.rb +15 -0
- data/lib/olyx/guardrails/secrets/internal_endpoint_source.rb +28 -0
- data/lib/olyx/guardrails/secrets/network_patterns.rb +13 -0
- data/lib/olyx/guardrails/secrets/normalized_source_set.rb +27 -0
- data/lib/olyx/guardrails/secrets/pattern_catalog.rb +37 -0
- data/lib/olyx/guardrails/secrets/private_network_validator.rb +19 -0
- data/lib/olyx/guardrails/secrets/redaction_spans.rb +32 -0
- data/lib/olyx/guardrails/secrets/redactor.rb +27 -0
- data/lib/olyx/guardrails/secrets/regexp_finding_source.rb +21 -0
- data/lib/olyx/guardrails/secrets/source_set.rb +19 -0
- data/lib/olyx/guardrails/single_message_injection_detector.rb +35 -0
- data/lib/olyx/guardrails/supplemental_violation_labels.rb +22 -0
- data/lib/olyx/guardrails/text/base64_decoder.rb +45 -0
- data/lib/olyx/guardrails/text/detection_variants.rb +28 -0
- data/lib/olyx/guardrails/text/html_decoder.rb +18 -0
- data/lib/olyx/guardrails/text/mapped_builder.rb +33 -0
- data/lib/olyx/guardrails/text/mapped_normalization.rb +27 -0
- data/lib/olyx/guardrails/text/normalizer.rb +35 -0
- data/lib/olyx/guardrails/text/unicode_escape_decoder.rb +20 -0
- data/lib/olyx/guardrails/text/url_decoder.rb +20 -0
- data/lib/olyx/guardrails/validation.rb +33 -0
- data/lib/olyx/guardrails/version.rb +8 -0
- data/lib/olyx/guardrails/violation_labels.rb +24 -0
- data/lib/olyx/guardrails.rb +63 -0
- metadata +283 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Copy the relevant sections into a Rails application. Nothing is scanned
|
|
4
|
+
# globally: each helper is called at an application-owned AI boundary.
|
|
5
|
+
|
|
6
|
+
require 'olyx/guardrails/rails'
|
|
7
|
+
|
|
8
|
+
# config/initializers/olyx_guardrails.rb
|
|
9
|
+
Olyx::Guardrails::Rails.configure do |config|
|
|
10
|
+
config.enabled = true
|
|
11
|
+
config.policy_path = Rails.root.join('config/olyx_guardrails.yml')
|
|
12
|
+
config.ai_analyzer = nil
|
|
13
|
+
config.notifier_handlers = {
|
|
14
|
+
audit: ->(event) { Rails.logger.warn(event.inspect) }
|
|
15
|
+
}
|
|
16
|
+
config.filter_parameters = %i[prompt system_prompt ai_input llm_input]
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Controller input, redaction, and completed-output enforcement.
|
|
20
|
+
class AiRequestsController < ApplicationController
|
|
21
|
+
include Olyx::Guardrails::Rails::Controller
|
|
22
|
+
|
|
23
|
+
rescue_from Olyx::Guardrails::Blocked do |error|
|
|
24
|
+
render json: { error: 'input_rejected', decision: error.decision },
|
|
25
|
+
status: :unprocessable_entity
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def create
|
|
29
|
+
prompt = params.require(:prompt)
|
|
30
|
+
guardrails_check!(prompt, metadata: { account_id: current_account.id })
|
|
31
|
+
safe_prompt = guardrails_redact(prompt)[:text]
|
|
32
|
+
completion = LlmClient.complete(safe_prompt)
|
|
33
|
+
Olyx::Guardrails::Rails::Enforcer.check_output!(completion)
|
|
34
|
+
render json: { completion: completion }, status: :created
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# GraphQL resolver or mutation.
|
|
39
|
+
class CompletionResolver
|
|
40
|
+
include Olyx::Guardrails::Rails::GraphQL
|
|
41
|
+
|
|
42
|
+
def resolve(prompt:)
|
|
43
|
+
guardrails_check_graphql!(prompt)
|
|
44
|
+
completion = LlmClient.complete(prompt)
|
|
45
|
+
guardrails_check_graphql_output!(completion)
|
|
46
|
+
{ completion: completion }
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Action Cable channel.
|
|
51
|
+
class PromptChannel < ApplicationCable::Channel
|
|
52
|
+
include Olyx::Guardrails::Rails::ActionCable
|
|
53
|
+
|
|
54
|
+
def receive(data)
|
|
55
|
+
guardrails_check_cable!(data.fetch('prompt'))
|
|
56
|
+
transmit(status: 'accepted')
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Active Job arguments are checked immediately before perform.
|
|
61
|
+
class CompletionJob < ApplicationJob
|
|
62
|
+
include Olyx::Guardrails::Rails::Job
|
|
63
|
+
|
|
64
|
+
guardrails_input_arguments 0, :system_prompt
|
|
65
|
+
|
|
66
|
+
def perform(prompt, system_prompt:)
|
|
67
|
+
LlmClient.complete(prompt, system_prompt: system_prompt)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Active Model validation for an explicitly declared AI-bound attribute.
|
|
72
|
+
class PromptDraft
|
|
73
|
+
include ActiveModel::Model
|
|
74
|
+
|
|
75
|
+
attr_accessor :prompt
|
|
76
|
+
|
|
77
|
+
validates :prompt, olyx_guardrails: true
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Caller-owned upload parsing. The extractor must return a String.
|
|
81
|
+
class UploadedPromptChecker
|
|
82
|
+
def self.call(upload)
|
|
83
|
+
Olyx::Guardrails::Rails::Upload.check!(
|
|
84
|
+
upload,
|
|
85
|
+
extractor: ->(file) { DocumentTextExtractor.call(file.tempfile) }
|
|
86
|
+
)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Service objects, callbacks, and custom transports use the reusable enforcer.
|
|
91
|
+
class ConversationService
|
|
92
|
+
def self.call(messages)
|
|
93
|
+
Olyx::Guardrails::Rails::Enforcer.check_messages!(
|
|
94
|
+
messages,
|
|
95
|
+
metadata: { boundary: 'conversation_service' }
|
|
96
|
+
)
|
|
97
|
+
LlmClient.chat(messages)
|
|
98
|
+
end
|
|
99
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Run with:
|
|
4
|
+
# ruby -Ilib examples/ruby_only.rb "Summarize these release notes"
|
|
5
|
+
#
|
|
6
|
+
# This path has no Rails dependency and keeps policy, enforcement, model calls,
|
|
7
|
+
# and notification delivery under the application's control.
|
|
8
|
+
|
|
9
|
+
require 'olyx/guardrails'
|
|
10
|
+
|
|
11
|
+
policy = Olyx::Guardrails::Policy.new(
|
|
12
|
+
name: 'ruby-application',
|
|
13
|
+
max_input_length: 4_000,
|
|
14
|
+
block_pii: true,
|
|
15
|
+
block_injections: true,
|
|
16
|
+
block_secrets: true,
|
|
17
|
+
ai_failure_mode: :block,
|
|
18
|
+
secret_patterns: ['company-token-[a-z0-9]{24}'],
|
|
19
|
+
rules: [
|
|
20
|
+
{
|
|
21
|
+
name: :confidential_project,
|
|
22
|
+
terms: ['Project Falcon'],
|
|
23
|
+
match: :whole_word,
|
|
24
|
+
replacement: '[CONFIDENTIAL_PROJECT]'
|
|
25
|
+
}
|
|
26
|
+
]
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
notifier = Olyx::Guardrails::Notifier.new(
|
|
30
|
+
policy: policy,
|
|
31
|
+
handlers: { stderr: ->(event) { warn("guardrail event: #{event.inspect}") } }
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
prompt = ARGV.fetch(0, 'Summarize these public release notes')
|
|
35
|
+
decision = Olyx::Guardrails.check(prompt, policy: policy)
|
|
36
|
+
notifier.notify(decision, input: prompt, metadata: { boundary: 'model_input' })
|
|
37
|
+
unless decision[:allowed]
|
|
38
|
+
rejected = decision[:checks].reject { |check| check[:allowed] }
|
|
39
|
+
rejected_types = rejected.map { |check| check[:type] }
|
|
40
|
+
abort "Input rejected: #{rejected_types.join(', ')}"
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
safe_prompt = Olyx::Guardrails.redact(prompt, policy: policy)[:text]
|
|
44
|
+
|
|
45
|
+
# Replace this lambda with the application's model client.
|
|
46
|
+
model = ->(input) { "Completed response for: #{input}" }
|
|
47
|
+
completion = model.call(safe_prompt)
|
|
48
|
+
|
|
49
|
+
output_decision = Olyx::Guardrails.check_output(completion, policy: policy)
|
|
50
|
+
notifier.notify(output_decision, input: completion, metadata: { boundary: 'model_output' })
|
|
51
|
+
abort 'Output rejected' unless output_decision[:allowed]
|
|
52
|
+
|
|
53
|
+
puts Olyx::Guardrails.redact_output(completion, policy: policy)[:text]
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rails/generators/base'
|
|
4
|
+
|
|
5
|
+
module OlyxGuardrails
|
|
6
|
+
module Generators
|
|
7
|
+
# Installs explicit, environment-specific Rails policy configuration.
|
|
8
|
+
class InstallGenerator < ::Rails::Generators::Base
|
|
9
|
+
source_root File.expand_path('templates', __dir__)
|
|
10
|
+
|
|
11
|
+
desc 'Install Olyx Guardrails Rails configuration and policies'
|
|
12
|
+
|
|
13
|
+
def copy_initializer
|
|
14
|
+
template 'initializer.rb.tt', 'config/initializers/olyx_guardrails.rb'
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def copy_policy
|
|
18
|
+
template 'policy.yml.tt', 'config/olyx_guardrails.yml'
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
Olyx::Guardrails::Rails.configure do |config|
|
|
4
|
+
config.enabled = true
|
|
5
|
+
config.policy_path = Rails.root.join("config/olyx_guardrails.yml")
|
|
6
|
+
|
|
7
|
+
# Optional semantic analyzer. Keep deterministic findings authoritative.
|
|
8
|
+
config.ai_analyzer = nil
|
|
9
|
+
|
|
10
|
+
# Handlers receive sanitized, immutable events. Keep this empty when
|
|
11
|
+
# ActiveSupport::Notifications is sufficient for your application.
|
|
12
|
+
config.notifier_handlers = {}
|
|
13
|
+
|
|
14
|
+
# Prevent common AI input parameter names from appearing in Rails logs.
|
|
15
|
+
config.filter_parameters = %i[prompt system_prompt ai_input llm_input]
|
|
16
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
development:
|
|
2
|
+
name: development
|
|
3
|
+
max_input_length: 10000
|
|
4
|
+
block_pii: false
|
|
5
|
+
block_injections: true
|
|
6
|
+
block_secrets: false
|
|
7
|
+
ai_failure_mode: allow
|
|
8
|
+
secret_patterns: []
|
|
9
|
+
rules: []
|
|
10
|
+
|
|
11
|
+
test:
|
|
12
|
+
name: test
|
|
13
|
+
max_input_length: 10000
|
|
14
|
+
block_pii: true
|
|
15
|
+
block_injections: true
|
|
16
|
+
block_secrets: true
|
|
17
|
+
ai_failure_mode: block
|
|
18
|
+
secret_patterns: []
|
|
19
|
+
rules: []
|
|
20
|
+
|
|
21
|
+
production:
|
|
22
|
+
name: production
|
|
23
|
+
max_input_length: 10000
|
|
24
|
+
block_pii: true
|
|
25
|
+
block_injections: true
|
|
26
|
+
block_secrets: true
|
|
27
|
+
ai_failure_mode: block
|
|
28
|
+
secret_patterns: []
|
|
29
|
+
# String values are case-insensitive regular-expression source.
|
|
30
|
+
# - 'company-token-[a-z0-9]{24}'
|
|
31
|
+
rules: []
|
|
32
|
+
# Add organization restrictions explicitly. Example:
|
|
33
|
+
# - name: confidential_projects
|
|
34
|
+
# description: Internal project names must not enter AI requests
|
|
35
|
+
# terms:
|
|
36
|
+
# - 'Project Falcon'
|
|
37
|
+
# match: whole_word
|
|
38
|
+
# patterns:
|
|
39
|
+
# - '\bPF-[0-9]{4}\b'
|
|
40
|
+
# block: true
|
|
41
|
+
# replacement: '[CONFIDENTIAL_PROJECT]'
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Olyx
|
|
4
|
+
module Guardrails
|
|
5
|
+
module Ai
|
|
6
|
+
# Converts supported schema-model results into Hash values.
|
|
7
|
+
module AnalysisNormalizer
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
def call(value)
|
|
11
|
+
return value if value.is_a?(Hash)
|
|
12
|
+
|
|
13
|
+
converted = convert(value)
|
|
14
|
+
converted if converted.is_a?(Hash)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def convert(value)
|
|
18
|
+
return value.deep_to_h if value.respond_to?(:deep_to_h)
|
|
19
|
+
|
|
20
|
+
value.to_h if value.respond_to?(:to_h)
|
|
21
|
+
end
|
|
22
|
+
private_class_method :convert
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Olyx
|
|
4
|
+
module Guardrails
|
|
5
|
+
module Ai
|
|
6
|
+
# Applies normalization, selection, Boolean validation, and reason bounds.
|
|
7
|
+
module AnalysisPipeline
|
|
8
|
+
INVALID_SHAPE_ERROR = 'ai_analyzer must return a Hash or a schema model with deep_to_h/to_h'
|
|
9
|
+
|
|
10
|
+
module_function
|
|
11
|
+
|
|
12
|
+
def call(value)
|
|
13
|
+
result = AnalysisNormalizer.call(value)
|
|
14
|
+
return { error: INVALID_SHAPE_ERROR } unless result
|
|
15
|
+
|
|
16
|
+
sanitized = ResultSanitizer.call(result)
|
|
17
|
+
invalid_key = BooleanValidator.invalid_key(sanitized)
|
|
18
|
+
return { error: "ai_analyzer #{invalid_key} must be true or false" } if invalid_key
|
|
19
|
+
|
|
20
|
+
sanitized[:reason] = sanitized[:reason].to_s[0...500] if sanitized.key?(:reason)
|
|
21
|
+
sanitized
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Olyx
|
|
4
|
+
module Guardrails
|
|
5
|
+
module Ai
|
|
6
|
+
# Finds the first analyzer flag whose value is not Boolean.
|
|
7
|
+
module BooleanValidator
|
|
8
|
+
KEYS = %i[injection_attempt pii_detected secret_leaked].freeze
|
|
9
|
+
|
|
10
|
+
module_function
|
|
11
|
+
|
|
12
|
+
def invalid_key(analysis)
|
|
13
|
+
KEYS.find { |key| analysis.key?(key) && ![true, false].include?(analysis[key]) }
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Olyx
|
|
4
|
+
module Guardrails
|
|
5
|
+
module Ai
|
|
6
|
+
# Merges one semantic Boolean finding into its deterministic check.
|
|
7
|
+
module FlagFindingMerger
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
def call(check, flagged, changes)
|
|
11
|
+
flagged ? check.merge(**changes, ai_flagged: true) : check
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Olyx
|
|
4
|
+
module Guardrails
|
|
5
|
+
module Ai
|
|
6
|
+
# Selects the bounded analyzer contract from an untrusted result Hash.
|
|
7
|
+
module ResultSanitizer
|
|
8
|
+
KEYS = %i[injection_attempt pii_detected secret_leaked risk_score reason].freeze
|
|
9
|
+
|
|
10
|
+
module_function
|
|
11
|
+
|
|
12
|
+
def call(result)
|
|
13
|
+
KEYS.each_with_object({}) do |key, output|
|
|
14
|
+
value, present = value_for(result, key)
|
|
15
|
+
output[key] = value if present
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def value_for(result, key)
|
|
20
|
+
return [result[key], true] if result.key?(key)
|
|
21
|
+
|
|
22
|
+
string_key = key.to_s
|
|
23
|
+
[result[string_key], result.key?(string_key)]
|
|
24
|
+
end
|
|
25
|
+
private_class_method :value_for
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Olyx
|
|
4
|
+
module Guardrails
|
|
5
|
+
module Ai
|
|
6
|
+
# Merges the semantic secret flag into a deterministic secret check.
|
|
7
|
+
module SecretFindingMerger
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
def call(check, analysis, policy)
|
|
11
|
+
return check unless analysis[:secret_leaked]
|
|
12
|
+
|
|
13
|
+
count = [check[:count].to_i, 1].max
|
|
14
|
+
check.merge(leaked: true, allowed: !policy.block_secrets?, count: count, ai_flagged: true)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Olyx
|
|
4
|
+
module Guardrails
|
|
5
|
+
module Ai
|
|
6
|
+
# Merges semantic PII and injection flags using declarative mappings.
|
|
7
|
+
module StandardFindingMerger
|
|
8
|
+
SETTINGS = {
|
|
9
|
+
pii: %i[pii_detected detected block_pii?],
|
|
10
|
+
injection: %i[injection_attempt injection_attempt block_injections?]
|
|
11
|
+
}.freeze
|
|
12
|
+
|
|
13
|
+
module_function
|
|
14
|
+
|
|
15
|
+
def call(checks, analysis, policy)
|
|
16
|
+
SETTINGS.to_h do |check_name, (finding_name, result_name, blocking_query)|
|
|
17
|
+
changes = { result_name => true, allowed: !policy.public_send(blocking_query) }
|
|
18
|
+
[check_name, FlagFindingMerger.call(checks[check_name], analysis[finding_name], changes)]
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'ai/analysis_normalizer'
|
|
4
|
+
require_relative 'ai/analysis_pipeline'
|
|
5
|
+
require_relative 'ai/boolean_validator'
|
|
6
|
+
require_relative 'ai/result_sanitizer'
|
|
7
|
+
|
|
8
|
+
module Olyx
|
|
9
|
+
module Guardrails
|
|
10
|
+
# Normalizes and bounds responses from an untrusted optional AI analyzer.
|
|
11
|
+
class AiAnalysis
|
|
12
|
+
def self.call(analyzer, text, context)
|
|
13
|
+
new(analyzer, text, context).call
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def initialize(analyzer, text, context)
|
|
17
|
+
@analyzer = analyzer
|
|
18
|
+
@text = text
|
|
19
|
+
@context = context
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def call
|
|
23
|
+
Ai::AnalysisPipeline.call(@analyzer.call(@text, @context))
|
|
24
|
+
rescue StandardError => error
|
|
25
|
+
{ error: error.message.to_s[0..200] }
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Olyx
|
|
4
|
+
module Guardrails
|
|
5
|
+
# Builds the bounded deterministic context passed to semantic analyzers.
|
|
6
|
+
module AiContextBuilder
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
def call(checks)
|
|
10
|
+
detection(checks).merge(policy(checks[:policy]))
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def detection(checks)
|
|
14
|
+
injection = checks[:injection]
|
|
15
|
+
{
|
|
16
|
+
pii_detected: checks[:pii][:detected],
|
|
17
|
+
injection_attempt: injection[:injection_attempt],
|
|
18
|
+
injection_patterns: injection[:patterns],
|
|
19
|
+
secret_leaked: checks[:secret][:leaked]
|
|
20
|
+
}
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def policy(check)
|
|
24
|
+
{
|
|
25
|
+
policy_violated: check[:violated],
|
|
26
|
+
policy_rules: check[:findings].map { |finding| finding[:rule] }
|
|
27
|
+
}
|
|
28
|
+
end
|
|
29
|
+
private_class_method :detection, :policy
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Olyx
|
|
4
|
+
module Guardrails
|
|
5
|
+
# Applies the policy-selected behavior for an analyzer failure.
|
|
6
|
+
module AiFailureHandler
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
def call(checks, analysis, mode)
|
|
10
|
+
return checks if mode == :allow
|
|
11
|
+
raise AiAnalyzerError, analysis[:error] if mode == :raise
|
|
12
|
+
|
|
13
|
+
checks.merge(ai: { type: 'ai', allowed: false, error: true })
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'ai/secret_finding_merger'
|
|
4
|
+
require_relative 'ai/flag_finding_merger'
|
|
5
|
+
require_relative 'ai/standard_finding_merger'
|
|
6
|
+
|
|
7
|
+
module Olyx
|
|
8
|
+
module Guardrails
|
|
9
|
+
# Unions semantic findings into deterministic checks without clearing any.
|
|
10
|
+
class AiFindingMerger
|
|
11
|
+
def self.call(checks, analysis, policy)
|
|
12
|
+
new(checks, analysis, policy).call
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def initialize(checks, analysis, policy)
|
|
16
|
+
@checks = checks
|
|
17
|
+
@analysis = analysis
|
|
18
|
+
@policy = policy
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def call
|
|
22
|
+
@checks.merge(
|
|
23
|
+
Ai::StandardFindingMerger.call(@checks, @analysis, @policy).merge(
|
|
24
|
+
secret: Ai::SecretFindingMerger.call(@checks[:secret], @analysis, @policy)
|
|
25
|
+
)
|
|
26
|
+
)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'ai_failure_handler'
|
|
4
|
+
|
|
5
|
+
module Olyx
|
|
6
|
+
module Guardrails
|
|
7
|
+
# Runs optional semantic analysis and merges valid findings.
|
|
8
|
+
module CheckAnalyzer
|
|
9
|
+
module_function
|
|
10
|
+
|
|
11
|
+
def call(checks, analyzer:, source:, policy:)
|
|
12
|
+
return [checks, nil] unless analyzer && checks[:length][:allowed]
|
|
13
|
+
|
|
14
|
+
analysis = AiAnalysis.call(analyzer, source, AiContextBuilder.call(checks))
|
|
15
|
+
[merge(checks, analysis, policy), analysis]
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def merge(checks, analysis, policy)
|
|
19
|
+
return AiFailureHandler.call(checks, analysis, policy.ai_failure_mode) if analysis[:error]
|
|
20
|
+
|
|
21
|
+
AiFindingMerger.call(checks, analysis, policy)
|
|
22
|
+
end
|
|
23
|
+
private_class_method :merge
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Olyx
|
|
4
|
+
module Guardrails
|
|
5
|
+
# Builds the stable deterministic portion of a check decision.
|
|
6
|
+
module CheckBaseResult
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
def call(checks, ordered, policy)
|
|
10
|
+
detection(checks, ordered).merge(policy(checks[:policy], policy))
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def detection(checks, ordered)
|
|
14
|
+
{
|
|
15
|
+
allowed: ordered.all? { |check| check[:allowed] },
|
|
16
|
+
pii_detected: checks[:pii][:detected],
|
|
17
|
+
injection_attempt: checks[:injection][:injection_attempt],
|
|
18
|
+
secret_leaked: checks[:secret][:leaked]
|
|
19
|
+
}
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def policy(check, configuration)
|
|
23
|
+
{
|
|
24
|
+
policy_name: configuration.name,
|
|
25
|
+
policy_violated: check[:violated],
|
|
26
|
+
policy_findings: check[:findings]
|
|
27
|
+
}
|
|
28
|
+
end
|
|
29
|
+
private_class_method :detection, :policy
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Olyx
|
|
4
|
+
module Guardrails
|
|
5
|
+
# Applies optional analysis and builds the public decision contract.
|
|
6
|
+
module CheckPipeline
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
def call(source, checks, policy:, ai_analyzer:)
|
|
10
|
+
merged, analysis = CheckAnalyzer.call(
|
|
11
|
+
checks, analyzer: ai_analyzer, source: source, policy: policy
|
|
12
|
+
)
|
|
13
|
+
CheckResultBuilder.call(merged, analysis, policy)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'risk_scorer'
|
|
4
|
+
require_relative 'check_base_result'
|
|
5
|
+
|
|
6
|
+
module Olyx
|
|
7
|
+
module Guardrails
|
|
8
|
+
# Presents merged checks as the stable public decision contract.
|
|
9
|
+
module CheckResultBuilder
|
|
10
|
+
CHECK_ORDER = %i[pii injection secret policy length ai].freeze
|
|
11
|
+
|
|
12
|
+
module_function
|
|
13
|
+
|
|
14
|
+
def call(checks, analysis, policy)
|
|
15
|
+
ordered = CHECK_ORDER.filter_map { |type| checks[type] }
|
|
16
|
+
result = CheckBaseResult.call(checks, ordered, policy)
|
|
17
|
+
result[:risk_score] = RiskScorer.call(checks, ordered, analysis)
|
|
18
|
+
result[:checks] = ordered
|
|
19
|
+
result[:ai_analysis] = analysis if analysis
|
|
20
|
+
result
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'ai_analysis'
|
|
4
|
+
require_relative 'ai_context_builder'
|
|
5
|
+
require_relative 'ai_finding_merger'
|
|
6
|
+
require_relative 'check_result_builder'
|
|
7
|
+
require_relative 'check_analyzer'
|
|
8
|
+
require_relative 'check_set'
|
|
9
|
+
require_relative 'check_pipeline'
|
|
10
|
+
require_relative 'policy'
|
|
11
|
+
require_relative 'validation'
|
|
12
|
+
|
|
13
|
+
module Olyx
|
|
14
|
+
module Guardrails
|
|
15
|
+
# Orchestrates deterministic checks and optional semantic enrichment.
|
|
16
|
+
class CheckRunner
|
|
17
|
+
def self.call(input, **)
|
|
18
|
+
new(input, **).call
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def initialize(input, policy:, ai_analyzer:)
|
|
22
|
+
@source = input.to_s
|
|
23
|
+
@policy = policy
|
|
24
|
+
@ai_analyzer = ai_analyzer
|
|
25
|
+
validate_options!
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def call
|
|
29
|
+
checks = CheckSet.call(@source, policy: @policy)
|
|
30
|
+
CheckPipeline.call(@source, checks, policy: @policy, ai_analyzer: @ai_analyzer)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def validate_options!
|
|
36
|
+
raise ArgumentError, 'policy must be an Olyx::Guardrails::Policy' unless @policy.is_a?(Policy)
|
|
37
|
+
|
|
38
|
+
Validation.callable_or_nil!(@ai_analyzer, name: 'ai_analyzer')
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|