Changelog#
This project follows Keep a Changelog and Semantic Versioning.
[Unreleased]#
The four first fixes of the API audit: a person's proof of presence in one call, presence methods, fault codes on alerts, and one Stop. Then the modules inventory, config, telemetry and diagnostics (14 operations that answered 501), the modules software, artifacts and schedules (15 more), and the modules privacy, ai, memory and evolving (17 more), with a real GET /evolution. Last, the modules security, platform, provisioning, peers, batch and native, PUT /tending/allowlist, and a Reasoning Gateway role for POST /reason (14 more): every operation of the API Index now has an implementation. Then the six second-priority fixes of the audit: one approval for a fleet command, commands to pushed systems behind NAT, the Basic owner key, grants and roles, polled values reaching the rules, and the effects of every change of ownership with POST /provisioning/release.
Changed (one autonomy ceiling)#
GETandPUT /heart/v1/tending/autonomy(getTendingAutonomy, putTendingAutonomy) take the place ofGET/PUT /tending/mode. A person sets one ceiling{"max": 0..5};autonomy.currentis the lowest ofmax, the promised level and what the system can do now;modeis read only and follows fromcurrent(0–2observe, 3suggest, 4–5act). Loweringmaxneeds onlyheart.tendand no approval, and to 2 or below it is Stop; raising it isdestructive, needsheart.adminand comes only from a person (403 capability-not-allowedotherwise). Stop setsmaxto 2.heart.controland Pulseautonomycarrymaxandcurrent; the event istending.autonomy.changed; attestation claims carrypolicies.autonomyMax.- Configuration:
Config::autonomy_maxand heartd[core] autonomy_max(default 5) set the starting ceiling.
Changed (consistency fixes)#
- Action operations at every level that creates Actions (API Standard 2):
GET /actions,GET /actions/{id}and the decision answer at Connected and Full, and at Basic with any module that changes something;501only where no Action can exist. API Index and OpenAPI give them levelbasic. Idempotency-Key(API Standard 5.5): required on every non-readPOSTexceptacknowledge,resolve,silenceand a decision (postLifecycleOperation,postBrainRestore,postArrhythmias,createSubscription,postTendingProposals,postPresenceMethodsand the rest now check it); optional onPUT,PATCHandDELETE, where a key still returns a waiting Action on retry.- Silence (API Standard 5.1): up to 30 days (a longer one answers
400),silencedUntilset on the alerts of that code, and repeats are raised and listed but not notified. Maintenance mutes notifications the same way.DELETEremovessilencedUntil. - Paging (5.5): alerts and Actions newest first, with a cursor of the last item's time and ID;
nextCursoris set when more remain. - Properties (5.6): one above the caller's clearance answers
404for reading and writing, checked before the gate; a read-only one409 conflict, also before the gate. PUT /brain/offline: the API Index scope isheart.tend;heart.adminalso works, and turning auto mode on needs it (403).- Event names (Part 3, Section 5.8):
system.lifecycle.started,.done,.failed,.cancelled,.maintenance-ended,.maintenance-window, andtending.silence.ended. - Native passthrough refused for
ai,peerandruleanswerscapability-not-allowed(a caller's own request);caller-not-allowedis only for Proposals. - heartd: a token's
actchain makes the calleraiunless every actor is a HEART Gateway markedheart_gateway: true, so a person working through a Gateway stays a person (API Standard 8.2); a HEART Full Card without thecontrolmodule is refused at start; the brain lease renewal sendsmaxLeaseSec. Thephoto-editorexample listscontrol. conformance/run.pysends anIdempotency-Keyon every non-readPOSTunless a test checks its absence.
Added#
- One approval for a fleet command (API Standard 13.3, Part 6 Section 8). The Gateway Action's digest is the fleet digest, over
{command, operation, arguments, physical, selector, targets, rollout}with the targets sorted; a person approves it once with proof of presence. Each system then gets the operation with a newactionIdandpresenceof methodfleet, signed by the Gateway for that system alone. The Core (heart_core::fleet) accepts it as the approval of its own Action only if the Gateway key it pinned signed it, the system is a target, the request is exactly the one approved, it is at most 300 seconds old, and the approver is not the caller; a physical Action also needsfleetPhysical. Its gate, guards and Stop still apply, and the decision recordspresenceMethod: fleet. A fleet request may sayphysical. heartd:[core] fleet_physical, andgateway_public_keypins the key for fleet approvals too. - Command channel for pushed systems (API Standard 9.3). With
[uplink] commands = true, heartd pushes its Card without endpoints, long-polls the Gateway's inbox withPrefer: wait, and runs each command the Gateway signed (checked for signature, system, freshness of 60 seconds and replay) through its own API and gate, answering with aheart.reply. Forged, stale, replayed and misdirected commands are dropped unanswered and recorded assecurity.command.refused. The Gateway relays any request it cannot answer from what was pushed, waits up to 30 seconds, and answers501without a live channel. - Basic credential profile, the owner key (API Standard 8.2).
[auth] owner_key_fileon a Basic Card: heartd makes a 256-bit owner key at first start, prints it once, keeps only its SHA-256 (mode 0600), compares in constant time, and treats its holder as the owner (human,private, theownerrole's scopes).heartd owner-key <config>replaces it; the old key stops at once. A Card above Basic with an owner key is refused. - Module
access(API Standard 8.2, 13.2 rule 21):GET /heart/v1/access/grants,PUTandDELETE /heart/v1/access/grants/{principal}. Rolesowner,installer,maintainer,operatorandviewer, each a fixed set of scopes and a clearance (heart_core::access::ROLES). Granting or widening isdestructive, narrowing or removingwrite, by a person only and never allowlisted; the first grant is an owner's, and the last owner grant stays. While any grant exists, heartd holds every caller to its own (Core::apply_grant); a client certificate maps throughCore::certificate_caller. heartd's[access]. POST /heart/v1/provisioning/release(API Standard 13.2 rule 8): the owner gives the system up with proof of presence. It removes the owner, presence methods, grants, subscriptions, consents and schedules, cancels waiting Actions, and makes a new claim code shown only at the device.- Tests:
tests/ownership.rs(7 tests: grants and roles, fleet approvals with forged, stale, misdirected, self-approved and physical cases, release, factory reset and decommission, a rule and a Trial on polled values), unit tests for roles, grant widening, fleet digests, the fleet object and channel commands; one heartd test of the new sections; two end-to-end tests inconformance/run.py(release and claim again with grants; the owner key) and three inconformance/gateway.py(one approval across three systems, routed and pushed, with forged and stale approvals refused; a command through a pushed system's channel; forged, stale, replayed and misdirected commands dropped). - Module
security(API Standard 10.1 and 13.2 rule 7):GET /heart/v1/security(key version, identity key, owner, TLS, presence methods, update keys, audit size),GET /heart/v1/security/attestation?nonce=(evidence signed by the identity key over the caller's nonce, the software running with its digests, the configuration revision and the policy revisions;internalclearance),POST /heart/v1/security/rotateandGET /heart/v1/audit. Rotation isdestructive: the host makes a new identity key and a rotation statement signed with the old one (Effect::RotateKey); for 7 days every Card carriesHeart-Key-Rotation, the old key stays in the key set, and a second rotation answers409, so no peer is cut off. The audit log is bounded and chained by SHA-256 over RFC 8785 JSON: every request that may change something, refused reads, and denied, refused or expired Actions; never a body, and a native request only by its digest. Filterscategoryandoutcome, andgap. - Module
platform:GET /heart/v1/platformfrom the configuration and the host's reports of power, network and clock ({"platform": {...}}on the host feed,Core::set_platform), through the Valve. - Module
provisioning(13.2 rule 8):POST /heart/v1/provisioning/claimandPOST /heart/v1/provisioning/transfer. A claim is proved by the code from the box, compared in constant time, and runs at once; after 5 wrong codes claims answer429for an hour, across restarts. It records the owner's key and issuer (Core::owner_authority), and the claim code is replaced and handed to the host (Effect::ClaimCode). A transfer needs the current owner's proof of presence; it moves the authority at once and removes presence methods, subscriptions, consents and schedules, cancels waiting Actions and replaces the claim code. A factory reset makes the system claimable again. - Module
peers(new 13.2 rule 20):GET /heart/v1/peers,PUTandDELETE /heart/v1/peers/{id}. Trusting a peer isdestructive. A blocked peer is refused; a listed peer's clearance is capped by its entry (publicwhilepending);typeslimits what the inbox takes from it. Messages stay data. - Module
batch(13.2 rule 9):POST /heart/v1/batch. Each item goes through the same route as a request sent alone: scope, clearance, peer trust, the gate with its own risk, its ownactionIdand proof of presence, and anIdempotency-Keymade from the batch's. An item that needs the host waits for its effect, and the batch goes on. At most 100 items,limits.maxBatchor the module'smaxItems;stopOnError; no batch in a batch. - Module
native(14.6):POST /heart/v1/native/{protocol}. Only a person withheart.nativeandprivateclearance; alwaysdestructive;reasonrequired; only the protocols and targets listed; read only unless writes are turned on, and never a write to an address a custom definition marksphysicalor guards (409 guard-failed).processis never open. PUT /heart/v1/tending/allowlist(6.3, 13.2 rule 10): widening isdestructive, narrowingwrite, and no allowlist covers it; entries are checked (initiators, envelopes, never a destructive or physical capability). The allowlist is kept in the state file until the configured one changes, andheart.controlshows it withapprovalTimeoutSec.- Reasoning Gateway (7.2):
POST /heart/v1/reasonwhenConfig::modules.reasoningis set. The Core checks the Reason request, refuses context above the Gateway's ceiling (valve-blocked), honoursmodelPolicy(pin, fallback, regions,maxCostUsd,maxOutputTokens), keeps a daily budget per caller, puts the owner's instructions in the system prompt and every context item and the conversation in escaped, marked blocks, and answers in the Reason format with provider, model, fallback and cost. The host adds the provider key (Effect::Reason); a provider's error text is never passed on. - heartd:
[security],[platform](with/etc/os-release,/proc/meminfoand a clock-sync file),[provisioning](claim_code_file, rewritten mode 0600),[peers],[batch],[native]and[reasoning_gateway](OpenAI-compatible and Anthropic Messages upstreams, keys from an environment variable or a file). The identity key rotates withidentity.key.previouskept for the overlap and both keys in the key set; uplink pushesheartkeyrotation; the Gateway moves a pinned key on a valid rotation statement and on nothing else. Once claimed, only the owner's key signs accepted tokens. Each batch item's proof of presence is verified. In WASM, the new effects come out asattest,rotate-key,claim-codeandreason. - Tests:
tests/trust.rs(16 tests) and unit tests for widening the allowlist, claim codes and thumbprints, native reads and writes, query decoding and prompt escaping; three heartd tests (the new sections, a rotated key reloaded, and the Reasoning Gateway against a local mock provider); nine new end-to-end tests inconformance/run.pyand one inconformance/gateway.py, every reply checked against the schemas.
- Module
privacy(API Standard 4 and 13.2 rule 6):GETandPUT /heart/v1/valve/policy,GETandPOST /heart/v1/consents,DELETE /heart/v1/consents/{id},POST /heart/v1/data/exportandPOST /heart/v1/data/erase. The Valve policy (llmCeiling, classifications,retentionDays) is the one the Valve filters with, kept in the state file until the Card'svalvechanges. Tightening it iswrite; loosening it isdestructive.llmCeiling: privateneeds a granted consent formodel-provider, and while none is granted private data does not reach a model; withconsentRequired: ["subscriber"]the same holds for webhook deliveries. Withdrawing a consent filters the next transfer at once. The person, or a person withheart.adminacting for them, exports their data (Memory, consents, Actions, ratings, receipts of transfers made for them, Rhythm and log entries that name them, earlier erasures), filtered for the caller's clearance and bounded; withartifacts, as an artifact of kindexportthat expires. Erasing isdestructive: it removes the person's Memory, consents, ratings, receipt links and stored exports, writes[erased]for their identifier in Actions, Rhythm and logs, and keeps only a record with a keyed digest of the identifier, which a later export shows. - Module
ai(API Standard 7 and 13.2 rule 18):GETandPUT /heart/v1/ai/policyandGET /heart/v1/ai/usage. The policy's modes, providers, regions, uses, daily and monthly budgets, the most per call and fallback apply to the next model call: a use or provider it does not allow is answered from rules, a call that would pass a budget answers429 budget-exceeded, andmodelPolicycarriesallowFallback,maxCostUsd,regionsand the pin. Any change isdestructive(API Standard 6.3); with thebrainmodule the only mode isorg-proxy. Usage per day (calls, tokens, cost, budget) is kept bounded and listed asheart.usage. - Module
memory(API Standard 8.1 and 13.2 rule 19):GET /heart/v1/memory,POST /heart/v1/memory/searchandDELETE /heart/v1/memory/{id}. Messages received at the inbox are kept as short-term items at their own level (at leastinternal), so inbox data can be read back; how each Trial ended is kept as a long-term item. An item above the caller's clearance is not listed, found or deleted. Bounded, expiring, and within the Valve policy'sretentionDays. - Module
evolving(API Standard 11 and 13.2 rule 11):PUT /heart/v1/evolution/pins/{purpose},POST /heart/v1/evolution/rollback,GET /heart/v1/evolution/lessonsandPOST /heart/v1/feedback. A pin change isdestructiveand runs the system's regression suite first (Effect::Regression); the model is pinned only if it passes, and the Rhythm recordsevolving.model.adoptedorevolving.model.rejected. The suite's Action is cancellable, and Stop cancels it. Rollback (write) returns to the previous pin once. Lessons are the Playbooks learned from Trials. Ratings of an Action, an answer, a Trial or a lesson are kept bounded.GET /heart/v1/evolutionnow shows the pin for each purpose with its regression result, the candidates tested and how many lessons exist; the pin for each use is sent with each model call. - heartd:
[privacy],[ai],[memory]and[evolving](withregression_command);heart_core::log::BoundedLog::values_mut. In WASM, the new effect comes out asregression. - Tests:
tests/rights.rs(23 tests) and unit tests for erasing a word, loosening the Valve and widening the AI policy; the hostile-request property intests/prop_robust.rsreaches the four modules; five new end-to-end tests inconformance/run.py, every reply checked against the schemas. - Module
software(API Standard 13.2 rule 4):GET /heart/v1/software,/software/sbom,/software/updates,/software/updates/{id},POST /software/updatesandPOST /software/rollback. The host lists what the update source offers (Effect::UpdateCheck); the Core verifies each manifest's Ed25519 signature over its RFC 8785 JSON againstConfig::modules.software.keys, then the downloaded payload's size and SHA-256 (Effect::UpdateDownload), and only then asks the host to install (Effect::UpdateInstall). Unsigned, forged and tampered updates answer403 signature-invalid, raisesecurity.update.refusedand install nothing. Installing isdestructive; the Action is cancellable, and stopped by Stop, until it installs. A failed install is rolled back.POST /software/rollback(Effect::Rollback) returns to the version before the last update. heartd's[software]reads an SBOM, a directory orhttpsupdate source,update_keys,install_commandandrollback_command, and lists itself as a package. - Module
artifacts(API Standard 13.2 rule 16):GETandPOST /heart/v1/artifacts,GETandDELETE /heart/v1/artifacts/{id}andGET /heart/v1/artifacts/{id}/content. Uploads carry the file in base64 withkind,mediaType,privacy,sha256andexpiresAt; the store is bounded in files, total bytes and file size; a file above the caller's clearance is not listed and answers404; content is checked against its digest before it is sent, withETag, and heartd answers one byte range with206. The bytes live with the host (Effect::ArtifactPut,ArtifactGet,ArtifactDelete); heartd keeps them in[artifacts] dirand removes files nothing refers to at start. - Module
schedules(API Standard 6.3 and 13.2 rule 17):GETandPOST /heart/v1/schedules,GETandDELETE /heart/v1/schedules/{id}. A schedule holds one operation at a time (at) or repeatedly (five-fieldcron, in UTC), or declares a maintenance window, and keeps its author with their scopes and clearance. Creating it has the risk of what it schedules, at leastwrite, anddestructivefor a maintenance window; no allowlist covers it; one made byai,peerorruleisproposeduntil a person approves it. Each run is a new Action withscheduleId, passed through the gate as the author at that moment: a destructive run waits for a person, a peer's or AI's run follows the mode and the allowlist as they are then, and under Stop no run that changes anything starts. Runs are recorded and bounded; a run too late ismissed.delaySecon a lifecycle request now becomes a schedule. Idempotency-Keyis required on the new writes, and accepted on everyDELETE: a retry of one that waits for approval returns the same Action.heart_core::codec(base64);Core::artifact_ids,Core::installed_version;heart_core::time::civil_from_days. In WASM, the new effects come out asupdate-check,update-download,update-install,rollback,artifact-put,artifact-getandartifact-delete.- Tests:
tests/manage.rs(21 tests) and unit tests for cron, payload names and base64; two heartd tests of the new sections and of byte ranges; seven new end-to-end tests inconformance/run.py, with a signed update source, every reply checked against the schemas. - Module
inventory(API Standard 13.2 rule 14):GET /heart/v1/components(parents before children, paged),GET /heart/v1/components/{id}andGET /heart/v1/dependencies, fromConfig::modules.inventory. A part'sstatuscomes from its open alerts.Core::dependency_statusraises or resolvesconnectivity.dependency-unreachable; heartd's[inventory]probes each dependency'shost:port. - Module
config(API Standard 13.2 rule 2):GETandPATCH /heart/v1/config(JSON Merge Patch),POST /heart/v1/config/validate,GET /heart/v1/config/historyandPOST /heart/v1/config/rollback. Settings declare type, range, default and privacy; the whole result is checked before anyone approves it; every change is a revision, applied first by the host (Effect::ApplyConfig); history is bounded and saysgap. Writes pass the gate (write), takeactionIdandpresence, match allowlist entries"PATCH /heart/v1/config"with an envelope per setting, and are refused forai,peerandruleafter Stop.never-sharesettings are never returned or changed through the API. heartd's[config]writes a settings file and runs an apply command. - Module
telemetry(API Standard 13.2 rule 15):GET /heart/v1/metrics,GET /heart/v1/metrics/{name}withfrom,toandstep, andGET /heart/v1/logswithsince,until,minSeverity,componentand a Server-Sent Events stream. Metrics come from vitals and numeric properties, logs from every Rhythm event and from the host (Core::host_log,{"log"}on the host feed, heartd'slog_fileandjournald_unit). Both are bounded, and secrets are removed from a line before it is kept. - Module
diagnostics(API Standard 13.2 rule 5):GET /heart/v1/diagnostics,POST /heart/v1/diagnostics/{test}andPOST /heart/v1/diagnostics/bundle. Tests run on the host (Effect::Diagnostic) with a time limit; a disruptive one iswrite, passes the gate, answers with its Action and is cancelled by Stop whencancellable. A support bundle takessince,until,includeandmaxBytes, is filtered for the caller's clearance and never holds secrets ornever-sharedata. heartd's[diagnostics]runs programs or native requests. Idempotency-Keyon these writes is required, kept per principal and operation, answered with the original response,422for another body and409while the first still runs.Config::modules;Core::config_values,Core::host_log,Core::dependency_status. In WASM,heart_starttakesmodules, and the new effects come out asconfiganddiagnostic.- Tests:
tests/modules.rs(22 tests); the hostile-request property intests/prop_robust.rsreaches the four modules; three heartd tests of the new sections; eleven new end-to-end tests inconformance/run.py, every reply checked against the schemas. - A person's own request in one call (API Standard 6.3, rules 1 and 3). Every request that can create an Action takes
actionId, a UUID the client chooses; the Action gets that ID, so the client computes the digest before sending and sendspresencefor it in the same request. A person's destructive or physical request with a proof that verifies runs at once (200with the finished Action, or202while it runs); a proof for another digest is refused with403 presence-invalidand creates nothing;presencewithoutactionIdis400; a usedactionIdis409 conflict; a retry with the sameIdempotency-Keyreturns the original Action. For an operation that is not a capability, the Action'sresultholds the operation's own answer. - Module
presence(API Standard 13.2 rule 13):GET,POST /heart/v1/presence/methodsandDELETE /heart/v1/presence/methods/{id}. Enrolling a WebAuthn passkey is destructive and passes the gate: proved by one of the person's own passkeys in the same request, or approved by another person. While nothing is enrolled, the first passkey is proved by presence at the device: a request through the local interface (Caller::local). Listing never returns key material. Removing needs no proof, except that the last method stays whencard.presence.requireMethodis set; a configured passkey is removed in the configuration.Core::passkeys,Core::seed_passkeysandCore::passkey_usedserve the host. heartd: passkeys in[presence]are seeded into the Core (source: "config"); enrolled ones are kept in the state file and verified too, withlastUsedAt. With[presence], the Card offers modulepresenceandcard.presence.rpId./serve: "stop the conveyor" maps to the named thing'shaltcapability (a capability namedhalt, or ending in.haltor_halt), which waits for confirmation like any physical action. Only "stop", "stop autonomy", "stop all automatic actions" and the like run Stop. A thing with nohaltgets a question, never Stop.- Tests:
tests/presence.rs(5 tests); intests/behaviour.rs, one call with proof,PUT /tending/modeobserve as Stop, halt versus Stop in/serve, and fault codes; intests/prop_gate.rs, the propertya_person_alone_runs_their_own_request_with_proof_in_one_call. The conformance suite has three new end-to-end tests and does every person's own change in one call.
Changed#
- Polled values reach the rules (Core Standard 4.3 and 7.3): a value the Core reads itself (Modbus,
process) feeds Trials'expectandundoIf, the Tending rules that watch it, and its metric, as a host-feed value does. - Ownership changes have one table of effects (API Standard 13.2 rule 8). A transfer also removes grants. A factory reset also removes peers, erases Memory, consents, ratings, receipt links, erasure records and every other Action, and makes a new claim code; a decommission does the same and can never be claimed.
Effect::ClaimCodegainsowner_changed, so a host renews its HEART Hub identity; heartd also makes a new Basic owner key. In WASM,claim-codecarriesownerChanged. - Specification: API Standard 6.3 (presence method
fleet; granting access is an authority change), 8.2 (roles, grants, the owner key), 9.3 (the command channel; a pushed system with one may claim Full), 13.1 (modulesaccessandprovisioning), 13.2 rules 8 and 21, and 13.3; Part 4 Sections 6.5, 6.6 and 9.5; Part 6 Sections 3.2, 4.1 and 8; Part 9 testsP4-OWNER-KEY,P4-ACCESS-GRANT,P4-GATEWAY-CHANNEL,P6-FLEET-ONE-APPROVALandP6-OWNERSHIP. New message typeheart.grant(grant.schema.json);fleetin the presence methods;fleetPhysicalincontrol.schema.json;physicalinfleet-request.schema.json;digestandphysicalinfleet.schema.json; moduleaccess. The API index has 124 operations (four new). - Rotating the identity key is
destructive(API Standard 13.1): it changes whom every peer trusts. - For 7 days after a rotation a Card response carries
Heart-Key-Rotation.heart.controlnow carries the allowlist andapprovalTimeoutSec. - Specification: API Standard 7.2 rule 6 (a Reasoning Gateway keeps the keys and a daily budget per caller), 10.1 rule 7 (the statement's header carries the old key; the old key stays in the key set), 13.2 rules 7 to 10 (what
security,provisioning,batchand the allowlist answer, and the effects of a claim, a transfer and a factory reset), new rule 20 (peers) and 14.6 rule 6 (listed protocols and targets; writes off, and never to guarded or physical addresses). The OpenAPI file gives attestation itsnonce, the audit log its filters, rotation and peers their bodies, and the batch its answer;security.schema.jsongainsidentity,owner,claimable,tls,presenceMethods,updateKeysandauditEntries, andpeer.schema.jsongainstypesandupdatedAt. PUT /valve/policytakes its risk from the change (API Standard 6.3, 13.1 and 13.2 rule 6): loosening isdestructive, tighteningwrite. Any change to the AI policy staysdestructive, as 6.3 says.POST /data/eraseno longer requirespresencein its body: without it, the request waits for another person's approval, like any destructive request.- The daily budget, the most per call and the model pin come from the AI policy and the pins kept in the state file; the Pulse leaves out
budgetUsdTodaywhen no daily budget is set. - Specification: API Standard 6.3 lists loosening the Valve policy among changes to authority; 8.1, 11 and 13.2 rules 6 and 11 say how
privacyandevolvinganswer, and new rules 18 and 19 coveraiandmemory. The OpenAPI file types the bodies ofputValvePolicy,postConsents,postDataExport,putAiPolicy,postMemorySearch,postEvolutionRollbackandpostFeedback. New message typesheart.usageandheart.export(usage.schema.json,export.schema.json);aipolicy.schema.jsongainsrevision,usesandmaxCallUsd;consent.schema.jsonexpiresAtandwithdrawnAt;memory.schema.jsonprivacyandsubject, withsummarytaking the item's level;evolution.schema.jsona pin'sversionand a candidate'spurpose,versionandrequestedAt; a classification path may namebrain,usageandexport. - The approver is never the initiator, a person included (API Standard 6.3; Part 6, Section 3.2). The Core and the Gateway no longer let a person approve their own Action; they may still deny or cancel it.
prop_gate.rs::a_person_never_approves_their_own_action_afterwardsruns again. - One Stop (API Standard 13.4):
PUT /heart/v1/tending/modewithobservehas exactly the effects ofPOST /heart/v1/tending/stop, riskreadand scopeheart.tend; any other mode also needsheart.admin. - Fault codes on alerts (Part 3, Section 5): every alert carries
code, andkindalongside where an old kind fits.POST /heart/v1/arrhythmiasreadscode, withexplanationandcomponent; a body with only an oldkindis turned into its code. Silences and de-duplication go by code. A Playbook that raises an old kind raises its code. A failed undo raisessystem.undo-failed; the 80% budget warning raisesai.budget-near-limit; the Gateway's flatline raisessystem.flatline. - An Action for an API operation is named
"METHOD /path"with the path as sent, and itsargumentsare the request body withoutactionIdandpresence. A physical property's write is physical. heartdreadspresencein the body ofPOST,PUT,PATCHandDELETE. The same assertion sent again for the same digest is a retry, not a replay.- Example rules raise dotted fault codes (
connectivity.dependency-unreachable,performance.memory-exhausted,storage.write-failed). - The API index has 120 operations (three new).
- An Action for a
per-itemoperation (a lifecycle operation, a disruptive test) records the risk its request was given,writeordestructive, neverper-item, whichaction.schema.jsondoes not allow. - Installing an update is
destructive(API Standard 13.1), as Part 2 and Part 6 already said of installing firmware.POST /software/rollbackstayswrite. - The gate's risk of an operation is worked out once, in
operation_gate, which a schedule also uses for the operation it holds. An Action created by the gate keeps the initiator's scopes and clearance, for a schedule it creates. - Specification: API Standard 5.5 lets a
DELETEcarry anIdempotency-Key; 13.2 rule 4 says howsoftwareanswers, new rules 16 and 17 coverartifactsandschedules, 6.3 says no allowlist covers creating a schedule, and rule 3.7 says howdelaySecis gated. The OpenAPI file types the bodies ofpostSoftwareUpdates,postSoftwareRollback,postArtifactsandpostSchedules, adds theRangeheader and everyDELETE's optional key;update.schema.json,schedule.schema.json,software.schema.jsonandaction.schema.json(scheduleId) gain the fields these modules return. - Specification: API Standard 13.2 rules 2 and 5 say how
configanddiagnosticsanswer, and new rules 14 and 15 coverinventoryandtelemetry. The OpenAPI file givesgetLogsits filters,postConfigRollbackits body,postConfigValidateits answer andpostDiagnosticsBundleits optional body;diagnostic.schema.jsonhasbundle. The API index and privacy table are rebuilt from them.
[0.6.0] - 2026-11-12#
Decision D36 of the HEART machines roadmap: the brain switch. A Command Center gives the Core its brain on a lease, signs every answer, and can take the brain away at once.
Added#
- Module
brain(API Standard 7.4, Core Standard 7.8), inheart_core::brain:- Leases.
Core::brain_leaseaccepts a lease only if it is signed with the pinned Command Center key (Ed25519,card.brain.commandCenter.key), is for this system, and is newer than the last one. Its length is at mostcard.brain.maxLeaseSec(default 300), counted on the Core's clock from receipt, so a wrong wall clock can only shorten it, and a clock that goes back ends it. - Checked answers. A Proposal, or a Reason response for
/askand/serve, is used only if it is signed for a request this Core sent (brain_sentkeeps their digests), under the current lease, by a model and for a use the lease names. A lease-only local model's answers are accepted unsigned, as rule 6 allows. A rejected answer is recorded asai.answer-rejectedand counted. - Brainless. When the lease ends, is dropped or is refused, waiting Proposals and
aiActions are cancelled, a running brain Trial is undone, and adopted Playbooks and people's own work go on. Pulseautonomy.limitedBybecomesbrain-cut, capped at A4, andai.lease-expiredorai.brain-revokedis raised. - Signals.
Core::brain_signalandPOST /heart/v1/brain/signalacceptbrain-off,revoke-model,revoke-useandautonomy-stoponly if signed, for this system, at most 300 seconds old, and not seen before. They can only lower risk. - Restore. After a cut (a signal or the self-cut, not a lease that ran out), a system with
restoreNeedsOwner(the default when any capability is physical) refuses leases until a person approvesPOST /heart/v1/brain/restore, which is destructive. - Auto mode.
PUT /heart/v1/brain/offlineis destructive to turn on and lowers risk to turn off. With the link lost (Core::brain_link(false)), a lease'sofflineblock is used for at most itsmaxSec, capped bycard.brain.offline.maxSec, with uses narrowing bystepDown. It ends at once when the link returns. - Self-cut. The Core cuts its own brain after
limits.brainRefusalsPerHour(default 10) refused Proposals, or 3 rejected answers, within an hour. GET /heart/v1/brainshows the state;brain_renewal,brain_renew_in_msandbrain_lease_idserve the host.
- Leases.
heartd:[brain](command_center,key,device_id,device_key_file,max_lease_sec,restore_needs_owner,offline_max_sec).- It renews the lease when a third is left, passes signals and leases to the Core, and reports link loss.
- Model calls go to
/cc/v1/devices/{id}/reason.[brain]and[model]cannot both be set: no model keys on the device.
- Tests:
tests/brain.rshas 16 tests.tests/prop_brain.rshas 2 properties: no answer is used after its lease, or without a good signature, in any order of messages; no signal raises anything.
- The API index has 117 operations (four new).
[0.5.0] - 2026-11-12#
Decisions D29 and D30 of the HEART machines roadmap: lifecycle requests that say how, what and when, and cancelling running work.
Added#
- Module
lifecycle(API Standard 13.2 rule 3):GET /heart/v1/lifecycleandPOST /heart/v1/lifecycle/{operation}forrestart,shutdown,sleep,wake,drain,reload,power-cycle,safe-mode,maintenance-on,maintenance-off,reset-config,reset-network,clear-data,factory-reset,decommissionand vendorx-operations. The request is checked againstlifecycle-request.schema.jsonbefore anyone is asked to approve it; unknown fields are refused. - The risk comes from the request and can only rise: a forced restart, a power cycle, partial and factory resets, and a shutdown of a system nothing can wake remotely (
card.lifecycle.wakeSources) aredestructive. - The Core hands a checked, approved operation to its host as
Effect::Lifecycle; the Action carriesnextReachableAt(ornullandnextReachableNote), names the operation as"POST /heart/v1/lifecycle/<op>"inoperation(D19), and a restart of the HEART or the system first undoes a running Trial. - Maintenance ends by itself at
maintenanceUntil(durationSecrequired, at mostlimits.maxMaintenanceSec). heartd:[lifecycle.<operation>]runs a program without a shell (the request inHEART_LIFECYCLE_REQUEST) or a native request; only configured operations are offered, and the Card lists them. A graceful operation that runs out of time fails; it is never forced.- Cancelling running work (API Standard 6.3 rule 5):
cancelon anapprovedorrunningAction withcancellable: truemoves it tocancelling; the host checksCore::cancel_requestedat its safe points and answers{"cancelled": true}. A cancel needs no proof of presence and never undoes what was done; a running Action that is not cancellable answers409. Stop asks running cancellable work to stop. PATCH /heart/v1/subscriptions/{id}pauses and resumes a webhook;DELETE /heart/v1/arrhythmias/{id}/silenceends a silence early.- Action status
refusedcounts as ended.
Fixed#
POST /heart/v1/arrhythmias/{id}/silencereadsdurationSecandsilencedUntil, as the OpenAPI file says (it read onlyforSec).- Gateway fleet commands (API Standard 13.3, Part 6 Section 8):
GETandPOST /heart/v1/fleet/commands,GET /heart/v1/fleet/commands/{id}andPOST /heart/v1/fleet/commands/{id}/{pause|resume|cancel}, with scopeheart.fleet. Targets come from the selector (systems, labels, profile, status); the rollout is staged, the first stage is never more than 10% of the targets, and the defaults are 5% per stage, one failure and 300 seconds. A command that is not aGETis a Gateway Action, approved withPOST /heart/v1/actions/{id}/decisionand a passkey ([presence]in the Gateway configuration) by a person other than a non-human initiator. A rollout pauses at its failure limit;pauseandcancelneed no approval and touch no further system;resumeis a new Action that needs a new approval, and its failure limit counts from the resume. Each system receives the operation as the sender, with anIdempotency-Key, and applies its own gate; a202from a system counts as accepted, not failed. heart_core::jcs(RFC 8785) andCore::messageare public, for hosts that answer some operations themselves.
[0.4.0] - 2026-11-12#
Follows the reviewed HEART Core Standard 1.0 Sections 4.3 and 7.1 to 7.7.
Security#
- One approval covers a Trial's whole plan: the first Action's digest now includes every step's arguments (
plan) and the values the undo restores (restore). Later steps and the undo carryapprovedBy(the first Action) and pass the guards, not the gate. - The Core reads the values to restore itself (its last applied values, or
Core::settingfrom the product).POST /heart/v1/tending/proposalsrefuses a body withprevious(400), and a Proposal whose current values are unknown is refused422 cannot-undo. - The allowlist is per initiator: entries are
{target, initiators, envelope}as inheart.control. A bare string is now an entry forruleonly (it used to coveraiandpeertoo). - A brain's Proposal uses the allowlist only at Autonomy Level 5 (
autonomy_level, Pulseautonomy.current). Insuggestevery non-read Action fromrule,aiorpeerasks a person; inobservethey are refused409 autonomy-stopped, including a peer's. - A rule runs only if
enabledis true andstatusisadopted. A rule with nostatusisproposed. A rule runs a physical capability only withadoptedPhysical: true(the olderphysical: trueis still read).
Fixed (found by the property tests, TESTING.md)#
- Action digests are SHA-256 over RFC 8785 JSON, as Part 6, Section 3.2 says: numbers are written the ECMAScript way (
2.0as2,1e21as1e+21) and keys sorted by UTF-16. They used serde_json's text, so a screen recomputing the digest got a different one for whole-number values. - Guards (
whenStates,whenStatus,requiresMaintenance,minIntervalSec,maxPerHour) are checked just before sending, for every Action: the first step of a Trial and an Action approved later were sent without them. - A Trial is kept only when each
expectcheck held by its ownwithinSec; before, one check could hold late as long as the longestwithinSechad not passed. - A check never uses samples from before the step it judges, also for
value: an old sample could keep a Trial. - A restart while a Trial is
undoingkeeps itundoing(it went back torunning). - A learned Playbook's fix is refused when the Core does not know the current value of every argument it changes (D10); the undo could not have restored them.
- In
observe, a Playbook may still run areadAction; only non-read Actions are refused. - A
PUTof a custom definition or a Playbook whose body is a JSON array, string, number or boolean is refused400; it panicked the Core. - A model reply that is not a JSON object no longer panics
POST /ask(it is treated as a failed call), and a negative reported cost no longer refunds the daily budget. - The Valve reads a
dynamicfield's level from itsprivacysibling even when an owner classification hides that sibling (it fell back to private), and a Card view no longer gainscapabilities: null.
Changed (schema alignment)#
- Interlocks are
common#/checkobjects ({"signal", "op", "value"}); the older text formtank.level > 10is still read. A boolean reads as 1 or 0. - Pulse
tending.lastActionis the id of the last Action (or null), not a capability name. - Card
sharesin the examples and the Gateway hold only message types andpublic/internal/private.
Changed#
- Envelopes name each argument:
{"arguments": {"<name>": {min, max, maxStep}}, maxPerDay, cooldownSec}. An argument it does not name never changes automatically.maxPerDaycounts automatic Trials in any rolling 24 hours, andcooldownSecruns from the end of the last automatic Trial.Config::envelopesis gone: envelopes live in the allowlist. - Trial states are
awaiting-approval,running,undoing,kept,undone,refused,stoppedandundo-failed, as published. An undo refused by a guard endsundo-failed. Stop ends a waiting Trialstopped. - A Trial is kept only when every
expectcheck held and the longestundoIfwithinSecpassed. Checks takewindowSec. - Statistics:
valuereplaceslast,stddevis added, andtrendis a least-squares slope per minute. - Proposal refusals carry the standard codes:
400 invalid-request,403 caller-not-allowed,404 not-found,409 conflict,409 autonomy-stopped,422 out-of-range,422 cannot-undo.Core::proposetakes nopreviousand returnsRefusal. The last step must equal the Proposal's arguments. - Learned Playbooks trigger on the opposite of the first
expectcheck (a check inwhen), carry an envelope of the values the Trial set or restored, and getreviewBy(one year) only when a person adopts them.==and!=are not learned. Playbook Trials carryruleIdand noproposalId. - Rules use the published form:
whenis a check or{event, component, count, windowSec}, andthen.raiseis{code, severity, summary}. Older rule files are converted on load. - Pulse carries
autonomy. Actions carryexpiresAt(approval_timeout_sec). Trials are never shown to public callers. postTendingProposalsis never a/servetarget.
[0.3.0] - 2026-11-12#
Added#
POST /heart/v1/tending/proposals(scopeheart.propose): a brain delivers a Proposal over the API. It becomes a gated Action with initiatoraiandonBehalfOfthe brain. Delivering changes nothing by itself.GET /heart/v1/tending/trials/{id}: one Trial, asheart.trial.
[0.2.0] - 2026-11-12#
Security#
Meets Security Advisory HEART-2026-001 (see spec/HEART-Security-Advisory-2026-001.md in the HEART specification).
- C1: approvals, and a person's destructive or physical actions, need a WebAuthn proof of presence over the Action's digest (
[presence]). Tokens withactare treated asaiand cannot approve. - C2: adding a Playbook and raising autonomy are destructive. Only an administrator adopts a Playbook, and it runs only its own capability, and a physical one only if adopted as physical.
- C3: tokens must name this system in
aud. Key-bound tokens need a DPoP proof, and off loopback every token must be bound. The Gateway no longer forwards tokens: it signs caller headers (RFC 9421), and systems trust headers only when signed (gateway_public_key) or fromgateway_uidon the socket. - C4: every system has an identity key and sends
Heart-Card-Signaturewith its Card. The Gateway requires a signed Card to join at Connected or Full, and pins the key. - H1: receipts are salted commitments. The salt travels with the data.
Added#
- Trials (Core Standard, Sections 7.1 to 7.7):
Core::proposeturns a brain's Proposal into a gated Action; each step and the undo are Actions;tickwatchesexpectandundoIf; stop and restart undo a running Trial; envelopes (Config::envelopes) limit automatic changes; a kept fix becomes a proposed learned Playbook, which runs as a Trial when adopted and is suspended after two undos or at its review date.Core::consultationbuilds features, never samples. New moduleheart_core::trial;time::parse_rfc3339. - HEART Gateway:
heartd gateway, with routed and pushed systems, Flatline alerts and persistence. [uplink]: a system announces itself to a Gateway, and keeps doing so.- Host feed: the product reports its own events, metrics and state.
heart_core::address: resolvesheart://addresses.- WASM:
heart_report, structured effects, custom definitions, rules and bring-your-own-key models. - Examples: air-conditioner controller, photo editor, Gateway.
scripts/dev-token.pyfor development keys and tokens.
Changed#
- Operations outside the Card's claimed level and modules now return
501. - If Recall matches nothing, a model chooses among the product's own commands.
Fixed#
- A model could pass off an invented value as a
default. Defaults are now checked against the definition.
[0.1.0] - 2026-11-07#
Added#
heart-core: Valve, approval gate, Recall tier, serving, Tending, custom definitions.heartd: HTTP over TLS and Unix sockets on ports 43278 and 7685, webhooks, Modbus TCP and process adapters, atomic state, watchdog.heart-wasm: the Core for browsers.- Pump station example and an end-to-end conformance suite.