Morphir Engine
A portable engine for transforming a file, directory, project or workspace:
Host files → frontend → current Morphir IR → Scheme transforms → backend → artifacts
The engine handles discovery, manifests, configuration and diagnostics. The host
owns I/O. Its synchronous read, exists and recursive list callbacks can read
an editor snapshot, a filesystem or a cache populated by asynchronous I/O.
The engine returns artifacts; the caller chooses how to persist or publish them.
Run the example
From the repository root:
mise exec -- moon run pkgs/morphir-engine/examples --target native
# Also supported: --target js, --target wasm, --target wasm-gc
This runs a project from a memory host, applies its configuration, emits an IR
artifact and executes its main entry. The CLI
exposes workspace discovery and pipeline execution through the host adapters.
TOML workspaces and execution plans
config wraps moonbit-community/toml with semantic encoding, deep merge,
environment mapping and provenance. workspace discovers canonical Morphir layouts,
expands member globs and selects by name or path. It accepts the protocol-v1 request
shape through discover_request; the supported TOML success snapshots are tested
against vendored upstream fixtures. YAML candidates produce explicit diagnostics.
Malformed members have error state and do not prevent selecting a valid sibling.
morphir.json is an Elm project
discover and plan read morphir.json (and the members of morphir-workspace.json) as a
morphir-elm project. A morphir.toml beside it wins, and a warning says so.
morphir.json |
Configuration |
|---|---|
name (default user; must be a valid package name) |
project.name |
sourceDirectory (default src) |
project.source_directory |
exposedModules (required list of module names) |
project.exposed_modules |
| none | frontend.language = "elm" |
| none | default pipeline.backend = "checkpoint", pipeline.checkpoint_format = "morphir-json" (system, global and workspace configuration can replace them) |
The planner refuses the plain ir-json backend for project compilers, so the default is a
morphir-json checkpoint. It writes the same Morphir IR JSON to
.morphir/out/compile.dest/<package path>.json. A run can choose another backend, for
example elm or moonbit, and an overlay can choose another language.
A bad field is a configuration error that names the file. dependencies,
localDependencies and decorations are not supported: when one is not empty, it adds a
line to Workspace.warnings.
Engine::list_projects resolves effective frontend and target choices through the
same configuration resolver used by planning, including workspace and member scripts.
It returns ProjectInfo entries with per-project errors, accepts an optional final
script and host runtime bindings, and does not enumerate or read sources. Backend
identifiers are target names. Unknown compiler names can be listed without registering
them in the engine.
Listing shares IR configuration validation with planning and raises cancellation
instead of turning it into a per-project error.
Engine::plan resolves project settings, evaluates configuration scripts, validates
registered frontends/backends and fixes source paths and output destinations.
Engine::execute runs that plan once against a source snapshot and returns a report.
Prepare a fresh plan for each execution so Scheme closure state never leaks across runs.
An embedding host can pass bind_runtime to Engine::plan or Engine::run to
register native procedures in each isolated project session before scripts run.
The callback receives the project runtime; it can use the Morphir Scheme backend's
register_external, register_native, and register_accelerator APIs.
morphir-host prepares snapshots and publishes reports through explicit capabilities.
See its README for the complete embedding sequence.
A source belongs to its most specific discovered project. Parent projects exclude nested members, and explicit file/directory selections cannot cross that ownership.
Workspace outputs live under <out_dir>/<member-path>/compile.dest/. Each input
still produces its own distribution. Scheme configuration output adds a subtree
inside that task destination. Project module prefixes and exposed modules apply
before IR transformations. Explicit CLI options take precedence over scripts.
IR JSON output can request versions 1 through 4. The existing migration codecs refuse lossy conversions. For example, Scheme output with absent member documentation cannot be converted to a classic encoding that requires documentation text; this produces a diagnostic and prevents publication.
Legacy embedding API
Declare finos/morphir-engine@0.1.0 in your module and import it as engine.
let host = @engine.Host::memory(Map::from_array([
("demo/morphir.toml", "[project]\nname = 'demo'\nsource_directory = 'src'\n"),
("demo/src/main.scm", "(* 6 7)"),
]))
let engine = @engine.Engine::new()
let report = engine.run(host, @engine.Project("demo"))
// report.artifacts[0].path == "demo/dist/main.ir.json"
Report contains artifacts, diagnostics and processed (successful artifact
count). successful() means no diagnostics. Diagnostics identify the path,
stage and error detail. A failed file produces no artifact; other files continue.
Output collisions produce a diagnostic and retain only the first artifact.
A Diagnostic has problem : Problem?. Source carries the frontend code, detail, path relative to the
source directory and range; Generation carries the generation code, node path and detail. code() and
location() read them.
Check the whole report before publishing. The host runner refuses publication when
any selected transformation fails.
| Input | Discovery |
|---|---|
File(path) |
One file, regardless of extension; parent directory supplies config |
Directory(root) |
Recursively listed files matching the selected frontend extensions |
Project(root) |
morphir.toml supplies [project] name and source_directory (defaults: user, src) |
Workspace(root) |
morphir.toml lists project directories in [workspace] members, as literal paths (no *, ?, [ or {) |
For this legacy API, project manifests must exist. Scheme is a new project type, so Engine::run
reads morphir.toml and ignores every key except the ones above; morphir.json and
morphir-workspace.json are not read by it. A morphir.json is only ever an Elm project, read
by discover and plan (see "morphir.json is an Elm project"). Engine::run uses the name
and source directory of the manifest; it does not implement Elm exposedModules, dependency
resolution or cross-file linking. Each source yields a separate IR distribution/artifact. A
Scheme module's name comes from its source-relative path without the final extension. Nested
source paths are retained in output paths. References to other implementations can be resolved
by loading them together in a Scheme runtime.
Workspace manifest (morphir.toml):
[workspace]
members = ['packages/orders', 'packages/pricing']
Members are literal paths: a member with *, ?, [ or { is refused with a diagnostic.
Workspace project order is retained; files within each project are sorted and
deduplicated. Duplicate project entries are ignored. Configuration and manifest
files at the project root, plus the output subtree, are excluded from discovery.
Script configuration
Place morphir.config.scm at the workspace/project/directory root, or pass a script
as the config argument to run:
(pipeline
(frontend "scheme")
(backend "ir-json")
(output "generated")
(transform
(lambda (file)
(ir-rewrite-file file
(lambda (node)
(if (ir-integer? node)
(ir-with-integer node (+ (ir-integer node) 2))
node))))))
Options: frontend, frontend-provider, frontend-profile, backend, output, source, package-name,
inference and transform. run refuses an inference other than "off", because the inference stage needs the
planned pipeline (Engine::plan).
Defaults are Scheme → IR JSON, output dist, package user, and the input directory
as source (project manifests default to src). source applies to discovery,
not explicit File inputs. Output paths are relative to the input/project root.
Precedence is manifest → workspace script → project script → explicit script.
Scalar options override earlier values; transforms accumulate and execute in order.
Each script returns a pipeline list of option records. Scripts can define helpers
and use the Scheme IR bridge.
Each transform accepts and returns a typed IR file. During transforms, source-path
and project-root are strings in the script session.
Every project/run gets a fresh runtime. Within a project, configuration closures and their state are shared across files in discovery order. Evaluation budgets apply to each script and each transform; frame allocation is bounded across the session. A workspace configuration is evaluated separately for each project.
Frontends and backends
Built-in frontends:
scheme:.scmand.ss, structural Scheme → current IR.ir-json:.json, versioned IR JSON → current IR using existing migrations.
Built-in backends:
ir-json: current IR JSON, suffix.ir.json.scheme: executable Scheme, suffix.scm; requires the Morphir Scheme runtime. From a rich unit it also writes<unit>.symbols.10n(formation-binary) with the unit's metadata and comments eachdefinewith its source name and position (see the Scheme README).
scheme accepts rich units (MoonBit, Elm): it writes <unit>.scm with source comments and <unit>.symbols.10n
with the unit's metadata (morphir-ukvn.5.40), and reports no backend.metadata_dropped. ir-json still refuses
them.
register_frontend(name, extensions, compile) accepts Source -> IRFile.
Source includes path, source-relative path, content, package name and module name.
register_backend(name, extension, emit, rich_units~) accepts IRFile -> String. A backend with rich_units=true
accepts rich units, emits their IR and warns backend.metadata_dropped when it drops metadata.
register_unit_backend(name, extension, emit, emit_unit) (the built-in scheme) also accepts
(data.IRUnit, source_path) -> Array[GeneratedFile] for rich units and keeps their metadata, so it reports no
backend.metadata_dropped; its files are relative to the job destination and a plan with such a job requires a byte
publisher.
Registrations replace the same name and share the standard transform/diagnostic flow.
All frontends and transforms operate on the current unversioned IR model.
Rich source frontends
register_unit_frontend(name, extensions, compile, profile?, dependencies?, limits?)
accepts (Source, unit_id, cancelled) -> data.IRUnit. The engine supplies canonical
package/module names, a source-relative logical ID and the execution cancellation
callback. Source paths and content retain their original spelling. Each file is an
independent unit; the hook does not imply linking or dependency acquisition.
Registrations reject conflicting names and copy extensions/dependency descriptors.
Plans capture the callback and limits. Plan::source_frontends() reports the selected
source, unit ID, frontend, optional profile, dependency versions and ir-file or
ir-unit output contract without reading source bodies. Legacy register_frontend
continues to accept IRFile and replace registrations with the same name.
During execution the engine checks the returned ID, envelope budgets and metadata
node ownership before components or output. Adapters can raise
SourceFrontendError::Rejected(code, detail, source_span); diagnostics preserve the
code and parser span in their message. Rich callbacks require planned checkpoint,
generation, unit-backend or rich_units backend output. ir-json and Engine::run reject
them. scheme is a unit backend: it keeps the metadata in a sidecar and comments the
source. A unit backend keeps the checkpoint rule: no transforms and no exposed_modules
outside a project compiler, because the metadata would no longer match the IR; components
still need a checkpoint or generator backend. Components retain their existing projection and
lineage rules. Morphir JSON remains supported; its checkpoint export rejects rich
metadata that the standard schema cannot represent.
The separate MoonBit adapter registers moonbit explicitly.
Engine::new() does not register it or import its parser into the core package.
A generator raises GenerationFailed with one GenerationProblem for each refused IR node. The report then has
one backend diagnostic for each problem, with the text code at node: detail.
Source providers and profiles
register_source_provider(SourceProvider) registers an in-process compiler provider
with one or more SourceLanguage offerings. Each language has file extensions, an
explicit default profile, and SourceProfile records. Each profile contains its
compile callback (SourceCompile; PerFile compiles one source file to one rich unit),
envelope limits, source capabilities, dependency versions and SourceEvidence. Registration validates bounded descriptors and copies mutable
arrays and maps. It invokes no compiler. max_documents is the number of source
documents one unit can hold. None means no limit (the default). A provider may declare
a limit, a positive number with no upper bound.
Configure frontend.language, frontend.provider and frontend.profile in TOML.
Scheme equivalents are frontend, frontend-provider and frontend-profile.
A missing profile selects that provider's default. A missing provider selects the
only provider offering the language. Multiple providers require an explicit choice,
even if a profile name matches only one of them. Unknown providers, a provider that
does not offer the language, and unknown profiles fail before model reads.
Source constraints are invalid on codec inputs, including ir-json.
Engine::languages() lists every source language the engine can compile as
LanguageOffer records: registered providers, plus frontends from register_frontend
and register_unit_frontend, including the built-in scheme. An offer has the language,
provider, extensions, default profile and ProfileOffer records (EachFile or
WholeProject, capabilities, evidence and unit limits). No offer holds a compile
callback, so the list is safe to give to any caller. The list has one offer for each
language and provider, sorted by language and then provider. The ir-json frontend and
the data formats (morphir-json, ion-text, ion-binary) are not source languages and
are left out.
finos/morphir-engine/elm registers the Elm frontend as a PerProject provider; see its README.
A plan captures the selected callback and its limits. Later registrations cannot
change execution of that plan. Selection descriptors include language, provider,
profile, default_profile, available_profiles, capabilities and evidence.
default_profile means the selected profile is the provider's default, including an
explicit request for it. The existing frontend field retains the language name.
Legacy registrations use their frontend name as provider ID, have no capability
claims, and report Declared evidence. Their overwrite API remains available;
a conflicting provider identity fails during selection rather than silently hiding
one compiler. register_unit_frontend rejects such collisions at registration.
SourceCapabilities describes supported and excluded source behavior, document
bounds, incremental support and configured parser budgets. These descriptions are
separate from IR feature vocabulary and protocol negotiation. LocalAcceptance
records repository qualification, not Morphir MCK certification. A PerFile callback
receives one source document per invocation. A PerProject callback receives every
source document of the project in one ProjectSources (the project name and
exposed_modules as written, and the sources in path order) and returns one unit whose
id is the canonical project name (Example.Project gives example/project). It needs a
checkpoint or generator backend, compiles whole projects only (a single-file request,
or a directory request that does not hold the project's source directory, fails with
frontend.project_compiler_file) and has no document limit unless its
profile declares one (max_documents: Some(n); planning then refuses more than n
sources with frontend.too_many_documents). The output budget unit_limits bounds the
unit. The plan accepts exposed_modules on the checkpoint path for a project compiler
and does not rewrite module access: the compiler sets it. A project compiler reports
all of its problems with RejectedSources; each problem is a diagnostic at its file. An
empty RejectedSources is a frontend.rejected diagnostic at the source directory.
Profile diagnostics keep their code and span and add the captured provider/profile
context. Project listing continues to report language and configured target, without
requiring compiler registration.
Type inference
pipeline.inference runs the language-neutral type inference of
morphir-typing on each unit. Set it in morphir.toml or with
(inference "fill") in a pipeline script.
| Value | Effect |
|---|---|
off (default) |
No inference. The IR is unchanged. |
fill |
Writes the solved types into the IR: an inferred_type on each node, and the solutions of the signature types that have the unknown marker. |
check |
Checks the IR and does not change it. |
The stage runs after the transforms and before the backend. It uses the Morphir SDK specification as a dependency.
On the checkpoint and generation paths, fill changes the unit through data.fill_lineage with the
no_derived metadata policy, so metadata stays on its logical nodes and a written type gets no metadata.
Each inference problem is a diagnostic at stage inference, in the form <code> at <node>: <message>.
A value other than off, fill or check stops planning and names the file that set it.
On Scheme input, a call from one top-level define to another, or a top-level define that calls itself,
fails fill with unknown_reference, because the Scheme frontend lowers that reference as a local variable. Bead
morphir-ukvn.5.22.8 tracks the frontend fix.
End to end. mise run e2e:scheme-moonbit and mise run e2e:scheme-elm compile the Scheme corpus in
acceptance/inference, fill its types and generate code. moon build must accept each MoonBit library, and
elm make must accept each Elm project. The MoonBit task uses MOON_HOME, or the pinned toolchain from
mise where http:moonbit when it is not set; to set it yourself, run
MOON_HOME=$(mise where http:moonbit) mise run e2e:scheme-moonbit. The corpus
holds monomorphic programs only, because the MoonBit generator has no specialization: a generic value, such as
(define (id x) x), needs the specialization follow-up. moonbit_gaps names the corpus programs that the MoonBit
generator does not support yet, with the error that it must raise; Elm checks them all.
Elm end to end. mise run e2e:elm-moonbit and mise run e2e:elm-elm compile every project of the Elm parity
corpus (pkgs/morphir-elm/conformance/projects) through the engine with the Elm provider and generate MoonBit and
Elm; moon build and elm make must accept the output. acceptance/elm/projects.mbt holds the sources; mise run elm:parity-refresh regenerates it. gaps in acceptance/elm/gaps.mbt names each project and backend that does not
generate yet, with the start of its first diagnostic and its bead.
Typed checkpoints
Planned pipelines can select pipeline.backend = "checkpoint". Its default
pipeline.checkpoint_format = "ion-binary" is independent of the frontend.
Built-in data formats are morphir-json, ion-text, and ion-binary; ir-json
is also accepted as the JSON frontend name. Checkpoints preserve a typed
data.IRUnit, including rich metadata, through the
morphir-pipeline-v1 profile. The current checkpoint pipeline
permits identity processing and rejects transforms or visibility rewrites until
they provide metadata lineage. Standard JSON export checks for metadata loss.
Engine::execute_content(plan, read) returns ContentReport and accepts a
callback (path, binary) -> data.Content. Each ContentArtifact records path,
source, format and text/bytes. The existing execute API adapts text plans to
the same executor and reports missing byte capabilities for binary plans.
on_unit receives each unit that reaches the backend stage, before generation, with its final IR. A host uses
it to show the model even when generation fails.
Both executors consume the same single-use plan. Host-level capability failures
occur before execution and leave the plan available for a capable host.
Plan::boundaries() returns source identities, selected input/output codecs,
profile and byte requirements. requires_byte_read/write also account for jobs
with no matching sources. Unit IDs are source-relative stems; envelope IDs must
match them. Selection, confinement, suffix collisions and destination ownership
use the existing planner.
For an additional codec, register it with data.Registry::register, register
its file extensions, output suffix and encoding using
Engine::register_data_format(name, DataFormat), and pass the registry to
Engine::plan(..., codecs=Some(registry)). Format registrations reject conflicts,
copy extension arrays and bind descriptors into the plan. Planning checks that
the selected codecs exist. Execution rejects outputs whose encoding disagrees
with the format descriptor. This keeps file selection and host capability
requirements separate from codec implementation.
Host contract and targets
Host paths are portable, relative, slash-separated strings. The engine removes
empty/. segments and rejects absolute paths, .., backslashes, colons and NUL.
list(root) returns recursive file paths relative to the host root, not the list
root. Listed files must remain inside the requested source subtree. exists tests
files; read returns text. The host must enforce its own filesystem root and symlink
policy. Output directories must be nonempty and cannot contain the source root.
Host::memory copies its input map so later caller changes cannot affect a run.
No filesystem/network dependency or Node-only API is imported by this module.
The same API and example build for native, js, wasm and wasm-gc.
Generation backends use GenerationBackend and return a GenerationBatch of
files and complete library projects. A batch carries legacy text warnings and
structured_warnings for diagnostics with codes, locations and fixes. Strict naming
mode checks structured naming warnings before accepting artifacts. The engine validates portable paths,
case collisions, file/directory conflicts, project root overlap and exact member
ownership before accepting a batch. Each input receives its own project root
under the task output. The generic engine contract has no filename or manifest
rules for a particular target.
A GenerationBackend has these fields:
generatetakes the IR unit, the options and a cancel check, and returns the batch.write_bytestells the engine that the host must be able to write binary files.validate_optionschecks the options at planning time.targetslists the build targets thatpipeline.validation = "required"can use.
The options of a generator are the [backends.<name>] table of the project
configuration, as JSON. The engine passes the table to validate_options when it
plans, and to generate for each unit. When validate_options fails, the plan
fails with Invalid. The message names the key and the file that set the key, or
the file that set its nearest table.
The built-in generators are:
moonbitimports the portablefinos/morphir-moonbitgenerator. Its targets arewasm,wasm-gc,jsandnative.[backends.moonbit] naming = "readable"is the default;"legacy-hex"retains the earlier generated ABI. Other keys remain ignored. Invalid naming settings fail when planning.elmimportsfinos/morphir-elmand writes an Elm package project for each unit. It has no targets. Its options are in the morphir-elm README, for example[backends.elm] profile = "literal".
pipeline.validation defaults to required for a generator that has targets, and
to source-only for a generator that has none. pipeline.target defaults to
wasm. A required plan fails with build.unsupported_target when the generator
does not list the target, so required always fails for elm. Set source-only
explicitly to publish source without build evidence. Existing String backends and
checkpoint outputs use the same execution and publication lifecycle.
Register typed or JSON-only components with register_component. Configure their
order with pipeline.components and select a registered pipeline.metadata_policy.
The built-in json-identity is a compatibility boundary exercised by the CLI.
Plans expose each component's declared transport and metadata policy separately
from the input and checkpoint codecs. A JSON-only step does not change the engine
or intermediate default from Ion. Legacy Scheme rewrites remain on their text
pipeline; typed pipelines require the explicit lineage component contract.
Generated-model execution
finos/morphir-engine/execution plans calls against a generated public invocation
manifest and runs a host-supplied Provider. Its Session builds once, retains an
executable for runtime suites, and disposes it after execution and comparison.
Custom providers use the same contracts without filesystem assumptions.
plan validates the suite and public boundary types before host effects. run
accepts an optional Evaluator and Observer. The Scheme evaluator uses the same
IR unit as the generator, finite limits and no runtime accelerators. Provider
failures are errors; unequal typed values are unsuccessful comparison reports.
Required reports bypass observation sampling, queues and sinks.
The invocation boundary supports exact Int/Decimal, Float64 bits, UTF-16 text
and characters, records, tuples, lists, nongeneric custom types, Maybe and Result
on JavaScript, Wasm GC and native C. A separately pinned LLVM provider runs the
same rich pricing suite on its verified host. plan(available_targets=...) rejects an unavailable
required target before acquisition; LLVM needs an explicit available capability.
Session build/invoke callbacks receive optional validated trace context, separate
from semantic inputs. Lifecycle hardening and verification publication are implemented. Independent
conformance uses the separate contract below. Native OpenTelemetry collector
integration remains Task 6. E1 build-only
behavior is unchanged. Execute/verify do not publish over generated project output.
run_report returns required call terminals and separate primary/cleanup causes
for provider failures and cancellation. run preserves raising behavior,
including CleanupFailed(original, cleanup). Input IR and suites are cloned
through binary Ion before effects, and source/manifest mutations cannot produce
a successful report. matrix_successful rejects partial, duplicate-target and
failed matrices; a required parity matrix also needs an evaluator profile.
Independent conformance
finos/morphir-engine/conformance accepts versioned Expectations with explicit
provenance and a list of EvaluatorLane providers. prepare validates the same
suite/model and expected output types, and rejects unavailable or unsupported
required evaluators before acquisition. run freezes the Ion model and cases,
runs generated execution once, disposes its session, then compares each call
separately against independent and live results. Provider callbacks cannot
mutate the frozen model, driver or suite to change the comparison inputs.
Each lane records its own profile, identity evidence and call statuses.
Unavailable/unsupported optional coverage never becomes a comparison. Any
independent or live mismatch fails Report.successful() and retains its call
and JSON Pointer. Report.encode() defaults to native binary Ion; JSON is an
explicit projection. Required evidence does not depend on observer delivery.
Host-specific Rust conversion/process code stays in morphir-host. Field
projection policies live in morphir-execution/projection, separate from
semantic values and from host exporters.
Source naming warnings
Rich source adapters may retain morphir-warning-v1 diagnostic records in unit
provenance. Planned execution reads these records before components and generation,
returns them through ContentReport.warnings with structured Diagnostic.details,
and deduplicates identical owning allocations within one source run. Warning
locations refer to original source spans, even when the input is a checkpoint.
Unknown provenance remains untouched; malformed records marked as this diagnostic
contract fail explicitly. Storage does not authenticate a producer or confer
upstream metadata trust.
For MoonBit recoverable name allocation warnings, [pipeline] strict_naming = true
rejects the run before artifacts or projects are returned for publication. Warnings
remain visible in the report, and successful execution does not require optional
logging. Normal processing preserves the warning records through Ion checkpoint
output. Existing checkpoint semantic identities are never normalized on load.
Declared target-name facts
Engine::new(metadata_capabilities=...) accepts the explicit provider restoration,
context acquisition and naming interpreter capabilities described in
the metadata contract. The built-in MoonBit generation
backend captures them at registration. Default capabilities are unavailable; neither
metadata nor configuration supplies provider trust. Known requests fail before artifacts
are returned when admission or required interpretation is missing. Rich Ion checkpoints
retain the authored payload, and Morphir JSON retains its checked loss boundary.
Project-scoped metadata providers
Applications can call Engine::register_project_metadata_provider(bind) to bind
separate metadata capabilities to each planned MoonBit project. bind receives a
ProjectMetadataScope containing the workspace root, project root/name and an
immutable configuration.MetadataProviderRequest. The engine binds once per
selected MoonBit project and captures the returned capabilities in that project's
jobs. Identical predicate or IR unit names do not share authority across projects.
Binding must perform no I/O, restoration or resource allocation requiring cleanup.
Restoration belongs in the returned restore_library callback, which interpretation
calls only when it encounters a required naming fact. list_projects does not
bind providers. Constructing a plan binds selections but does not restore providers;
projects without naming facts perform no restoration. Each new plan binds again.
The host owns approval of paths, confinement, authentication and later session
lifecycle. Host failures should raise morphir-metadata.Invalid with a stable code,
owner and repair so generation retains their diagnostic details.
The existing Engine::new(metadata_capabilities=...) remains the static embedding
API. An active static capability and a project binder, or two binder registrations,
fail with metadata.ambiguous_authority. Unavailable static defaults grant no
additional authority. Custom generation backends keep their existing callback ABI;
this binder applies to the built-in MoonBit generator.
Configuration selects a service; it cannot install a callback. The pure
Document::metadata_provider_request() parser accepts disabled defaults or this
explicit request:
[metadata]
provider = "shared-local-restore"
service = "tools/morphir-provider-host"
policy = "trust/policy.json"
lock = "morphir.lock"
registry = "registry"
state = "trust/state"
assurance = "portable"
[metadata.naming]
release = "example.com/morphir/naming@1.0.0"
Every path retains its winning configuration origin for host resolution. Parsing
preserves the selected strings and performs no filesystem access. The host must
resolve paths from their owning documents, or the invocation for CLI selections,
and enforce its grants. The parser requires all shown fields, canonical stable
naming-release syntax and explicit portable assurance. Unsupported providers,
hardened assurance, incomplete requests and non-string selections fail with
metadata.invalid_configuration, naming the key and origin. provider = "none"
is the explicit disabled selection; service selections cannot accompany it.
Unrelated metadata configuration remains available to its own consumers. A disabled
request means configuration requests no service; an embedding may still grant
capabilities explicitly, as with the existing static constructor API.
This contract does not install a signature verifier or shared restore service. The installed CLI still supplies unavailable capabilities; configuration alone does not enable authenticated generation. Shared-service snapshot export, checked native projection, scoped host sessions and installed authenticated qualification are separate integration work. A trusted-validation summary or stored receipt cannot substitute for freshly authenticated provider bytes.
The release identifies the authority-qualified publisher package in the shared
registry, such as example.com/morphir/naming@1.0.0. It is distinct from the
provider's native IR package name, morphir/naming, which admission must check
independently. The example authority is a placeholder, not a default trust grant.
Host-owned execution jobs
The execution jobs package owns bounded in-memory snapshot-bound receipts around admitted engine plans. It captures rich inputs, allows one execution claim, acknowledges cooperative cancellation and validates terminal outputs against the retained public declarations. Authenticated host transport and shared Workbench UI adoption are separate integrations.