Skip to content

Zero-friction request evidence: the gem is nobody's nanny (v0.3.1) - #6

Merged
rameerez merged 2 commits into
mainfrom
request-evidence-zero-friction
Aug 20, 2026
Merged

Zero-friction request evidence: the gem is nobody's nanny (v0.3.1)#6
rameerez merged 2 commits into
mainfrom
request-evidence-zero-friction

Conversation

@rameerez

@rameerez rameerez commented Aug 20, 2026

Copy link
Copy Markdown
Owner

Ships as v0.3.1. Follow-up to #5, on the owner's escalation. v0.3.0 was tagged and released from the #5 merge while this work was in progress, so the escalation is a follow-up release rather than part of the one it was drafted against — ## [0.3.0] in the CHANGELOG is left exactly as published, and this adds a ## [0.3.1] entry.

#5 made enabling request evidence one switch; this removes the remaining friction everywhere else it survived.

The principle

In the owner's words: the host application and its privacy policy own why data is collected; this gem records what was collected, honestly, and is nobody's nanny. Its job is evidence mechanics, not gatekeeping collection.

#5 removed the boot refusals around the initializer defaults. It left the per-policy surface, the keep-indefinitely declaration, the encryption escape hatch, and the installer still asking for sentences. This finishes the job.

Everything below now works exactly as written

Clickwrap.configure do |config|
  config.record_request_evidence_by_default = true
  config.keep_recorded_ip_addresses_indefinitely!
  config.deliberately_store_request_evidence_unencrypted!
end

Clickwrap.policy :anything do
  agree_to :terms
  record_ip_address
  record_browser_user_agent
  record_ip_geolocation
end
  • Zero keyword arguments on all three record_ verbs. because:, legal_basis_reference:, data_protection_impact_assessment_reference:, delete_after:, retain_until:, encrypted: — all optional in fact, not just in the signature. legal_basis_reference and the DPIA reference never were required anywhere in the gem; there is now a test pinning that they never become so.
  • record_ip_geolocation with no field named records the coarse trio (country, region, city) — the same set the switch turns on, nothing finer. The field keywords moved from false to nil defaults so "did not mention" is distinguishable from "named it and turned it off". Naming even one field means you chose the set; naming every field false is still refused, because calling the verb and disabling everything cannot mean anything (do_not_record_ip_geolocation is how to say that).
  • keep_recorded_*_indefinitely! takes no arguments at all.
  • deliberately_store_request_evidence_unencrypted! no longer needs a because:. The method name is the ceremony: encrypt_recorded_* = false still cannot be reached without that line, and a reviewer still finds it in the diff. Encryption itself is untouched — on by default, all three categories, with a new test pinning that the switch does not weaken it.
  • The installer stops refusing an incomplete category. --record-ip-addresses-by-default with no reason and no period writes the file and omits those two lines. Scaffolding text is still refused (a TODO written into a shipped initializer is worse than no line, and the gem would reject it at boot anyway), as is a negative day count.
  • The ReviewedText placeholder check now only ever applies to text a host actually supplied. Absence is never scaffolding.

What deliberately keeps its because:

Reasons that are not about collection: delete_recorded_ip_address! and siblings, dispose_core_event!, place_on_legal_hold! / release_legal_hold!, plan_disposition_for, unredacted receipt export, and the lifecycle verbs. Those record a destructive act, an access, or a state change — the audit trail is the reason, and there is no honest default for "why did somebody delete this". Flag this if you read the directive more broadly and want them gone too.

Also unchanged: the code default is still record-nothing, claim boundaries are untouched, receipt states stay distinct (not_configured / unavailable / recorded / deleted_after_retention), and no released receipt format changed.

Rule 6

Rewritten around the principle, and it names the directive and date so a future agent does not restore the refusals as a "fix". What it keeps as non-negotiable: no switch whose NAME hides what it collects, no overclaiming, no blurring of receipt states, encryption on by default behind its named call, host-supplied scaffolding rejected, and a clock alongside keep-indefinitely still refused as a contradiction.

Tests

