clickwrap 0.2.1 → 0.3.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: be18f8fc09eee066b71a234a134440010c6cf2beb82987b4bbe3636dde2f9133
4
- data.tar.gz: a2c66a9be09cf999de30d890c4338e084146414cadbe0747022a3aedc8a4ef5f
3
+ metadata.gz: 504024fe0e6e146a907899092e9da9fe3f62501a2d2b251cb0f190cc72516860
4
+ data.tar.gz: 542284305afe7e79d11e5759ce2c88630982ce7ad0862866a58a50824056b4d8
5
5
  SHA512:
6
- metadata.gz: 891f939a6b827915a5d2703ecf98b0ed9b9e761d3689a1117e8ba3df6ae0450c50a3f9d53edd0cbf844e996a7be447b080070b6ab2d916409b5d1ecfdc851ddc
7
- data.tar.gz: 2387391b3d960d05dcdc783221bb5653de21406ecd0173b8fbafa1dc17d716549044a22bc719740892553b8a9c66beb8b42417c61e9bc7f585d569849fa43d9d
6
+ metadata.gz: 34500cc2c493a726afa16ed51e592102109bf56750eeb19a67624b85d0565d98ec316f45ac8653ffe2aa6f757c690577d37b34b986c71e8f5cd95fa766648b6c
7
+ data.tar.gz: 5ed6946bb7aa2319a3f27b11c40ff79a64d85759353567b1e46be342e7966e7f180eda5be79acf9229c156d5bbcf9397e2ad473184947dce2e64ec577bd615cb
data/CHANGELOG.md CHANGED
@@ -6,6 +6,119 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.3.0] - 2026-08-20
10
+
11
+ ### Changed — recording request evidence is one switch, not a checklist
12
+
13
+ Owner directive, and a deliberate reversal of part of this gem's own
14
+ philosophy. The reasoning, stated plainly so a future reader can weigh it:
15
+ the ceremony around *enabling* request evidence — a written purpose, a
16
+ disposal answer, and a reviewed trusted-proxy digest, each of them a boot
17
+ refusal — was not producing better-reviewed collection. It was producing
18
+ **no collection at all**. Integrators hit three refusals in a row on the way
19
+ to their first capture and turned the fields off, and an agreement with no
20
+ corroboration is worse evidence than one corroborated under a purpose the gem
21
+ stated on their behalf. So the friction is gone from turning collection *on*.
22
+ None of it is gone from describing collection honestly.
23
+
24
+ - **`config.record_request_evidence_by_default = true`.** One line records, on
25
+ every policy, the IP address the request arrived from, the browser user
26
+ agent it sent, and a coarse country / region / city estimate for that
27
+ address. Nothing finer: a postal code, coordinates, a timezone, a continent,
28
+ a metro code, and an accuracy radius each remain their own separately named
29
+ setting. It is a fan-out setter over the existing `record_*_by_default`
30
+ flags, so it composes with them in reading order, and any policy still
31
+ overrides it with `record_ip_address(...)` or `do_not_record_ip_address`.
32
+ The reader reports what is actually on rather than a remembered assignment.
33
+ - **A purpose is no longer the price of admission.** A category enabled
34
+ without `because:` / `reason_for_recording_*_by_default` records
35
+ `Vocabulary::DEFAULT_REQUEST_EVIDENCE_PURPOSE` — "Corroborate who performed
36
+ each recorded act, from where, on what client — to defend the recorded
37
+ agreement itself." Every compiled policy revision therefore still carries a
38
+ purpose into every receipt, and `Clickwrap::Privacy.inventory` marks each one
39
+ `"purpose_source": "gem_default"` or `"host"` so the gem's sentence can never
40
+ be mistaken for a decision somebody reviewed.
41
+ - **No disposal answer means indefinite.** The four gatekeepers that refused a
42
+ recorded field with no clock — `Configuration#validate_request_evidence_defaults!`,
43
+ the policy-level check in `RequestEvidencePolicy`, the reference validator,
44
+ and the capture-time extractor — now treat absence as "keeps pace with the
45
+ evidence it corroborates", which is what core evidence has done since 0.2.0.
46
+ The annex is stamped with no schedule and the retention planner never lists
47
+ it. `keep_recorded_*_indefinitely!` still works, still records its reason,
48
+ and now accepts no `because:` at all (defaulting to "Corroboration lives as
49
+ long as the evidence it corroborates").
50
+ - **`trusted_proxy_configuration_digest` is no longer required to record an IP
51
+ address.** When it is absent the annex stores `nil`, and that nil is honest
52
+ provenance: no reviewed proxy configuration was recorded when this address
53
+ was observed. Hosts who set one still get the stronger record, the setter
54
+ still refuses anything that is not a complete prefixed SHA-2 digest, and
55
+ `clickwrap:doctor` still warns while it is unset.
56
+ - **Bundled `trackdown` is used without a wiring line.** A policy that records
57
+ IP geolocation and names no resolver now gets
58
+ `Clickwrap::IpGeolocation::TrackdownResolver` automatically when the host's
59
+ bundle carries trackdown 0.4 or newer — lazily, considered once, and only at
60
+ the moment something actually needs an address resolved. The privacy
61
+ inventory reports such a resolver with `"source": "gem_default"` and doctor
62
+ names it. An installed release older than 0.4 gets the adapter's own
63
+ upgrade sentence rather than a misleading "trackdown is not installed", and
64
+ a host with no trackdown gets the boot sentence, now naming `bundle add
65
+ trackdown` as the first option.
66
+
67
+ ### Unchanged, deliberately
68
+
69
+ - The gem's code default is still record-nothing. The switch is opt-in.
70
+ - Claim boundaries are untouched: nothing says compliant, enforceable, proves
71
+ identity, or physical location, and IP geolocation remains network context —
72
+ not identity, not GPS.
73
+ - Encryption stays on by default, and turning it off keeps its
74
+ `deliberately_store_request_evidence_unencrypted!(because:)` ceremony. That
75
+ one is a genuine hazard with a named escape hatch; the directive was about
76
+ the friction of enabling collection, not the friction of weakening it.
77
+ - Scaffolding text is still refused wherever the host actually wrote it
78
+ (`"TODO: ask legal"` is not a purpose), and a deletion clock declared
79
+ alongside `keep_recorded_..._indefinitely!` for the same category is still
80
+ refused as opposite decisions.
81
+ - Receipts still distinguish `not_configured` / `unavailable` / `recorded` /
82
+ `deleted_after_retention`, and no released receipt format changed. The
83
+ purpose provenance is deliberately kept off `to_snapshot`, so policy
84
+ revision digests and every golden fixture verify exactly as before.
85
+
86
+ ### Documentation — the request-evidence story is rewritten around the switch
87
+
88
+ - README, `guides/request-evidence.md`, `guides/integrating.md`, and
89
+ `guides/naming.md` lead with the one switch and present purposes, legal
90
+ bases, clocks, and proxy digests as the upgrade path for teams who want
91
+ reviewed records — not as the entry fee. The now-false "boot refuses without
92
+ a reason or a period" claims are gone.
93
+ - `CLAUDE.md` / `AGENTS.md` rule 6 is rewritten to the new truth and records
94
+ this directive and its date, so a future agent does not restore the
95
+ refusals as a "fix". The prohibition it keeps is the one that was always the
96
+ point: never add a switch whose *name* hides what it collects.
97
+
98
+ ### Documentation — the docs stop contradicting the shipped gem
99
+
100
+ - **The README and `guides/integrating.md` teach `gem "clickwrap"`.** Both
101
+ still told readers that `bundle add clickwrap` would fetch the empty 0.0.0
102
+ name placeholder and that they should install from GitHub instead — four
103
+ published releases after that stopped being true, and directly under a
104
+ correct Gemfile snippet in the README's own quick start.
105
+ - **The status banner says where the gem actually stands.** It claimed the gem
106
+ was "not yet proven in production" and had not been through its planned
107
+ production integrations; it now records that the gem runs in production in
108
+ more than one application — behind real signups, and under a
109
+ payout-authorization flow — and ships enabled by default in RailsFast, while
110
+ naming the two gates that genuinely remain open — the unfamiliar-developer setup
111
+ exercise, and the legal/privacy review of the default wording. The old
112
+ "don't put it under a payout flow just yet" line is replaced by the
113
+ discipline such a flow deserves: keep your existing verifier authoritative
114
+ until you have shown parity with it, bind each one-time authorization to its
115
+ subject, and test the double-submit race.
116
+ - **One `## [0.1.0]` heading in this file, not two.** The section dated
117
+ 2026-08-15 was drafted as release notes on the day the engine landed, but
118
+ 0.1.0 was not published until 2026-08-19. It now reads as the
119
+ first-implementation subsection of the release it was always part of, with
120
+ its text intact.
121
+
9
122
  ## [0.2.1] - 2026-08-19
10
123
 
11
124
  ### Added — request evidence can keep pace with the evidence it corroborates
@@ -584,7 +697,13 @@ Changes driven by the first production host application:
584
697
  tests read the signed presentation token and its controls back off the
585
698
  rendered page, the way a browser does.
586
699
 
587
- ## [0.1.0] - 2026-08-15
700
+ ### The first implementation, drafted 2026-08-15
701
+
702
+ > These notes were written on 2026-08-15, the day the engine first landed, and
703
+ > were drafted then as the release notes for 0.1.0. The release itself did not
704
+ > go out until 2026-08-19; the sections above are the four days in between.
705
+ > Everything below shipped as part of that same 0.1.0 and was never released
706
+ > on its own.
588
707
 
589
708
  First implemented release. `clickwrap` turns terms acceptance, privacy notice
590
709
  acknowledgment, consent, factual declarations, operator attestations, and
@@ -599,7 +718,7 @@ its purpose, and its retention. The gem provides evidence mechanics only: your
599
718
  application and its counsel still own the legal text, lawful basis, substantive
600
719
  validity, capacity, authority, and retention periods.
601
720
 
602
- ### Added
721
+ #### Added
603
722
 
604
723
  - **Immutable versioned documents.** `Clickwrap.document :terms, version:, from:`
605
724
  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,37 +723,54 @@ 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, because Clickwrap supplies honest defaults for the parts 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.
756
774
 
757
775
  A single regulated surface can also name a field per policy instead of by default:
758
776
 
@@ -773,10 +791,11 @@ end
773
791
  Recorded values live in a separately encrypted annex with their own retention, so
774
792
  they can be deleted later without rewriting the core event payload. Core payloads
775
793
  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.
794
+ Encryption is on by default and turning it off keeps its own ceremony
795
+ `config.deliberately_store_request_evidence_unencrypted!(because: "…")` because
796
+ that one is a real hazard, not paperwork.
778
797
 
779
- For IP geolocation, [`trackdown`](https://github.com/rameerez/trackdown) 0.4 or newer is the optional official resolver:
798
+ 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
799
 
781
800
  ```ruby
782
801
  Trackdown.configure do |trackdown|
@@ -1237,8 +1256,11 @@ Clickwrap.configure do |config|
1237
1256
  controller.current_user == receipt.actor
1238
1257
  end
1239
1258
 
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.
1259
+ # Nothing request-derived is stored until you say so. This one line says so:
1260
+ # IP address, browser user agent, and a coarse country/region/city estimate,
1261
+ # on every policy. Per-field flags and per-policy declarations still override
1262
+ # it in either direction.
1263
+ config.record_request_evidence_by_default = true
1242
1264
 
1243
1265
  # Optional hooks run only after evidence and domain state have committed:
1244
1266
  config.after_event_is_committed = ->(event) { }
@@ -1340,7 +1362,7 @@ No — and `clickwrap` never equates scrolling with reading. It makes documents
1340
1362
 
1341
1363
  ### Should I record IP addresses?
1342
1364
 
1343
- Only for policies with a real, documented purpose. They corroborate request context; they don't prove identity or location. Everything defaults off.
1365
+ 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
1366
 
1345
1367
  ### Can I keep my domain models?
1346
1368
 
@@ -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:` |
@@ -5,11 +5,25 @@ configured actor and authentication source, the policy and application version,
5
5
  request ID when one is available. None of that is derived from the person's network or browser.
6
6
 
7
7
  It records **nothing** about the request itself — no IP address, no browser user-agent, no
8
- IP-geolocation field — unless the initializer or a policy names that exact field. There is no
9
- category switch, no profile, and no option that turns one of these on as a side effect of
10
- turning on something else.
8
+ IP-geolocation field — until the initializer or a policy says otherwise. Saying otherwise takes
9
+ one line:
11
10
 
12
- That default is evidence design, not squeamishness. Three things follow from it:
11
+ ```ruby
12
+ Clickwrap.configure do |config|
13
+ config.record_request_evidence_by_default = true
14
+ end
15
+ ```
16
+
17
+ That switch records exactly what its name says and nothing else: the IP address, the browser
18
+ user agent, and a coarse country / region / city estimate. Every finer geolocation field — a
19
+ postal code, coordinates, a timezone, a continent, a metro code, an accuracy radius — remains
20
+ its own separately named line, and no option here turns a category on as a side effect of
21
+ turning on something else. There is still no profile switch and no name that hides its
22
+ contents (`gdpr_compliant_mode`, `maximum_evidence`, `legal_proof`); a switch that reads
23
+ `record_request_evidence_by_default` is the opposite of one.
24
+
25
+ The gem's own default is still record-nothing, and that default is evidence design, not
26
+ squeamishness. Three things follow from it:
13
27
 
14
28
  - An IP address and other online identifiers can be personal data. The CJEU addressed dynamic
15
29
  IP addresses in [Breyer, Case C-582/14](https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX%3A62014CJ0582)
@@ -230,7 +244,8 @@ they tell an auditor completely different things:
230
244
 
231
245
  Two places, and the policy always wins.
232
246
 
233
- **In the initializer, for every policy.** Each field is its own setting, and each is `false`:
247
+ **In the initializer, for every policy.** Either the one switch, or the individual settings
248
+ each field has its own, and each is `false`:
234
249
 
235
250
  ```ruby
236
251
  Clickwrap.configure do |config|
@@ -242,10 +257,15 @@ Clickwrap.configure do |config|
242
257
  end
243
258
  ```
244
259
 
245
- Turning a default on without a purpose or without a deletion period is a `ConfigurationError`
246
- at the end of the `configure` block, not a warning. Enabling any
247
- `record_ip_geolocation_*_by_default` without an `ip_geolocation_resolver` is likewise a
248
- `ConfigurationError` — there would be nothing to resolve them.
260
+ Everything below the first line there is optional. A purpose you do not write becomes
261
+ Clickwrap's own stated one (marked `gem_default` in the inventory); a deletion period you do
262
+ not set means the field keeps pace with the evidence it corroborates.
263
+
264
+ Three things are still a `ConfigurationError` at the end of the `configure` block rather than a
265
+ warning: scaffolding text standing in for a purpose, a deletion clock set alongside
266
+ `keep_recorded_..._indefinitely!` for the same category, and enabling a
267
+ `record_ip_geolocation_*_by_default` field with no `ip_geolocation_resolver` configured *and*
268
+ no `trackdown` in the bundle — there would be nothing to resolve them with.
249
269
 
250
270
  **In one policy, for one flow.** This is the shape most applications want: ordinary signup
251
271
  inherits nothing, and the one consequential action opts in by name.
@@ -293,13 +313,19 @@ end
293
313
  Every keyword there is doing work:
294
314
 
295
315
  - **`because:`** is the present purpose, in a sentence someone outside engineering can read. It
296
- is stored, printed by `bin/rails clickwrap:privacy:inventory`, and required.
316
+ is stored and printed by `bin/rails clickwrap:privacy:inventory`. It is optional: a policy
317
+ that omits it records Clickwrap's own stated purpose instead, and the inventory marks that
318
+ entry `"purpose_source": "gem_default"` so nobody mistakes it for a sentence your team
319
+ reviewed. What is refused is scaffolding text — `"TODO: ask legal"` is not a purpose.
297
320
  - **`legal_basis_reference:`** and **`data_protection_impact_assessment_reference:`** are
298
321
  host-supplied pointers to your own reviewed documents. Clickwrap stores them. It does not
299
322
  read them, validate them, or endorse them.
300
- - **`delete_after:`** or **`retain_until:`** is not optional. If a policy records a field and
301
- neither the policy nor the configuration says when it goes away, capture raises a
302
- `ConfigurationError` before the row is written. There is no keep-forever default in this gem.
323
+ - **`delete_after:`** and **`retain_until:`** are both optional. When neither the policy, its
324
+ retention class, nor the configuration names a schedule, the field keeps pace with the
325
+ evidence it corroborates — the same posture the core event has and the annex is stamped
326
+ with no deadline at all, so the retention planner never lists it. Setting a clock alongside
327
+ an application-wide `keep_recorded_..._indefinitely!` for the same category is still refused:
328
+ those are opposite decisions.
303
329
  - **`fail_if_unavailable:`** (default `false`) decides whether evidence you cannot get is worse
304
330
  than no capture at all. When it is `true` and the field cannot be resolved, the capture and
305
331
  the protected action roll back together.
@@ -359,6 +385,12 @@ So "we record IP addresses" is not one decision. It is four:
359
385
  later reader identify which rules were in force — the difference between corroborating
360
386
  evidence and a number with no recorded collection context.
361
387
 
388
+ Since 0.3.0 this step is not a precondition for recording an address. Leave it unset and the
389
+ annex stores `trusted_proxy_configuration_digest` as `nil`, which is the honest reading of
390
+ the situation: nobody recorded having reviewed a proxy topology when this address was
391
+ observed. `bin/rails clickwrap:doctor` warns while it stays that way. Hosts who complete
392
+ step 4 get the stronger record; hosts who do not get an address that says what it is worth.
393
+
362
394
  If you replace the reader, you own that decision, and the receipt says so: any host-assigned
363
395
  lambda is labeled `host_configured_reader` rather than `rails_request_remote_ip`, even when the
364
396
  body is identical. Clickwrap will not claim Rails' spoof checks on your behalf.
@@ -378,6 +410,18 @@ gem and must never become one.
378
410
  bundle add trackdown --version ">= 0.4"
379
411
  ```
380
412
 
413
+ That is the whole wiring step. When your bundle carries trackdown and you have not named a
414
+ resolver of your own, Clickwrap uses `Clickwrap::IpGeolocation::TrackdownResolver` for any
415
+ policy that records IP geolocation — lazily, only at the moment something actually needs an
416
+ address resolved, and never as a collection decision on its own (nothing is resolved until a
417
+ policy has already enabled a geolocation field). `bin/rails clickwrap:privacy:inventory` reports
418
+ that resolver with `"source": "gem_default"`, and `clickwrap:doctor` names it, so an adopted
419
+ resolver never reads as a host decision. An installed trackdown older than 0.4 is not hidden
420
+ behind "the gem is missing": you get the adapter's own sentence about upgrading.
421
+
422
+ Set it explicitly when you want a different provider per policy, or when you are wiring
423
+ Trackdown's per-request CDN trust:
424
+
381
425
  ```ruby
382
426
  # clickwrap-doc-test: syntax-only — requires the optional trackdown gem installed above
383
427
  Trackdown.configure do |trackdown|