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 +4 -4
- data/CHANGELOG.md +121 -2
- data/README.md +45 -23
- data/guides/integrating.md +14 -10
- data/guides/naming.md +13 -5
- data/guides/request-evidence.md +57 -13
- data/lib/clickwrap/configuration.rb +146 -66
- data/lib/clickwrap/doctor.rb +7 -2
- data/lib/clickwrap/ip_geolocation/trackdown_resolver.rb +16 -0
- data/lib/clickwrap/privacy.rb +28 -7
- data/lib/clickwrap/request_evidence_extractor.rb +16 -27
- data/lib/clickwrap/request_evidence_policy.rb +40 -39
- data/lib/clickwrap/services/validate_policy_references.rb +7 -32
- data/lib/clickwrap/version.rb +1 -1
- data/lib/clickwrap/vocabulary.rb +27 -0
- data/lib/generators/clickwrap/install_generator.rb +7 -3
- data/lib/generators/clickwrap/templates/initializer.rb.erb +40 -22
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 504024fe0e6e146a907899092e9da9fe3f62501a2d2b251cb0f190cc72516860
|
|
4
|
+
data.tar.gz: 542284305afe7e79d11e5759ce2c88630982ce7ad0862866a58a50824056b4d8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
(
|
|
114
|
-
|
|
115
|
-
a period for you. A minimal,
|
|
116
|
-
|
|
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`
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
777
|
-
|
|
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
|
|
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
|
-
#
|
|
1241
|
-
#
|
|
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
|
-
|
|
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
|
|
data/guides/integrating.md
CHANGED
|
@@ -23,10 +23,8 @@ survived contact with production code.
|
|
|
23
23
|
## 1. Install, in this exact order
|
|
24
24
|
|
|
25
25
|
```ruby
|
|
26
|
-
# Gemfile
|
|
27
|
-
|
|
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
|
-
|
|
566
|
-
|
|
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
|
-
|
|
578
|
-
a digest of the effective proxy rules, not a
|
|
579
|
-
records which configuration was in force. Generate
|
|
580
|
-
rules (or Rails' actual defaults when none were
|
|
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
|
|
112
|
-
|
|
113
|
-
|
|
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` |
|
|
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:` |
|
data/guides/request-evidence.md
CHANGED
|
@@ -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 —
|
|
9
|
-
|
|
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
|
-
|
|
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.**
|
|
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
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
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
|
|
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:`**
|
|
301
|
-
|
|
302
|
-
|
|
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|
|