A build system for specs and architecture.
You write the spec. An LLM generates the code, verified task by task. You review the plan before it runs.
$ uv tool install ossature
The pipeline is three commands.
$ ossature validate # structural check, no LLM
$ ossature audit # review the specs, write a plan
$ ossature build # generate the code, verify each taskBuilding yep, end to end.
yep
is a Rust take on the yes command: print a line forever until the pipe
closes. It is built from three files, one for what it does, one for how it fits
together, and one for the checks you write yourself.
Say what it does
The behavior, the inputs and outputs, and every error case. No code, no types.
---
id: YEP
status: draft
priority: medium
---
# Yep
## Overview
yep writes a line to stdout, over and over, until the
process is killed or the pipe closes.
## Requirements
### Output Repeated String
**Accepts:** Zero or more string arguments. Defaults to
"y". Multiple arguments join with a single space.
**Returns:** The string plus a newline, forever.
**Errors:**
- Broken pipe -> exit silently with code 0
- Other write error (ENOSPC, EIO) -> exit code 1
- Invalid UTF-8 argument -> error to stderr, exit 1Lay out the pieces
The architecture is optional. It names the components and the contracts each one has to hold.
# Architecture: Yep
## Components
### OutputLoop
@path: src/output.rs
Handles buffered writing to stdout in a tight loop.
**Interface:**
fn run(output: &str) -> Result<(), WriteError>
**Contracts:**
- run returns Ok(()) when stdout closes (broken pipe)
- run pre-allocates a buffer and reuses it each iteration
- fill_buffer packs as many copies of output+newline
as fit in the bufferWrite the checks yourself
You write the cases with the exact outcome you expect. The model never sees the expected values, so it cannot write the code to fit them.
@spec YEP
# yep streams forever, so only invocations that stop on
# their own can be pinned here. Invalid UTF-8 is rejected
# at argument parsing and exits right away, so it has an
# outcome you can write down: exit 1 and an error on
# stderr that names the problem.
@covers "Output Repeated String"
scenario rejects bytes that are never valid utf-8:
when $ yep \xff\xfe
then exit 1
then stderr has "UTF-8"
@covers "Output Repeated String"
scenario rejects invalid utf-8 in any argument:
when $ yep hello \xc3\x28
then exit 1
then stderr has "UTF-8"Audit plans the work
Audit turns the three files into a plan you can read and edit before anything runs.
[[task]]
id = "006"
spec = "YEP"
title = "Verify: scenarios (YEP)"
outputs = [
"checks/scenarios.yep.YEP.cases.json",
"checks/test_checks_scenarios_yep_YEP.py",
]
depends_on = ["005"]
status = "done"
verify = ["python -m pytest checks/test_checks_scenarios_yep_YEP.py -q"]
kind = "verify"
vmd_file = "specs/YEP.vmd"
vmd_group = "@scenarios"
covers = ["Output Repeated String"]Build generates the code
Build runs the plan. The output loop it writes matches the contract from the
architecture: return Ok(()) when stdout closes.
use std::io::{self, Write};
/// Buffered writing to stdout in a tight loop.
/// Returns Ok(()) if stdout closes due to a broken pipe.
pub fn run(output: &str) -> Result<(), WriteError> {
let mut buffer = Vec::with_capacity(65536);
fill_buffer(output, &mut buffer);
let mut stdout = io::stdout().lock();
loop {
if let Err(e) = stdout.write_all(&buffer) {
if e.kind() == io::ErrorKind::BrokenPipe {
return Ok(());
} else {
return Err(WriteError::Io(e));
}
}
}
}Your checks run against it
Your cases become a real test suite that runs against the built binary. It has to pass.
"""Generated verification harness for YEP. Do not edit.
The scenarios come from a generated fixture, produced
from the author-written verification spec. The expected
values are author-owned; changing this file does not
change what correct means.
"""
_EXPECTED_CASE_COUNT = 3
@pytest.mark.parametrize("case", _CASES, ids=_name)
def test_YEP_scenarios(case, tmp_path):
if case["kind"] == "command":
_run_command_scenario(case, tmp_path)
def test_YEP_scenario_count():
assert len(_CASES) == _EXPECTED_CASE_COUNTThe whole project is on GitHub
yep ships with its specs, the generated plan, every prompt, and the code that came out, so you can read the full path from spec to binary. The other examples do too.
Read the full yep example on GitHub →The specs are the source. The code is output.
The spec is the source
Specs describe what to build, architecture describes how it fits together. The code is the output, not the source.
Compiles incrementally
Every input is checksummed, so a change recompiles only what depends on it, never the whole project.
Scoped per task
Each task is compiled against only the spec sections, types, and files it needs.
Reproducible on disk
Specs, the plan, every prompt, and the generated code live in the repo, checksummed.