MarainMARAIN
← Writing

August 10, 2026

The DSAR pipeline that stops at a person

Our positioning is "public where it makes sense, private where it matters." For most workloads that is a judgment call about cost, latency, and how much a piece of text would matter if it leaked. A data subject access request leaves no room for that judgment. Under GDPR and CCPA a company has 30 days to tell someone what personal data it holds on them, and the request is a demand to compile that person's data in one place. Sending that compilation to a public API would create a second copy of the problem the law exists to prevent. So we built the pipeline with the private model doing the sensitive work.

The dsar-pipeline canvas in the n8n editor: webhook, secret check, validation, governance log, discovery, private-model summary, report compilation, and the human-gate notification, ending at a 200 response
The dsar-pipeline canvas in the n8n editor: webhook, secret check, validation, governance log, discovery, private-model summary, report compilation, and the human-gate notification, ending at a 200 response

What it does

The pipeline runs at POST /webhook/dsar-intake, behind the same shared-secret header every webhook on our box uses, compared in constant time. It fails closed. A missing header returns 401, a wrong secret returns 401, and a payload without a valid requestType returns 400 with a readable error. An open DSAR intake endpoint would let anyone trigger a search of every personal record in our CRM and have the results delivered to us, which is a privacy incident of its own.

Once a request clears both checks, and before the pipeline looks up a single record, it writes a governance entry into our CRM: the subject's email, the request type, the received-at timestamp, and the statutory 30-day deadline. That ordering is deliberate. If discovery or the private model fails downstream, the compliance record already exists. It lives in the CRM rather than in the workflow engine's own memory, which has proven unreliable on at least one other webhook we run.

Discovery runs next and covers everything our CRM holds on the subject: the person record, notes, opportunities, and tasks. A subject who isn't found produces a clean, reportable outcome of "no data held" rather than an error, and the request is logged the same way as any other.

The next step is what separates this workflow from the others we've built. The collected facts go to a 7B model, qwen2.5:7b-instruct, running on our own Mac Studio and reached only over our private Tailscale network, which writes a plain-language summary of what is held and why. Those facts are the subject's name, note titles, opportunity stages, and record counts, never raw CRM payloads. If that endpoint isn't configured, or the call fails, the pipeline does not fall back to a public model. It refuses, and says so in the report. Subject data has exactly one path out of discovery, and nothing is sent when that path is unavailable.

The pipeline then compiles a governance report covering the request, the timeline, the systems searched, the records found, the private-model summary, an explicit statement of the model boundary that was honored, and a proposed response that has not been sent. The report goes to us by Telegram and email, and the pipeline stops there.

The human gate

This workflow never sends a message to the data subject. The report and the proposed response land in front of a person, who decides what to say and sends it. A DSAR response can require legal judgment about what is actually owed, and getting the scope wrong has real consequences. It is the last place we want an LLM's confident phrasing standing in for a decision nobody made. What the pipeline handles is the deadline-bound, error-prone part: find everything, log it immediately, summarize it accurately, and keep it off a public API.

The notification step fails loudly. Telegram and email are attempted independently, and the step throws an error only when both fail, so one bad channel gets reported rather than lost. Even then the governance record written at the start is unaffected.

What verification showed

We ran this against the live endpoint on 2026-08-10 rather than a mock. The secret gate rejected a missing header and a wrong secret, both with 401. A payload missing requestType came back 400 with a readable error. Against a synthetic fixture person created for the test, an access request found the person record, three notes, and one opportunity, and the private model returned a summary grounded in that data in 1264 to 1765 milliseconds, without touching a public API. Two of those three notes were governance entries the test runs had written themselves. A request for an email address with no record in the CRM came back as "subject not found" with the governance log still written, because a negative result is still a result a regulator can ask about.

Execution 473 of the dsar-pipeline workflow: discovery, the private-model summary, report compilation, and the notification step all showing green success checks, succeeded in 1.76 seconds
Execution 473 of the dsar-pipeline workflow: discovery, the private-model summary, report compilation, and the notification step all showing green success checks, succeeded in 1.76 seconds

Six of six executions succeeded with zero errors. All of the fixture data was deleted afterward: the fixture note, the two governance-log notes the test runs created, the opportunity, and the fixture person. A re-query for that email address returned nothing.

Model placement is a decision per workload rather than a company-wide policy. Many workloads tolerate a public model easily. A DSAR, where a regulator can later ask what you did with someone's personal data, does not tolerate one at all. If you want to know which of your own workflows fall on which side of that line, the audit is where that gets worked out.