RFD-0032 — Shared-base package classification for workspace-vendored deps
Question
When a customer overlay vendors a foundational package (UFO, std, …) as a workspace member instead of pulling it from the registry, how does the bundle assembler classify that member so the kernel-side compile path treats it as SharedBaseProvided rather than as a tenant-emitted overlay?
Context
The oxc_runtime::PackageRole enum has four variants — Root, WorkspaceMember, Dependency, SharedBaseProvided. The kernel-side compile pipeline (kernel/api/src/v2/schema/compile.rs) filters emit output for SharedBaseProvided packages so their declarations don’t land as tenant axioms — the kernel’s base tenant (SHARED_BASE_TENANT_ID) carries equivalent declarations already, and per-tenant duplication would shadow-collide on reload (OE7062).
The classification happens in argon/ox/src/bundle.rs::build_virtual_source_bundle. Before this RFD it consulted BundleBuildOptions::shared_base_policy only for locked external deps — packages installed into ~/.argon/packages/ and pinned in ox.lock. Workspace members got Root (for the package being compiled) or WorkspaceMember (for siblings) regardless of policy.
That left a gap for codebases that vendor foundational packages as workspace members:
- The Sharpe internal overlay (
sharpe-ontology) keeps UFO source as a sibling workspace member of the customer’s domain package. Hand-authoring UFO concepts inside the same workspace is convenient; pulling UFO from the registry would force a publish-rebuild loop on every UFO edit. - The Argon
examples/lease-storydemo vendors UFO + economic-foundation packages as workspace members for similar reasons.
Without the workspace-member policy consult, both setups end up uploading UFO twice: once via the bundle (as WorkspaceMember, emit-output stored as overlay axioms), and once into the kernel’s base tenant via the out-of-band base-loader path. The second store wins on read but pollutes the overlay’s storage and triggers shadow-collision warnings.
Workarounds before this RFD:
- Drop the foundational package from the workspace and pin it as a registry dep instead. Defeats the “edit UFO inline” workflow.
- Manually massage the bundle’s
PackageRoleafter the fact. Bypasses the public API; brittle. - Live with the duplicate-emit cost. What the customer overlay was effectively doing.
Decision
build_virtual_source_bundle consults BundleBuildOptions::shared_base_policy for non-Root workspace members. Role precedence becomes:
Root— the package the bundle is centered on (selection target). Always Root regardless of policy. A modeler editing UFO directly inside a workspace where they own UFO’s source is the package’s author — they want emit output back from the kernel.SharedBaseProvided— non-Root members whose canonical name matchespolicy.is_shared_base_provided(name). The kernel filters their emit; the kernel base provides equivalent declarations.WorkspaceMember— every other non-Root member.
The default BundleBuildOptions::default() constructs an empty policy, so codebases that have not opted in keep the prior WorkspaceMember classification. BundleBuildOptions::kernel_v2() (used by first-party loaders for the kernel-api v2 endpoint) seeds the policy with the kernel’s authoritative shared-base list (std, ufo, core, coex, cofris, ccf).
Locked-dep classification is unchanged — Arm 1 (workspace member root precedence) does not apply to lockfile packages, and the existing locked-dep path already consults the policy.
Consequences
For modelers. Customer overlays that vendor UFO as a workspace member now have UFO classified as SharedBaseProvided automatically, when uploading through a first-party loader. No bundle-level surgery, no manual policy override per call site.
For codebases on BundleBuildOptions::default(). No change. Empty policy preserves the prior classification.
For examples/lease-story. Loaders using BundleBuildOptions::kernel_v2() will now classify lease-story’s vendored ufo / coex / cofris / ccf as SharedBaseProvided. The kernel filter strips their emit — matches the existing locked-dep behavior. Lease-story’s local-toolchain-only tests (which don’t involve the kernel) are unaffected.
For the LSP and InfoView. The LSP doesn’t run the bundle assembler; this change is invisible to editor-side resolution. Only the kernel-upload path sees the new classification.
Implementation
Landed on kernel/base-schema-compile-resolution (Phase 1). Changes:
argon/ox/src/bundle.rs::build_virtual_source_bundle— workspace-member role assignment now consultsoptions.shared_base_policy. Same precedence forRoot. NewSharedBaseProvidedarm for non-Root members.argon/ox/src/bundle.rs::tests— three new tests covering (a) workspace-member shared-base classification, (b) Root precedence over policy, (c) empty-policy backwards-compat.
No changes to public API. No changes to wire format. No changes to the policy endpoint or storage layer.
Phase 2 (planned, separate RFD)
The kernel-side base loader has loose ends that this RFD does not address:
- Source-of-truth coupling. The hardcoded
SHARED_BASE_PROVIDED_PACKAGESlist inkernel/api/src/v2/schema/package_policy.rsdoes not consult the actual base-tenant content. A customer who renames or repartitions UFO would have to update the kernel binary’s policy list to match — coupling at compile time when the kernel and customer overlays could be on different release cadences. The fix is for the policy endpoint to read the live base-tenant package metadata. - Argon-source base loading. The current base loader (
orca-db-reset→kernel/owl-loader) loads UFO from OWL2 EL++ Turtle files. A future Phase 2 ships a canonical Argon-source UFO package, compiles it kernel-side at DB-reset time, and stores axioms underSHARED_BASE_TENANT_IDdirectly fromCoreModule. Drops a translation layer; aligns content-hash provenance. - Customer override. A customer who wants a different UFO variant (or a non-default foundational stack) needs a way to pin it. The natural surface is a per-tenant config row that overrides the default base packages; the kernel’s policy endpoint then returns the per-tenant overrides instead of the global default.
These are tracked as RFD-0033 (planned).
Open questions
- What should
BundleBuildOptions::default()ship with? Today it returns an empty policy, which preserves backwards-compat. The argument for changing the default toSharedBasePolicy::kernel_v2()is that it’s the right thing for the dominant first-party-loader case. The argument against is silent-behavior-change for any third-party code consumingbundle::build_virtual_source_bundledirectly. Phase 1 keeps the conservative empty default; revisit when Phase 2 lands. - Should
SharedBasePolicy::kernel_v2()continue to be a hardcoded list, or read from a feed? Hardcoded works while the base content is stable. A feed (e.g., served by the kernel-api alongside the policy endpoint) lets the kernel evolve the base without forcing a toolchain rebuild on every consumer. Tracked alongside the policy endpoint redesign in Phase 2.