Layering Rules¶
This repository follows a strict dependency direction to keep semantics shared and prevent accidental drift between the compiler and the runtime. The crates live in five rings under loaves/ (see Repository layout in Architecture); the direction runs kernel → oven → compiler → toolchain, with the standard library beside them:
- The kernel ring (
incan_lang,incan_syntax,incan_semantics_core,incan_vocab,incan_codegraph) depends on nothing outside itself;incan_langandincan_vocabdepend on nothing at all. - The oven ring (
oven_model,oven_store,oven_rustc,oven_interop,oven_cargo_compat,oven_registry) depends on nothing outside itself; within it,oven_interopandoven_cargo_compatsit overoven_rustc, which runs no Cargo. It knows Incan by name only — the compiler's provider interface is implemented inincan_oven_facet, on the compiler side of the seam. - The compiler ring (
incan_frontend,incan_ir,incan_emit,incan_format,incan_provider,rust_inspect,incan_driver,incan_oven_facet,incan_semantics_stdlib) depends on the kernel and, for the build orchestration, on the oven ring. Within the ring,incan_irdepends onincan_frontend,incan_emiton both, andincan_driveron all three plusincan_providerandincan_oven_facet. - The toolchain ring (
incan-cli,incan-lsp,oven-cli) is the only place that depends on the driver; nothing outside the ring depends on it (incan-clidepends onoven-clifor the commands it mounts). - No compiler-ring or kernel-ring crate depends on a standard library facet (
incan_std_coreand the otherincan_std_<component>crates) except as a dev-dependency for parity tests. - Among the facets,
incan_std_coredepends onincan_langandincan_derive;incan_std_dataandincan_std_testingdepend onincan_std_core;incan_std_asyncandincan_std_webstand alone. - Generated user programs depend on
incan_std_coreand on whichever other facets their namespaces reach.
flowchart TD
cli["incan-cli · incan-lsp · oven-cli (toolchain)"] --> driver["incan_driver"]
driver --> emit["incan_emit"]
driver --> provider["incan_provider"]
driver --> facet["incan_oven_facet"]
emit --> ir["incan_ir"]
ir --> frontend["incan_frontend"]
frontend --> format["incan_format"]
frontend -. optional .-> semanticsStdlib["incan_semantics_stdlib"]
frontend --> rustInspect["rust_inspect"]
provider --> frontend
facet --> provider
frontend --> kernel["kernel: incan_lang · incan_syntax · incan_semantics_core · incan_vocab · incan_codegraph"]
driver --> oven["oven: oven_model · oven_store · oven_rustc · oven_interop · oven_cargo_compat · oven_registry"]
provider --> oven
facet --> oven
frontend --> oven
stdCore["incan_std_core"] --> kernel
stdCore --> incanDerive["incan_derive"]
stdFacets["incan_std_data · incan_std_testing"] --> stdCore
generatedProgram["generated program"] --> stdCore
generatedProgram --> stdFacets
generatedProgram --> stdStandalone["incan_std_async · incan_std_web"]
generatedProgram --> incanDerive
generatedProgram --> incanWebMacros["incan_web_macros"]
CI/Test guardrails enforce that the compiler and kernel rings keep the facets out of their normal dependencies. If you need runtime helpers inside tests, add them under [dev-dependencies] only.
Workspace crate categories¶
Use this policy when deciding where new code belongs:
- Stable contracts: the kernel ring —
incan_lang,incan_syntax,incan_semantics_core,incan_vocabandincan_codegraph. Other layers build on these crates. Keep them deterministic, dependency-light, and free of runtime side effects. - Build tool: the oven ring —
oven_model,oven_store,oven_rustc,oven_interop,oven_cargo_compat,oven_registry. Manifests, the store, direct-rustc units, native interop, the explicit Cargo-compatibility baker; nothing Incan-specific beyond the runtime-crate naming rule. - Compiler/toolchain implementation: the compiler ring —
incan_frontend,incan_ir,incan_emit,incan_format,incan_provider,rust_inspect,incan_driver,incan_oven_facet,incan_semantics_stdlib— and the toolchain ring's three packages. These crates are tied to the current compiler/tooling. They may depend on stable contracts but should not become runtime APIs. - Runtime-only implementation: the standard library facets (
incan_std_core,incan_std_data,incan_std_async,incan_std_web,incan_std_testing),incan_derive, andincan_web_macros. Generated Rust programs use these crates. The compiler may generate references to them but must not depend on them in normal builds. - Transitional runtime surfaces: the current
incan_std_webfacet and related macro glue. This runtime code is not yet a stable long-term contract. Keep it quarantined and avoid treating it as compiler-owned policy.
Why we do this¶
We want one “source of truth” for language behavior so the compiler and runtime don’t drift:
- Semantics must match: if const-eval validates something, runtime should do the same thing the same way (especially for Unicode-sensitive string operations and numeric edge cases).
- Diagnostics/panics must stay aligned: user-facing error messages should not diverge between compile-time and runtime.
- Compiler stays lean: the compiler shouldn’t accidentally pull in runtime-only APIs or heavy dependencies.
What goes where (contracts vs implementations)¶
incan_lang:
- Pure helpers that define meaning/policy (e.g., string indexing/slicing rules, numeric promotion, canonical error message constants).
- Central registries for language vocabulary and stdlib wiring (for example
incan_lang::lang::stdlib::STDLIB_NAMESPACESand keyword metadata used by the lexer/parser). - Must be deterministic and side-effect free.
- Should not depend on compiler internals (AST, spans, lexer/parser state).
- Should not gain new stdlib-owned runtime surface types unless the type metadata is truly shared language policy.
incan_syntax:
- Lexer, parser, AST, and syntax diagnostics shared by compiler, formatter, LSP, and future tooling.
- May use language vocabulary from stable contract crates.
- Must not perform name resolution, typechecking, lowering, Rust interop loading, or runtime behavior.
incan_semantics_core:
- Stable action-descriptor and semantics-pack contracts that compiler stages can consume.
- Owns behavior descriptors, not compiler execution. Packs describe what to do; compiler stages decide how to do it.
incan_semantics_stdlib:
- Stdlib semantics-pack implementation for current built-in library surfaces.
- Toolchain-locked implementation crate, not a stable external API.
- Should return descriptors and canonical targets instead of reaching into compiler internals.
rust_inspect:
- Dedicated Rust metadata preparation, extraction, and caching subsystem.
- Allowed behind compiler/tooling features for Rust interop.
- Should remain explicit and staged: prepare/prewarm metadata at CLI/LSP/project boundaries, then read cached metadata in semantic paths.
The standard library facets (incan_std_core, incan_std_data, incan_std_async, incan_std_web, incan_std_testing):
- Runtime helpers used by generated Rust code, one crate per stdlib component that has Rust beside its Incan sources (
loaves/stdlib/<component>/rust/);incan_std_coreis mandatory and serves the language itself, the others serve their component's namespaces. - Should delegate behavior to
incan_langfor policy/consistency, and implement runtime-only actions (like panicking) using the shared error messages/taxonomy. - May contain transitional implementation modules, but those modules must not become compiler dependencies.
incan_derive / incan_web_macros:
- Runtime-side macro support for generated Rust programs.
- Must not become a backchannel for compiler logic.
- Web macro/runtime glue is transitional until the web surface has a stable long-term ownership model.
The compiler ring (incan_frontend, incan_ir, incan_emit, incan_driver and their neighbours):
- Typing (
incan_frontend), lowering (incan_ir), codegen (incan_emit), formatting (incan_format), provider and SDK contracts (incan_provider), and the build session that drives them (incan_driver); diagnostics are catalogued inincan_syntax. - May use stable contract crates to implement checks/const-eval and to keep error text aligned, and the oven ring to plan and run builds.
- Must not use runtime-only crates in normal builds; only
incan_std_coreas a dev-dependency for parity tests.
Allowed / forbidden dependencies¶
Allowed:
- a compiler-ring crate →
incan_lang,incan_syntax,incan_semantics_core,incan_semantics_stdlib,incan_vocab,incan_codegraphas normal dependencies. - a compiler-ring crate →
oven_model,oven_store,oven_rustc,oven_interop,oven_cargo_compatwhere it plans or runs builds (incan_driver,incan_provider,incan_oven_facet;incan_frontendandincan_emitfor the manifest and receipt types). - a compiler-ring crate →
rust_inspectbehind the Rust interop path. - a facet →
incan_lang(andincan_std_dataorincan_std_testing→incan_std_core) as normal dependencies. - a compiler-ring crate →
incan_std_coreas a dev-dependency only, for tests.
Forbidden:
- a compiler-ring or kernel-ring crate → any facet in
[dependencies](this breaks layering). - a compiler-ring crate →
incan_deriveorincan_web_macrosin normal dependencies. - a kernel-ring crate → any compiler-ring, oven-ring, toolchain-ring or runtime crate.
- an oven-ring crate → any Incan crate; what Oven needs to know about Incan enters through
incan_oven_facet. - Runtime crates calling back into compiler crates.
Common pitfalls¶
-
Adding a “quick helper” in a facet and calling it from the compiler.
- Fix: move the policy/logic to
incan_langand keep only runtime glue (panics, wrappers) in the facet.
- Fix: move the policy/logic to
-
Adding another stdlib-specific surface type to
incan_langbecause similar metadata already exists there.- Fix: decide whether the type is true language policy or library-owned surface. Prefer library-defined ownership when possible, and document the exception when it must stay core-owned.
-
Emitting direct Rust operations that bypass shared semantics (e.g., slicing Rust
Stringby byte indices).- Fix: emit calls to
incan_std_corewrappers which themselves delegate toincan_lang.
- Fix: emit calls to
-
Duplicating error messages as string literals in multiple places.
- Fix: put canonical text in
incan_langand reuse it from both compiler and runtime.
- Fix: put canonical text in
-
Loading Rust metadata opportunistically from typechecking or lowering.
- Fix: prewarm through the explicit
rust_inspectpreparation path and keep semantic lookups cache-oriented.
- Fix: prewarm through the explicit
Guardrails (how it is enforced)¶
- Dependency gate:
loaves/toolchain/incan-cli/tests/layering_guard.rsfails if a facet appears in the[dependencies]section of the root, compiler-ring or kernel-ring manifests (keeping one in[dev-dependencies]for parity tests is allowed), if the registry's facet facts disagree withsdk-components.tomland the crates on disk, or if the compiler ring spells a runtime crate the catalog does not know.
How to add shared behavior safely¶
When you notice drift risk (compiler vs runtime):
- Put the policy in
incan_lang(pure function + typed error or canonical message). - Add a thin wrapper in the owning facet (
incan_std_corefor language runtime) that calls semantics and performs runtime-only behavior (panic, allocation, conversions). - Update compiler const-eval / typechecking to use the semantics helper directly (never stdlib).
- Add a parity test in the owning ring's integration roots (the frontend's
semantic_core_parityroots underloaves/compiler/incan_frontend/tests/, orincan_emit's codegen roots) that compares compiler/semantics/runtime behavior for the edge case.