1. Scope, Contracts and Value Flow
BinaryWrapper represents CLI invocations with generated C# command types and exposes process output through independent parsing contracts. Arguments, environment variables, stdout/stderr lines and terminal status each have a place in this value flow.
For a junior developer, the entry point is a command object. For an architect or product lead, the starting point is the contract: which invocation can be constructed, which output can be interpreted and which parts remain raw or version-dependent.
The recurring problem
Each CLI has its own command syntax, output formats and version boundaries. Packer's flags differ from Vagrant's, Git offers several porcelain protocols, and Compose can interleave progress with service logs.
A handwritten command string may compile despite a misspelled flag. A path containing spaces can split into multiple arguments. A parser can classify a progress message as success even though the process later fails. A tool upgrade can change the output format while leaving a command name intact.
These failures have different causes. Typing arguments helps with construction; it does not establish a schema for stdout. Recognizing a message helps with observation; it does not determine the final process exit. BinaryWrapper treats those as separate contracts.
The value flow
The application moves through inspectable values before it crosses an execution boundary.
Typed command
-> command path + argument array + environment dictionary
-> ProcessSpec
-> process lines and actual process exit
-> wire records + semantic observations
-> application decisions or collected resultTyped command
-> command path + argument array + environment dictionary
-> ProcessSpec
-> process lines and actual process exit
-> wire records + semantic observations
-> application decisions or collected resultThe index example uses VagrantUpCommand, VagrantEnvironment and IVagrantRunner. The constructor and properties describe an invocation. Enumerating the runner's stream performs execution. A fake transport can substitute recorded lines for a real process.
This split matters during review. You can inspect the command's arguments without creating a VM, then test how the application responds to readiness, diagnostics and nonzero exit. The production composition chooses the actual executable and transport.
What changed in this iteration
The v1 article introduced the core progression from help scraping to a generated API. The current code strengthens the boundaries around that progression.
| Concern | Current implementation |
|---|---|
| Output compatibility | Factories select parsers from semantic versions and command-specific formats. |
| Stateful parsing | A factory creates a fresh parser for each execution. |
| Evidence | Line-derived events retain the original stdout/stderr line and parsing metadata. |
| Completion | Typed execution receives the runner's real exit status after output drainage. |
| Consumption | Tool-specific .Contracts assemblies expose commands, environments, messages, results and runner interfaces. |
| Environment | Declarative C# catalogues feed build-time generation of typed records and compatibility serializers. |
| Explicit values | Commands and environment records distinguish unassigned properties from explicitly assigned default values. |
These additions separate command construction, transport and output interpretation. A business component can consume contracts while the application host supplies concrete runners. Unsupported formats remain visible through raw fallback instead of guessed milestones.
Generated commands and build inputs
Roslyn produces code during compilation. Versioned JSON command trees and a descriptor become concrete C# command types, builders and clients. Environment catalogues become generated abstract records and related types.
The generated properties give IntelliSense and C# type checking. Generated serialization preserves token boundaries and uses the descriptor's flag conventions. Version guards carry the compatibility information derived from the collected help samples.
There is still runtime validation. The compiler cannot infer which executable a host will resolve tomorrow, which plugins are installed or whether a daemon is reachable. Application logic still decides how to use the command and its observations. Versioned commands explains where generated guards stop.
Output observations and terminal status
A Vagrant state,running observation can describe an already-running VM. A communicator-ready message reports a different condition. Provisioning may still fail after that message. The final VagrantCommandExited event reports the process result.
Each observation supports a specific interpretation:
- A machine state observation updates inventory.
- Communicator readiness can unlock operations requiring that communicator.
- A provisioning observation can inform progress reporting.
- Process completion decides whether that invocation returned successfully.
- Application health requires a separate health observation or probe.
This vocabulary avoids turning every log line into a claimed state transition. Typed output follows the distinction from raw evidence to semantic records.
The boundary exposed by a wrapper
A consumer library connects invocation values to observations through explicit execution and parsing contracts. Those contracts describe one tool boundary.
The generated command supplies arguments and environment overrides. A binding identifies an executable and version metadata. The runner executes that command and produces raw lines, recognized protocol records, semantic observations and terminal status.
An application chooses how to use those observations and whether to request a collected result. Chapter 14 demonstrates the implemented composition points and the evidence retained at each boundary.
Questions to carry into the implementation
When evaluating a wrapper, ask which evidence supports each guarantee. Does its help surface contain the needed positional argument? Which output profile covers the selected binary version? Does its environment setting request the intended format? Does its collector require terminal success?
The implementation matrix is organized around those questions. It treats coverage as a set of known contracts, rather than a single “supported tool” label. That is the useful unit for planning a feature and estimating maintenance.
Source snapshot
Source inspection: 2 October 2026. These implementation paths are relative to Net/FrenchExDev in the local ecosystem checkout. The v2 changes are largely uncommitted; these paths identify inspected code, without claiming that public branch links contain it.
BinaryWrapper/README.mdBinaryWrapper/doc/CONSUMER-MIGRATION.md