RFD-0034 — Composition pipeline and the oxc / ox boundary
Question
What does it mean to take N elaborated Argon packages and produce a single closed input that a runtime can consume? Where does the work split between the per-package compiler (oxc) and the workspace orchestrator (ox)? What is the wire shape of the per-package output, and what is the wire shape of the workspace-level composed artifact?
Context
Today’s Argon pipeline stops halfway. Source elaborates through oxc into CoreIR serialised as JSON, and the toolchain ends there. There is no discrete pass that closes cross-package references, merges standpoint lattices, validates tier consistency across the workspace, or produces an artifact a runtime can load. Three implicit composition paths exist instead — the kernel loading packages into a tenant overlay, the test runner materialising using <frame> bodies, the planned ox doc cross-rendering across packages — each implementing its own version with no shared model.
The cost of this gap is structural: Argon’s runtime concerns leak into every consumer, the kernel becomes the de facto runtime by absorption, and there is no portable artifact to ship a built workspace anywhere else. Decoupling Argon from any single runtime requires a discrete composition phase with a typed wire shape between elaboration and execution.
The PL-modules literature has already converged on the answer. Backpack ’17 (Yang, Eisenberg, Yorgey, Peyton Jones et al., POPL 2017), the WebAssembly Component Model (WebAssembly Community Group), and the Engine / Module / Store factoring shared across Wasmtime, JVM HotSpot, and Truffle / GraalVM all reach the same two-phase pipeline: a workspace-level pass computes wiring (cross-component reference resolution, signature unification) and produces a composition signature; a per-component compiler instantiates against that wiring and produces a typed artifact. Three independent designs from three different lineages converging on the same shape is not accidental.
Argon’s existing oxc / ox split already implements half of this without saying so. The other half — the explicit composition pass, the typed per-package output, the wire shape — is what this RFD locks.
A research campaign preceding this RFD ([vault: research/argon-execute-layer/]) surveyed the space across six tracks (PL modules, KG persistence, standpoints, saturation/provenance, runtime/execution, artifact formats) and produced a primary-source-grounded set of recommendations. The campaign’s most striking finding is that the structural shape Argon needs is settled by literature; the genuine invention surface lives almost entirely on the artifact side (RFD-0035) rather than the composition side. RFD-0034 ratifies the convergent shape; RFD-0035 specifies the artifact.
Decision
Argon adopts a two-phase composition pipeline with a sharp oxc / ox boundary, an explicit compose pass, and typed wire shapes at every stage.
1. Pipeline shape
per-package source
│
│ elaborate ← oxc owns
▼
per-package CoreIR
│
│ instantiate ← oxc owns; consumes wiring
▼
per-package .oxc cache
│ ─┐
│ │ ox owns
(workspace metadata + lockfile) │ (compute wiring +
│ │ composition signature
│ compose │ from package graph)
▼ ─┘
workspace .oxbin
│
│ consume ← runtime backend (kernel,
▼ in-process, sandboxed, …)
answers / docs / saturated state / …
Three stages, three artifacts. Each artifact is typed; each transition is a well-specified pass.
2. ox is the workspace orchestrator
ox owns everything that requires a workspace-level view of the package graph:
-
Wiring computation. Resolve every
usepath across the workspace + lockfile-pinned dependency cache. Produce a wiring diagram — a typed mapping from every public-eligible symbol reference to the qualified path of its target. Identical to Backpack ’17 Cabal-side mixin linking; identical to WASM Component Model component-type signature resolution. -
Standpoint lattice composition. Workspaces composed from N packages may declare standpoints in any of them. Standpoints from different packages identify by stable UUID, never by name. Lattice composition is union with cycle rejection. Lattice cycles surface as
OE0905 StandpointLatticeCycle. Soundness invariant: the composed lattice is a DAG. -
Tier consistency check. Every rule’s tier classification is visible at composition time. The workspace declares a
max_tiercap; composition rejects rules whose classified tier exceeds the cap unless the rule sits inside anunsafe logic { }block (which lifts the cap to FOL by construction). Violations surface asOE0906 TierCapExceeded. -
Composition signature. A SHA-256 content-hash over four pre-hashed legs:
composition_signature = SHA-256( wiring_diagram_hash ∥ // SHA-256 over the canonical-form wiring diagram standpoint_lattice_hash ∥ // SHA-256 over the canonical-form standpoint lattice tier_ladder_version ∥ // u32, little-endian kernel_api_version // u32, little-endian )Two identical inputs produce the same signature; any change to any axis produces a different one. The signature is the cache key for downstream re-composition. Crucially, the wiring diagram source itself is not preserved in the artifact — it is a transient build-time input. The artifact stores
wiring_diagram_hashplus the other three legs, which is sufficient for the runtime to reverify signature integrity at load time without reconstructing the wiring (RFD-0035 §6 Layer-2 sub-pass).ox composeis the build-time check that the wiring is correct; the runtime is the load-time check that the artifact’s stored sub-hashes have not been tampered with. -
Workspace artifact emission.
ox composereads N per-package.oxccaches, validates they agree on the wiring diagram, applies the standpoint-lattice + tier-cap + reference-closure invariants, and emits a workspace.oxbin(RFD-0035).
3. oxc is the per-package compiler
oxc owns everything that is per-package and stops at the package boundary:
- Elaboration of the package’s source files into
CoreIR(every existingoxcresponsibility — name resolution, type checking, refinement validation, rule classification, structural checks). - Instantiation of
CoreIRagainst a wiring diagram supplied byox. This produces the per-package.oxccache: an instantiated, typed, lazily-deserialisable serialisation of the package’s elaborated content as it stands relative to this composition. The cache is not abstract; it is composition-specific. Yang et al. 2017 deliberately abandoned separately-compiled-uninstantiated-components for performance (“no cross-package inlining can occur”); Argon adopts the same lesson. - Per-package introspection commands —
oxc emit core-ir | tier-classification | symbols | resolve | classify | bench-elab. These expose the elaborated form to compiler-internal work and tooling without requiringox-level workspace context.
oxc does not see the workspace. oxc does not resolve cross-package references on its own. oxc does not merge standpoint lattices. Every cross-package concern that would otherwise live in oxc lives in ox instead.
4. Per-package .oxc cache shape
The .oxc is a per-package, instantiated, typed serialisation. The format is the per-package analogue of the workspace .oxbin defined in RFD-0035: same encoding family (CBOR + Cap’n Proto layered), same content-addressing discipline, smaller scope.
A .oxc carries:
- Composition signature. The signature
oxcomputed at instantiation. Re-instantiating the package against a different signature invalidates the cache. - Symbol table. Every public-eligible item (concept, rule, metatype, metarel, decorator, frame, query, mutation, compute, axiom) with stable_id + qualified_path + tier + visibility + doc-presence. Front-coded.
- CoreIR serialisation. The elaborated form, sectioned by item kind. Lazy-deserialise on first lookup, mirroring Idris 2’s
ContextEntry = Either Binary Defnpattern. - Per-package events. Axioms declared in the package, in the unified event-log shape defined by RFD-0035. Empty for packages that declare only TBox.
- Per-package standpoint declarations. Local additions to the workspace’s standpoint lattice; merged at compose time.
- Doc strings. Per RFD-0029, every public-eligible item carries a
DocBlock. The cache serialises the parsed shape, not the raw///strings.
Re-instantiating against a new wiring diagram (composition signature changed) re-runs oxc instantiate. Cabal-style content-hash caching mitigates the re-work cost: if the signature didn’t change and the source’s content-hash didn’t change, the existing cache is reused.
5. Workspace .oxbin artifact
The workspace-level composed artifact is specified in RFD-0035. RFD-0034 commits to:
.oxbinis the file extension.- It is produced by
ox compose, which internally invokesoxc instantiateper package against the computed wiring diagram and merges the resulting.oxccaches. There is nooxc compose— composition is a workspace-level concern owned entirely byoxper §3. - It carries the merged events from every package + the composed standpoint lattice + the resolved tier table + the composition signature + every section RFD-0035 specifies.
- It is content-addressed; semantically equivalent compositions produce byte-equivalent artifacts.
6. Engine / Module / Store runtime factoring
The runtime contract that consumes a .oxbin factors into three roles, mirroring the convergence across Wasmtime / JVM / Truffle:
- Engine — the shared, immutable base. Argon’s analogue is the shared schema (UFO + Core + std + any other base packages declared by the workspace), held as
Arc<SchemaStore>. Hot-path: mmap’d; shareable across processes. - Module — a typed loaded artifact.
.oxbinis the Module. One.oxbinmay be loaded into many Stores concurrently. - Store — an isolated execution context. Argon’s analogue is the per-tenant overlay, holding events that aren’t part of the Engine’s shared base. The kernel today implements this as a per-tenant overlay over the shared base.
The factoring is ratified, not invented — it is already implicit in Argon’s existing shared-base + per-tenant-overlay design. RFD-0034 names it explicitly so future runtime-backend work has a vocabulary to align against.
7. Module identity is a wiring-diagram content-hash
Backpack ’17 establishes that module identity in a recursive composition is a recursive term (a regular infinite tree under , Kilpatrick et al. 2014). Cyclic compositions need Huet’s regular-tree unification to compute identity; non-cyclic compositions reduce to flat content-hashes.
Argon adopts the flat content-hash form for the v1 of this RFD. Module identity is the SHA-256 over the wiring-diagram subtree rooted at the module’s top-level. The recursive form (and its unification machinery) is reserved for a future RFD if cyclic pub use chains are observed in practice.
8. Cyclic re-exports are rejected
A pub use chain that produces a cycle in the import graph surfaces as OE0907 CyclicReexportInComposition at compose time. The cycle is reported with the full chain. The current cut admits no cycles by construction.
The Huet regular-tree unification escape hatch is out of scope for this RFD. If a real use-case for cyclic re-exports surfaces — and Argon’s stable_id-as-content-hash discipline starts producing aliasing errors — a follow-up RFD admits cycles with the formal identity calculus. Reject-by-default is the conservative choice; admitting cycles later is a strict superset.
9. Diagnostic codes introduced
| Code | Severity | Trigger |
|---|---|---|
OE0905 | Error | Standpoint lattice cycle detected at compose time. |
OE0906 | Error | Rule’s classified tier exceeds the workspace’s max_tier cap. |
OE0907 | Error | Cyclic pub use chain detected at compose time. |
OE0908 | Error | Per-package .oxc cache’s composition signature does not match ox’s recomputed signature (cache invalidation race or filesystem corruption). |
OW0909 | Warning | Composition signature is the same as a prior cached composition; ox reused the cached .oxbin. |
Codes register in oxc-protocol::core::codes via the existing inventory::submit! registry. The drift gate ensures uniqueness.
10. CLI surface
ox compose— produce a workspace.oxbinfrom the package graph. Implicit inox check,ox test,ox doc,ox run; explicit when ahead-of-time composition is desired (deployment, distribution, CI caching).oxc instantiate <package> --wiring <wiring.json>— instantiate a single package against a supplied wiring diagram. Compiler-internal; primarily used byox composeand byoxc-internal benchmarking.oxc emit <shape>—core-ir | symbols | tier-classification | wiring. Compiler-internal introspection. Mirrorsrustc --emit.
ox runs oxc instantiate per package as part of ox compose. End users do not invoke oxc instantiate directly.
Rationale
Three independent designs converged on the two-phase pipeline. Backpack ’17 (PL modules), the WebAssembly Component Model (component composition), and Argon’s existing oxc / ox split (workspace orchestration). Convergence from three lineages on one architectural shape is the strongest available signal that the shape is correct. RFD-0034 ratifies the convergence rather than inventing.
Per-package output is instantiated, not abstract. Yang et al. 2017 explicitly abandoned separately-compiled-uninstantiated-components for performance reasons — uninstantiated components prevent cross-package optimisation and forced the Backpack team away from that model. Argon should not pay the cost. A per-package .oxc cache that is instantiated against the current composition is the right shape; Cabal-style content-hash caching covers the re-work cost when compositions don’t change.
Engine / Module / Store factoring is convergent across runtime traditions. Wasmtime, JVM HotSpot, and Truffle / GraalVM independently reach this shape. Argon’s existing shared-base + per-tenant-overlay implements it; naming it explicitly gives future runtime-backend work a stable vocabulary.
Reject cycles by default. Backpack handles cyclic compositions with Huet regular-tree unification, but the machinery is heavy and the use-case is narrow. Admitting cycles later when there is empirical evidence is a strict superset of rejecting them now. Reject-by-default is conservative; the upgrade path is open.
Composition signature ≠ lockfile hash. The lockfile pins source content. The composition signature pins the wiring diagram + standpoint lattice + tier ladder + kernel API version. Two compositions with identical sources but different standpoint lattices produce different signatures and require different .oxc caches. The two hashes serve different purposes and both belong in the cache key.
Standpoint lattice composition is novel. Every surveyed standpoint-logic implementation (Strass et al. 2023’s Standpoint EL+ Soufflé prototype; Emmrich et al. 2023’s Standpoint-OWL 2 reasoner; DDL/MCS implementations; named graphs) assumes a single monolithic lattice. Cross-package lattice composition is unaddressed in the literature. Argon’s rules (UUID-based equality, union with cycle rejection, soundness check at compose time) are small and grounded; the documentation lives here so future contributors do not re-litigate.
oxc keeps a rich CLI for compiler-internal work. The ox surface is the user-facing surface; oxc is the toolsmith’s surface. The emit family (modelled after rustc --emit) plus introspection commands give compiler-internal work a stable footing without loading user-facing semantics into oxc.
Consequences
- New crate / module: composition pipeline implementation.
oxc instantiate,oxc emit, the.oxccache reader/writer. Newox composesubcommand. Workspace + lockfile + composition-signature integration. - Per-package
.oxccache format is wire-stable and lifted intooxc-protocol. The format is the per-package analogue of the workspace.oxbin(RFD-0035). - Composition signature joins the lockfile content-hash and constructs-hash as a third hash on workspace state. Cache invalidation walks all three.
- Module identity = wiring-diagram content-hash. A package’s stable_id is no longer just a function of its source; it is a function of its source and the wiring it is instantiated against. Cross-composition references to the same package are stable; cross-composition references between packages can shift if the wiring changes.
- Cyclic
pub usechains rejected. Existing packages that depend on cyclic re-exports (none known at the time of this RFD) need to refactor. - The kernel becomes one runtime backend among several. The kernel’s storage layer continues to consume composed events as it does today; the
.oxbinartifact is the formalised input to that consumption. RFD-0035 specifies the trait surface that other backends (in-processoxc-runtime, sandboxed bytecode, future oxigraph-backed embedded runtime) implement. - Clean introspection surface for compiler-internal work.
oxc emitfamily andoxc resolveenable compiler-internal debugging without going through the workspace orchestrator. - The
argon-codegendrift gate covers the new wire shapes.oxc-protocol::core::oxc_cacheandoxc-protocol::core::wiringare added; ts-rs + JSON schema generation flow through the existing pipeline. - Diagnostic codes
OE0905–OE0908,OW0909register inoxc-protocol::core::codes. TheOWprefix onOW0909is correct: per RFD-0024’s severity convention, warnings carryOW, errors carryOE. Code-uniqueness checks span both prefixes.
Historical lineage
This RFD is largely novel. It builds on three convergent prior designs:
- Backpack ’17 (Yang, “Backpack: Towards Practical Mix-In Linking for Haskell,” 2017). The two-phase pipeline (Cabal-side mixin linking → GHC-side instantiation+typecheck) is the closest existing prior art. Argon’s
ox≈ Cabal; Argon’soxc≈ GHC; Argon’s.oxc≈ instantiated component. Argon adopts the two-phase shape with the per-package output instantiated (Yang’s pragmatism — uninstantiated components prevent cross-package optimisation, and Backpack ’17 explicitly abandoned that route). - WebAssembly Component Model (WebAssembly Community Group, ongoing). Component-type signature resolution → core-module wiring is the artifact-side analogue of Backpack’s package-side resolution. Argon’s
ox composeresolves wiring at the workspace level; the workspace.oxbin≈ a WASM component. - Engine / Module / Store factoring (Wasmtime, JVM HotSpot, Truffle / GraalVM). Three independent runtime traditions converge on this shape. Argon’s
Arc<SchemaStore>≈ Engine;.oxbin≈ Module; per-tenant overlay ≈ Store. Already implicit in Argon’s design; named explicitly here.
The standpoint lattice composition rules are Argon-specific; the surveyed literature does not address cross-package lattice composition.
The reject-cycles-by-default policy is a conservative interpretation of Backpack’s regular-tree unification. The literature supports cycles; Argon defers them. A future RFD revisits if real use-cases emerge.