@@ -53,14 +53,16 @@ The SKI (Subject Key Identifier) extraction system uses a pluggable provider arc
53533 . ** Built-in Providers** :
5454 - ** IdemixSKIProvider** : Extracts SKI from Idemix NymPublicKey
5555 - ** IdemixNymSKIProvider** : Extracts SKI from Idemix pseudonym identities
56- - ** NoopSKIProvider** : Returns empty SKI list (used for X.509)
56+ - ** NoopSKIProvider** : Derives no SKIs, marking an identity type as deliberately excluded from
57+ cleanup. Registered for X.509 — see [ X.509 is intentionally out of scope] ( #x509-is-intentionally-out-of-scope )
5758 - ** FallbackSKIProvider** : Computes SHA256 hash of identity bytes as SKI (default)
5859
5960** Provider Registration:**
6061``` go
6162extractor := NewSKIExtractor ()
6263extractor.RegisterProvider (" idemix" , idemix.NewSKIProvider ())
6364extractor.RegisterProvider (" idemixnym" , idemixnym.NewSKIProvider (identityStore))
65+ // X.509 keys belong to the wallet, not to individual tokens: deliberately never cleaned up
6466extractor.RegisterProvider (" x509" , NewNoopSKIProvider ())
6567// Fallback provider is used for any unregistered types
6668```
@@ -72,6 +74,28 @@ extractor.RegisterProvider("x509", NewNoopSKIProvider())
72744 . If not found, uses fallback provider (SHA256 hash)
73755 . Returns list of SKI strings in hexadecimal format
7476
77+ ### X.509 is intentionally out of scope
78+
79+ X.509 is registered with ` NoopSKIProvider ` , so ** no keystore key is ever deleted for an
80+ X.509-owned token** . This is deliberate, not an unimplemented provider.
81+
82+ An X.509 owner identity is a long-lived, non-anonymous certificate: ` x509.KeyManager ` reports
83+ ` Anonymous() == false ` and always serves the same identity descriptor. Its private key therefore
84+ belongs to the ** wallet** , not to any individual token — the same key signs every token that
85+ wallet ever owns, and it remains in use long after those tokens are spent. A real X.509 SKI
86+ provider would make the cleanup sweep delete the wallet's own signing key as soon as the first of
87+ its tokens aged past the TTL, permanently breaking the wallet.
88+
89+ The Idemix providers are the opposite case: their SKIs identify a one-shot pseudonym key created
90+ for a single recipient identity. That key is dead once its token is deleted, which is exactly what
91+ makes it safe to remove.
92+
93+ ** Consequence to be aware of:** a deleted X.509-owned token still gets a row in
94+ ` token_ski_cleanups ` , because the cleanup manager records "no key material to delete" the same way
95+ it records a completed deletion. For X.509 that row means * nothing to clean* , not * keys were
96+ removed* . Do not read the ` token_ski_cleanups ` table as evidence that X.509 key material was
97+ purged.
98+
7599### Interfaces
76100
77101#### Storage Interface
@@ -264,7 +288,8 @@ cleanupManager := cleanup.NewServiceManager(
264288 - Get keystore for TMS
265289 - Derive SKIs from owner identity using appropriate provider
266290 - Delete each SKI from keystore
267- - Mark token as cleaned in database (even on partial success)
291+ - Mark token as cleaned in database **only if every key was deleted** (or if the owner type has
292+ no keys to delete at all)
2682937. **Release Leadership** : Close leadership lock
2692948. **Wait** : Sleep until next scan interval
2702959. **Repeat** : Go to step 2
@@ -281,16 +306,26 @@ The cleanup service handles errors gracefully with specific retry behavior:
281306
282307# ## Key Deletion Errors
283308
284- - **All keys fail to delete**: Token is NOT marked as cleaned; will retry on next sweep
285- - **Some keys fail to delete**: Token IS marked as cleaned (partial success); logs warnings for failed keys
286- - **No SKIs derived**: Token IS marked as cleaned to avoid infinite retries; logs warning
309+ Key deletion is **all-or-nothing** per token :
310+
311+ - **Any key fails to delete**: Token is NOT marked as cleaned; the whole token is retried on the
312+ next sweep. The returned error joins every per-key cause, so callers can inspect them with
313+ ` errors.Is` /`errors.As`
314+ - **All keys deleted**: Token IS marked as cleaned
315+ - **No SKIs derived**: Token IS marked as cleaned to avoid infinite retries; logs warning. This is
316+ the normal path for X.509 — see
317+ [X.509 is intentionally out of scope](#x509-is-intentionally-out-of-scope)
287318
288319# ## Rationale
289320
290- This error handling strategy balances reliability with forward progress :
291- - Complete failures trigger retries (transient errors may resolve)
292- - Partial successes are recorded to avoid reprocessing successfully deleted keys
293- - Empty SKI cases are marked complete to prevent infinite retry loops
321+ Marking a token cleaned while some of its key material is still in the keystore would turn a
322+ transient database error into a permanent, un-retriable key-retention hole : the token would never
323+ be selected again, so the surviving key would never be deleted. Retrying the whole token is safe
324+ and cheap because `Keystore.Delete` is idempotent — re-deleting the keys that already succeeded
325+ costs one no-op call each.
326+
327+ Empty SKI cases are still marked complete, otherwise every sweep would rescan the same tokens
328+ forever.
294329
295330# ## Other Errors
296331
@@ -395,11 +430,12 @@ Key metrics to monitor:
395430- **Error Rate**: Failed cleanup attempts (check logs for details)
396431- **Leadership Changes**: Frequency of leader election (should be stable)
397432- **Processing Time**: Duration of each cleanup sweep
398- - **Partial Failures**: Tokens with some keys deleted but not all
433+ - **Retried Tokens**: Tokens that failed at least one key deletion and are still pending. A token
434+ stuck here across many sweeps means a key deletion is failing persistently, not transiently
399435
400436**Log Levels:**
401437- `INFO` : Successful cleanup operations, manager start/stop
402- - `WARN` : Partial failures , key not found, leadership issues
438+ - `WARN` : Failed key deletions , key not found, leadership issues
403439- `DEBUG` : Detailed sweep information, SKI derivation, leadership acquisition
404440
405441# # Security Considerations
@@ -408,7 +444,11 @@ Key metrics to monitor:
408444- **Idempotency**: Safe to retry cleanup operations
409445- **Audit Trail**: `token_ski_cleanups` table provides cleanup history with timestamps and instance tracking
410446- **Key Isolation**: Only deletes keys for deleted tokens, never active tokens
411- - **Partial Success Handling**: Prevents infinite retries while maintaining audit trail
447+ - **All-or-Nothing Marking**: A token is only recorded as cleaned once *all* of its keys are gone,
448+ so a failed deletion can never be silently forgotten
449+ - **X.509 Exclusion**: X.509 key material is never deleted by design — a `token_ski_cleanups` row
450+ for an X.509-owned token means "nothing to clean". See
451+ [X.509 is intentionally out of scope](#x509-is-intentionally-out-of-scope)
412452- **Instance Tracking**: `cleaned_by` field records which instance performed cleanup
413453
414454# # Comparison with Recovery Service
0 commit comments