A Spring Boot starter for masking sensitive data, sanitizing logs, tracing privacy operations, and operating dead-letter workflows securely.
- Latest published release:
v0.5.0 - Current branch version:
0.5.0 - Active maintenance currently stops at
v0.5.0;docs/ROADMAP.mdis retained only as an archive of historical planning notes. - Newly completed in the current development cycle: tenant-native single-entry dead-letter delete / replay, dedicated by-id capability flags and write-path telemetry, stable tenant alert route / delivery / monitoring policy resolution, and expanded sample policy / observability visibility
- Best sources for current project status:
CHANGELOG.mdfor shipped changes,docs/INDEX.mdfor the doc map, anddocs/ROADMAP.mdfor archived planning context
privacy-guard-core/: masking annotations, built-in sensitive types, text sanitization, and masking SPI contracts.privacy-guard-spring-boot-starter/: Spring Boot auto-configuration, Jackson integration, Logback integration, audit storage, dead-letter handling, webhook/email alerts, and receiver verification support.samples/privacy-demo/: runnable sample covering masking, auditing, dead-letter operations, signed alert delivery, and verified receiver flows.
- Field masking with
@SensitiveDataand customMaskingStrategy - Tenant-aware privacy policies with per-tenant fallback mask characters, text rules, and Logback MDC/structured key selection
- Safe JSON serialization and free-text log sanitization
- Audit recording, querying, pagination, sorting, and statistics
IN_MEMORYandJDBCrepositories for audits and dead letters- Async publishing, batching, retries, dead letters, replay, and export/import
- Actuator health checks and Micrometer metrics for backlog and alert delivery
- Signed webhook/email alerts plus reusable webhook verifier, replay store, filter, and interceptor support
Requirements:
- Java
17+ - Spring Boot
3.3.x
Add the starter:
<dependency>
<groupId>io.github.koyan9</groupId>
<artifactId>spring-privacy-guard-spring-boot-starter</artifactId>
<version>0.5.0</version>
</dependency>The current branch and the latest published artifact are aligned to v0.5.0.
Example model:
public record PatientView(
@SensitiveData(type = SensitiveType.NAME) String patientName,
@SensitiveData(type = SensitiveType.PHONE) String phone,
@SensitiveData(type = SensitiveType.ID_CARD) String idCard,
@SensitiveData(type = SensitiveType.EMAIL) String email
) {
}Minimal config:
privacy:
guard:
fallback-mask-char: "*"
audit:
repository-type: IN_MEMORYRecommended quick-start paths:
- Basic starter integration: start with the model and minimal config above, then review
## Key Config - Multi-tenant integration: read
docs/MULTI_TENANT_GUIDE.md - Tenant observability, health, and alerting: read
docs/TENANT_OBSERVABILITY_GUIDE.md - Runnable demo, receiver verification, and multi-instance rehearsal: read
samples/privacy-demo/README.md - Published release scope and archived planning context: read
CHANGELOG.md,docs/INDEX.md, anddocs/ROADMAP.md
| Property | Default | Notes |
|---|---|---|
privacy.guard.enabled |
true |
Master switch for the starter. |
privacy.guard.logging.enabled |
true |
Enables privacy-safe logging helpers. |
privacy.guard.logging.mdc.enabled |
false |
Enables masking MDC values for Logback events. |
privacy.guard.logging.mdc.include-keys |
empty | Optional allowlist of MDC keys to sanitize. |
privacy.guard.logging.mdc.exclude-keys |
empty | Optional denylist of MDC keys to skip. |
privacy.guard.logging.structured.enabled |
false |
Enables masking structured logging fields. |
privacy.guard.logging.structured.include-keys |
empty | Optional allowlist of structured keys to sanitize. |
privacy.guard.logging.structured.exclude-keys |
empty | Optional denylist of structured keys to skip. |
privacy.guard.tenant.enabled |
false |
Enables tenant-aware privacy policy resolution and request-header tenant propagation. |
privacy.guard.tenant.header-name |
X-Privacy-Tenant |
Request header used to populate the current tenant in servlet applications. |
privacy.guard.tenant.default-tenant |
empty | Fallback tenant identifier when no tenant header is present. |
privacy.guard.masking.text.email-pattern |
empty | Override email regex used by text masking. |
privacy.guard.masking.text.phone-pattern |
empty | Override phone regex used by text masking. |
privacy.guard.masking.text.id-card-pattern |
empty | Override ID card regex used by text masking. |
privacy.guard.masking.text.additional-patterns |
empty | Extra text masking rules mapped to a SensitiveType. |
privacy.guard.audit.enabled |
true |
Enables audit services and publishers. |
privacy.guard.audit.repository-type |
NONE |
NONE, IN_MEMORY, or JDBC. |
privacy.guard.audit.async.thread-pool-size |
1 |
Thread pool size for async/batch audit executor. |
privacy.guard.audit.dead-letter.repository-type |
NONE |
Dead-letter storage type. |
privacy.guard.audit.dead-letter.observability.health.tenant-enabled |
false |
Exposes a tenant-aware dead-letter health indicator when Actuator is present. |
privacy.guard.audit.dead-letter.observability.health.tenant-ids |
empty | Optional tenant IDs included in the tenant-aware dead-letter health indicator. |
privacy.guard.audit.dead-letter.observability.health.warning-threshold |
1 |
Backlog warning threshold. |
privacy.guard.audit.dead-letter.observability.health.down-threshold |
100 |
Backlog down threshold. |
privacy.guard.audit.dead-letter.observability.alert.enabled |
false |
Enables dead-letter alerting. |
privacy.guard.audit.dead-letter.observability.alert.tenant.enabled |
false |
Enables tenant-scoped dead-letter alert monitoring for the configured tenant list. |
privacy.guard.audit.dead-letter.observability.alert.tenant.tenant-ids |
empty | Optional allowlist of tenants monitored by tenant-scoped dead-letter alert routing. |
privacy.guard.tenant.policies.*.observability.dead-letter.alert.* |
empty | Preferred tenant-scoped webhook, email, and receiver route overrides for dead-letter alerts. |
privacy.guard.tenant.policies.*.observability.dead-letter.alert.{logging,webhook,email}.enabled |
empty | Optional tenant-scoped delivery toggles that override the global logging/webhook/email alert defaults. |
privacy.guard.audit.dead-letter.observability.alert.tenant.routes |
empty | Legacy tenant route override map kept for backward compatibility. |
privacy.guard.audit.dead-letter.observability.alert.webhook.url |
empty | Enables built-in webhook alerts. |
privacy.guard.audit.dead-letter.observability.alert.webhook.backoff-policy |
FIXED |
Retry backoff policy for webhook alerts. |
privacy.guard.audit.dead-letter.observability.alert.webhook.max-backoff |
backoff*10 (max 30s) |
Max delay for webhook retry backoff when exponential policy is used. |
privacy.guard.audit.dead-letter.observability.alert.webhook.jitter |
0 |
Jitter factor (0-1) applied to webhook retry delays. |
privacy.guard.audit.dead-letter.observability.alert.email.to |
empty | Enables built-in email alerts. |
privacy.guard.audit.dead-letter.observability.alert.receiver.filter.enabled |
false |
Enables the built-in receiver verification filter. |
privacy.guard.audit.dead-letter.observability.alert.receiver.interceptor.enabled |
false |
Enables the built-in receiver verification interceptor. |
privacy.guard.audit.dead-letter.observability.alert.receiver.verification.enabled |
false |
Creates receiver verification settings from properties. |
privacy.guard.audit.dead-letter.observability.alert.receiver.verification.bearer-token |
empty | Optional bearer token required by the built-in verifier. |
privacy.guard.audit.dead-letter.observability.alert.receiver.verification.signature-secret |
empty | Optional HMAC secret required by the built-in verifier. |
privacy.guard.audit.dead-letter.observability.alert.receiver.verification.max-skew |
5m |
Allowed timestamp skew for receiver verification. |
privacy.guard.audit.dead-letter.observability.alert.receiver.replay-store.file.enabled |
false |
Enables the file-backed replay store for receiver verification. |
privacy.guard.audit.dead-letter.observability.alert.receiver.replay-store.file.path |
privacy-audit-webhook-replay-store.json |
File path for replay store entries. |
privacy.guard.audit.dead-letter.observability.alert.receiver.replay-store.namespace |
empty | Optional namespace prefixed to replay-store nonces to separate receiver domains on a shared backend. |
privacy.guard.audit.dead-letter.observability.alert.receiver.replay-store.redis.enabled |
false |
Enables the Redis-backed replay store for receiver verification. |
privacy.guard.audit.dead-letter.observability.alert.receiver.replay-store.redis.key-prefix |
privacy:audit:webhook:replay: |
Redis key prefix for replay store entries. |
privacy.guard.audit.dead-letter.observability.alert.receiver.replay-store.redis.scan-batch-size |
500 |
Redis SCAN batch size used by replay store snapshot and clear operations. |
privacy.guard.audit.dead-letter.observability.alert.receiver.replay-store.jdbc.enabled |
false |
Enables the JDBC replay store for receiver verification. |
privacy.guard.audit.dead-letter.observability.alert.receiver.replay-store.jdbc.table-name |
privacy_audit_webhook_replay_store |
JDBC table name for replay store entries. |
privacy.guard.audit.dead-letter.observability.alert.receiver.replay-store.jdbc.initialize-schema |
false |
Runs the replay store schema initializer. |
privacy.guard.audit.dead-letter.observability.alert.receiver.replay-store.jdbc.cleanup-interval |
5m |
Minimum interval between replay store cleanup passes. |
privacy.guard.audit.jdbc.tenant-column-name |
empty | Optional dedicated JDBC tenant column used by the audit repository for tenant-native writes and reads. |
privacy.guard.audit.jdbc.tenant-detail-key |
tenantId |
Audit detail key copied into the optional audit tenant column when configured. |
privacy.guard.audit.dead-letter.jdbc.tenant-column-name |
empty | Optional dedicated JDBC tenant column used by the dead-letter repository for tenant-native writes and reads. |
privacy.guard.audit.dead-letter.jdbc.tenant-detail-key |
tenantId |
Dead-letter detail key copied into the optional tenant column when configured. |
For the full property matrix, examples, and advanced options, see the sections below.
Implement MaskingStrategy when built-in SensitiveType rules are not enough:
@Bean
MaskingStrategy customNameMaskingStrategy() {
return new MaskingStrategy() {
@Override
public boolean supports(MaskingContext context) {
return context.sensitiveType() == SensitiveType.NAME;
}
@Override
public String mask(String value, MaskingContext context) {
return "[custom]" + value;
}
};
}Public extension points that are intended to remain compatible within the current minor line are marked with @StableSpi.
Stable interfaces include:
MaskingStrategyPrivacyTenantProviderPrivacyTenantContextScopePrivacyTenantContextSnapshotPrivacyTenantPolicyResolverPrivacyTenantAwareMaskingStrategyPrivacyTenantAuditPolicyResolverPrivacyTenantLoggingPolicyResolverPrivacyTenantDeadLetterObservabilityPolicyResolverPrivacyTenantDeadLetterAlertDeliveryPolicyResolverPrivacyTenantDeadLetterAlertRoutePolicyResolverPrivacyTenantAuditReadRepositoryPrivacyTenantAuditDeadLetterReadRepositoryPrivacyTenantAuditWriteRepositoryPrivacyTenantAuditDeadLetterWriteRepositoryPrivacyTenantAuditDeadLetterDeleteRepositoryPrivacyTenantAuditDeadLetterReplayRepositoryPrivacyAuditPublisherPrivacyAuditRepository,PrivacyAuditQueryRepository, andPrivacyAuditStatsRepositoryPrivacyAuditDeadLetterRepository,PrivacyAuditDeadLetterStatsRepository, andPrivacyAuditDeadLetterHandlerPrivacyAuditDeadLetterAlertCallbackPrivacyAuditDeadLetterWebhookReplayStorePrivacyAuditDeadLetterWebhookAlertTelemetryPrivacyAuditDeadLetterWebhookVerificationTelemetry
Directly coupled carrier types such as MaskingContext, TextMaskingRule, PrivacyTenantPolicy, PrivacyTenantAuditPolicy, PrivacyTenantLoggingPolicy, PrivacyTenantDeadLetterObservabilityPolicy, PrivacyTenantDeadLetterAlertDeliveryPolicy, PrivacyTenantDeadLetterAlertRoutePolicy, PrivacyTenantDeadLetterAlertWebhookPolicy, PrivacyTenantDeadLetterAlertEmailPolicy, PrivacyTenantDeadLetterAlertReceiverPolicy, PrivacyTenantAuditWriteRequest, PrivacyTenantAuditDeadLetterWriteRequest, PrivacyAuditEvent, PrivacyAuditQueryCriteria, PrivacyAuditDeadLetterEntry, and replay-store snapshot records are also marked with @StableSpi.
Repository implementations, auto-configuration classes, servlet adapters, metrics binders, and schema helpers are internal runtime wiring rather than stable SPI.
Enable tenant-aware masking when different tenants need different fallback mask characters or text detection rules:
privacy:
guard:
tenant:
enabled: true
header-name: X-Privacy-Tenant
default-tenant: public
policies:
tenant-a:
fallback-mask-char: "#"
text:
additional-patterns:
- type: GENERIC
pattern: EMP\d{4}
tenant-b:
fallback-mask-char: XWhen tenant mode is enabled in a servlet application, the starter registers a request filter that reads the configured tenant header into the current thread context for JSON masking, log sanitization, and audit sanitization.
Custom masking beans can inspect the active tenant by implementing PrivacyTenantAwareMaskingStrategy.
Tenant policies can also narrow audit details per tenant:
privacy:
guard:
tenant:
policies:
tenant-a:
audit:
include-detail-keys:
- phone
- employeeCode
attach-tenant-id: true
tenant-detail-key: tenant
tenant-b:
audit:
include-detail-keys:
- phone
attach-tenant-id: true
tenant-detail-key: tenantinclude-detail-keys is applied before exclude-detail-keys, and the tenant detail tag is appended after sanitization when enabled.
Tenant policies can also override which MDC or structured logging keys are sanitized for that tenant:
privacy:
guard:
tenant:
policies:
tenant-a:
logging:
mdc:
enabled: true
include-keys:
- email
- phone
tenant-b:
logging:
structured:
enabled: true
include-keys:
- phoneWhen a tenant logging override is present, its enabled, include-keys, and exclude-keys values replace the global MDC or structured logging selection for that tenant. Unset tenant logging fields inherit the global logging settings.
Tenant policies can also override dead-letter backlog thresholds and recovery notifications per tenant:
privacy:
guard:
tenant:
policies:
tenant-a:
observability:
dead-letter:
warning-threshold: 1
down-threshold: 2
notify-on-recovery: true
tenant-b:
observability:
dead-letter:
warning-threshold: 2
down-threshold: 4
notify-on-recovery: falseThese overrides only affect the tenant-scoped dead-letter backlog snapshots, tenant health summary, and tenant alert monitor. Unset fields fall back to the existing global dead-letter observability settings.
For the full request/header contract, stable SPI customization points, and tenant-scoped helper and facade usage, see docs/MULTI_TENANT_GUIDE.md.
The starter supports:
- async and batched audit publishing
- retry with dead-letter fallback
- dead-letter query, cleanup, replay, export, and import
- operation-audit traces for management activity
- tenant-aware audit query and stats via
PrivacyTenantAuditQueryService - tenant-aware dead-letter query and stats via
PrivacyTenantAuditDeadLetterQueryService - tenant-aware dead-letter batch delete and replay via
PrivacyTenantAuditDeadLetterOperationsService - tenant-aware dead-letter export, import, and manifest flows via
PrivacyTenantAuditDeadLetterExchangeService - unified tenant-aware management entry point via
PrivacyTenantAuditManagementService - explicit tenant-aware single dead-letter delete and replay by
idviaPrivacyTenantAuditManagementServiceand the dead-letter helper layer - optional tenant-scoped dead-letter alert monitoring through
PrivacyTenantAuditDeadLetterAlertMonitorandPrivacyTenantAuditDeadLetterAlertCallback
Sample dead-letter endpoints include:
GET /audit-dead-lettersGET /audit-dead-letters/statsDELETE /audit-dead-letters/{id}DELETE /audit-dead-letters/{id}?tenant=tenant-aPOST /audit-dead-letters/{id}/replayPOST /audit-dead-letters/{id}/replay?tenant=tenant-bPOST /audit-dead-letters/replay?limit=100
When Actuator is present, the starter can expose:
privacyAuditDeadLettershealthprivacy.audit.deadletters.totalprivacy.audit.deadletters.state{state=*}privacy.audit.deadletters.threshold{level=*}privacy.audit.deadletters.alert.webhook.attemptsprivacy.audit.deadletters.alert.webhook.retriesprivacy.audit.deadletters.alert.webhook.deliveries{outcome=*}privacy.audit.deadletters.alert.webhook.last_delivery_seconds{outcome=*}privacy.audit.deadletters.alert.webhook.failures{type=*,retryable=*,category=*}privacy.audit.deadletters.alert.webhook.last_failure_statusprivacy.audit.deadletters.alert.webhook.last_failure_retryableprivacy.audit.deadletters.alert.webhook.last_failure_type{type=*}privacy.audit.deadletters.receiver.replay_store.countprivacy.audit.deadletters.receiver.replay_store.expiring_soonprivacy.audit.deadletters.receiver.replay_store.expiry_seconds{kind=*}privacy.audit.deadletters.receiver.replay_store.cleanup.last_countprivacy.audit.deadletters.receiver.replay_store.cleanup.last_duration_msprivacy.audit.deadletters.receiver.replay_store.cleanup.last_timestampprivacy.audit.deadletters.receiver.verification.failures{reason=*}privacy.audit.tenant.read.path{domain=*,path=*}privacy.audit.tenant.read.path{domain=dead_letter_export,path=*}privacy.audit.tenant.read.path{domain=dead_letter_manifest,path=*}privacy.audit.tenant.write.path{domain=*,path=*}privacy.audit.tenant.write.path{domain=dead_letter_import,path=*}privacy.audit.deadletters.alert.tenant.transitions{tenant=*,state=*,recovery=*}privacy.audit.deadletters.alert.tenant.deliveries{tenant=*,channel=*,outcome=*}privacy.audit.deadletters.receiver.route.failures{route=*,reason=*}
privacy:
guard:
audit:
dead-letter:
observability:
alert:
enabled: true
webhook:
url: https://example.com/privacy-alerts
bearer-token: demo-token
signature-secret: demo-hmac-secret
signature-algorithm: HmacSHA256
signature-header: X-Privacy-Alert-Signature
timestamp-header: X-Privacy-Alert-Timestamp
nonce-header: X-Privacy-Alert-Nonce
max-attempts: 3
backoff: 200msThe built-in webhook callback retries failed deliveries, signs timestamp.nonce.payload, and records delivery metrics when Micrometer is available.
When delivery fails, the callback logs include failure type, status code, retryable flag, and a short error message.
When tenant alert monitoring is enabled, the built-in tenant webhook wrapper also includes tenantId in the alert payload.
You can also route specific tenants to different webhook targets:
privacy:
guard:
tenant:
policies:
tenant-a:
observability:
dead-letter:
alert:
webhook:
url: https://tenant-a.example.com/privacy-alerts
bearer-token: tenant-a-tokenThe legacy privacy.guard.audit.dead-letter.observability.alert.tenant.routes.<tenantId>.webhook.* path is still supported and bridges into the same effective tenant route policy.
If you need to suppress webhook delivery for a tenant while keeping tenant alert monitoring enabled, set privacy.guard.tenant.policies.<tenantId>.observability.dead-letter.alert.webhook.enabled=false.
If the same application also receives tenant-specific callbacks, you can route receiver verification by path:
privacy:
guard:
tenant:
policies:
tenant-a:
observability:
dead-letter:
alert:
receiver:
path-pattern: /tenant-a/privacy-alerts
bearer-token: tenant-a-token
signature-secret: tenant-a-secret
replay-namespace: tenant-a-receiver
audit:
dead-letter:
observability:
alert:
receiver:
filter:
enabled: truespring:
mail:
host: smtp.example.com
port: 587
username: demo-user
password: demo-password
privacy:
guard:
audit:
dead-letter:
observability:
alert:
enabled: true
email:
from: privacy@example.com
to: ops@example.comWhen tenant alert monitoring is enabled, the built-in tenant email wrapper appends the tenant marker to the subject and includes tenantId: in the body.
You can also override recipients or subject prefixes per tenant:
privacy:
guard:
tenant:
policies:
tenant-a:
observability:
dead-letter:
alert:
email:
to: tenant-a-ops@example.com
subject-prefix: "[tenant-a]"Tenant alert delivery can also be controlled independently per channel:
privacy:
guard:
tenant:
policies:
tenant-a:
observability:
dead-letter:
alert:
logging:
enabled: true
webhook:
enabled: false
email:
enabled: trueApplications receiving signed alerts can reuse:
PrivacyAuditDeadLetterWebhookRequestVerifierPrivacyAuditDeadLetterWebhookReplayStoreInMemoryPrivacyAuditDeadLetterWebhookReplayStoreFilePrivacyAuditDeadLetterWebhookReplayStoreRedisPrivacyAuditDeadLetterWebhookReplayStoreJdbcPrivacyAuditDeadLetterWebhookReplayStorePrivacyAuditDeadLetterWebhookVerificationFilterPrivacyAuditDeadLetterWebhookVerificationInterceptor
Choose one of these protection modes:
privacy.guard.audit.dead-letter.observability.alert.receiver.filter.enabled=trueprivacy.guard.audit.dead-letter.observability.alert.receiver.interceptor.enabled=true- manual verifier injection in your own endpoint code
Verification failures return a JSON body with an error message and a reason code, for example:
{"error":"Invalid signature","reason":"INVALID_SIGNATURE"}Reason codes include INVALID_AUTHORIZATION, MISSING_SIGNATURE_HEADERS, INVALID_TIMESTAMP, EXPIRED_TIMESTAMP, INVALID_SIGNATURE, and REPLAY_DETECTED.
You can supply verification settings with properties instead of defining a bean:
privacy:
guard:
audit:
dead-letter:
observability:
alert:
receiver:
verification:
enabled: true
bearer-token: demo-receiver-token
signature-secret: demo-receiver-secret
max-skew: 5mIf receiver verification is enabled without a bearer token or signature secret, verification becomes a no-op and only basic routing is applied.
Use the file-backed replay store for single-instance deployments:
privacy:
guard:
audit:
dead-letter:
observability:
alert:
receiver:
replay-store:
file:
enabled: true
path: /var/lib/privacy-audit/replay-store.jsonUse the Redis-backed replay store when you need shared nonce state without managing JDBC schema:
privacy:
guard:
audit:
dead-letter:
observability:
alert:
receiver:
replay-store:
redis:
enabled: true
key-prefix: privacy:audit:webhook:replay:
scan-batch-size: 500privacy:
guard:
audit:
dead-letter:
observability:
alert:
receiver:
replay-store:
jdbc:
enabled: true
initialize-schema: true
table-name: privacy_audit_webhook_replay_storeFor production MySQL/PostgreSQL rollout guidance, schema-management recommendations, optional tenant-column rollout, and migration notes across audit, dead-letter, and replay-store tables, see docs/JDBC_PRODUCTION_GUIDE.md.
If more than one replay-store backend is enabled, the starter prefers JDBC, then Redis, then file, and finally falls back to InMemory.
samples/privacy-demo/ demonstrates:
- masking and audit query endpoints
- dead-letter export/import/replay endpoints
- signed receiver endpoint:
POST /demo-alert-receiver - receiver inspection:
GET /demo-alert-receiver/last - replay-store query:
GET /demo-alert-receiver/replay-store?limit=20&offset=0 - replay-store stats:
GET /demo-alert-receiver/replay-store/stats?expiringWithin=PT5M - replay-store clear:
DELETE /demo-alert-receiver/replay-store
Current sample status:
- the default sample profile already exercises tenant-aware management and
/demo-tenants/observability - built-in
IN_MEMORYandJDBCdead-letter repositories now reportdead_letter_replay=native - JDBC and Redis multi-instance rehearsal scripts are available in both PowerShell and shell variants under
samples/privacy-demo/scripts/ postgres-redis-tenantandpostgres-redis-tenant-node2add a production-like sample topology with JDBC audit/dead-letter storage on PostgreSQL plus shared Redis replay-store protectionsamples/privacy-demo/docker-compose.postgres-redis.ymlcan boot PostgreSQL, Redis, and two sample nodes together for local rollout rehearsal/demo-tenants/observabilitynow reports repository tenant capability flags and exchange-path telemetry for import/export/manifest verificationfallback-tenantadds a generic-only comparison profile that intentionally disables tenant SPI support sofallbackcounters can be verified locallycustom-tenant-nativeadds a custom repository SPI reference profile that keeps native tenant paths without relying on the built-in repository implementationscustom-jdbc-tenantadds a custom JDBC tenant repository reference that keeps native tenant paths while still using sample-owned JDBC beans instead of the built-in repository beanscustom-jdbc-tenant-node2extends that custom JDBC reference into the same local two-node rehearsal model used by the built-in JDBC sample
The sample uses filter mode by default. To switch to interceptor mode:
- Windows:
mvnw.cmd spring-boot:run -Dspring-boot.run.profiles=interceptor - macOS / Linux:
./mvnw spring-boot:run -Dspring-boot.run.profiles=interceptor
- Verify everything:
./mvnw -q verifyormvnw.cmd -q verify - Install local artifacts:
./mvnw -q -DskipTests install - Run sample:
./mvnw -q -f samples/privacy-demo/pom.xml spring-boot:run
- Triage using
SUPPORT.md,SECURITY.md, anddocs/GITHUB_LABELS.md - Run
python scripts/check_repo_hygiene.pybefore tagging - Follow
docs/MAINTAINER_GUIDE.md; the latest published release template is nowdocs/RELEASE_EXECUTION_v0.5.0.md
Recent release-readiness and post-release polish:
- JDBC production guide and migration notes across audit, dead-letter, and replay-store tables
- Stable SPI markers for supported extension points and their carrier types
- Final docs, roadmap, and GitHub release copy consistency pass
- Tenant observability sample endpoint and lazy Micrometer telemetry registration fixes
- Tenant-native dead-letter replay for built-in
IN_MEMORY/JDBCrepositories plus observability alignment - Stable tenant logging policy resolution over the existing
privacy.guard.tenant.policies.*.logging.*model - Cross-platform JDBC / Redis multi-instance verification scripts for the sample app
- Tenant-scoped dead-letter observability policy overrides for thresholds and recovery notifications
- Tenant operational telemetry for alert transitions, tenant delivery outcomes, and receiver route verification failures
- PostgreSQL + Redis production-like sample profiles, compose recipe, and verification scripts
Historical planning context remains archived in docs/ROADMAP.md.
- Documentation index:
docs/INDEX.md - JDBC production guide:
docs/JDBC_PRODUCTION_GUIDE.md - Multi-tenant guide:
docs/MULTI_TENANT_GUIDE.md - Tenant adoption playbook:
docs/TENANT_ADOPTION_PLAYBOOK.md - Tenant observability guide:
docs/TENANT_OBSERVABILITY_GUIDE.md - Maintainer guide:
docs/MAINTAINER_GUIDE.md - Receiver operations:
docs/RECEIVER_OPERATIONS.md - Roadmap:
docs/ROADMAP.md - Release checklist:
docs/RELEASE_CHECKLIST.md - Current published release notes:
docs/releases/RELEASE_NOTES_v0.5.0.md - Current published release execution guide:
docs/RELEASE_EXECUTION_v0.5.0.md - Current published release runbook:
docs/RELEASE_RUNBOOK_v0.5.0.md - Current published release dry run record:
docs/RELEASE_DRY_RUN_v0.5.0.md - Current published release announcement:
docs/RELEASE_ANNOUNCEMENT_v0.5.0.md - Current published release announcement (zh-CN):
docs/RELEASE_ANNOUNCEMENT_v0.5.0.zh-CN.md - Current published GitHub release copy:
docs/GITHUB_RELEASE_COPY_v0.5.0.md - Previous release notes archive:
docs/releases/RELEASE_NOTES_v0.4.0.md - Older release notes archive:
docs/releases/RELEASE_NOTES_v0.3.0.md - GitHub metadata:
docs/GITHUB_METADATA.md
The release workflow looks for docs/releases/RELEASE_NOTES_<tag>.md first and falls back to docs/releases/RELEASE_NOTES_TEMPLATE.md.