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

Workspaces and Dependencies

A workspace groups several related packages under one root with a shared lockfile and a shared dependency-version pool. Use one when you have packages that need to evolve together — a domain ontology and the test suite that exercises it, for example, or a foundational vocabulary and the domain packages that consume it.

This chapter covers workspaces and the lockfile + registry mechanics that hang off them.

A workspace manifest

The workspace root has its own ox.toml, with a [workspace] section in place of (or alongside) [package]:

[workspace]
members = ["packages/lease-tutorial", "packages/lease-tests"]
resolver = "1"

[workspace.package]
edition = "2025"
license = "CC-BY-4.0"
authors = ["Lease Team <leases@example.com>"]

[workspace.dependencies]
ufo = "0.2.1"
cofris = "0.2.1"

Three sections:

  • [workspace] — declares the workspace and lists members. members is a list of relative paths to package directories (glob patterns allowed); each must contain its own ox.toml.
  • [workspace.package] — defaults that members can inherit. Common fields: edition, license, authors, repository, description.
  • [workspace.dependencies] — version pinnings that members can inherit via <dep>.workspace = true.

Each member’s ox.toml carries both the [project] perspectival-composition layer and the [package] registry layer (see Chapter 1.3 for why both):

[project]
name = "lease-tutorial"
version = "0.1.0"
edition = "2025"
entry = "root.ar"

[schema]
root = "src"

[package]
name = "lease-tutorial"
version = "0.1.0"
edition.workspace = true
license.workspace = true

[dependencies]
ufo.workspace = true
cofris.workspace = true

workspace = true says “use the workspace root’s value.” This keeps version pins consistent across members without the rote of repeating them per file.

How resolution works

When you run ox install at the workspace root:

  1. The compiler walks each member’s [dependencies], expanding workspace = true to the workspace root’s pin.
  2. It builds the unified dependency graph across all members.
  3. External deps that appear in multiple members resolve to the same version — Cargo’s pattern. If two members declare ufo with incompatible version constraints, the resolution fails up front.
  4. Workspace members always resolve to the local sibling, never to a registry version. So if lease-tests depends on lease-tutorial and lease-tutorial is a workspace member, lease-tests sees the local source, not a published version.
  5. The lockfile lands at the workspace root (ox.lock) and is shared across members.

The lockfile

ox.lock records the exact resolution. It is bivalent: each entry carries two identifiers:

  • A content hash — the byte hash of the package’s tarball as published. Reproducible-byte verification at install.
  • A Merkle root over construct signatures — a hash over the package’s exported items’ shapes (concept names, field types, rule signatures). Semantic identity: two packages with the same Merkle root expose the same surface, even if their byte hashes differ.

The bivalence matters because Argon packages can be republished with cosmetic changes (a comment fix, a doc-string rewrite) that change the content hash but not the Merkle root. Tooling that wants “did the API change” looks at the Merkle root; tooling that wants “did the artifact change” looks at the content hash.

The lockfile is checked into version control — it is the source of truth for “what versions did this build use.” Re-running ox install against an existing lockfile is deterministic.

Updating dependencies

Two commands:

$ ox update                       # update all packages within their semver constraints
$ ox update <package>             # update a specific package

Both write a new ox.lock. Both report a compatibility diff: which package versions changed, and (via the Merkle root) which API surfaces changed. A surface-changing update is a yellow flag worth reviewing before committing.

The registry

ox publish ships a package to the registry:

$ ox publish
   Building canonical tarball
   Computing content hash + Merkle root
   Authenticating against registry
   Uploading tarball
✓ Published lease-tutorial v0.1.0

The registry’s URL and authentication are configured in ~/.argon/settings.toml; for the Sharpe-internal argon-packages registry, authentication uses GitHub tokens.

ox audit re-verifies the lockfile’s hashes against the cached packages:

$ ox audit
   ✓ All package content hashes match the lockfile.
   ✓ All Merkle roots match the lockfile.

