Class: Insika::Commands::ForgetCustomer

Inherits:
Object
  • Object
show all
Includes:
SessionPurge
Defined in:
lib/insika/commands/forget_customer.rb

Overview

Control command (WS8, phase 2 — LGPD): purges what the engine holds about ONE customer — the customer's memory cell, their sessions (found through the customer var the Executor stamps on a tagged conversation) and everything those sessions left behind (traces, tasks, checkpoints, outbox deliveries — see SessionPurge). The scope string IS the isolation boundary, so zeroing it cannot touch another customer's or another tenant's data. Operator-only BY CONSTRUCTION: the generic command ingress is operator-grade (a tenant principal never reaches it).

WHICH TENANT is the whole correctness of the purge: the memory cell is "memory::" and the sessions are the ":" ones. It is read from the command meta (an internal caller acting AS a tenant) or from the payload ({ customer:, tenant: } — the operator naming it over HTTP, where the principal has no tenant of its own). Absent from both, the purge is deployment-wide: the untagged memory cell plus that customer's sessions in EVERY tenant — right for a single-tenant deployment, and never what a multi-tenant operator means, so they must name the tenant.

Instance Method Summary collapse

Methods included from SessionPurge

#purge_sessions

Constructor Details

#initialize(memory_store:, session_store:, tool_trace_store: nil, context_trace_store: nil, model_visible_trace_store: nil, task_store: nil, checkpoint_store: nil, outbox_store: nil, shadow_pairs: nil, audit_store: nil, event_stream:, followup_store: nil, contact_store: nil, proposal_store: nil) ⇒ ForgetCustomer

Returns a new instance of ForgetCustomer.



27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/insika/commands/forget_customer.rb', line 27

def initialize(memory_store:, session_store:, tool_trace_store: nil,
               context_trace_store: nil, model_visible_trace_store: nil,
               task_store: nil, checkpoint_store: nil,
               outbox_store: nil, shadow_pairs: nil, audit_store: nil, event_stream:,
               followup_store: nil, contact_store: nil, proposal_store: nil)
  @memory_store = memory_store
  @session_store = session_store
  @tool_trace_store = tool_trace_store
  @context_trace_store = context_trace_store
  @model_visible_trace_store = model_visible_trace_store #  ; nil = parity
  @task_store = task_store
  @checkpoint_store = checkpoint_store
  @outbox_store = outbox_store
  @shadow_pairs = shadow_pairs
  @audit_store = audit_store
  @event_stream = event_stream
  @followup_store = followup_store #  ; nil = nothing to sweep
  @contact_store = contact_store   #  ; nil = nothing to sweep
  @proposal_store = proposal_store #  ; nil = nothing to sweep
end

Instance Method Details

#call(command) ⇒ Object

-> { customer:, tenant:, memory_records:, sessions: [], tasks:, checkpoints:, deliveries:, followups:, contacts: }.

Raises:



50
51
52
53
54
55
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
83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/insika/commands/forget_customer.rb', line 50

def call(command)
  customer = Coercion.presence(command.payload[:customer] || command.payload["customer"])
  raise ValidationError, "customer is required" if customer.nil?

  tenant = command.meta[:tenant] ||
           Coercion.presence(command.payload[:tenant] || command.payload["tenant"])
  memory_scope = [tenant, customer].compact.join(":")
  memory_records = @memory_store.purge(tenant: memory_scope)

  # the follow-up footprint dies with the person — the
  # schedule records and the contact cell (LGPD).
  followups = @followup_store&.purge_customer(tenant: tenant, customer: customer) || 0
  contacts = @contact_store&.delete(tenant: tenant, customer: customer) ? 1 : 0

  # the distilled PROPOSALS die with the person — a proposal
  # is born inside a customer cell (D6), so forget_customer reaches it.
  proposals = @proposal_store&.purge_customer(tenant: tenant, customer: customer) || 0

  sessions = session_ids_for(customer, tenant)
  purged = purge_sessions(sessions)

  # the audit records the thing that happened, content-free —
  # a digest-free line with the counts (the deleted VALUES never enter
  # the audit store). Written AFTER the purge, so the line describes a
  # deletion that actually happened. nil audit_store = no-op.
  @audit_store&.record(
    cell: @memory_store.cell_for(tenant, customer),
    action: "purge", actor: operator(command), tenant: tenant, customer: customer,
    note: "memory_records: #{memory_records}, sessions: #{sessions.size}"
  )

  @event_stream.emit(Insika::Event.new(
                       type: :customer_forgotten,
                       data: { customer: customer, tenant: tenant,
                               memory_records: memory_records,
                               sessions: sessions,
                               followups: followups,
                               contacts: contacts,
                               proposals: proposals }.merge(purged),
                       meta: { at: Time.now.utc.iso8601 }
                     ))
  { customer: customer, tenant: tenant, memory_records: memory_records,
    sessions: sessions, followups: followups, contacts: contacts,
    proposals: proposals }.merge(purged)
end