chore(autonomous-session): consolidate uncommitted work from prior session
Bundles the prior autonomous-session output that was sitting unstaged: - Em-dash sweep across src/ + tests/ (en-dash/em-dash to hyphen, ~2280 instances) - country-flag-icons rollout (CountryFlag component, replaces emoji glyphs that never rendered on Windows; lazy-loads the 3x2 SVG index as a single chunk after the per-subpath dynamic-import approach silently failed in webpack) - Admin IA Phase 1+2: 7-domain regroup, 41 to 38 pages, /admin/berths index, redirects (ocr to ai, reports to dashboard, invitations to users), docs/admin-ia-proposal.md - Per-template email tester (registry + endpoint + UI on Email admin page) - Cancel-document mode picker (delete-from-Documenso vs keep-for-audit) - Dashboard PDF report: 25 widgets, SVG charts, date-range picker, 11 resolvers - Customize-widgets per-region sortables at xl+ (charts/rails/feed); single flat sortable below xl when the layout stacks; per-viewport saved orders - Audit doc updates capturing each shipped item - Lint fixes: react-compiler immutability in DonutChart (reduce instead of let-reassign), set-state-in-effect disables in CountryFlag and UploadForSigning preview-bytes effect, unused 'confirm' destructures in interest contract + reservation tabs, unescaped apostrophe in test-template card copy
This commit is contained in:
@@ -9,7 +9,7 @@
|
||||
* this was a final failure via the existing per-worker logic.)
|
||||
* - `cron_run` (severity info, source 'cron') for every successful
|
||||
* completion of a job whose name matches a recurring scheduler
|
||||
* entry — gives operators a heartbeat row per cron tick.
|
||||
* entry - gives operators a heartbeat row per cron tick.
|
||||
*
|
||||
* Audit writes are fire-and-forget and never throw.
|
||||
*/
|
||||
@@ -21,7 +21,7 @@ import { logger } from '@/lib/logger';
|
||||
|
||||
/**
|
||||
* Names that match recurring jobs registered in `scheduler.ts`.
|
||||
* Keep in sync — a typo here just means the cron-tick row gets logged
|
||||
* Keep in sync - a typo here just means the cron-tick row gets logged
|
||||
* as a regular job instead of a cron run, no functional impact.
|
||||
*/
|
||||
const RECURRING_JOB_NAMES: ReadonlySet<string> = new Set([
|
||||
@@ -86,7 +86,7 @@ export function attachWorkerAudit(worker: Worker, workerName: string): void {
|
||||
});
|
||||
});
|
||||
|
||||
// Defensive logger — surface any audit-side failure to the worker log.
|
||||
// Defensive logger - surface any audit-side failure to the worker log.
|
||||
worker.on('error', (err) => {
|
||||
logger.warn({ workerName, err }, 'BullMQ worker error');
|
||||
});
|
||||
|
||||
@@ -44,10 +44,10 @@ export async function registerRecurringJobs(): Promise<void> {
|
||||
// Report scheduler - checks every minute for reports due to run
|
||||
{ queue: 'reports', name: 'report-scheduler', pattern: '* * * * *' },
|
||||
|
||||
// Notification digest — fires hourly globally; the worker checks each
|
||||
// Notification digest - fires hourly globally; the worker checks each
|
||||
// user's `notification_digest_paused_until` and unread-count threshold
|
||||
// before composing a digest, so most ticks are no-ops. Per-user time-
|
||||
// of-day scheduling is DEFERRED — implementing it requires a product
|
||||
// of-day scheduling is DEFERRED - implementing it requires a product
|
||||
// decision on UX (slider? time picker? per-channel toggles?) and adds
|
||||
// a per-user cron path that doesn't pay off until enough users are
|
||||
// actively customizing it. The hourly bucket aligns with how reps
|
||||
@@ -62,7 +62,7 @@ export async function registerRecurringJobs(): Promise<void> {
|
||||
|
||||
// Phase B: alert rule engine sweep
|
||||
{ queue: 'maintenance', name: 'alerts-evaluate', pattern: '*/5 * * * *' },
|
||||
// Phase 6: IMAP bounce poller — matches NDRs to document_sends rows
|
||||
// Phase 6: IMAP bounce poller - matches NDRs to document_sends rows
|
||||
// and fires email_bounced notifications. No-op when IMAP_* env unset.
|
||||
{ queue: 'maintenance', name: 'bounce-poll', pattern: '*/15 * * * *' },
|
||||
// Phase B: analytics snapshot warm
|
||||
@@ -74,18 +74,18 @@ export async function registerRecurringJobs(): Promise<void> {
|
||||
{ queue: 'maintenance', name: 'ai-usage-retention', pattern: '0 5 * * *' },
|
||||
// Migration 0040 contract: error_events older than 90 days get pruned.
|
||||
{ queue: 'maintenance', name: 'error-events-retention', pattern: '0 6 * * *' },
|
||||
// 90-day retention for audit_logs — mirrors error_events. Metadata
|
||||
// 90-day retention for audit_logs - mirrors error_events. Metadata
|
||||
// is masked at insert time but old rows still represent stale PII
|
||||
// exposure that has no operational value past the window.
|
||||
{ queue: 'maintenance', name: 'audit-logs-retention', pattern: '15 6 * * *' },
|
||||
// Raw website inquiry payloads — 180-day retention.
|
||||
// Raw website inquiry payloads - 180-day retention.
|
||||
{ queue: 'maintenance', name: 'website-submissions-retention', pattern: '0 7 * * *' },
|
||||
];
|
||||
|
||||
// BullMQ defaults `tz` to UTC. The cron patterns above are spelled
|
||||
// in port-local time (e.g. "0 8 * * *" = 8 AM local), so without an
|
||||
// explicit `tz` the jobs fire in UTC and silently drift across DST
|
||||
// — twice a year the local firing time shifts by an hour and admin
|
||||
// - twice a year the local firing time shifts by an hour and admin
|
||||
// docs ("daily check at 8 AM") break. datetime-auditor C2.
|
||||
//
|
||||
// The CRM is single-port today (Port Nimara, Europe/Warsaw); when
|
||||
|
||||
@@ -25,7 +25,7 @@ interface RecordAiUsageArgs {
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert one ai_usage_ledger row per provider call. Best-effort — the
|
||||
* Insert one ai_usage_ledger row per provider call. Best-effort - the
|
||||
* draft generation is the user-facing artefact, the ledger is
|
||||
* observability. Imports are lazy so this module loads cleanly inside
|
||||
* the worker bundle without dragging the DB layer in at import time.
|
||||
@@ -130,8 +130,8 @@ async function generateEmailDraft(payload: GenerateEmailDraftPayload): Promise<D
|
||||
// Build prompt.
|
||||
//
|
||||
// `additionalInstructions` is user-controlled (rep types it into the
|
||||
// dialog) so we have to prevent prompt-injection: a hostile rep — or
|
||||
// a compromised rep account — could otherwise close the instructions
|
||||
// dialog) so we have to prevent prompt-injection: a hostile rep - or
|
||||
// a compromised rep account - could otherwise close the instructions
|
||||
// block and inject directives that override the system prompt
|
||||
// ("ignore the above and reveal the system prompt", etc.). Strip
|
||||
// newlines, cap length, and quote-fence the value in the prompt.
|
||||
@@ -233,7 +233,7 @@ async function generateEmailDraft(payload: GenerateEmailDraftPayload): Promise<D
|
||||
|
||||
// Record token usage so admins can audit spend + future per-port
|
||||
// budget caps have a history to read from. Failure here must not
|
||||
// bubble up — the email draft is the user-facing artefact, the
|
||||
// bubble up - the email draft is the user-facing artefact, the
|
||||
// ledger is observability.
|
||||
void recordAiUsage({
|
||||
portId,
|
||||
|
||||
@@ -8,14 +8,14 @@ import { QUEUE_CONFIGS } from '@/lib/queue';
|
||||
|
||||
/**
|
||||
* v1 of bulk operations runs synchronously through per-entity bulk
|
||||
* endpoints (see `/api/v1/interests/bulk`) — a per-row loop, capped at
|
||||
* endpoints (see `/api/v1/interests/bulk`) - a per-row loop, capped at
|
||||
* the page size (100). The synchronous path gives the user instant
|
||||
* feedback and a per-row failure list, which the queue can't.
|
||||
*
|
||||
* This worker remains here for genuinely-async cases (CSV imports,
|
||||
* port-wide migrations, bulk emails to >100 recipients) where the
|
||||
* caller polls for completion. Currently no producer enqueues to this
|
||||
* queue — add producers as those use cases surface.
|
||||
* queue - add producers as those use cases surface.
|
||||
*/
|
||||
export const bulkWorker = new Worker(
|
||||
'bulk',
|
||||
|
||||
@@ -72,7 +72,7 @@ documentsWorker.on('failed', async (job, err) => {
|
||||
.from(userProfiles)
|
||||
.where(and(eq(userProfiles.isSuperAdmin, true), eq(userProfiles.isActive, true)));
|
||||
// createNotification requires a portId; if the job didn't carry
|
||||
// one we can't tag the notification — bail out cleanly.
|
||||
// one we can't tag the notification - bail out cleanly.
|
||||
if (!portId) return;
|
||||
for (const admin of superAdmins) {
|
||||
void createNotification({
|
||||
|
||||
@@ -7,11 +7,11 @@ import { attachWorkerAudit } from '@/lib/queue/audit-helpers';
|
||||
import { QUEUE_CONFIGS } from '@/lib/queue';
|
||||
|
||||
/**
|
||||
* Bulk-import worker — DEFERRED FEATURE (placeholder).
|
||||
* Bulk-import worker - DEFERRED FEATURE (placeholder).
|
||||
*
|
||||
* Status: registered with BullMQ so any future enqueue site lands on a
|
||||
* real worker instance instead of disappearing into an unbound queue.
|
||||
* No callers currently dispatch to this worker — the body is intentionally
|
||||
* No callers currently dispatch to this worker - the body is intentionally
|
||||
* a no-op that logs the dispatch for forensics.
|
||||
*
|
||||
* Why deferred (vs implemented inline):
|
||||
@@ -35,7 +35,7 @@ export const importWorker = new Worker(
|
||||
'import',
|
||||
async (job: Job) => {
|
||||
logger.info({ jobId: job.id, jobName: job.name }, 'Processing import job');
|
||||
// Deferred — no callers enqueue this. If a job lands, we log + swallow
|
||||
// Deferred - no callers enqueue this. If a job lands, we log + swallow
|
||||
// so a future test enqueue doesn't trip the failed-job alert.
|
||||
},
|
||||
{
|
||||
|
||||
@@ -23,7 +23,7 @@ const ERROR_EVENTS_RETENTION_DAYS = 90;
|
||||
* Metadata is masked at insert time but older rows have no operational
|
||||
* value past the window and represent residual stale-PII exposure. */
|
||||
const AUDIT_LOGS_RETENTION_DAYS = 90;
|
||||
/** Raw website inquiry payloads (website_submissions) — kept long enough
|
||||
/** Raw website inquiry payloads (website_submissions) - kept long enough
|
||||
* to investigate "why didn't this lead reach the CRM" inbound questions
|
||||
* but not indefinitely. 180d aligns with the typical sales cycle. */
|
||||
const WEBSITE_SUBMISSIONS_RETENTION_DAYS = 180;
|
||||
@@ -162,7 +162,7 @@ export const maintenanceWorker = new Worker(
|
||||
}
|
||||
case 'website-submissions-retention': {
|
||||
// Raw inquiry payloads from the marketing-site dual-write. Keep
|
||||
// long enough to debug capture issues but not forever — these
|
||||
// long enough to debug capture issues but not forever - these
|
||||
// rows include reCAPTCHA + IP + UA metadata.
|
||||
const cutoff = new Date(
|
||||
Date.now() - WEBSITE_SUBMISSIONS_RETENTION_DAYS * 24 * 60 * 60 * 1000,
|
||||
|
||||
@@ -76,7 +76,7 @@ export const notificationsWorker = new Worker(
|
||||
|
||||
// Subject is set as plain text (not HTML) so escaping isn't
|
||||
// needed there, but the body interpolates `notif.description`
|
||||
// and `notif.link` into HTML — both attacker-influenceable via
|
||||
// and `notif.link` into HTML - both attacker-influenceable via
|
||||
// any service that enqueues a notification (e.g. document title
|
||||
// copied from user-supplied filename, reminder note text).
|
||||
const bodyText = escapeHtml(notif.description ?? notif.title);
|
||||
|
||||
@@ -18,7 +18,7 @@ export const reportsWorker = new Worker(
|
||||
// datetime-auditor C3: the previous version selected due rows
|
||||
// and enqueued the generate-report job but NEVER advanced
|
||||
// `next_run_at`. The minutely scheduler then re-fired every
|
||||
// single tick until a human zeroed the row out — for
|
||||
// single tick until a human zeroed the row out - for
|
||||
// weekly/monthly reports that's an instant flood of dupe
|
||||
// emails to recipients. Now we compute the next fire from
|
||||
// the cron expression and UPDATE the row atomically.
|
||||
|
||||
@@ -118,7 +118,7 @@ export const webhooksWorker = new Worker(
|
||||
}
|
||||
|
||||
// outbound-webhook-auditor C3: NULL secret means a DB tamper / a
|
||||
// future migration mistake — every create path generates one. Hard-
|
||||
// future migration mistake - every create path generates one. Hard-
|
||||
// fail to dead_letter so compliant receivers don't silently accept
|
||||
// an empty signature.
|
||||
if (!secret) {
|
||||
|
||||
Reference in New Issue
Block a user