olyx-guardrails 1.0.0 → 1.1.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.
Files changed (112) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +50 -22
  3. data/CODE_OF_CONDUCT.md +2 -5
  4. data/CONTRIBUTING.md +92 -23
  5. data/README.md +183 -531
  6. data/SECURITY.md +32 -6
  7. data/docs/API.md +650 -313
  8. data/docs/OPERATIONS.md +351 -0
  9. data/docs/POLICIES.md +293 -0
  10. data/docs/RAILS.md +443 -0
  11. data/docs/README.md +56 -0
  12. data/docs/RELEASING.md +69 -0
  13. data/examples/custom_policy.rb +7 -1
  14. data/examples/local_llm_provider.rb +66 -0
  15. data/examples/notifier.rb +5 -0
  16. data/examples/rails_opt_in.rb +18 -8
  17. data/examples/ruby_only.rb +7 -1
  18. data/lib/generators/olyx_guardrails/templates/initializer.rb.tt +8 -8
  19. data/lib/generators/olyx_guardrails/templates/policy.yml.tt +19 -15
  20. data/lib/olyx/guardrails/check_analyzer.rb +9 -6
  21. data/lib/olyx/guardrails/check_pipeline.rb +2 -2
  22. data/lib/olyx/guardrails/check_result_builder.rb +34 -7
  23. data/lib/olyx/guardrails/check_runner.rb +4 -7
  24. data/lib/olyx/guardrails/errors.rb +20 -8
  25. data/lib/olyx/guardrails/injection_detector.rb +22 -2
  26. data/lib/olyx/guardrails/{ai → llm}/analysis_normalizer.rb +1 -1
  27. data/lib/olyx/guardrails/llm/analysis_pipeline.rb +37 -0
  28. data/lib/olyx/guardrails/{ai → llm}/boolean_validator.rb +2 -2
  29. data/lib/olyx/guardrails/{ai → llm}/result_sanitizer.rb +2 -2
  30. data/lib/olyx/guardrails/llm_analysis.rb +26 -0
  31. data/lib/olyx/guardrails/{ai_context_builder.rb → llm_context_builder.rb} +2 -2
  32. data/lib/olyx/guardrails/llm_failure_handler.rb +17 -0
  33. data/lib/olyx/guardrails/llm_finding_merger.rb +39 -0
  34. data/lib/olyx/guardrails/message_check_runner.rb +3 -3
  35. data/lib/olyx/guardrails/notification/delivery_dispatcher.rb +3 -1
  36. data/lib/olyx/guardrails/notification_event_builder.rb +5 -6
  37. data/lib/olyx/guardrails/notification_sanitizer.rb +1 -2
  38. data/lib/olyx/guardrails/notifier.rb +49 -22
  39. data/lib/olyx/guardrails/notifier_configuration.rb +1 -2
  40. data/lib/olyx/guardrails/pii/sin_validator.rb +2 -2
  41. data/lib/olyx/guardrails/pii_scrubber.rb +22 -2
  42. data/lib/olyx/guardrails/policy/configuration.rb +11 -5
  43. data/lib/olyx/guardrails/policy.rb +62 -5
  44. data/lib/olyx/guardrails/policy_aware_redactor.rb +1 -2
  45. data/lib/olyx/guardrails/policy_rule/configuration.rb +8 -2
  46. data/lib/olyx/guardrails/policy_rule.rb +34 -2
  47. data/lib/olyx/guardrails/rails/action_cable.rb +9 -2
  48. data/lib/olyx/guardrails/rails/active_job_handler.rb +16 -2
  49. data/lib/olyx/guardrails/rails/active_model_validator.rb +8 -1
  50. data/lib/olyx/guardrails/rails/configuration.rb +79 -19
  51. data/lib/olyx/guardrails/rails/configuration_finalizer.rb +0 -2
  52. data/lib/olyx/guardrails/rails/configuration_registry.rb +6 -3
  53. data/lib/olyx/guardrails/rails/controller.rb +19 -4
  54. data/lib/olyx/guardrails/rails/decision_service.rb +1 -1
  55. data/lib/olyx/guardrails/rails/enforcer.rb +22 -8
  56. data/lib/olyx/guardrails/rails/graphql.rb +11 -2
  57. data/lib/olyx/guardrails/rails/ingress.rb +2 -0
  58. data/lib/olyx/guardrails/rails/job.rb +38 -5
  59. data/lib/olyx/guardrails/rails/policy_document.rb +1 -1
  60. data/lib/olyx/guardrails/rails/runtime.rb +36 -10
  61. data/lib/olyx/guardrails/rails/upload.rb +21 -7
  62. data/lib/olyx/guardrails/rails.rb +75 -3
  63. data/lib/olyx/guardrails/railtie.rb +5 -2
  64. data/lib/olyx/guardrails/redactor.rb +2 -2
  65. data/lib/olyx/guardrails/risk/{ai_score.rb → llm_score.rb} +4 -4
  66. data/lib/olyx/guardrails/risk_scorer.rb +8 -8
  67. data/lib/olyx/guardrails/secret_scanner.rb +25 -2
  68. data/lib/olyx/guardrails/secrets/blocked.rb +17 -2
  69. data/lib/olyx/guardrails/supplemental_violation_labels.rb +1 -1
  70. data/lib/olyx/guardrails/validation.rb +6 -0
  71. data/lib/olyx/guardrails/version.rb +1 -1
  72. data/lib/olyx/guardrails.rb +77 -27
  73. metadata +56 -51
  74. data/examples/claude_analyzer.rb +0 -53
  75. data/examples/openai_analyzer.rb +0 -45
  76. data/lib/olyx/guardrails/ai/analysis_pipeline.rb +0 -26
  77. data/lib/olyx/guardrails/ai/flag_finding_merger.rb +0 -16
  78. data/lib/olyx/guardrails/ai/secret_finding_merger.rb +0 -19
  79. data/lib/olyx/guardrails/ai/standard_finding_merger.rb +0 -24
  80. data/lib/olyx/guardrails/ai_analysis.rb +0 -29
  81. data/lib/olyx/guardrails/ai_failure_handler.rb +0 -17
  82. data/lib/olyx/guardrails/ai_finding_merger.rb +0 -30
  83. data/lib/olyx/guardrails/check_base_result.rb +0 -32
  84. data/lib/olyx/guardrails/integrations/openai/analyzer_setup.rb +0 -21
  85. data/lib/olyx/guardrails/integrations/openai/configuration_resolver.rb +0 -28
  86. data/lib/olyx/guardrails/integrations/openai/dependency_contracts.rb +0 -28
  87. data/lib/olyx/guardrails/integrations/openai/input_builder.rb +0 -39
  88. data/lib/olyx/guardrails/integrations/openai/member_reader.rb +0 -21
  89. data/lib/olyx/guardrails/integrations/openai/model_identifier.rb +0 -28
  90. data/lib/olyx/guardrails/integrations/openai/refusal_guard.rb +0 -28
  91. data/lib/olyx/guardrails/integrations/openai/request_builder.rb +0 -36
  92. data/lib/olyx/guardrails/integrations/openai/request_configuration.rb +0 -25
  93. data/lib/olyx/guardrails/integrations/openai/request_values.rb +0 -33
  94. data/lib/olyx/guardrails/integrations/openai/response_contents.rb +0 -22
  95. data/lib/olyx/guardrails/integrations/openai/response_option_keys.rb +0 -32
  96. data/lib/olyx/guardrails/integrations/openai/response_options.rb +0 -23
  97. data/lib/olyx/guardrails/integrations/openai/schema_registry.rb +0 -46
  98. data/lib/olyx/guardrails/integrations/openai/sdk.rb +0 -26
  99. data/lib/olyx/guardrails/integrations/openai/signal_summary.rb +0 -40
  100. data/lib/olyx/guardrails/integrations/openai_analyzer.rb +0 -107
  101. data/lib/olyx/guardrails/integrations/openai_analyzer_configuration.rb +0 -38
  102. data/lib/olyx/guardrails/integrations/openai_response_parser.rb +0 -34
  103. data/lib/olyx/guardrails/notification/delivery_summary.rb +0 -17
  104. data/lib/olyx/guardrails/policy/ai_failure_mode.rb +0 -15
  105. data/lib/olyx/guardrails/policy_rule/match_mode.rb +0 -15
  106. data/lib/olyx/guardrails/rails/evaluation_service.rb +0 -12
  107. data/lib/olyx/guardrails/rails/input_runtime.rb +0 -25
  108. data/lib/olyx/guardrails/rails/integration_configuration.rb +0 -27
  109. data/lib/olyx/guardrails/rails/message_evaluation_service.rb +0 -13
  110. data/lib/olyx/guardrails/rails/message_runtime.rb +0 -20
  111. data/lib/olyx/guardrails/rails/output_runtime.rb +0 -26
  112. data/lib/olyx/guardrails/rails/policy_configuration.rb +0 -25
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '069f552d05ca4f0ee61bf1496b27da5961477bb75eb422424b1cd19b31e1ac61'
4
- data.tar.gz: 9189f069fc540a3ecab429ebec194cb9c870917966837fb4c7fff428d705ed78
3
+ metadata.gz: c330386f60e14497f63e1e07978fa28aa3b21b028685662fd524964689d6b340
4
+ data.tar.gz: 052c2af355cc8351a69052e1e07e5e8655c723b4f17b46c86f0d9726b4978d1d
5
5
  SHA512:
