Request evidence is one switch, not a checklist (v0.3.0) - #5
Merged
Conversation
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 on the way to their first capture and turned the fields off. An agreement with no corroboration is worse evidence than one corroborated under a purpose the gem stated. So `config.record_request_evidence_by_default = true` records the IP address, the browser user agent, and a coarse country/region/city estimate on every policy, and nothing finer. It is a fan-out setter over the existing per-field flags, so it composes with them in reading order, and any policy still overrides it in either direction. Everything the refusals used to demand now has an honest default. A category enabled with no purpose records the gem's own stated one, and the privacy inventory marks every entry `gem_default` or `host` so the two can never be confused. No disposal answer means the corroboration keeps pace with the evidence it corroborates — the posture core evidence has had since 0.2.0 — so the annex is stamped with no schedule and the planner never lists it. An absent trusted-proxy digest is recorded as nil, which is the honest reading that nobody reviewed a proxy topology; doctor still warns. A bundled trackdown 0.4+ is adopted as the resolver with no wiring line, lazily and only when a policy has already asked for geolocation. What did not move: the code default is still record-nothing, the claim boundaries are untouched, encryption keeps its `deliberately_store_request_evidence_unencrypted!` ceremony, scaffolding text is still refused as a purpose, a clock declared alongside keep-indefinitely is still refused as opposite decisions, and no released receipt format changed — the purpose provenance is deliberately kept off `to_snapshot` so revision digests and golden fixtures verify as before. Rule 6 in CLAUDE.md/AGENTS.md is rewritten to the new truth and records the owner directive and its date, so this does not get "fixed" back. The prohibition it keeps is the one that was always the point: never add a switch whose NAME hides what it collects. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013A6ZmfmFuQ2z3GyvQCuECA
rameerez
added a commit
that referenced
this pull request
Aug 20, 2026
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this reverses, and why
This is an owner directive that deliberately reverses part of the gem's request-evidence philosophy, so the reasoning belongs at the top rather than in a footnote.
Enabling request evidence used to demand three things before boot would succeed: a written purpose, a disposal answer, and a reviewed
trusted_proxy_configuration_digest. Each was a refusal, not a warning. The theory was that collection should never be silent. The theory held; the cost did not. Integrators hit three refusals in a row on the way to their first capture and turned the fields off instead — 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.
The one switch
Records, on every policy: the IP address, the browser user agent, and a coarse country / region / city estimate. Nothing finer — a postal code, coordinates, a timezone, a continent, a metro code, and an accuracy radius each remain their own separately named setting, and the switch can never grow a fourth field without changing its name.
It is a fan-out setter over the existing
record_*_by_defaultflags, so it composes with them in reading order (switch = truethenrecord_browser_user_agent_by_default = falsecarves one back out), and any policy still overrides it withrecord_ip_address(...)ordo_not_record_ip_address. The reader reports what is actually on rather than a remembered assignment.Gem-supplied defaults instead of boot refusals
because:/reason_for_recording_*Vocabulary::DEFAULT_REQUEST_EVIDENCE_PURPOSE. Every compiled revision still carries a purpose into every receipt, and the privacy inventory marks each"purpose_source": "gem_default"or"host"trusted_proxy_configuration_digestnil, which is itself the honest provenance: no reviewed proxy configuration was in force.clickwrap:doctorstill warnskeep_recorded_*_indefinitely!with nobecause:TrackdownResolverautomatically when the bundle carries trackdown 0.4+, lazily and only once a policy has actually asked for geolocation. No trackdown → the same boot sentence, now namingbundle add trackdownfirstAn installed trackdown older than 0.4 gets the adapter's own upgrade sentence, not a misleading "trackdown is not installed" — the adoption probe answers only the narrow question of whether the gem loads.
What deliberately did not move
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 of weakening it."TODO: ask legal"), and a deletion clock declared alongsidekeep_recorded_..._indefinitely!for the same category.not_configured/unavailable/recorded/deleted_after_retentionapart.RequestEvidencePolicy::Setting#to_snapshotand lives on the policy object instead, so policy revision digests and every golden fixture verify exactly as before.Design decisions where the brief left room
enabled_default_ip_geolocation_fields, the inventory, doctor, and the policy builder working unchanged. The reader is computed from the three flags, so it never disagrees with reality.RequestEvidencePolicyand exposed aspurpose_source_for(category).ip_geolocation_resolver_in_forcevsapplication_default_ip_geolocation_resolver. The inventory and doctor need to report the resolver without causing adoption, so describing a configuration never makes one.keep_recorded_*_indefinitely!(because:). It never had one; adding a refusal while removing refusals seemed like the wrong direction.Tests
bundle exec rake test— 820 runs, 5333 assertions, 0 failures, 0 errors.bundle exec rubocopclean. Coverage 92.47 line / 73.54 branch, up from 92.10 / 72.62 against floors of 91 / 71.Every test that pinned a removed refusal was rewritten to pin the default that replaced it, not deleted:
request_evidence_test.rb(purpose, retention rule, proxy provenance),request_evidence_keep_indefinitely_test.rb,configuration_test.rb,doctor_test.rb. New coverage for the switch's exact field set, per-field and per-policy composition in both directions,gem_defaultvshostpurpose marking in the inventory, indefinite-by-absence end to end (boot → compile → capture → planner), nil digest recorded honestly, automatic trackdown adoption (adopted / never displaces a host resolver / never triggered by describing the config / too-old release), and the surviving contradiction and scaffolding refusals.Docs
README leads with the one switch and presents the full-discipline form as the upgrade path;
guides/request-evidence.md,guides/integrating.md, andguides/naming.mdlose their now-false "boot refuses without a reason or a period" claims.CLAUDE.md/AGENTS.mdrule 6 is rewritten and records the 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 a switch whose name hides what it collects.Not merged, not tagged, not released.
🤖 Generated with Claude Code
https://claude.ai/code/session_013A6ZmfmFuQ2z3GyvQCuECA