Skip to content
Beskid Platform specification

Beskid

Jump to a Beskid service

Beskid

Jump to a Beskid service

Mod host bridge - AOT artifact contract

Platform spec article

Mod host bridge - AOT artifact contract

Spec standingStandard

Owner
Piotr Mikstacki
Submitter
Piotr Mikstacki

This article is the normative AOT artifact contract for type: Mod packages. It complements Mod host bridge / design model lifecycle steps and Compiler Mod SDK contract discovery.

Hosts must store each built mod artifact under the workspace object store using a content-addressed layout:

<workspaceRoot>/.beskid/obj/mods/<packageId>/<artifactKey>/<targetTriple>/
mod.o # or platform archive (mod.a / mod.so) — native object for AOT load
mod.descriptor.json
Path segmentMeaning
<packageId>Stable package identity from lock/manifest (normalized, case-sensitive as resolved).
<artifactKey>Lowercase hex SHA-256 of the cache key tuple (see below).
<targetTriple>LLVM-style triple string for the built artifact (e.g. aarch64-apple-darwin).

Registry-fetched mods use the same layout under the workspace object store after materialization; global package caches may mirror the tuple but the host must treat the workspace path above as authoritative for an active compilation.

Rebuild is required when any component of the tuple changes:

FieldSource
lock_hashHash of relevant lockfile bytes for the mod package slice.
mod_source_hashHash of mod project sources + Project.proj / project.mod bytes.
target_tripleActive host target for AOT link/load.
compiler_versionReference compiler version token (CLI --version / embedded build id).

artifactKey = SHA256(canonical_json({ lock_hash, mod_source_hash, target_triple, compiler_version })).

beskid mod rebuild must invalidate entries whose tuple diverges; beskid mod clean must remove .beskid/obj/mods/ (or the documented subset for named projects).

The sidecar must be UTF-8 JSON. Hosts must reject artifacts when the descriptor is missing, unreadable, or fails schema validation (E1821–E1835).

FieldTypeRequiredMeaning
schemaVersionintegeryesMust be 1 for this contract revision.
packageIdstringyesMatches resolved package id.
packageVersionstringnoRegistry-assigned version when known.
modSourceHashstring (hex)yesMust match cache tuple mod_source_hash.
lockHashstring (hex)yesMust match cache tuple lock_hash.
targetTriplestringyesMust match directory <targetTriple>.
compilerVersionstringyesMust match cache tuple compiler_version.
objectFilestringyesRelative path within the artifact directory (typically mod.o).
registrationsarrayyesContract export table (may be empty only when the mod exports no contracts).

Each registrations[] element:

FieldTypeRequiredMeaning
contractIdstringyesStable contract name (e.g. Beskid.Compiler.Collect.Collector).
typeIdstringyesPublic Beskid type implementing the contract in the mod assembly.
entrySymbolstringyesAOT export symbol used to invoke the contract entrypoint.

Example:

{
"schemaVersion": 1,
"packageId": "compiler_sdk_test_mod",
"packageVersion": "0.0.0-local",
"modSourceHash": "a1b2…",
"lockHash": "c3d4…",
"targetTriple": "aarch64-apple-darwin",
"compilerVersion": "0.2.0-dev",
"objectFile": "mod.o",
"registrations": [
{
"contractId": "Beskid.Compiler.Collect.Collector",
"typeId": "TestMod.DemoCollector",
"entrySymbol": "beskid_mod_entry_TestMod_DemoCollector"
}
]
}

Hosts may also read an embedded export table inside the native object, but mod.descriptor.json is authoritative for discovery when present.

  1. Resolve transitive Mod nodes from CompilePlan.
  2. Locate artifact directory for (packageId, artifactKey, targetTriple).
  3. Verify modSourceHash, lockHash, targetTriple, and compilerVersion against the active tuple.
  4. Load native object and parse registrations.
  5. Bind (contractId, typeId, entrySymbol) tuples into the mod host schedule before mod.collect.

Any failure must emit a diagnostic from E1821–E1835 and must not partially schedule contracts.

CodeWhen emitted
E1821Artifact directory or objectFile missing.
E1822mod.descriptor.json missing or not valid UTF-8 JSON.
E1823schemaVersion unsupported or required field absent.
E1824modSourceHash or lockHash does not match active cache tuple (stale artifact).
E1825targetTriple does not match host compilation target.
E1826compilerVersion does not match active compiler build.
E1827Native object load/link failed (platform loader error).
E1828entrySymbol not found in loaded object export table.
E1829Duplicate (contractId, typeId) registration in one artifact.
E1830registrations empty but mod package declared required contracts in manifest metadata.
E1831Capability required at load time not granted in project.mod.capabilities.
E1832maxGeneratorRounds exceeded during host compilation (runtime policy).
E1833Sandbox / FFI bridge violation during mod bootstrap.
E1834Artifact built for different packageId than resolved graph node.
E1835Catch-all mod-host bootstrap failure when no more specific E1821–E1834 code applies.

Register each code in diagnostic_kinds.rs when implementation lands; extend this table if new load paths need distinct identities.

Scheduling-conflict diagnostics (E1851–E1870)

Section titled “Scheduling-conflict diagnostics (E1851–E1870)”

After mod.load but before mod.collect, the host must validate cross-artifact and cross-registration consistency. Failures in this band short-circuit scheduling and are reported as ModHostDiagnostics in compiler/crates/beskid_analysis/src/mod_host/diagnostics.rs.

CodeWhen emitted
E1851Same (contractId, typeId) is provided by multiple mod artifacts.
E1852Two distinct artifacts export the same entrySymbol for different (contractId, typeId) tuples.
E1853Unknown contractId value in a registration (not a recognized SDK contract suffix: Collector, Generator, AttributeGenerator, Analyzer, Rewriter).
E1854Rewriter registration without an attached Analyzer registration in the same artifact.
E1855Catch-all scheduling-stage failure when no narrower E1851–E1870 code applies.
E1856–E1870Reserved for future scheduling-stage failures.

These diagnostics carry artifact-level locations (descriptor sidecar path or manifest path) rather than Beskid source spans because the conflict is detected on the loaded registration table, not on user source. Implementations must report all issues found in a single load pass deterministically before aborting.