clickwrap 0.0.0 → 0.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 (156) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +90 -0
  3. data/CHANGELOG.md +612 -0
  4. data/README.md +830 -1204
  5. data/SECURITY.md +33 -0
  6. data/app/assets/stylesheets/clickwrap.css +241 -0
  7. data/app/controllers/clickwrap/application_controller.rb +79 -0
  8. data/app/controllers/clickwrap/captures_controller.rb +145 -0
  9. data/app/controllers/clickwrap/document_versions_controller.rb +71 -0
  10. data/app/controllers/clickwrap/receipts_controller.rb +115 -0
  11. data/app/controllers/clickwrap/withdrawals_controller.rb +60 -0
  12. data/app/helpers/clickwrap/engine_helper.rb +97 -0
  13. data/app/views/clickwrap/captures/show.html.erb +34 -0
  14. data/app/views/clickwrap/receipts/index.html.erb +38 -0
  15. data/app/views/clickwrap/receipts/show.html.erb +91 -0
  16. data/app/views/clickwrap/shared/_error_summary.html.erb +39 -0
  17. data/app/views/clickwrap/shared/_fields.html.erb +100 -0
  18. data/app/views/clickwrap/shared/_statement.html.erb +105 -0
  19. data/app/views/clickwrap/withdrawals/new.html.erb +30 -0
  20. data/config/locales/en.yml +160 -0
  21. data/config/locales/es.yml +138 -0
  22. data/config/routes.rb +41 -0
  23. data/exe/clickwrap +374 -0
  24. data/guides/README.md +30 -0
  25. data/guides/accessibility.md +249 -0
  26. data/guides/consent-and-lifecycle.md +299 -0
  27. data/guides/integrating.md +610 -0
  28. data/guides/integrity.md +212 -0
  29. data/guides/migrating.md +335 -0
  30. data/guides/naming.md +320 -0
  31. data/guides/organizations.md +320 -0
  32. data/guides/receipts-and-verification.md +415 -0
  33. data/guides/request-evidence.md +512 -0
  34. data/guides/retention-and-legal-holds.md +438 -0
  35. data/lib/clickwrap/actor_proxy.rb +147 -0
  36. data/lib/clickwrap/anonymous_actor.rb +47 -0
  37. data/lib/clickwrap/authority.rb +174 -0
  38. data/lib/clickwrap/canonical_json.rb +216 -0
  39. data/lib/clickwrap/capture/event_builder.rb +220 -0
  40. data/lib/clickwrap/capture/presentation_verifier.rb +521 -0
  41. data/lib/clickwrap/capture.rb +650 -0
  42. data/lib/clickwrap/configuration.rb +1129 -0
  43. data/lib/clickwrap/controller_helpers.rb +758 -0
  44. data/lib/clickwrap/current_state.rb +282 -0
  45. data/lib/clickwrap/digest.rb +125 -0
  46. data/lib/clickwrap/doctor.rb +418 -0
  47. data/lib/clickwrap/document_definition.rb +255 -0
  48. data/lib/clickwrap/document_renderer.rb +83 -0
  49. data/lib/clickwrap/document_renderers/markdown.rb +175 -0
  50. data/lib/clickwrap/document_renderers/markdown_rails.rb +126 -0
  51. data/lib/clickwrap/dsl/policy_builder.rb +462 -0
  52. data/lib/clickwrap/dsl/retention_builder.rb +89 -0
  53. data/lib/clickwrap/durable_commit_callback.rb +37 -0
  54. data/lib/clickwrap/engine.rb +184 -0
  55. data/lib/clickwrap/errors.rb +181 -0
  56. data/lib/clickwrap/form_builder_extensions.rb +341 -0
  57. data/lib/clickwrap/front_matter.rb +67 -0
  58. data/lib/clickwrap/identifier.rb +112 -0
  59. data/lib/clickwrap/import/external_receipt.rb +241 -0
  60. data/lib/clickwrap/import/fine_print.rb +290 -0
  61. data/lib/clickwrap/import/legacy.rb +450 -0
  62. data/lib/clickwrap/integrations/organizations_authority.rb +81 -0
  63. data/lib/clickwrap/integrity/anchor.rb +130 -0
  64. data/lib/clickwrap/integrity/attestation_reconciler.rb +114 -0
  65. data/lib/clickwrap/integrity/attestor.rb +221 -0
  66. data/lib/clickwrap/integrity/chain.rb +313 -0
  67. data/lib/clickwrap/integrity/timestamp.rb +143 -0
  68. data/lib/clickwrap/ip_geolocation/location.rb +112 -0
  69. data/lib/clickwrap/ip_geolocation/null_resolver.rb +35 -0
  70. data/lib/clickwrap/ip_geolocation/resolver.rb +97 -0
  71. data/lib/clickwrap/ip_geolocation/static_resolver.rb +107 -0
  72. data/lib/clickwrap/ip_geolocation/trackdown_resolver.rb +330 -0
  73. data/lib/clickwrap/ip_geolocation.rb +16 -0
  74. data/lib/clickwrap/lifecycle.rb +534 -0
  75. data/lib/clickwrap/linter.rb +382 -0
  76. data/lib/clickwrap/localized_text.rb +101 -0
  77. data/lib/clickwrap/macros.rb +203 -0
  78. data/lib/clickwrap/models/application_record.rb +20 -0
  79. data/lib/clickwrap/models/chain_head.rb +79 -0
  80. data/lib/clickwrap/models/concerns/has_clickwraps.rb +55 -0
  81. data/lib/clickwrap/models/disposition_plan.rb +208 -0
  82. data/lib/clickwrap/models/document.rb +46 -0
  83. data/lib/clickwrap/models/document_version.rb +163 -0
  84. data/lib/clickwrap/models/event.rb +743 -0
  85. data/lib/clickwrap/models/event_document.rb +79 -0
  86. data/lib/clickwrap/models/event_statement.rb +92 -0
  87. data/lib/clickwrap/models/external_action.rb +150 -0
  88. data/lib/clickwrap/models/integrity_attestation.rb +90 -0
  89. data/lib/clickwrap/models/legal_hold.rb +81 -0
  90. data/lib/clickwrap/models/policy_revision.rb +115 -0
  91. data/lib/clickwrap/models/presentation.rb +59 -0
  92. data/lib/clickwrap/models/receipt_access.rb +53 -0
  93. data/lib/clickwrap/models/recording_sequence.rb +21 -0
  94. data/lib/clickwrap/models/request_evidence.rb +378 -0
  95. data/lib/clickwrap/models/statement_identity_lock.rb +38 -0
  96. data/lib/clickwrap/models/statement_state.rb +130 -0
  97. data/lib/clickwrap/pending_receipt.rb +177 -0
  98. data/lib/clickwrap/policy.rb +283 -0
  99. data/lib/clickwrap/presentation_manifest.rb +210 -0
  100. data/lib/clickwrap/presenter.rb +716 -0
  101. data/lib/clickwrap/privacy.rb +419 -0
  102. data/lib/clickwrap/protected_outcome.rb +120 -0
  103. data/lib/clickwrap/receipt.rb +606 -0
  104. data/lib/clickwrap/receipt_html.rb +235 -0
  105. data/lib/clickwrap/receipt_verifier.rb +978 -0
  106. data/lib/clickwrap/reference.rb +44 -0
  107. data/lib/clickwrap/registration.rb +236 -0
  108. data/lib/clickwrap/registry.rb +54 -0
  109. data/lib/clickwrap/remediation_token.rb +155 -0
  110. data/lib/clickwrap/request_evidence_extractor.rb +590 -0
  111. data/lib/clickwrap/request_evidence_policy.rb +261 -0
  112. data/lib/clickwrap/retention/applier.rb +231 -0
  113. data/lib/clickwrap/retention/disposition.rb +221 -0
  114. data/lib/clickwrap/retention/planner.rb +502 -0
  115. data/lib/clickwrap/retention_class.rb +97 -0
  116. data/lib/clickwrap/reviewed_text.rb +28 -0
  117. data/lib/clickwrap/schema_requirements.rb +196 -0
  118. data/lib/clickwrap/services/authorize_external_action.rb +149 -0
  119. data/lib/clickwrap/services/load_policies.rb +69 -0
  120. data/lib/clickwrap/services/publish_documents.rb +251 -0
  121. data/lib/clickwrap/services/validate_policy_references.rb +166 -0
  122. data/lib/clickwrap/statement.rb +248 -0
  123. data/lib/clickwrap/subject_fingerprint.rb +28 -0
  124. data/lib/clickwrap/submission.rb +169 -0
  125. data/lib/clickwrap/system_actor.rb +31 -0
  126. data/lib/clickwrap/test_helpers.rb +676 -0
  127. data/lib/clickwrap/testing.rb +211 -0
  128. data/lib/clickwrap/trusted_proxy_configuration.rb +92 -0
  129. data/lib/clickwrap/verification.rb +504 -0
  130. data/lib/clickwrap/version.rb +12 -1
  131. data/lib/clickwrap/view_helpers.rb +190 -0
  132. data/lib/clickwrap/vocabulary.rb +294 -0
  133. data/lib/clickwrap.rb +497 -7
  134. data/lib/generators/clickwrap/document_generator.rb +164 -0
  135. data/lib/generators/clickwrap/hardening_generator.rb +177 -0
  136. data/lib/generators/clickwrap/install_generator.rb +1287 -0
  137. data/lib/generators/clickwrap/link_generator.rb +56 -0
  138. data/lib/generators/clickwrap/policy_generator.rb +118 -0
  139. data/lib/generators/clickwrap/templates/clickwrap_hardening.rb.erb +256 -0
  140. data/lib/generators/clickwrap/templates/clickwrap_policies.rb.erb +192 -0
  141. data/lib/generators/clickwrap/templates/create_clickwrap_external_action_tables.rb.erb +128 -0
  142. data/lib/generators/clickwrap/templates/create_clickwrap_integrity_tables.rb.erb +157 -0
  143. data/lib/generators/clickwrap/templates/create_clickwrap_presentation_tables.rb.erb +160 -0
  144. data/lib/generators/clickwrap/templates/create_clickwrap_request_evidence_tables.rb.erb +180 -0
  145. data/lib/generators/clickwrap/templates/create_clickwrap_retention_tables.rb.erb +174 -0
  146. data/lib/generators/clickwrap/templates/create_clickwrap_tables.rb.erb +568 -0
  147. data/lib/generators/clickwrap/templates/initializer.rb.erb +439 -0
  148. data/lib/generators/clickwrap/templates/link_clickwrap_event_migration.rb.erb +12 -0
  149. data/lib/generators/clickwrap/templates/policy.rb.erb +31 -0
  150. data/lib/generators/clickwrap/templates/policy_test.rb.erb +56 -0
  151. data/lib/generators/clickwrap/templates/privacy.md.erb +58 -0
  152. data/lib/generators/clickwrap/templates/terms.md.erb +49 -0
  153. data/lib/generators/clickwrap/upgrade_generator.rb +50 -0
  154. data/lib/generators/clickwrap/views_generator.rb +101 -0
  155. data/lib/tasks/clickwrap.rake +569 -0
  156. metadata +311 -16
