Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

RFD-0034 — Composition pipeline and the oxc / ox boundary

Committed Opened 2026-05-04 · Committed 2026-05-04

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 use path 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_tier cap; composition rejects rules whose classified tier exceeds the cap unless the rule sits inside an unsafe logic { } block (which lifts the cap to FOL by construction). Violations surface as OE0906 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_hash plus 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 compose is 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 compose reads N per-package .oxc caches, 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 existing oxc responsibility — name resolution, type checking, refinement validation, rule classification, structural checks).
  • Instantiation of CoreIR against a wiring diagram supplied by ox. This produces the per-package .oxc cache: 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 commandsoxc emit core-ir | tier-classification | symbols | resolve | classify | bench-elab. These expose the elaborated form to compiler-internal work and tooling without requiring ox-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 ox computed 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 Defn pattern.
  • 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:

  • .oxbin is the file extension.
  • It is produced by ox compose, which internally invokes oxc instantiate per package against the computed wiring diagram and merges the resulting .oxc caches. There is no oxc compose — composition is a workspace-level concern owned entirely by ox per §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. .oxbin is the Module. One .oxbin may 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

CodeSeverityTrigger
OE0905ErrorStandpoint lattice cycle detected at compose time.
OE0906ErrorRule’s classified tier exceeds the workspace’s max_tier cap.
OE0907ErrorCyclic pub use chain detected at compose time.
OE0908ErrorPer-package .oxc cache’s composition signature does not match ox’s recomputed signature (cache invalidation race or filesystem corruption).
OW0909WarningComposition 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 .oxbin from the package graph. Implicit in ox 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 by ox compose and by oxc-internal benchmarking.
  • oxc emit <shape>core-ir | symbols | tier-classification | wiring. Compiler-internal introspection. Mirrors rustc --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 .oxc cache reader/writer. New ox compose subcommand. Workspace + lockfile + composition-signature integration.
  • Per-package .oxc cache format is wire-stable and lifted into oxc-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 use chains 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 .oxbin artifact is the formalised input to that consumption. RFD-0035 specifies the trait surface that other backends (in-process oxc-runtime, sandboxed bytecode, future oxigraph-backed embedded runtime) implement.
  • Clean introspection surface for compiler-internal work. oxc emit family and oxc resolve enable compiler-internal debugging without going through the workspace orchestrator.
  • The argon-codegen drift gate covers the new wire shapes. oxc-protocol::core::oxc_cache and oxc-protocol::core::wiring are added; ts-rs + JSON schema generation flow through the existing pipeline.
  • Diagnostic codes OE0905OE0908, OW0909 register in oxc-protocol::core::codes. The OW prefix on OW0909 is correct: per RFD-0024’s severity convention, warnings carry OW, errors carry OE. 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’s oxc ≈ 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 compose resolves 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.