Add client-side GDPR right-to-erasure for PII encryption keys#3449
Conversation
Expose GDPR crypto-shredding through the kernel so a subject's PII encryption key can be deleted, making all PII encrypted under it permanently unreadable. - Add a DeleteEncryptionKey operation (and request) to the ICompliance gRPC service contract. - Implement it in ComplianceService by resolving the kernel IPIIManager grain via IGrainFactory (mirroring the Recommendations service). - Remove the non-functional CreateAndRegisterKeyFor from the IPIIManager grain; the append path already auto-registers keys on first use, and the operation would add a new key revision that silently orphaned any PII already encrypted under the prior revision. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reading a PII value after its encryption key was deleted previously threw MissingEncryptionKey, which crashed every read model and query touching an erased subject. Guard the release path with HasFor and surface the value as empty instead, so consumers of an erased subject keep working. Decryption of values whose key is still present is unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The client IPIIManager previously threw NotImplementedException, so an application had no way to trigger GDPR right-to-erasure. Implement it to proxy over the Chronicle client connection to the compliance service. - PIIManager.DeleteEncryptionKeyFor forms the encryption key identifier from the subject the PII was encrypted under and calls the kernel via the ICompliance gRPC contract. - Expose it as IEventStore.PII and register IPIIManager in the client service collections so applications can inject it directly. - Remove the non-functional CreateAndRegisterKeyFor from the client IPIIManager to match the kernel contract. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Unit specs assert the client PIIManager calls the compliance contract with the correctly-formed identifier for the subject. - Integration spec appends a PII event under a subject, reads it back decrypted, deletes the key through IEventStore.PII, and asserts the PII property is empty (not throwing) while non-PII content survives. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a rule to the corpus that specs (and code) should reference a concept's NotSet sentinel rather than a raw string.Empty/Guid.Empty that implicitly converts, and apply it to the PII compliance handler specs (EventStoreName.NotSet / EventStoreNamespaceName.NotSet). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
@einari could you have a look when you get a chance? 🙏 This wires up the client-side trigger for GDPR crypto-shredding — Two things I'd especially value your take on:
Not merging — leaving it for your review. |
There was a problem hiding this comment.
⚠️ Performance Alert ⚠️
Possible performance regression was detected for benchmark 'Cratis Benchmarks'.
Benchmark result of this commit is worse than the previous benchmark result exceeding threshold 1.50.
| Benchmark suite | Current: 786dc3a | Previous: 9c41e1c | Ratio |
|---|---|---|---|
Cratis.Chronicle.Benchmarks.AppendManyBenchmark.AppendManyEvents(EventCount: 100) |
171652856.56666666 ns (± 31208453.233121686) |
68128824.7 ns (± 7671149.609764319) |
2.52 |
Cratis.Chronicle.Benchmarks.AppendManyBenchmark.AppendManyEvents(EventCount: 1000) |
1164955576.3333333 ns (± 59627787.49573026) |
587439487.4 ns (± 115163188.51672608) |
1.98 |
This comment was automatically generated by workflow using github-action-benchmark.
CC: @einari
Releasing a PII value and ensuring a key on append both did HasFor followed by GetFor — two storage round-trips per key on a cold cache, on the read/decrypt hot path. Introduce a single-query TryGetFor that returns null when no key exists, implement it across every backend, and have GetFor delegate to it (throwing MissingEncryptionKey only when a caller demands the key). The PII handler now resolves the key in one call for both release and key-creation. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Follow-up: addressed the decrypt-path performance concern from above.
Implemented across all seven backends (Mongo, SQL, InMemory, Cache, Composite, HashiCorp Vault, Azure Key Vault). Verified green: handler specs, cache-store specs, Mongo encryption-key specs, and the end-to-end erasure + PII round-trip integration. One thing to weigh for the semver call: this adds a member to the public |
|
Context on the removed As shaped (
If explicit key management is ever wanted, it should come back deliberately shaped, not as this stub:
The method was scaffolded for symmetry with |
Summary
Enables GDPR right-to-erasure (crypto-shredding) from a Chronicle client application. Deleting a person's PII encryption key makes every PII value encrypted under that subject permanently unreadable — everywhere it appears. Previously the client
IPIIManageronly threwNotImplementedException, so there was no way to trigger erasure from application or ops code.Added
IPIIManager.DeleteEncryptionKeyForandIEventStore.PII.IPIIManagercan also be injected directly.Changed
Removed
CreateAndRegisterKeyForfromIPIIManager; it only ever threwNotImplementedException, and encryption keys are already registered automatically on first use.