///|
/// Keep the historical complete-manifest description for strict host mappings.
fn typed_profiles(file : @ir.IRFile) -> Json {
try {
let library = @moonbit.generate(@moonbit.Input::new("workbench", file))
let values = evaluation_definitions(file)
@wire.obj([
(
"entries",
@wire.obj(
library.invocations.map(entry => {
(
entry.fqname,
typed_profile(
{
name: entry.fqname,
inputs: entry.inputs,
output: entry.output,
},
library.invocation_types,
values,
),
)
}),
),
),
])
} catch {
@moonbit.Invalid(code, _, detail) =>
@wire.obj([("reason", @wire.text(code + ": " + detail))])
}
}
///|
fn typed_profile(
entry : @protocol.PublicEntry,
definitions : Array[@protocol.CustomType],
values : Map[String, @ir.ValueDefinition],
) -> Json {
let registry = definitions.map(d => {
(
{
"name": d.name,
"constructors": d.constructors.map(c => {
(
{
"name": c.name,
"inputs": c.inputs.map(@protocol.ValueType::to_json),
} : Json)
}),
} : Json)
})
let (defaults, default_reason) = (
Json::array(
entry.inputs.map(t => @arguments.typed_default(t.to_json(), registry)),
),
"",
) catch {
_ =>
(
Json::array([]),
"No finite default was found. Enter tagged values in parameter order.",
)
}
let names = match values.get(entry.name) {
Some(ExpressionBody(inputs, _, _)) => inputs.map(p => display_name(p.0))
_ => []
}
{
"supported": true,
"default": defaults,
"defaultReason": default_reason,
"inputs": entry.inputs.mapi((i, t) => {
(
{
"label": names.get(i).unwrap_or("Argument " + (i + 1).to_string()),
"type": t.to_json(),
} : Json)
}),
"output": entry.output.to_json(),
"definitions": registry,
}
}
///|
/// Local worker descriptions only. Each selected type closure is discovered
/// independently; neither this description nor generated-code success validates a run.
fn capability_profiles(file : @ir.IRFile) -> Json {
let values = evaluation_definitions(file)
let names = values.keys().collect()
names.sort()
let profiles : Array[(String, Json)] = []
let mut bytes = 0
for name in names {
// This eagerly describes inputs for the inspector. Bound both the number of
// independent closures and their combined editor/default expansion.
if profiles.length() >= 128 || bytes >= 1048576 {
break
}
let profile = try {
let (entry, definitions) = @typed_engine.scheme_entry_boundary(file, name)
typed_profile(entry, definitions, values)
} catch {
error =>
(
{
"supported": false,
"reason": "Unsupported input/output boundary: " +
capability_problem(error).0,
} : Json)
}
bytes += @utf8.encode(profile.stringify()).length()
if bytes > 1048576 {
break
}
profiles.push((name, profile))
}
{
"evaluator": @capabilities.SchemePortable.to_json(),
"admission": @capabilities.policy_offer(),
"entries": @wire.obj(profiles),
"reason": "No supported public boundary is described for this function. Local descriptions are limited to 128 entries and 1 MiB.",
}
}
///|
/// The standalone worker captures the policy and builds an immutable plan before
/// execution. Connected/native mappings continue to use their strict old path.
fn evaluate_capabilities(file : @ir.IRFile, request : Json) -> Json raise {
let policy = match @wire.optional(request, "admission") {
None => @typed_engine.Strict
Some(selection) =>
@capabilities.selection_from_json(selection) catch {
@capabilities.InvalidProfile =>
raise @scheme.Invalid("Unknown checking-policy profile")
@capabilities.InvalidPolicy =>
raise @scheme.Invalid("Unknown checking policy")
}
}
let selection = @capabilities.selection_to_json(policy)
let reply = try {
let raw = match @wire.optional(request, "argumentsIon") {
Some(String(source)) =>
@arguments.ion_arguments(Json::null(), source, true)
_ => @wire.field(request, "arguments")
}
let suite = @protocol.Suite::from_json({
"profile": "morphir-invocations-v1",
"calls": [
{
"id": "workbench",
"entry": @wire.field(request, "entry"),
"arguments": raw,
},
],
})
let plan = @typed_engine.plan_scheme_capabilities(
@data.IRUnit::new("workbench", file),
suite,
policy~,
)
let outcomes = plan.evaluate_outcomes()
(
{
"profile": "scheme-portable-v1/shared-sdk/bounded",
"terminalOutcomes": outcomes.to_json(),
"admission": selection,
} : Json)
} catch {
error => {
let (code, message) = capability_problem(error)
let failed = failure(@outcomes.bounded(message, 480), code, "evaluate")
guard failed is Object(fields) else { abort("failure object") }
fields["admission"] = selection
Json::object(fields)
}
}
@outcomes.prepare(reply, expected_ids=Some(["workbench"])).0
}
///|
fn capability_problem(error : Error) -> (String, String) {
match error {
@ion.Syntax(message) => ("ion.syntax", "Ion: " + message)
@ion.DataModel(message) => ("ion.data_model", "Ion: " + message)
@ion.Unsupported(message) => ("ion.unsupported", "Ion: " + message)
@typed_engine.AdmissionInvalid(code) | @protocol.Invalid(code) =>
(code, code)
@moonbit.Invalid(code, _, detail) => (code, detail)
@wire.Invalid(code, detail) => (code, detail)
_ => (scheme_code(error), @scheme.error_message(error))
}
}
///|
fn evaluate_typed(file : @ir.IRFile, request : Json) -> Json raise {
evaluate_typed_with(file, request, @typed_engine.Evaluator::scheme())
}
///|
fn evaluate_typed_with(
file : @ir.IRFile,
request : Json,
evaluator : @typed_engine.Evaluator,
) -> Json raise {
let raw = match @wire.optional(request, "argumentsIon") {
Some(String(source)) =>
@wire.array(@arguments.ion_arguments(Json::null(), source, true))
_ => @wire.array(@wire.field(request, "arguments"))
}
let entry = @wire.string(@wire.field(request, "entry"))
// Decode and validate the published profile before entering the runtime.
let suite = @protocol.Suite::from_json({
"profile": "morphir-invocations-v1",
"calls": [{ "id": "workbench", "entry": entry, "arguments": raw }],
})
// The public decoder enforces the encoded byte budget too.
ignore(@protocol.Suite::decode(suite.encode("json"), "json"))
let unit = @data.IRUnit::new("workbench", file)
let plan = @typed_engine.plan(unit, suite)
let manifest = plan.library.invocations.filter(e => e.fqname == entry)[0]
let terminal : @protocol.TerminalOutcome = try {
let value = (evaluator.evaluate)(
unit,
suite.calls[0],
manifest.output,
plan.library.invocation_types,
)
match value {
ModelError(code) => ModelError(code)
_ => Returned(value)
}
} catch {
error =>
Failed({
stage: Evaluate,
code: "execution.evaluate",
message: @outcomes.bounded(@scheme.error_message(error), 480),
})
}
let evidence : @protocol.Outcomes = {
calls: [{ id: suite.calls[0].id, outcome: terminal, }],
cleanup: None,
extensions: suite.extensions,
}
@protocol.validate_outcomes(
evidence,
suite,
plan.library.invocations.map(e => @protocol.PublicEntry::{
name: e.fqname,
inputs: e.inputs,
output: e.output,
}),
plan.library.invocation_types,
)
let reply : Json = {
"profile": evaluator.profile,
"terminalOutcomes": evidence.to_json(),
"message": match terminal {
Failed(failure) => failure.code + ": " + failure.message
_ => ""
},
}
@outcomes.prepare(reply, expected_ids=Some(["workbench"])).0
}