Finding codes¶
Every finding the Rhylthyme validator reports has a code, a message, a where (the step, track or path it is about) and a fix. This page lists every code: what it means, a program that triggers it, and how to fix it. The codes are those of validateProgram in @rhylthyme/timeline/schedule.
The hosted MCP server
The MCP tool validate_program and the checks visualize_schedule runs before it publishes still use the server's own copy of the validator. It reports many of the codes below, and also these, which this page does not list: missing_program_id, missing_name, no_tracks, bad_track, missing_track_id, missing_track_name, bad_step, missing_step_id, missing_step_name, missing_start_trigger, bad_duration_type, fixed_without_seconds, variable_without_bounds, indefinite_without_default, choice_too_few_options and bad_alert. validateProgram reports each of those problems as a schema_* finding instead (they were removed from it in 2.0.0-beta.7).
Errors make a program invalid; warnings and info notes do not. The Python column says what the rhylthyme package's validator reports for the same program: rhylthyme validate --json (the CLI, which checks the program as written) and validate_program_structured (the library, which expands replicates first). It reports schema problems as one schema_error and has no code of its own for many logic checks.
This page is generated from the catalogue that @rhylthyme/timeline ships (findingCodes, version 2.0.0-beta.7 here) by tools/gen-finding-codes.js; a test fails when it falls behind.
All codes¶
- Schema:
not_an_object,not_json,schema_type,schema_enum,schema_const,schema_required,schema_additionalProperties,schema_minItems,schema_maxItems,schema_minLength,schema_maxLength,schema_pattern,schema_minimum,schema_maximum,schema_exclusiveMinimum,schema_exclusiveMaximum,schema_oneOf,schema_anyOf,schema_not,schema_false - Structure and references:
program_id_format,missing_schema_version,empty_track,duplicate_track_id,duplicate_step_id,compound_without_triggers,bad_trigger_logic,bad_trigger,unknown_trigger_type,trigger_without_step_id,dangling_step_ref,self_reference,buffer_without_seconds,bad_trigger_event,offset_without_seconds,choice_ref_no_choice,choice_ref_unknown - Timing and overlap:
missing_duration,unparseable_duration,variable_min_gt_max,variable_without_default,negative_offset_on_fixed,dependency_cycle,track_overlap,tracks_finish_far_apart - Resources:
bad_resource_constraint,duplicate_constraint,bad_max_concurrent,task_not_constrained,unused_constraint - Instances and replicates:
E_INSTANCES_ON_SINGLE,E_EACH_WITH_REPLICATES,E_EACH_COUNT_MISMATCH,E_INFLIGHT_GT_COUNT,E_INFLIGHT_NO_CHAIN,W_UNBARRIERED_CHAIN,I_IMPLICIT_BARRIER,expansion_failed - Alerts:
E_ALERT_BAD_OFFSET,W_ALERT_BEFORE_MANUAL_START,W_ALERT_BEFORE_PROGRAM_START,W_ALERT_BEFORE_INDEFINITE_END - Instruments:
I_INSTRUMENT_DURATION,instrument_bad_shape,instrument_no_end,instrument_unchecked,instrument_unknown_tool_type,instrument_invalid_command,instrument_over_limit,instrument_returns_early,instrument_dose_outlasts_step,instrument_catalog_not_loaded
Base program¶
Each example below is a fragment: its top-level keys replace those of this valid program, which has no findings at all.
{
"programId": "example",
"name": "Example",
"schemaVersion": "0.2.0-alpha",
"tracks": [
{
"trackId": "main",
"name": "Main",
"steps": [
{
"stepId": "prep",
"name": "Prep",
"duration": { "type": "fixed", "seconds": 300 },
"startTrigger": { "type": "programStart" }
},
{
"stepId": "cook",
"name": "Cook",
"duration": { "type": "fixed", "seconds": 600 },
"startTrigger": { "type": "afterStep", "stepId": "prep" }
}
]
}
],
"resourceConstraints": []
}
Schema¶
The program does not match the JSON Schema its schemaVersion selects (rhylthyme-spec's, bundled), or cannot be checked against it.
| Code | Severity | Summary | Python |
|---|---|---|---|
not_an_object | error | The program is not a JSON object. | no |
not_json | error | The program cannot be written as JSON, so the schema was not checked. | no |
schema_type | error | A value has the wrong JSON type. | schema_error |
schema_enum | error | A value is not one of the allowed choices. | schema_error |
schema_const | error | A value is not the one fixed value the schema allows. (not reachable today) | schema_error |
schema_required | error | A required property is missing. | schema_error |
schema_additionalProperties | error | An object has a property the schema does not allow. | schema_error |
schema_minItems | error | A list has too few items. | schema_error |
schema_maxItems | error | A list has too many items. (not reachable today) | schema_error |
schema_minLength | error | A string is shorter than allowed (usually empty). | schema_error |
schema_maxLength | error | A string is longer than allowed. | schema_error |
schema_pattern | error | A string does not match the required pattern. | schema_error |
schema_minimum | error | A number is below the minimum. | schema_error |
schema_maximum | error | A number is above the maximum. | schema_error |
schema_exclusiveMinimum | error | A number must be greater than a bound and is not. | schema_error |
schema_exclusiveMaximum | error | A number must be less than a bound and is not. (not reachable today) | schema_error |
schema_oneOf | error | A value matches none, or more than one, of the allowed shapes. | schema_error |
schema_anyOf | error | A value matches none of the alternatives it may take. | schema_error |
schema_not | error | A value has a combination of keys the schema forbids. | schema_error |
schema_false | error | A value is present where the schema allows nothing. (not reachable today) | schema_error |
not_an_object¶
Error. The program is not a JSON object.
validateProgram was given an array, a string, a number, null or another non-object. Nothing else is checked: the result has this one error and stats null.
Example. Passed to validateProgram as it is:
The whole value passed to validateProgram, not a fragment.
Fix. Pass the full program object ({"programId": ..., "tracks": [...]}), not a string, an array or a wrapper around it.
Python. Python reports nothing for it. Python raises on a program that is not an object instead of reporting a finding.
not_json¶
Error. The program cannot be written as JSON, so the schema was not checked.
A JavaScript caller passed a value JSON cannot carry: a circular reference, a BigInt or similar. The JSON Schema check is skipped and this finding is reported in its place; the logic checks still run. Programs read from JSON text never get it.
Example. Merged over the base program:
metadata.bytes is passed as a JavaScript BigInt, which no JSON text can carry.
Fix. Pass plain JSON data: no circular references, BigInt values, functions or class instances.
Python. Python reports nothing for it. Python always validates parsed JSON.
schema_type¶
Error. A value has the wrong JSON type.
The schema expects a different type at this path: a string for a name, an integer for maxConcurrent, an object for a duration. Numbers written in quotes ("2") are strings. A value of the wrong type is reported once, not again by the oneOf/anyOf beside it.
Example. Merged over the base program:
{
"tracks": [
{
"trackId": "main",
"name": "Main",
"steps": [
{
"stepId": "prep",
"name": 7,
"duration": { "type": "fixed", "seconds": 300 },
"startTrigger": { "type": "programStart" }
}
]
}
]
}
Fix. Write the value as the type the message names; for a quoted number, drop the quotes.
Python. Python reports schema_error. Python reports the first schema violation only, as one schema_error whose message is jsonschema's.
schema_enum¶
Error. A value is not one of the allowed choices.
The field accepts a fixed set of strings (an alert's level, a replicates mode, a trigger's event). Choices that tell apart the shapes of a oneOf, such as a duration's type, are reported as schema_oneOf instead.
Example. Merged over the base program:
{
"tracks": [
{
"trackId": "main",
"name": "Main",
"steps": [
{
"stepId": "prep",
"name": "Prep",
"duration": { "type": "fixed", "seconds": 300 },
"startTrigger": { "type": "programStart" },
"alerts": [{ "event": "end", "level": "loud" }]
}
]
}
]
}
Fix. Use one of the values listed in the message.
Python. Python reports schema_error. Python reports the first schema violation only, as one schema_error whose message is jsonschema's.
schema_const¶
Error. A value is not the one fixed value the schema allows.
A const keyword pins a field to one value.
Not reachable today
The bundled 0.3.0-alpha schema uses const only inside an if (metadata.varianceFactors), where a mismatch selects the other branch instead of failing, and 0.2.0-alpha does not use it. The checker supports it so that a schema update that starts using it keeps working; test/finding-codes.test.js fails when that happens, asking for an example here.
Fix. Set the field to the value the message names.
Python. Python reports schema_error. Python reports the first schema violation only, as one schema_error whose message is jsonschema's.
schema_required¶
Error. A required property is missing.
Every program needs programId, name, tracks and resourceConstraints (or environment / environmentType); every track a trackId, name and steps; every step a stepId, name and startTrigger; every resource constraint a task and maxConcurrent. A missing startTrigger's fix names the step to chain after.
Example. Merged over the base program:
{
"tracks": [
{
"trackId": "main",
"steps": [
{
"stepId": "prep",
"name": "Prep",
"duration": { "type": "fixed", "seconds": 300 },
"startTrigger": { "type": "programStart" }
}
]
}
]
}
Fix. Add the property the message names.
Python. Python reports schema_error. Python reports the first schema violation only, as one schema_error whose message is jsonschema's.
schema_additionalProperties¶
Error. An object has a property the schema does not allow.
Some objects are closed (instrument, instrument actions, alerts, ...): any key besides the listed ones is an error, usually a typo. Programs, tracks, steps and triggers are open and accept extra keys.
Example. Merged over the base program:
{
"tracks": [
{
"trackId": "main",
"name": "Main",
"steps": [
{
"stepId": "prep",
"name": "Prep",
"duration": { "type": "fixed", "seconds": 300 },
"startTrigger": { "type": "programStart" },
"instrument": {
"tool": "shaker",
"toolType": "bioshake",
"command": "start_shake",
"params": { "speed": 1000, "duration": 300 },
"retries": 2
}
}
]
}
]
}
Fix. Remove the key or fix its spelling; the fix lists the keys allowed there.
Python. Python reports schema_error. Python reports the first schema violation only, as one schema_error whose message is jsonschema's.
schema_minItems¶
Error. A list has too few items.
Lists such as a compound trigger's triggers (at least 2), an instrument's start/end actions and a step's alerts must not be empty or too short.
Example. Merged over the base program:
{
"tracks": [
{
"trackId": "main",
"name": "Main",
"steps": [
{
"stepId": "prep",
"name": "Prep",
"duration": { "type": "fixed", "seconds": 300 },
"startTrigger": { "type": "programStart" },
"alerts": []
}
]
}
]
}
Fix. Add items, or remove the optional list entirely.
Python. Python reports schema_error. Python reports the first schema violation only, as one schema_error whose message is jsonschema's.
schema_maxItems¶
Error. A list has too many items.
A maxItems keyword caps a list's length.
Not reachable today
No bundled program schema (0.2.0-alpha, 0.3.0-alpha) uses maxItems where a failure is reported, so validateProgram cannot emit this code today. The checker supports it so that a schema update that starts using it keeps working; test/finding-codes.test.js fails when that happens, asking for an example here.
Fix. Shorten the list.
Python. Python reports schema_error. Python reports the first schema violation only, as one schema_error whose message is jsonschema's.
schema_minLength¶
Error. A string is shorter than allowed (usually empty).
Names that must not be empty: an instrument's tool and command, an alert's message.
Example. Merged over the base program:
{
"tracks": [
{
"trackId": "main",
"name": "Main",
"steps": [
{
"stepId": "prep",
"name": "Prep",
"duration": { "type": "fixed", "seconds": 300 },
"startTrigger": { "type": "programStart" },
"instrument": { "tool": "", "command": "start_shake" }
}
]
}
]
}
Fix. Give the string a value, or remove the optional field.
Python. Python reports schema_error. Python reports the first schema violation only, as one schema_error whose message is jsonschema's.
schema_maxLength¶
Error. A string is longer than allowed.
An alert's message is at most 200 characters (code points, as Python counts them).
Example. Merged over the base program:
{
"tracks": [
{
"trackId": "main",
"name": "Main",
"steps": [
{
"stepId": "prep",
"name": "Prep",
"duration": { "type": "fixed", "seconds": 300 },
"startTrigger": { "type": "programStart" },
"alerts": [
{
"event": "end",
"message": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}
]
}
]
}
]
}
Fix. Shorten the string to the length the message names.
Python. Python reports schema_error. Python reports the first schema violation only, as one schema_error whose message is jsonschema's.
schema_pattern¶
Error. A string does not match the required pattern.
Schema 0.3.0-alpha constrains a few identifiers: metadata.varianceFactors keys (a letter, then letters, digits, _ and -) and a duration's calibratedFrom.programVersion ("sha256:" and 64 hex digits; inside a duration it is reported as schema_oneOf).
Example. Merged over the base program:
{
"schemaVersion": "0.3.0-alpha",
"metadata": { "varianceFactors": [{ "key": "2nd oven", "type": "number" }] }
}
Fix. Make the string match the pattern in the message.
Python. Python reports schema_error. Python reports the first schema violation only, as one schema_error whose message is jsonschema's.
schema_minimum¶
Error. A number is below the minimum.
maxConcurrent is at least 1; durations and their bounds are at least 0.
Example. Merged over the base program:
Fix. Use a value at or above the minimum the message names.
Python. Python reports schema_error. Python reports the first schema violation only, as one schema_error whose message is jsonschema's.
schema_maximum¶
Error. A number is above the maximum.
A taskResources fraction (the share of a resource a step occupies) is at most 1.
Example. Merged over the base program:
{
"tracks": [
{
"trackId": "main",
"name": "Main",
"steps": [
{
"stepId": "prep",
"name": "Prep",
"duration": { "type": "fixed", "seconds": 300 },
"startTrigger": { "type": "programStart" },
"taskResources": [{ "name": "oven", "fraction": 1.5 }]
}
]
}
]
}
Also emits task_not_constrained for the oven.
Fix. Use a value at or below the maximum the message names.
Python. Python reports schema_error. Python reports the first schema violation only, as one schema_error whose message is jsonschema's.
schema_exclusiveMinimum¶
Error. A number must be greater than a bound and is not.
An instrument's or action's timeoutSeconds must be greater than 0.
Example. Merged over the base program:
{
"tracks": [
{
"trackId": "main",
"name": "Main",
"steps": [
{
"stepId": "prep",
"name": "Prep",
"duration": { "type": "fixed", "seconds": 300 },
"startTrigger": { "type": "programStart" },
"instrument": {
"tool": "shaker",
"toolType": "bioshake",
"command": "start_shake",
"params": { "speed": 1000, "duration": 300 },
"timeoutSeconds": 0
}
}
]
}
]
}
Fix. Use a value greater than the bound, or remove the optional field.
Python. Python reports schema_error. Python reports the first schema violation only, as one schema_error whose message is jsonschema's.
schema_exclusiveMaximum¶
Error. A number must be less than a bound and is not.
An exclusiveMaximum keyword caps a number below a bound.
Not reachable today
No bundled program schema (0.2.0-alpha, 0.3.0-alpha) uses exclusiveMaximum where a failure is reported, so validateProgram cannot emit this code today. The checker supports it so that a schema update that starts using it keeps working; test/finding-codes.test.js fails when that happens, asking for an example here.
Fix. Use a value below the bound.
Python. Python reports schema_error. Python reports the first schema violation only, as one schema_error whose message is jsonschema's.
schema_oneOf¶
Error. A value matches none, or more than one, of the allowed shapes.
Durations, start triggers and the program's own startTrigger have several shapes told apart by their type. A value that fits none gets the closest shape's first problem (or the list of allowed types); a value that fits two (a duration with both seconds and timeString) is ambiguous.
Example. Merged over the base program:
{
"tracks": [
{
"trackId": "main",
"name": "Main",
"steps": [
{
"stepId": "prep",
"name": "Prep",
"duration": { "type": "fixd", "seconds": 300 },
"startTrigger": { "type": "programStart" }
}
]
}
]
}
Fix. Follow the fix: set type to an allowed value, add the keys the closest shape needs, or remove the keys that make it ambiguous.
Python. Python reports schema_error. Python reports the first schema violation only, as one schema_error whose message is jsonschema's.
schema_anyOf¶
Error. A value matches none of the alternatives it may take.
A program needs resourceConstraints, environment or environmentType; an instrument needs a command, an until, or start/end actions. The message lists the alternatives.
Example. Merged over the base program, without resourceConstraints:
Fix. Add one of the alternatives the message lists.
Python. Python reports schema_error. Python reports the first schema violation only, as one schema_error whose message is jsonschema's.
schema_not¶
Error. A value has a combination of keys the schema forbids.
An instrument has both command and until (only one call can end the step); a program has resourceConstraints together with environment or environmentType.
Example. Merged over the base program:
{
"tracks": [
{
"trackId": "main",
"name": "Main",
"steps": [
{
"stepId": "prep",
"name": "Prep",
"duration": { "type": "fixed", "seconds": 300 },
"startTrigger": { "type": "programStart" },
"instrument": {
"tool": "shaker",
"command": "start_shake",
"until": { "command": "wait" }
}
}
]
}
]
}
Also emits instrument_bad_shape.
Fix. Keep only one of the keys the message names.
Python. Python reports schema_error. Python reports the first schema violation only, as one schema_error whose message is jsonschema's.
schema_false¶
Error. A value is present where the schema allows nothing.
A false subschema forbids any value at that path.
Not reachable today
No bundled program schema (0.2.0-alpha, 0.3.0-alpha) uses a false subschema (a boolean schema) where a failure is reported, so validateProgram cannot emit this code today. The checker supports it so that a schema update that starts using it keeps working; test/finding-codes.test.js fails when that happens, asking for an example here.
Fix. Remove the value.
Python. Python reports schema_error. Python reports the first schema violation only, as one schema_error whose message is jsonschema's.
Structure and references¶
Ids, triggers and the references between steps.
| Code | Severity | Summary | Python |
|---|---|---|---|
program_id_format | warning | programId is not kebab-case. | no |
missing_schema_version | warning | schemaVersion is missing. | no |
empty_track | warning | A track has no steps; it is left out. | schema_error (CLI), none (library) |
duplicate_track_id | error | Two tracks share a trackId. | no |
duplicate_step_id | error | A stepId is used more than once. | duplicate_step_id |
compound_without_triggers | error | A trigger has logic but no triggers list. | no |
bad_trigger_logic | error | A trigger's logic is not "all" or "any". | no |
bad_trigger | error | A trigger inside a compound trigger is not an object. (not reachable today) | no |
unknown_trigger_type | error | A trigger's type is not one the runner knows. | no |
trigger_without_step_id | error | An afterStep, afterStepWithBuffer or onAbort trigger has an empty stepId. | dangling_step_ref |
dangling_step_ref | error | A trigger waits for a step that does not exist. | dangling_step_ref |
self_reference | error | A step waits for itself. | no |
buffer_without_seconds | error | An afterStepWithBuffer trigger has no bufferSeconds. | no |
bad_trigger_event | error | A trigger's event is not "start" or "end". | no |
offset_without_seconds | error | A programStartOffset trigger has no offsetSeconds. | no |
choice_ref_no_choice | error | A trigger's choiceId refers to a step that offers no choice. | choice_ref |
choice_ref_unknown | error | A trigger's choiceId is not one of the referenced step's options. | choice_ref |
program_id_format¶
Warning. programId is not kebab-case.
programId should start with a letter or digit and contain only letters, digits, dashes and underscores, so it is safe in URLs and file names.
Example. Merged over the base program:
Fix. Use lowercase letters, digits and dashes, e.g. "my-program".
Python. Python reports nothing for it.
missing_schema_version¶
Warning. schemaVersion is missing.
Without schemaVersion the program is checked against the 0.2.0-alpha schema and the viewer assumes 0.1.0. instances triggers and maxInFlight need 0.3.0-alpha.
Example. Merged over the base program, without schemaVersion:
Fix. Set schemaVersion: "0.1.0", "0.2.0-alpha" (choice branching) or "0.3.0-alpha" (instances on triggers).
Python. Python reports nothing for it.
empty_track¶
Warning. A track has no steps; it is left out.
A track whose steps list is missing or empty (and that names no templateId) is dropped before the other checks, as Python's expander drops it.
Example. Merged over the base program:
{
"tracks": [
{
"trackId": "main",
"name": "Main",
"steps": [
{
"stepId": "prep",
"name": "Prep",
"duration": { "type": "fixed", "seconds": 300 },
"startTrigger": { "type": "programStart" }
}
]
},
{ "trackId": "idle", "name": "Idle", "steps": [] }
]
}
Fix. Add steps to the track or remove it.
Python. rhylthyme validate reports schema_error, as an error; validate_program_structured reports nothing. rhylthyme validate checks the program as written, where an empty steps list breaks the schema; validate_program_structured drops the track while expanding, as JS does, and says nothing.
duplicate_track_id¶
Error. Two tracks share a trackId.
trackIds must be unique: the renderer, the runner and replicate expansion address tracks by id.
Example. Merged over the base program:
{
"tracks": [
{
"trackId": "main",
"name": "Main",
"steps": [
{
"stepId": "prep",
"name": "Prep",
"duration": { "type": "fixed", "seconds": 300 },
"startTrigger": { "type": "programStart" }
}
]
},
{
"trackId": "main",
"name": "Main",
"steps": [
{
"stepId": "cook",
"name": "Cook",
"duration": { "type": "fixed", "seconds": 300 },
"startTrigger": { "type": "programStart" }
}
]
}
]
}
Fix. Give every track its own trackId.
Python. Python reports nothing for it.
duplicate_step_id¶
Error. A stepId is used more than once.
stepIds must be unique across the whole program, not only within a track, since triggers refer to steps by id alone. Replicate copies get ids "
Example. Merged over the base program:
{
"tracks": [
{
"trackId": "main",
"name": "Main",
"steps": [
{
"stepId": "prep",
"name": "Prep",
"duration": { "type": "fixed", "seconds": 300 },
"startTrigger": { "type": "programStart" }
}
]
},
{
"trackId": "side",
"name": "Side",
"steps": [
{
"stepId": "prep",
"name": "Prep",
"duration": { "type": "fixed", "seconds": 300 },
"startTrigger": { "type": "programStart" }
}
]
}
]
}
Fix. Rename one of the steps and update the triggers that point at it.
Python. Python reports duplicate_step_id.
compound_without_triggers¶
Error. A trigger has logic but no triggers list.
logic ("all"/"any") combines the triggers in triggers; on a single trigger it has nothing to combine. The schema accepts the extra key on a single trigger, so this check names it.
Example. Merged over the base program:
{
"tracks": [
{
"trackId": "main",
"name": "Main",
"steps": [
{
"stepId": "prep",
"name": "Prep",
"duration": { "type": "fixed", "seconds": 300 },
"startTrigger": { "type": "programStart", "logic": "all" }
}
]
}
]
}
Fix. Remove logic, or make the trigger compound: {"logic": "all", "triggers": [...]} with at least two triggers.
Python. Python reports nothing for it.
bad_trigger_logic¶
Error. A trigger's logic is not "all" or "any".
Reported when the trigger otherwise passes the schema as a single trigger that also carries logic and triggers keys; a compound trigger with a bad logic value is a schema_oneOf finding.
Example. Merged over the base program:
{
"tracks": [
{
"trackId": "main",
"name": "Main",
"steps": [
{
"stepId": "prep",
"name": "Prep",
"duration": { "type": "fixed", "seconds": 300 },
"startTrigger": { "type": "programStart" }
}
]
},
{
"trackId": "side",
"name": "Side",
"steps": [
{
"stepId": "cook",
"name": "Cook",
"duration": { "type": "fixed", "seconds": 300 },
"startTrigger": {
"type": "programStart",
"logic": "xor",
"triggers": [
{ "type": "afterStep", "stepId": "prep" },
{ "type": "programStart" }
]
}
}
]
}
]
}
Fix. Use logic "all" (every trigger must fire) or "any" (the first one starts the step).
Python. Python reports nothing for it.
bad_trigger¶
Error. A trigger inside a compound trigger is not an object.
Meant for a non-object entry in a compound trigger's triggers list.
Not reachable today
Non-object entries are dropped when the trigger is flattened, before this check looks at them, and the schema reports them (schema_type / schema_oneOf) wherever they would matter. Dead code in validateProgram.
Fix. Make every entry of triggers an object such as {"type": "afterStep", "stepId": "..."}.
Python. Python reports nothing for it.
unknown_trigger_type¶
Error. A trigger's type is not one the runner knows.
Known types: programStart, programStartOffset, afterStep, afterStepWithBuffer, manual, onAbort. A bad type on a trigger the schema checks is a schema_oneOf finding; this check reaches the entries of a triggers list on a trigger the schema took as a single trigger.
Example. Merged over the base program:
{
"tracks": [
{
"trackId": "main",
"name": "Main",
"steps": [
{
"stepId": "prep",
"name": "Prep",
"duration": { "type": "fixed", "seconds": 300 },
"startTrigger": { "type": "programStart" }
}
]
},
{
"trackId": "side",
"name": "Side",
"steps": [
{
"stepId": "cook",
"name": "Cook",
"duration": { "type": "fixed", "seconds": 300 },
"startTrigger": {
"type": "programStart",
"logic": "all",
"triggers": [
{ "type": "afterStepp", "stepId": "prep" },
{ "type": "programStart" }
]
}
}
]
}
]
}
Fix. Use programStart, programStartOffset, afterStep, afterStepWithBuffer, manual or onAbort.
Python. Python reports nothing for it.
trigger_without_step_id¶
Error. An afterStep, afterStepWithBuffer or onAbort trigger has an empty stepId.
These triggers wait for another step and need its id. The schema requires the key; this check catches an empty string.
Example. Merged over the base program:
{
"tracks": [
{
"trackId": "main",
"name": "Main",
"steps": [
{
"stepId": "prep",
"name": "Prep",
"duration": { "type": "fixed", "seconds": 300 },
"startTrigger": { "type": "programStart" }
}
]
},
{
"trackId": "side",
"name": "Side",
"steps": [
{
"stepId": "cook",
"name": "Cook",
"duration": { "type": "fixed", "seconds": 300 },
"startTrigger": { "type": "afterStep", "stepId": "" }
}
]
}
]
}
Fix. Set stepId to the step this one waits for.
Python. Python reports dangling_step_ref. Python reports the empty id as a reference to a step that does not exist.
dangling_step_ref¶
Error. A trigger waits for a step that does not exist.
The stepId in an afterStep, afterStepWithBuffer or onAbort trigger matches no step. Ids are case-sensitive; after replicate expansion a replicated step's copies are "
Example. Merged over the base program:
{
"tracks": [
{
"trackId": "main",
"name": "Main",
"steps": [
{
"stepId": "prep",
"name": "Prep",
"duration": { "type": "fixed", "seconds": 300 },
"startTrigger": { "type": "programStart" }
},
{
"stepId": "cook",
"name": "Cook",
"duration": { "type": "fixed", "seconds": 300 },
"startTrigger": { "type": "afterStep", "stepId": "Prep" }
}
]
}
]
}
Fix. Point the trigger at an existing stepId (check case and spelling).
Python. Python reports dangling_step_ref.
self_reference¶
Error. A step waits for itself.
A step's trigger names its own stepId, so it can never start.
Example. Merged over the base program:
{
"tracks": [
{
"trackId": "main",
"name": "Main",
"steps": [
{
"stepId": "prep",
"name": "Prep",
"duration": { "type": "fixed", "seconds": 300 },
"startTrigger": { "type": "afterStep", "stepId": "prep" }
}
]
}
]
}
Also emits dependency_cycle.
Fix. Point the trigger at the step before it, or use programStart.
Python. Python reports nothing for it. Python reports nothing; the step is left unscheduled.
buffer_without_seconds¶
Error. An afterStepWithBuffer trigger has no bufferSeconds.
The schema requires bufferSeconds on a trigger it checks; this check reaches the entries of a triggers list on a trigger the schema took as a single trigger.
Example. Merged over the base program:
{
"tracks": [
{
"trackId": "main",
"name": "Main",
"steps": [
{
"stepId": "prep",
"name": "Prep",
"duration": { "type": "fixed", "seconds": 300 },
"startTrigger": { "type": "programStart" }
}
]
},
{
"trackId": "side",
"name": "Side",
"steps": [
{
"stepId": "cook",
"name": "Cook",
"duration": { "type": "fixed", "seconds": 300 },
"startTrigger": {
"type": "programStart",
"logic": "all",
"triggers": [
{ "type": "afterStepWithBuffer", "stepId": "prep" },
{ "type": "programStart" }
]
}
}
]
}
]
}
Fix. Add bufferSeconds (seconds or a time string such as "5m"), or use afterStep.
Python. Python reports nothing for it.
bad_trigger_event¶
Error. A trigger's event is not "start" or "end".
event chooses whether the trigger anchors on the referenced step's start or end (the default). A bad value on a trigger the schema checks is a schema finding; this check reaches the entries of a triggers list on a trigger the schema took as a single trigger.
Example. Merged over the base program:
{
"tracks": [
{
"trackId": "main",
"name": "Main",
"steps": [
{
"stepId": "prep",
"name": "Prep",
"duration": { "type": "fixed", "seconds": 300 },
"startTrigger": { "type": "programStart" }
}
]
},
{
"trackId": "side",
"name": "Side",
"steps": [
{
"stepId": "cook",
"name": "Cook",
"duration": { "type": "fixed", "seconds": 300 },
"startTrigger": {
"type": "programStart",
"logic": "all",
"triggers": [
{ "type": "afterStep", "stepId": "prep", "event": "middle" },
{ "type": "programStart" }
]
}
}
]
}
]
}
Fix. Use event "start" or "end", or leave it out ("end").
Python. Python reports nothing for it.
offset_without_seconds¶
Error. A programStartOffset trigger has no offsetSeconds.
The schema requires offsetSeconds on a trigger it checks; this check reaches the entries of a triggers list on a trigger the schema took as a single trigger.
Example. Merged over the base program:
{
"tracks": [
{
"trackId": "main",
"name": "Main",
"steps": [
{
"stepId": "prep",
"name": "Prep",
"duration": { "type": "fixed", "seconds": 300 },
"startTrigger": {
"type": "programStart",
"logic": "all",
"triggers": [{ "type": "programStartOffset" }, { "type": "programStart" }]
}
}
]
}
]
}
Fix. Add offsetSeconds (seconds or a time string such as "10m"), or use programStart.
Python. Python reports nothing for it.
choice_ref_no_choice¶
Error. A trigger's choiceId refers to a step that offers no choice.
choiceId makes a step run only when the referenced step's choice selects that option (schema 0.2.0-alpha branching). The referenced step has no choice.
Example. Merged over the base program:
{
"tracks": [
{
"trackId": "main",
"name": "Main",
"steps": [
{
"stepId": "prep",
"name": "Prep",
"duration": { "type": "fixed", "seconds": 300 },
"startTrigger": { "type": "programStart" }
},
{
"stepId": "cook",
"name": "Cook",
"duration": { "type": "fixed", "seconds": 300 },
"startTrigger": { "type": "afterStep", "stepId": "prep", "choiceId": "red" }
}
]
}
]
}
Fix. Add a choice with that option to the referenced step, or remove choiceId.
Python. Python reports choice_ref. Python has one code for both choiceId problems.
choice_ref_unknown¶
Error. A trigger's choiceId is not one of the referenced step's options.
The referenced step offers a choice, but none of its options has this choiceId. The message lists the options.
Example. Merged over the base program:
{
"tracks": [
{
"trackId": "main",
"name": "Main",
"steps": [
{
"stepId": "pick",
"name": "Pick",
"duration": { "type": "fixed", "seconds": 60 },
"startTrigger": { "type": "programStart" },
"choice": {
"prompt": "Sauce?",
"options": [
{ "choiceId": "red", "label": "Red" },
{ "choiceId": "white", "label": "White" }
]
}
},
{
"stepId": "cook",
"name": "Cook",
"duration": { "type": "fixed", "seconds": 300 },
"startTrigger": { "type": "afterStep", "stepId": "pick", "choiceId": "pesto" }
}
]
}
]
}
Fix. Use one of the option ids the message lists (ids are case-sensitive).
Python. Python reports choice_ref. Python has one code for both choiceId problems.
Timing and overlap¶
Durations, the resolved timeline, cycles and steps that overlap in a track.
| Code | Severity | Summary | Python |
|---|---|---|---|
missing_duration | error | A step has no duration. | no |
unparseable_duration | error | A fixed duration's seconds is not a positive number or time string. | no |
variable_min_gt_max | error | A variable duration's minSeconds is greater than its maxSeconds. | no |
variable_without_default | warning | A variable duration has no defaultSeconds; planning uses maxSeconds. | no |
negative_offset_on_fixed | warning | A negative offset counts down into a step that is not indefinite. | negative_offset |
dependency_cycle | error | Steps wait for each other in a cycle. | no |
track_overlap | error | Two steps in one track overlap in time. | track_overlap |
tracks_finish_far_apart | warning | Some tracks finish long before the last one. | no |
missing_duration¶
Error. A step has no duration.
Every step needs a duration, except an instrument step whose command or until call ends it (see I_INSTRUMENT_DURATION) or one with only start/end actions (see instrument_no_end).
Example. Merged over the base program:
{
"tracks": [
{
"trackId": "main",
"name": "Main",
"steps": [
{ "stepId": "prep", "name": "Prep", "startTrigger": { "type": "programStart" } }
]
}
]
}
Fix. Add duration: {"type": "fixed", "seconds": 300} (or "5m"), variable with min/max, or indefinite.
Python. Python reports nothing for it. Python times a step with no duration as 0 s.
unparseable_duration¶
Error. A fixed duration's seconds is not a positive number or time string.
seconds may be a number or a string such as "90s", "5m", "1h30m". A string that does not parse ("soon") or a negative one is reported here; the schema accepts any string.
Example. Merged over the base program:
{
"tracks": [
{
"trackId": "main",
"name": "Main",
"steps": [
{
"stepId": "prep",
"name": "Prep",
"duration": { "type": "fixed", "seconds": "soon" },
"startTrigger": { "type": "programStart" }
}
]
}
]
}
Fix. Use a number of seconds or a time string like "5m" or "1h30m".
Python. Python reports nothing for it.
variable_min_gt_max¶
Error. A variable duration's minSeconds is greater than its maxSeconds.
A variable step can end anywhere between minSeconds and maxSeconds; the bounds are the wrong way round.
Example. Merged over the base program:
{
"tracks": [
{
"trackId": "main",
"name": "Main",
"steps": [
{
"stepId": "prep",
"name": "Prep",
"duration": {
"type": "variable",
"minSeconds": 600,
"maxSeconds": 300,
"defaultSeconds": 450
},
"startTrigger": { "type": "programStart" }
}
]
}
]
}
Fix. Swap the bounds so minSeconds <= maxSeconds.
Python. Python reports nothing for it.
variable_without_default¶
Warning. A variable duration has no defaultSeconds; planning uses maxSeconds.
Previews, the makespan and the analyzer place a variable step at defaultSeconds; without it they use maxSeconds, the worst case.
Example. Merged over the base program:
{
"tracks": [
{
"trackId": "main",
"name": "Main",
"steps": [
{
"stepId": "prep",
"name": "Prep",
"duration": { "type": "variable", "minSeconds": 300, "maxSeconds": 600 },
"startTrigger": { "type": "programStart" }
}
]
}
]
}
Fix. Add defaultSeconds (the expected time).
Python. Python reports nothing for it.
negative_offset_on_fixed¶
Warning. A negative offset counts down into a step that is not indefinite.
A negative offsetSeconds on afterStep starts this step before the referenced step ends. The live runner can only honour that when the referenced step is indefinite (its end is announced, so the countdown has an anchor); against a fixed or variable step it is ignored at run time.
Example. Merged over the base program:
{
"tracks": [
{
"trackId": "main",
"name": "Main",
"steps": [
{
"stepId": "prep",
"name": "Prep",
"duration": { "type": "fixed", "seconds": 600 },
"startTrigger": { "type": "programStart" }
},
{
"stepId": "cook",
"name": "Cook",
"duration": { "type": "fixed", "seconds": 300 },
"startTrigger": {
"type": "afterStep",
"stepId": "prep",
"offsetSeconds": -120
}
}
]
}
]
}
The two steps overlap, so track_overlap is reported too; put them in separate tracks to see the warning alone.
Fix. Give the referenced step duration.type "indefinite", or use a positive offset from event "start".
Python. Python reports negative_offset, as an error. An error in Python, which also rejects a negative offset on any trigger other than afterStep / afterStepWithBuffer.
dependency_cycle¶
Error. Steps wait for each other in a cycle.
Each step in the cycle waits (directly or transitively) for itself, so none of them can be scheduled. Reported once per step left unscheduled whose references all exist.
Example. Merged over the base program:
{
"tracks": [
{
"trackId": "main",
"name": "Main",
"steps": [
{
"stepId": "prep",
"name": "Prep",
"duration": { "type": "fixed", "seconds": 300 },
"startTrigger": { "type": "afterStep", "stepId": "cook" }
}
]
},
{
"trackId": "side",
"name": "Side",
"steps": [
{
"stepId": "cook",
"name": "Cook",
"duration": { "type": "fixed", "seconds": 300 },
"startTrigger": { "type": "afterStep", "stepId": "prep" }
}
]
}
]
}
Fix. Break the cycle: start one of the steps from programStart or from a step outside the cycle.
Python. Python reports nothing for it. Python reports nothing; the steps are left unscheduled.
track_overlap¶
Error. Two steps in one track overlap in time.
Steps in a track run one after another (a track is one person, machine or lane). Two steps whose resolved times overlap usually lack an afterStep chain. Steps on different choice branches are exempt.
Example. Merged over the base program:
{
"tracks": [
{
"trackId": "main",
"name": "Main",
"steps": [
{
"stepId": "prep",
"name": "Prep",
"duration": { "type": "fixed", "seconds": 300 },
"startTrigger": { "type": "programStart" }
},
{
"stepId": "cook",
"name": "Cook",
"duration": { "type": "fixed", "seconds": 300 },
"startTrigger": { "type": "programStart" }
}
]
}
]
}
Fix. Chain the later step with {"type": "afterStep", "stepId": "
Python. Python reports track_overlap.
tracks_finish_far_apart¶
Warning. Some tracks finish long before the last one.
At least one track ends more than 30 minutes (or half the makespan, whichever is more) before the program does. Often fine; but if everything should be ready together, the early tracks start too soon.
Example. Merged over the base program:
{
"tracks": [
{
"trackId": "main",
"name": "Main",
"steps": [
{
"stepId": "prep",
"name": "Prep",
"duration": { "type": "fixed", "seconds": 7200 },
"startTrigger": { "type": "programStart" }
}
]
},
{
"trackId": "side",
"name": "Side",
"steps": [
{
"stepId": "cook",
"name": "Cook",
"duration": { "type": "fixed", "seconds": 600 },
"startTrigger": { "type": "programStart" }
}
]
}
]
}
Fix. Delay the early tracks with programStartOffset or afterStep so they converge at the end, or ignore it when finishing apart is intended.
Python. Python reports nothing for it.
Resources¶
Tasks and resourceConstraints.
| Code | Severity | Summary | Python |
|---|---|---|---|
bad_resource_constraint | error | A resource constraint has an empty task name. | no |
duplicate_constraint | warning | Two resource constraints name the same task. | no |
bad_max_concurrent | error | A resource constraint's maxConcurrent is not an integer >= 1. | no |
task_not_constrained | error, warning | A task used by steps has no resource constraint. | task_not_constrained (with --strict) only |
unused_constraint | warning | A resource constraint names a task no step uses. | no |
bad_resource_constraint¶
Error. A resource constraint has an empty task name.
Each entry of resourceConstraints limits one task type. A missing task is a schema_required finding; an empty string is reported here.
Example. Merged over the base program:
Fix. Set task to the task name the steps use, or remove the entry.
Python. Python reports nothing for it.
duplicate_constraint¶
Warning. Two resource constraints name the same task.
Only one limit can apply to a task; which one wins depends on the reader.
Example. Merged over the base program:
{
"tracks": [
{
"trackId": "main",
"name": "Main",
"steps": [
{
"stepId": "prep",
"name": "Prep",
"duration": { "type": "fixed", "seconds": 300 },
"startTrigger": { "type": "programStart" },
"task": "oven"
}
]
}
],
"resourceConstraints": [
{ "task": "oven", "maxConcurrent": 1 },
{ "task": "oven", "maxConcurrent": 2 }
]
}
Fix. Keep one constraint per task.
Python. Python reports nothing for it.
bad_max_concurrent¶
Error. A resource constraint's maxConcurrent is not an integer >= 1.
The schema reports a bad maxConcurrent itself (schema_minimum, schema_type, schema_required), and this check stays quiet where it has. A program with an instances or replicates error (an E_* code) is checked against the schema as written, and those schema findings do not quiet the checks that follow, so this code is reported next to the schema's.
Example. Merged over the base program:
{
"schemaVersion": "0.3.0-alpha",
"tracks": [
{
"trackId": "main",
"name": "Main",
"steps": [
{
"stepId": "mix",
"name": "Mix",
"duration": { "type": "fixed", "seconds": 300 },
"startTrigger": { "type": "programStart" }
},
{
"stepId": "cool",
"name": "Cool",
"duration": { "type": "fixed", "seconds": 300 },
"startTrigger": { "type": "afterStep", "stepId": "mix", "instances": "each" }
}
]
}
],
"resourceConstraints": [{ "task": "oven", "maxConcurrent": 0 }]
}
Also emits E_INSTANCES_ON_SINGLE, schema_minimum and unused_constraint.
Fix. Set maxConcurrent to a whole number of at least 1.
Python. Python reports nothing for it.
task_not_constrained¶
Error or Warning. A task used by steps has no resource constraint.
Every task named by a step (task, tasks, taskResources, buffer taskResources) needs an entry in resourceConstraints, so the runner knows how many can run at once. It is an error, unless the program names an environment or actors, which may supply the constraint: then it is a warning.
Example 1 (error). Merged over the base program:
{
"tracks": [
{
"trackId": "main",
"name": "Main",
"steps": [
{
"stepId": "prep",
"name": "Prep",
"duration": { "type": "fixed", "seconds": 300 },
"startTrigger": { "type": "programStart" },
"task": "oven"
}
]
}
]
}
Example 2 (warning). Merged over the base program:
{
"tracks": [
{
"trackId": "main",
"name": "Main",
"steps": [
{
"stepId": "prep",
"name": "Prep",
"duration": { "type": "fixed", "seconds": 300 },
"startTrigger": { "type": "programStart" },
"task": "oven"
}
]
}
],
"actors": 2
}
Fix. Add {"task": "
Python. Python reports nothing for it by default. rhylthyme validate --strict reports task_not_constrained, as an error. That includes a program that names an environment or actors, where validateProgram reports a warning. The library (validate_program_structured) has no strict mode.
unused_constraint¶
Warning. A resource constraint names a task no step uses.
Usually a typo in the task name on the constraint or on the steps.
Example. Merged over the base program:
Fix. Remove the constraint, or set task on the steps that use it.
Python. Python reports nothing for it.
Instances and replicates¶
Schema 0.3.0-alpha instances triggers, replicates and maxInFlight, and replicate expansion.
| Code | Severity | Summary | Python |
|---|---|---|---|
E_INSTANCES_ON_SINGLE | error | instances is used on a reference to a step that is not replicated. | E_INSTANCES_ON_SINGLE |
E_EACH_WITH_REPLICATES | error | A step has both an instances: "each" trigger and its own replicates. | E_EACH_WITH_REPLICATES |
E_EACH_COUNT_MISMATCH | error | A compound "each" trigger pairs groups of different sizes. | E_EACH_COUNT_MISMATCH |
E_INFLIGHT_GT_COUNT | error | replicates.maxInFlight is greater than replicates.count. | E_INFLIGHT_GT_COUNT |
E_INFLIGHT_NO_CHAIN | error | maxInFlight on a serial replicate with no "each" descendants has no effect. | E_INFLIGHT_NO_CHAIN |
W_UNBARRIERED_CHAIN | warning | An instances: "each" chain has no "all" barrier, but later steps do not wait for it. | W_UNBARRIERED_CHAIN |
I_IMPLICIT_BARRIER | info | A 0.3.0 program waits for a replicated step without saying instances. | no |
expansion_failed | error | Replicates could not be expanded. | none (CLI), expansion_failed (library) |
E_INSTANCES_ON_SINGLE¶
Error. instances is used on a reference to a step that is not replicated.
instances ("each", "all", "any") says how a reference fans in over a replicated step's copies. The referenced step has no replicates and is not itself per-instance, so there is nothing to fan in.
Example. Merged over the base program:
{
"schemaVersion": "0.3.0-alpha",
"tracks": [
{
"trackId": "main",
"name": "Main",
"steps": [
{
"stepId": "mix",
"name": "Mix",
"duration": { "type": "fixed", "seconds": 300 },
"startTrigger": { "type": "programStart" }
},
{
"stepId": "cool",
"name": "Cool",
"duration": { "type": "fixed", "seconds": 300 },
"startTrigger": { "type": "afterStep", "stepId": "mix", "instances": "each" }
}
]
}
]
}
Fix. Remove instances, or add replicates to the referenced step.
Python. Python reports E_INSTANCES_ON_SINGLE.
E_EACH_WITH_REPLICATES¶
Error. A step has both an instances: "each" trigger and its own replicates.
instances: "each" already makes one copy of the step per upstream instance; its own replicates would multiply them again.
Example. Merged over the base program:
{
"schemaVersion": "0.3.0-alpha",
"tracks": [
{
"trackId": "main",
"name": "Main",
"steps": [
{
"stepId": "mix",
"name": "Mix",
"duration": { "type": "fixed", "seconds": 300 },
"startTrigger": { "type": "programStart" }
},
{
"stepId": "bake",
"name": "Bake",
"duration": { "type": "fixed", "seconds": 300 },
"startTrigger": { "type": "afterStep", "stepId": "mix" },
"replicates": { "count": 3 }
},
{
"stepId": "cool",
"name": "Cool",
"duration": { "type": "fixed", "seconds": 300 },
"startTrigger": { "type": "afterStep", "stepId": "bake", "instances": "each" },
"replicates": { "count": 2 }
}
]
}
]
}
Fix. Drop replicates on the step; it inherits the upstream count.
Python. Python reports E_EACH_WITH_REPLICATES.
E_EACH_COUNT_MISMATCH¶
Error. A compound "each" trigger pairs groups of different sizes.
Instance i of the step pairs with instance i of every "each" upstream, so all of them need the same count.
Example. Merged over the base program:
{
"schemaVersion": "0.3.0-alpha",
"tracks": [
{
"trackId": "sauce",
"name": "Sauce",
"steps": [
{
"stepId": "sauce",
"name": "Sauce",
"duration": { "type": "fixed", "seconds": 300 },
"startTrigger": { "type": "programStart" },
"replicates": { "count": 2 }
}
]
},
{
"trackId": "pasta",
"name": "Pasta",
"steps": [
{
"stepId": "pasta",
"name": "Pasta",
"duration": { "type": "fixed", "seconds": 300 },
"startTrigger": { "type": "programStart" },
"replicates": { "count": 3 }
}
]
},
{
"trackId": "plating",
"name": "Plating",
"steps": [
{
"stepId": "plate",
"name": "Plate",
"duration": { "type": "fixed", "seconds": 300 },
"startTrigger": {
"logic": "all",
"triggers": [
{ "type": "afterStep", "stepId": "sauce", "instances": "each" },
{ "type": "afterStep", "stepId": "pasta", "instances": "each" }
]
}
}
]
}
]
}
Fix. Give the upstream steps the same replicates.count.
Python. Python reports E_EACH_COUNT_MISMATCH.
E_INFLIGHT_GT_COUNT¶
Error. replicates.maxInFlight is greater than replicates.count.
maxInFlight caps how many instances are in progress at once; it cannot exceed the number of instances.
Example. Merged over the base program:
{
"schemaVersion": "0.3.0-alpha",
"tracks": [
{
"trackId": "main",
"name": "Main",
"steps": [
{
"stepId": "mix",
"name": "Mix",
"duration": { "type": "fixed", "seconds": 300 },
"startTrigger": { "type": "programStart" }
},
{
"stepId": "bake",
"name": "Bake",
"duration": { "type": "fixed", "seconds": 300 },
"startTrigger": { "type": "afterStep", "stepId": "mix" },
"replicates": { "count": 3, "maxInFlight": 5 }
},
{
"stepId": "cool",
"name": "Cool",
"duration": { "type": "fixed", "seconds": 300 },
"startTrigger": { "type": "afterStep", "stepId": "bake", "instances": "each" }
},
{
"stepId": "box",
"name": "Box",
"duration": { "type": "fixed", "seconds": 300 },
"startTrigger": { "type": "afterStep", "stepId": "cool", "instances": "all" }
}
]
}
]
}
Fix. Set maxInFlight to at most count, or omit it.
Python. Python reports E_INFLIGHT_GT_COUNT.
E_INFLIGHT_NO_CHAIN¶
Error. maxInFlight on a serial replicate with no "each" descendants has no effect.
Serial instances never overlap, and with no per-instance steps after them an instance is done as soon as it ends, so the cap never holds one back.
Example. Merged over the base program:
{
"schemaVersion": "0.3.0-alpha",
"tracks": [
{
"trackId": "main",
"name": "Main",
"steps": [
{
"stepId": "mix",
"name": "Mix",
"duration": { "type": "fixed", "seconds": 300 },
"startTrigger": { "type": "programStart" }
},
{
"stepId": "bake",
"name": "Bake",
"duration": { "type": "fixed", "seconds": 300 },
"startTrigger": { "type": "afterStep", "stepId": "mix" },
"replicates": { "count": 3, "mode": "serial", "maxInFlight": 2 }
},
{
"stepId": "box",
"name": "Box",
"duration": { "type": "fixed", "seconds": 300 },
"startTrigger": { "type": "afterStep", "stepId": "bake", "instances": "all" }
}
]
}
]
}
Fix. Remove maxInFlight, or chain a per-instance step after it with instances: "each".
Python. Python reports E_INFLIGHT_NO_CHAIN.
W_UNBARRIERED_CHAIN¶
Warning. An instances: "each" chain has no "all" barrier, but later steps do not wait for it.
The per-instance chain is never joined, so nothing waits for every instance to finish while other work carries on. Often a missing barrier step.
Example. Merged over the base program:
{
"schemaVersion": "0.3.0-alpha",
"tracks": [
{
"trackId": "cookies",
"name": "Cookies",
"steps": [
{
"stepId": "mix",
"name": "Mix",
"duration": { "type": "fixed", "seconds": 300 },
"startTrigger": { "type": "programStart" }
},
{
"stepId": "bake",
"name": "Bake",
"duration": { "type": "fixed", "seconds": 300 },
"startTrigger": { "type": "afterStep", "stepId": "mix" },
"replicates": { "count": 3, "mode": "serial" }
},
{
"stepId": "cool",
"name": "Cool",
"duration": { "type": "fixed", "seconds": 300 },
"startTrigger": { "type": "afterStep", "stepId": "bake", "instances": "each" }
}
]
},
{
"trackId": "cleanup",
"name": "Cleanup",
"steps": [
{
"stepId": "wipe",
"name": "Wipe",
"duration": { "type": "fixed", "seconds": 300 },
"startTrigger": { "type": "programStartOffset", "offsetSeconds": 3600 }
}
]
}
]
}
Fix. Add a step with instances: "all" on the chain's last step if later work should wait for every instance.
Python. Python reports W_UNBARRIERED_CHAIN.
I_IMPLICIT_BARRIER¶
Info. A 0.3.0 program waits for a replicated step without saying instances.
A reference to a replicated step with no instances waits for every instance (an implicit "all" barrier), as in 0.2.0. In 0.3.0-alpha programs the note suggests saying so.
Example. Merged over the base program:
{
"schemaVersion": "0.3.0-alpha",
"tracks": [
{
"trackId": "main",
"name": "Main",
"steps": [
{
"stepId": "mix",
"name": "Mix",
"duration": { "type": "fixed", "seconds": 300 },
"startTrigger": { "type": "programStart" }
},
{
"stepId": "bake",
"name": "Bake",
"duration": { "type": "fixed", "seconds": 300 },
"startTrigger": { "type": "afterStep", "stepId": "mix" },
"replicates": { "count": 3, "mode": "serial" }
},
{
"stepId": "box",
"name": "Box",
"duration": { "type": "fixed", "seconds": 300 },
"startTrigger": { "type": "afterStep", "stepId": "bake" }
}
]
}
]
}
Fix. Add instances: "all" to make the barrier explicit, or "each" to run the step once per instance.
Python. Python reports nothing for it. JS only (PRD per-instance triggers §9 Q5).
expansion_failed¶
Error. Replicates could not be expanded.
The replicate expander rejected the program; the message says why. The one case the checks before it do not catch: instances: "all" or "any" inside a compound trigger of the other logic, which would need a nested compound. The program is then schema-checked as written.
Example. Merged over the base program:
{
"schemaVersion": "0.3.0-alpha",
"tracks": [
{
"trackId": "main",
"name": "Main",
"steps": [
{
"stepId": "bake",
"name": "Bake",
"duration": { "type": "fixed", "seconds": 300 },
"startTrigger": { "type": "programStart" },
"replicates": { "count": 2 }
},
{
"stepId": "box",
"name": "Box",
"duration": { "type": "fixed", "seconds": 300 },
"startTrigger": {
"logic": "all",
"triggers": [
{ "type": "afterStep", "stepId": "bake", "instances": "any" },
{ "type": "programStart" }
]
}
}
]
}
]
}
Fix. Check replicates and instances on the steps the message names; match the compound's logic to instances or split the trigger.
Python. rhylthyme validate reports nothing; validate_program_structured reports expansion_failed. rhylthyme validate does not expand replicates, so it has nothing to fail; validate_program_structured does.
Alerts¶
Step alerts (offsets before or after a step's start or end).
| Code | Severity | Summary | Python |
|---|---|---|---|
E_ALERT_BAD_OFFSET | error | An alert's offsetSeconds is not a number or time string. | E_ALERT_BAD_OFFSET |
W_ALERT_BEFORE_MANUAL_START | warning | An alert before the start of a manually started step never fires. | W_ALERT_BEFORE_MANUAL_START |
W_ALERT_BEFORE_PROGRAM_START | warning | An alert would fire before the program starts, so it never fires. | W_ALERT_BEFORE_PROGRAM_START |
W_ALERT_BEFORE_INDEFINITE_END | warning | An alert before the end of an indefinite step never fires. | W_ALERT_BEFORE_INDEFINITE_END |
E_ALERT_BAD_OFFSET¶
Error. An alert's offsetSeconds is not a number or time string.
offsetSeconds is signed seconds or a signed time string ("-2m", "30s", "1h30m") relative to the step's start or end.
Example. Merged over the base program:
{
"tracks": [
{
"trackId": "main",
"name": "Main",
"steps": [
{
"stepId": "prep",
"name": "Prep",
"duration": { "type": "fixed", "seconds": 300 },
"startTrigger": { "type": "programStart" },
"alerts": [{ "event": "end", "offsetSeconds": "a bit before" }]
}
]
}
]
}
Fix. Use seconds (-120) or a signed time string ("-2m").
Python. Python reports E_ALERT_BAD_OFFSET.
W_ALERT_BEFORE_MANUAL_START¶
Warning. An alert before the start of a manually started step never fires.
A manual step's start is not known until someone starts it, so an alert timed before it has no anchor.
Example. Merged over the base program:
{
"tracks": [
{
"trackId": "main",
"name": "Main",
"steps": [
{
"stepId": "prep",
"name": "Prep",
"duration": { "type": "fixed", "seconds": 300 },
"startTrigger": { "type": "manual" },
"alerts": [{ "event": "start", "offsetSeconds": "-1m" }]
}
]
}
]
}
Fix. Anchor the alert on the end of the step before, or use offsetSeconds >= 0.
Python. Python reports W_ALERT_BEFORE_MANUAL_START.
W_ALERT_BEFORE_PROGRAM_START¶
Warning. An alert would fire before the program starts, so it never fires.
The step starts at (or soon after) the program start and the alert's negative offset reaches further back than that.
Example. Merged over the base program:
{
"tracks": [
{
"trackId": "main",
"name": "Main",
"steps": [
{
"stepId": "prep",
"name": "Prep",
"duration": { "type": "fixed", "seconds": 300 },
"startTrigger": { "type": "programStart" },
"alerts": [{ "event": "start", "offsetSeconds": -300 }]
}
]
}
]
}
Fix. Shorten the offset, or delay the step with programStartOffset.
Python. Python reports W_ALERT_BEFORE_PROGRAM_START.
W_ALERT_BEFORE_INDEFINITE_END¶
Warning. An alert before the end of an indefinite step never fires.
An indefinite step has no projected end, so an alert timed before it has no anchor.
Example. Merged over the base program:
{
"tracks": [
{
"trackId": "main",
"name": "Main",
"steps": [
{
"stepId": "prep",
"name": "Prep",
"duration": { "type": "indefinite", "defaultSeconds": 900 },
"startTrigger": { "type": "programStart" },
"alerts": [{ "event": "end", "offsetSeconds": "-2m" }]
}
]
}
]
}
Fix. Use offsetSeconds >= 0 on event "end", or give the step a fixed or variable duration.
Python. Python reports W_ALERT_BEFORE_INDEFINITE_END.
Instruments¶
Instrument steps: galago-tools commands and LabMCP server calls.
| Code | Severity | Summary | Python |
|---|---|---|---|
I_INSTRUMENT_DURATION | info | An instrument step with no duration ends when its call replies; timings use an estimate. | no |
instrument_bad_shape | error | A step's instrument block is malformed. | schema_error |
instrument_no_end | warning | An instrument step has no command, no until and no duration: only the operator can end it. | instrument_no_end (CLI), none (library) |
instrument_unchecked | warning | Instrument calls were not checked. | instrument_unchecked (CLI), none (library) |
instrument_unknown_tool_type | error, warning | An instrument's toolType is not a known galago tool type or LabMCP server. | instrument_unknown_tool_type (CLI), logic_error (library) |
instrument_invalid_command | error | An instrument call is not valid for its tool type. | instrument_invalid_command (CLI), logic_error (library) |
instrument_over_limit | error | A LabMCP call's parameter is beyond the server's default limit. | instrument_over_limit (CLI), logic_error (library) |
instrument_returns_early | error | A LabMCP call that replies as soon as it starts is used to end a step. | instrument_returns_early (CLI), logic_error (library) |
instrument_dose_outlasts_step | warning | A pump dose started by a step takes longer than the step. | instrument_dose_outlasts_step (CLI), none (library) |
instrument_catalog_not_loaded | warning | A LabMCP step was not checked because the LabMCP catalogue is not loaded. | no |
I_INSTRUMENT_DURATION¶
Info. An instrument step with no duration ends when its call replies; timings use an estimate.
The step's command (or until call) ends it, so it needs no duration. For planning, the validator and analyzer time it by an estimate from its params (e.g. a shake's duration, a LabMCP series' count x interval) or a default, and say which.
Example. Merged over the base program:
{
"tracks": [
{
"trackId": "main",
"name": "Main",
"steps": [
{
"stepId": "shake",
"name": "Shake",
"startTrigger": { "type": "programStart" },
"instrument": {
"tool": "shaker",
"toolType": "bioshake",
"command": "start_shake",
"params": { "speed": 1000, "duration": 300 }
}
}
]
}
]
}
Fix. Nothing to fix. rhylthyme plan --workcell writes the tool's own estimate into the program.
Python. Python reports nothing for it.
instrument_bad_shape¶
Error. A step's instrument block is malformed.
The instrument has a missing or empty tool, both command and until, no call or actions at all, an empty start/end/onAbort list, an action without a command, an unknown key or a bad timeoutSeconds. The schema reports the same problem (schema_*), with the program path; this finding says it in the instrument's own terms. The step's calls are not checked further.
Example. Merged over the base program:
{
"tracks": [
{
"trackId": "main",
"name": "Main",
"steps": [
{
"stepId": "shake",
"name": "Shake",
"startTrigger": { "type": "programStart" },
"instrument": {
"tool": "shaker",
"toolType": "bioshake",
"command": "start_shake",
"params": { "speed": 1000, "duration": 300 },
"start": []
}
}
]
}
]
}
Also emits schema_minItems.
Fix. Use {"tool": "shaker", "command": "start_shake", "params": {...}}, or start/until/end actions: {"command", "params", "tool"}.
Python. Python reports schema_error. Python reports a malformed instrument through its schema check only.
instrument_no_end¶
Warning. An instrument step has no command, no until and no duration: only the operator can end it.
Its start actions run and nothing ends the step on its own. Timings use a default.
Example. Merged over the base program:
{
"tracks": [
{
"trackId": "main",
"name": "Main",
"steps": [
{
"stepId": "shake",
"name": "Shake",
"startTrigger": { "type": "programStart" },
"instrument": {
"tool": "shaker",
"toolType": "bioshake",
"start": [
{ "command": "start_shake", "params": { "speed": 1000, "duration": 300 } }
]
}
}
]
}
]
}
Fix. Add a duration, or an until action whose reply ends the step.
Python. rhylthyme validate reports instrument_no_end; validate_program_structured reports nothing. validate_program_structured keeps only the errors of the instrument checks.
instrument_unchecked¶
Warning. Instrument calls were not checked.
The step's instrument has no toolType, or some of its actions go to another tool, so the commands cannot be checked without the lab's workcell file.
Example. Merged over the base program:
{
"tracks": [
{
"trackId": "main",
"name": "Main",
"steps": [
{
"stepId": "shake",
"name": "Shake",
"startTrigger": { "type": "programStart" },
"instrument": {
"tool": "shaker",
"command": "start_shake",
"params": { "speed": 1000, "duration": 300 }
}
}
]
}
]
}
Fix. Add toolType (the galago tool type, e.g. "bioshake", or a LabMCP package such as "labmcp-ika"), or run rhylthyme validate --workcell.
Python. rhylthyme validate reports instrument_unchecked; validate_program_structured reports nothing. Python reports it once per program ("Instrument steps were not checked") when the driver package is missing, and as an error with --workcell. validate_program_structured keeps only the errors of the instrument checks.
instrument_unknown_tool_type¶
Error or Warning. An instrument's toolType is not a known galago tool type or LabMCP server.
An unknown galago toolType is an error. A toolType starting with "labmcp-" that is not in the LabMCP catalogue is a warning: the server may be newer than the catalogue, so its calls are just not checked.
Example 1 (error). Merged over the base program:
{
"tracks": [
{
"trackId": "main",
"name": "Main",
"steps": [
{
"stepId": "shake",
"name": "Shake",
"startTrigger": { "type": "programStart" },
"instrument": {
"tool": "shaker",
"toolType": "blender",
"command": "start_shake"
}
}
]
}
]
}
Example 2 (warning). Merged over the base program:
{
"tracks": [
{
"trackId": "main",
"name": "Main",
"steps": [
{
"stepId": "heat",
"name": "Heat",
"duration": { "type": "fixed", "seconds": 300 },
"startTrigger": { "type": "programStart" },
"instrument": {
"tool": "stirrer",
"toolType": "labmcp-nope",
"command": "stop_heating"
}
}
]
}
]
}
Fix. Use a galago tool type the message lists, or a LabMCP package name from the catalogue.
Python. rhylthyme validate reports instrument_unknown_tool_type; validate_program_structured reports logic_error. Needs rhylthyme-galago / rhylthyme-labmcp. validate_program_structured keeps only the errors of the instrument checks, as logic_error.
instrument_invalid_command¶
Error. An instrument call is not valid for its tool type.
The command does not exist for the toolType, or its params do not match the command's parameters (unknown, missing or of the wrong type). galago tool types are checked against galago-tools' commands, LabMCP servers against the LabMCP catalogue.
Example. Merged over the base program:
{
"tracks": [
{
"trackId": "main",
"name": "Main",
"steps": [
{
"stepId": "shake",
"name": "Shake",
"startTrigger": { "type": "programStart" },
"instrument": {
"tool": "shaker",
"toolType": "bioshake",
"command": "start_shake",
"params": { "rpm": 1000 }
}
}
]
}
]
}
Fix. Use a command and params the message lists for that tool type.
Python. rhylthyme validate reports instrument_invalid_command; validate_program_structured reports logic_error. Needs rhylthyme-galago. From the rhylthyme-galago / rhylthyme-labmcp driver packages, when installed. validate_program_structured reports it as logic_error.
instrument_over_limit¶
Error. A LabMCP call's parameter is beyond the server's default limit.
LabMCP servers refuse values past their safety limits (a maximum temperature, a series length, ...). The check uses each server's default limits; a lab may configure others.
Example. Merged over the base program:
{
"tracks": [
{
"trackId": "main",
"name": "Main",
"steps": [
{
"stepId": "heat",
"name": "Heat",
"duration": { "type": "fixed", "seconds": 300 },
"startTrigger": { "type": "programStart" },
"instrument": {
"tool": "stirrer",
"toolType": "labmcp-ika",
"start": [
{ "command": "set_temperature", "params": { "temperature_c": 200 } }
]
}
}
]
}
]
}
Fix. Use a value within the limit the message names, or raise the limit in the server's configuration.
Python. rhylthyme validate reports instrument_over_limit; validate_program_structured reports logic_error. Needs rhylthyme-labmcp. From rhylthyme-labmcp, when installed. validate_program_structured reports it as logic_error.
instrument_returns_early¶
Error. A LabMCP call that replies as soon as it starts is used to end a step.
Some calls (a syringe pump's infuse and withdraw) reply when the work starts, not when it is done; as the step's command or until call they would end the step at once.
Example. Merged over the base program:
{
"tracks": [
{
"trackId": "main",
"name": "Main",
"steps": [
{
"stepId": "dose",
"name": "Dose",
"startTrigger": { "type": "programStart" },
"instrument": {
"tool": "pump",
"toolType": "labmcp-new-era",
"command": "infuse",
"params": { "volume_ml": 1, "rate_ml_min": 1 }
}
}
]
}
]
}
Fix. Send it as a start action and give the step a duration at least as long as the dose.
Python. rhylthyme validate reports instrument_returns_early; validate_program_structured reports logic_error. Needs rhylthyme-labmcp. From rhylthyme-labmcp, when installed. validate_program_structured reports it as logic_error.
instrument_dose_outlasts_step¶
Warning. A pump dose started by a step takes longer than the step.
The dose's volume / rate is longer than the step's duration, so its end actions and the next steps start while the pump still runs.
Example. Merged over the base program:
{
"tracks": [
{
"trackId": "main",
"name": "Main",
"steps": [
{
"stepId": "dose",
"name": "Dose",
"duration": { "type": "fixed", "seconds": 10 },
"startTrigger": { "type": "programStart" },
"instrument": {
"tool": "pump",
"toolType": "labmcp-new-era",
"start": [
{ "command": "infuse", "params": { "volume_ml": 1, "rate_ml_min": 1 } }
]
}
}
]
}
]
}
Fix. Lengthen the step to at least the dose time, or lower the volume or raise the rate.
Python. rhylthyme validate reports instrument_dose_outlasts_step; validate_program_structured reports nothing. Needs rhylthyme-labmcp. From rhylthyme-labmcp, when installed. validate_program_structured keeps only the errors of the instrument checks.
instrument_catalog_not_loaded¶
Warning. A LabMCP step was not checked because the LabMCP catalogue is not loaded.
The browser entry of @rhylthyme/timeline/schedule (and browser/schedule.js) leaves out the LabMCP catalogue (about 350 KB). Until it is registered, each LabMCP step gets this warning instead of its checks. The Node entry always has it.
Example. Merged over the base program:
{
"tracks": [
{
"trackId": "main",
"name": "Main",
"steps": [
{
"stepId": "heat",
"name": "Heat",
"duration": { "type": "fixed", "seconds": 300 },
"startTrigger": { "type": "programStart" },
"instrument": {
"tool": "stirrer",
"toolType": "labmcp-ika",
"start": [
{ "command": "set_temperature", "params": { "temperature_c": 40 } }
]
}
}
]
}
]
}
Validated with the browser entry before the LabMCP catalogue is registered.
Fix. import '@rhylthyme/timeline/schedule/labmcp-catalog' (or add browser/labmcp-catalog.js to the page) before validating.
Python. Python reports nothing for it. Python always has its catalogue.