Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Clickwrap's value is evidence that is still true and still verifiable years afte
3. **Released evidence formats are permanent.** Every released receipt schema, canonicalization profile, digest field, event action, and lifecycle meaning gets a golden fixture, and new versions must keep verifying old receipts. A format change means a new explicit schema and verifier, never a silent reinterpretation. Do not edit a released migration underneath an installed application; add an upgrade migration.
4. **Required writes cannot be error-isolated.** Evidence and the protected database action commit together or not at all. Optional after-commit hooks, analytics, and notifications are isolated and can never undo a committed action or stand in for one.
5. **The browser is not a policy author.** Policy key, revision, document versions, validity, subject binding, retention, and request-evidence fields are resolved server-side and rechecked at submit. Never add a hidden field, parameter, or header that lets a client choose any of them.
6. **Default to collecting nothing.** IP address, browser user-agent, and every individual IP-geolocation field stay off until a policy names them with a plain-English purpose and a retention decision. Never add an option that enables a category of personal data as a side effect of enabling something else, and never add an opaque profile switch (`gdpr_compliant_mode`, `full_evidence`, `maximum_evidence`, `legal_proof`).
6. **Collecting nothing is the gem's default; collecting the ordinary trio is one honest switch.** Every `record_*` flag ships false. Turning on the coarse trio — IP address, browser user agent, and a country/region/city estimate — is `config.record_request_evidence_by_default = true` and nothing else: the purpose and the disposal answer have gem-supplied defaults (`Vocabulary::DEFAULT_REQUEST_EVIDENCE_PURPOSE`, and no clock means it keeps pace with the evidence it corroborates), the per-field flags and per-policy `record_ip_address(...)`/`do_not_record_ip_address` still override it, and every finer geolocation field stays its own separately named one-liner. Never add a switch whose NAME hides what it collects (`gdpr_compliant_mode`, `full_evidence`, `maximum_evidence`, `legal_proof`) — that prohibition is about dishonest names and legal claims, and it survives; `record_request_evidence_by_default` says exactly what it records. **Owner directive, 2026-08-20 (v0.3.0), deliberately reversing the previous "every field needs a written purpose and a retention decision or boot fails" rule:** the ceremony was pushing integrators to collect nothing, and no evidence is worse than un-reviewed evidence. Do not "fix" this back. What is still refused: scaffolding text (`TODO`) standing in for a purpose the host actually wrote, a deletion clock set alongside a keep-indefinitely declaration for the same category, and turning encryption off without `deliberately_store_request_evidence_unencrypted!(because:)`.
7. **Names read aloud.** Complete verb-and-noun names, positive booleans, destructive methods that say exactly what they delete, `ip_address` not `ip`, `browser_user_agent` not `ua`, `ip_geolocation` not `location`, `http_request` not `context`, `recorded_at_by_server` not `signed_at`. If an example does not make sense read aloud by a developer who has never seen the gem, the name is wrong.

## Working here
Expand Down
89 changes: 89 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,95 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.3.0] - 2026-08-20

### Changed — recording request evidence is one switch, not a checklist

Owner directive, and a deliberate reversal of part of this gem's own
philosophy. The reasoning, stated plainly so a future reader can weigh it:
the ceremony around *enabling* request evidence — a written purpose, a
disposal answer, and a reviewed trusted-proxy digest, each of them a boot
refusal — was not producing better-reviewed collection. It was producing
**no collection at all**. Integrators hit three refusals in a row on the way
to their first capture and turned the fields off, and an agreement with no
corroboration is worse evidence than one corroborated under a purpose the gem
stated on their behalf. So the friction is gone from turning collection *on*.
None of it is gone from describing collection honestly.

