The BOSL Language
Business Operating System Language. Language Specification, version bosl/v1.
- Introduction
- 1. Scope
- 2. Conformance
- 3. Normative references
- 4. Terms and definitions
- 5. Notation
- 6. Program structure
- 7. Identity
- 8. States
- 9. Transitions
- 10. Visual definitions
- 11. User interface
- 12. Visibility
- 13. Metadata
- 14. Operations
- 15. Journeys
- 16. Journey nodes
- 17. Journey bindings and references
- 18. Journey conditions and branches
- 19. Events
- 20. Journey controls
- 21. Static semantics
- 22. Execution semantics
- 23. Security
- Annex A. Examples
Introduction
BOSL is the language in which applications for the Business Operating System are written. A BOSL program is a graph. It declares the states an application can be in, the transitions between them, the interface people see in each state, who may see and do what, and the operations and journeys the application can perform. A BOSL program running on the platform is an app. Lead Director and Subscription Director are both apps implemented in BOSL.
BOSL is declarative. A program has no procedures of its own. It states what exists and what is allowed, and a platform interprets it, so the same language describes the behavior, the interface and the permissions of an application, and all of them can be examined before anything runs. Work that acts on the world is an operation, defined outside the language and reached by name. Work that an agent carries out step by step is a journey, which is a smaller graph with its own form.
BOSL takes its syntax from JSON. Every BOSL program is a JSON text, so any JSON parser can read one. BOSL adds no syntax of its own. What it adds is a required structure, and rules that give that structure a meaning.
1. Scope
This specification defines the syntax of BOSL programs and journeys, which share one parser, the rules that determine whether they are valid, and their meaning when they run.
It does not define any particular application, the operations a program may name, how a platform stores or transmits programs, how a client finds operations, or any bound on size or duration.
2. Conformance
A conforming program is a JSON text that matches the syntax in this specification and satisfies the rules in section 21.
A conforming processor accepts conforming programs, rejects every other program before any part of it runs, and carries out accepted programs as section 22 requires. A processor MAY refuse a conforming program for a reason outside this specification, such as a resource bound or an operation the author cannot use, provided it does so before any part of it runs.
The key words MUST, MUST NOT, REQUIRED, SHOULD and MAY are to be interpreted as described in RFC 2119.
3. Normative references
- RFC 8259, The JavaScript Object Notation (JSON) Data Interchange Format.
- RFC 2119, Key words for use in RFCs to Indicate Requirement Levels.
- JSON Schema, draft 2020-12.
- Unicode Standard Annex #15, Unicode Normalization Forms.
4. Terms and definitions
- program
- A BOSL graph: a JSON text whose value is a program object.
- app
- A program running on the platform.
- platform
- The system that interprets programs. A processor is the part of it that validates and executes them.
- organization
- The party for which an app is installed.
- record
- An item an app tracks, such as a lead or a subscriber.
- state
- A named place a record or a user can be, declared in
node_types. - transition
- A declared move from one state to another.
- role
- A named set of permissions held by a user.
- plugin
- A unit of capability declared by a program.
- operation
- A named unit of work defined outside the program.
- contract
- The published declaration of what an operation accepts, produces and requires.
- journey
- A graph of steps carried out by an agent and the service.
- agent
- The party that carries out client nodes of a journey.
- author
- The party that registers a journey, and on whose behalf its runs act.
- run
- One execution of a registered journey.
- visit
- One entry into a journey node during a run.
5. Notation
BOSL's lexical grammar is that of JSON, except that an object MUST NOT repeat a member name. Its syntactic grammar is stated here over JSON values, not over characters. A member whose name begins with an underscore is an annotation: it is valid in any object and has no effect on execution. In an object production, ... after other members means an implementation may accept further members there. In { name : X, ... } it means any number of members of that form.
| Form | Denotes |
|---|---|
{ a : X, b : Y? } | A JSON object with exactly these members, in any order. A member followed by ? is optional. An object MUST NOT have any other member. |
[ X ] | A JSON array in which every element is an X. |
[ X ]+ | A JSON array of at least one X. |
X | Y | Either X or Y. |
"text" | That exact JSON string. |
/pattern/ | A JSON string matching the regular expression in full. |
string, object, integer, boolean, true | The corresponding JSON value. A string in a production MUST NOT be empty. |
positive-integer | An integer greater than zero. |
name | Any member name chosen by the author. |
value | Any JSON value. |
schema | A JSON object that is a JSON Schema. |
6. Program structure
A BOSL program is a single JSON object whose members are its sections, stored in a UTF-8 file with the extension .bosl. Six sections are required for a program to run.
program = { org_id : string, name : string, app_id : string,
metadata : metadata, node_types : [ state ]+, fsm : fsm,
description : string?, version : string?,
ui_config : ui-config?, visibility_rules : visibility? }
| Section | Required | Purpose | Defined in |
|---|---|---|---|
org_id | Yes | The organization the program belongs to. | section 7 |
name | Yes | The application's name. | section 7 |
app_id | Yes | The application's identity. | section 7 |
metadata | Yes | Roles, plugins and operations. | section 13 |
node_types | Yes | The states. | section 8 |
fsm | Yes | The transitions. | section 9 |
description | No | What the application is for. | section 7 |
version | No | The program's version. | section 7 |
ui_config | No | The application's visual definitions. | section 10 |
visibility_rules | No | Which roles see which states. | section 12 |
The interface is declared in each state (section 11). Journeys are declared by plugins or authored by clients (section 15). Sections may appear in any order.
The smallest program that can run has one state and no transitions:
{
"org_id": "00000000-0000-0000-0000-000000000001",
"app_id": "00000000-0000-0000-0000-000000000002",
"name": "Smallest app",
"metadata": { "roles": {}, "operational_metadata": { "known_operations": [] }, "plugins": {} },
"node_types": [
{ "code": "home", "name": "Home", "is_entry_point": true, "is_exit_point": false }
],
"fsm": { "transitions": [] }
}
What a program needs in order to run is stated in section 22.
7. Identity
org_id and app_id identify the organization and the application. name is the title people see. description and version are descriptive and have no effect on execution. A program belongs to one application, and each application has one program for each organization it is installed for.
8. States
node_types declares the states of the application. A state is both a place a record can be and a screen a user can see. An app's sign-in screen, its work queue and a lead's stage in a pipeline are all states.
state = { code : state-code, name : string,
category : string?, description : string?,
status_label : string?,
is_entry_point : boolean?, is_exit_point : boolean?,
is_default : boolean?, is_external_entry : boolean?,
is_positive_goal : boolean?, is_negative_goal : boolean?,
auth_required : boolean?,
queue_assignment : { role : [ string ] }?,
schema : schema?, tabs : tabs?,
ui_tree : component?, attributes : object? }
state-code = /^[a-z][a-z0-9_]*$/
| Member | Meaning |
|---|---|
code | The state's name within the program. |
name, status_label | What people see. |
category | A grouping of states, such as intake or retention. |
is_entry_point | The application or a record can begin here. |
is_exit_point | The record's path ends here. |
is_default | The state used when none is specified. |
is_external_entry | Records can arrive here from outside the app. |
is_positive_goal, is_negative_goal | Reaching this state counts as a success or a loss. |
auth_required | Whether a signed-in user is needed. |
queue_assignment | The queues in which each role sees records in this state. |
schema | A JSON Schema for the data a record holds in this state. |
tabs | The tabs shown in this state. |
ui_tree | The interface for this state (section 11). |
attributes | Further properties of the state, which may also hold its ui_tree. |
9. Transitions
fsm holds the transitions. A record or user moves between states only by a declared transition.
fsm = { transitions : [ transition ], entry_point : state-code?, ... }
transition = { code : string, name : string?,
from_node_type_code : state-code,
to_node_type_code : state-code,
trigger : "manual" | "system"?,
conditions : value?, actions : value?,
requires_plugin : string?,
action_metadata : action-metadata? }
action-metadata = { action_type : action-type,
role_permissions : { role : boolean }?,
system_actor_allowed : boolean?,
label_template : string?, icon : string?, color : string?,
priority : integer?, endpoint : string?,
requires_input : boolean?, form_schema : schema?,
plugin_id : string?, ... }
A transition with the trigger system is taken by the platform. Otherwise it is offered to a user as an action, shown with its label_template, icon and color and ordered by priority. role_permissions states which roles may take it. system_actor_allowed states whether the platform may take it on a role's behalf. A transition that names requires_plugin is available only when that plugin is enabled.
action_type states how the transition is carried out:
| Action type | Meaning |
|---|---|
transition | Move the record immediately. |
transition_with_form | Collect a form, then move the record. |
navigation | Move the user to another state. |
external_action | Perform an action on the user's device, then move the record. |
plugin | Hand the action to a plugin. |
oauth | Sign the user in or connect a provider. |
10. Visual definitions
ui_config declares the values an interface refers to by name, so that an application's appearance is defined in one place.
ui-config = { colors : object?, styles : object?, typography : object?,
icon_library : string?, app_icon : string?, ... }
colors is a tree of named colors and styles a set of named style objects. A style may refer to colors and to other styles. These values are available to every interface in the program.
11. User interface
The interface of a state is a tree of components, declared in its ui_tree. BOSL states what to show and what it does. The platform renders it on each device. An app has no interface code of its own.
component-name = "View" | "SafeAreaView" | "ScrollView" | "Text" | "Icon" | "Image"
| "TouchableOpacity" | "Pressable" | "TextInput" | "Picker" | "Switch"
| "FlatList" | "ActionList" | "Modal" | "ActivityIndicator"
| "Sparkline" | "SelectablePriceGrid"
component = { component : component-name,
style : style?, dynamicStyle : style?,
value : value?, props : object?,
children : [ component ]?,
action : action?,
condition : value?, visible_if : value?,
data : ui-reference?, item_template : component?,
emptyComponent : component? }
style = object | ui-reference
ui-reference = /^\$[a-z_]+(\.[A-Za-z0-9_.-]+)?$/
11.1 Components
| Component | Purpose |
|---|---|
View, SafeAreaView | Contain and lay out children. |
ScrollView | Contain content that scrolls. |
Text | Show value as text. |
Icon, Image | Show an icon or an image. |
TouchableOpacity, Pressable | Perform an action when pressed. |
TextInput, Picker, Switch | Collect a value from the user. |
FlatList | Repeat item_template for each element of data, or show emptyComponent when there are none. |
ActionList | Show the actions available to the user in this state. |
Modal | Show content over the current screen. |
ActivityIndicator | Show that work is in progress. |
Sparkline, SelectablePriceGrid | Show a trend, or a choice of prices. |
11.2 References
A string beginning with $ refers to a value. Interface references are distinct from the journey references of section 17.
| Reference | Value |
|---|---|
$colors.path | A color from ui_config. |
$styles.name | A style from ui_config. |
$node.data.field | A field of the current record. |
$node.status_label, $node.status_color | The display status of the current record. |
$node.items | The items of the current state. |
$item.field | A field of the current list element, also written {item.field}. |
$actions | The actions available in this state. |
$metadata.key | A value from the state's metadata. |
A style object may combine styles with $merge, whose value is a list of styles.
11.3 Actions
action = { action_id : string, action_type : string?,
endpoint : string?, method : string?,
params : object?, payload : object?,
navigate : value?, target : value?,
confirm : value?, collect_form_data : boolean?, ... }
An action is what a component does when used. action_id names it. It may call an endpoint with a method, navigate, or collect the values of the surrounding form with collect_form_data. confirm asks the user first. Whether a user may perform an action is decided by the platform when it is performed (section 22.2), not by the interface showing it.
11.4 Conditional display
A component with condition or visible_if is shown only when that value is present and true.
11.5 Native platform apps
The interface of a native platform app is rendered by the platform's React Native build, not by a browser. A native platform app MUST conform its interface to what that build supports:
- Every component MUST be one the React Native build renders (section 11.1).
- Every
styleMUST use React Native style properties. CSS selectors, stylesheets and HTML are not available. - Every
propsmember MUST be one the component accepts in the React Native build. - An interface MUST NOT depend on behavior that exists only in one environment, because the same program renders on phones and on the web through the same build.
A processor MUST reject a native platform app whose interface uses a component the build does not support.
12. Visibility
visibility_rules states which roles can see each state.
visibility = { state-code : [ role ] | { role_based : boolean, visible_for : [ role ] }, ... }
With role_based false, the state is visible to everyone who can reach it.
13. Metadata
metadata declares who uses the application and what it can do.
tabs = [ { id : string, label : string, ... } ]+ | ui-reference
role-definition = { label : string?, description : string?,
agent_capabilities : [ string ]?, screen : value?, ... }
metadata = { roles : { role : role-definition },
plugins : { string : plugin },
operational_metadata : { known_operations : [ string ] },
tabs : tabs?, ... }
known_operations lists the operations the program uses. tabs defines the app's one tab bar.
13.1 Roles
roles is the only place a program defines roles. A role definition gives its label and description, the screen a user with that role starts on, and agent_capabilities: the capabilities an agent acting in that role holds. An operation's contract states the capability it requires (section 14).
13.2 Plugins
A plugin is a unit of capability, such as a provider connection or a payment service.
plugin = { id : string, name : string, description : string?,
enabled : boolean?, category : string?,
operation_contracts : { operation-name : contract }?,
mcp_settings_schema : schema?, settings_sections : value?,
connection : object?, workflow : object?, ... }
A plugin's operation_contracts publish its operations. mcp_settings_schema and settings_sections declare its settings. connection states how it connects to a provider. workflow declares its setup screens, in the same components as section 11. An app's capabilities are the enabled plugins of its program.
14. Operations
An operation is defined outside the language. BOSL has no way to express what an operation does. A program publishes an operation's contract through a plugin (section 13.2), and a journey names the operation. The contract states what that name means.
operation-name = string
Operation names are dotted, as in subscription-director.products.search. An operation belongs to a plugin, a plugin to an application, and an application is installed for an organization, so the set of operations available depends on the organization, the app and the user.
14.1 Contracts
A contract declares:
- the schema of the operation's inputs, and which classes of binding each input accepts;
- the schema of its outputs;
- its effect;
- whether it requires approval, and what that approval covers;
- the capability an author must hold to use it;
- the errors it can report, and how to recover from them;
- what must be true before it can run;
- the evidence it returns about its outcome.
A program is checked against the contract and never against the operation's implementation.
14.2 Server operations
A contract declares whether its operation is a server operation. Only a server operation can be named by an operating node. Any other operation is called directly, outside the journey. A client that needs its result before a journey starts calls it first and passes the result as a journey input.
14.3 Binding classes
Every binding has a class: literal, journey_input, server_node_output, client_node_output, server_node_error or client_node_error. A contract states the classes it accepts for each input.
This is how an operation controls where its inputs may come from. An input that accepts only literal is fixed when the journey is written. An input that accepts client_node_output can receive an agent's attestation, and one that does not cannot.
14.4 Effects
Each contract declares the effect of its operation. A read observes state and changes nothing. Other effects include create, update, delete and communicate, and a contract may name a more specific one. A program cannot alter an operation's effect.
14.5 Approval
Approval in BOSL is data that flows through the graph. A client node attests it through its approval output, and a later operating node binds that output to an input of its operation:
{ "approval": { "from": "$nodes.review.outputs.approval" } }
An operation that requires approval MUST NOT run without it. Approval covers what the contract says it covers and nothing else.
14.6 Errors and recovery
An operation that fails produces an error object with a code, a message, and an indication of whether it may be retried. A contract may also give a recovery instruction for the agent.
An operation with an external effect MUST NOT be repeated automatically unless its contract permits it. An uncertain outcome MUST NOT be treated as a failure that is safe to repeat.
15. Journeys
A journey is a graph of steps carried out by an agent and the service. Where states and transitions describe how an app stands, a journey describes a piece of work from start to finish. A plugin may publish one, and a client may author one for a particular task.
A journey is a JSON object with five required sections and two optional ones.
journey = { identity : string, name : string, inputs : object,
entry : node-code, nodes : [ node ]+,
bootstrap : bootstrap?, controls : controls? }
identity names the journey. It is compared after normalization to Unicode Normalization Form C and MUST NOT contain control characters. name is a title for people to read. inputs holds the journey's input values, each a plain JSON value. entry names the node at which a run begins. Execution order comes only from entry and from each node's successors, so the order of nodes has no meaning.
A journey's hash is the Merkle sum of its node hashes. Each node's hash is the SHA-256 of its canonical JSON text. The node hashes are sorted, and adjacent pairs are hashed in turn until one remains. Only the contents of nodes take part, so identity, name and inputs do not change a journey's hash. Two journeys with the same hash are the same job.
16. Journey nodes
A journey has two types of node, named for the party that carries them out.
node = client-node | server-node
server-node = operating-node | terminal-node
client-node = { code : node-code, type : "client",
instruction : instruction,
inputs : bindings, outputs : outputs,
successor, catch : branch?, max_visits : positive-integer? }
operating-node
= { code : node-code, type : "server",
operation : operation-name,
inputs : bindings, outputs : outputs,
successor, catch : branch?, max_visits : positive-integer? }
terminal-node
= { code : node-code, type : "server", terminal : true,
inputs : bindings, outputs : outputs,
catch : branch?, max_visits : positive-integer? }
successor = next : node-code | transitions : branch
instruction = { goal : string, message : string }
bindings = { name : binding, ... }
outputs = { name : schema, ... }
node-code = /^[a-z][a-z0-9_]*$/
successor stands for exactly one of the two members it names. A node's code names it within the journey. max_visits is the number of times one run may enter the node. When it is absent, that number is one.
16.1 Client nodes
A client node is an instruction to the agent. goal states the purpose in a short identifying form and message states it in words. The node's resolved inputs accompany the instruction. Input names MUST NOT be goal, message, after_success or on_failure: the first two are the instruction itself, and the processor uses the last two to give the agent its actions for reporting completion and failure.
A client node's outputs are attestations, not data. An agent can state that something was done or agreed, but it cannot supply values for later steps through a client node. Output names are limited to these:
| Output | Attests | Schema |
|---|---|---|
approval | The user approved. | The attestation schema below. |
confirmation | The user confirmed. | The attestation schema below. |
acknowledgment | The user acknowledged. | The attestation schema below. |
failure | The step failed. | A closed object requiring code, one of USER_STOPPED or CLIENT_STEP_FAILED, and message, with optional details of scalar values. |
The attestation schema is this exact schema, in which confirmed can only be true:
{ "type": "object", "required": ["confirmed"],
"properties": { "confirmed": { "const": true } },
"additionalProperties": false }
16.2 Operating nodes
An operating node asks the service to perform an operation. Its inputs and outputs are determined by that operation's contract (section 14).
16.3 Terminal nodes
Terminal is not a third type of node. A terminal node is a server node that declares terminal : true, and reaching one ends the run. It has no operation and no successor.
A journey usually ends at a terminal node with empty inputs and outputs. A terminal node with outputs produces the outcome of the run. Either its output names equal its input names, or it has one output whose schema declares a property for every input and requires only inputs.
17. Journey bindings and references
A binding supplies the value of a node input.
binding = { value : value }
| { from : reference, optional : true? }
reference = /^\$journey\.inputs\.[A-Za-z_][A-Za-z0-9_]*$/
| /^\$nodes\.[a-z][a-z0-9_]*\.outputs\.[A-Za-z_][A-Za-z0-9_]*$/
| /^\$nodes\.[a-z][a-z0-9_]*\.error$/
| /^\$outputs\.[A-Za-z_][A-Za-z0-9_]*$/
| /^\$error\.[A-Za-z_][A-Za-z0-9_]*$/
| /^\$event\.fields\.[a-z][a-z0-9_.]*$/
{ value : v } supplies v literally. { from : r } supplies the value of reference r. With optional : true, the input is omitted when r has no value.
| Reference | Denotes | Appears in |
|---|---|---|
$journey.inputs.name | A journey input. | Bindings |
$nodes.code.outputs.name | That output from the latest successful visit to the node that produced it. | Bindings |
$nodes.code.error | The error from the latest failed visit to the node. | Bindings |
$outputs.name | An output of the current node. | transitions |
$error.field | A field of the current node's error. | catch |
$event.fields.path | A field of the event being matched. | A callback's match and dedupe_on |
18. Journey conditions and branches
branch = { cases : [ case ], default : node-code }
case = { when : condition, next : node-code }
condition = predicate
| { all : [ predicate ]+ }
| { any : [ predicate ]+ }
predicate = { left : operand, op : comparator, right : operand? }
operand = { from : reference } | { value : value }
comparator = "eq" | "neq" | "gt" | "gte" | "lt" | "lte"
| "exists" | "not_exists"
Conditions do not nest. A predicate has a left and, except with exists and not_exists, a right.
An operand has a value if it is a literal, or if its reference has one. Predicates evaluate as follows:
| Comparator | True when |
|---|---|
exists | left has a value. |
not_exists | left has no value. |
eq | Both operands have values, and the values are equal. |
neq | Both operands have values, and the values differ. |
gt, gte, lt, lte | Both operands have values of an ordered type, and the ordering holds. Numbers and strings are ordered. |
It follows that a comparison with a missing operand is false, including neq. all is true when every predicate is true. any is true when at least one is.
A branch evaluates its cases in array order and selects the next of the first case whose condition is true. If none is true, it selects default.
19. Events
A journey can wait for something to happen outside it. Its optional bootstrap section declares what it may wait for, and it waits at an ordinary operating node.
bootstrap = { callbacks : [ callback ]+, activate_on : callback-code? }
callback = { code : callback-code, source : source,
inputs : object, match : condition,
dedupe_on : [ event-field ]+?,
retry : retry?, dead_letter : dead-letter? }
retry = { max_attempts : positive-integer, backoff : "exponential",
max_delay_seconds : positive-integer }
dead-letter = { queue : "journey", retention_seconds : positive-integer }
callback-code = /^[a-z][a-z0-9_]*$/
source = /^[a-z0-9_-]+(\.[a-z0-9_-]+)+$/
event-field = /^\$event\.fields\.[a-z][a-z0-9_.]*$/
A callback names an event source and states, in match, which events it accepts. Its operands use the event reference of section 17. dedupe_on names the fields that identify a delivery, so a repeated delivery cannot advance a run twice. retry and dead_letter state what happens to a delivery that cannot be processed.
The operation bosl.callbacks.await waits for an event. It is defined by this specification and takes three inputs, each a literal. callback is REQUIRED and names a declared callback. ttl_seconds is OPTIONAL: a positive integer of seconds. Without it, the processor applies its own default. A processor may grant a shorter wait than requested. match is OPTIONAL: a condition that narrows the wait further. Its operands may refer to the event and to outputs of earlier nodes that every path to the wait has passed through. It produces a reference to the captured event, an object with kind equal to callback_event and an opaque reference. If the wait expires, the node fails with the code CALLBACK_WAIT_EXPIRED.
Starting a run establishes the point after which events are captured. A run captures each event at most once. With activate_on, which names a declared callback, each matching event starts a new run instead.
20. Journey controls
A journey's optional controls section states which roles may direct a run.
controls = { actions : [ action ]+ }
action = { verb : verb, roles : [ role ]+ }
verb = "start" | "stop" | "retire" | "complete" | "step" | "failed"
role = /^[a-z][a-z0-9_-]*$/
An action states the roles required to perform a lifecycle verb. Every role in one action is required. Several actions for the same verb are alternatives. Roles govern who may direct a run. They do not change what the run's operations may do.
21. Static semantics
21.1 Programs
A program that matches the syntax above is valid only if it also satisfies these rules.
- State codes are unique within a program, and transition codes are unique within it.
- At least one state is an entry point.
- Every transition's
from_node_type_codeandto_node_type_codename a state. - Every role named in a state, transition or visibility rule is defined in
metadata.roles. - A transition's
requires_pluginnames a plugin inmetadata.plugins. - Every interface reference resolves to a declared value.
- Every component names a component in section 11.1, and a native platform app conforms to section 11.5.
21.2 Journeys
A journey that matches the syntax above is valid only if it also satisfies these rules, which are decidable from the journey and the contracts it names, without running it.
- Node codes are unique within a journey, and callback codes are unique within
bootstrap. entry, everynextand everydefaultname a node in the journey.- Every node is reachable from
entry, following successors andcatch. - Every cycle in the graph contains a node that declares
max_visits. This makes every run finite. To repeat a cycle, each node on it needs amax_visitslarge enough for the repetitions, because a node without one can be entered only once. - A
$journey.inputsreference names a member of the journey'sinputs. - A
$nodesoutput reference names a declared output of a node in the journey, and a$nodeserror reference names a node in the journey. - A binding uses only
$journeyand$nodesreferences. A condition intransitionsuses only$outputsreferences, naming declared outputs of its node. A condition incatchuses only$errorreferences, naming fields of the node's error.$event.fieldsreferences appear only in acallbackand in thematchof a wait. - A predicate with
existsornot_existshas noright. Every other predicate has one. A literalrightis valid for the schema of itsleft, a referencedrighthas a compatible type, and an ordered comparison compares two numbers or two strings. - An operating node names a server operation that is available to the author. It supplies every required input and no undeclared one, and each input uses a binding class the contract accepts. Each literal is valid for its input's schema, and each reference has a compatible schema.
- An operating node's
outputshave the same names as its contract's outputs, with equivalent schemas. activate_onand thecallbackinput of a wait name declared callbacks. A wait requiresbootstrap.- A journey contains nothing prohibited by section 23.
22. Execution semantics
22.1 Requirements to run
A program runs as an app only if it has all six required sections (section 6), it satisfies section 21, and it is installed for an organization.
22.2 States and transitions
Each record is in exactly one state. A user sees the interface of the state they are in, subject to visibility_rules. The actions offered are the transitions out of that state that the user's role may take and whose plugins are enabled.
A transition is checked when it is performed: the user's current role MUST permit it, or it MUST be a system transition. On success, the record is in the new state. A record moves only by a declared transition. Interface and agent requests get the same check, so what a person can do and what an agent can do are the same.
22.3 Journey registration
A journey runs only if it is valid (section 21.2), each operation it names is available to its author and ready, each event source it declares is ready, and it has been registered and explicitly started. Registration presents a journey to a processor, which validates it. Registration performs no effects.
A job has one current run. Registering a journey with the same hash as an existing job of the same author replaces that job: its current run ends, its record is kept, and the job keeps its identity. Each run is a distinct execution of the job. Spend against a job's budget carries across its runs. How long a run may last is set by the limits of the organization and its subscription.
22.4 Journey runs
A run has a current node and an ordered history of visits. Starting a run sets the current node to entry. The processor then repeats these steps:
- If the current node has been visited as many times as its
max_visitsallows, the run fails. Otherwise, record a visit. - Resolve the node's bindings. If a binding that is not optional has no value, the run fails.
- If the node is a terminal node, compute its outcome and end the run.
- If it is a client node, give the instruction to the agent and wait. The agent reports completion or failure. The agent reports what happened but does not choose the next step.
- If it is an operating node, perform its operation.
- If the node succeeded, the next node is its
next, or the result of evaluatingtransitionsagainst its outputs. - If the node failed and has a
catch, the next node is the result of evaluating it against the error. If it has none, the run fails. A failure with the codeUSER_STOPPEDnever enterscatch.
A run that has failed stays failed.
22.5 Authority
A run acts on behalf of its author. Whether an operation may run is determined by the author's current standing, not by the journey, and it is determined each time the operation runs. A journey cannot grant itself access, and a run's agent cannot lend it any.
23. Security
A journey describes work. It is not a place for authority, code or secrets, and a processor MUST reject a journey that tries to use it as one. A program's interface grants no permissions: what a user may do is decided by the platform, whatever a screen shows.
- Authority. A journey MUST NOT carry anything that selects an identity, an organization, a role, an installation or a credential.
- Code. A journey MUST NOT carry handlers, scripts, queries or transport locations. The
callbackinput ofbosl.callbacks.awaitis a name, not an exception to this rule. - Secrets. A journey MUST NOT contain credentials or key material.
- Instructions. An
instructionis read by an agent, so a processor MUST treat it as untrusted. It MUST NOT contain links, file paths, embedded markup, requests to run code, or text directing the agent to set aside its instructions.
Annex A. Examples
This annex is informative. Each example is a conforming journey.
A.1 Acknowledge one result
The smallest useful journey: one client node and one terminal node.
{
"identity": "example:acknowledgment",
"name": "Acknowledge one result",
"inputs": {
"result_message": "Ready"
},
"entry": "acknowledge",
"nodes": [
{
"code": "acknowledge",
"type": "client",
"instruction": {
"goal": "acknowledge_result",
"message": "Show the result and obtain acknowledgment."
},
"inputs": {
"result_message": {
"from": "$journey.inputs.result_message"
}
},
"outputs": {
"acknowledgment": {
"type": "object",
"required": [
"confirmed"
],
"properties": {
"confirmed": {
"const": true
}
},
"additionalProperties": false
}
},
"next": "completed"
},
{
"code": "completed",
"type": "server",
"terminal": true,
"inputs": {},
"outputs": {}
}
]
}
A.2 Wait for one event
A journey that waits for an event, with separate terminal nodes for success, expiry and failure.
{
"identity": "example:configured-gmail-callback",
"name": "Wait for one configured Gmail event",
"inputs": {},
"bootstrap": {
"callbacks": [
{
"code": "mail",
"source": "gmail.trigger",
"inputs": {},
"match": {
"left": {
"from": "$event.fields.subject"
},
"op": "exists"
}
}
]
},
"entry": "wait_for_mail",
"nodes": [
{
"code": "wait_for_mail",
"type": "server",
"operation": "bosl.callbacks.await",
"inputs": {
"callback": {
"value": "mail"
},
"ttl_seconds": {
"value": 60
}
},
"outputs": {
"event": {
"type": "object",
"required": [
"kind",
"reference"
],
"properties": {
"kind": {
"const": "callback_event"
},
"reference": {
"type": "string",
"pattern": "^bosevt_[A-Za-z0-9_-]{32}$"
}
},
"additionalProperties": false
}
},
"next": "completed",
"catch": {
"cases": [
{
"when": {
"left": {
"from": "$error.code"
},
"op": "eq",
"right": {
"value": "CALLBACK_WAIT_EXPIRED"
}
},
"next": "wait_expired"
}
],
"default": "wait_failed"
}
},
{
"code": "completed",
"type": "server",
"terminal": true,
"inputs": {},
"outputs": {}
},
{
"code": "wait_expired",
"type": "server",
"terminal": true,
"inputs": {},
"outputs": {}
},
{
"code": "wait_failed",
"type": "server",
"terminal": true,
"inputs": {},
"outputs": {}
}
]
}
A.3 Observe one result
A journey with no external effect.
{
"identity": "example:notification-observation",
"name": "Observe one no-send journey result",
"inputs": {
"result_message": "The no-send journey result was observed."
},
"entry": "observe_result",
"nodes": [
{
"code": "observe_result",
"type": "client",
"instruction": {
"goal": "observe_journey_result",
"message": "Read this harmless journey result to verify notification delivery. No customer or provider action is required."
},
"inputs": {
"result_message": {
"from": "$journey.inputs.result_message"
}
},
"outputs": {},
"next": "completed"
},
{
"code": "completed",
"type": "server",
"terminal": true,
"inputs": {},
"outputs": {}
}
]
}