chore(hardening): maintenance jobs, defense-in-depth, redis-backed public rate limit
- maintenance worker now expires GDPR export bundles (db row + MinIO object) on the gdpr_exports.expires_at boundary, plus 90-day retention sweep on ai_usage_ledger; both jobs scheduled daily. - portId scoping added to listClientRelationships and listClientExports (defense-in-depth — parent-resource gates already prevent cross-tenant reads, but service layer should enforce on its own). - SELECT FOR UPDATE on parent client/company row inside add/update address transactions to serialize concurrent isPrimary toggles. - public /interests + /residential-inquiries endpoints swap their in-memory ipHits maps for the redis sliding-window limiter via the new rateLimiters.publicForm config (5/hr/IP), so the cap survives restarts and is shared across worker processes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -52,6 +52,11 @@ export async function registerRecurringJobs(): Promise<void> {
|
||||
{ queue: 'maintenance', name: 'alerts-evaluate', pattern: '*/5 * * * *' },
|
||||
// Phase B: analytics snapshot warm
|
||||
{ queue: 'maintenance', name: 'analytics-refresh', pattern: '*/15 * * * *' },
|
||||
|
||||
// Phase 3d: GDPR Article 17 — actually delete expired export bundles
|
||||
{ queue: 'maintenance', name: 'gdpr-export-cleanup', pattern: '0 4 * * *' },
|
||||
// Phase 3b: AI usage ledger retention (90-day rolling window)
|
||||
{ queue: 'maintenance', name: 'ai-usage-retention', pattern: '0 5 * * *' },
|
||||
];
|
||||
|
||||
for (const job of recurring) {
|
||||
|
||||
Reference in New Issue
Block a user