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.
Declaration identities
Section titled “Declaration identities”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
Section titled “Composition”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.
Suites
Section titled “Suites”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.
Validation
Section titled “Validation”mise run checkmise run testSee Sidecars And External Processes for the direct Harmos sidecar declaration and Installed Artifact for the default composition.
Cross-field answer rules
Section titled “Cross-field answer rules”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.