Source Code Is Becoming a Build Artifact
Engineers have programmed systems through physical configuration, assembly code and languages such as C, C++ and Rust. Each higher layer delegated more translation to tools. In disciplined embedded development, requirements already guide the human implementation.
AI may now change which layer engineers author directly: precise requirements, controlled design inputs and acceptance criteria can become the basis for generated code. What has to be reproducible is not the generated source but the acceptance process and its verdict for recorded inputs, established without trusting the generator's own verdict. A different candidate is nevertheless a new controlled baseline and must produce its own functional, structural and timing evidence for the exact binary and configuration under review.
There is a pattern in the history of software that becomes easy to overlook once a new abstraction has become normal. Engineers move the point at which they describe a system. The lower layer does not disappear; it becomes something a tool produces for us, and we go back to it only when we have a reason.
These examples overlap historically. They are arranged by abstraction level: the point at which a human describes what the machine should do.
At the lowest level, that description can be physical. Connections, switches, coefficients and machine configuration embody the computation directly. At the next level, symbolic instructions describe machine operations. With high-level languages, engineers still implement against requirements, but they express the solution as algorithms, control flow and data structures while the compiler translates it into machine instructions. AI-based development may delegate one more transformation: from controlled engineering intent to candidate source code and tests. Requirements are not new; what changes is how directly they drive the implementation.

Early computing makes the abstraction boundary unusually visible. In a machine like this analog computer, the intended calculation is expressed directly in the physical configuration of the machine: connections, functional blocks and parameters become part of the program.
The machine in the photograph dates from 1968, long after stored-program computers and FORTRAN already existed. It illustrates coexistence, not sequence: lower and higher abstraction levels remained in use at the same time. What changed was the level at which more engineers could describe the result they wanted and delegate the translation to another layer.
We have made this move before
The more important transition was not from one particular machine to the next, but from one level of description to another. High-level languages made that shift consequential: programmers could express algorithms in a notation designed for humans while a compiler took responsibility for translating them into machine instructions.
FORTRAN moved that boundary dramatically. Programmers still implemented required behavior, but they no longer had to express every operation in the vocabulary of the processor. The quality of the compiler’s translation - especially the efficiency of the generated code - became the central engineering question.
When John Backus and his team developed FORTRAN in the 1950s, programmers were skeptical of automatic programming, and for good reason. Skilled programmers knew their machines intimately and could produce very efficient hand-written code. A translator that produced poor code would not have been useful simply because it made programming more convenient.
Backus later wrote that the FORTRAN team believed their system would only be accepted if the generated programs came close to the efficiency of hand-coded ones. The compiler was therefore not an afterthought; it was the central engineering problem. Once that problem was solved well enough, the level at which programmers worked changed surprisingly quickly.


