clickwrap 0.0.0 → 0.1.1

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 +650 -0
  4. data/README.md +830 -1204
  5. data/SECURITY.md +33 -0
  6. data/app/assets/stylesheets/clickwrap.css +245 -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 +154 -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 +187 -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 +729 -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,299 @@
1
+ # Consent, kinds, and lifecycle
2
+
3
+ Not every checkbox is consent, and not every timestamp is a signature. Clickwrap gives each act
4
+ the lifecycle it actually needs, which is why there are six verbs rather than one
5
+ `accept` method with options.
6
+
7
+ **This taxonomy is product design, not statutory vocabulary.** Naming an act `agreement` or
8
+ `consent` does not determine its legal effect anywhere. The verb decides which lifecycle
9
+ Clickwrap enforces and what the receipt says; your application and its counsel decide whether
10
+ that is the right characterization, what the words mean, and what basis the processing rests
11
+ on. Choosing between the verbs is the one piece of thinking this gem deliberately does not do
12
+ for you.
13
+
14
+ ---
15
+
16
+ ## The six kinds
17
+
18
+ | Verb | Kind | Means | First action | Every action it can ever record |
19
+ |---|---|---|---|---|
20
+ | `agree_to` | `agreement` | Assent to contractual terms | `agreed` | `agreed`, `superseded` |
21
+ | `acknowledge` | `acknowledgment` | Affirmative receipt or awareness of a notice or risk | `acknowledged` | `acknowledged`, `superseded`, `expired` |
22
+ | `consent_to` | `consent` | Purpose-specific permission, where the host has decided consent is the right basis | `granted` | `granted`, `declined`, `withdrawn`, `renewed`, `scope_changed` |
23
+ | `declare` | `declaration` | A factual statement made by the actor | `declared` | `declared`, `corrected`, `superseded`, `expired` |
24
+ | `attest` | `attestation` | An operational fact affirmed by an authorized actor, usually an operator | `attested` | `attested`, `corrected`, `superseded` |
25
+ | `authorize` | `authorization` | Narrow permission bound to one protected action | `authorized` | `authorized`, `consumed`, `expired`, `revoked` |
26
+
27
+ The action lists are not suggestions. `EventStatement` validates that an action belongs to its
28
+ kind and refuses the row otherwise, with a message naming what that kind can record. An
29
+ agreement cannot be `withdrawn`; a declaration cannot be `consumed`.
30
+
31
+ ### Which capability each kind has, and why
32
+
33
+ | Capability | Kinds that have it | Why the others do not |
34
+ |---|---|---|
35
+ | **Withdrawable** | `consent` only | Withdrawing future consent must never rewrite a historical agreement or a factual declaration. Those are statements about what happened; only a permission can be taken back |
36
+ | **Expirable** (`valid_for:`) | `acknowledgment`, `consent`, `declaration`, `authorization` | An agreement does not lapse on a timer — it is superseded by a new version. An attestation is an operational fact that stays true about the moment it described |
37
+ | **Correctable** (`correct_declaration!`) | `declaration`, `attestation` | A correction says the fact is different now, not that the original statement was false when it was made. Only statements of fact have that shape |
38
+ | **One-time** (`one_time: true`) | `authorization` only | Scoping to a single protected action is what makes an authorization an authorization |
39
+
40
+ The policy compiler rejects incoherent combinations at boot rather than at 3am: a one-time
41
+ authorization that is also indefinite, a consent with no withdrawal path, a subject-bound
42
+ statement with no subject fingerprint, a request-evidence field with no purpose or retention
43
+ decision.
44
+
45
+ ---
46
+
47
+ ## States and how they change
48
+
49
+ Current state is a **projection** derived from retained event payloads.
50
+ `CurrentState.rebuild_for!(actor_reference:)` replays those payloads while they still contain
51
+ the affected statement identity. A reviewed core disposition deliberately removes that personal
52
+ identity; after that point the method refuses to delete an existing projection that depends on
53
+ the disposed root. Do not drop the projection after core disposition and expect removed identity
54
+ facts to be reconstructed. Keeping it downstream of retained evidence is what lets it be mutable
55
+ and fast without treating the projection itself as the evidence record.
56
+
57
+ | State | Satisfies a requirement | How a statement gets there |
58
+ |---|---|---|
59
+ | `active` | Yes | A `capture`, `renewal`, or `scope_change` event recorded the initial action |
60
+ | `declined` | No | The person answered no to a statement with explicit `choices:` |
61
+ | `withdrawn` | No | `Clickwrap.withdraw!` appended a `withdrawal` event |
62
+ | `expired` | No | The validity period ran out. Evaluated live against the clock, so verification does not depend on a job having run |
63
+ | `superseded` | No | A newer act for the same identity replaced it, or `Clickwrap.supersede!` appended a `supersession` event |
64
+ | `consumed` | No | A one-time authorization was used by the transaction it authorized |
65
+ | `revoked` | No | `Clickwrap.revoke!` appended a `revocation` event |
66
+ | `corrected` | No | `Clickwrap.correct_declaration!` appended a `correction` event; the correction becomes the active statement |
67
+ | `exempted` | Only `exempted_from?` | `Clickwrap.exempt!` recorded that no human acted |
68
+
69
+ ### The transition table
70
+
71
+ | From | Event type | Action recorded | To | What is preserved |
72
+ |---|---|---|---|---|
73
+ | — | `capture` | the kind's initial action | `active` | Everything: the exact assertion, documents, manifest, answers |
74
+ | `active` | `capture` (newer act, same identity) | the kind's initial action | previous becomes `superseded` | The earlier event, untouched. The projection moves on; the event does not |
75
+ | `active` | `withdrawal` | `withdrawn` | `withdrawn` | The original grant. Withdrawal appends; it never deletes or mutates |
76
+ | `active` | `renewal` | `renewed` | `active`, with a **fresh** validity period | The old expiry does not quietly survive: `effective_at` moves, `withdrawn_at` clears, `expires_at` is recomputed |
77
+ | `active` | `scope_change` | `scope_changed` | `active` | The previous scope, as its own event |
78
+ | `active` | `correction` | `corrected` | `corrected`, and the correction is `active` | The original statement, which was not false when it was made |
79
+ | `active` | `supersession` | `superseded` | `superseded` | Everything the superseded act recorded |
80
+ | `active` | `consumption` | `consumed` | `consumed` | The binding between this evidence and the exact transaction it authorized |
81
+ | `active` | `revocation` | `revoked` | `revoked` | The original authorization |
82
+ | `active` | (validity elapses) | `expired` | `expired` | The original act. Expiry does not imply the statement was ever untrue |
83
+ | — | `exemption` | the kind's initial action | `exempted` | Who or what created it, and why. Never satisfies a human-action predicate |
84
+
85
+ Only four event types count as a human action recorded through a Clickwrap presentation:
86
+ `capture`, `correction`, `renewal`, `scope_change`. Only those can satisfy `agreed_to?`,
87
+ `consented_to?`, `declared?`, and the rest. An `exemption`, an `imported_legacy` event, or an
88
+ `external_receipt` never does — there is no "missing checkbox means system account" inference
89
+ anywhere in this gem.
90
+
91
+ The remaining event types record things that happened *to* evidence rather than acts by a
92
+ person: `disposition`, `legal_hold_placed`, `legal_hold_released`, `receipt_access`,
93
+ `provider_outcome`.
94
+
95
+ ---
96
+
97
+ ## Consent specifically
98
+
99
+ Consent is the only withdrawable kind, and Clickwrap structurally requires an accessible
100
+ withdrawal path before it will compile a consent policy. It does not decide whether consent is
101
+ the correct basis for your processing.
102
+
103
+ ```ruby
104
+ Clickwrap.policy :marketing_preferences do
105
+ consent_to :product_updates,
106
+ document: :marketing_notice,
107
+ statement: "I agree to receive product update emails.",
108
+ optional: true,
109
+ withdrawal_path: "/settings/privacy"
110
+
111
+ consent_to :partner_offers,
112
+ document: :marketing_notice,
113
+ statement: "I agree to receive offers from selected partners.",
114
+ optional: true,
115
+ withdrawal_path: "/settings/privacy"
116
+
117
+ retain_with :marketing_consent_evidence
118
+ end
119
+ ```
120
+
121
+ Two separate purposes, two separate controls, two separate lifecycles. Bundling them into one
122
+ sentence produces a consent record whose meaning nobody can reconstruct later — the development
123
+ linter flags exactly that as `consent_statement_bundles_purposes` when it sees "and", "and/or",
124
+ "as well as", or "plus" in a consent assertion.
125
+
126
+ **An unselected optional control creates no grant.** The receipt can show the option was offered
127
+ and not taken, but it does not call silence an affirmative refusal — those are different facts
128
+ and only one of them happened. When you actually need a recorded decision rather than the
129
+ absence of one, ask for it explicitly:
130
+
131
+ ```ruby
132
+ consent_to :research_contact,
133
+ choices: { yes: :grant, no: :decline },
134
+ require_an_explicit_choice: true,
135
+ withdrawal_path: "/settings/privacy"
136
+ ```
137
+
138
+ Both controls start unselected, and the answer becomes `granted` or `declined` — a real
139
+ recorded decision either way.
140
+
141
+ Withdrawal appends:
142
+
143
+ ```ruby
144
+ Clickwrap.withdraw!(
145
+ :product_updates,
146
+ actor: current_user,
147
+ http_request: request,
148
+ because: "The user withdrew this purpose in privacy settings"
149
+ )
150
+ ```
151
+
152
+ Stopping the downstream processing is your job, and it happens after the transaction commits so
153
+ that the record of the withdrawal never depends on a job backend being up:
154
+
155
+ ```ruby
156
+ config.after_event_is_committed = lambda do |event|
157
+ if event.event_type == "withdrawal"
158
+ Marketing::StopProcessingJob.perform_later(event.actor_reference)
159
+ end
160
+ end
161
+ ```
162
+
163
+ The hook is an observer, never authorization. A failure here is reported through
164
+ `report_after_commit_failure_with` and swallowed, because the withdrawal event has already
165
+ committed and nothing a job backend does may undo it.
166
+
167
+ ---
168
+
169
+ ## Choosing a verb: Terms, privacy notice, marketing
170
+
171
+ This is the case that could plausibly be any of the three, and getting it wrong is the most
172
+ common way an evidence record ends up saying something that did not happen. One signup page,
173
+ three different acts:
174
+
175
+ ```ruby
176
+ Clickwrap.policy :signup do
177
+ agree_to :terms
178
+ acknowledge :privacy_notice
179
+ consent_to :product_updates,
180
+ document: :marketing_notice,
181
+ optional: true,
182
+ withdrawal_path: "/settings/privacy"
183
+
184
+ retain_with :ordinary_agreement_evidence
185
+ end
186
+ ```
187
+
188
+ **Terms of service → `agree_to`.** The Terms are a contract you are asking the person to be
189
+ bound by. The act is assent. It does not expire on a timer; it is superseded when a new version
190
+ publishes and you decide the change is material enough to require reacceptance. It is not
191
+ withdrawable — you do not "withdraw" from terms, you stop using the service or the contract
192
+ ends.
193
+
194
+ **Privacy notice → `acknowledge`.** A privacy notice is information the person is *entitled to*
195
+ under transparency duties ([GDPR Article 13](https://eur-lex.europa.eu/eli/reg/2016/679/art_13/oj/eng))
196
+ *(law)*. It is not something they agree to, and it is not permission for anything. Recording it
197
+ as an agreement would claim they assented to processing; recording it as consent would be
198
+ worse, because it would claim they granted a basis they were never asked for.
199
+
200
+ The Spanish supervisory authority puts the distinction plainly: a checkbox may serve to prove
201
+ that the privacy information was provided, while purposes that genuinely rest on consent should
202
+ be collected separately
203
+ ([AEPD FAQ 02.48](https://www.aepd.es/preguntas-frecuentes/2-tus-obligaciones-como-responsable-del-tratamiento/6-el-deber-de-informacion/FAQ-0248-sobre-si-el-usuario-tiene-que-dar-consentimiento-a-clausula-de-privacidad))
204
+ *(regulator guidance)*. `acknowledge` is the verb that records the first thing without claiming
205
+ the second.
206
+
207
+ **Marketing email → `consent_to`, optional, with a withdrawal path.** This is a specific
208
+ purpose, separable from the service, and it is the one thing on the page that is genuinely a
209
+ permission. It is optional, so leaving it alone grants nothing. It is withdrawable, and the
210
+ withdrawal path is required at compile time rather than promised in a policy document.
211
+
212
+ Consent is one lawful basis among several, and where it is used, GDPR requires it to be
213
+ demonstrable, distinguishable from other matters, and withdrawable
214
+ ([Article 6](https://eur-lex.europa.eu/eli/reg/2016/679/art_6/oj/eng),
215
+ [Article 7](https://eur-lex.europa.eu/eli/reg/2016/679/art_7/oj/eng)) *(law)*. Clickwrap gives
216
+ you the mechanics for all three. It does not choose the basis, and no verb here should be read
217
+ as advice that you have chosen correctly.
218
+
219
+ ### A quick way to decide
220
+
221
+ Ask, in order:
222
+
223
+ 1. **Is this a permission the person can take back without ending the relationship?** If yes,
224
+ `consent_to`, with a real withdrawal path. If withdrawing it would just mean cancelling the
225
+ account, it is not consent.
226
+ 2. **Is this information they are entitled to receive, rather than something they grant?** If
227
+ yes, `acknowledge`.
228
+ 3. **Is this assent to terms that bind them?** If yes, `agree_to`.
229
+ 4. **Is it a statement of fact about themselves or the world?** `declare` if the person is
230
+ asserting it, `attest` if an authorized operator is.
231
+ 5. **Is it permission for one specific, consequential operation?** `authorize`, with
232
+ `one_time: true` and a validity window.
233
+
234
+ If a single control would need two of these answers, it is two controls.
235
+
236
+ ### One control, several acts — and the line consent never crosses
237
+
238
+ The default rendering composes an ordinary `agree_to` and `acknowledge` into one checkbox
239
+ carrying one sentence, and the receipt still records them as two acts with two lifecycles. That
240
+ is not a contradiction of the rule above: the person is not being asked to answer them
241
+ differently, they are being asked to do the two ordinary things a signup asks for, and the
242
+ manifest signs which statements the one control covered.
243
+
244
+ A `consent_to` never joins that line, under any configuration. It cannot be optional inside a
245
+ required sentence without silently becoming required; it cannot be separately withdrawable if
246
+ withdrawing it would mean withdrawing assent to the Terms; and it cannot be distinguishable from
247
+ other matters if it is one clause in a sentence about other matters. Only `agreement` and
248
+ `acknowledgment` are composable at all, so this is not a default somebody can configure their
249
+ way past — it is the shape of the code.
250
+
251
+ ---
252
+
253
+ ## Reading current state
254
+
255
+ ```ruby
256
+ user.clickwraps.current_for?(:signup)
257
+ user.clickwraps.required_for?(:current_terms)
258
+ user.clickwraps.agreed_to?(:terms)
259
+ user.clickwraps.acknowledged?(:privacy_notice)
260
+ user.clickwraps.consented_to?(:product_updates)
261
+ user.clickwraps.declared?(:independent_contractor, subject: scheme)
262
+ user.clickwraps.attested?(:bank_accepted_transfer)
263
+ user.clickwraps.authorized?(:withdrawal, subject: withdrawal)
264
+ user.clickwraps.exempted_from?(:signup)
265
+ ```
266
+
267
+ Every predicate has a structured form for when "no" needs an explanation:
268
+
269
+ ```ruby
270
+ result = Clickwrap.verify(:withdrawal_authorization, actor: user, subject: withdrawal)
271
+
272
+ result.success? # => false
273
+ result.error # => :declaration_expired
274
+ result.message # localized human explanation
275
+ result.event_id
276
+ result.details # stable machine-readable facts, no surprise personal data
277
+ ```
278
+
279
+ The convention is consistent: predicates answer booleans, `verify` returns a result, and bang
280
+ methods (`Clickwrap.require!`) raise a typed error carrying that same result. Applications never
281
+ need to parse an English message to make an authorization decision. The failure symbols are a
282
+ fixed vocabulary — `declaration_expired`, `consent_withdrawn`, `authorization_consumed`,
283
+ `superseded`, `wrong_subject`, `subject_fingerprint_mismatch`, `replay_rejected`, and the rest —
284
+ and like every other stable string in the gem, that list is added to, never renamed or
285
+ repurposed.
286
+
287
+ ---
288
+
289
+ ## Sources
290
+
291
+ | Source | Class |
292
+ |---|---|
293
+ | [GDPR Article 6](https://eur-lex.europa.eu/eli/reg/2016/679/art_6/oj/eng) — lawful bases | Law |
294
+ | [GDPR Article 7](https://eur-lex.europa.eu/eli/reg/2016/679/art_7/oj/eng) — conditions for consent | Law |
295
+ | [GDPR Article 13](https://eur-lex.europa.eu/eli/reg/2016/679/art_13/oj/eng) — information to be provided | Law |
296
+ | [AEPD FAQ 02.48](https://www.aepd.es/preguntas-frecuentes/2-tus-obligaciones-como-responsable-del-tratamiento/6-el-deber-de-informacion/FAQ-0248-sobre-si-el-usuario-tiene-que-dar-consentimiento-a-clausula-de-privacidad) — a privacy-information checkbox is not blanket consent | Regulator guidance |
297
+ | [Directive 93/13/EEC](https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=celex%3A31993L0013) — electronic form does not make an unfair term fair | Law |
298
+ | [15 U.S.C. § 7001](https://www.law.cornell.edu/uscode/text/15/7001), [§ 7003](https://www.law.cornell.edu/uscode/text/15/7003) — electronic validity preserved, substantive requirements and exclusions retained | Law |
299
+ | The six-kind taxonomy, the capability matrix, and the decision order above | Product-design inference |