6
- metadata.gz: 264d94b6a842c268cd30430ecf1cb6f2d49557d12266f331e594160e7100d8539b1c35351e4dbab58dd0525c97760c738a4f139270aefcb8d2f251835bd04e5c
7
- data.tar.gz: 75bd129939354ed2333d622cc215cc4a34b0967e39fcb482345719ddd312f9637ceb6a13beddfd27cbfa242f6057bd82178590e8ab98e63e10d79a461fe99b6d
6
+ metadata.gz: 94dbcc7502ced8d907c5154c585ab735330b33fa4b8a7cd0f3d74e4ec6cc1d325991ecb15fbcb9a00a5033b263486e4c040285fa0243e8a92540c7ec171c1647
7
+ data.tar.gz: fd53482fe7bb8c4f977d01cc0ea0064b10ef29ca5d722b024862b7d581d54647fb7e7d6b6a935b4874bc80f67c5fbd152b004d5454c925a54287084cd2b763b0
data/CHANGELOG.md CHANGED
@@ -3,6 +3,28 @@
3
3
  All notable changes to olyx-guardrails are documented here.
4
4
  Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
5
5
 
6
+ ## [1.1.0] - 2026-07-23
7
+
8
+ ### Changed
9
+
10
+ - Rails metadata now validates consistently as a Hash at every facade and
11
+ adapter boundary.
12
+ - Blocking exceptions use boundary-neutral wording, Active Job selectors
13
+ validate when declared, and inherited job declarations remain effective.
14
+ - Notifier misuse raises `ArgumentError`; `nil` now means only that a valid
15
+ decision has zero risk.
16
+ - The Rails generator provides copyable policy customization instructions and
17
+ tests the customized YAML path.
18
+ - Contributor setup, complete local quality validation, and maintainer release
19
+ verification have canonical commands and documentation.
20
+ - Closely coupled proxy objects were folded into their owning runtime,
21
+ configuration, result-building, and notification components.
22
+ - Provider risk scores now require a finite Numeric value; numeric strings are
23
+ ignored instead of being coerced across the untrusted provider boundary.
24
+ - Contributor and Rails appraisal locks use the current `net-imap` patch.
25
+ - CI refreshes the Ruby advisory database and blocks known-vulnerable locked
26
+ dependencies without making the offline local quality gate network-dependent.
27
+
6
28
  ## [1.0.0] - 2026-07-21