- **`config.record_request_evidence_by_default = true`.** One line 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. Nothing finer: a postal code, coordinates, a timezone, a continent,
a metro code, and an accuracy radius each remain their own separately named
setting. It is a fan-out setter over the existing `record_*_by_default`
flags, so it composes with them in reading order, and any policy still
overrides it with `record_ip_address(...)` or `do_not_record_ip_address`.
The reader reports what is actually on rather than a remembered assignment.
- **A purpose is no longer the price of admission.** A category enabled
without `because:` / `reason_for_recording_*_by_default` records
`Vocabulary::DEFAULT_REQUEST_EVIDENCE_PURPOSE` — "Corroborate who performed
each recorded act, from where, on what client — to defend the recorded
agreement itself." Every compiled policy revision therefore still carries a
purpose into every receipt, and `Clickwrap::Privacy.inventory` marks each one
`"purpose_source": "gem_default"` or `"host"` so the gem's sentence can never
be mistaken for a decision somebody reviewed.
- **No disposal answer means indefinite.** The four gatekeepers that refused a
recorded field with no clock — `Configuration#validate_request_evidence_defaults!`,
the policy-level check in `RequestEvidencePolicy`, the reference validator,
and the capture-time extractor — now treat absence as "keeps pace with the
evidence it corroborates", which is what core evidence has done since 0.2.0.
The annex is stamped with no schedule and the retention planner never lists
it. `keep_recorded_*_indefinitely!` still works, still records its reason,
and now accepts no `because:` at all (defaulting to "Corroboration lives as
long as the evidence it corroborates").
- **`trusted_proxy_configuration_digest` is no longer required to record an IP
address.** When it is absent the annex stores `nil`, and that nil is honest
provenance: no reviewed proxy configuration was recorded when this address
was observed. Hosts who set one still get the stronger record, the setter
still refuses anything that is not a complete prefixed SHA-2 digest, and
`clickwrap:doctor` still warns while it is unset.
- **Bundled `trackdown` is used without a wiring line.** A policy that records
IP geolocation and names no resolver now gets
`Clickwrap::IpGeolocation::TrackdownResolver` automatically when the host's
bundle carries trackdown 0.4 or newer — lazily, considered once, and only at
the moment something actually needs an address resolved. The privacy
inventory reports such a resolver with `"source": "gem_default"` and doctor
names it. An installed release older than 0.4 gets the adapter's own
upgrade sentence rather than a misleading "trackdown is not installed", and
a host with no trackdown gets the boot sentence, now naming `bundle add
trackdown` as the first option.

### Unchanged, deliberately

- The gem's code default is still record-nothing. The switch is opt-in.
- Claim boundaries are untouched: nothing says compliant, enforceable, proves
identity, or physical location, and IP geolocation remains network context —
not identity, not GPS.
- Encryption stays on by default, and turning it off keeps its
`deliberately_store_request_evidence_unencrypted!(because:)` ceremony. That
one is a genuine hazard with a named escape hatch; the directive was about
the friction of enabling collection, not the friction of weakening it.
- Scaffolding text is still refused wherever the host actually wrote it
(`"TODO: ask legal"` is not a purpose), and a deletion clock declared
alongside `keep_recorded_..._indefinitely!` for the same category is still
refused as opposite decisions.
- Receipts still distinguish `not_configured` / `unavailable` / `recorded` /
`deleted_after_retention`, and no released receipt format changed. The
purpose provenance is deliberately kept off `to_snapshot`, so policy
revision digests and every golden fixture verify exactly as before.

### Documentation — the request-evidence story is rewritten around the switch

- README, `guides/request-evidence.md`, `guides/integrating.md`, and
`guides/naming.md` lead with the one switch and present purposes, legal
bases, clocks, and proxy digests as the upgrade path for teams who want
reviewed records — not as the entry fee. The now-false "boot refuses without
a reason or a period" claims are gone.
- `CLAUDE.md` / `AGENTS.md` rule 6 is rewritten to the new truth and records
this directive and its date, so a future agent does not restore the
refusals as a "fix". The prohibition it keeps is the one that was always the
point: never add a switch whose *name* hides what it collects.

### Documentation — the docs stop contradicting the shipped gem

- **The README and `guides/integrating.md` teach `gem "clickwrap"`.** Both
Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Clickwrap's value is evidence that is still true and still verifiable years afte
3. **Released evidence formats are permanent.** Every released receipt schema, canonicalization profile, digest field, event action, and lifecycle meaning gets a golden fixture, and new versions must keep verifying old receipts. A format change means a new explicit schema and verifier, never a silent reinterpretation. Do not edit a released migration underneath an installed application; add an upgrade migration.
4. **Required writes cannot be error-isolated.** Evidence and the protected database action commit together or not at all. Optional after-commit hooks, analytics, and notifications are isolated and can never undo a committed action or stand in for one.
5. **The browser is not a policy author.** Policy key, revision, document versions, validity, subject binding, retention, and request-evidence fields are resolved server-side and rechecked at submit. Never add a hidden field, parameter, or header that lets a client choose any of them.
6. **Default to collecting nothing.** IP address, browser user-agent, and every individual IP-geolocation field stay off until a policy names them with a plain-English purpose and a retention decision. Never add an option that enables a category of personal data as a side effect of enabling something else, and never add an opaque profile switch (`gdpr_compliant_mode`, `full_evidence`, `maximum_evidence`, `legal_proof`).
6. **Collecting nothing is the gem's default; collecting the ordinary trio is one honest switch.** Every `record_*` flag ships false. Turning on the coarse trio — IP address, browser user agent, and a country/region/city estimate — is `config.record_request_evidence_by_default = true` and nothing else: the purpose and the disposal answer have gem-supplied defaults (`Vocabulary::DEFAULT_REQUEST_EVIDENCE_PURPOSE`, and no clock means it keeps pace with the evidence it corroborates), the per-field flags and per-policy `record_ip_address(...)`/`do_not_record_ip_address` still override it, and every finer geolocation field stays its own separately named one-liner. Never add a switch whose NAME hides what it collects (`gdpr_compliant_mode`, `full_evidence`, `maximum_evidence`, `legal_proof`) — that prohibition is about dishonest names and legal claims, and it survives; `record_request_evidence_by_default` says exactly what it records. **Owner directive, 2026-08-20 (v0.3.0), deliberately reversing the previous "every field needs a written purpose and a retention decision or boot fails" rule:** the ceremony was pushing integrators to collect nothing, and no evidence is worse than un-reviewed evidence. Do not "fix" this back. What is still refused: scaffolding text (`TODO`) standing in for a purpose the host actually wrote, a deletion clock set alongside a keep-indefinitely declaration for the same category, and turning encryption off without `deliberately_store_request_evidence_unencrypted!(because:)`.
7. **Names read aloud.** Complete verb-and-noun names, positive booleans, destructive methods that say exactly what they delete, `ip_address` not `ip`, `browser_user_agent` not `ua`, `ip_geolocation` not `location`, `http_request` not `context`, `recorded_at_by_server` not `signed_at`. If an example does not make sense read aloud by a developer who has never seen the gem, the name is wrong.

## Working here
Expand Down
Loading
Loading