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,439 @@
1
+ # frozen_string_literal: true
2
+
3
+ # ==============================================================================
4
+ # Clickwrap
5
+ # ==============================================================================
6
+ #
7
+ # Everything here is a decision about evidence: what gets recorded, who can read
8
+ # it, how long it lives, and what the receipt is allowed to claim. The comments
9
+ # explain the CONSEQUENCE of each setting rather than its syntax, because the
10
+ # person reading this file in three years will be trying to answer a question
11
+ # about something that happened, not looking up an argument type.
12
+ #
13
+ # ONLY DECISIONS ARE LIVE IN THIS FILE. Every setting left at the gem's default
14
+ # appears commented, with its default value shown — the defaults are already the
15
+ # safe posture (nothing personal is collected, annexes encrypted, receipt access
16
+ # denied until granted), and restating them as live code would dress a few dozen
17
+ # non-decisions up as decisions. `bin/rails clickwrap:doctor` prints the full
18
+ # effective posture at any time.
19
+ #
20
+ # The one deliberate exception is the request-evidence block. Every
21
+ # `record_*_by_default` line is live even when it says `false`, because each one
22
+ # is an answer to a question this installer actually asked, and "we decided not
23
+ # to collect this" is a decision worth reading rather than inferring from a
24
+ # file that does not mention it.
25
+ #
26
+ # A policy can override the request-evidence defaults below, so a high-risk
27
+ # authorization can collect more context without ordinary signup inheriting it.
28
+ #
29
+ # There is deliberately no `maximum_evidence`, `full_evidence`, `legal_proof`,
30
+ # or regulation-named mode setting. No runtime flag can make a legal
31
+ # determination for you, and an option that turns on a category of personal data
32
+ # as a side effect of something else is exactly what this gem exists not to do.
33
+ <%- if recipe -%>
34
+ #
35
+ # This file was scaffolded with `--request-evidence-recipe=<%= recipe %>`. The
36
+ # recipe expanded into the individual settings below and then stopped existing:
37
+ # it is a generator convenience, not a runtime mode, and nothing reads it again.
38
+ <%- end -%>
39
+ #
40
+ # Documents, policies, and retention classes live in config/clickwrap.rb.
41
+ # ==============================================================================
42
+
43
+ Clickwrap.configure do |config|
44
+ # --- Identity ---------------------------------------------------------------
45
+ #
46
+ # Who can act, how a controller finds them, and how they are referenced in
47
+ # evidence. This is the mapping every receipt depends on, so it is the one the
48
+ # installer refuses to guess at when it is ambiguous.
49
+
50
+ <%- if actor_class_name && !default_identity_settings? -%>
51
+ # DECISION — detected during install. Clickwrap checks every recorded actor
52
+ # against this class, so a wrong value here is refused at capture rather than
53
+ # attributing evidence to the wrong kind of record for years.
54
+ config.actor_class_name = "<%= actor_class_name %>"
55
+
56
+ # The controller method that returns the current actor. Rails 8's generated
57
+ # Authentication concern exposes `Current.user` rather than a `current_user`
58
+ # helper — if that is your stack, either define `current_user` or point this
59
+ # at the method you actually have.
60
+ config.current_actor_method_name = :<%= current_actor_method_name %>
61
+ <%- elsif actor_class_name -%>
62
+ # Detected during install, and the same as the gem's defaults — so there is
63
+ # nothing to override. Clickwrap checks every recorded actor against this
64
+ # class, so if your actor model is not <%= actor_class_name %>, set it here:
65
+ #
66
+ # config.actor_class_name = "<%= actor_class_name %>"
67
+ # config.current_actor_method_name = :<%= current_actor_method_name %>
68
+ <%- else -%>
69
+ # Clickwrap could not infer your actor class unambiguously, so it did not
70
+ # choose one for you: which record can act is a security-relevant identity
71
+ # mapping, and a wrong guess attributes years of evidence to the wrong kind of
72
+ # record. Uncomment and set both lines.
73
+ #
74
+ <%= actor_class_reason_comment %>
75
+ #
76
+ # config.actor_class_name = "User"
77
+ # config.current_actor_method_name = :current_user
78
+ <%- end -%>
79
+
80
+ # The engine's screens render inside your application: your layout, your
81
+ # authentication callbacks, your locale. They are only as protected as this
82
+ # controller is. Actor, tenant, and subject stay three separate facts and
83
+ # never collapse into one polymorphic id; returning nil for the tenant means
84
+ # "single tenant", which is correct for most applications:
85
+ #
86
+ # config.parent_controller_class_name = "ApplicationController"
87
+ # config.find_current_tenant_with = lambda do |controller|
88
+ # controller.current_organization if controller.respond_to?(:current_organization)
89
+ # end
90
+
91
+ # How an actor is named and snapshotted in evidence. The defaults record a
92
+ # stable reference that outlives row deletion — the model's own
93
+ # `clickwrap_actor_reference`, then a GlobalID, then a `ClassName/id` string —
94
+ # and snapshot NOTHING. A snapshot is permanent, so it should carry fields
95
+ # someone chose rather than every column that existed that day: an email
96
+ # copied in here is still in the receipt after the account is deleted.
97
+ # Override only against a reviewed identifier scheme or a reviewed field list:
98
+ #
99
+ # config.identify_actor_with = ->(actor) { "account/#{actor.public_id}" }
100
+ # config.snapshot_actor_with = ->(_actor) { {} }
101
+
102
+ # Authentication is recorded as context, never as an identity claim: it says
103
+ # which application-supplied authentication state was in force, and nothing
104
+ # more. The default asks the controller for the current actor and records
105
+ # `{ method: :authenticated_session }` only when it gets one, so a signup form
106
+ # — where nobody is signed in yet — describes nothing rather than claiming a
107
+ # session that did not exist. Override it to ADD reviewed context:
108
+ #
109
+ # config.describe_authentication_with = lambda do |_controller|
110
+ # session = Current.session
111
+ # next {} unless session
112
+ #
113
+ # { method: session.authentication_method, authenticated_at: session.created_at }
114
+ # end
115
+
116
+ # --- Documents and policies -------------------------------------------------
117
+
118
+ <%- if renders_documents_through_markdown_rails? -%>
119
+ # DECISION — your legal pages are Markdown files this application already
120
+ # renders through markdown-rails, so Clickwrap renders them through that same
121
+ # registered renderer: the snapshot people accept is byte-identical to the
122
+ # page they read, by construction, and sanitizing stays in the pipeline those
123
+ # pages already trust rather than a second pass behind the digest.
124
+ config.document_renderer = :markdown_rails
125
+
126
+ <%- end -%>
127
+ # Where published bytes live, how they become the representation people are
128
+ # offered, and where declarations are read from. `:database` needs nothing
129
+ # else to stay readable in ten years; the default renderer keeps the faithful
130
+ # escaped-text representation, `:markdown` renders through whichever Markdown
131
+ # library you already bundle, and `:markdown_rails` renders through your
132
+ # application's own registered renderer — which is what keeps a snapshot
133
+ # byte-identical to the public page of the same file. A custom renderer must
134
+ # return the exact bytes it offered, because Clickwrap stores their digest
135
+ # next to the source digest: "this Markdown file existed" and "this rendered
136
+ # representation was offered" stay two separate claims.
137
+ #
138
+ <%- unless renders_documents_through_markdown_rails? -%>
139
+ # config.document_renderer = :markdown_rails
140
+ <%- end -%>
141
+ # config.store_document_contents_in = :database
142
+ # config.document_resolver = nil
143
+ # config.policy_paths = [ "config/clickwrap.rb", "config/clickwrap/*.rb" ]
144
+
145
+ # Publishing rides `db:prepare`, so the deploy step everyone forgets stops
146
+ # existing: by the time the server takes traffic, every declared version has
147
+ # an immutable snapshot. It is idempotent, and a refusal — a reused version
148
+ # label over changed bytes — fails the deploy out loud instead of letting
149
+ # signups fail quietly some hours later. Set it to false to keep publishing a
150
+ # deliberate `bin/rails clickwrap:publish` step of your own. A required legal
151
+ # statement with no translation raises rather than render a raw I18n key
152
+ # beside a control someone is about to press:
153
+ #
154
+ # config.publish_documents_after_database_preparation = true
155
+ # config.raise_on_missing_translation = true
156
+
157
+ # How immutable document links open — never WHERE they point, which is signed
158
+ # into the presentation and refused if this hook tries to return `href:`. It
159
+ # is evaluated in the rendering view, so per-request helpers work:
160
+ #
161
+ # config.document_link_html_options_with = ->(_document) { { target: "_blank", rel: "noopener" } }
162
+
163
+ # Hotwire Native gets a declarative answer covering both halves — the signed
164
+ # href AND the link attributes. Inside a native authentication sheet, a
165
+ # same-host document link is routed by the app itself, which pops the sheet
166
+ # and takes the half-filled form with it; `:external_browser` absolutizes the
167
+ # link against your canonical host so the form is still there when the person
168
+ # comes back. `:same_screen` keeps ordinary same-host links for your own
169
+ # native path configuration to route. `open_in:` also takes a callable, for an
170
+ # app that needs both answers on different screens:
171
+ #
172
+ # config.hotwire_native_document_links = {
173
+ # open_in: :external_browser,
174
+ # canonical_host: "https://www.example.com"
175
+ # }
176
+
177
+ # --- Presentation and integrity ---------------------------------------------
178
+ #
179
+ # A signed presentation manifest binds the exact wording, versions, and call
180
+ # to action of the server-generated offer to the submission that followed. Its
181
+ # validity window is short by design — long enough for a slow reader, short
182
+ # enough to keep the substitution check meaningful — receipts digest with
183
+ # SHA-256, and the higher integrity tiers stay off until you bring an adapter.
184
+ # Each tier claims exactly what it supplies: the baseline detects ordinary
185
+ # mutation of the verified bytes, not someone who controls the database and
186
+ # the application.
187
+ #
188
+ # Chaining links each event to the previous one per tenant, which makes
189
+ # rewriting history detectable. Anchoring offers each committed chain position
190
+ # to an outside publication service, and a receipt upgrades only after the
191
+ # adapter verifies that exact publication. A timestamp adapter preserves
192
+ # exactly the assurance its provider supplies — Clickwrap's own
193
+ # `recorded_at_by_server` is named for what it is: this server's clock.
194
+ #
195
+ # The chaining and attestation tables ship with
196
+ # `rails generate clickwrap:install --with-integrity`.
197
+ #
198
+ # config.presentation_valid_for = 2.hours
199
+ # config.digest_canonical_receipts_with = :sha256
200
+ # config.chain_event_history_with = nil
201
+ # config.anchor_event_history_with = nil
202
+ # config.timestamp_receipts_with = nil
203
+
204
+ # Stamp every event with the code that wrote it, so a receipt can be traced to
205
+ # a deploy. Point these at whatever your deployment actually sets:
206
+ #
207
+ # config.application_version = -> { ENV["RELEASE_SHA"] }
208
+ # config.template_version = -> { nil }
209
+
210
+ # --- Authorization ----------------------------------------------------------
211
+
212
+ # DECISION — actors read their own receipts. The gem's default denies ALL
213
+ # receipt access until you write this check; the installer grants exactly
214
+ # self-service and nothing wider. A foreign id returns not found rather than
215
+ # leaking that it exists.
216
+ #
217
+ # The `present?` check is load-bearing: `receipt.actor` is legitimately nil
218
+ # for imported evidence and after an actor's account is deleted, and a
219
+ # signed-out viewer is nil too — a bare `==` would let nil equal nil and
220
+ # fail open on exactly those receipts.
221
+ config.authorize_receipt_access_with = lambda do |controller, receipt|
222
+ controller.<%= current_actor_method_name %>.present? &&
223
+ controller.<%= current_actor_method_name %> == receipt.actor
224
+ end
225
+
226
+ # Unredacted request evidence — the raw IP address, User-Agent, and
227
+ # IP-geolocation values — stays denied (the default) until you write a real
228
+ # check. Every granted access needs a human-readable reason and appends an
229
+ # access event, so "who read this and why" stays answerable. The shape a real
230
+ # one takes:
231
+ #
232
+ # config.authorize_unredacted_request_evidence_access_with =
233
+ # ->(controller, _receipt, because) { controller.current_user&.security_operator? && because.present? }
234
+
235
+ # --- Request evidence: what is recorded by default --------------------------
236
+ #
237
+ # Clickwrap always records its own event id, the server time, the capture
238
+ # channel, the policy and application version, the configured actor and
239
+ # authentication source, and the HTTP request id when one is available. None
240
+ # of that is personal request evidence.
241
+ #
242
+ # The settings below are, and they are the one block in this file written out
243
+ # in full even when every answer is "no". Each one is a separate line because
244
+ # each one is a separate decision about what to keep about a person, and none
245
+ # of them turns on as a side effect of another.
246
+ #
247
+ # IP addresses and linked online identifiers can be personal data. Keeping
248
+ # them on your own infrastructure does not remove purpose, transparency,
249
+ # minimization, security, or retention duties. Provider-estimated geolocation
250
+ # is network context: not identity, not GPS, not a street address, and not
251
+ # proof that anyone was physically anywhere.
252
+ <%- if records_any_request_evidence? -%>
253
+ #
254
+ # You enabled some of these during install. The generator required an explicit
255
+ # purpose and deletion period before writing this file; review both below. The
256
+ # annex table itself came with `--with-request-evidence`, which this install
257
+ # added for you.
258
+ <%- else -%>
259
+ #
260
+ # Everything here is off, which is the safe default — and the annex table was
261
+ # not created at all. A single policy that genuinely needs a field can still
262
+ # name it (add the table with
263
+ # `rails generate clickwrap:install --with-request-evidence`), without
264
+ # ordinary signup inheriting the collection.
265
+ <%- end -%>
266
+
267
+ config.record_ip_address_by_default = <%= record_ip_addresses? %>
268
+ config.record_browser_user_agent_by_default = <%= record_browser_user_agents? %>
269
+ <%- ip_geolocation_fields.each do |field| -%>
270
+ config.record_ip_geolocation_<%= field %>_by_default = <%= record_ip_geolocation_field?(field) %>
271
+ <%- end -%>
272
+
273
+ # --- Request evidence: why, how long, and how it is protected ---------------
274
+ #
275
+ # An enabled field with a blank purpose or no deletion rule is a configuration
276
+ # error at boot, not a warning. "We turned it on and never decided why" is the
277
+ # failure mode these settings exist to prevent. There is no keep-forever
278
+ # default anywhere in this gem, and deleting a value removes the encrypted
279
+ # annex entry and appends a disposition event — the historical agreement,
280
+ # declaration, or authorization stays intact and verifiable without it.
281
+
282
+ <%- if record_ip_addresses? -%>
283
+ config.reason_for_recording_ip_addresses_by_default =
284
+ <%= reason_for_recording_ip_addresses.inspect %>
285
+ config.delete_recorded_ip_addresses_after = <%= delete_recorded_ip_addresses_after_days %>.days
286
+ <%- end -%>
287
+ <%- if record_browser_user_agents? -%>
288
+ config.reason_for_recording_browser_user_agents_by_default =
289
+ <%= reason_for_recording_browser_user_agents.inspect %>
290
+ config.delete_recorded_browser_user_agents_after = <%= delete_recorded_browser_user_agents_after_days %>.days
291
+ <%- end -%>
292
+ <%- if any_ip_geolocation_field? -%>
293
+ config.reason_for_recording_ip_geolocation_by_default =
294
+ <%= reason_for_recording_ip_geolocation.inspect %>
295
+ config.delete_recorded_ip_geolocation_after = <%= delete_recorded_ip_geolocation_after_days %>.days
296
+ <%- end -%>
297
+ <%- if records_any_request_evidence? -%>
298
+ # A date to look at this configuration again, so an enabled field does not
299
+ # quietly outlive the reason it was turned on. `clickwrap:doctor` reports an
300
+ # enabled field with no review date.
301
+ config.review_default_request_evidence_configuration_on = <%= review_date_literal %>
302
+ <%- else -%>
303
+ # Nothing above is enabled, so there is nothing to give a purpose or a
304
+ # deletion period to. Enabling a field is never one line — the gem refuses an
305
+ # enabled field with no recorded purpose and no deletion rule — and it wants a
306
+ # review date so the decision gets looked at again by a person instead of
307
+ # outliving its reason:
308
+ #
309
+ # config.reason_for_recording_ip_addresses_by_default = "Investigate disputed submissions"
310
+ # config.delete_recorded_ip_addresses_after = 90.days
311
+ # config.review_default_request_evidence_configuration_on = Date.new(<%= Date.today.year + 1 %>, 1, 1)
312
+ <%- end -%>
313
+
314
+ # Your own reference to the assessment or basis you recorded elsewhere, if
315
+ # your jurisdiction and policy call for one. Clickwrap stores the string in
316
+ # the privacy inventory and the receipt; it does not choose a lawful basis,
317
+ # and it cannot tell you whether the one you chose is the right one:
318
+ #
319
+ # config.legal_basis_reference_for_recording_ip_addresses_by_default = nil
320
+ # config.legal_basis_reference_for_recording_browser_user_agents_by_default = nil
321
+ # config.legal_basis_reference_for_recording_ip_geolocation_by_default = nil
322
+
323
+ # Recorded values are encrypted at rest with Active Record encryption.
324
+ # Turning one of these off puts the raw value in plain text in your database,
325
+ # and therefore in every ordinary backup and database dump. It is allowed,
326
+ # because some applications have a reviewed reason, but it is never a quiet
327
+ # one-character change: Clickwrap refuses `false` until you have said why in
328
+ # `config.deliberately_store_request_evidence_unencrypted!(because: "…")`.
329
+ #
330
+ # config.encrypt_recorded_ip_addresses = true
331
+ # config.encrypt_recorded_browser_user_agents = true
332
+ # config.encrypt_recorded_ip_geolocation = true
333
+
334
+ # Server-observed, always. The browser cannot submit or replace these values,
335
+ # and a client-reported address is labeled as such rather than silently
336
+ # standing in for one Clickwrap saw.
337
+ #
338
+ # `request.remote_ip` is only as trustworthy as your proxy configuration. If
339
+ # you record IP addresses, verify and TEST `config.action_dispatch` trusted
340
+ # proxies behind your load balancer or CDN — otherwise you are recording a
341
+ # header a client controls:
342
+ # https://api.rubyonrails.org/classes/ActionDispatch/RemoteIp.html
343
+ #
344
+ # config.read_ip_address_from_http_request_with = ->(http_request) { http_request.remote_ip }
345
+ # config.read_browser_user_agent_from_http_request_with = ->(http_request) { http_request.user_agent }
346
+
347
+ <%- if trusted_proxy_configuration_digest -%>
348
+ # DECISION — the reviewed trusted-proxy digest you supplied to the installer.
349
+ # It does not make the rules or the network path correct; it records which
350
+ # rules were in force beside every address recorded under them.
351
+ config.trusted_proxy_configuration_digest =
352
+ <%= trusted_proxy_configuration_digest.inspect %>
353
+ <%- elsif records_ip_derived_request_evidence? -%>
354
+ # DECISION — this install records IP-derived evidence, so the digest is
355
+ # derived from the EFFECTIVE Rails rules (`config.action_dispatch.trusted_proxies`,
356
+ # or Rails' own defaults) rather than from a prose description of them. It
357
+ # does not make the rules correct; it records which rules were in force.
358
+ config.trusted_proxy_configuration_digest =
359
+ Clickwrap.trusted_proxy_configuration_digest_for_rails_application
360
+ <%- else -%>
361
+ # A digest of the effective trusted-proxy rules, stored beside any recorded
362
+ # address. Nothing here records one, so there is nothing to stamp:
363
+ #
364
+ # config.trusted_proxy_configuration_digest =
365
+ # Clickwrap.trusted_proxy_configuration_digest_for_rails_application
366
+ <%- end -%>
367
+
368
+ <%- if any_ip_geolocation_field? -%>
369
+ # DECISION — you enabled IP-geolocation fields and explicitly selected this
370
+ # resolver. Make its gem and data source available in every environment:
371
+ # Clickwrap refuses to boot with fields it has nothing to resolve, rather than
372
+ # recording blanks that later read as "no result".
373
+ #
374
+ # Only the fields authorized above are stored, never the whole result object,
375
+ # and every stored value keeps the provider, source, estimated state,
376
+ # resolution time, and any accuracy or database provenance the resolver
377
+ # supplies. A policy cannot keep the coordinates and drop the uncertainty
378
+ # needed to read them.
379
+ config.ip_geolocation_resolver = <%= ip_geolocation_resolver_class_name %>.new
380
+ <%- else -%>
381
+ # Required before any IP-geolocation field can be enabled, here or in a
382
+ # policy. The official optional resolver requires trackdown >= 0.4 so the
383
+ # provider that actually answered and per-request source trust are preserved:
384
+ #
385
+ # bundle add trackdown --version ">= 0.4"
386
+ # config.ip_geolocation_resolver = Clickwrap::IpGeolocation::TrackdownResolver.new
387
+ <%- end -%>
388
+
389
+ # Whether an unavailable resolver blocks capture or produces an `unavailable`
390
+ # state in the receipt. Blocking means a provider outage stops people from
391
+ # signing up; not blocking means some receipts honestly say the lookup failed.
392
+ # Receipts keep `not_configured`, `unavailable`, `recorded`, and `deleted`
393
+ # apart, so "we chose not to collect it" never blurs into "collection failed":
394
+ #
395
+ # config.fail_capture_when_ip_geolocation_is_unavailable = false
396
+
397
+ # --- Hooks ------------------------------------------------------------------
398
+ #
399
+ # These run only after the required evidence and the domain state have
400
+ # committed. A failure here is reported and can never undo the committed
401
+ # action — and a hook can never stand in for one either.
402
+ #
403
+ # config.after_event_is_committed = lambda do |event|
404
+ # Marketing::StopProcessingJob.perform_later(event.actor_id) if event.consent_was_withdrawn?
405
+ # end
406
+
407
+ # DECISION — after-commit hook failures land in your error reporter instead of
408
+ # vanishing. The gem's default reports nothing, because it has no reporter to
409
+ # assume.
410
+ config.report_after_commit_failure_with = ->(error, _event) { Rails.error.report(error) }
411
+
412
+ # --- Development aids -------------------------------------------------------
413
+ #
414
+ # In development and test, every render is scanned for the mistakes a form can
415
+ # make silently: a preselected consent control, a consent sentence carrying
416
+ # two purposes, a document link below the submit button, a missing
417
+ # presentation token. Findings go to the log and never raise — a lint finding
418
+ # is a thing to look at, not a reason to stop a page from rendering — and it
419
+ # is off in production, because a production request has no business scanning
420
+ # its own HTML on the way out. `nil` means "decide from the environment":
421
+ #
422
+ # config.lint_presentations = nil
423
+
424
+ # --- Retention calculations -------------------------------------------------
425
+ #
426
+ # A duration alone cannot express every schedule, so a retention class can
427
+ # name a host event instead ("the later of five years after capture and three
428
+ # years after the subject was closed") and register the calculation here.
429
+ # Returning nil is a legitimate answer: it means the triggering event has not
430
+ # happened yet, so the record is reported as unresolved rather than given an
431
+ # invented date.
432
+ #
433
+ # config.calculate_retention_time_for :regulated_evidence_retention_ends do |event|
434
+ # [
435
+ # event.recorded_at_by_server + 5.years,
436
+ # event.subject_liquidated_at&.+(3.years)
437
+ # ].compact.max
438
+ # end
439
+ end
@@ -0,0 +1,12 @@
1
+ class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
2
+ # Which exact clickwrap capture authorized each row of <%= table_name %>:
3
+ # one column, pointing at a verifiable receipt. Nullable because rows that
4
+ # predate the gem have no event; ULID string to match clickwrap_events'
5
+ # primary key; no foreign key on purpose — evidence and domain rows keep
6
+ # independent retention schedules, and neither table may block the other's
7
+ # disposition.
8
+ def change
9
+ add_column :<%= table_name %>, :clickwrap_event_id, :string, limit: 26
10
+ add_index :<%= table_name %>, :clickwrap_event_id
11
+ end
12
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ # ==============================================================================
4
+ # The :<%= policy_key %> policy
5
+ # ==============================================================================
6
+ #
7
+ # A policy is a server-owned offer: exactly what the server will generate, and
8
+ # exactly what it will accept back. It compiles at boot, so a mistake here is a
9
+ # startup failure with an explanation rather than a surprise during a signup.
10
+ #
11
+ # Render it with:
12
+ #
13
+ # <%%= form.clickwrap :<%= policy_key %>, submit: "Your call to action" %>
14
+ #
15
+ # Capture it with `Clickwrap.capture!`, or with `Clickwrap.capture_and!` when a
16
+ # domain action must commit in the same transaction as its evidence.
17
+ # ==============================================================================
18
+
19
+ Clickwrap.policy :<%= policy_key %> do
20
+ # The verb controls the lifecycle, so the generator required you to choose it
21
+ # explicitly. The statement is the exact first-person sentence stored in the
22
+ # receipt; review both as product/legal copy, not as incidental labels.
23
+ <%= policy_verb %> :<%= statement_key %>,
24
+ <% statement_options.each_with_index do |option, index| -%>
25
+ <%= option %><%= "," unless index == statement_options.length - 1 %>
26
+ <% end -%>
27
+
28
+ # This class must already exist in a Clickwrap.retention declaration. The
29
+ # generator names it; Clickwrap never invents or silently defaults a period.
30
+ retain_with :<%= retention_class_key %>
31
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+
5
+ # A policy is executable meaning, so it is worth testing like one. These are the
6
+ # four questions worth asking about any policy. The first is complete because
7
+ # the generator knows the exact inputs you gave it; the remaining application
8
+ # behavior stays skipped until you connect a real actor and flow.
9
+ #
10
+ # Add `include Clickwrap::TestHelpers` to your ActiveSupport::TestCase (or keep
11
+ # it here) to get `capture_clickwrap` and the assertions below. They create
12
+ # real, internally consistent evidence without your tests knowing table names.
13
+ class <%= test_class_name %> < ActiveSupport::TestCase
14
+ include Clickwrap::TestHelpers
15
+
16
+ # 1. It compiles, and it says what you think it says.
17
+ #
18
+ # This is the cheap test that catches a renamed document, a deleted statement,
19
+ # or a verb someone changed without meaning to.
20
+ test "the policy is defined with the statements it is supposed to have" do
21
+ policy = Clickwrap.policy!(:<%= policy_key %>)
22
+
23
+ assert_equal ["<%= statement_key %>"], policy.statements.map(&:key)
24
+ assert_equal ["<%= evidence_kind %>"], policy.statements.map(&:kind)
25
+ end
26
+
27
+ # 2. A complete submission produces the evidence you expect.
28
+ test "capturing it records the acts and verifies" do
29
+ skip "connect this test to your actor fixture or factory and submit the real answer"
30
+ end
31
+
32
+ # 3. An incomplete submission records NOTHING.
33
+ #
34
+ # The interesting half of any evidence policy is what it refuses. A missing
35
+ # required answer must not produce a partial event that later reads as assent.
36
+ test "it refuses an incomplete submission" do
37
+ skip "connect this test to your actor fixture or factory"
38
+ end
39
+
40
+ # 4. The evidence and the protected action commit together, or not at all.
41
+ #
42
+ # Only relevant if this policy guards a domain action through `capture_and!`.
43
+ # Fault injection is the point: if the event write fails, the action must roll
44
+ # back, and a failed evidence write must never leave a usable domain record
45
+ # behind.
46
+ test "a failed evidence write rolls the protected action back" do
47
+ # Clickwrap::Testing.fail_next_event_write do
48
+ # assert_raises(Clickwrap::EventWriteFailed) do
49
+ # # ...the call that captures evidence and performs the action...
50
+ # end
51
+ # end
52
+ #
53
+ # assert_no_clickwrap_event :<%= policy_key %>
54
+ skip "fill this in if this policy protects a domain action"
55
+ end
56
+ end
@@ -0,0 +1,58 @@
1
+ ---
2
+ title: Privacy Notice
3
+ last_updated: <%= Date.today.iso8601 %>
4
+ ---
5
+
6
+ # PLACEHOLDER — this is not your Privacy Notice
7
+
8
+ **Replace this entire file before you present it to anyone.**
9
+
10
+ The `clickwrap` gem generated this placeholder so that `config/clickwrap.rb` has
11
+ a file to point at and `bin/rails clickwrap:publish` has bytes to digest. It is
12
+ not a privacy notice, and it describes no actual processing.
13
+
14
+ Clickwrap deliberately never writes legal text. Only you know what your
15
+ application collects, why, on what basis, who it goes to, and for how long — and
16
+ a notice the gem invented would describe an application that does not exist.
17
+
18
+ Your notice has to describe what your application actually does, which now
19
+ includes whatever `clickwrap` records on your behalf. By default that is the
20
+ evidence event itself: an identifier, your server's time, the capture channel,
21
+ the policy and application version, the configured actor reference and
22
+ authentication context, and the HTTP request id when one is available.
23
+
24
+ If you enabled anything in the request-evidence section of
25
+ `config/initializers/clickwrap.rb` — the IP address, the browser User-Agent, or
26
+ any provider-estimated IP-geolocation field — those are personal data too, and
27
+ your notice is where you tell people about them. `bin/rails
28
+ clickwrap:privacy:inventory` lists exactly what your configuration records,
29
+ for which stated purpose, under which retention rule. It reports your
30
+ configuration; it does not judge it.
31
+
32
+ ## What to do now
33
+
34
+ 1. Replace this file with your own reviewed Privacy Notice, keeping the front
35
+ matter at the top of it.
36
+ 2. Keep `last_updated:` accurate. That line is the version label: the
37
+ declaration in `config/clickwrap.rb` carries no `version:` and reads the
38
+ label from here, so this file names its own version and there is no second
39
+ copy of the label to drift. Use `clickwrap_version:` — it wins over
40
+ `last_updated:` — when a same-day correction changes the bytes and the date
41
+ readers see has to stay put.
42
+ 3. Run `bin/rails clickwrap:publish` to freeze an immutable snapshot. Deploys do
43
+ this for you: publishing rides `db:prepare`.
44
+ 4. Bump the label and publish again whenever the text changes, so a receipt
45
+ keeps binding the notice version in the accepted server offer.
46
+
47
+ A Markdown `config.document_renderer` strips the front-matter block from the
48
+ representation people are shown. The source digest covers this file's exact
49
+ bytes either way, front matter included.
50
+
51
+ Note how this document is used in `config/clickwrap.rb`: it is `acknowledge`,
52
+ not `agree_to` and not `consent_to`. A notice is information people are entitled
53
+ to receive, not a contract term they assent to or permission they grant.
54
+ Anything you genuinely need consent for gets its own separate, unselected,
55
+ withdrawable consent statement.
56
+
57
+ If this text ever reaches a real person, something went wrong: check that this
58
+ file was replaced, and that `clickwrap:publish` ran against the replacement.
@@ -0,0 +1,49 @@
1
+ ---
2
+ title: Terms of Service
3
+ last_updated: <%= Date.today.iso8601 %>
4
+ ---
5
+
6
+ # PLACEHOLDER — this is not your Terms of Service
7
+
8
+ **Replace this entire file before you present it to anyone.**
9
+
10
+ The `clickwrap` gem generated this placeholder so that `config/clickwrap.rb` has
11
+ a file to point at and `bin/rails clickwrap:publish` has bytes to digest. It is
12
+ not terms. It is not a draft of terms. It is not a starting point for terms.
13
+
14
+ Clickwrap deliberately never writes legal text. It has no idea who you are, what
15
+ you sell, where you operate, who your users are, or which rules apply to any of
16
+ that — and text that merely *looked* plausible would be worse than this file,
17
+ because someone would ship it.
18
+
19
+ What the gem does own is the mechanics around whatever you put here: the exact
20
+ bytes and their digest, the version and locale, the wording and call to action
21
+ in the server-generated offer, the submitted answer, the time your server
22
+ recorded it, and a receipt that can be reproduced and verified years later.
23
+ What the words say, whether they are fair, whether they are complete, and
24
+ whether they do what you need them to do is yours and your counsel's.
25
+
26
+ ## What to do now
27
+
28
+ 1. Replace this file with your own reviewed Terms of Service, keeping the front
29
+ matter at the top of it.
30
+ 2. Keep `last_updated:` accurate. That line is the version label: the
31
+ declaration in `config/clickwrap.rb` carries no `version:` and reads the
32
+ label from here, so the file that holds the words is also the file that names
33
+ its version and there is no second copy of the label to drift. A label is
34
+ yours to choose; reusing one for different bytes is refused rather than
35
+ accepted. When a same-day correction has to change the bytes without moving
36
+ the date readers see, add `clickwrap_version:` — it wins over
37
+ `last_updated:` — and leave the date alone.
38
+ 3. Run `bin/rails clickwrap:publish` to freeze an immutable snapshot. Deploys do
39
+ this for you: publishing rides `db:prepare`.
40
+ 4. Bump the label and publish again whenever the text changes. Published bytes
41
+ are never edited in place — that is what lets a receipt from three years ago
42
+ reproduce the exact document version its accepted server offer bound.
43
+
44
+ A Markdown `config.document_renderer` strips the front-matter block from the
45
+ representation people are shown. The source digest covers this file's exact
46
+ bytes either way, front matter included.
47
+
48
+ If this text ever reaches a real person, something went wrong: check that this
49
+ file was replaced, and that `clickwrap:publish` ran against the replacement.