7
29
 
8
30
  Initial public release.
@@ -13,7 +35,7 @@ Initial public release.
13
35
  prompt-injection, secret, and restricted-policy findings without
14
36
  transforming the input. Returns `allowed`, per-category detection flags,
15
37
  `risk_score`, and a `checks` array with one entry per category. The
16
- `length` check runs first; content and AI checks are skipped (not just
38
+ `length` check runs first; content and LLM checks are skipped (not just
17
39
  failed) when it does, so an oversized payload never pays their cost.
18
40
  - `Olyx::Guardrails.redact` — transforms text by removing every
19
41
  regex-detected PII, secret, and restricted-policy match, without making an
@@ -33,9 +55,9 @@ Initial public release.
33
55
  monitoring-only restricted-content rules, `substring`/`whole_word`/`regexp`
34
56
  term matching, case-aware regex support, configurable safe replacements,
35
57
  and `Policy.from_h` for YAML/JSON-style Hash loading.
36
- - `ai_failure_mode:` controls how an analyzer failure is handled: `:allow`
37
- (default, keeps the deterministic result), `:block` (adds a failed `ai`
38
- check and rejects), or `:raise` (`AiAnalyzerError`).
58
+ - `llm_failure_mode:` controls how a provider failure is handled: `:allow`
59
+ (default, keeps the deterministic result), `:block` (adds a failed `llm`
60
+ check and rejects), or `:raise` (`LlmProviderError`).
39
61
  - Invalid configuration — duplicate rule names, empty-matching or invalid
40
62
  patterns, malformed message arrays, invalid custom regexes — raises
41
63
  `ArgumentError` at construction time rather than silently degrading into a
@@ -61,31 +83,29 @@ Initial public release.
61
83
  span. Custom patterns extend detection and are classified as secrets; use
62
84
  `PolicyRule` for named business restrictions instead.
63
85
 
64
- ### AI analyzer hook
86
+ ### LLM provider hook
65
87
 
66
- - Optional `ai_analyzer:` callable receives `(text, context)` and returns a
88
+ - Optional `llm_provider:` callable receives `(text, context)` and returns a
67
89
  Hash or a schema-model object implementing `deep_to_h`/`to_h`. Findings
68
90
  can add a violation but cannot clear a deterministic one. Boolean fields
69
91
  must be actual `true`/`false` values; non-finite risk scores are ignored
70
92
  rather than crashing. Exceptions and malformed responses are bounded under
71
- `result[:ai_analysis][:error]` and handled per `ai_failure_mode`.
72
- - Optional `Integrations::OpenAIAnalyzer` connector for the official OpenAI
73
- Ruby SDK: sends a strict `OpenAI::BaseModel` schema through the Responses
74
- API, consumes the parsed schema-model result, defaults to `store: false`,
75
- and has no model allowlist — it forwards any String/Symbol model
76
- identifier and relies on Structured Outputs support as the compatibility
77
- check. Refusals, API failures, and missing parsed output degrade into the
78
- same `result[:ai_analysis][:error]` path. The `openai` gem is optional and
79
- loaded lazily.
93
+ `result[:llm_analysis][:error]` and handled per `llm_failure_mode`.
94
+ - Provider transport is application-owned. The gem has no vendor SDK adapters,
95
+ endpoint assumptions, model allowlists, or model-specific parameters.
96
+ A framework-free local HTTP example demonstrates an open-source-first
97
+ classifier boundary using only Ruby standard-library HTTP and JSON support.
80
98
 
81
99
  ### Notifications
82
100
 
83
101
  - Vendor-neutral `Notifier` dispatches one sanitized, deeply frozen,
84
102
  versioned event to named callable handlers. Handlers run synchronously and
85
103
  independently — one failure doesn't stop the others, and errors are
86
- returned per handler rather than raised. Input previews, AI reasons,
104
+ returned per handler rather than raised. Input previews, LLM reasons,
87
105
  metadata keys/values, and handler errors are bounded and redacted with the
88
106
  policy's restricted-content rules and the built-in PII/secret detectors.
107
+ - Notification events expose optional provider reasoning as `llm_reason`;
108
+ the pre-release `ai_reason` field is not retained.
89
109
 
90
110
  ### Rails integration
91
111
 
@@ -103,15 +123,23 @@ Initial public release.
103
123
  - `Rails::ActiveJobHandler` enqueues sanitized notification events through
104
124
  any Active Job backend, resolving a reload-safe String/Symbol job
105
125
  constant at delivery time.
126
+ - The initial compatibility window covers Rails 8.0 and 8.1. Only listed Rails
127
+ series are tested and supported; subsequent gem releases may remove a series
128
+ after its upstream security support ends.
106
129
 
107
130
  ### Quality and security posture
108
131
 
109
- - No runtime dependencies for the core checks; Rails and the OpenAI SDK are
110
- both optional and loaded only when used.
111
- - CI enforces RuboCop, a Flog structural-complexity gate (max 10 per
112
- ordinary method, 60 per class/module, with documented DSL exemptions
113
- only), and a RubyCritic maintainability gate, alongside the Appraisal
114
- matrix across Rails 7.2, 8.0, and 8.1.
132
+ - The core has one lightweight runtime dependency, Ruby's `base64` bundled gem.
133
+ Rails remains optional and is loaded only when used.
134
+ - CI enforces RuboCop — including calibrated structural-complexity cops
135
+ (`Metrics/AbcSize`, `CyclomaticComplexity`, `PerceivedComplexity`,
136
+ `ClassLength`) in place of a separate complexity tool — and a RubyCritic
137
+ maintainability gate, alongside the Appraisal matrix across Rails 8.0
138
+ and 8.1.
139
+ - Native RDoc covers every supported public class, module, constant, attribute,
140
+ and method. CI blocks undocumented additions to the explicit public API
141
+ manifest while leaving implementation-only constants outside the
142
+ compatibility contract.
115
143
  - Least-privilege, SHA-pinned CI actions; CodeQL and OpenSSF Scorecard
116
144
  scanning; a private vulnerability-reporting process (see
117
145
  [SECURITY.md](SECURITY.md)).
data/CODE_OF_CONDUCT.md CHANGED
@@ -13,9 +13,7 @@ for everyone, regardless of background, identity, experience, or ability.
13
13
  - Respect privacy and confidentiality.
14
14
  - Focus disagreement on ideas, evidence, and project outcomes.
15
15
 
16
- Harassment, discrimination, threats, sexualized conduct, sustained disruption,
17
- doxing, or publication of another person's private information are not
18
- acceptable.
16
+ Harassment, discrimination, threats, sexualized conduct, sustained disruption, doxing, or publication of another person's private information are not acceptable.
19
17
 
20
18
  ## Enforcement
21
19
 
@@ -24,5 +22,4 @@ or remove contributions, issue warnings, temporarily restrict participation, or
24
22
  permanently ban participants when necessary. Reports will be handled as
25
23
  confidentially as reasonably possible, with conflicts of interest recused.
26
24
 
27
- This policy applies in project spaces and when someone is publicly representing
28
- the project.
25
+ This policy applies in project spaces and when someone is publicly representing the project.
data/CONTRIBUTING.md CHANGED
@@ -4,19 +4,44 @@ Thank you for improving Olyx Guardrails.
4
4
 
5
5
  ## Development setup
6
6
 
7
+ Detection logic (`lib/olyx/guardrails/pii/`, `secrets/`, `injection_detector.rb`,
8
+ `policy*`, and friends) remains independent of Rails at gem runtime. The
9
+ repository installs Rails as a development and test dependency so every
10
+ contributor runs the integration tests from the same default bundle. Consuming
11
+ applications that use only `require "olyx/guardrails"` do not load Rails.
12
+
13
+ Prepare the complete development bundle:
14
+
7
15
  ```bash
8
16
  rbenv install
9
- bundle install
10
- bundle exec rake test
11
- COVERAGE=true bundle exec rake test
12
- bundle exec rubocop --cache false
13
- bundle exec ruby script/flog_gate.rb
14
- bundle exec rubycritic -f console -f json -p tmp/rubycritic
15
- ruby script/rubycritic_gate.rb tmp/rubycritic/report.json
16
- bundle exec appraisal install
17
+ bin/setup
18
+ ```
19
+
20
+ Run the complete pre-pull-request gate:
21
+
22
+ ```bash
23
+ bin/ci
24
+ ```
25
+
26
+ `bin/ci` runs coverage-enforced tests, documentation validation, public RDoc
27
+ coverage, RuboCop, RubyCritic, and the strict per-file quality gate. Individual
28
+ tests remain runnable through `bundle exec ruby -Itest path/to/test_file.rb`.
29
+ CI separately refreshes the Ruby advisory database and audits the committed
30
+ dependency lockfile. This network-backed check stays outside `bin/ci` so the
31
+ local quality gate remains reproducible offline after setup.
32
+
33
+ ### Rails adapter changes
34
+
35
+ ```bash
17
36
  bundle exec appraisal rake test
18
37
  ```
19
38
 
39
+ Run this in addition to the above only when the change touches
40
+ `lib/olyx/guardrails/rails/`, `lib/generators/`, or their tests. The Appraisal
41
+ matrix covers Rails 8.0 and 8.1; Rails integration changes must pass every
42
+ configured line while the standalone core remains free of Rails runtime
43
+ dependencies.
44
+
20
45
  Ruby 3.4 or newer is supported. Changes must remain compatible with the oldest
21
46
  supported Ruby unless the same pull request deliberately changes the gem's
22
47
  requirement.
@@ -31,27 +56,71 @@ address genuine responsibility or clarity problems, but do not introduce proxy
31
56
  methods, unnecessary indirection, or metaprogramming merely to silence a
32
57
  heuristic.
33
58
 
34
- Flog is also a blocking structural gate: ordinary methods must score at most
35
- 10, and each class or module must total at most 60. Do not split cohesive code,
36
- hide behavior behind dynamic dispatch, or weaken a public API merely to improve
37
- a score. A public DSL or metaprogramming macro may be listed in
38
- `.flog_exemptions.yml` only when it is the clearest design; the entry must use
39
- Flog's fully-qualified method name and contain an explicit DSL/metaprogramming
40
- reason. Stale or unexplained exemptions fail CI.
59
+ RuboCop's `Metrics/AbcSize` (max 10), `Metrics/CyclomaticComplexity` (max 7),
60
+ `Metrics/PerceivedComplexity` (max 7), and `Metrics/ClassLength` (max 60 lines)
61
+ are the blocking structural-complexity gate, calibrated against this
62
+ codebase's measured distribution rather than RuboCop's looser defaults. Do not
63
+ split cohesive code, hide behavior behind dynamic dispatch, or weaken a public
64
+ API merely to improve a score. A public DSL or metaprogramming macro that
65
+ genuinely needs more room is a `# rubocop:disable` with an inline reason, or a
66
+ file-level exclude in `.rubocop.yml` with a comment explaining the constraint
67
+ — reviewed like any other exception, not a separate exemption file.
68
+
69
+ ## Documentation
70
+
71
+ Public documentation follows the same contract discipline as production code:
72
+
73
+ - write simple, declarative sentences in the present tense;
74
+ - keep the README focused on the shortest correct integration;
75
+ - place task-oriented explanations in the appropriate guide;
76
+ - keep signatures, defaults, return shapes, and exceptions in `docs/API.md`;
77
+ - use complete, copyable examples with synthetic data;
78
+ - link to one canonical explanation instead of duplicating it; and
79
+ - update documentation and tests in the same change as public behavior.
41
80
 
42
- The Appraisal matrix covers Rails 7.2, 8.0, and 8.1. Rails integration changes
43
- must pass every configured Rails line while the standalone core remains free of
44
- Rails runtime dependencies.
81
+ Run `ruby script/documentation_gate.rb` after changing Markdown. The gate checks
82
+ local files and heading anchors. CI also syntax-checks every Ruby example.
83
+
84
+ Public code comments use native RDoc conventions and the same concise,
85
+ declarative style. Document behavior, accepted arguments, return values,
86
+ exceptions, security boundaries, and non-obvious edge cases. Do not narrate
87
+ implementation mechanics or repeat the method name. Add supported source files
88
+ to the RDoc whitelist in `olyx-guardrails.gemspec`; implementation-only
89
+ constants remain outside that manifest and are not compatibility commitments.
45
90
 
46
91
  ## Pull requests
47
92
 
48
- 1. Open an issue first for substantial API or policy changes.
49
- 2. Keep each pull request focused and include tests for behavior changes.
50
- 3. Add adversarial regression tests for security-sensitive fixes.
93
+ Contributions follow the same fork-and-pull-request workflow used by established
94
+ open-source projects:
95
+
96
+ 1. Open an issue first for substantial API, policy, or architectural changes.
97
+ Small fixes may go directly to a pull request.
98
+ 2. Fork the repository and create a focused branch from the latest `master`.
99
+ Do not work directly on `master`.
100
+ 3. Add tests for behavior changes and adversarial regression tests for
101
+ security-sensitive fixes.
51
102
  4. Update README, API reference, examples, and changelog when public behavior
52
103
  changes.
53
- 5. Confirm tests, RuboCop, Flog, RubyCritic, syntax checks, and gem packaging
54
- pass locally.
104
+ 5. Run `bin/ci` locally, plus the Appraisal Rails matrix when the change touches
105
+ `lib/olyx/guardrails/rails/` or `lib/generators/`.
106
+ 6. Open a draft pull request early when maintainer feedback would prevent
107
+ rework. Mark it ready only when its description and validation checklist are
108
+ complete.
109
+
110
+ Pull requests run with read-only GitHub token permissions, including
111
+ contributions from forks. CI never requires repository secrets. The protected
112
+ default branch requires the supported Ruby and Rails matrix, dependency audit,
113
+ quality gate, CodeQL analysis, resolved review conversations, and maintainer
114
+ approval. Maintainers squash approved pull requests, and GitHub deletes the
115
+ source branch after merge.
116
+
117
+ Maintainers may ask for a pull request to be split when unrelated behavior,
118
+ refactoring, or generated dependency changes make review unsafe. Force-pushing
119
+ a contributor branch is acceptable while responding to review; GitHub
120
+ dismisses stale approvals when the protected branch changes.
121
+
122
+ Maintainer releases follow [docs/RELEASING.md](docs/RELEASING.md). Do not
123
+ publish an artifact that has not completed that runbook.
55
124
 
56
125
  Never include real credentials, personal data, production endpoints, or
57
126
  customer content in fixtures. Use clearly synthetic values.