Looking back, it is tempting to describe that transition as the replacement of one skill by another. I think that misses the more important point. The programmer still had to understand the problem, the machine and the consequences of the implementation. What changed was the level at which the solution was normally expressed.
Assembly code did not vanish when C arrived, and machine code did not vanish when assembly code arrived. Both moved down the stack. We still inspect them when timing is tight, when a compiler does something unexpected or when the hardware behaves differently from what the source suggests. They remain important without remaining the place where every development starts.
The important sequence is not chronological. It is functional. We are not looking at generations of machines, but at generations of abstraction between human intent and machine execution.
Now source code may be moving down the stack
Requirements are not new. In a disciplined embedded project, they already define the behavior to be implemented and accepted. Today, a human engineer interprets them - together with architecture, interfaces and other controlled design inputs - and writes C, C++ or Rust. The compiler turns that source into object code, and the target executes it.
AI-based development changes the translation step, not the existence of the requirements. A generator can produce candidate code, tests and configuration from sufficiently precise requirements and acceptance criteria. The engineer still owns the intent; the compiler still produces the executable; and a separate acceptance mechanism still has to establish whether the resulting binary did what was required.
The source language did not make the machine code unimportant. It changed who was expected to produce it.
AI-based development may do something similar to source code. C, C++ or Rust remain important artifacts. Engineers still need to inspect and debug them. But increasingly they can be generated and regenerated from a higher-level description of intent.
That is why source code is becoming a build artifact. Requirements and acceptance criteria have long been durable engineering assets. What changes is their operational role: together with controlled design inputs, they become the basis from which candidate code and tests are generated and regenerated, while independent evidence establishes whether each resulting binary satisfied them.
If that is the new development stack, the difficult part moves as well. Generating another implementation becomes cheap; deciding whether that implementation can be accepted does not.
I do not actually think reproducibility of the generated source is the important property. The generator may well be non-deterministic: the same controlled intent may yield implementation A today, implementation B after a late requirement change, and perhaps C on the next run. What has to be reproducible is the acceptance decision: given controlled requirements and acceptance criteria, we must be able to establish independently whether the newly generated implementation satisfies them.
The generated implementation does not have to be reproducible. Its acceptance has to be reproducible.
This is precisely what could take much of the fear out of late requirement changes. Today, a mature implementation embodies years of accumulated work, and a late change means modifying that valuable artifact with care: understanding the architecture, tracing side effects, adapting tests, chasing regressions. If the durable truth lives one level up - in requirements, architecture decisions, interfaces, constraints and acceptance criteria - the response changes: regenerate, then independently re-establish the evidence that the new system is acceptable. Regeneration can replace manual modification; it does not remove change control or re-verification.
What survives regeneration
A regenerated implementation does not inherit the evidence of the candidate it replaces. What can remain stable are the controlled requirements, acceptance criteria, test obligations, gate logic and approval rules. Functional results, structural coverage, timing measurements and the release decision belong to the exact artifacts and configuration for which they were produced.
A different implementation is therefore a new controlled baseline. The applicable change-impact analysis determines which lifecycle activities must be repeated, and every result credited to the new candidate has to be tied to its source, binary, toolchain, target configuration and recorded run. Deterministic or constrained generation can reduce unnecessary churn and simplify that analysis, but determinism is not the assurance argument: an incorrect implementation can be perfectly reproducible.
The economic premise is not “regenerate without revalidating.” It is “make rebuilding the evidence part of the pipeline.” The AI generator and compiler remain in the transformation path; the acceptance policy, evidence ownership and verdict remain outside it. The external gate does not prove either tool universally correct. It records what has been established for this exact build and leaves everything else unresolved.
That chain contains three different engineering layers that are often grouped together.
A hardware-aware generator can read technical documentation, produce firmware, flash a board and run functional tests. A standards-aware generator can produce code aligned with rules such as MISRA® or AUTOSAR. Both are useful advances. Neither one, by itself, is the release decision.
Acceptance asks a different question: did the optimized target binary satisfy the declared acceptance criteria, measured through a mechanism whose criteria, oracles and verdict the generator cannot control - and with anything not established left visible?
CEDARtools.Forge is built around that third layer. Generation produces candidates. A separately controlled acceptance mechanism decides what the available evidence actually establishes.
A build artifact can be extremely important and still be replaceable. We care deeply about the binary that runs in a controller, but we do not normally edit that binary by hand. If it is wrong, we fix the source and build it again. In the same way, if generated C can be regenerated from an authoritative requirement, then the long-term asset is no longer every individual line in the generated file.
This also gives us a useful test for whether the abstraction is complete. If engineers routinely have to hand-edit generated C and preserve those edits because the higher-level description cannot express them, then the C file is still partly source. The day those changes can be expressed in the controlled higher-level inputs - requirements, architecture, interfaces, constraints or configuration - and the code can be regenerated without losing intent is the day the role has genuinely changed.
A prompt is not the new source language
There is an obvious trap here. If requirements and controlled design inputs are to become a direct generation basis for source code, that does not mean ordinary prose suddenly becomes an executable specification. A prompt such as “build a safe motor controller” leaves almost everything that matters unanswered. A human programmer would have to make assumptions, and an AI generator will do the same.
The useful abstraction is therefore not natural language by itself. It is intent that is precise enough to be checked. Depending on the system, that may combine normal prose with interface definitions, timing limits, state models, invariants, examples and acceptance criteria. The exact notation matters less than whether two engineers can read the requirement and agree on what observable behavior would satisfy it.
In that sense, AI makes good requirements more important rather than less important. A strong generator can turn ambiguity into a plausible implementation very quickly. That is helpful when the ambiguity is harmless, but dangerous when the missing decision concerns a failure mode, timing constraint or boundary condition. The better generation becomes, the less comfort we should take from code that merely looks reasonable.
Domain rules improve the candidate, but they do not replace acceptance. Code can be MISRA-clean, AUTOSAR-aligned or generated from a hardware-aware context and still implement the wrong requirement, omit a boundary condition or leave behavior unexamined in the optimized binary. Conformance by construction reduces known classes of defects. Independent acceptance establishes what has actually been demonstrated for this particular build.
The compiler analogy has a limit
A compiler and a generative model are not the same kind of tool. Given the same source, compiler version, options and target, a compiler will normally perform the same transformation again. That repeatability is valuable, but it should not be confused with infallibility. Conventional compilers are complex programs and can contain defects, including defects that silently produce wrong code.
CompCert is a notable exception to the usual level of assurance. Its central compilation passes are accompanied by machine-checked proofs that the generated assembly code preserves the behavior defined by the source program. The qualification is important: not every step around the compiler lies inside that proof boundary. Preprocessing, parts of the front end, assembling and linking still have to be considered separately. CompCert therefore does not make verification unnecessary; it shows how much confidence can be gained when the translation itself is no longer treated as an opaque implementation detail.
Generative AI starts from a less stable position. The same requirement can lead to different implementations, and gaps in the requirement may be filled with assumptions that are plausible rather than intended. Reading the generated source helps, but it is not enough to establish trust. If the chain from requirements through AI to code is to be dependable, every generated candidate has to be evaluated through an acceptance mechanism whose criteria, test oracles, measurement path and verdict are controlled outside the generator. The generator may propose artifacts, including tests; it must not be the sole authority over what counts as a pass.
So what does that evidence have to look like? Generated source code is not literally a black box: the code is visible, can be reviewed and can be debugged. What remains opaque is the relationship between that code and the engineer’s original intent. Reading the implementation cannot tell us with sufficient confidence that every requirement was implemented, that no unintended behavior was introduced, or that all relevant behavior will occur correctly on the target. Trust therefore has to come from evidence collected independently of the generator’s verdict. Independence here does not mean that no generated artifact may be used. It means that acceptance-policy ownership is separated from artifact generation, that tests and oracles are versioned and reviewable, and that the generator cannot silently alter the conditions under which its own output is accepted. Within that arrangement, the evidence itself needs two kinds of completeness.
What acceptance evidence has to establish
The first is functional completeness. Every declared acceptance criterion in scope needs a recorded verification outcome. Where the claim concerns runtime behavior, tests include relevant boundaries, error cases and failure behavior and run in an environment representative of that claim. This is the minimum, and on its own it proves less than it seems: requirements-based verification says nothing about implementation behavior that no requirement asked for.
The second is structural completeness. Structural coverage does not prove that the implementation is correct, but it reveals which parts of the generated implementation were not exercised by the requirements-based tests. That makes the remaining gaps visible and turns them into explicit engineering questions rather than hidden behavior.
We measure coverage top-down: start at the highest integration level at which the requirement can be meaningfully observed, then use integration and unit tests for obligations that demonstrably remain. Each step down the ladder is justified, and so is whatever remains unexecuted at the bottom: a missing requirement, which gets written; a missing test, which gets added; or unreachable code, which is either removed or - where it exists intentionally, for example as a defensive check or deactivated feature - retained with a documented justification.
The accounting is complete only when every structural item is either exercised or explicitly dispositioned. A justification does not turn an unexecuted item into covered code. It records why the item remains outside executed coverage, the evidence supporting that decision and who approved it. That is the method we laid out at DX 2024 and Embedded World 2026, and it is the only sense in which we use the word complete.
Where the agreed assurance scope calls for target evidence, execution and coverage are collected on the target or project-representative hardware from the optimized binary under review. This avoids relying solely on a model for the observed behavior. Virtual platforms may still be useful elsewhere in the lifecycle, but representativeness has to be argued wherever credit is taken for their results.
The application executes on the target while trace-based observation avoids application-side coverage counters in the binary under review. The supported trace configuration, measurement boundary and applicable probe effect are stated for the target and engagement.
Is that more effort than most projects spend today? Yes, and by a wide margin if people had to write and run those tests by hand. The objection weakens when the tests are generated and executed by machine against requirements the engineer owns and the structural accounting comes from the observation described above.
Only then is the generated implementation trusted for a reason that can be written down: every declared acceptance criterion in the agreed scope has a recorded outcome, every structural item in the agreed scope was either exercised or explicitly dispositioned, with executed coverage and justified non-execution reported separately, and the measurement was taken on the target, not in the generator’s own account of itself. The result is a stronger and more reviewable assurance case - not proof of complete system correctness, but a recorded basis for understanding what was tested, what was observed and what remains outside the claim.
Without that evidence, the process is still trusting a black box. The generated code may be visible and readable, yet the decision to accept it would rest on the model’s apparent plausibility rather than on a checkable engineering case. The objective is not to prove that AI is generally trustworthy. It is to make each generated implementation earn trust by showing what it does, what was tested and how the results relate back to the requirement.
CEDARtools.Forge closes the acceptance loop
AI coding tools no longer necessarily stop at source code. Hardware-aware agents can compile, flash, drive bench instruments, run their own tests and repair the result on real silicon. That closes a functional generation loop. It does not by itself close an acceptance loop, because the same system may still propose the implementation, propose much of the test logic and interpret the result.
CEDARtools.Forge places a separately controlled acceptance policy around the candidate-generation loop. The engineer owns the requirements and acceptance criteria. Within the workflow, a generator may produce code, tests and configuration; deterministic build, test, coverage and integration gates evaluate the result and produce pass, fail or unresolved outcomes from recorded inputs. The generator may iterate, but it cannot change what counts as a pass or set its own result to green.
The candidate executes on the target, and Forge* combines the functional results with structural coverage derived from the target’s trace data. Anything left uncovered has to lead somewhere explicit: to a missing requirement, a missing test, unreachable code that should be removed, or a documented justification for code that intentionally remains.
This creates a closed acceptance loop. When a requirement changes, the implementation and its tests can be regenerated and the evidence can be rebuilt from execution on the target, without making the engineering decision less reviewable.
There is also an economic consequence. Generating tests from requirements and collecting the structural accounting from trace-based observation of the binary under review can make this discipline economically plausible for a broader class of projects than those that already budget for certification-driven evidence.
This is the important difference between code generation and closed-loop development. Generating software is becoming easy. Forge* is intended to make it practical to decide, with evidence rather than plausibility, whether the generated software should be accepted.
What changes for engineers?
Some low-level skills will become less common, just as many software engineers today rarely write assembly code. That is not necessarily a loss of engineering competence. It is a shift in where that competence is used.
Someone still has to notice that two requirements contradict each other, that a specified timing budget cannot be met on the selected hardware, or that a failure case has never been defined. Someone still has to decide whether a trace from the target actually supports the claim being made. Those tasks become more important when generating another candidate implementation becomes cheap.
There will also be plenty of reasons to go down a layer. When a compiler behaves unexpectedly, we inspect assembly code. When generated C behaves unexpectedly, we will inspect C. The existence of a higher abstraction does not remove the lower ones; it changes how often we have to work there and what we expect to preserve by hand.
In high-assurance work, source-code review may remain an explicit lifecycle activity. The scalable shift is not that nobody will ever inspect generated code; it is that line-by-line human review no longer has to be the sole acceptance mechanism for every regenerated candidate. Evidence is rebuilt every time, while human review is applied where the assurance plan, change impact or unresolved findings require it.
One more layer up
The progression from plugboards to assembly code and from assembly code to C, C++ and Rust did not remove engineering from the process. It changed the level at which engineers normally expressed their intent and allowed increasingly complex systems to be built without manually carrying every lower-level detail.
There is no obvious reason to assume that today’s programming languages are the final layer engineers must author directly. Requirements and acceptance criteria already guide disciplined development. They may now become the controlled basis from which candidate implementations are routinely generated and regenerated - provided they are precise enough and each result can be accepted independently of the system that generated it. The invariant is not same requirements, same code. It is same controlled intent, independently acceptable system.
That is the condition that matters. If changing a requirement allows us to regenerate the implementation, execute it on the real target and rebuild a reviewable chain of evidence from the requirement to the observed behavior, then source code no longer has to carry the project’s intent by itself.
AI can now write firmware that runs. Hardware-aware agents can even flash the board and run bench tests. Standards-aware models can produce rule-aligned code. CEDARtools.Forge answers the next question: did the optimized target binary satisfy the declared acceptance criteria - measured through a mechanism other than the generator, with everything not established left visible?
Once that loop is complete, source code has not disappeared. It has simply taken the same step that machine code and assembly code took before it: still important, still inspectable and still available whenever engineers need to go down a layer, but no longer necessarily the primary artifact they author and preserve as the expression of system intent.
Explore CEDARtools.Forge · See the CEDARtools.Forge assurance boundary
Historical notes
For the history of FORTRAN and the skepticism around automatic programming, see John Backus, The History of FORTRAN I, II, and III, ACM SIGPLAN Notices 13(8), 1978, DOI 10.1145/960118.808380, and IBM’s historical material on FORTRAN.
For compiler correctness, see Xuejun Yang et al., Finding and Understanding Bugs in C Compilers (PLDI 2011), and the CompCert C compiler.
Image credits: the EAI 580 plugboard photograph by Tomwsulcer is released under CC0 via Wikimedia Commons; the Motorola 6800 listing (Michael Holley) and the FORTRAN punch card (Tangopaso) are in the public domain.
Related: CEDARtools.Forge · the CEDARtools.Forge assurance boundary · closed-loop AI-based development · coverage for integration evidence · our publications
Comments or feedback on this article? blog_comments@accemic.com