{
  "uid": "cogitave.learn.build-a-diyar-solution.verdict-engines",
  "kind": "moduleUnit",
  "href": "/modules/build-a-diyar-solution/verdict-engines/",
  "title": "Verdict engines and the certified registry",
  "summary": "",
  "type": null,
  "products": [],
  "roles": [],
  "levels": [],
  "subjects": [],
  "headings": [
    "The problem a profile alone could not solve",
    "A narrow port, owned by the consumer",
    "The fail-closed certified registry",
    "Product engines: a frozen rule in its own crate",
    "The generic engines: a product with no new code",
    "The reserved-id rule against laundering"
  ],
  "source": "The process rule that decides pass or fail - a threshold hold, a pressure band, a cumulative-lethality integral - is not welded into the session loop. It plugs in through a narrow trait, and only a fixed, firmware-compiled list of engines may ever be named by a signed profile.\n\n## The problem a profile alone could not solve\n\nA device profile can describe new hardware, but until recently it could not describe a new *process*. The verdict logic was invoked directly by the session loop, and profile validation matched exactly one hard-coded engine id. A product whose hardware arrived with its own compliance rule had nowhere to attach it - signing a profile was not enough, because there was no seam to sign into.\n\n## A narrow port, owned by the consumer\n\nThe fix applies dependency inversion at the measurement engine, not at the certified oracle:\n\n```rust\npub trait VerdictEngine {\n    fn push(&mut self, reading: &[f64]) -> StepVerdict;\n    fn steps(&self) -> u32;\n}\n```\n\n`VerdictEngine` lives in `edge-measurement-engine` - the orchestrator that needs it - not in a frozen, byte-parity engine crate. That placement is deliberate: a local trait can be implemented for a foreign type, so a frozen, byte-parity product engine sitting in its own crate gets a pure forwarding `impl VerdictEngine`, and that frozen crate is untouched by the change. Putting the trait the other way around - inside the frozen crate, with the orchestrator depending on it - was considered and rejected: it would grow a zero-dependency, byte-parity crate an abstraction shaped by a future consumer's needs, and it would couple every future engine to that crate.\n\nThe engine is handed in as a **factory**, not a shared instance - it carries per-session state (step and hold counters), and each session gets a fresh one so no run's progress leaks into the next.\n\nEverything above the rule is unchanged and inherited by any engine that plugs into this seam: acquisition cadence, stop polling, fail-safe fault abort, the journal sink, and bounded step counts - and, from the worker, the frozen `Idle -> Armed -> Heating` safety typestate, the per-step guard, the quarantine cross-check, and every de-energize path. None of the safety logic moved; only where the rule comes from did.\n\n## The fail-closed certified registry\n\nSigning a profile is not enough to introduce a new rule. Profile validation checks the named engine against a table compiled into the binary:\n\n```rust\npub const CERTIFIED_ENGINES: &[EngineSpec] = &[\n    EngineSpec {\n        id: \"diyar:engine:threshold-hold;1\",\n        exact_channels: None, // a generic engine binds its inputs by name\n        // ...\n    },\n    // more certified engines follow, each with a stable id\n];\n```\n\nA signed profile naming an id this build does not recognize is rejected at load, and where an engine declares a required input width (`exact_channels`), that too is checked along with its id. Onboarding a new process rule is therefore three separate, explicit acts:\n\n1. an entry in `CERTIFIED_ENGINES`,\n2. a `VerdictEngine` implementation,\n3. that product's own certification evidence - its own parity oracle.\n\nA compromised or over-eager signer cannot put an uncertified verdict rule into production by signing a profile alone; the registry is compiled into the binary, and adding to it is a firmware release.\n\n## Product engines: a frozen rule in its own crate\n\nSome `CERTIFIED_ENGINES` entries are **product engines**. Each wraps one industry's own rule - often a frozen regulatory oracle it must reproduce byte for byte - and lives in its own crate, separate from the generic library below, because it exists to preserve one specific certified behavior rather than to be reused. A product engine's verdict trace, canonical evidence bytes, and chain head are held stable, and socketing it in behind the trait must not perturb them: a test asserts the certified path yields identical outcomes through both the old direct-construction entry point and the new socketed one. Diyar's first product engine is the ISPM-15 phytosanitary rule, worked through end to end in its own dedicated unit rather than here.\n\n## The generic engines: a product with no new code\n\nAlongside any product engines, the registry carries a small library of **generic** engines - certified code, compiled into the same protected crate, parameterized entirely by typed profile data:\n\n| Engine | What it does |\n| --- | --- |\n| `diyar:engine:telemetry-only;1` | records every step; has no pass condition, `passed` is always `false` |\n| `diyar:engine:threshold-hold;1` | passes after N consecutive in-band samples on a bound signal |\n| `diyar:engine:cumulative-dwell-lethality;1` | accumulates a lethal-rate integral over a run (for example steam F0, or a plain hold-above-threshold) |\n\nBecause these are already certified and already compiled in, a genuinely different product can be onboarded as **pure signed data** - a device profile and its app package - with no new crate and no new code. The platform's own stated, falsifiable bar for this claim: if roughly 95 of 100 product types cannot be onboarded this way, the design has failed.\n\n> [!NOTE]\n> `pressure-hold` (a pressure band held over Modbus TCP), `coldroom-hold` (a temperature band with no heat actuator at all), and `steam-sterilizer-f0` (an F0 lethality target) are the repository's internal proof fixtures for this claim - each a signed profile and app package, no product crate, run through a real session loop into a real evidence store. They are test/example fixtures that exercise the generic-engine pattern, not customer case studies or field-proven deployments.\n\nA parameterized engine is still not an escape hatch for an arbitrary rule: everything a profile hands an engine is a closed, tagged enum of typed scalars - there is no expression language, no formula string, no operator, and no field that references another field. The only way to extend what a generic engine can express is a new enum *variant*, which is a firmware event like any other `CERTIFIED_ENGINES` addition. Every params block is validated twice - once when the profile loads, once again in the engine's own constructor - so a caller that bypasses profile validation still cannot build a malformed engine.\n\n## The reserved-id rule against laundering\n\nBecause a generic engine serves many products, the evidence identity it stamps into its own records (`solution_id`) has to come from profile data, not a compiled constant. That opens a path a fixed-id engine never had to worry about: a signed profile could name a generic engine and stamp a reserved product id into its own evidence, producing records a downstream verifier might mistake for a certified product engine's output.\n\nThe registry closes this in both directions: every **fixed** id in `CERTIFIED_ENGINES` - a product engine's own id - is reserved, and a generic profile that claims one is refused at load. Symmetrically, a product engine owns its id and no profile may rename it. A regulator reading a solution id in the evidence chain can trust it names what it says it names.\n",
  "partOf": "cogitave.learn.build-a-diyar-solution",
  "durationInMinutes": 8,
  "quiz": null
}