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 0022 — Package-path addressing (pkg) and the build evaluability gate

  • State: committed
  • Opened: 2026-06-07
  • Decides: two surface/contract decisions settled by building the real-package-layout module-resolution work — (1) how a path addresses modules, including the self-reference anchor and restricted visibility (pkg:: / pub(pkg), not crate:: / pub(crate)), and (2) that ox build refuses to emit an artifact containing a rule the runtime cannot evaluate, rather than silently dropping it. Built and merged across PR #128 (resolver anchoring, re-export propagation, pub(pkg), the build gate). Relates to Modules and the loudness stance of RFD 0019.

This RFD gives a home to two decisions the module-resolution PR made that change the surface (cratepkg) and the build contract (warn→error). Per AGENTS.md surface changes route through an RFD; these are recorded as built.


Question

The corpus examples were hand-flattened into a single namespace, so module resolution had never been exercised on a real nested package. Two questions surfaced when it was:

  1. How does a path name a module — and how does a package refer to itself? Rust uses crate:: for self-reference and pub(crate) for package-wide visibility. Argon is not Rust; it has packages, not crates. What is the canonical self-anchor and restricted-visibility spelling?
  2. What does ox build do with a rule the runtime cannot evaluate? The lowering admits rule shapes the executor’s compile_rule then refuses (a forall/exists quantifier → OE1315, not <aggregate> → OE1313, an unsupported aggregate kind → OE1312, …). Such a rule is silently dropped from evaluation.

Context

Resolution was filesystem-relative to the importing file, with no notion of the package root: pkg::a::b, super::, and any reference from a file deep in the tree all failed; only flat-relative paths from the package root happened to work (where relative coincides with absolute). The real overlay emitted ~190 OE0101 errors as a result.

Separately, ox build lowered every rule, loaded the artifact, and warned (non-fatally) on rules the runtime couldn’t evaluate — then wrote the artifact anyway. A consumer querying such a model gets wrong (under-derived) answers with no runtime error, the build warning easy to miss.


Decision

D1 — Path addressing: pkg is the sole self-anchor; no crate

A qualified path’s leading segment selects a root (Name resolution):

  • pkg — the current package’s root. The only way a package refers to itself; a package never names itself by its own package name. pkg::a::b::X names X in module a::b. Rename-safe: changing the package’s name in ox.toml doesn’t break internal paths.
  • self — the current module; super (repeatable) — an ancestor module.
  • A dependency package name ([dependencies]), and the always-available std root.
  • Otherwise the leading segment resolves against the scope chain (a submodule of the current module, or a use-imported name).

Restricted visibility is pub(pkg) (package-wide), not pub(crate). There is no crate keyword anywhere in the surface. pub(<anything-but-pkg>) is a parse error (OE0001) — it is not silently widened to pub.

pub use … ; / pub use … ::*; re-export (transitively); a plain use is a private import and does not re-export. Under the v0 world-assumption simplification pub(pkg) re-exports identically to pub (package boundaries aren’t yet modeled as a visibility cut).

D2 — ox build refuses to emit an un-evaluable artifact

If any rule in the lowered program is one the runtime’s compile_rule refuses, ox build fails and writes no artifact (it re-runs the runtime’s own rule compiler as the oracle, before write_oxbin). A built .oxbin therefore evaluates every rule it contains, or it does not exist.


Rationale

  • One obvious self-anchor. Supporting both pkg:: and the package’s own name would make every self-reference a silent style choice and blur the inside/outside boundary (a reader couldn’t assume a package-name path is a dependency). pkg:: is unambiguous and rename-safe; the package name stays the dependent-facing absolute path.
  • crate means nothing in Argon. The unit of distribution is a package ([package] in ox.toml); borrowing Rust’s crate vocabulary would be a false cognate.
  • Loud over silently-wrong (D2). A knowledge system that returns a plausible-but-wrong answer is worse than one that refuses — the same instinct as RFD 0019. A dropped rule is a silent under-derivation; refusing the artifact moves the failure to build time where it’s visible. The full examples corpus builds clean under this gate, so no real model relied on the warn-only behaviour.

Alternatives

  • Support both pkg:: and package-name self-reference (referential transparency: a symbol’s absolute path is identical inside and outside). Rejected: the cosmetic upside is outweighed by two-ways-to-say-it and rename-fragility; the external absolute path is still expressible.
  • Keep pub(crate) (Rust-familiar). Rejected: crate is not an Argon concept.
  • Warn, don’t fail, on un-evaluable rules (the prior behaviour). Rejected: it ships silently-incomplete artifacts.
  • A new diagnostic code for the visibility error. Deferred: reusing OE0001 with a clear message is sufficient; a dedicated code can come later if needed.

Consequences

  • Visibility::pubPackage (Lean Syntax/Decl.lean), Visibility::Package (Rust oxc-db), and the book §3.1/§3.4 are aligned on pub(pkg). There is no oxc-protocol Visibility mirror, so the rename is maintained by hand, not the drift gate.
  • self:: as a path-start segment does not yet parse (self is a lexer keyword for self.field); resolver support is in place. Accepting self/super/pkg as first-class path-root keywords is a follow-up.
  • Build-time refusal currently keys on the runtime rule compiler; an un-evaluable rule fails the whole build (no partial artifact). This is intentional for v0.

Open questions

  • Should pub(pkg) become a true visibility cut (distinct from pub) once package boundaries are modeled, rather than the v0 Package == Public simplification?
  • Should the unknown-pub(...) rejection get its own diagnostic code (vs the reused OE0001)?
  • Should the build gate ever support a partial/--allow-unevaluable mode for iterative authoring, or is whole-program evaluability the permanent contract?