SFEP-0020
Role-Oriented Compiler Capsules
- Status
- Implemented
- Type
- tooling
- Created
- Updated
- Author
- agent:compiler-architect; project owner (direction + naming); agent:Codex (2026-08-03 amendment; 2026-08-08 §3.5 corrections 1–2 status; 2026-08-17 SFN-894 check-filter amendment); agent:implementer (2026-08-05 provider slot); agent:Sailbot (2026-08-06 narrow-stdlib matrix amendment; 2026-08-06 §3.5 correction 7 prelude-slug extension)
- Tracking
- #345 (historical epic); Linear project Role-Oriented Compiler Capsules. §3.7 steps 1-2 (landed): SFN-705, SFN-706, SFN-707, SFN-708, SFN-709, SFN-710, SFN-711, SFN-712, SFN-713, SFN-714, SFN-715, SFN-717, SFN-718, SFN-734. §3.7 steps 3-8 (groomed 2026-08-06): SFN-735 through SFN-751. §3.3.1 remediation: SFN-894.
SFEP-0020 — Role-Oriented Compiler Capsules
The 2026-08-03 amendment replaces the original
compiler-frontend/compiler-backend/compiler-commondesign. The compiler has more than doubled in size since that design was measured, and its present phase structure supports boundaries named for responsibilities rather than for which side of the pipeline they occupy.
1. Summary
Decompose the self-hosted compiler into six workspace-private capsules:
sfn/syntax, sfn/analyzer, sfn/ir, sfn/codegen,
sfn/codegen-llvm, and the sfn/compiler binary. Each name denotes a stable
responsibility and an explicit input/output contract. All six capsules live
under compiler/, carry [capsule] publish = false, version in lockstep with
the compiler, and remain implementation details of the Sailfin toolchain. A
seventh name, sfn/codegen-native, is reserved but not created by this
proposal — it is the peer-provider slot SFEP-0066 §3.2 requires, and every
count of “six capsules” below excludes it.
This design deliberately has no frontend, backend, or common capsule.
Those labels describe relative position, not ownership. A generic common
capsule would also turn dependency mistakes into a permanent dumping ground.
Shared generic functionality belongs in the standard library; shared compiler
data belongs to the phase that defines its contract.
2. Motivation
The compiler is again large enough that a single capsule obscures ownership,
inflates rebuilds, and makes independent tool surfaces harder to isolate. On
2026-08-03, compiler/src/ contains 351 .sfn files and 145,292 lines,
compared with the original proposal’s 166 files and approximately 99,000 lines.
The current tree now has substantial dedicated subgraphs for:
- syntax (
parser/,lexer.sfn,token.sfn,ast.sfn); - semantic analysis (
typecheck/,typecheck_types/,effect_checker/, ownership, decorators, and diagnostics); - three intermediate representations (native IR, typed SSA, and tensor IR);
- LLVM lowering (
llvm/, currently 139 files and approximately 65,000 lines); - workspace/build orchestration (
build/,capsule_resolver/); - commands and tools (
cli/,tools/, test orchestration).
The original two-way cut no longer describes this system. In particular:
frontendbundled tokenization, parsing, semantic analysis, import metadata, and user diagnostics even thoughsfn check, formatting, symbol indexing, and future language services need different subsets of those facilities.backendbundled target-neutral IR, optimization, LLVM-specific lowering, filesystem publication, assembly, and linking. That boundary conflicts with SFEP-0066’s requirement that LLVM be a replaceable provider.commonwas defined by the existence of cycles rather than by a coherent contract. That rewards the current dependency graph instead of correcting it.- The old proposal marked reusable compiler libraries publishable. These are not standard-library APIs and must not acquire compatibility obligations by accident.
The goal is not to make the compiler look sophisticated by choosing unusual names. Mature compilers routinely use responsibility names such as syntax, semantic analysis, IR, code generation, and target-specific generation. The production-quality property is that each name predicts what the capsule owns, what it may depend on, and what it returns.
3. Design
3.1 Naming and visibility decisions
The accepted capsule set is six, plus one reserved slot marked as such:
| Capsule | Responsibility | Stable output | Must not own |
|---|---|---|---|
sfn/syntax |
Lexing, parsing, source spans, tokens, and AST shapes | parsed syntax tree | symbol resolution, type/effect rules, lowering |
sfn/analyzer |
Name/import resolution, types, effects, ownership, decorators, and semantic diagnostics | analyzed program + diagnostics | CLI rendering, filesystem discovery, target lowering |
sfn/ir |
Native IR, typed-SSA, and tensor-IR data models, parsers, renderers, and verifiers | validated IR values | AST traversal, target code generation, file publication |
sfn/codegen |
Target-neutral lowering from analyzed syntax into Sailfin IR, including IR-producing optimization passes | verified Sailfin IR | LLVM spelling, process execution, linking |
sfn/codegen-llvm |
LLVM-specific lowering from Sailfin IR to LLVM IR/object inputs | LLVM module text or object-ready data | parsing source, semantic analysis, final linking |
sfn/codegen-native |
(empty slot) A native code-generation provider, peer to sfn/codegen-llvm, consuming verified Sailfin IR directly instead of LLVM text |
object-ready data | parsing source, semantic analysis, final linking |
sfn/compiler |
The sfn binary: command dispatch, workspace resolution, build planning, caches, artifact I/O, toolchain selection, assembly, and linking |
executable/tool responses | reusable language-phase implementations |
analyzer is preferred to semantics because it names an executable service:
given parsed input and imported interfaces, it produces an analyzed program or
diagnostics. codegen is retained because it is the precise industry term for
the transformation being performed. The LLVM implementation is named
codegen-llvm rather than backend so its target dependency is visible and a
future provider can sit beside it without renaming the rest of the compiler.
sfn/codegen-native is that named slot (SFEP-0066 §3.2); it stays empty until
it clears §3.7’s no-placeholder rule.
The sfn/* names are workspace identities, not public-library promises.
publish = false is the normative distribution boundary. Promoting any one of
these capsules into a supported external API requires a separate SFEP covering
versioning, compatibility, documentation, and registry publication.
3.2 Physical layout
Standard-library capsules remain under the repository’s top-level capsules/
directory. Compiler implementation capsules do not go there:
compiler/ capsule.toml # sfn/compiler, binary, publish = false src/ # driver/build/CLI during and after migration capsules/ syntax/ capsule.toml # sfn/syntax src/mod.sfn analyzer/ capsule.toml # sfn/analyzer src/mod.sfn ir/ capsule.toml # sfn/ir src/mod.sfn codegen/ capsule.toml # sfn/codegen src/mod.sfn codegen-llvm/ capsule.toml # sfn/codegen-llvm src/mod.sfnworkspace.toml adds compiler/capsules/* as a member glob. The existing
capsules/sfn/* glob continues to mean first-party standard-library capsules;
the two populations are visibly distinct in the repository even though their
capsule names share the sfn scope.
Every library exposes a small src/mod.sfn facade. Consumers import that
facade or an explicitly documented submodule; filesystem location is not an
implicit public API. Internal capsules share the compiler release version and
are upgraded atomically. When SFEP-0051 workspace package inheritance ships,
the version should be inherited rather than copied into six manifests.
3.3 Dependency graph
The allowed direct dependencies are:
| Capsule | May depend on |
|---|---|
sfn/syntax |
runtime prelude and narrow standard-library capsules |
sfn/ir |
runtime prelude and narrow standard-library capsules |
sfn/analyzer |
sfn/syntax, sfn/ir, and narrow standard-library capsules |
sfn/codegen |
sfn/syntax, sfn/analyzer, sfn/ir, and narrow standard-library capsules |
sfn/codegen-llvm |
sfn/ir and narrow standard-library capsules |
sfn/codegen-native |
sfn/ir and narrow standard-library capsules |
sfn/compiler |
all internal libraries, runtime, and required standard-library capsules |
The intended data flow is:
source text -> sfn/syntax (AST) -> sfn/analyzer (analyzed program + diagnostics) -> sfn/codegen (validated Sailfin IR) -> sfn/codegen-llvm (LLVM/object input) -> sfn/compiler (artifact publication, assembly, and link)Narrow standard-library capsules (sfn/strings, sfn/json, sfn/path, and
the rest of the non-capability-bearing set) are available to every internal
role, including sfn/codegen and the codegen providers. They are leaf
dependencies: they import no internal compiler capsule, so granting them
creates no reverse edge and preserves dependency fan-out. Without this,
§3.5.7’s “generic helpers graduate” is unachievable for codegen and the
providers, which would be left owning private copies of exactly the generic
helpers the correction retires. Capability-bearing capsules remain restricted
to sfn/compiler; that is the boundary this grant does not cross.
sfn/ir is a representation dependency used by the middle stages, not an
orchestrating stage. Its representations must therefore remain independent of
syntax, analysis, code generation, and the driver.
There is a real cost to sfn/analyzer -> sfn/ir: the current resolver
enumerates every .sfn source in a dependency capsule rather than compiling
only the entry facade’s import closure. Grouping native IR, typed SSA, and
tensor IR therefore makes analyzer-only consumers stage the entire IR capsule.
The first extraction must measure that cost. If it materially defeats the
sfn check isolation goal, split the serialized import contract into a narrow
workspace-private sfn/module-interface capsule; do not solve the problem by
moving artifact parsing into analyzer or by restoring a generic common capsule.
For this decision, “materially” means more than a 5% regression in the
three-run median cold sfn check wall time or peak RSS against the monolith
baseline on the same host.
3.3.1 Adjudication (2026-08-15, SFN-747) — over the gate
Verdict: the 5% gate is exceeded, decisively. Three-run median cold
sfn check peak RSS on the trivial workload went from 157.7 MiB at the
monolith baseline to 696.3 MiB in the current tree (+341.5%) — 68× the 7.9 MiB
budget — measured across a five-point commit series on one host, with the
monolith commit re-measured there and reproducing the frozen baseline to within
0.2 MiB. The sfn/analyzer extraction’s own isolated marginal cost is
+35.0%, 7× the gate. Full data, procedure, and caveats:
docs/perf/decomposition-baseline.md § “2026-08-15 — SFN-747 §3.3 adjudication”.
The prescribed remedy does not address the measured cause, so it is not
adopted here. This paragraph’s cost model is the sfn/analyzer -> sfn/ir edge
making analyzer-only consumers stage the whole IR capsule. Measurement shows the
regression instead tracks the number of internal capsules the consuming
manifest declares: compiler/capsule.toml names all five directly (it named
none at the baseline), so a check inside the compiler project enumerates all
five regardless of that edge, and each extraction contributed cost of the same
order (syntax+ir +136 MiB, analyzer +120 MiB, codegen+codegen-llvm +233 MiB). A
narrow sfn/module-interface capsule would remove part of one 120 MiB
contribution out of a 539 MiB regression. There is also no analyzer-only
consumer in the tree today — one binary capsule exists and it depends on all
five — so the shape this paragraph reasoned about is currently hypothetical.
The mechanism is confirmed live in code, not merely inferred:
_cr_collect_capsule_sources
(compiler/src/capsule_resolver/discovery.sfn:181-251) enumerates a dependency
capsule’s whole src/ tree with no import-relevance test. SFN-833 / SFEP-0070
added a reachability filter that originally narrowed only the build path.
SFN-894 extends that same filter to sfn check; this is the accepted remedy.
It is forbidden by neither prohibition above (it moves no artifact parsing
into the analyzer and restores no generic common capsule), and it addresses the
measured fixed cost directly by refusing to stage dependency modules that no
checked file can import.
The former cross-command superset invariant is superseded. build roots its
closure in the project entry plus the selected runtime sources; check roots
its closure in the files the user explicitly requested. Those are different
semantic questions, so requiring one retained set to contain the other adds
unreachable work without adding correctness. The normative invariant is now
per-command closure completeness: every module reachable from that
command’s roots is retained, and the shared verification pass fails open to the
unfiltered set if it cannot prove closure. A build-only runtime module need not
be staged by a check that never links it; a dependency module unreachable from
the checked files is checked in its own capsule’s CI, not speculatively on every
consumer check. sfn/module-interface remains neither adopted nor rejected
on its own merits — it is set aside as not responsive to the measured cost.
The post-remedy SFN-894 run records a three-run median of 149.0 MiB on the
trivial workload, below both the 157.7 MiB monolith reference and its 165.6
MiB (+5%) ceiling. On that Darwin arm64 host, disabling the filter measures
762.0 MiB, so the same-host causal reduction is 613.0 MiB (80.4%). The trace
retains 0 of 301 dependency sources. Full procedure, raw runs, and the
cross-host caveat are in docs/perf/decomposition-baseline.md § “2026-08-17 —
SFN-894 post-remedy check closure”.
The following dependency rules are build/test invariants:
- no internal library imports
sfn/compileror files from its source tree; sfn/syntaxnever importssfn/analyzer;sfn/irnever imports syntax, analyzer, codegen, LLVM, or driver modules;sfn/codegen-llvmnever reparses source or invokes semantic analysis;- only
sfn/compilerperforms workspace discovery, persistent artifact I/O, process execution, assembly, or final linking; - no top-level standard-library capsule imports an internal compiler capsule.
3.4 Source ownership map
This table is a migration map, not a promise that every current file moves unchanged. Mixed-responsibility files must be split at the contract boundary.
| Current area | Destination |
|---|---|
lexer.sfn, token.sfn, ast.sfn, parser/ |
sfn/syntax |
typecheck/, typecheck_types/, effect_checker/, ownership, decorators, import/re-export checks, semantic diagnostic model |
sfn/analyzer |
native_ir* data/parser/verification, typed_ssa.sfn, typed_ssa_{render,verify}.sfn, tensor-IR data/fusion/verification |
sfn/ir |
emit_native*, emitter_sailfin*, typed-SSA production, tensor lowering and target-neutral IR production |
sfn/codegen |
llvm/ |
sfn/codegen-llvm |
main.sfn, cli/, tools/, check/engine.sfn, build/, capsule_resolver/, test runner, cache/manifest/version/lock/release modules, backend.sfn assembly/link provider |
sfn/compiler |
The present backend.sfn stays with the compiler driver because it owns
external tool invocation and final link planning. It may later be renamed to a
driver-oriented name, but it is not part of sfn/codegen-llvm: generating LLVM
and invoking a host linker are separate responsibilities.
3.5 Boundary corrections required before moves
The current import graph contains historical shortcuts. They are not reasons to
reintroduce a common capsule. The migration first fixes these seams inside the
monolith, then moves files:
-
Landed (SFN-734): syntax is syntax-only at this seam.
parser/expressions/prefix.sfnno longer importsspawn_future_kind; the symbol lives only in analyzer-side type rules. The parser retains the source type annotation, and the analyzer assigns the resolved future kind. -
Landed (SFN-734): IR owns its interchange types.
NativeArtifactis defined innative_ir.sfn, andemit_native_layout.sfnimports it from IR. Construction remains in codegen, so the edge points from codegen to IR and IR does not depend back on codegen or analyzer. -
Intrinsic semantics are not LLVM semantics. Effect analysis currently reaches into
llvm/runtime_helpers. Move target-neutral intrinsic identity and effect metadata into IR/analyzer-owned contracts. LLVM keeps only symbol and ABI lowering. -
LLVM does not re-run the frontend. Lambda lowering currently imports the parser and capture analyzer. Capture/lift work must finish before the IR boundary so
sfn/codegen-llvmconsumes typed IR only. -
Code generation returns artifacts; the driver writes them. Current emit and lowering paths import
build/fs.sfn. Replace those edges with returned lines/bytes plus explicit diagnostics.sfn/compilerowns atomic publication and cache policy. -
Driver engines are not analysis libraries.
check/engine.sfncurrently imports workspace resolution, build paths, and CLI-facing tools. It stays insfn/compilerand calls a pure analyzer API. Diagnostic JSON/text rendering that encodes CLI or protocol envelopes also stays in the driver; diagnostic data types stay insfn/analyzer. -
Generic helpers graduate; specialized helpers stay owned. Replace generic string/TOML/JSON/path/crypto copies with
sfn/strings,sfn/toml,sfn/json,sfn/path, andsfn/cryptowhere their APIs are sufficient. LLVM symbol sanitization belongs tosfn/codegen-llvm; native-IR text helpers belong tosfn/ir; build filesystem helpers remain insfn/compiler. There is no catch-all support capsule.This includes the prelude’s share of that surface. Seven files under
compiler/src/import from the slug"runtime/prelude", and between them they take exactly three symbols —char_code(4 sites),substring(3),char_at(2) — which aresfn/stringsmaterial by the rule above, and reachable there from every holding capsule under §3.3’s narrow-stdlib grant. §3.3 permitssfn/syntax,sfn/ir, andsfn/compilerto depend on the runtime prelude, so imports from those roles are not invariant breaches; they are the same generic-helper cleanup, and doing it here removes the prelude from four of the five library capsules’ dependency sets for free.What remains after that cleanup is a migration hazard rather than a boundary error, and it is not this proposal’s to fix.
"runtime/prelude"is a path-shaped import slug resolving to something that is not a capsule: no manifest resolution consults it, and thecapsules/sfn/prelude/stub that nominally represents it contains only acapsule.tomlwhoseentry = "../../../runtime/prelude.sfn"escapes its own root. §3.2 states that filesystem location is not an implicit public API; this slug is one, and the files holding it move to new relative depths undercompiler/capsules/*/src/. SFEP-0006 §2.11 already catalogues the four privileges behind this (hard-coded location, hard-codedruntime__preludemodule name, a link position outsidellvm-link’s merge, and an implicit dependency from every other compile), and §4.8 already designs the fix —sfn/preludeas a realimplicit = truelibrary capsule, which is outstanding Stage F work. Verify slug resolution from the new capsule depths as part of step 3, and treat any breakage as a reason to pull SFEP-0006 §4.8 forward rather than to add a compensating path constant here.
Each correction must reduce or preserve dependency fan-out. A move that creates a reverse edge is incomplete even if the workspace still compiles.
3.6 Private-capsule manifest contract
This SFEP moves publish from the old draft’s proposed [build] location to
[capsule], because publication is package-distribution policy, not a build
mode:
[capsule]name = "sfn/analyzer"version = "0.8.4"description = "Sailfin semantic analysis pipeline (toolchain-internal)"publish = false
[capabilities]required = []
[build]kind = "library"entry = "src/mod.sfn"Manifest semantics:
[capsule] publishis a strict boolean and defaults totruefor backwards compatibility. Any non-boolean value is a manifest error; it must not be interpreted as either true or false.sfn publishrejectspublish = falsebefore packaging, authentication, or any network call and names the capsule in the diagnostic.- Workspace resolution may use a
publish = falsemember from its declared local path. A conforming registry/cache resolver must reject a fetched manifest carryingpublish = falsebefore staging or compilation. - Older compiler binaries cannot be retroactively constrained by this field.
The package registry should also reject bundles whose manifests set
publish = false, providing server-side defense against obsolete or modified publishing clients; that service change is tracked with the schema rollout. publish = falseis not source-language visibility. Other members of the same workspace can import the capsule normally when they declare it.- All six compiler capsules, including the binary, set
publish = false.
3.7 Migration sequence
The decomposition lands as self-hosting slices:
- Private manifest policy. Implement and test
[capsule] publish, then set it on the existing compiler capsule without changing its name. - Monolith boundary cleanup. Land the seven corrections in §3.5 while all
files still live under
compiler/src/. Add static dependency tests before physical moves. - Foundation capsules. Create
sfn/syntaxandsfn/ir; move their leaf graphs and make the existing compiler depend on them. - Analyzer capsule. Move semantic analysis behind one analyzed-program API.
sfn checkremains a driver command but no longer pulls codegen through an analysis import. - Target-neutral codegen. Move IR production to
sfn/codegen; keep all filesystem writes in the compiler. - LLVM provider. Move
llvm/tosfn/codegen-llvmafter the no-reparse and no-driver-import guards are green. - Compiler identity. Rename the root capsule from
sailfintosfn/compiler, preserving the executable path/namebuild/bin/sfnand the installedsfncommand. Verify scoped artifact paths and cache prefixes. - Final gate and measurements. Run the fixed-point gate, full tests, determinism checks, and compare cold/warm compile time and peak RSS with the pre-migration baseline.
No step creates empty placeholder capsules far ahead of a source move. A new capsule lands when it owns a usable contract and at least one real consumer.
4. Effect & capability impact
The decomposition changes no Sailfin effect semantics. It does make the compiler’s authority boundaries more honest:
sfn/syntax,sfn/ir,sfn/analyzer,sfn/codegen, andsfn/codegen-llvmtargetrequired = []. They accept text/data and return values/diagnostics without filesystem, network, clock, or process effects.sfn/compilerretains the toolchain’s![io],![clock], and![net]authority for workspace access, cache/artifact publication, timing, package operations, toolchain download, assembly, and linking.- Any unavoidable effect in an internal library must be documented as a boundary exception and reviewed before the move; convenience is not enough.
This division ensures importing the analyzer does not implicitly grant the authority needed to execute a linker or access the registry.
5. Self-hosting impact
This is a structural change to the self-hosted compiler and therefore follows the strictest bootstrap path.
Before the first manifest switch, a fixture must prove that the pinned seed can:
- parse the expanded workspace member list;
- resolve the private library members from workspace paths;
- build their transitive dependency order; and
- link them into the
sfn/compilerbinary.
The seed does not need to enforce publish = false to build local members, but
it must tolerate the new manifest key. If it cannot, the schema implementation
must ship and be pinned as a seed before private manifests adopt the field.
For each .sfn change under compiler/src/ or compiler/capsules/:
- format the touched files;
- run
make compilebefore targeted tests so they use the new compiler; - run the narrow relevant unit/integration tests; and
- keep the compiler able to rebuild itself at the end of the change.
Every physical capsule move is structural and therefore requires
make clean-build before rebuilding. The final slice runs make check to prove
the seedcheck fixed point. Module/capsule names affect symbol mangling and
artifact paths, so deterministic differences must be explained as mechanical
renames; semantic output must remain unchanged.
6. Alternatives considered
Keep compiler-frontend, compiler-backend, and compiler-common
Rejected. Frontend/backend are directional labels that conceal several stable services, and common has no ownership rule. The current tree already proves the three names are too coarse.
Use sfn/compiler-* for every component
Rejected as the default. The sfn scope, private manifest, and physical
compiler/capsules/ location already establish ownership. Repeating
compiler- makes imports longer without making the boundary more precise.
Target-qualified names such as sfn/codegen-llvm remain appropriate because
the qualifier distinguishes implementations.
Use only sfn/analyzer, sfn/codegen, and sfn/compiler
Rejected as the final architecture. It is a reasonable first extraction, but it
would leave target-independent IR contracts buried inside codegen and the
65,000-line LLVM graph inseparable from other generation work. sfn/syntax,
sfn/ir, and sfn/codegen-llvm create useful independent cache and dependency
boundaries.
Name the semantic capsule sfn/semantics or sfn/sema
Rejected. sema is concise but project-insider jargon; semantics describes a
subject rather than the service. analyzer is clear at import sites and aligns
with sfn check and future language-service consumers.
Put compiler capsules under top-level capsules/sfn/
Rejected. That directory is the standard-library inventory. Co-locating private compiler implementation would make repository layout contradict manifest policy and blur what ships for user import.
Introduce sfn/compiler-common or sfn/compiler-support
Rejected. Renaming common to support does not create an ownership rule. Generic facilities belong in standard-library capsules; representation-specific helpers belong to IR/codegen/analyzer; orchestration helpers belong to the compiler.
Extract sfn/driver in the first decomposition
Rejected. sfn/compiler is currently the only consumer of build planning,
cache policy, process execution, and final linking. A driver library without a
second consumer would add another capsule boundary while leaving the same
authority and dependency closure. Extract it later if a real embedding or
daemon use case establishes a smaller reusable contract.
Publish syntax/analyzer as reusable libraries now
Rejected. Their APIs are not versioned or supported for third-party use. A private capsule boundary is valuable for the compiler without prematurely turning an internal refactor seam into a public ecosystem contract.
7. Stage1 readiness mapping
This proposal changes toolchain structure rather than language behavior. The checklist is interpreted as preservation across the decomposed workspace:
- Existing programs parse identically through
sfn/syntax. - Existing type/effect/ownership checks and diagnostics remain equivalent
through
sfn/analyzer. -
sfn/codegenemits valid, deterministic.sfn-asm/ typed IR. -
sfn/codegen-llvmlowers the validated IR to equivalent LLVM IR. - Boundary, manifest, regression, and end-to-end coverage is green.
- The decomposed compiler self-hosts to a fixed point.
- All moved
.sfnfiles passsfn fmt --check. -
docs/status.mdand relevant architecture references describe the shipped capsule graph. No language-spec change is required.
SFN-751 closed the implementation gate on 2026-08-17. make check reached a
byte-identical stage2/stage3 fixed point with zero module differences and
passed 316 unit, 56 integration, 336 end-to-end, and 92 capsule test files.
The dedicated ten-iteration, four-worker determinism sweep covered 445 modules
with zero nondeterministic, emit-failed, or missing-result modules. The final
cold/warm build measurements and their cross-host interpretation are recorded
in docs/perf/decomposition-baseline.md; raw values are in
docs/baselines/compile-sfep0020-post-migration-darwin-arm64.csv.
All readiness items are complete, so this SFEP is Implemented. The unrelated clean-tree Make-contract concurrency race observed during the gate is tracked separately as SFN-918; both affected tests and the complete stable rerun passed.
8. Test plan
Manifest policy
- Unit-test omitted/true/false/malformed
[capsule] publishvalues. - E2E-test that
sfn publishrejects a private capsule before authentication or network access. - Resolver-test that workspace-private members resolve locally and the same manifest is rejected when presented as a registry/cache candidate.
Dependency architecture
- Add a static import-boundary test for every invariant in §3.3.
- Build/check each internal capsule through its public facade.
- Prove
sfn checkdepends on syntax/analyzer/IR but not codegen or LLVM. - Measure the analyzer-only cost of the whole-source
sfn/irdependency and adjudicate the 5% cold-check wall-time or peak-RSS budget in §3.3. If the budget is exceeded, explain the measured cause and either apply thesfn/module-interfacesplit when it addresses that cause or record the responsive remediation/design follow-up. SFN-747 supplied that adjudication; §3.3.1 records why the split was not responsive and why SFN-894’s filtered check closure is the accepted remedy. - Prove
sfn/codegen-llvmhas no parser, analyzer, driver, filesystem, process, or network imports. - Prove no
capsules/sfn/*source imports an internal compiler capsule. - Prove the
"runtime/prelude"slug resolves from every capsule that still holds it after §3.5 correction 7, at its post-move depth undercompiler/capsules/*/src/. A capsule that no longer needs the prelude should assert it imports nothing from it.
Behavioral preservation
- Run targeted parser, type/effect/ownership, native-IR, typed-SSA, tensor-IR, LLVM lowering, build, resolver, and CLI suites as their owning files move.
- Run
make compilefor every compiler-source slice. - Run
make clean-build && make compileafter each structural capsule move. - Run
make checkafter the final graph and identity changes. - Run determinism checks before and after capsule-name/symbol-mangling changes.
- Record cold/warm compile time, cache hits, and peak RSS before the first move and after the final move; regressions require explanation or remediation.
9. References
- SFEP-0006 — Unified Build Architecture
- SFEP-0014 — Agent-Legible Build/Test Output
- SFEP-0066 — Codegen Provider Ownership (the
sfn/codegen-nativeslot, §3.2) - SFEP-0027 — CLI Modularization
- SFEP-0041 — Unified Expected-Type and Typing-Environment Context
- SFEP-0046 — Native Toolchain Version Pinning and Dispatch
- SFEP-0051 — Workspace Manifest
- SFEP-0053 — Shape-Typed Tensor IR and Fusion
- SFEP-0059 — Typed SSA Activation
- Historical tracking epic #345