⚙️ Engineering Finds Errors at Build Time. GRC Finds Them During the Audit.
What a compiler catches in software engineering, what your GRC workflows have in common with runtime errors, and how tight feedback loops change the GRC experience you ship

Ask an engineer why their change hasn't shipped yet and you rarely hear about a person. You hear "my tests are failing," or "I can't push because main is down."
The thing blocking them is a machine. It rejected the change and printed the reason before any human looked.
That machine is a compiler, plus the checks built on top of it. It does a job GRC still does by hand: check work against rules before the work ships. Everyone says get technical. This is worth getting technical at.
IN PARTNERSHIP WITH

Live on September 22: The AI-powered GRC team
The challenge for GRC teams today isn’t building control frameworks - it’s knowing whether they’re working, identifying gaps, and keeping controls effective at scale.
On September 22nd, I’ll be joining GRC experts from Tines for a live conversation on how security teams are using AI and automation to build more responsive, scalable GRC programs.
Two ways to run a program
Every language makes a choice about when you find out you were wrong.
An interpreted language reads your code and executes it line by line. Python and JavaScript work this way. Your mistake stays in the file until the program reaches that exact line in production.
# error surfaces only when this line runs
def approve_change(change):
return risk_score(change) < treshold # NameError at runtime: 'treshold'A compiled language checks the whole program before anything runs. Go and Rust work this way. Misspell a variable and the build fails on your machine. It names the file and the line.
// Go refuses to produce a program at all
func approveChange(c Change) bool {
return riskScore(c) < treshold // compile error: undefined: treshold
}The typo costs a production incident in Python and four seconds in Go.
The four-second loop is the engineer's private one: the editor runs the compiler continuously, and the red squiggle is its output. Then CI compiles it again in a clean environment on every pull request, with the test suite on top.
A red build blocks the merge regardless of who wrote the change. The local loop gives fast feedback; the CI gate is enforced and logged, which makes it the layer an auditor can rely on.
So why did the popular languages pick the other side?
Most languages that came after C chose interpretation: Python, Ruby, JavaScript, PHP. No build step between you and running code is exactly what you want for scripts and prototypes.
Production is where the choice reverses. Twitter started in Ruby and rewrote its core services onto the JVM, the compiled runtime behind Java, and credited the move with surviving record-traffic spikes that used to take the site down. Stripe kept Ruby and built Sorbet, a type checker that reads their fifteen million lines of Ruby and raises compile-style errors without leaving the language. TypeScript exists for the same reason.
Teams pick interpreted languages to iterate fast, then pay for compilation wherever the code becomes load-bearing.

Why agents need a compiler
I watched this recently while building a GRC data pipeline: a script that pulls findings from a CSPM platform and maps their categories to entries in the risk register. I had the agent build it in Go. The compiler caught the typos; the agent's own runs against the real data caught the rest: a reference to the wrong row, a SQL query that failed, category matching that didn't handle uppercase.
The agent worked through both kinds of error, fixing them one by one while I finished the HTTP server.
That loop is why delegation works: write, compile, read the errors, fix exactly what they name, compile again. The compiler answers in seconds, in a format the agent can parse, so the agent can be wrong fifty times without a human in the middle. Take away the compile step and you are back to hoping the model was right.
💡 A compiler is a reviewer that answers in seconds and never negotiates.
Tests extend the same gate from syntax to outcomes. As long as the function satisfies the test cases, the code is good to merge, and human review sits on top. The gate exists so you never have to say "write it this way, not that way." There are many valid ways to build the same thing.
The tests are the feedback loop for the agent. They are also continuous feedback on whether the business requirement and the technical requirement are satisfied, codified into one file: the test suite.

The GRC version
Your GRC program is an interpreted language.
A human re-derives every answer at the moment somebody asks. The access review repeats the whole derivation quarterly, and someone answers the questionnaire from memory.
Look closer and GRC has already codified more than it gets credit for, well beyond policy-as-code and the checks that run on infrastructure. Every control has a documented test procedure. Third-party risk has an assessment methodology you follow step by step. The formalisation targets the wrong runtime: each of those procedures is written for a human operator to execute, and a procedure precise enough for a person to follow is still not precise enough for a machine to check.
That reframes the job ahead. The formal groundwork already exists; what it needs is a second codification, one a machine or an agent can check, the version where compliance becomes a query on data you already collect. The human version stays, because a person still performs the control. Both runtimes need their own source.
Until that second source exists, the errors surface where interpreted errors always do. An audit finding is a runtime error. So is a decayed PR approval nobody noticed.
The rest of the toolchain maps too:
Compiler concept | In your program today |
|---|---|
Compile error | Finding discovered during the audit, months late |
Type check | Nothing. A screenshot can back a "continuous" control |
Unresolved symbol | Obligation with no control behind it |
Dead code | Control that maps to no obligation |
Recompilation | Redoing the readiness project by hand |
There is a harder version of the missing build step, and engineers are not the ones creating it. Engineers already run risk management for their own context, and the test suite is where they codified it. Then GRC arrives on top with requirements that come from frameworks.
An engineer can say the tests are passing and be right, while the property your framework cares about goes unchecked, because it is a macro property nobody has codified yet. The second codification meets its limit there: turn the framework requirements that can become checks into checks, and own the ones that cannot yet.
Until GRC work can fail before it ships, it cannot be safely delegated to AI. The skill of assessing AI output stays manual until you write down what it checks. When an agent runs the work, that written form is an eval set.
Take the third-party risk assessment your team already runs. The procedure lists exactly what a good review checks. Written as evals, the same criteria become a gate the agent's output must pass:
# vendor-assessment.evals.yaml: what a finished review must pass
cuecs_reviewed: every CUEC in the SOC 2 mapped to an internal control
open_vulns: no medium or higher finding still open past SLA
auditor_tier: signing firm appears in tier 1 or 2 of our auditor list
narrative_maps: each Section 4 control narrative maps to a named implementation
tech_stack: Section 3 system description parsed into components
shared_subprocessors: their stack mapped against ours, shared vendors inherited
exceptions_mapped: every exception in the report linked to an internal controlThe eval set needs its golden set next to it: worked examples where every criterion passes, so the agent sees a passing review before it runs. That is the second codification in practice.
Last issue I argued a job is standing vacant. A compile step for governed decisions, where an interpretation is signed once and everything downstream is checked against it, is the machinery that job operates.
That is the GRCX direction, and it stays a candidate practice until pilots produce receipts. We have borrowed the runtime before, and Git before that. The compiler is the first of those borrowings that enforces anything: the garbage collector and Git described your program; this one blocks the merge.
Try this week
Pick one control and declare its type: live telemetry, config snapshot, or human attestation.
Time your own loop: how long between a wrong GRC answer and anyone knowing?