ALL-1873 PR-3: Audit runner service + GraphQL mutation (triggerDataAudit)
queued## ALL-1873 PR-3: Audit runner service + GraphQL mutation
**Linear issue:** ALL-1873 — Feature: Data audit entrypoint for Data Review Center
**Branch:** wilbo/all-1873-audit-runner
**Depends on:** PR-2 (dataAudit profile must be registered before the runner can reference it)
### What to do
#### 1. Create domains/identity/src/services/staging/auditRunnerService.ts
This service collects existing production entities for a workspace and enqueues them as a DATA_AUDIT staged batch. Pattern: mimic ingestionService.ts but instead of receiving rows from a caller it queries the DB itself.
Interface:
async function triggerDataAudit(input: {
workspaceId: string;
createdBy: string; // userId who triggered the run
title?: string; // defaults to "Data audit — <ISO date>"
}): Promise<{ batch: StagedBatch; recordCount: number }>
Implementation steps:
1. Query the workspace for all contacts (ThirdParty), sites (CustomerSite), and devices (CustomerDevice) — just their IDs. Do three separate queries to keep it simple; no cross-DB joins needed at this stage.
2. Map each entity to a row: { entityType: "contact" | "site" | "device", rawData: { entityType, entityId, workspaceId } }
3. Call ingestStagedBatch (from ingestionService.ts) with:
- sourceType: "DATA_AUDIT" (from STAGED_BATCH_SOURCE_TYPES)
- profile: "dataAudit"
- autoPromote: false (audit batches always go to human review)
- rows: the assembled rows
4. Return the result from ingestStagedBatch.
Safeguard: if workspaceId has 0 entities, throw a descriptive error rather than creating an empty batch (ingestStagedBatch already rejects empty row arrays).
#### 2. Create the GraphQL mutation in domains/identity/src/graphql/resolvers/staging/mutations.ts
Add a new resolver: triggerDataAudit
- Input: { workspaceId: string }
- Auth: require workspace canCreate permission (same pattern as other staging mutations)
- Call auditRunnerService.triggerDataAudit(...)
- Return: { batchId: string, recordCount: number }
Also wire the mutation into the GraphQL schema. Find the SDL file that declares staging mutations (look for *.graphql or typeDefs in domains/identity/src/graphql/) and add:
type Mutation {
triggerDataAudit(input: TriggerDataAuditInput!): TriggerDataAuditResult!
}
input TriggerDataAuditInput { workspaceId: ID! }
type TriggerDataAuditResult { batchId: ID! recordCount: Int! }
#### 3. Unit test for auditRunnerService.ts
Add auditRunnerService.test.ts:
- Mock prisma queries to return a handful of fake entity IDs
- Mock ingestStagedBatch
- Assert ingestStagedBatch is called with correct sourceType, profile, and row shapes
- Assert 0-entity workspace throws
### Acceptance test
- triggerDataAudit mutation exists in the identity GraphQL schema
- Calling it (with a mocked workspace) creates a StagedBatch with sourceType=DATA_AUDIT, profile=dataAudit, autoPromote=false
- TypeScript compiles, all tests pass
### Gotchas
- Read ingestionService.ts carefully — the batch fails to FAILED status if BullMQ scheduling throws; auditRunnerService just re-throws, no extra handling needed.
- The DATA_AUDIT source type must be in the DB constraint (PR-1) before ingestStagedBatch can succeed at runtime.
- Keep entity queries simple and workspace-scoped. No cross-DB joins. Cross-domain data (enrollments referencing devices in another schema) is Phase 2.
- Do not paginate entity fetches in this PR — for Phase 1, a simple findMany with no limit is acceptable; add pagination as a follow-up if scale demands it.
### Out of scope
- Scheduled/recurring audits (Phase 2)
- DRC UI button (PR-4)
- Per-workspace audit configuration (Phase 2)
Event Timeline
created