- Route 53 registered in integration health with D-12 live NS-delegation check
- Records EGRESS-OK path taken (Node dns module, no DoH fallback needed)
- Documents local sanitizeAwsError duplication vs plan 24-03 (parallel worktree gap)
- checkRoute53(): config gate + ListHostedZonesCommand auth probe, mirrors
checkDattoRmm()'s custom-body shape (key: 'route53', category: 'network')
- Auth-probe errors classified via isAwsAuthError (InvalidClientTokenId,
SignatureDoesNotMatch, AccessDenied, UnrecognizedClientException, 401/403)
and redacted through a local sanitizeAwsError before reaching IntegrationHealth.error
- D-12: queries route53_zones (is_deleted=false, capped at 50 by name),
feeds checkAllZoneDelegations(); mismatches downgrade status to a new
'degraded' HealthStatus member; lookup failures reported separately via
nsDelegationErrors, never counted as mismatches
- Whole delegation step wrapped in try/catch so a Postgres failure or
blocked resolver can never abort checkIntegrationHealth()'s Promise.all (T-24-16)
- summarize() updated so 'degraded' counts toward failed/hasIssues instead
of falling through uncounted (Rule 1 fix)
- No changes to applyDisableOverlay() — route53 covered by the existing
generic by-key overlay (D-10)
Route 53 sync + drift detection plan complete: 3 tasks, 4 files, all
verification passing (22 new tests, tsc clean, full suite green aside from
pre-existing unrelated itglue-search failures).
- 6 unit tests exercising the pure drift-classification wiring in Route53SyncService
- Confirms whole-recordset before/after payloads, not per-field deltas
- Confirms CRUD-originated changes still get tagged sync_detected_drift (sync cannot distinguish actor)
- Full suite green aside from 2 pre-existing itglue-search.test.ts failures logged in deferred-items.md (unrelated to this plan)
- fullSync/incrementalSync + getRoute53SyncService() singleton
- Paginated ListHostedZonesCommand + GetHostedZoneCommand (delegation set for D-12)
- Paginated ListResourceRecordSetsCommand per live zone
- Soft-delete reconciliation for zones and records (never hard-delete)
- sync_history bookkeeping with entity_type='route53', literal full/incremental sync_type
- Drift detection wired via buildDriftHistoryRows, writing sync_detected_drift history rows
- No integration_settings gating anywhere (D-10)
- createPendingAuditLog/markAuditCommitted/markAuditFailed implement the pending -> committed/failed lifecycle (D-07, SC-3); markAuditFailed always sanitizes via sanitizeAwsError
- insertPulseCrudHistory writes 'pulse_crud' history rows, documented as callable only after a committed write
- upsertMirrorRecord/softDeleteMirrorRecord/loadMirrorRecord manage the route53_records mirror; mirror writes are best-effort and soft-delete only (D-08), audit/history writes are not best-effort
- log same pre-existing itglue-search.test.ts failures (unrelated, out of scope) in deferred-items.md
- buildRecordKey, normalizeRecordSet, recordSetsEqual, classifyDrift, toHistoryPayload
- Pure, dependency-free module (no pg, no AWS client construction)
- 16 unit tests covering every behavior bullet from the plan
Checkpoint task 3 resolved: BWS secret keys renamed in Bitwarden
(AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY confirmed present), DNS egress
to public resolvers confirmed OK, credentials confirmed reaching the
Node process. IAM scope left as an open operational item for the
developer to confirm via AWS console.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Tasks 1-2 complete (AWS SDK + schema migration, types + credential factory).
Task 3 is a blocking human-verify checkpoint requiring BWS/AWS credential
confirmation and live docker/DNS-egress verification — not fabricated, not
run unilaterally. Execution stops here pending developer response.
- lib/services/route53-factory.ts: isRoute53Configured() / getRoute53Client()
/ resetRoute53Client(), following the veeam-factory.ts singleton shape
- No explicit credentials option passed to Route53Client — relies on the AWS
SDK's default credential chain reading AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY
from process.env, exactly how BWS injects them at the container entrypoint
- CLAUDE.md: document the AWS_* env-prefix exception in the integration table
- All 7 route53-factory.test.ts assertions pass; npx tsc --noEmit clean
Tier 3 is the only tier automatic (webhook-triggered) grouping ever
reaches, since grouping runs before message parsing. It was scoped to
reports.requester_contact_id, so the same campaign reported by
different employees at the same company never consolidated into one
campaign — each report's evidence/blast-radius view silently
under-reported the campaign's true recipients.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Add mimecast_tenants routing branch to stageQueries and company_id to
report fixtures
- New test: enabled mimecast_tenants row -> getBlastRadius called with
{ client, cacheScope: companyId } and the tenant SQL is issued
- New parameterized test: null companyId and companyId-with-no-enabled-row
-> getBlastRadius called with no second argument (global fallback)
- Thread company_id through reports query and CampaignReportSummary
- Mirror the route's Bug 2 (D-05) tenant-resolution block: query
mimecast_tenants for an enabled row, build a tenant-scoped client via
getMimecastClientForTenant, and pass { client, cacheScope } to
getBlastRadius() when one exists
- Preserve global env fallback unchanged when no companyId or no
enabled tenant row is present
- Updated the getHeldMessages window test to assert no `recipient` key and
added an explicit no-`to` assertion for searchDeliveredMessages
- Added a new test driving searchDeliveredMessages with 3 distinct `to`
addresses, asserting all appear in perRecipient and count toward
matched/delivered
- searchDeliveredMessages now called with from+subject+start+end only
(no `to`) so it returns every delivered/rejected message matching the
campaign across all recipients, not just the reporter's mailbox
- getHeldMessages now called with start+end only (no `recipient`) —
domainsMatch() post-filter is the sole scoping mechanism for held rows
- Updated inline comments to document the tenant-wide fan-out and the
per-recipient merge behavior it now produces
- Add migration 101: guarded UPDATE moving mimecast-sync from 0 2 * * * to
45 4 * * * (minute 45 is unused by any other schedule row)
- Guarded on the stale cron value so it's a no-op if already moved and won't
clobber an admin's manual schedule change
- Applied the same UPDATE directly to the live pulse-postgres container
(migrations only auto-apply on first volume boot, per CLAUDE.md)
- Eliminates the 3-way 2 AM collision with qbo-sync-2am and veeam-full
- Extend ScheduleConfig.sync_type union with 'mimecast-sync'
- Add mimecast-sync branch calling runMimecastIncrementalSync() behind
isMimecastConfigured(), mirroring the engagement/zoom configured-gate pattern
- Add qbo branch calling getQboSyncService().incrementalSync('scheduled')
behind an integration_settings disabled check, mirroring the pax8-daily
disable-check pattern
- Both branches previously fell through to the generic Autotask fullSync()
catch-all, which also contended for the SyncService singleton mutex
Records the plan, execution summary, and STATE.md quick-task log entry
for the QBO_INTEGRATION_HANDOFF.md doc committed in ea8a36b.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W6RuWdiUiXrPK6FLBHjtpY
Documents Pulse's QuickBooks Online integration (OAuth2 authorization-code
flow, token storage/refresh, sandbox vs production API base URLs, scopes,
minor version, and gotchas learned from the AR reconciliation/soft-delete
work) so a new app's team can build their own QBO connection without
access to the Pulse codebase.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W6RuWdiUiXrPK6FLBHjtpY
Bundles several in-progress efforts that were sitting uncommitted:
- User queue-preferences (migration 087, API route, popover component)
- QBO invoice soft-delete (migration 088) and AR diagnostics route
- Dashboard/mobile engagement route and page adjustments
- Docker Compose log-rotation config
- One-off ticket/RMM investigation scripts (scripts/)
- Planning docs: phase verification/pattern notes, mobile shell design spec
- .gitignore: exclude local scratch financial/inventory data and Claude Code
worktree/local-settings runtime state (never meant for version control)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W6RuWdiUiXrPK6FLBHjtpY