clickwrap 0.2.1 → 0.3.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: be18f8fc09eee066b71a234a134440010c6cf2beb82987b4bbe3636dde2f9133
4
- data.tar.gz: a2c66a9be09cf999de30d890c4338e084146414cadbe0747022a3aedc8a4ef5f
3
+ metadata.gz: 55f77c92e4ac5400a7c7fba781bd2a592875f3deef59fd8b3c1d6269e302ffc6
4
+ data.tar.gz: f15af7f288d8349bc8cd322b28b472e68bca750a8c17718648d7fa2db3877021
5
5
  SHA512:
6
- metadata.gz: 891f939a6b827915a5d2703ecf98b0ed9b9e761d3689a1117e8ba3df6ae0450c50a3f9d53edd0cbf844e996a7be447b080070b6ab2d916409b5d1ecfdc851ddc
7
- data.tar.gz: 2387391b3d960d05dcdc783221bb5653de21406ecd0173b8fbafa1dc17d716549044a22bc719740892553b8a9c66beb8b42417c61e9bc7f585d569849fa43d9d
6
+ metadata.gz: fceb30a214fe8e4cdfcc19a775f8082fefe61424eba2e463958240dec07a0c8d099e1d3c6e5e0b9d67bd47eb11a445ee591f4b520c08f847479fbed277f197f2
7
+ data.tar.gz: e8d53673c7a5d9ba553078ace895a187e404b6a486525ee56d362f584e262f3bb9f9000287bf57311b22a40e2049140867f5eefdb300e945a7d69d25ff31dcf4
data/CHANGELOG.md CHANGED
@@ -6,6 +6,210 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.3.1] - 2026-08-20
10
+
11
+ ### Changed — the rest of the collection friction, and the principle behind removing it
12
+
13
+ 0.3.0 made *enabling* request evidence one switch and gave the initializer
14
+ defaults honest gem-supplied purposes. It left the friction standing
15
+ everywhere else: a policy-level `record_ip_address` still read as though it
16
+ wanted a sentence, `record_ip_geolocation` refused to do anything without a
17
+ field list, and the encryption escape hatch still demanded a reason be
18
+ phrased twice. This finishes the job the owner asked for.
19
+
20
+ The principle, in their words: **the host application and its privacy policy
21
+ own *why* data is collected; this gem records *what* was collected, honestly,
22
+ and is nobody's nanny.** Its job is evidence mechanics, not gatekeeping
23
+ collection. Everything below now works exactly as written:
24
+
25
+ ```ruby
26
+ Clickwrap.configure do |config|
27
+ config.record_request_evidence_by_default = true
28
+ config.keep_recorded_ip_addresses_indefinitely!
29
+ config.deliberately_store_request_evidence_unencrypted!
30
+ end
31
+
32
+ Clickwrap.policy :anything do
33
+ agree_to :terms
34
+ record_ip_address
35
+ record_browser_user_agent
36
+ record_ip_geolocation
37
+ end
38
+ ```
39
+
40
+ - **All three `record_` verbs take zero keyword arguments.** `because:`,
41
+ `legal_basis_reference:`, `data_protection_impact_assessment_reference:`,
42
+ `delete_after:`, `retain_until:`, and `encrypted:` are optional in fact and
43
+ not merely in the signature — nothing downstream refuses their absence.
44
+ (`legal_basis_reference` and the DPIA reference never were required anywhere
45
+ in the gem; a test now pins that they never become so.)
46
+ - **`record_ip_geolocation` with no field named records the coarse trio** —
47
+ country, region, city — the same set `record_request_evidence_by_default`
48
+ turns on, and nothing finer. The field keywords moved from `false` to `nil`
49
+ defaults so "did not mention this field" is distinguishable from "named it
50
+ and turned it off". Naming even one field means you are choosing the set
51
+ yourself, and the set is exactly what you named. Naming every field `false`
52
+ is still refused, because calling the verb and disabling everything cannot
53
+ mean anything; `do_not_record_ip_geolocation` is how to say that.
54
+ - **`keep_recorded_{ip_addresses,browser_user_agents,ip_geolocation}_indefinitely!`
55
+ take no arguments at all.**
56
+ - **`deliberately_store_request_evidence_unencrypted!` no longer needs a
57
+ `because:`.** The method NAME is the ceremony: `encrypt_recorded_* = false`
58
+ still cannot be reached without writing that line, and a reviewer still
59
+ finds it in the diff. When the host writes no reason the gem records
60
+ `Vocabulary::DEFAULT_REASON_FOR_STORING_REQUEST_EVIDENCE_UNENCRYPTED`.
61
+ Encryption itself is untouched — on by default for all three categories,
62
+ with a test pinning that the one switch does not weaken it.
63
+ - **The install generator stops refusing an incomplete category.**
64
+ `--record-ip-addresses-by-default` with no reason and no period writes the
65
+ file and simply omits those two lines, so the gem's own defaults apply.
66
+ - The `ReviewedText` placeholder check now only ever applies to text a host
67
+ actually supplied. Absence is never scaffolding.
68
+
69
+ ### Unchanged, deliberately
70
+
71
+ - Reasons that are not about *collection* keep their required `because:`:
72
+ `delete_recorded_ip_address!` and its siblings, `dispose_core_event!`,
73
+ `place_on_legal_hold!` / `release_legal_hold!`, `plan_disposition_for`,
74
+ unredacted receipt export, and the lifecycle verbs. Those record a
75
+ destructive act, an access, or a state change — there the audit trail *is*
76
+ the reason, and there is no honest default for "why did somebody delete
77
+ this".
78
+ - Still refused, because each is the host contradicting themselves rather than
79
+ leaving a blank: scaffolding text the host actually wrote standing in for a
80
+ purpose (including in the installer, where a `TODO` in a shipped initializer
81
+ is worse than no line and the gem would reject it at boot anyway), a
82
+ negative deletion period passed to the installer, a deletion clock declared
83
+ alongside `keep_recorded_..._indefinitely!` for the same category, and
84
+ `record_ip_geolocation` with every field explicitly off.
85
+ - The gem's code default is still record-nothing. Claim boundaries, receipt
86
+ state labeling (`not_configured` / `unavailable` / `recorded` /
87
+ `deleted_after_retention`), and every released receipt format are untouched.
88
+
89
+ ### Documentation
90
+
91
+ - README, `guides/request-evidence.md`, and `guides/naming.md` show the
92
+ zero-keyword forms as the ordinary way to write a policy, with purposes,
93
+ legal bases, and clocks as the upgrade path.
94
+ - `CLAUDE.md` / `AGENTS.md` rule 6 is rewritten around the principle above and
95
+ records the owner directive and its date. What it keeps as non-negotiable:
96
+ never a switch whose NAME hides what it collects, never an overclaim, never
97
+ a blurred receipt state, encryption on by default behind its named call,
98
+ host-supplied scaffolding rejected, and contradictions refused.
99
+
100
+ ## [0.3.0] - 2026-08-20
101
+
102
+ ### Changed — recording request evidence is one switch, not a checklist
103
+
104
+ Owner directive, and a deliberate reversal of part of this gem's own
105
+ philosophy. The reasoning, stated plainly so a future reader can weigh it:
106
+ the ceremony around *enabling* request evidence — a written purpose, a
107
+ disposal answer, and a reviewed trusted-proxy digest, each of them a boot
108
+ refusal — was not producing better-reviewed collection. It was producing
109
+ **no collection at all**. Integrators hit three refusals in a row on the way
110
+ to their first capture and turned the fields off, and an agreement with no
111
+ corroboration is worse evidence than one corroborated under a purpose the gem
112
+ stated on their behalf. So the friction is gone from turning collection *on*.
113
+ None of it is gone from describing collection honestly.
114
+
115
+ - **`config.record_request_evidence_by_default = true`.** One line records, on
116
+ every policy, the IP address the request arrived from, the browser user
117
+ agent it sent, and a coarse country / region / city estimate for that
118
+ address. Nothing finer: a postal code, coordinates, a timezone, a continent,
119
+ a metro code, and an accuracy radius each remain their own separately named
120
+ setting. It is a fan-out setter over the existing `record_*_by_default`
121
+ flags, so it composes with them in reading order, and any policy still
122
+ overrides it with `record_ip_address(...)` or `do_not_record_ip_address`.
123
+ The reader reports what is actually on rather than a remembered assignment.
124
+ - **A purpose is no longer the price of admission.** A category enabled
125
+ without `because:` / `reason_for_recording_*_by_default` records
126
+ `Vocabulary::DEFAULT_REQUEST_EVIDENCE_PURPOSE` — "Corroborate who performed
127
+ each recorded act, from where, on what client — to defend the recorded
128
+ agreement itself." Every compiled policy revision therefore still carries a
129
+ purpose into every receipt, and `Clickwrap::Privacy.inventory` marks each one
130
+ `"purpose_source": "gem_default"` or `"host"` so the gem's sentence can never
131
+ be mistaken for a decision somebody reviewed.
132
+ - **No disposal answer means indefinite.** The four gatekeepers that refused a
133
+ recorded field with no clock — `Configuration#validate_request_evidence_defaults!`,
134
+ the policy-level check in `RequestEvidencePolicy`, the reference validator,
135
+ and the capture-time extractor — now treat absence as "keeps pace with the
136
+ evidence it corroborates", which is what core evidence has done since 0.2.0.
137
+ The annex is stamped with no schedule and the retention planner never lists
138
+ it. `keep_recorded_*_indefinitely!` still works, still records its reason,
139
+ and now accepts no `because:` at all (defaulting to "Corroboration lives as
140
+ long as the evidence it corroborates").
141
+ - **`trusted_proxy_configuration_digest` is no longer required to record an IP
142
+ address.** When it is absent the annex stores `nil`, and that nil is honest
143
+ provenance: no reviewed proxy configuration was recorded when this address
144
+ was observed. Hosts who set one still get the stronger record, the setter
145
+ still refuses anything that is not a complete prefixed SHA-2 digest, and
146
+ `clickwrap:doctor` still warns while it is unset.
147
+ - **Bundled `trackdown` is used without a wiring line.** A policy that records
148
+ IP geolocation and names no resolver now gets
149
+ `Clickwrap::IpGeolocation::TrackdownResolver` automatically when the host's
150
+ bundle carries trackdown 0.4 or newer — lazily, considered once, and only at
151
+ the moment something actually needs an address resolved. The privacy
152
+ inventory reports such a resolver with `"source": "gem_default"` and doctor
153
+ names it. An installed release older than 0.4 gets the adapter's own
154
+ upgrade sentence rather than a misleading "trackdown is not installed", and
155
+ a host with no trackdown gets the boot sentence, now naming `bundle add
156
+ trackdown` as the first option.
157
+
158
+ ### Unchanged, deliberately
159
+
160
+ - The gem's code default is still record-nothing. The switch is opt-in.
161
+ - Claim boundaries are untouched: nothing says compliant, enforceable, proves
162
+ identity, or physical location, and IP geolocation remains network context —
163
+ not identity, not GPS.
164
+ - Encryption stays on by default, and turning it off keeps its
165
+ `deliberately_store_request_evidence_unencrypted!(because:)` ceremony. That
166
+ one is a genuine hazard with a named escape hatch; the directive was about
167
+ the friction of enabling collection, not the friction of weakening it.
168
+ - Scaffolding text is still refused wherever the host actually wrote it
169
+ (`"TODO: ask legal"` is not a purpose), and a deletion clock declared
170
+ alongside `keep_recorded_..._indefinitely!` for the same category is still
171
+ refused as opposite decisions.
172
+ - Receipts still distinguish `not_configured` / `unavailable` / `recorded` /
173
+ `deleted_after_retention`, and no released receipt format changed. The
174
+ purpose provenance is deliberately kept off `to_snapshot`, so policy
175
+ revision digests and every golden fixture verify exactly as before.
176
+
177
+ ### Documentation — the request-evidence story is rewritten around the switch
178
+
179
+ - README, `guides/request-evidence.md`, `guides/integrating.md`, and
180
+ `guides/naming.md` lead with the one switch and present purposes, legal
181
+ bases, clocks, and proxy digests as the upgrade path for teams who want
182
+ reviewed records — not as the entry fee. The now-false "boot refuses without
183
+ a reason or a period" claims are gone.
184
+ - `CLAUDE.md` / `AGENTS.md` rule 6 is rewritten to the new truth and records
185
+ this directive and its date, so a future agent does not restore the
186
+ refusals as a "fix". The prohibition it keeps is the one that was always the
187
+ point: never add a switch whose *name* hides what it collects.
188
+
189
+ ### Documentation — the docs stop contradicting the shipped gem
190
+
191
+ - **The README and `guides/integrating.md` teach `gem "clickwrap"`.** Both
192
+ still told readers that `bundle add clickwrap` would fetch the empty 0.0.0
193
+ name placeholder and that they should install from GitHub instead — four
194
+ published releases after that stopped being true, and directly under a
195
+ correct Gemfile snippet in the README's own quick start.
196
+ - **The status banner says where the gem actually stands.** It claimed the gem
197
+ was "not yet proven in production" and had not been through its planned
198
+ production integrations; it now records that the gem runs in production in
199
+ more than one application — behind real signups, and under a
200
+ payout-authorization flow — and ships enabled by default in RailsFast, while
201
+ naming the two gates that genuinely remain open — the unfamiliar-developer setup
202
+ exercise, and the legal/privacy review of the default wording. The old
203
+ "don't put it under a payout flow just yet" line is replaced by the
204
+ discipline such a flow deserves: keep your existing verifier authoritative
205
+ until you have shown parity with it, bind each one-time authorization to its
206
+ subject, and test the double-submit race.
207
+ - **One `## [0.1.0]` heading in this file, not two.** The section dated
208
+ 2026-08-15 was drafted as release notes on the day the engine landed, but
209
+ 0.1.0 was not published until 2026-08-19. It now reads as the
210
+ first-implementation subsection of the release it was always part of, with
211
+ its text intact.
212
+
9
213
  ## [0.2.1] - 2026-08-19
10
214
 
11
215
  ### Added — request evidence can keep pace with the evidence it corroborates
@@ -584,7 +788,13 @@ Changes driven by the first production host application:
584
788
  tests read the signed presentation token and its controls back off the
585
789
  rendered page, the way a browser does.
586
790
 
587
- ## [0.1.0] - 2026-08-15
791
+ ### The first implementation, drafted 2026-08-15
792
+
793
+ > These notes were written on 2026-08-15, the day the engine first landed, and
794
+ > were drafted then as the release notes for 0.1.0. The release itself did not
795
+ > go out until 2026-08-19; the sections above are the four days in between.
796
+ > Everything below shipped as part of that same 0.1.0 and was never released
797
+ > on its own.
588
798
 
589
799
  First implemented release. `clickwrap` turns terms acceptance, privacy notice
590
800
  acknowledgment, consent, factual declarations, operator attestations, and
@@ -599,7 +809,7 @@ its purpose, and its retention. The gem provides evidence mechanics only: your
599
809
  application and its counsel still own the legal text, lawful basis, substantive
600
810
  validity, capacity, authority, and retention periods.
601
811
 
602
- ### Added
812
+ #### Added
603
813
 
604
814
  - **Immutable versioned documents.** `Clickwrap.document :terms, version:, from:`
605
815
  points at the files your application already owns; `bin/rails clickwrap:publish`
data/README.md CHANGED
@@ -35,7 +35,7 @@ If the evidence can't be recorded, the action doesn't happen. If the action fail
35
35
  No JavaScript package. No Redis. No background jobs. No external accounts or per-event API calls. No legal-document vendor. Just Rails, your database, and a DSL that reads like plain English.
36
36
 
37
37
  > [!IMPORTANT]
38
- > **Status: built and tested, not yet proven in production.** Everything in this README is implemented and covered by the test suite, but the gem hasn't been through its planned production integrations, an unfamiliar-developer usability test, or legal review of its default wording yet. Treat it as a release candidate for evaluation don't put it under a payout flow just yet. The [stability promise](#stability-and-upgrade-promise) applies from 0.1.0 onward.
38
+ > **Status: in production, with two gates still open.** Everything in this README is implemented and covered by the test suite; the gem runs in production in more than one application today, behind real signups and under a payout-authorization flow; and it ships enabled by default — request-evidence annex included — in the [RailsFast](https://railsfast.com/?ref=clickwrap) template. Two gates remain open: no unfamiliar developer has installed it unassisted, and no focused legal or privacy review has passed over the default wording, the receipt claim boundaries, or the request-evidence posture — so read those defaults with your own counsel rather than adopting them on trust. High-assurance flows payouts, one-time authorizations — still deserve the full discipline rather than a leap of faith: keep your existing verifier authoritative until you have demonstrated parity with it, bind every authorization to its subject and consume it exactly once, and test the double-submit race in your own application. The [stability promise](#stability-and-upgrade-promise) applies from 0.1.0 onward.
39
39
 
40
40
  ## 👨‍💻 Example
41
41
 
@@ -110,10 +110,11 @@ Clickwrap.policy :signup do
110
110
  end
111
111
  ```
112
112
 
113
- (Yes, the payload-retention decision is mandatory `clickwrap` will not silently
114
- default captured evidence or request evidence to "keep forever" and will not pick
115
- a period for you. A minimal, digest-linked disposition tombstone remains after a
116
- reviewed core deletion so the deletion itself does not become an unexplained hole.)
113
+ (A retention class that names no rule keeps the evidence, which is the same
114
+ posture request evidence takes: keeping is reversible, deleting is the explicit
115
+ act. `clickwrap` will not pick a *deletion* period for you. A minimal,
116
+ digest-linked disposition tombstone remains after a reviewed core deletion so
117
+ the deletion itself does not become an unexplained hole.)
117
118
 
118
119
  Add one macro to your model:
119
120
 
@@ -165,7 +166,7 @@ bin/rails generate clickwrap:install
165
166
  bin/rails db:migrate
166
167
  ```
167
168
 
168
- `bundle add clickwrap` would install version 0.0.0, a deliberately empty name placeholder on RubyGems install from GitHub until the first real version is published there.
169
+ `bundle add clickwrap` writes that Gemfile line and installs the gem in one command, if you would rather not edit the file yourself.
169
170
 
170
171
  The installer detects Rails authentication vs. Devise, integer vs. UUID primary keys, and your database adapter, then generates adaptive migrations, one annotated initializer, and a conventional signup policy. It emits only the tables your installation can actually write to; the capabilities that are off by default bring their own migration when you want them:
171
172
 
@@ -722,39 +723,72 @@ With the engine mounted, users can view and download their own receipts, and ope
722
723
 
723
724
  `clickwrap` always records its event ID, server time, capture channel, and policy version. That proves **what was offered and what came back**. But an agreement dispute is rarely about the words — it's "that wasn't me" — and the answer to that is request evidence: the IP address, the browser, and where in the world the request came from, bound into the same digest-linked record at the same instant. Years later, "this exact sentence was accepted from this address, on this client, from this city, at this second, in the transaction that created the account" is a different conversation from "the row says yes". **Our recommendation is to record IP + user agent + geolocation on every assent policy** — you already hold a purpose (defending the very agreement being made) and the evidence lives encrypted, in its own annex, deletable on its own schedule if your counsel ever decides so.
724
725
 
725
- What the gem refuses to do is turn it on *silently*. Every field is a separate named decision with a written purpose — there is deliberately no `maximum_evidence` switch — so the recommended posture is three explicit blocks in your initializer:
726
+ So turn it on. One line, and you are done:
727
+
728
+ ```ruby
729
+ Clickwrap.configure do |config|
730
+ config.record_request_evidence_by_default = true
731
+ end
732
+ ```
733
+
734
+ That records, on every policy: the IP address the request arrived from, the browser user agent it sent, and a coarse country / region / city estimate for that address. Add [`trackdown`](https://github.com/rameerez/trackdown) 0.4+ to your Gemfile and the geolocation half resolves itself — Clickwrap picks up the official adapter with no wiring line, so trackdown plus Cloudflare genuinely is "bundle it and flip the switch".
735
+
736
+ Nothing else is required — anywhere, at either level. **Your privacy policy owns the why; the gem records the what, honestly, and is nobody's nanny.** Clickwrap supplies its own defaults for every part you did not write:
737
+
738
+ - **Purpose.** Every recorded field carries one into the receipt. Yours if you wrote one, otherwise Clickwrap's: *"Corroborate who performed each recorded act, from where, on what client — to defend the recorded agreement itself."* The [privacy inventory](#operations) marks which of the two it is reading back (`"purpose_source": "gem_default"` vs `"host"`), so a gem sentence never passes for a decision your team reviewed.
739
+ - **How long.** No clock means it keeps pace with the evidence it corroborates — the same posture core evidence has had since 0.2.0. A corroboration scheduled to expire before the agreement it corroborates is a scheduled weakening of the record.
740
+ - **Proxy provenance.** `trusted_proxy_configuration_digest` is recorded when you set one and left `nil` when you have not, and the `nil` is itself the honest answer: no reviewed proxy configuration was in force when this address was observed. `bin/rails clickwrap:doctor` says so out loud.
741
+
742
+ What the gem still will not do is hide what it collects behind a name. The switch is called `record_request_evidence_by_default` because that is what it does; there is deliberately no `gdpr_compliant_mode`, `maximum_evidence`, or `legal_proof`, and no flag here makes any claim about the law. It also stops exactly at the coarse trio: a postal code, coordinates, a timezone, a metro code, and an accuracy radius each stay their own separately named line, because a switch that reads "record request evidence" should not hand you coordinates you never asked for.
743
+
744
+ ### The upgrade path: reviewed records
745
+
746
+ The one-liner is the entry point, not the ceiling. Teams who want the stronger record — words their counsel signed off on, a legal basis reference, a named clock, a reviewed proxy topology — write them, and Clickwrap keeps every one of them as the host's own:
726
747
 
727
748
  ```ruby
728
749
  # clickwrap-doc-test: syntax-only — the resolver needs trackdown installed
729
750
  Clickwrap.configure do |config|
730
- config.record_ip_address_by_default = true
751
+ config.record_request_evidence_by_default = true
752
+
731
753
  config.reason_for_recording_ip_addresses_by_default =
732
754
  "Corroborate who performed each recorded act, to defend the agreement itself"
755
+ config.legal_basis_reference_for_recording_ip_addresses_by_default = "LIA-SECURITY-2026-01"
733
756
  config.keep_recorded_ip_addresses_indefinitely!(
734
757
  because: "Corroboration must live exactly as long as the evidence it corroborates")
735
758
 
736
- config.record_browser_user_agent_by_default = true
737
759
  config.reason_for_recording_browser_user_agents_by_default =
738
760
  "Corroborate the client context of each recorded act"
739
- config.keep_recorded_browser_user_agents_indefinitely!(
740
- because: "Corroboration must live exactly as long as the evidence it corroborates")
761
+ config.delete_recorded_browser_user_agents_after = 2.years
741
762
 
742
- config.record_ip_geolocation_country_by_default = true
743
- config.record_ip_geolocation_region_by_default = true
744
- config.record_ip_geolocation_city_by_default = true
745
763
  config.reason_for_recording_ip_geolocation_by_default =
746
764
  "Corroborate where each recorded act was performed from"
747
- config.keep_recorded_ip_geolocation_indefinitely!(
748
- because: "Corroboration must live exactly as long as the evidence it corroborates")
749
765
  config.ip_geolocation_resolver = Clickwrap::IpGeolocation::TrackdownResolver.new
750
766
 
767
+ config.trusted_proxy_configuration_digest =
768
+ Clickwrap.trusted_proxy_configuration_digest_for_rails_application
751
769
  config.review_default_request_evidence_configuration_on = Date.new(2027, 8, 1)
752
770
  end
753
771
  ```
754
772
 
755
- (`keep_recorded_..._indefinitely!` matches the retention default since 0.2.0 evidence keeps until deletion is an explicit reviewed act. A corroboration that expires before the agreement it corroborates is a scheduled weakening of the record; if your counsel wants a clock instead, `delete_recorded_..._after` is the same one-line decision in the other direction.)
773
+ Two things are still refused, and both are you contradicting yourself rather than leaving a blank: scaffolding text (`"TODO: ask legal"`) standing in for a purpose, and a deletion clock set alongside `keep_recorded_..._indefinitely!` for the same category.
774
+
775
+ ### Per policy, with as much or as little as you want
776
+
777
+ A single surface can name the fields itself instead of inheriting the default. The frictionless form takes no arguments at all:
778
+
779
+ ```ruby
780
+ Clickwrap.policy :withdrawal_authorization do
781
+ authorize :regulated_action, one_time: true, valid_for: 10.minutes
782
+
783
+ record_ip_address
784
+ record_browser_user_agent
785
+ record_ip_geolocation
786
+ end
787
+ ```
788
+
789
+ `record_ip_geolocation` with no field named records the same coarse trio as the switch — country, region, city. Name even one field and you are choosing the set yourself, and the set is exactly what you named.
756
790
 
757
- A single regulated surface can also name a field per policy instead of by default:
791
+ The same declarations with the full record a reviewed team would want:
758
792
 
759
793
  ```ruby
760
794
  Clickwrap.policy :regulated_authorization do
@@ -773,10 +807,13 @@ end
773
807
  Recorded values live in a separately encrypted annex with their own retention, so
774
808
  they can be deleted later without rewriting the core event payload. Core payloads
775
809
  have their own reviewed disposition path and leave a digest-linked tombstone.
776
- There is deliberately no `gdpr_compliant_mode` or `maximum_evidence` switch
777
- every field is named individually, in plain English.
810
+ Encryption is on by default for all three categories. Turning it off keeps its
811
+ own ceremony you cannot reach `encrypt_recorded_ip_addresses = false` without
812
+ first writing `config.deliberately_store_request_evidence_unencrypted!` — but the
813
+ ceremony is the method name a reviewer finds in the diff, not a sentence the gem
814
+ makes you type. `because:` there is optional too.
778
815
 
779
- For IP geolocation, [`trackdown`](https://github.com/rameerez/trackdown) 0.4 or newer is the optional official resolver:
816
+ For IP geolocation, [`trackdown`](https://github.com/rameerez/trackdown) 0.4 or newer is the official resolver, and Clickwrap uses it automatically when your bundle has it and you named no resolver of your own. Set it explicitly when you want a different provider per policy, or when you are wiring Trackdown's per-request CDN trust:
780
817
 
781
818
  ```ruby
782
819
  Trackdown.configure do |trackdown|
@@ -1237,8 +1274,11 @@ Clickwrap.configure do |config|
1237
1274
  controller.current_user == receipt.actor
1238
1275
  end
1239
1276
 
1240
- # Safe defaults: no IP address, browser user-agent, or IP geolocation is stored.
1241
- # Enable fields per policy, each with a plain-English purpose and retention rule.
1277
+ # Nothing request-derived is stored until you say so. This one line says so:
1278
+ # IP address, browser user agent, and a coarse country/region/city estimate,
1279
+ # on every policy. Per-field flags and per-policy declarations still override
1280
+ # it in either direction.
1281
+ config.record_request_evidence_by_default = true
1242
1282
 
1243
1283
  # Optional hooks run only after evidence and domain state have committed:
1244
1284
  config.after_event_is_committed = ->(event) { }
@@ -1340,7 +1380,7 @@ No — and `clickwrap` never equates scrolling with reading. It makes documents
1340
1380
 
1341
1381
  ### Should I record IP addresses?
1342
1382
 
1343
- Only for policies with a real, documented purpose. They corroborate request context; they don't prove identity or location. Everything defaults off.
1383
+ We think yes, for assent policies they are what turns "the row says yes" into "this sentence was accepted from this address, on this client, at this second". They corroborate request context; they do not prove identity or physical location. The gem's own default is still off, and turning them on is `config.record_request_evidence_by_default = true`.
1344
1384
 
1345
1385
  ### Can I keep my domain models?
1346
1386
 
@@ -23,10 +23,8 @@ survived contact with production code.
23
23
  ## 1. Install, in this exact order
24
24
 
25
25
  ```ruby
26
- # Gemfile — `bundle add clickwrap` would install 0.0.0, a deliberately empty
27
- # name placeholder on RubyGems. Install from GitHub until the first real
28
- # version is published there.
29
- gem "clickwrap", github: "rameerez/clickwrap"
26
+ # Gemfile
27
+ gem "clickwrap"
30
28
  ```
31
29
 
32
30
  ```bash
@@ -562,8 +560,11 @@ history, not just post-migration. The shape that worked:
562
560
 
563
561
  ## 9. Request evidence, when a surface earns it
564
562
 
565
- Default to collecting nothing; enable per policy, per field, when a surface
566
- has a real purpose (our money path did; our signup did not):
563
+ The gem collects nothing until you say otherwise, and since 0.3.0 saying
564
+ otherwise for every policy at once is one line
565
+ `config.record_request_evidence_by_default = true`. We took the narrower road
566
+ and enabled per policy, per field, where the surface earned it (our money path
567
+ did; our signup did not):
567
568
 
568
569
  ```ruby
569
570
  policy.record_ip_address(
@@ -574,16 +575,19 @@ policy.record_ip_address(
574
575
  )
575
576
  ```
576
577
 
577
- Enabling any IP field requires `config.trusted_proxy_configuration_digest`
578
- a digest of the effective proxy rules, not a prose label, so old evidence
579
- records which configuration was in force. Generate it from Rails' configured
580
- rules (or Rails' actual defaults when none were overridden):
578
+ `config.trusted_proxy_configuration_digest` is worth setting and is no longer
579
+ required to record an address — a digest of the effective proxy rules, not a
580
+ prose label, so old evidence records which configuration was in force. Generate
581
+ it from Rails' configured rules (or Rails' actual defaults when none were
582
+ overridden):
581
583
 
582
584
  ```ruby
583
585
  config.trusted_proxy_configuration_digest =
584
586
  Clickwrap.trusted_proxy_configuration_digest_for_rails_application
585
587
  ```
586
588
 
589
+ Leave it unset and the annex records a `nil` digest, which honestly says nobody
590
+ reviewed a proxy topology; `clickwrap:doctor` warns while that is the case.
587
591
  This records configuration provenance; it does not prove the rules were
588
592
  correctly deployed or reviewed. Sharing the same fields across several policies?
589
593
  A plain Ruby lambda in `config/clickwrap.rb` calling
data/guides/naming.md CHANGED
@@ -106,11 +106,19 @@ The same distinction runs through the six request-evidence states. `not_configur
106
106
  configuration fact; `unavailable` is an observation; `deleted_after_retention` is a third thing
107
107
  entirely. None of them is blank.
108
108
 
109
- ### 6. One option never secretly enables another category of data
109
+ ### 6. One option never *secretly* enables another category of data
110
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.
111
+ **Why:** this is the failure mode the gem exists to prevent. An option whose name does not say
112
+ what it collects makes the diff unreviewable and the upgrade dangerous — a later release can
113
+ widen what the profile covers, and nobody reading the initializer would know.
114
+
115
+ The test is the name, not the count. `config.record_request_evidence_by_default = true` does
116
+ enable three categories at once, and it is fine, because the line tells a reviewer what those
117
+ three are and the gem may never widen them: the switch is defined as the IP address, the
118
+ browser user agent, and a coarse country/region/city estimate, and a future release that
119
+ wanted to add coordinates would have to change its name. A `precision: :full` or a
120
+ `level: :enhanced` fails the test for the opposite reason — nobody reading it can say what
121
+ came back.
114
122
 
115
123
  ```ruby
116
124
  # Before
@@ -249,7 +257,7 @@ banned for a specific reason, not for taste.
249
257
  |---|---|
250
258
  | `:network`, `:full`, `:enhanced`, `:forensic`, `:maximum` | They hide what will be collected behind a word that sounds like a quality level |
251
259
  | `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 |
260
+ | `request_evidence: :network`, `track_everything`, `record_everything` | Their names do not name their contents. See rule 6, and contrast `record_request_evidence_by_default`, which does |
253
261
  | `maximum_evidence`, `full_evidence`, `legal_proof: true` | They imply a verdict the gem cannot reach, and they enable data as a side effect |
254
262
  | 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
263
  | `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:` |
@@ -283,7 +291,7 @@ object.
283
291
  | `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
292
  | `after_event_is_committed` | `after_commit` | Says which commit, and reads as a sentence |
285
293
  | `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` |
294
+ | `deliberately_store_request_evidence_unencrypted!` | `encryption: false` | Turning encryption off should be a sentence a reviewer can find in a diff not a `false`. The method NAME is the ceremony; its `because:` is optional |
287
295
 
288
296
  The last row is the pattern worth copying. When an option has a consequence somebody should
289
297
  have to think about, make the name carry the thinking.