Skip to content

Request evidence is one switch, not a checklist (v0.3.0) - #5

Merged
rameerez merged 1 commit into
mainfrom
request-evidence-one-switch
Aug 20, 2026
Merged

Request evidence is one switch, not a checklist (v0.3.0)#5
rameerez merged 1 commit into
mainfrom
request-evidence-one-switch

Conversation

@rameerez

Copy link
Copy Markdown
Owner

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

Clickwrap.configure do |config|
  config.record_request_evidence_by_default = true
end

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_default flags, so it composes with them in reading order (switch = true then record_browser_user_agent_by_default = false carves one back out), 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.

Gem-supplied defaults instead of boot refusals

Used to refuse Now
Enabled field with no because: / reason_for_recording_* Records 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"
Recorded field with no clock (4 separate gatekeepers) Keeps pace with the evidence it corroborates, as core evidence has since 0.2.0. No schedule stamped; the retention planner never lists it
IP recording with no trusted_proxy_configuration_digest Records nil, which is itself the honest provenance: no reviewed proxy configuration was in force. clickwrap:doctor still warns
keep_recorded_*_indefinitely! with no because: Optional; defaults to "Corroboration lives as long as the evidence it corroborates"
Geolocation enabled with no resolver Uses TrackdownResolver automatically 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 naming bundle add trackdown first

An 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

  • The gem's code default is still record-nothing. The switch is opt-in.
  • Claim boundaries untouched: nothing says compliant, enforceable, proves identity, or physical location. Geolocation stays network context, not identity, not GPS.
  • Encryption stays on by default and 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 of weakening it.
  • Still refused: scaffolding text standing in for a purpose the host actually wrote ("TODO: ask legal"), and a deletion clock declared alongside keep_recorded_..._indefinitely! for the same category.
  • Receipts still keep not_configured / unavailable / recorded / deleted_after_retention apart.
  • No released receipt format changed. Purpose provenance is deliberately kept off RequestEvidencePolicy::Setting#to_snapshot and 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

  • Fan-out setter, not an OR'd mode flag. Order-dependent composition is predictable and leaves 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.
  • Purpose provenance is not in the snapshot. Comparing the stored string against the constant would mislabel a host who copied the gem's sentence; adding a field to the snapshot would change revision digests. It is tracked on RequestEvidencePolicy and exposed as purpose_source_for(category).
  • ip_geolocation_resolver_in_force vs application_default_ip_geolocation_resolver. The inventory and doctor need to report the resolver without causing adoption, so describing a configuration never makes one.
  • No new placeholder refusal on keep_recorded_*_indefinitely!(because:). It never had one; adding a refusal while removing refusals seemed like the wrong direction.
  • The generator still asks about each category one at a time, with the prose corrected to say the gem would have accepted the fields without them — the installer asks because a sentence your team wrote beats the honest default.

Tests

bundle exec rake test820 runs, 5333 assertions, 0 failures, 0 errors. bundle exec rubocop clean. 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_default vs host purpose 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, and guides/naming.md lose their now-false "boot refuses without a reason or a period" claims. CLAUDE.md / AGENTS.md rule 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

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
rameerez merged commit abff914 into main Aug 20, 2026
23 checks passed
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
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