Ossature 0.2 is out. The main addition is VMD, a third spec file where you write test cases yourself, and the model that generates the code never sees them.
Some context for why. Ossature generates code from specs: you write what the software should do, review the plan it produces, and an LLM generates the code with a verify command after each step. Until now, when a task needed tests, the same model that generated the code generated the tests. If it misread a requirement, it misread it in both places, and the suite passed. The 0.1 reviewer helps, but a reviewer is still a model checking a model.
A VMD holds concrete cases with the outcome you expect. For a pure function it's a table, one row per case:
parse_duration(text) -> int
compact | "2h30m" | 9000
colon | "1:30:00" | 5400
empty | "" | !ValueError: empty
Commands get scenarios. This one is from yep, the small Rust clone of yes from the last post:
@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"
Writing that file was its own exercise, because yep streams forever by design. Only behavior that ends on its own can be pinned to an expected outcome, and in yep that's one thing, the invalid UTF-8 check, which runs at argument parsing and exits immediately. So the file holds three scenarios for three ways bytes can fail UTF-8 validation. Bytes that are never valid, a lone continuation byte, and a broken pair in the second argument, that last one to check that validation covers every argument. The comment explaining what stays out of the file came out about as long as the cases. Everything else yep does needs a bounding consumer like head, so it stays in ordinary test tasks, and ossature validate prints a coverage table showing which requirements are pinned by cases and which aren't.
The part I care about is what never touches these cases. Audit adds a verification task to the plan, and when that task runs, Ossature serializes the cases to a fixture and generates a pytest harness from a fixed template. No model call anywhere in that path. The implementer's prompt never includes the VMD, so the code can't be generated to fit the tests. On failure, the fixer edits the implementation files while the whole checks/ directory rejects its writes. The harness also asserts it ran exactly as many cases as the fixture holds, because a loader that quietly skips cases would otherwise pass. Maybe that's paranoid. But the feature rests on the assumption that anything a model can route around, it eventually will.
Could I write a pytest file into the output directory by hand instead? Sure, and for a one-off that works. But that file lives outside the system. The fixer can edit it, and the coverage table doesn't know it exists. As a plan task, an edited case re-runs the suite against the existing code without rebuilding anything.
Function tables run for Python output only right now. For other languages they validate and count toward coverage, but the harness can't call into the code yet. Command scenarios run for any language, which is all yep needs.
The other change in 0.2 closes an item from the last post. Interfaces work like header files between specs, but the boundary only held across builds. Rebuilding a spec re-ran every dependent in the same build even when the extracted interface came out identical. Now a dependent hashes the upstream interface into its inputs and skips when that interface is unchanged. Edit a function body and the dependents stay put; change a signature and they rebuild. If the upstream rebuilt and no interface could be extracted, the dependents rebuild too, since the interface file on disk can't be trusted at that point.
The rest of 0.2 is the move to pydantic-ai 2.0 and a long cleanup pass over audit, planning, the build loop and the parsers, which shook out its own pile of bug fixes. The full list is in the changelog.
That's both prerequisites for self-hosting from the last post done. Next is the attempt itself, writing Ossature's own specs and seeing how far a build gets. I don't expect it to get far at first, but a failure is now something I can write down.