nyxcore-systems
5 min read

Automating Compliance: From Workflow Data to GitHub PRs

Dive into how we built our new compliance report export feature, covering everything from structured Markdown generation and GitHub PR automation to critical refactoring and handling common development gotchas.

complianceautomationtypescriptgithubprismarefactoringtestingdocumentationtrpcnextjsweb-development

In the world of complex applications, ensuring compliance and providing clear, auditable reports is paramount. Our recent development sprint focused on delivering a robust solution for exporting compliance reports, transforming raw workflow outputs into structured, human-readable Markdown documents, and even automating their submission as GitHub Pull Requests.

This session was a deep dive into full-stack feature development – touching on data processing, UI/UX, API integration, refactoring, and comprehensive documentation. Let's break down how we brought this critical feature to life.

The Journey: From Data to Document

Our primary goal was to create a mechanism to generate a comprehensive compliance report based on the outputs of our automated workflows. This isn't just about dumping data; it's about synthesizing it into a structured narrative.

Crafting the Report: compliance-report-formatter.ts

The heart of the report generation lies in src/server/services/compliance-report-formatter.ts. This new service is responsible for taking all the intricate details from a completed workflow – executive metrics, six detailed sections, quality gate results, and even hallucination/consistency analysis – and assembling them into a coherent Markdown document. Think of it as a sophisticated data storyteller, ensuring every piece of information is presented clearly and logically.

Exposing the Power: exportComplianceReport Mutation

To make this report accessible, we introduced an exportComplianceReport mutation within our tRPC router (src/server/trpc/routers/workflows.ts). This mutation offers dual functionality:

  1. Download-only mode: Users can simply download the generated Markdown file for immediate use.
  2. Idempotent GitHub PR creation mode: For teams that track compliance reports directly in their repositories, this mode automates the creation of a new branch, commits the report, and opens a Pull Request on GitHub. We built this with idempotency in mind, meaning repeated calls won't create duplicate PRs, ensuring a smooth user experience. Robust error wrapping and runtime validation for linked document references (docRefs) were crucial here to prevent unexpected failures.

A Seamless User Experience: The ComplianceExportPanel

A powerful backend needs an intuitive frontend. We developed src/components/workflow/compliance-export-panel.tsx, a self-contained React component that provides a clean interface for users to interact with the new export functionality. This panel, weighing in at a concise 143 lines, features:

  • An expandable card for a clean UI.
  • A dedicated checkbox to enable the GitHub PR creation mode.
  • Clear loading, success, and error states to keep the user informed.

This panel was then seamlessly integrated into our dashboard (src/app/(dashboard)/dashboard/workflows/[id]/page.tsx), appearing automatically for completed compliance workflows.

Building for Robustness and Maintainability

Beyond just shipping features, we always strive for a robust, maintainable, and well-tested codebase.

Upholding Quality with Tests

To ensure the report formatter behaves exactly as expected, we added 12 dedicated unit tests in tests/unit/compliance-report-formatter.test.ts. All of these are passing, giving us confidence in the accuracy and reliability of the generated reports.

The Unsung Hero: Refactoring for Consistency

One of the most satisfying parts of this sprint was a significant refactoring effort: consolidating the formatDuration utility. Before this, we had no fewer than six duplicate implementations scattered across various files (compliance-report-formatter.ts, workflow-bundle.ts, report-generator.ts, model-usage-table.tsx, workflow-performance.tsx, page.tsx). We centralized this into src/lib/workflow-metrics.ts, drastically improving consistency, reducing technical debt, and making future updates a breeze. This kind of cleanup is often overlooked but pays dividends in the long run.

Audit Trails: Knowing What Happened

For compliance-related actions, an audit trail is non-negotiable. We integrated auditLog() into the exportComplianceReport mutation, specifically logging the compliance_report.pr_created action. This ensures that every GitHub PR creation is recorded, providing transparency and accountability.

Navigating Development Hurdles: Lessons Learned

No development sprint is without its challenges. Here are a couple of "gotchas" we encountered and how we tackled them:

1. Runtime Validation of Prisma JsonValue Arrays

The Challenge: When working with Prisma's JsonValue fields, especially when trying to access properties on elements within a JsonArray, TypeScript can be quite strict. A direct as cast on array elements often results in TS2339, as property access isn't allowed directly on JsonObject | JsonArray.

The Solution: The standard pattern we've adopted for robust runtime validation of Prisma JSON fields involves a two-step cast and type guard. First, cast the element to Record<string, unknown>, then use a type guard to verify the expected properties and their types.

typescript
// Example of validating a docRef within a JsonArray
type DocRef = { id: string; url: string; };

function isDocRef(obj: unknown): obj is DocRef {
  return (
    typeof obj === 'object' &&
    obj !== null &&
    'id' in obj && typeof (obj as any).id === 'string' &&
    'url' in obj && typeof (obj as any).url === 'string'
  );
}

// Inside your mutation or service logic:
const rawDocRefs = prismaRecord.docRefs as Prisma.JsonArray; // Assuming docRefs is a JsonArray
const validatedDocRefs: DocRef[] = rawDocRefs.filter(element => {
  // Cast to Record<string, unknown> first
  return isDocRef(element as Record<string, unknown>);
}) as DocRef[];

// Now validatedDocRefs is type-safe and ready to use.

This ensures type safety at runtime, preventing unexpected errors from malformed JSON data.

2. Locale-Agnostic Numeric Assertions in Tests

The Challenge: We hit a snag when testing locale-formatted numbers. An assertion like toContain("8,000") would pass in a US locale but fail in a German locale, where the thousands separator is a dot (8.000).

The Solution: The fix was to use a regular expression for locale-agnostic assertions. Instead of hardcoding the separator, we used toMatch(/8[,.]000/), which correctly matches both 8,000 and 8.000. This is a small but critical detail for ensuring tests are robust across different environments.

Solidifying with Documentation

No feature is truly complete without comprehensive documentation. We generated two crucial documents:

  • docs/CONTRIB.md: A go-to guide for new and existing contributors, covering environment setup, a comprehensive list of 14 core scripts and 4 helper scripts, 11 essential environment variables, the recommended development workflow, and testing procedures.
  • docs/RUNBOOK.md: An indispensable resource for operations, detailing deployment steps, monitoring guidelines, fixes for 6 common issues, rollback procedures, and secrets management.

These documents are vital for onboarding, maintaining, and operating the application smoothly.

What's Next?

With the core feature complete and pushed to origin/main (across 4 focused commits: bf1fd8d2bd16c0f1adeeedb22b1a), our immediate next steps involve thorough end-to-end testing:

  1. Verifying the .md download after running a compliance workflow.
  2. Testing the GitHub PR creation flow, ensuring the branch, file, and PR are correctly created in the linked repository.
  3. Updating related documentation (docs/04-20.md and docs/06-workflow-intelligence.md) to reflect the new compliance report export and the shared formatDuration utility.

All 139 tests are passing, and our codebase is type-check and lint clean. It's been a productive sprint, delivering a powerful new feature while also improving the overall quality and maintainability of our application.