The verification surfaces tampering or registry corruption. Run it in CI if you need a strong guarantee that the build’s inputs have not been substituted.

Local-path dependencies

For dependencies under active development, point at a local directory:

[dependencies]
local-pkg = { path = "../local-pkg" }

The compiler treats local-path deps as workspace members for resolution — they resolve to the source on disk, not a registry version. Use this during development; switch to a version pin before release.

Edge cases worth knowing

  • Single-package workspace is fine. A package with [package] only is a workspace of one. You do not need a separate [workspace] table to use a workspace pattern; many small packages live this way.
  • Members cannot have [workspace]. A member’s ox.toml carries [project] + [package] (and optionally [dependencies]); the workspace declaration belongs at the root.
  • The lockfile lives at the workspace root, not per member. Members do not have their own ox.lock.
  • target/ is per-workspace. Build artifacts and test caches share a directory at the workspace root, so members do not duplicate work.
  • Git deps are policy-controlled. The Sharpe-internal default disallows git-source dependencies (only registry + path are admitted). The mechanism exists in the manifest grammar for future use.

Putting it in the running example

We have built lease-tutorial as a single-package workspace so far. To grow into a multi-member layout, restructure:

lease-workspace/
├── ox.toml
├── ox.lock
└── packages/
    ├── lease-tutorial/
    │   ├── ox.toml
    │   └── src/
    │       ├── root.ar
    │       ├── prelude.ar
    │       ├── metatypes.ar
    │       └── ...
    └── lease-extras/
        ├── ox.toml
        └── src/
            └── ...

Workspace ox.toml:

[workspace]
members = ["packages/lease-tutorial", "packages/lease-extras"]

[workspace.package]
edition = "2025"
license = "CC-BY-4.0"

[workspace.dependencies]
# none yet

Each member’s ox.toml:

[project]
name = "lease-tutorial"
version = "0.1.0"
edition = "2025"
entry = "root.ar"

[schema]
root = "src"

[package]
name = "lease-tutorial"
version = "0.1.0"
edition.workspace = true
license.workspace = true

[dependencies]

Type-check from the workspace root:

$ ox check
ox lease-tutorial v0.1.0 (./packages/lease-tutorial/ox.toml)
ox lease-extras v0.1.0 (./packages/lease-extras/ox.toml)
check passed

The workspace pattern scales — a real ontology project may have ten or twenty members, with foundational packages (UFO, BFO), domain packages (lease, payments, regulatory), and a test workspace member coordinating them.

Manifest patterns in the catalog

The _catalog/manifest-*/ workspace family exercises each manifest section in isolation:

  • _catalog/manifest-catalog/{minimal,multi-version,registry-override,negative-missing-dep,idiom}/[catalog] declarations across single- and multi-version slot configurations, plus a registry-override variant that points one dep at a private registry.
  • _catalog/manifest-tree-shaking/{minimal,with-conditional-exports,with-feature-flags,cross-package,negative-orphan-import}/ — the [tree-shaking] directives that prune unused items at canonicalization time.
  • _catalog/manifest-defeat/{minimal,prioritized-ordering,plugin-strategy,cross-package,negative-missing-strategy}/[defeat] section configurations driving the defeasible-rule ordering (foundation: Chapter 5.2).
  • _catalog/manifest-modules/{minimal,nested-lattice,cross-package,mixed-world,negative-cycle}/ — the [modules] + [standpoints] machinery covered in Chapter 5.4.

Each variant ships a runnable ox check (and, for negative variants, the expected diagnostic code). Read them as a reference when wiring up a new workspace.

Summary

A workspace groups packages under one root with a shared lockfile and shared version pool. Members inherit defaults from [workspace.package]; they pin versions through [workspace.dependencies]. The lockfile is bivalent — content hash plus Merkle root — so tooling can distinguish byte and semantic identity. The registry handles publish/fetch/audit. Local-path deps support development. Single-package workspaces are fine; the pattern scales when you need it.