bundle exec rake test829 runs, 5387 assertions, 0 failures, 0 errors. bundle exec rubocop clean. Coverage 92.46 line / 73.6 branch against floors of 91 / 71.

New: every record_ verb with no arguments; a zero-keyword policy capturing end to end and storing exactly the coarse trio; one named field meaning you chose the set; all-fields-false still refused; legal basis never required at either level; the unencrypted ceremony with and without a reason; encryption still on under the switch; and three installer cases replacing the old "incomplete category is refused" pin.

🤖 Generated with Claude Code

https://claude.ai/code/session_013A6ZmfmFuQ2z3GyvQCuECA

rameerez and others added 2 commits August 20, 2026 01:20
Owner escalation on top of the one-switch change. The principle, in their
words: the host application and its privacy policy own WHY data is
collected; this gem records WHAT was collected, honestly. Its job is
evidence mechanics, not gatekeeping collection.

So every collection-related purpose, reason, and reference is optional now,
at both levels rather than only in the initializer. `record_ip_address`,
`record_browser_user_agent`, and `record_ip_geolocation` take zero keyword
arguments in a policy. `keep_recorded_*_indefinitely!` takes none at all.
`legal_basis_reference:` and the DPIA reference never were required, and a
test now pins that they never become so.

`record_ip_geolocation` with no field named records the coarse trio —
country, region, city — the same set the switch turns on, and nothing finer.
The field keywords moved from `false` to `nil` defaults so "did not mention"
is distinguishable from "named it and turned it off"; naming every field
false is still refused, because calling the verb and disabling everything
cannot mean anything, and `do_not_record_ip_geolocation` says that properly.

`deliberately_store_request_evidence_unencrypted!` no longer needs a
`because:`. The method NAME is the ceremony: `encrypt_recorded_* = false`
still cannot be reached without writing that line, and a reviewer still
finds it in the diff. Encryption itself is untouched — on by default, all
three categories.

The install generator stops refusing an incomplete category: enabling a
field with no reason and no period writes the file and omits those lines.
Scaffolding text is still refused, because a TODO written into a shipped
initializer is worse than no line at all and the gem would reject it at boot
anyway.

Reasons that are not about collection keep their `because:` deliberately —
deletion, disposition, legal holds, unredacted export, and the lifecycle
verbs. Those record a destructive act, an access, or a state change, where
the audit trail IS the reason and there is no honest default for "why did
somebody delete this".

Rule 6 in CLAUDE.md/AGENTS.md is rewritten around the principle and keeps
the parts that are not negotiable: no switch whose name hides what it
collects, no overclaiming, no blurring of the receipt states, encryption on
by default behind its named call, host-supplied scaffolding still rejected,
and a clock alongside keep-indefinitely still refused as a contradiction.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013A6ZmfmFuQ2z3GyvQCuECA
v0.3.0 shipped and was tagged from the merge of #5 while this work was in
progress, so the escalation is a follow-up release rather than part of the
one it was drafted against.

Bumps VERSION to 0.3.1 and moves the escalation notes out of the released
0.3.0 entry into their own `## [0.3.1]` section: 0.3.0's entry describes
what 0.3.0 did and is left exactly as published. The 0.3.1 entry leads with
the principle the owner set — the host's privacy policy owns the why, the
gem records the what, and it is nobody's nanny — then lists the zero-keyword
record verbs, the coarse-trio default for a bare record_ip_geolocation, the
argument-free keep-indefinitely declarations, the optional because: on
deliberately_store_request_evidence_unencrypted!, and the installer no
longer refusing an incomplete category.

Also corrects one doc comment that dated the optional unencrypted `because:`
to 0.3.0; it ships in 0.3.1.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013A6ZmfmFuQ2z3GyvQCuECA
@rameerez rameerez changed the title Zero-friction request evidence: the gem is nobody's nanny Zero-friction request evidence: the gem is nobody's nanny (v0.3.1) Aug 20, 2026
@rameerez
rameerez merged commit 459b906 into main Aug 20, 2026
23 checks passed
@rameerez
rameerez deleted the request-evidence-zero-friction branch August 20, 2026 00:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant