HEART Embedded Systems logoHEART

Conformance statement#

Reading tracks3 Advanced
Advanced: the reference implementation of HEART Full. Which track am I?

heartd 0.6.0 against HEART API Standard 1.0 (draft) and HEART Core Standard 1.0 (draft).

Claimed: HEART Full, class Core-M, with modules control, properties, custom and core; presence when [presence] (passkey settings) is configured; and lifecycle, brain, inventory, config, telemetry, diagnostics, software, artifacts, schedules, privacy, ai, memory, evolving, security, platform, provisioning, peers, access, batch and native when their sections are configured; the Reasoning Gateway role (POST /heart/v1/reason) with [reasoning_gateway]. A module that is not configured returns 501.

Coverage: every one of the 124 operations in the API Index has an implementation: 118 in the Core (served by heartd run, and by WASM), and GET /heart/v1/systems with the five fleet operations in heartd gateway. Each is offered only where its level, module or role is configured, and answers 501 elsewhere.

Claimed by the implementer and tested only by conformance/run.py, not independently certified.

Implemented#

AreaStatus
Basic, Connected and Full operationsYes
CloudEvents envelope, published schemasYes, every response validated in the suite
Valve: levels, fail-closed, owner classifications, receiptsYes, matches the Python vectors
Approval gateYes, all 480 combinations (risk, physical, initiator, per-initiator allowlist, mode, level) match the vectors
Consulting a brain: Proposals, envelopes per argument, Trials and their states, one approval per plan, learned Playbooks (Core 7.1 to 7.7)Yes, 20 end-to-end tests in tests/trials.rs
Playbook adoption (status, adoptedPhysical, reviewBy on adoption)Yes
A person's proof of presence with their own request: client-chosen actionId, one call, 409 on reuse, 403 presence-invalid for another digest (API 6.3 rules 1 and 3)Yes, on invoke and every gated operation (PUT /properties/{name}, POST /lifecycle/{operation}, PUT /tending/autonomy, PUT /tending/rules/{id}, PUT /custom/{name}, PUT /brain/offline, POST /brain/restore, POST /presence/methods); heartd reads presence on POST, PUT, PATCH and DELETE
The approver is never the initiator, a person included (API 6.3, Part 6 3.2)Yes, in the Core and at the Gateway; the initiator may still deny or cancel
Module presence: list, enrol and remove passkeys (API 13.2 rule 13)Yes for webauthn; enrolled passkeys persist in the Core's state and are verified beside those in the configuration (source: "config"), the first one through the local interface
One autonomy ceiling: PUT /tending/autonomy sets max 0–5, the mode follows from the current level; lowering max to 2 or below has the effects and scope of POST /tending/stop; raising it is destructive, heart.admin, a person only (API 13.4)Yes
/serve: "stop the conveyor" maps to that thing's halt; only "stop", "stop autonomy" and the like run Stop (Core 6.2)Yes
Fault codes on alerts: code on every alert, kind alongside where an old kind fits; POST /arrhythmias reads code (Part 3, Section 5)Yes
Security Advisory HEART-2026-001: proof of presence, authority changes, audience and DPoP, signed Cards and Gateway headers, salted receiptsYes (MQTT commands not applicable: no MQTT binding)
Ports 43278 and 7685, fallback, system.port.fallbackYes
TLS, refusal of plain HTTP off loopbackYes
Unix socket with restricted permissionsYes
OAuth 2.0 access tokensYes, as Ed25519 JWTs
Server-Sent Events on Rhythm, with resumeYes
Webhooks: handshake, RFC 9421 signature, clearance filteringYes
Custom definitions, injection rules, guards, interlocks, previewYes
Serve, grounding, confirmation of every changeYes
Tiers Reflex and RecallYes
Tiers Local and CloudThrough a Reasoning Gateway, which heartd can also be; tested with mock providers only
Stop, autonomy modesYes
Crash recovery, atomic state, damaged-state start, watchdogYes, tested by killing the process
Bounded logs and stores, drop orderYes
Compiles for wasm32-unknown-unknown and thumbv7em-none-eabihfYes (heart-core), and WASM run in Node
Operations outside the Card's level and modules return 501Yes
heart:// address resolution with fallback orderYes, heart_core::address
Host feed for the product's own eventsYes: Unix socket in heartd, heart_report in WASM
Gateway: joining by Card, names tied to sender, ID and alias pathsYes
Gateway: routed (Unix socket with verified peer user, TLS with signed caller headers) and pushed systemsYes
Gateway: forwarding headers stripped and never trusted from clientsYes
Gateway: its own Card, Pulse and Flatline alertsYes
Gateway: CORS for allowed browser origins, socket directory and address limitsYes
Gateway: a pinned identity key moves only on a valid rotation statement signed by it (API 9.3 rule 3, 10.1 rule 7)Yes
Module inventory: GET /components (parents first, paged), /components/{id}, /dependencies (API 13.2 rule 14)Yes, from [inventory]; a part's status from its open alerts; dependencies with a probe are tried by heartd and raise or resolve connectivity.dependency-unreachable
Module config: GET, PATCH (JSON Merge Patch), POST /config/validate, GET /config/history, POST /config/rollback (API 13.2 rule 2)Yes, from [config]: the whole result checked before approval (400, 422 out-of-range, 403 above the caller's clearance or never-share); write through the gate with actionId/presence, allowlist entries "PATCH /heart/v1/config" with a per-setting envelope, and Stop; Idempotency-Key required and kept; applied by the host (Effect::ApplyConfig) before the revision exists; per-setting privacy through the Valve; bounded history with gap
Module telemetry: GET /metrics, /metrics/{name} (from, to, step), /logs (since, until, minSeverity, component, cursor, and Server-Sent Events) (API 13.2 rule 15)Yes, from [telemetry]: metrics from vitals and numeric properties in bounded rings; logs from every Rhythm event, the host feed, a followed file or a journald unit, bounded, with secrets removed before they are kept
Module diagnostics: GET /diagnostics, POST /diagnostics/{test}, POST /diagnostics/bundle (API 13.2 rule 5)Yes, from [diagnostics]: programs or native requests with time limits (Effect::Diagnostic); a disruptive test is write, through the gate, answered with its Action, cancellable and stopped by Stop; one run of a test at a time; bounded runs; bundles scoped by since, until, include, filtered for the caller's clearance (never above private) and bounded by maxBytes
Module software: GET /software, /software/sbom, /software/updates, /software/updates/{id}, POST /software/updates, POST /software/rollback (API 13.2 rule 4)Yes, from [software]: packages and the running heartd; an SPDX or CycloneDX SBOM for internal callers; updates from a directory or an https source, each manifest's Ed25519 signature verified by the Core against update_keys (signed in the list), then the payload's size and SHA-256, before install_command runs; unsigned, forged or tampered updates answer 403 signature-invalid and install nothing; installing is destructive (proof of presence or another person's approval), cancellable and stopped by Stop while it downloads, never while it installs; a failed install runs rollback_command and ends rolled-back; POST /software/rollback (write) returns to the version before the last update; one update at a time; bounded records
Module artifacts: GET, POST /artifacts, GET, DELETE /artifacts/{id}, GET /artifacts/{id}/content (API 13.2 rule 16)Yes, from [artifacts]: base64 uploads with kind, mediaType, privacy (at most the caller's clearance, never never-share), optional sha256 and expiresAt; files on disk, mode 0600, bounded in count, total bytes and file size; a file above the caller's clearance is not listed and answers 404; content checked against its digest before it is sent, with ETag and one byte range (206, 416); expired files deleted; write through the gate
Module schedules: GET, POST /schedules, GET, DELETE /schedules/{id} (API 6.3, 13.2 rule 17)Yes, from [schedules]: at or five-field cron (UTC), a minimum interval, bounded schedules and runs; the operation and the author's scope checked first; creating has the risk of what it schedules, at least write, destructive for a maintenance window, never allowlisted; a schedule by ai, peer or rule is proposed until a person approves it; each run is a new Action with scheduleId that passes the gate as the author at that moment; under Stop no run that changes anything starts; late runs are missed; delaySec on a lifecycle request becomes a schedule
Module privacy: GET, PUT /valve/policy, GET, POST /consents, DELETE /consents/{id}, POST /data/export, POST /data/erase (API 4, 13.2 rule 6)Yes, from [privacy]: the Valve policy (llmCeiling, classifications, retentionDays) is what the Valve filters with; tightening is write, loosening destructive (proof of presence or another person's approval), never allowlisted; llmCeiling: private needs a granted consent; private data reaches a model (and, when configured, a subscriber) only while a consent covers it, and withdrawal filters the next transfer at once; export and erasure by the person or an administrator acting for them; an export is filtered for the caller's clearance, bounded, and stored as an artifact of kind export with [artifacts]; erasure is destructive, reaches Memory, consents, ratings, receipt links and stored exports, writes [erased] in Actions, Rhythm and logs, and keeps only a record with a keyed digest
Module ai: GET, PUT /ai/policy, GET /ai/usage (API 7, 13.2 rule 18)Yes, from [ai]: modes, providers, regions, uses, daily and monthly budgets, the most per call and fallback; any change is destructive (API 6.3); with [brain] only org-proxy; the policy applies to the next call (a use or provider it does not allow is answered from rules; a budget it would pass answers 429); usage per day (calls, tokens, cost) bounded
Module memory: GET /memory, POST /memory/search, DELETE /memory/{id} (API 8.1, 13.2 rule 19)Yes, from [memory]: messages received at the inbox (short-term, at their own level, at least internal) and Trial outcomes (long-term); an item above the caller's clearance is not listed, found or deleted (404); search by every word, best first, at most 100; bounded, expiring and within retentionDays
Module evolving: PUT /evolution/pins/{purpose}, POST /evolution/rollback, GET /evolution/lessons, POST /feedback; and GET /evolution (API 11, 13.2 rule 11)Yes, from [evolving]: a pin change is destructive, runs the regression suite first (Effect::Regression, heartd's regression_command), and pins only if it passes; evolving.model.adopted or evolving.model.rejected in the Rhythm; the suite's Action is cancellable and stopped by Stop; rollback (write) returns to the previous pin once; the pin for each use is sent as modelPolicy.pinned; lessons are learned Playbooks; ratings bounded
Module security: GET /security, GET /security/attestation, POST /security/rotate, GET /audit (API 10.1, 13.2 rule 7)Yes, from [security]: the summary per clearance; evidence signed by the identity key over the caller's nonce (software and digests, configuration and policy revisions), internal only; rotation is destructive, with a statement signed by the old key, Heart-Key-Rotation on every Card and both keys in the key set for 7 days, a second rotation refused meanwhile, kept across restarts; a bounded audit log chained by SHA-256 over RFC 8785 JSON, of every change, refused read and denied Action, without bodies
Module platform: GET /platformYes, from [platform] and the host feed; serial and location private
Module provisioning: POST /provisioning/claim, POST /provisioning/transfer (API 10.1 rule 3, 13.2 rule 8)Yes, from [provisioning]: the code compared in constant time, 5 wrong codes lock claims for an hour (kept across restarts), the code replaced after use and written only to claim_code_file; the owner's key then signs the only tokens accepted; transfer with the owner's proof of presence ends the old owner's tokens at once and removes presence methods, subscriptions, consents and schedules; a factory reset makes the system claimable
Module peers: GET /peers, PUT, DELETE /peers/{id} (13.2 rule 20)Yes, from [peers]: trust is destructive; blocked peers refused, clearance capped, inbox types limited; messages never start an Action
Module batch: POST /batch (13.2 rule 9)Yes, from [batch]: each item through the same route as alone (scope, clearance, gate, its own proof, an item key from the batch's), effects awaited in turn, stopOnError, bounded, no nesting, the whole answer kept under the batch's key
Module native: POST /native/{protocol} (14.6)Yes, from [native]: a person with heart.native and private clearance only, always destructive, reason required, listed protocols and targets only, read only unless allow_writes, never a write to a guarded or physical address, logged by digest
Module access: GET /access/grants, PUT, DELETE /access/grants/{principal} (API 8.2, 13.2 rule 21)Yes, from [access]: the five roles as fixed scopes and clearances; granting or widening destructive (proof of presence or another person's approval), narrowing or removing write, never allowlisted, a person only; the first grant is an owner's and the last owner grant stays; expiry; bounded; every caller (token, Gateway-forwarded, command channel) held to its grant by heartd, a caller without one peer/public/no scopes; certificate.sanUri kept as the mutual-TLS table (Core::certificate_caller)
POST /provisioning/release and the table of effects (API 13.2 rule 8)Yes: release with the owner's proof of presence removes the owner, presence methods, grants, subscriptions, consents and schedules, cancels waiting Actions and writes a new claim code to claim_code_file; transfer does the same for the old owner; a factory reset also removes peers and erases Memory, consents, ratings, receipt links, erasure records and every other Action, and makes a new claim code; a decommission does so and can never be claimed again; Effect::ClaimCode says owner_changed so a host renews its HEART Hub identity, and heartd makes a new Basic owner key
One approval for a fleet command (API 13.3, Part 6 8)Yes: the Gateway's Action digest is the fleet digest over the command, operation, arguments, physical, selector, sorted targets and rollout; each system gets a new actionId and a fleet approval the Gateway signs for it; the Core accepts it only with the pinned key, as a target, for exactly that request, at most 300 s old, from someone other than the caller, and for a physical Action only with fleet_physical; gate, guards and Stop still apply; presenceMethod: fleet in the decision
Command channel for pushed systems (API 9.3)Yes, with [uplink] commands: the Card is pushed without endpoints, long polls with Prefer: wait, commands signed by the Gateway key, checked for system, freshness (60 s) and replay, run through the API and gate as from the Gateway, answered as heart.reply; the Gateway queues, waits 30 s and relays, never decides
Basic credential profile: the owner key (API 8.2)Yes, with [auth] owner_key_file on a Basic Card only: 256 random bits, printed once, kept as SHA-256 (mode 0600), compared in constant time, heartd owner-key replaces it at once, never sent anywhere
Polled values reach rules, Trials and metrics (Core 4.3, 7.3)Yes: a value the Core reads itself (Modbus, process) is a sample for Trials' expect and undoIf, fires Tending rules that watch it, and is a metric point, as a host-feed value is
PUT /tending/allowlist (6.3, 13.2 rule 10)Yes: widening destructive, narrowing write, never allowlisted, entries checked, kept in the state file, shown in heart.control
Reasoning Gateway: POST /reason (7.2)Yes, with [reasoning_gateway]: OpenAI-compatible and Anthropic Messages upstreams, keys only in heartd, the Gateway's ceiling (valve-blocked), the pin honoured, fallback, maxCostUsd and a daily budget per caller (429), instructions apart from escaped untrusted context, the Reason format with provider, model, fallback and cost; a provider's error text is never passed on. Tested against local mock providers only
Idempotency-Key (API 5.5): required on every POST whose risk is not read (a self-test only when it is disruptive, a plain /serve request optional), except acknowledge, resolve, silence and a decision; optional on every PUT, PATCH and DELETEYes: per principal and operation, the original answer returned (a waiting Action's retry returns that Action), 422 for another body, 409 while the first still runs, released by a restart
The Action operations follow the Actions (API 2): GET /actions, GET /actions/{id} and the decision are offered by every system that can create an Action, whatever its levelYes: at Connected and Full, and at Basic with any module that changes something; 501 otherwise
Alerts and Actions listed newest first, with a stable cursor (API 5.5)Yes: ordered by detectedAt or createdAt, then ID; the cursor is the last item's time and ID
Silencing mutes notifications, not alerts (API 5.1): 60 s to 30 days; repeats raised and listed with silencedUntil, not notified; maintenance mutes the same way (13.2 rule 3)Yes
A property above the caller's clearance answers 404, for reading and writing; a read-only one 409 conflict (API 5.6)Yes, checked before the gate
act names the intermediary (API 8.2): a token whose only actors are marked HEART Gateways keeps the subject's kind; any other actor makes the caller ai without heart.approveYes, in heartd
PUT /brain/offline: off with heart.tend, on also needs heart.admin (13.2 rule 12)Yes
A HEART Full Card lists control (API 2.1)Yes: heartd refuses to start with one that does not
The lease renewal sends the device's maxLeaseSec (HEART Command Center 1.0, Section 3)Yes
Event types system.lifecycle.* and tending.silence.ended (Part 3, Section 5.8)Yes

Not implemented#

RequirementLevelNote
Reading current values through a matching property before a TrialMAY (Core 7.5)The Core uses the values it last set and those the product reports with Core::setting
Native adapters other than modbus-tcp and processMAYThe others in the standard return an error. Passthrough (module native) opens modbus-tcp only
Claiming by SPAKE2+SHOULD (10.1 rule 3)The code travels in the request body; local_only keeps claims to the local interface
Rotating the TLS certificate through the APIMAYPOST /security/rotate rotates the identity key; the certificate files are the host's
Pinning a peer's keyMAYA peer is known by the principal it calls with
Mutual TLSMAYOAuth with DPoP is offered instead. Grants keep the certificate table, which a host terminating mutual TLS reads with Core::certificate_caller
Renewing a HEART Hub identity after a change of ownerModule rule (13.2 rule 8)heartd does not report to a HEART Hub; Effect::ClaimCode tells a host that does (owner_changed)
Destroying the identity key on decommissionModule rule (13.2 rule 8)The host's [lifecycle.decommission] command does it; the Core erases and refuses any claim
Erasing Rhythm and audit entries on a factory resetModule rule (13.2 rule 8)Memory, consents, ratings, receipt links, erasure records and Actions are erased; the Rhythm and the audit chain, the system's own record, stay
Proof of presence by CIBA, a device button or a remote siteMAYOnly WebAuthn passkeys are verified; enrolling ciba answers 501
Checking a passkey's attestation when it is enrolledSHOULDThe credential's public key is taken as sent
Passkey signature counters across restartsSHOULDKept in memory only
Webhook retries for 24 hoursSHOULDThree attempts, then suspended
traceparent propagationSHOULD
ETag and Cache-Control on the CardSHOULD
Streaming on /properties/{name}Module ruleReading works, streaming does not
Failing pending Actions when a definition is deletedModule ruleThey fail at run time instead
Marking timestamps unsynced on a bad clockMUST (Core 9.11)Gap. Sequence numbers do continue
CPU ceiling inside the processMUST (Core 9.4)Delegated to systemd, see packaging/
Signed updates of Index and PlaybooksMUST (Core 9.15)Gap. They load from local files
Regression tests before adopting a model without the evolving moduleMUST at FullWithout [evolving], pins come from [model] and cannot change through the API, so no model is adopted untested
Currencies other than US dollars, and days other than UTC, in the AI policyMAYAs the standard says: US dollars and UTC
Consents matched by purposeMAYA consent covers its recipient (a kind or a name); its purpose is recorded
requiresMaintenance guardPartialPasses during maintenance: lifecycle/maintenance-on or a maintenance window (module schedules); otherwise fails closed
Time zones other than UTC in a scheduleMAYRefused with 400 invalid-request
Automatic update policies (updatePolicy.auto other than none)MAYUpdates install only on request
Interlocks not in the form <property> <op> <value>PartialFail closed
Argument schemas beyond the supported subsetPartialRefused when the definition loads
heart_core.h C interfaceCore-SNot implemented
Core-S class (no allocation after startup)Core-Sheart-core needs an allocator
Local model in the Model SlotOptionalThe interface exists, no model is bundled
Gateway: subscriptions on behalf of pushed systemsGateway rule501; inbox on their behalf works
Gateway: state is rewritten whole when it changesScaleFine to a few thousand systems; a log-structured store would go further
Gateway: Flatline uses arrival time, not the Pulse's own timeRule 5.3Arrival time cannot be forged by the sender, but is not what the rule says
Metric points and log entries across a restartScaleKept in memory only; revisions and self-test runs are in the state file
Support bundle as an artifact (artifactId)MAYThe bundle is returned in the answer, as bundle; module artifacts is not offered
since and until on a log streamModule ruleA stream follows minSeverity and component; since and until apply to pages
A non-disruptive test that runs longer than 61.5 sTransportheartd answers 503; the run still ends and is listed

The two gaps mean this implementation does not yet fully meet its claimed level. They are tracked as open work.