@@ -0,0 +1,184 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/engine"
4
+
5
+ module Clickwrap
6
+ # The mountable engine: wires autoloading, migrations, locales, the
7
+ # ActiveRecord macro, the form-builder extension, the controller helpers, and
8
+ # boot-time policy loading into the host app.
9
+ class Engine < ::Rails::Engine
10
+ isolate_namespace Clickwrap
11
+
12
+ # -------------------------------------------------------------------------
13
+ # Zeitwerk: the gem keeps its ActiveRecord models under lib/clickwrap/models
14
+ # (same layout as the moderate and chats gems) so the whole domain ships in
15
+ # lib/ and the engine's app/ tree only holds the web layer. For that to
16
+ # autoload correctly we manage the loader by hand:
17
+ #
18
+ # - `push_dir(lib/clickwrap, namespace: Clickwrap)` makes
19
+ # lib/clickwrap/models/... autoloadable *under the Clickwrap namespace*.
20
+ # - `collapse(models)` + `collapse(models/concerns)` mean the files in
21
+ # those folders define Clickwrap::Event / Clickwrap::HasClickwraps —
22
+ # not Clickwrap::Models::Event.
23
+ # - The SPINE files are required explicitly by lib/clickwrap.rb at boot,
24
+ # so they must be *ignored* by the loader or Zeitwerk would complain
25
+ # about double definitions.
26
+ # -------------------------------------------------------------------------
27
+ LIB_ROOT = File.expand_path("..", __dir__)
28
+ CLICKWRAP_LIB = File.expand_path("clickwrap", LIB_ROOT)
29
+
30
+ ZEITWERK_IGNORED = %w[
31
+ version.rb
32
+ errors.rb
33
+ vocabulary.rb
34
+ canonical_json.rb
35
+ digest.rb
36
+ identifier.rb
37
+ ip_geolocation.rb
38
+ ip_geolocation/location.rb
39
+ ip_geolocation/resolver.rb
40
+ ip_geolocation/null_resolver.rb
41
+ ip_geolocation/static_resolver.rb
42
+ ip_geolocation/trackdown_resolver.rb
43
+ configuration.rb
44
+ engine.rb
45
+ macros.rb
46
+ authority.rb
47
+ registry.rb
48
+ localized_text.rb
49
+ document_definition.rb
50
+ statement.rb
51
+ request_evidence_policy.rb
52
+ policy.rb
53
+ retention_class.rb
54
+ dsl/policy_builder.rb
55
+ dsl/retention_builder.rb
56
+ document_renderers/markdown.rb
57
+ form_builder_extensions.rb
58
+ view_helpers.rb
59
+ controller_helpers.rb
60
+ registration.rb
61
+ ].freeze
62
+
63
+ initializer "clickwrap.autoload", before: :set_autoload_paths do
64
+ loader = Rails.autoloaders.main
65
+
66
+ ZEITWERK_IGNORED.each do |file|
67
+ path = File.join(CLICKWRAP_LIB, file)
68
+ loader.ignore(path) if File.exist?(path)
69
+ end
70
+
71
+ %w[models models/concerns].each do |dir|
72
+ path = File.join(CLICKWRAP_LIB, dir)
73
+ loader.collapse(path) if File.directory?(path)
74
+ end
75
+
76
+ loader.push_dir(CLICKWRAP_LIB, namespace: Clickwrap)
77
+ end
78
+
79
+ config.eager_load_paths << CLICKWRAP_LIB
80
+
81
+ # Make the gem's migrations runnable from the host without copying
82
+ # (`rails db:migrate` picks them up). The install generator still copies a
83
+ # host-owned migration, which is the recommended path: a released migration
84
+ # is never edited underneath an installed application, so the host owns the
85
+ # file that describes its own schema.
86
+ initializer "clickwrap.migrations" do |app|
87
+ unless app.root.to_s == root.to_s
88
+ config.paths["db/migrate"].expanded.each do |path|
89
+ app.config.paths["db/migrate"] << path
90
+ end
91
+ end
92
+ end
93
+
94
+ # Expose `has_clickwraps` on every AR model.
95
+ initializer "clickwrap.active_record" do
96
+ ActiveSupport.on_load(:active_record) do
97
+ extend Clickwrap::Macros
98
+ end
99
+ end
100
+
101
+ # Expose `form.clickwrap` and `form.clickwrap_fields` on the standard form
102
+ # builder, so the one-line happy path works in an ordinary `form_with`.
103
+ initializer "clickwrap.form_builder" do
104
+ ActiveSupport.on_load(:action_view) do
105
+ ActionView::Helpers::FormBuilder.prepend(Clickwrap::FormBuilderExtensions) unless
106
+ ActionView::Helpers::FormBuilder < Clickwrap::FormBuilderExtensions
107
+ # The custom-surface helpers (token field, statement controls, the
108
+ # manifest-worded submit) — available in every view, like the form
109
+ # builder methods above.
110
+ include Clickwrap::ViewHelpers
111
+ end
112
+ end
113
+
114
+ # Give host controllers `clickwrap_submission`, `capture_clickwrap_and!`,
115
+ # and the `requires_clickwrap` gate.
116
+ initializer "clickwrap.action_controller" do
117
+ ActiveSupport.on_load(:action_controller) do
118
+ include Clickwrap::ControllerHelpers
119
+
120
+ # `clickwraps_registration_with :signup` for Devise controllers. The
121
+ # macro is available everywhere; nothing happens unless a controller
122
+ # actually calls it, and Devise is never required.
123
+ include Clickwrap::Registration
124
+ end
125
+ end
126
+
127
+ # Ship the gem's locale files. Host locale files with the same keys override
128
+ # these automatically (I18n's load order puts the app last), which is how a
129
+ # host rewords a statement without forking a view.
130
+ initializer "clickwrap.locales" do |app|
131
+ app.config.i18n.load_path += Dir[root.join("config", "locales", "**", "*.{rb,yml}").to_s]
132
+ end
133
+
134
+ initializer "clickwrap.assets" do |app|
135
+ app.config.assets.paths << root.join("app/assets/stylesheets") if app.config.respond_to?(:assets)
136
+ end
137
+
138
+ # Load the host's document, policy, and retention declarations. They live in
139
+ # ordinary Ruby files so they are reviewable in a diff and deployable like
140
+ # code; `to_prepare` re-reads them on every boot and every development
141
+ # reload, and compilation raises on a mistake rather than deferring it to
142
+ # the first person who tries to sign up.
143
+ # Every `requires_clickwrap` gate, checked once the host's routes exist.
144
+ #
145
+ # `after_routes_loaded` rather than `after_initialize`, and the difference
146
+ # matters: `after_initialize` runs while the application is still
147
+ # initializing, which is precisely when the routes reloader declines to
148
+ # load. Checking there would report "the engine is not mounted" to
149
+ # applications that mount it perfectly well, which is worse than not
150
+ # checking at all. This hook fires at the one moment the answer is knowable.
151
+ #
152
+ # A dead-end gate is still better found at boot than by the first person it
153
+ # stops, so the request path keeps its own backstop for controllers that are
154
+ # autoloaded later in development.
155
+ if config.respond_to?(:after_routes_loaded)
156
+ config.after_routes_loaded do
157
+ Clickwrap::ControllerHelpers.verify_registered_gates!
158
+ Clickwrap::Services::ValidatePolicyReferences.validate_remediation_paths!
159
+ end
160
+ end
161
+
162
+ config.to_prepare do
163
+ Clickwrap::Services::LoadPolicies.new.call
164
+
165
+ # Whether the raw request-evidence values are encrypted is a host decision
166
+ # read from the initializer, so it can only be applied once that has been
167
+ # read — and it has to be re-applied on every development reload, because
168
+ # the model class is a fresh object each time.
169
+ Clickwrap::RequestEvidence.apply_configured_encryption!
170
+
171
+ # `clickwrap:install` emits only the tables a default installation can
172
+ # actually put a row in; the rest come with their own flag. The one
173
+ # failure mode that creates is turning a capability on and forgetting its
174
+ # migration, and that must not be discovered by a capture failing in
175
+ # production. It is discovered here, with the exact command that fixes
176
+ # it. Answering needs a database, and "there is no database yet"
177
+ # (db:create, an asset build) is not a misconfiguration — the check
178
+ # simply does not fire.
179
+ Clickwrap::SchemaRequirements.reset!
180
+ missing = Clickwrap::SchemaRequirements.missing_for_configuration
181
+ raise Clickwrap::ConfigurationError, missing.map(&:explanation).join("\n\n") if missing.any?
182
+ end
183
+ end
184
+ end
@@ -0,0 +1,181 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Clickwrap
4
+ # Base class for every error Clickwrap raises. Hosts can rescue this one
5
+ # class and still distinguish causes through the subclasses below.
6
+ class Error < StandardError; end
7
+
8
+ # --- Configuration and boot -------------------------------------------------
9
+
10
+ # Raised when the initializer contains a value Clickwrap cannot safely use.
11
+ class ConfigurationError < Error; end
12
+
13
+ # Raised while compiling a policy, document, or retention class definition.
14
+ class DefinitionError < Error; end
15
+
16
+ # Raised when a policy, document, statement, or retention class is referenced
17
+ # but was never defined.
18
+ class NotDefinedError < Error; end
19
+
20
+ class UnknownPolicyError < NotDefinedError; end
21
+ class UnknownStatementError < NotDefinedError; end
22
+ class UnknownRetentionClassError < NotDefinedError; end
23
+
24
+ # --- Documents --------------------------------------------------------------
25
+
26
+ # Raised when a document version is required but has not been published, or
27
+ # when stored bytes no longer match their recorded digest.
28
+ class DocumentNotPublishedError < Error; end
29
+ class DocumentDigestMismatchError < Error; end
30
+
31
+ # Raised when a version label is reused for different bytes.
32
+ class DocumentVersionConflictError < Error; end
33
+
34
+ # --- Presentation and submission -------------------------------------------
35
+
36
+ # The family of refusals a PERSON can cause from a form: a stale or missing
37
+ # presentation, a submission that doesn't parse, a required box left empty.
38
+ # These are not application errors — they deserve a re-rendered form with a
39
+ # localized sentence, never a 500. One rescue covers the whole family:
40
+ #
41
+ # rescue Clickwrap::CaptureRefused => refusal
42
+ # redirect_to somewhere_path, alert: refusal.user_facing_message
43
+ #
44
+ # Everything OUTSIDE this family (an evidence write failure above all) stays
45
+ # loud on purpose: infrastructure problems are yours to see, not the
46
+ # person's to absorb.
47
+ class CaptureRefused < Error
48
+ # The localized sentence the gem's own screens show for this refusal —
49
+ # safe to put in front of a person, never a developer-facing message.
50
+ def user_facing_message
51
+ I18n.t("clickwrap.errors.presentation_no_longer_valid")
52
+ end
53
+ end
54
+
55
+ # Raised when the submitted presentation token is missing, malformed, signed
56
+ # with a different key, expired, or bound to a different actor, tenant,
57
+ # subject, policy, or revision than the one being captured.
58
+ class PresentationInvalid < CaptureRefused
59
+ attr_reader :result
60
+
61
+ def initialize(message = nil, result: nil)
62
+ @result = result
63
+ super(message || result&.message || "The presentation could not be verified")
64
+ end
65
+ end
66
+
67
+ class PresentationExpired < PresentationInvalid; end
68
+
69
+ # Raised when a submission cannot be parsed or contains keys the manifest
70
+ # never declared.
71
+ class SubmissionInvalid < CaptureRefused; end
72
+
73
+ # --- Capture ----------------------------------------------------------------
74
+
75
+ # Raised when a required answer is missing or an answer is not one of the
76
+ # choices the server offered.
77
+ class AnswerInvalid < CaptureRefused
78
+ attr_reader :statement_key, :reason
79
+
80
+ def initialize(message = nil, statement_key: nil, reason: nil)
81
+ @statement_key = statement_key
82
+ @reason = reason
83
+ super(message || "The submitted answer for #{statement_key.inspect} was not accepted")
84
+ end
85
+
86
+ def user_facing_message
87
+ I18n.t("clickwrap.errors.required_statement")
88
+ end
89
+ end
90
+
91
+ # Raised when the evidence event itself could not be written. The protected
92
+ # database action in the same transaction rolls back with it.
93
+ class EventWriteFailed < Error; end
94
+
95
+ # Raised when an identical idempotency key arrives with a different payload.
96
+ class ReplayRejected < Error; end
97
+
98
+ # Raised when a registration block returns without persisting the exact
99
+ # prospective actor the presentation was issued for.
100
+ class RegistrationFailed < Error; end
101
+
102
+ # Raised when a represented-party creation block does not return a persisted
103
+ # record of the type whose browser flow was bound at presentation.
104
+ class RepresentedPartyCreationFailed < Error; end
105
+
106
+ # Raised when delegated action was not explicitly permitted by the policy and
107
+ # positively verified by the host's authority callback.
108
+ class AuthorityNotVerified < Error; end
109
+
110
+ # Raised when a signed in-place remediation context is missing, expired,
111
+ # belongs to a different actor/tenant/policy, or names a resource that can no
112
+ # longer be resolved and authorized.
113
+ class RemediationInvalid < Error; end
114
+ class RemediationNotAuthorized < RemediationInvalid; end
115
+
116
+ # Raised when a deadlock or serialization failure occurred and Clickwrap
117
+ # cannot prove the caller's block is safe to retry. The host decides.
118
+ class RetryableTransactionError < Error; end
119
+
120
+ # --- Verification and gating ------------------------------------------------
121
+
122
+ # Raised by the bang verification methods. Always carries the same structured
123
+ # result the non-bang `Clickwrap.verify` would have returned, so applications
124
+ # never parse an English message to make an authorization decision.
125
+ class VerificationFailed < Error
126
+ attr_reader :result
127
+
128
+ def initialize(result)
129
+ @result = result
130
+ super(result.message)
131
+ end
132
+
133
+ def error = result.error
134
+ end
135
+
136
+ # --- Lifecycle --------------------------------------------------------------
137
+
138
+ class LifecycleError < Error; end
139
+ class AlreadyWithdrawnError < LifecycleError; end
140
+ class AlreadyConsumedError < LifecycleError; end
141
+ class OneTimeAuthorizationConflict < LifecycleError; end
142
+ class NotWithdrawableError < LifecycleError; end
143
+
144
+ # --- Request evidence, retention, and disposition ---------------------------
145
+
146
+ # Raised when a policy requires IP address, browser user-agent, or IP
147
+ # geolocation evidence and it could not be resolved.
148
+ class RequestEvidenceUnavailable < Error; end
149
+
150
+ # Raised when disposition is attempted on evidence under a legal hold.
151
+ class LegalHoldInEffect < Error; end
152
+
153
+ # Raised when ordinary Active Record mutation is attempted against evidence
154
+ # whose only permitted changes are named, audited lifecycle transitions.
155
+ class ImmutableEvidenceError < Error; end
156
+
157
+ # Raised when a disposition plan is stale, already applied, expired, or no
158
+ # longer matches what the operator reviewed.
159
+ class DispositionPlanInvalid < Error; end
160
+
161
+ # Raised when unredacted request evidence is requested without host
162
+ # authorization or without a human-readable reason.
163
+ class AccessNotAuthorized < Error; end
164
+
165
+ # --- Integrity and receipts -------------------------------------------------
166
+
167
+ # Raised when a receipt cannot be parsed, its schema version is unknown, or
168
+ # its canonical bytes do not match the digest it carries.
169
+ class ReceiptInvalid < Error; end
170
+ class UnknownReceiptSchema < ReceiptInvalid; end
171
+ class IntegrityCheckFailed < Error; end
172
+
173
+ # Raised when a pending receipt is used after its transaction rolled back, or
174
+ # when export/verification is called before commit.
175
+ class ReceiptNotCommitted < Error; end
176
+
177
+ # --- External actions -------------------------------------------------------
178
+
179
+ class ExternalActionError < Error; end
180
+ class ExternalActionAlreadyResolved < ExternalActionError; end
181
+ end