Module: Clickwrap::Privacy

Defined in:
lib/clickwrap/privacy.rb

Overview

What the application configured, what one actor's evidence contains, and what a disposition of it would look like.

============================================================================

THIS MODULE DESCRIBES A CONFIGURATION. Describing a configuration is not the same as the configuration being lawful, proportionate, or adequately justified. inventory reads back the fields this application chose to record, the purposes it wrote down, the references it supplied, and the periods it set. It does not evaluate any of them. A complete inventory with every field filled in is a well-documented configuration, and nothing more; the lawful basis, the necessity, the proportionality, the retention period, and the answer to an erasure request all belong to the host application and its counsel.

Clickwrap::Privacy.inventory
Clickwrap::Privacy.export_for(user, requested_by: current_operator)
Clickwrap::Privacy.plan_disposition_for(user, requested_by: operator, because: "DSAR-2026-41")

Constant Summary collapse

CATEGORIES =
%i[ip_address browser_user_agent ip_geolocation].freeze
GEM_LIB_ROOT =

Used only to tell a host-supplied callback apart from the gem's own declining default when the inventory reports where a decision is made.

File.expand_path("..", __dir__)

Class Method Summary collapse

Class Method Details

.export_for(actor, requested_by:, because: nil, include_ip_address: false, include_browser_user_agent: false, include_ip_geolocation: false) ⇒ Object

One actor's evidence, under exactly the authorization and redaction rules a receipt export uses — the same because:, the same host callback, the same field-by-field opt-in, and the same recorded access. There is deliberately no privacy-flavored shortcut that reveals more than Clickwrap.export_receipt would.



56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/clickwrap/privacy.rb', line 56

def export_for(actor, requested_by:, because: nil, include_ip_address: false,
               include_browser_user_agent: false, include_ip_geolocation: false)
  reference = reference_for!(actor)

  events = Event.for_actor(reference)
                .includes(:statements, :documents, :policy_revision, :request_evidence)
                .chronological

  {
    "actor_reference" => reference,
    "generated_at" => Receipt.format_time(Clickwrap.now),
    "receipt_count" => events.size,
    "receipts" => events.map do |event|
      Receipt.export(
        Receipt.new(event),
        requested_by: requested_by,
        because: because,
        include_ip_address: include_ip_address,
        include_browser_user_agent: include_browser_user_agent,
        include_ip_geolocation: include_ip_geolocation
      )
    end,
    "current_statements" => current_statements_for(reference),
    "means" => "Every Clickwrap event recorded against this actor reference, rendered as " \
               "receipts with the same redaction rules that apply everywhere else."
  }
end

.inventoryObject

A structured description of what this application configured: every policy, every personal or request-derived field it enables, the stated purpose, the host-supplied legal-basis and DPIA references, the resolver, the encryption state, the retention rule, the host events that cannot currently be resolved, and the review date.



35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# File 'lib/clickwrap/privacy.rb', line 35

def inventory
  {
    "describes" => "configuration",
    "generated_at" => Receipt.format_time(Clickwrap.now),
    "gem_version" => Clickwrap::VERSION,
    "defaults" => default_inventory,
    "access_decisions" => access_decisions,
    "policies" => Clickwrap.policies.values.map { |policy| policy_inventory(policy) },
    "retention_classes" => Clickwrap.retention_classes.values.map { |klass| retention_inventory(klass) },
    "unresolved_host_events" => unresolved_host_events,
    "means" => "The fields this application is configured to record, the purposes it wrote " \
               "down, and the periods it set. Clickwrap does not assess whether any of them " \
               "is lawful, necessary, or long enough."
  }
end

.plan_disposition_for(actor, requested_by:, because:) ⇒ Object

Creates a reviewable disposition plan for one actor, and NOTHING else.

It does not delete anything, does not release a hold, does not decide whether an erasure request overrides a retention duty, a legal claim, or a hold, and does not tell the host which of those apply. What it does is put the whole picture in one reviewable place: what exists, what is still inside its retention period, and what is held — so the person who has to make that decision makes it with the facts in front of them, and so their decision is recorded as a plan somebody else can read.



93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/clickwrap/privacy.rb', line 93

def plan_disposition_for(actor, requested_by:, because:)
  require_reason!(because)
  reference = reference_for!(actor)

  items = actor_items(reference)
  held, due = items.partition { |item| item.status == :held }

  DispositionPlan.create!(
    kind: "actor_privacy",
    disposition_scope: {
      "kind" => "actor_privacy",
      "at" => Receipt.format_time(Clickwrap.now),
      "actor_reference" => reference,
      "items" => due.map(&:to_plan_entry)
    },
    summary: actor_summary(reference, due, held),
    item_count: due.length,
    created_by_reference: reference_for(requested_by),
    reason: because
  )
end