The driver handled one file. It now loads the graph rooted at the entry file and checks every unit in it. The import root is derived rather than configured: a unit named `a.b` read from `<root>/a/b.fe` fixes `<root>`, so a sibling `import c.d;` is looked for at `<root>/c/d.fe`. That is enough for the fixtures and for any tree that follows 8.1, and it means there is no path flag to get wrong yet. Loading is depth-first with the chain of units currently open kept on a stack, so meeting one again is a cycle rather than a revisit -- a unit reached twice by different paths is loaded once. Cycles, imports with no source file, and two imports claiming the same binding are all reported. One thing this exposed: FeDiags held a single source buffer, so once a build spanned several files every excerpt was drawn from whichever file was parsed last. `cycle/b.fe:3` printed the text of a.fe. fe_diags_source switches it, and loading and checking both set it per unit. The cycle fixtures lost their line markers on purpose. Which import closes the cycle depends on which file you enter from -- entering at a.fe reports b.fe, entering at b.fe reports a.fe -- so pinning a line would pin an arbitrary half of a symmetric pair. The message is pinned; the line is not. units: missing, bindconf and cycle pass, on top of the identity cases. 146 -> 148 of 188. What is left in units/ needs cross-unit name resolution and visibility: an importer still cannot see `util.answer`.
M8 fixtures
M8 is the first multi-unit milestone, so cases live in subdirectories. Each case is compiled separately with that directory on the import path.
Pass cases:
basic: public function across units.pubfld: public type and public field across units.dottedandalias: canonical dotted unit paths, last-segment binding, and explicit import aliases.dotpriv: a dotted unit prefix does not grant access to another unit's private declarations.errsame: two units use the same anonymouserror.Name; the driver must assign one deterministiccore.Errorcode.errdet: the same anonymous error-name set appears in a different source/import order; generatedfe_errors.hmust be byte-identical toerrsamemodulo the intentionally different unit graph.
Fail cases:
privfn: private declaration access.privfld: private field access.missing: unresolved import.cycle: cyclic imports.unitbad: filename/unit-name mismatch.badupperandbadlong: DOS-safe lowercase, eight-character unit-segment limit.bindconf: two imports with the same last-segment binding require an alias.pubpriv: a public signature cannot expose a private nominal type.errnom: nominal error cannot flow intocore.Errorviatry.
They are not registered in src/ferrolang_vm/registry.py yet. M8 should add procedural checks for .fei creation/hash invalidation and deterministic fe_errors.h using these fixtures.
The M8 DOS gate must additionally construct two separate -I roots containing the same canonical unit and require an ambiguity error; repeat the case with two paths to the same canonical file and require deduplication. It must also verify that std.* resolves only from the built-in std root, ordinary user units never do, and that changing a private non-generic implementation preserves the dependent interface-cache hit. Those checks need temporary roots/cache inspection and deliberately remain procedural rather than encoding host paths in fixtures.