Skip to content

Installed Artifact Workflow

Dokime consumes directly declared Harmos sidecars. Harmos also has guests upstream; Dokime refuses them at admission. Rust declarations describe the Dokime product domains that belong to an artifact: suites, telemetry, records, actions, settings, and credentials.

Identity-declaring macros accept optional id = …: a template’s step, criterion and stream methods default to their function name; suite, template and route default to the decorated type’s terminal name; sidecar defaults to the struct name. channel and record have no default: each names its id or the macro refuses. A plugin sampler’s #[dokime::sampler] names no id at all, because its Row type already declared the channel, and listen names none because the record type it hears is the declaration. Explicit IDs win. Preserve spelling and case, strip r#, and add no automatic namespace. Labels remain independent. Existing validation and uniqueness scopes still apply.

Route and sidecar IDs are wire identities, read by things that never saw the Rust name: they must start with a lowercase ASCII letter and contain only lowercase ASCII letters, digits, dots, or hyphens. An id defaulted from a Rust type name is checked before it is forwarded, so a PascalCase route type or sidecar struct is refused on the line it is written, naming id = …. Raw sidecar struct names are unsupported by the pinned Harmos macro; use an ordinary struct name with an explicit ID. Override an id to preserve identity across a Rust rename, and namespace ids by plugin — meter.status, not status. Remove an existing ID only when it equals the derived name; otherwise its removal changes identity. Dependency references such as requires remain explicit.

Composition is code, and a contribution is a type. #[dokime::suites] closes the authored suite set, and #[dokime::sidecar] names that enum:

#[dokime::suites]
enum MiniSuites { MiniChecks }
#[dokime::sidecar(id = "mini-sidecar", suites(MiniSuites))]
pub struct MySidecar;
impl dokime::Plugin for MySidecar {
type Config = Config;
type Error = Error;
type Resources = ();
async fn initialize(_config: Config, _ctx: &dokime::plugin::Context) -> Result<Self, Error> {
Ok(Self)
}
fn resources(&self) -> Self::Resources {}
}

Every other contribution composes the same way, as a closed catalog of types named on the attribute: samplers(Enum), listeners(Enum), routes(Enum), and records(Type, …) for the records this plugin publishes with no run behind them. What carries values or code stays on the trait, and the trait is short: three associated types — Config, Error, Resources — the definitions settings, permissions, prompts, documents and appearance, each defaulting to empty, and the lifecycle initialize, resources and cleanup. There is nothing else on it. A zero-suite sidecar simply omits suites; there is no aggregate wrapper, archive, package manifest, or generated composition.

The host reads source identity, version, and description from the Harmos declaration. It parses the complete registration slice, validates every domain, and atomically installs the resulting source-owned projection. Dependencies install in the order declared by #[dokime::requires].

The host inventories every catalog in a declaration pass that never runs initialize, so nothing may be registered there: initialize opens handles and reads the settings and secrets the host admitted, and nothing else. The artifact remains installed while a sidecar crashes, restarts, or backs off; replacement and unload operate on the whole source atomically.

One installed artifact may publish several suites. A #[dokime::suites] enum closes that set, and each suite names one #[dokime::templates] enum:

#[dokime::templates]
enum AcceptanceTemplates { Calibration }
#[dokime::suite(id = "meter.acceptance", label = "Acceptance", templates(AcceptanceTemplates))]
impl Acceptance {}

Each suite contains its complete template collection. A template has no independent registration path. Dokime installs the complete suite set atomically with the artifact and rejects duplicate suite or template ids.

At execution time the runtime resolves global parameters into concrete cases and dispatches the sidecar that registered the owning suite. Steps act and criteria judge through role-scoped generated contexts; a template’s own streams observe through the attempt’s own handles and the tick the engine asked them on. A channel the plugin measures for the whole process is a dokime::Sampler in the plugin’s samplers(Enum) catalog instead, with a lifetime of its own.

Terminal window
mise run check
mise run test

See Sidecars And External Processes for the direct Harmos sidecar declaration and Installed Artifact for the default composition.

A form can compare its answers with the existing serializable expression AST:

use dokime::expressions::BooleanExpression;
use dokime::prompts::{FormPrompt, TextPrompt};
let question = FormPrompt::<std::collections::BTreeMap<String, String>>::new(
"confirm.serial", "Confirm serial number",
)
.field(TextPrompt::new("serial", "Serial number").field())
.field(TextPrompt::new("repeat", "Repeat serial number").field())
.rule(
BooleanExpression::selector("serial").eq_selector("repeat"),
"Serial numbers must match",
);

Selectors name required answer fields directly. They read the submitted values, not defaults, runtime state, or invocation context. Optional fields cannot appear in rules. Field validation runs first; a failed rule leaves the question pending and the operator can correct the same draft. Cancellation remains separate from submitted negative values.

Rules support boolean composition, structural equality, numeric ordering, and text comparisons from the expression AST. Equality preserves value types (1 and 1.0 differ); numeric ordering compares integers exactly even above 2^53. Literals may be finite numbers, text, or booleans. Function calls and structured literals are rejected. Declarations allow at most 32 rules, each with at most 128 expression nodes and 16 levels, and require a failure message. The host validates declarations on installation and answers before recording a response. An open question retains its original rules across plugin replacement or unload; existing serialized definitions without rules have none.

Dokime markDokime · Stokker Technologies