Context Service¶
What is a context?¶
Every call to Filter requires a context name — an arbitrary string that groups related filtering operations together. The same context name should be used for all documents or text fragments that belong to the same logical session, user, or case.
The context name is stored in the Context field of each returned Span and in FilterResult.Context, so you can trace back which context produced a given span.
Referential integrity¶
Referential integrity means that the same PII token always receives the same replacement value within the same context. Without it, a value like 123-45-6789 could be redacted as {{{REDACTED-ssn}}} in one sentence but as a different token in another, making it impossible to correlate occurrences across a document or session.
The ContextService achieves referential integrity by acting as a store keyed by:
When a token is seen for the first time in a context it is stored together with its generated replacement. On subsequent encounters the stored replacement is returned instead of generating a new one.
ContextService interface¶
type ContextService interface {
// Get returns the replacement for the given token in the given context.
// The second return value is false when no entry exists.
Get(context, token string) (string, bool)
// Put stores a token → replacement mapping under the given context.
Put(context, token, replacement string)
}
Default: InMemoryContextService¶
InMemoryContextService is the built-in implementation. It stores all data in a map[string]map[string]string protected by a sync.RWMutex, making it safe for concurrent use.
It is used automatically when you create a FilterService with NewFilterService:
Custom ContextService¶
To supply your own implementation — for example, a Redis-backed store for distributed or multi-process deployments — implement the ContextService interface and pass it to NewFilterServiceWithContext:
package main
import (
"github.com/philterd/go-phileas/pkg/policy"
"github.com/philterd/go-phileas/pkg/services"
)
// RedisContextService is an example custom implementation.
type RedisContextService struct {
// ... Redis client fields ...
}
func (r *RedisContextService) Get(context, token string) (string, bool) {
// retrieve from Redis
return "", false
}
func (r *RedisContextService) Put(context, token, replacement string) {
// store in Redis
}
func main() {
pol := &policy.Policy{
Identifiers: policy.Identifiers{
SSN: &policy.SSNFilter{},
},
}
redisSvc := &RedisContextService{ /* ... */ }
svc := services.NewFilterServiceWithContext(pol, redisSvc)
result, err := svc.Filter(pol, "session-abc", "My SSN is 123-45-6789.")
if err != nil {
panic(err)
}
_ = result
}
Multiple contexts¶
Different context names are fully isolated. The same token can have a different replacement in each context:
svc := services.NewFilterService(pol)
// Context A
r1, _ := svc.Filter(pol, "ctx-a", "SSN: 123-45-6789")
// Context B — independent store; replacement may differ
r2, _ := svc.Filter(pol, "ctx-b", "SSN: 123-45-6789")
_ = r1
_ = r2
Contexts are never automatically cleared from InMemoryContextService. If long-running processes accumulate many contexts, consider supplying a custom implementation with eviction logic (e.g., LRU or TTL).