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
data/guides/naming.md ADDED
@@ -0,0 +1,320 @@
1
+ # The public naming contract
2
+
3
+ Every public name must say what it contains and where it came from. The test is literal: a
4
+ 15-year-old reading the initializer, a policy, an error message, a task's output, or a receipt
5
+ should not need to know internal abbreviations to understand what the application does.
6
+
7
+ This is not style. Clickwrap's value is evidence that is still true and still legible years
8
+ after it was written, and the names are part of the evidence. A field called `ua` in a receipt
9
+ from 2026 is a puzzle in 2031. A method called `dispose!` is a method nobody can review. An
10
+ option called `full` is a decision nobody can audit.
11
+
12
+ The rules below are normative for the whole gem — public methods, configuration settings, DSL
13
+ keywords, error messages, generated comments, task output, and receipt fields — not only for
14
+ request evidence.
15
+
16
+ ---
17
+
18
+ ## The eight rules
19
+
20
+ ### 1. Prefer a complete verb plus noun
21
+
22
+ **Why:** a bare noun leaves the reader to guess the verb, and different readers guess
23
+ differently. "With ip" — with it how? Recorded? Filtered? Required?
24
+
25
+ ```ruby
26
+ # Before
27
+ config.with_ip = true
28
+ policy.ip = :on
29
+
30
+ # After
31
+ config.record_ip_address_by_default = true
32
+ record_ip_address(because: "...", delete_after: 90.days)
33
+ ```
34
+
35
+ ### 2. Qualify the source
36
+
37
+ **Why:** "resolver" says something gets resolved. It does not say from what, which is exactly
38
+ the fact a reader needs in order to judge how much the value is worth. An address read from an
39
+ HTTP request and an address supplied by a client are different facts and must not share a name.
40
+
41
+ ```ruby
42
+ # Before
43
+ config.ip_resolver = ->(x) { x.remote_ip }
44
+
45
+ # After
46
+ config.read_ip_address_from_http_request_with = ->(http_request) { http_request.remote_ip }
47
+ ```
48
+
49
+ The same rule produces `read_browser_user_agent_from_http_request_with`, and it is why a
50
+ host-assigned reader is labeled `host_configured_reader` in the receipt rather than being
51
+ allowed to borrow the credibility of `rails_request_remote_ip`.
52
+
53
+ ### 3. Positive booleans
54
+
55
+ **Why:** a negative boolean makes every reader do a double negative in their head, and
56
+ `disable_ip_capture = false` is a line that has been misread in every codebase that has ever
57
+ contained one.
58
+
59
+ ```ruby
60
+ # Before
61
+ config.disable_ip_capture = false
62
+ config.no_geolocation = true
63
+
64
+ # After
65
+ config.record_ip_address_by_default = true
66
+ config.record_ip_geolocation_country_by_default = false
67
+ ```
68
+
69
+ ### 4. Destructive methods name exactly what they delete
70
+
71
+ **Why:** `dispose!` in a disposition report a year from now tells nobody which value went. Three
72
+ methods that each name one field mean the report, the audit event, and the log line all say
73
+ which category disappeared, rather than a euphemism covering several.
74
+
75
+ ```ruby
76
+ # Before
77
+ Clickwrap.dispose!(receipt)
78
+ Clickwrap.purge_network_context!(receipt)
79
+ Clickwrap.delete_personal_data!(receipt)
80
+
81
+ # After
82
+ Clickwrap.delete_recorded_ip_address!(receipt, because: "Retention period ended")
83
+ Clickwrap.delete_recorded_browser_user_agent!(receipt, because: "Retention period ended")
84
+ Clickwrap.delete_recorded_ip_geolocation!(receipt, because: "Retention period ended")
85
+ ```
86
+
87
+ The implementation may centralize disposal internally — it does. Public calls, task output,
88
+ audit events, and documentation keep the explicit field name.
89
+
90
+ Every destructive method also takes a plain-English `because:`. It is stored on the disposition
91
+ event and it is the only thing that will explain the deletion to somebody reading the record
92
+ years from now.
93
+
94
+ ### 5. Distinguish configuration from fact
95
+
96
+ **Why:** "we are set up to record this" and "this was recorded" are different claims, and a
97
+ receipt that blurs them is worthless. The `_by_default` suffix marks a setting; the trailing
98
+ question mark marks an observation about one event.
99
+
100
+ ```ruby
101
+ config.record_ip_address_by_default # configuration: what we intend to do
102
+ receipt.recorded_ip_address? # fact: what happened for this event
103
+ ```
104
+
105
+ The same distinction runs through the six request-evidence states. `not_configured` is a
106
+ configuration fact; `unavailable` is an observation; `deleted_after_retention` is a third thing
107
+ entirely. None of them is blank.
108
+
109
+ ### 6. One option never secretly enables another category of data
110
+
111
+ **Why:** this is the failure mode the gem exists to prevent. An option that turns on a category
112
+ of personal data as a side effect makes the diff unreviewable and the upgrade dangerous — a
113
+ later release can widen what the profile covers, and nobody reading the initializer would know.
114
+
115
+ ```ruby
116
+ # Before
117
+ record_ip_geolocation(precision: :full)
118
+ record_request_context(level: :enhanced)
119
+
120
+ # After
121
+ record_ip_geolocation(
122
+ country: true,
123
+ region: true,
124
+ city: true,
125
+ postal_code: false,
126
+ latitude_and_longitude: true,
127
+ timezone: false,
128
+ continent: false,
129
+ metro_code: false,
130
+ accuracy_radius_in_kilometers: true,
131
+ because: "...",
132
+ retain_until: :security_evidence_retention_ends
133
+ )
134
+ ```
135
+
136
+ Nine fields, nine visible decisions, each with its own line in the privacy inventory.
137
+ `latitude_and_longitude` is one coupled choice on purpose: half a coordinate is not a result.
138
+
139
+ The installer follows the same rule. Interactive prompts ask about one category at a time, and
140
+ non-interactive installs use individual flags such as
141
+ `--record-ip-addresses-by-default`. The sole recipe,
142
+ `--request-evidence-recipe=privacy-minimized`, writes every collection setting explicitly as
143
+ `false`; it can never enable a data category. There is no `evidence-rich`, `full`, or other named
144
+ recipe whose expansion could collect more in a later release.
145
+
146
+ ### 7. No unexplained acronyms or abbreviations
147
+
148
+ **Why:** an abbreviation is a shared secret between the author and whoever was in the room. A
149
+ receipt is read by people who were not.
150
+
151
+ ```ruby
152
+ # Before
153
+ receipt.ua
154
+ receipt.geo
155
+ event.addr
156
+ capture!(:signup, ctx: request)
157
+
158
+ # After
159
+ receipt.browser_user_agent
160
+ receipt.ip_geolocation_country_code
161
+ receipt.ip_address
162
+ Clickwrap.capture!(:signup, http_request: request)
163
+ ```
164
+
165
+ Two abbreviations are permitted because they are the standards' own names and expanding them
166
+ would be less clear, not more: `ip` inside `ip_address` and `ip_geolocation`, and `http` inside
167
+ `http_request`.
168
+
169
+ ### 8. Every example must make sense read aloud
170
+
171
+ **Why:** it is the cheapest review anyone can run, and it catches almost everything the other
172
+ seven rules are trying to prevent.
173
+
174
+ Read this aloud:
175
+
176
+ > Clickwrap capture and, withdrawal authorization, actor current user, subject withdrawal, http
177
+ > request request, submission clickwrap submission.
178
+
179
+ It is a sentence. Now read the alternative:
180
+
181
+ > Clickwrap capture, with ip, ctx request, full.
182
+
183
+ If an example does not survive being read aloud by a developer who has never seen the gem, the
184
+ name is wrong. Fix the name, not the example.
185
+
186
+ ---
187
+
188
+ ## Required vocabulary
189
+
190
+ Use the left column. The right column is what has been rejected and why.
191
+
192
+ | Use | Not | Because |
193
+ |---|---|---|
194
+ | `ip_address` | `ip`, `remote_address`, `network_address`, `addr` | Abbreviations, and "network address" hides which address |
195
+ | `ip_geolocation` | `geo`, `location`, `coordinates` | "Location" reads as where the person is. It is an estimate about an address |
196
+ | `ip_geolocation_latitude_and_longitude` | `precise_location`, `fine_location` | "Precise" is the opposite of true here |
197
+ | `ip_geolocation_accuracy_radius_in_kilometers` | `accuracy` | A number with no unit and no meaning |
198
+ | `ip_geolocation_provider_name`, `ip_geolocation_provider_source`, `ip_geolocation_database_version` | `provider`, `source` | In a receipt, "source" could mean four things |
199
+ | `browser_user_agent` | `ua`, `client_info`, `browser` | The raw header is a specific thing; "browser" suggests a parsed result |
200
+ | `http_request` | `context`, `ctx`, `env` | "Context" is where unreviewed data goes to hide |
201
+ | `capture_channel` | `flow`, `source` | Unqualified nouns again |
202
+ | `authentication_method` | `assurance` | Assurance supplied by whom, measured how? |
203
+ | `recorded_at_by_server` | `signed_at`, `timestamped_at` | Nothing signed it. It is the application server's own clock, and the name has to keep saying so |
204
+ | `estimated`, `client_supplied`, `server_observed`, `provider_reported` | dropping the qualifier | These four words are the difference between a fact and an overclaim |
205
+
206
+ ## Booleans read as questions
207
+
208
+ ```ruby
209
+ receipt.recorded_ip_address?
210
+ receipt.recorded_browser_user_agent?
211
+ receipt.recorded_ip_geolocation_country?
212
+ receipt.recorded_ip_geolocation_city?
213
+ receipt.recorded_ip_geolocation_latitude_and_longitude?
214
+ receipt.ip_geolocation_was_estimated?
215
+ receipt.ip_geolocation_source_was_verified_by_host?
216
+ receipt.ip_address_was_deleted?
217
+ receipt.browser_user_agent_was_deleted?
218
+ ```
219
+
220
+ The `was_` prefix on the last four is doing work: `ip_geolocation_was_estimated?` is a statement
221
+ about the value that was stored, not about the resolver's current settings, and
222
+ `ip_address_was_deleted?` is a fact about this record rather than about policy.
223
+
224
+ The same grammar runs through the actor proxy, which is where most application code meets the
225
+ gem:
226
+
227
+ ```ruby
228
+ user.clickwraps.agreed_to?(:terms)
229
+ user.clickwraps.acknowledged?(:privacy_notice)
230
+ user.clickwraps.consented_to?(:product_updates)
231
+ user.clickwraps.declared?(:independent_contractor, subject: scheme)
232
+ user.clickwraps.attested?(:bank_accepted_transfer)
233
+ user.clickwraps.authorized?(:withdrawal, subject: withdrawal)
234
+ user.clickwraps.exempted_from?(:signup)
235
+ ```
236
+
237
+ Six kinds, six predicates. There is no generic `accepted?`, because collapsing them would erase
238
+ the distinction the six kinds exist to preserve — and `exempted_from?` is separate precisely so
239
+ an exemption can never answer a human-action question.
240
+
241
+ ---
242
+
243
+ ## Prohibited option names
244
+
245
+ These may not appear as public options, keywords, settings, or command-line flags. Each one is
246
+ banned for a specific reason, not for taste.
247
+
248
+ | Never | Why |
249
+ |---|---|
250
+ | `:network`, `:full`, `:enhanced`, `:forensic`, `:maximum` | They hide what will be collected behind a word that sounds like a quality level |
251
+ | `record_location` | A developer could reasonably read it as GPS or physical location. It is neither |
252
+ | `request_evidence: :network`, `track_everything`, `record_everything` | Category switches. See rule 6 |
253
+ | `maximum_evidence`, `full_evidence`, `legal_proof: true` | They imply a verdict the gem cannot reach, and they enable data as a side effect |
254
+ | An opaque privacy-profile switch keyed to a regulation | No runtime flag can make a legal determination on anyone's behalf, and the name would be the least accurate string in the codebase |
255
+ | `include_sensitive_context: true` | One flag turning on three categories of personal data makes an operator's intent unreviewable. Use `include_ip_address:`, `include_browser_user_agent:`, `include_ip_geolocation:` |
256
+ | `dispose!`, `purge!`, `cleanup!` as public API | See rule 4 |
257
+
258
+ There is also no `--gdpr-*`, `--record-network-context`, `--record-everything`, or
259
+ `--full-evidence` generator flag. Non-interactive installer options are as explicit as the
260
+ settings they write:
261
+
262
+ ```text
263
+ --record-ip-addresses-by-default
264
+ --record-ip-geolocation-cities-by-default
265
+ --record-ip-geolocation-latitude-and-longitude-by-default
266
+ --delete-recorded-ip-addresses-after-days=90
267
+ ```
268
+
269
+ ---
270
+
271
+ ## The same standard for the rest of the initializer
272
+
273
+ The rule is not a request-evidence rule. It produced the shape of the whole configuration
274
+ object.
275
+
276
+ | Shipped name | Rejected | Why |
277
+ |---|---|---|
278
+ | `actor_class_name` | `actor_class` | It holds a string, constantized lazily so the initializer works before the model loads. The name says which |
279
+ | `current_actor_method_name` | `current_actor` | It is a method name, not an actor |
280
+ | `find_current_tenant_with` | `tenant_resolver` | Verb plus source. It is a callable that finds something |
281
+ | `store_document_contents_in` | `document_store` | Says what goes where |
282
+ | `digest_canonical_receipts_with` | `integrity` | "Integrity" is a category, not a decision. This one names the algorithm's job |
283
+ | `chain_event_history_with`, `anchor_event_history_with`, `timestamp_receipts_with` | one `integrity_level` setting | Three different mechanisms making three different claims. One setting would let a reader infer the strongest from the presence of the weakest |
284
+ | `after_event_is_committed` | `after_commit` | Says which commit, and reads as a sentence |
285
+ | `authorize_unredacted_request_evidence_access_with` | `access_control` | Long, and correct. It names exactly which access it authorizes |
286
+ | `deliberately_store_request_evidence_unencrypted!(because:)` | `encryption: false` | Turning encryption off should be a sentence a reviewer can find in a diff, with the host's own reason attached — not a `false` |
287
+
288
+ The last row is the pattern worth copying. When an option has a consequence somebody should
289
+ have to think about, make the name carry the thinking.
290
+
291
+ ---
292
+
293
+ ## Reviewing a new name
294
+
295
+ Before you add a public method, option, setting, or receipt field:
296
+
297
+ - [ ] Read the example aloud. Does it parse as English?
298
+ - [ ] Does the name say what it contains **and** where it came from?
299
+ - [ ] Is the boolean positive?
300
+ - [ ] If it deletes something, does the name say which thing?
301
+ - [ ] Does it distinguish "we are configured to" from "this happened"?
302
+ - [ ] Could enabling it turn on a category of personal data the reader did not name?
303
+ - [ ] Are there abbreviations that are not `ip` or `http`?
304
+ - [ ] If it goes into a receipt: will it still be legible in 2031, to somebody who has never
305
+ seen this codebase?
306
+ - [ ] If it goes into a receipt: is it in `Clickwrap::Vocabulary`? Stable strings live there,
307
+ frozen, in one place, and they are **added to, never renamed or repurposed** — a value
308
+ that changed meaning underneath an old receipt would make it say something it never said.
309
+
310
+ A name that fails any of these is cheaper to change now than after the first release, because
311
+ after the first release it is in receipts.
312
+
313
+ ---
314
+
315
+ ## Sources
316
+
317
+ | Source | Class |
318
+ |---|---|
319
+ | `docs/strategy/02-request-evidence.md`, "Public naming rules" — the normative source for this guide | Internal normative design document |
320
+ | Every rule, rejection, and rationale above | Product-design inference |
@@ -0,0 +1,320 @@
1
+ # Binding an organization through a human actor
2
+
3
+ Clickwrap keeps the person who acted and the party they represented as two
4
+ different facts:
5
+
6
+ | Concept | Typical `organizations` record | What it means |
7
+ |---|---|---|
8
+ | actor | `current_user` | The human account that performed the clickwrap action |
9
+ | represented party (`acting_for`) | `current_organization` | The organization the action is intended to bind |
10
+ | tenant | usually `current_organization` | The application data boundary in which the action happened |
11
+ | subject | optional domain record | The exact order, withdrawal, contract, or other object the statement covers |
12
+
13
+ A `User` therefore does not disappear behind an organization. The event and
14
+ receipt keep both references, plus the membership evidence used to authorize
15
+ the represented action.
16
+
17
+ ## One policy line
18
+
19
+ Choose the organization role your application and counsel have reviewed:
20
+
21
+ ```ruby
22
+ Clickwrap.policy :organization_terms do
23
+ agree_to :organization_terms
24
+
25
+ permit_acting_for_organization when_actor_is_at_least: :admin
26
+
27
+ retain_with :ordinary_agreement_evidence
28
+ end
29
+ ```
30
+
31
+ Or name a purpose-specific permission from the `organizations` gem instead of
32
+ coupling the decision to a broad role:
33
+
34
+ ```ruby
35
+ Clickwrap.policy :data_processing_terms do
36
+ agree_to :data_processing_terms
37
+
38
+ permit_acting_for_organization \
39
+ when_actor_has_permission: :accept_data_processing_terms
40
+
41
+ retain_with :ordinary_agreement_evidence
42
+ end
43
+ ```
44
+
45
+ If both options are present, both must pass. A policy with neither is rejected
46
+ at boot: organization membership by itself is not treated as legal authority.
47
+
48
+ The permission-oriented form is usually the more durable design. It lets an
49
+ application grant exactly “may accept these terms for this organization”
50
+ without silently equating that power with every other thing an `admin` can do.
51
+
52
+ ## Present and capture the same represented party
53
+
54
+ Make organizational capacity visible in both the interface copy and the Ruby
55
+ call:
56
+
57
+ ```erb
58
+ <%= form_with model: @agreement do |form| %>
59
+ <p>
60
+ You are accepting these terms for
61
+ <strong><%= current_organization.name %></strong>.
62
+ </p>
63
+
64
+ <%= form.clickwrap :organization_terms,
65
+ acting_for: current_organization,
66
+ submit: "Accept for #{current_organization.name}" %>
67
+ <% end %>
68
+ ```
69
+
70
+ Then pass the server-owned organization again at submit:
71
+
72
+ ```ruby
73
+ def create
74
+ organization = current_organization
75
+
76
+ capture_clickwrap_and!(
77
+ :organization_terms,
78
+ acting_for: organization
79
+ ) do |pending_receipt|
80
+ organization.update!(
81
+ terms_accepted_with_clickwrap_event_id: pending_receipt.event_id
82
+ )
83
+ end
84
+
85
+ redirect_to organization_settings_path
86
+ end
87
+ ```
88
+
89
+ Do not permit an organization ID from the form and turn it into
90
+ `acting_for:`. Resolve the organization from the authenticated server-side
91
+ context. The signed presentation is also bound to that exact represented-party
92
+ reference, so it cannot be moved to another organization between render and
93
+ submit.
94
+
95
+ If the application uses the organization as its Clickwrap tenant too, configure
96
+ the resolver once — **and declare tenant semantics on every policy**, because
97
+ the resolver alone reproduces a real production bug: an organization member's
98
+ session resolves an ambient organization on every request, so a *personal*
99
+ policy (a contractor declaration, a payout gate) would silently bind — or refuse —
100
+ under whatever organization happened to be current:
101
+
102
+ ```ruby
103
+ Clickwrap.configure do |config|
104
+ config.find_current_tenant_with = ->(controller) {
105
+ controller.current_organization
106
+ }
107
+ end
108
+
109
+ Clickwrap.policy :contractor_declaration do
110
+ tenant_is :not_applicable # personal evidence: joining an org changes nothing
111
+
112
+ declare :independent_contractor,
113
+ document: :terms,
114
+ statement: "I provide these services as an independent contractor, not as an employee."
115
+
116
+ retain_with :ordinary_agreement_evidence
117
+ end
118
+
119
+ Clickwrap.policy :organization_terms do
120
+ tenant_is :required # org-scoped evidence: the org is passed deliberately
121
+
122
+ agree_to :terms
123
+ permit_acting_for_organization when_actor_is_at_least: :admin
124
+
125
+ retain_with :ordinary_agreement_evidence
126
+ end
127
+ ```
128
+
129
+ `tenant_is :not_applicable` policies always record a nil tenant regardless of
130
+ the ambient organization; `:required` policies refuse to present or capture
131
+ without one; `:optional` (the default) passes the resolved value through.
132
+ Presentation, capture, verification, withdrawal, and import all translate the
133
+ ambient value through the same declaration. `acting_for:` stays explicit
134
+ because “this happened inside Acme” and “this person intended to bind Acme”
135
+ are not interchangeable claims.
136
+
137
+ ## What is checked at presentation and submit
138
+
139
+ The built-in adapter has no hard runtime dependency on `organizations`; it is
140
+ used only by a policy that calls `permit_acting_for_organization`. For a
141
+ persisted organization it:
142
+
143
+ 1. requires a persisted `Organizations::Organization` (host subclasses are
144
+ accepted);
145
+ 2. requires the actor to have a current membership in that exact organization
146
+ before rendering the form;
147
+ 3. signs the presentation-time membership reference, role, source, criterion,
148
+ and verification time into the manifest;
149
+ 4. locks and rereads the membership inside the capture transaction;
150
+ 5. checks the configured minimum role and/or permission at both moments; and
151
+ 6. records both snapshots plus the available authentication method.
152
+
153
+ That means an admin removed or demoted after the page was rendered is denied at
154
+ submit. A role change that remains authorized is not flattened: the receipt
155
+ keeps the role at presentation and the role at capture. A token rendered for
156
+ one organization cannot be submitted for another, and an organizational
157
+ acceptance never satisfies a personal-capacity query:
158
+
159
+ ```ruby
160
+ user.clickwraps.current_for?(
161
+ :organization_terms,
162
+ tenant: organization,
163
+ acting_for: organization
164
+ ) # => true
165
+
166
+ user.clickwraps.current_for?(
167
+ :organization_terms,
168
+ tenant: organization
169
+ ) # => false
170
+ ```
171
+
172
+ ## Create the organization and its evidence together
173
+
174
+ A person may reach the form before the organization has a row or a membership.
175
+ That is not the ordinary `acting_for:` case: Clickwrap cannot truthfully say it
176
+ verified a membership that does not exist. Opt into the prospective flow in the
177
+ policy, and include an explicit declaration for the real-world authority or
178
+ content-rights claim the application needs:
179
+
180
+ ```ruby
181
+ Clickwrap.policy :organization_creation do
182
+ declare :authority_and_content_rights,
183
+ statement: "I am authorized to create and act for this organization and may use the content I submit.",
184
+ document: nil,
185
+ protected_outcome_version: "created-organization-v1",
186
+ record_protected_outcome_with: ->(organization) {
187
+ Clickwrap.protected_outcome(
188
+ action: :created,
189
+ record: organization,
190
+ facts: {
191
+ name: organization.name,
192
+ logo_checksum: organization.logo.blob.checksum
193
+ }
194
+ )
195
+ }
196
+
197
+ permit_acting_for_organization(
198
+ when_actor_is_at_least: :owner,
199
+ including_when_this_action_creates_the_organization: true
200
+ )
201
+
202
+ retain_with :ordinary_agreement_evidence
203
+ end
204
+ ```
205
+
206
+ Pass the same new model as `acting_for:`. The form helper creates and signs a
207
+ server-owned browser-flow identifier; there is no organization id, role, policy
208
+ option, or evidence field for the browser to choose:
209
+
210
+ ```erb
211
+ <%= form_with model: @organization do |form| %>
212
+ <%# name, logo, and other organization fields %>
213
+
214
+ <%= form.clickwrap :organization_creation,
215
+ acting_for: @organization,
216
+ submit: "Create organization" %>
217
+ <% end %>
218
+ ```
219
+
220
+ At submit, return the persisted organization after creating its owner
221
+ membership in the protected block. The result may come from an ordinary
222
+ creation service; it must have the same class the presentation bound:
223
+
224
+ ```ruby
225
+ def create
226
+ @organization = Organizations::Organization.new(organization_params)
227
+
228
+ receipt = create_represented_party_with_clickwrap(
229
+ :organization_creation,
230
+ represented_party: @organization
231
+ ) do |pending_receipt|
232
+ @organization.save!
233
+ @organization.add_member!(current_user, role: :owner)
234
+ @organization.update!(creation_clickwrap_event_id: pending_receipt.event_id)
235
+ @organization
236
+ end
237
+
238
+ redirect_to organization_path(receipt.event.represented_party)
239
+ end
240
+ ```
241
+
242
+ The manifest labels presentation-time authority `not_yet_verifiable`. After the
243
+ block returns the persisted record, the configured adapter verifies the new
244
+ owner membership inside the same transaction, and the event is rebound to the final
245
+ stable organization reference before its digest and projections are written.
246
+ An evidence-write failure, model-validation failure, missing/insufficient
247
+ membership, protected-outcome failure, or outer transaction rollback leaves no
248
+ created organization or Clickwrap event. An identical nonce retry returns the
249
+ original receipt without running the creation block twice.
250
+
251
+ That post-creation owner check proves only the application state the block just
252
+ created. It does not prove that the person already had real-world authorization
253
+ to use a legal name or logo or to bind an external company. Record that claim as
254
+ an explicit `declare` statement, choose its wording with counsel, and keep the
255
+ protected outcome specific enough to identify the organization and submitted
256
+ assets it covered.
257
+
258
+ API clients use the same primitive with a server-owned flow id:
259
+
260
+ ```ruby
261
+ Clickwrap.create_represented_party!(
262
+ :organization_creation,
263
+ actor: current_user,
264
+ represented_party: organization,
265
+ represented_party_creation_flow_id: server_session_flow_id,
266
+ http_request: request,
267
+ submission: Clickwrap.submission_from(params)
268
+ ) do |pending_receipt|
269
+ organization.save!
270
+ organization.add_member!(current_user, role: :owner)
271
+ organization.update!(creation_clickwrap_event_id: pending_receipt.event_id)
272
+ organization
273
+ end
274
+ ```
275
+
276
+ Pass that same `represented_party_creation_flow_id:` to `Clickwrap.present`.
277
+ Do not derive it from form fields or accept it as the authority decision; it is
278
+ opaque server session state used only to bind one prospective browser flow.
279
+
280
+ ## The legal boundary
281
+
282
+ The `organizations` role or permission is an application authorization fact.
283
+ Clickwrap records that fact and its provenance; it cannot decide whether an
284
+ `admin`, officer, employee, guardian, or agent has legal capacity to bind a
285
+ party for a particular agreement in a particular jurisdiction. Spain's Civil
286
+ Code Article 1259, for example, makes authorization or legal representation a
287
+ substantive issue and addresses later ratification; it does not say that a SaaS
288
+ role named `admin` or `owner` supplies that authorization ([official current
289
+ consolidated text](https://www.boe.es/eli/es/rd/1889/07/24/%281%29/con),
290
+ [official BOE document view with Article 1259](https://www.boe.es/buscar/doc.php?id=BOE-A-1889-4763&lang=es)).
291
+ EU eIDAS Article 25 also distinguishes the evidential treatment of an
292
+ electronic signature from the specific handwritten-signature equivalence of a
293
+ qualified electronic signature ([official consolidated EUR-Lex
294
+ text](https://eur-lex.europa.eu/eli/reg/2014/910/en/cons)).
295
+
296
+ Those are jurisdiction-specific legal sources, not a universal answer. Choose
297
+ the criterion and statement with counsel, use interface copy that makes the
298
+ represented capacity conspicuous, and keep any organization identity fields
299
+ your evidentiary policy requires. The built-in adapter records stable
300
+ database/GlobalID references; it does not claim that an organization display
301
+ name is a verified legal name, that a membership role is statutory authority,
302
+ or that an ordinary click is a qualified electronic signature.
303
+
304
+ For a different authority system, keep the same actor/represented-party model
305
+ and register a named server-side adapter with
306
+ `config.register_represented_party_authority`.
307
+
308
+ ## Exact implementation sources
309
+
310
+ These are source-code observations, not legal authorities:
311
+
312
+ - The `organizations` gem's public organization and membership APIs are
313
+ documented at
314
+ https://github.com/rameerez/organizations/blob/fd0eace263c54cd3c8ab98a9505190df6856c6d6/README.md#the-complete-api
315
+ - Its organization-to-membership association is implemented at
316
+ https://github.com/rameerez/organizations/blob/fd0eace263c54cd3c8ab98a9505190df6856c6d6/lib/organizations/models/organization.rb#L37-L41
317
+ - Its membership role and permission checks are implemented at
318
+ https://github.com/rameerez/organizations/blob/fd0eace263c54cd3c8ab98a9505190df6856c6d6/lib/organizations/models/membership.rb#L106-L160
319
+ - Its default role hierarchy and permission evaluation are implemented at
320
+ https://github.com/rameerez/organizations/blob/fd0eace263c54cd3c8ab98a9505190df6856c6d6/lib/organizations/roles.rb#L12-L146