# HEART Part 3: Information Model

**What a HEART tells you, and in what shape**

**Author:** Milind K. Patil, Syncaissa Systems Inc.
**Status:** Working draft 1, November 2026. Part of the [HEART Standards Family](HEART-Standards-Family.md).
**Version:** 1.0 (draft)
**Read first:** [Part 1: Basics](HEART-Part-1-Basics-1.0.md), for the words used here.
**Track:** Basic: "In one minute", Sections 2, 3.1, 3.2, 3.4, 4, 5.1 to 5.4, 5.6, 5.7, 7 and 9. Intermediate adds 3.3, 3.7, 5.8, 8, 10 and 11. Advanced adds 3.5, 3.6 and 6. Sections 5.5 and 12 to 14 are reference.

---

## In one minute

Every HEART says things about itself using **seven main messages**. Six travel as messages of their own; the Capability lives inside the Card:

| Message | In one sentence |
|---|---|
| **Card** | Who I am, and what I can do. |
| **Pulse** | How I am right now. |
| **Rhythm** | What has happened, in order. |
| **Arrhythmia** | Something is wrong. Here is what, and why. |
| **Capability** | Something you can ask me to do. |
| **Action** | Something I am doing, or did, and who allowed it. |
| **Receipt** | Proof of what data I sent, and to whom. |

Every message travels in the **same envelope**, and uses the **same shared word lists**. For example, health is always one of `healthy`, `degraded`, `failing` or `unknown`.

Problems are named with **fault codes** from one shared list, such as `storage.full` or `sensors.drift`. A fault code means the same thing on every product, the way OBD-II code P0300 means "engine misfire" on every car. Outside messages it is written `HEART storage.full`, and each standard code also has a short number, such as `H-04002`.

This part describes **what** is said. It does not say how messages travel (that is Part 4), so the same information can be carried by HTTP, MQTT or, later, industrial standards such as OPC UA.

---

## 1. What an "information model" is

Think of a paper form, like a doctor's check-up form. It has fixed boxes: name, date, blood pressure, temperature, notes. Every doctor fills in the same boxes, so any other doctor can read it.

An **information model** is the same idea for machines. It says:

- **which forms exist**: Card, Pulse, Arrhythmia and so on,
- **which boxes each form has**, and which are required,
- **what may be written in each box**: a number, a date, or one word from a fixed list.

It does **not** say how the form is delivered: by post, by fax or by hand. That is Part 4.

Keeping the two apart matters. The forms stay the same while delivery methods change. A tiny sensor and a cloud server can fill in the same forms, and deliver them in completely different ways.

### Where the exact definitions live

This part explains the model in words. The exact, machine-checkable definitions are the JSON Schema files in [`schemas/1.0/`](schemas/1.0/), with one example of each message in [`examples/`](examples/). If this part and a schema disagree, that is a mistake in the standard, and should be reported.

---

## 2. The envelope

Every message a HEART sends about itself goes inside the same **envelope**. Like the outside of a parcel, the envelope says who it is from, what is inside and when it was sent. The message itself sits inside, in `data`.

HEART does not invent its own envelope. It uses **CloudEvents**, a widely used standard for event messages, so existing tools can already route HEART messages.

```json
{
  "specversion": "1.0",
  "id": "c7f3a9e2-1b44-4d0e-9a51-2f6c8e1d7b90",
  "source": "/heart/com.example.cool-air.ac-controller/unit-12",
  "type": "heart.pulse",
  "time": "2026-11-08T10:00:00Z",
  "datacontenttype": "application/json",
  "heartversion": "1.0",
  "heartprivacy": "public",
  "data": {
    "system": { "id": "com.example.cool-air.ac-controller", "name": "CoolAir AC Controller", "version": "0.1.0" },
    "status": "healthy",
    "sequence": 90,
    "intervalSec": 60
  }
}
```

| Box | What it holds |
|---|---|
| `id` | A unique number for this message. If the message is sent again, it keeps the same `id`, so duplicates can be spotted. |
| `source` | Which system sent it: `/heart/` followed by the system's ID, and optionally which copy of the system (the *instance*). |
| `type` | Which message is inside, such as `heart.pulse` (Section 3). |
| `time` | When it was created, in the standard internet time format (RFC 3339). |
| `datacontenttype` | Always `application/json`. |
| `heartversion` | Which version of HEART it follows, such as `1.0`. |
| `heartprivacy` | The most private level of anything inside, after filtering. |
| `heartredacted` | `true` if some fields were removed because the receiver may not see them. |
| `data` | The message itself. |

All of these are required except `heartredacted`, which is present only when something was removed.

Requests sent *to* a system, such as "invoke this capability", are not statements about the system, so they do not need the envelope over HTTP. Part 4 explains how they are carried.

---

## 3. The seven main messages

| Message | `type` | Answers | Schema |
|---|---|---|---|
| **Card** | `heart.card` | Who are you, and what can you do? | [`card`](schemas/1.0/card.schema.json) |
| **Pulse** | `heart.pulse` | How are you right now? | [`pulse`](schemas/1.0/pulse.schema.json) |
| **Rhythm event** | `heart.rhythm` | What happened? | [`rhythm`](schemas/1.0/rhythm.schema.json) |
| **Arrhythmia** | `heart.arrhythmia` | What is wrong, and why? | [`arrhythmia`](schemas/1.0/arrhythmia.schema.json) |
| **Capability** | (inside the Card) | What can I ask you to do? | in [`card`](schemas/1.0/card.schema.json) |
| **Action** | `heart.action` | What are you doing, and who allowed it? | [`action`](schemas/1.0/action.schema.json) |
| **Receipt** | `heart.receipt` | What data did you send, and to whom? | [`receipt`](schemas/1.0/receipt.schema.json) |

Six of these have a `type` of their own. The Capability does not: it is a part of the Card. One more message belongs with them: **Evolution** (`heart.evolution`, schema [`evolution`](schemas/1.0/evolution.schema.json)) says which AI model is pinned and how new candidates tested. It is part of the Full Feature Set ("AI model tracking", Section 7), so the API Standard lists it among its seven core types, in place of the Capability.

There are also smaller messages for optional modules (Section 8), such as `heart.component` and `heart.property`. The prefix `heart.` is reserved for the standard. A product may add its own message types, named with its own reversed domain name, such as `com.coolair.service-report` (Section 9).

In the tables below, a **bold** box name means the box is required.

### 3.1 Card: who I am

The Card is the system's identity card. A consumer reads it first, and usually only once.

```json
{
  "system": { "id": "com.example.cool-air.ac-controller", "name": "CoolAir AC Controller",
              "version": "0.1.0", "owner": "Example Climate Ltd" },
  "level": "full",
  "bindings": ["http-server", "unix-socket"],
  "pulse": { "intervalSec": 30 },
  "modules": ["control", "properties", "custom", "core"],
  "profiles": ["hvac"],
  "autonomy": { "level": 2, "higher": [ { "level": 4, "areas": ["connectivity", "storage"] } ] },
  "capabilities": [ { "name": "calibrate", "risk": "write", "...": "see Section 3.5" } ]
}
```

| Box | What it holds |
|---|---|
| **`system`** | `id` (unique name, Section 9), `name` (for people), `version`, and optionally `instance`, `alias` and `owner`. |
| **`level`** | The system's Feature Set: `basic`, `connected` or `full` (Section 7). The box keeps its old name, `level`, for now. |
| **`bindings`** | How the system can be reached, such as `http-server` or `mqtt` (Part 4). |
| **`pulse`** | `intervalSec`: how often a Pulse is sent. |
| `endpoints` | Addresses: the HEART API, a Gateway, and other standards the system also speaks (MCP, A2A, OpenTelemetry). |
| `modules` | Optional modules the system has (Section 8). |
| `profiles` | Product profiles it follows, such as `hvac` (Part 8). |
| `autonomy` | Its promised Autonomy Level and scope (Part 2). |
| `capabilities` | What it can be asked to do (Section 3.5). |
| `events` | Product-specific Rhythm events it may send. |
| `shares` | Who may read each kind of message: keys are message types without `heart.`, such as `pulse`, and values are `public`, `internal` or `private` (Part 5). |
| `valve` | Privacy settings, including the most private level that may ever reach an AI model, and `classifications` that raise the level of further boxes, each written `<message>:<JSON Pointer>`, such as `rhythm:/attributes/recordingPath` (Part 5). |
| `ai` | Which ways of thinking it uses, and what it does offline. |
| `core` | Its HEART Core size class and ways of thinking (Part 7). |
| `auth` | Where to log in (Part 4). |
| `identity` | The system's identity key, which signs the Card (Part 4). |
| `heartVersion` | Version of the HEART implementation it runs. |
| `template` | The HEART template it was built from, if any. |
| `limits` | Size limits, for small devices. |
| `labels` | Owner's tags, used to pick groups of systems. |
| `ext` | The product's own extra information (Section 10). |

The Card is shown **filtered**. An anonymous caller sees only the public parts. A capability is shown only to callers allowed to use it.

### 3.2 Pulse: how I am

The Pulse is a short message sent at a regular interval, or read on demand. Its first job is to say "I'm alive". Its second is to say how the system is.

| Box | Who can see it | What it holds |
|---|---|---|
| **`system`** | Anyone | Which system |
| **`status`** | Anyone | `healthy`, `degraded`, `failing` or `unknown` (Section 4) |
| **`sequence`** | Anyone | A counter that goes up by one with each Pulse, so a missed one can be spotted |
| **`intervalSec`** | Anyone | Seconds until the next Pulse |
| `reconnaissance` | Owner's side | Current `state`, the last event, and the **Vitals**: uptime, error rate, response time, CPU and memory use |
| `ai` | Owner's side | How it is thinking right now (`rules-only`, `local` or `cloud`), which model, and today's calls and cost against the budget |
| `tending` | Owner's side | How many problems are open, and the last action taken |
| `autonomy` | Owner's side | `current`: its Autonomy Level right now (0 to 5), and `limitedBy`: why it is lower than promised, if it is: `stopped`, `no-model`, `no-network`, `budget`, `degraded` or `brain-cut` (Part 2). Always present at Full. |
| `evolving` | Owner's side | HEART version, and when its AI model last changed |

So anyone can learn whether a system is healthy, and only the owner's side learns the details.

**Vitals are a summary.** Detailed measurements belong in a monitoring standard such as OpenTelemetry, not in the Pulse.

**Flatline.** If no Pulse has arrived for **three intervals**, a consumer MUST treat the system's status as `unknown`. A Gateway or monitoring system SHOULD then raise an Arrhythmia with code `system.flatline` (Section 5).

### 3.3 Rhythm: what happened

The Rhythm is the system's history: a list of events, oldest first. Each event is one thing that happened.

| Box | What it holds |
|---|---|
| **`sequence`** | Its position in the history. Always increases. |
| **`name`** | What happened, as lower-case words (letters, digits, `-` or `_`) joined by dots, such as `component.restarted` |
| **`category`** | Which family the event belongs to (below) |
| `severity` | How important it is: `info`, `warning`, `error` or `critical` |
| `component` | Which part of the system it concerns |
| `summary` | A short sentence for people |
| `attributes` | Details. Private by default. |

| Category | Covers |
|---|---|
| `system` | Start, stop, version changes |
| `state` | Changes of state or status |
| `component` | A part starting, stopping or failing |
| `ai` | Calls to AI models |
| `tending` | Problems noticed and actions taken |
| `evolving` | AI models adopted or rejected |
| `valve` | Data released or blocked |
| `circulation` | Messages to and from other systems |
| `security` | Logins, permissions, suspicious activity |
| `domain` | The product's own events, such as `wash.finished` |

The event names starting with `system.`, `state.`, `component.`, `ai.`, `tending.`, `evolving.`, `valve.`, `circulation.` and `security.` are reserved for the standard. Product events use any other name and the category `domain`.

The standard **event types** (the values of `name` reserved for the standard) are listed in Section 5.8. An event type says that something happened. It is not a fault code.

If old history has been deleted to save space, the system MUST say so (Part 4 explains how), so nobody mistakes a gap for "nothing happened".

### 3.4 Arrhythmia: something is wrong

An Arrhythmia is an alert. It is the most important message, because it carries the system's own view of what is wrong.

It answers three questions:

1. **What is wrong?** The fault `code`, for example `actuators.short-cycling`.
2. **Why?** The `cause`, with its own fault code where known, for example `sensors.drift`.
3. **What is being done?** Its `status`, and any `actions`.

```json
{
  "id": "5b1e0c9a-7d2f-4b8e-9c31-0a6f2e4d8b17",
  "code": "actuators.short-cycling",
  "severity": "warning",
  "status": "open",
  "detectedAt": "2026-11-08T09:58:00Z",
  "component": "compressor",
  "summary": "Compressor short cycling",
  "explanation": "The compressor is starting and stopping too often (12 times in the last hour). Cooling may be weak. Your installer has been told.",
  "cause": {
    "code": "sensors.drift",
    "component": "room-sensor",
    "text": "The room sensor reads 3 °C higher than the return-air sensor.",
    "confidence": "medium"
  },
  "evidence": [4012, 4013, 4019]
}
```

| Box | What it holds |
|---|---|
| **`id`** | A unique ID for this alert |
| **`code`** | What is wrong, as a fault code (Section 5) |
| **`severity`** | `info`, `warning`, `error` or `critical` |
| **`status`** | Where the alert is in its life (below) |
| **`detectedAt`** | When it was first noticed |
| `resolvedAt` | When it was fixed |
| `component` | Which part has the problem |
| `summary` | A few words for a list of alerts |
| `explanation` | Plain language: what happened and what it means for the user. Required for Autonomy Level A1 (Part 2). |
| `cause` | Why it happened: a fault `code` if one fits, the `component`, a plain-language `text`, and a `confidence` of `low`, `medium` or `high`. Required for A2. If the cause is not known, `text` says so and `code` is left out. |
| `evidence` | The Rhythm events that led to this alert, so a person can check |
| `actions` | Actions taken or proposed for it |
| `escalation` | When and why a person was told |
| `acknowledgedAt`, `acknowledgedBy` | When, and by whom, a person first acknowledged it |
| `silencedUntil` | Until this time, the alert and repeats of its code are raised and listed but not notified (API Standard 5.1). Set by a silence or by maintenance |
| `kind` | Deprecated. The older, broad alert type, kept so that existing software keeps working (Section 5.5). Optional. |

**What is seen, and why.** One problem often causes another. In the example, the compressor problem (`code`) is caused by a sensor problem (`cause.code`). Both come from the same list of fault codes. This is how a HEART tells you not only "something is wrong", but "this is the real reason".

An alert moves through these states:

```
open ──► acknowledged ──► tending ──► resolved
  │            │             │           ▲
  │            ▼             │           │
  └──────► escalated ◄───────┘           │
               └─────────────────────────┘
```

Any state may also move straight to `resolved` when the problem goes away by itself. `acknowledged` is reached through `POST /arrhythmias/{id}/acknowledge`. An escalated alert is resolved by the person it was handed to, or by the HEART once the problem is gone.

| Status | Meaning |
|---|---|
| `open` | Noticed. Nobody has responded yet. |
| `acknowledged` | A person has seen it. |
| `tending` | The HEART is working on it. |
| `escalated` | Handed to a person, because the HEART cannot or may not fix it. |
| `resolved` | Fixed. |

### 3.5 Capability: what you can ask me to do

A capability is something the system can be asked to do. Its boxes are a **superset of an MCP tool**, so any capability can also be offered to AI assistants as an MCP tool without changes.

```json
{
  "name": "calibrate",
  "title": "Calibrate a sensor",
  "description": "Recalibrates one temperature sensor against the reference sensor. Takes about 2 minutes.",
  "inputSchema": { "type": "object", "required": ["sensor"], "properties": { "sensor": { "type": "string" } } },
  "risk": "write",
  "physical": false,
  "reversible": true,
  "idempotent": true,
  "privacy": "internal",
  "callers": ["human", "rule", "ai"],
  "kind": "standard"
}
```

| Box | What it holds |
|---|---|
| **`name`** | A short name of up to 128 letters, digits, `_`, `.` or `-`. Use a standard name where one exists (Section 6). |
| `title` | A short title for people |
| **`description`** | What it does, for people and for AI models deciding what to call |
| **`inputSchema`** | The shape of what goes in (JSON Schema). Use `{"type": "object"}` with no properties when it takes nothing. |
| `outputSchema` | The shape of what comes out (JSON Schema) |
| **`risk`** | `read`, `write` or `destructive` (Part 1) |
| `physical` | `true` if it moves, heats, powers or switches something in the real world |
| `reversible` | Whether it can be undone |
| `idempotent` | Whether doing it twice has the same effect as doing it once |
| **`privacy`** | How private its results are: `public`, `internal` or `private`. A caller needs at least this clearance to see or use it. |
| `callers` | Who may start it: `human`, `peer`, `ai`, `rule`. The default is `human` only. |
| `kind` | `standard` (a standard name), `product` (the product's own, the default) or `custom` (from a custom definition, Part 8) |
| `guards` | Checks made just before it runs: `whenStates`, `whenStatus`, `requiresMaintenance`, `minIntervalSec`, `maxPerHour` and `interlocks` (API Standard 14.4). Each interlock is a check: a `signal` (a Signal Table signal or a property of this system), an operator and a number, read from the device at that moment. A boolean reads as 1 or 0. An interlock that cannot be read counts as failed. |

### 3.6 Action: what I am doing

When anyone asks for a capability, the system creates an **Action**. The Action records the whole story: who asked, who approved, what happened.

| Box | What it holds |
|---|---|
| **`id`** | A unique ID (a UUID). The client may choose it, as `actionId` in its request, so it can compute the `digest` in advance (API Standard 6.3). |
| **`capability`** or **`operation`** | What is being done: a capability name, or for any other API operation `operation` written `"METHOD /path"`, such as `"PUT /heart/v1/tending/autonomy"`. Exactly one of the two. |
| **`status`** | Where it is in its life (below) |
| **`risk`** | `read`, `write` or `destructive`: the risk the gate applied, which may be higher than the capability's own (API Standard 6.3) |
| **`createdAt`** | When it was requested |
| `expiresAt` | When it expires if still awaiting approval |
| `digest` | SHA-256 fingerprint of what is being approved: system, `id`, capability or operation, arguments and `physical` (API Standard 6.3). It cannot change once approval is asked for. |
| `arguments` | The inputs. Private. |
| `initiator` | Who started it: `kind` is `human`, `peer`, `ai` or `rule` (a Playbook). For an AI, `onBehalfOf` says whose authority it is using. |
| `decision` | Approve, deny or cancel, when, by whom, and how presence was proved (`webauthn`, `ciba`, `device-button` or `remote-site`) |
| `approvedBy` | For a later step or the undo of a Trial: the first Action of that Trial, whose approval covered the whole plan (Core Standard) |
| `arrhythmiaId` | The alert it is fixing, if any |
| `explanation` | Plain language: what it does and why |
| `physical` | Whether it affects the real world |
| `dryRun` | `true` if it only reports what *would* happen |
| `progress`, `result`, `error` | How it is going, and how it ended |
| `cancellable` | Whether a cancel can still take effect |
| `nextReachableAt` | For shutdown, sleep and restarts: when the system expects to answer again |

```
awaiting-approval ──► approved ──► running ──► succeeded
        │                │            │     └─► failed
        ├──► denied      │            └──► cancelling ──► cancelled
        ├──► expired     └──► refused
        └──► cancelled
```

`denied`: a person said no. `refused`: the system said no (a guard, a precondition or the autonomy mode). `cancelling`: a cancel takes effect at the next safe point; see API Standard 6.3, rule 5.

An Action that needs no approval starts at `running`. The rules for who must approve what are in Part 6.

### 3.7 Receipt: what data I sent

Whenever non-public data leaves the system, to an AI provider, a Gateway or another system, the HEART writes a **Receipt**. It records who received the data, why, how private it was, how many bytes were sent, and a **salted commitment** to exactly what was sent: SHA-256 over a fresh random salt followed by the payload. The salt goes to the recipient, never into the Receipt, so nobody can guess short values back from it. A Receipt never contains the data itself.

Part 5 explains Receipts fully.

---

## 4. Shared word lists

Many boxes accept only words from a fixed list. That is what lets a program understand any HEART without guessing.

| List | Words |
|---|---|
| **Status** | `healthy`, `degraded`, `failing`, `unknown` |
| **Severity** | `info`, `warning`, `error`, `critical` |
| **Risk** | `read`, `write`, `destructive` |
| **Privacy level** | `public`, `internal`, `private`, `never-share` |
| **Confidence** | `low` (a guess worth checking), `medium` (likely), `high` (well supported by the evidence given) |
| **Initiator** | `human`, `peer`, `ai`, `rule` |
| **Arrhythmia status** | `open`, `acknowledged`, `tending`, `escalated`, `resolved` |
| **Action status** | `awaiting-approval`, `approved`, `running`, `cancelling`, `succeeded`, `failed`, `denied`, `refused`, `cancelled`, `expired` |
| **Component kind** | `service`, `process`, `sensor`, `actuator`, `board`, `module`, `library`, `store`, `network`, `other` |
| **Rhythm category** | Section 3.3 |
| **Area** | Section 5.2 |
| **Units** | UCUM codes, the standard list of units of measure, such as `Cel` for degrees Celsius |

The four statuses mean exactly this:

| Status | Meaning | Air-conditioner example |
|---|---|---|
| `healthy` | Working normally | Cooling as expected |
| `degraded` | Working, but less well | Cooling, but weakly |
| `failing` | Cannot do its main job | Not cooling at all |
| `unknown` | Cannot tell yet | Just switched on, or no Pulse for a while |

---

## 5. Fault codes

### 5.1 Why fault codes

A message like *"error 0x3F"* means nothing outside one company. A message like *"something is wrong"* means nothing to a program. A **fault code** fixes both. It is a short, readable name for one specific problem, with **one fixed meaning on every product**.

A building dashboard watching 500 different devices can then count every `storage.full` across all of them, without knowing any of the brands.

HEART fault codes are **words, not numbers**. `sensors.drift` is easier to read and remember than `S0402`, and still easy for programs. For a small screen, a blinking light or a phone call, each standard code also has a short fixed number, its **H-number**, such as `H-08005` for `sensors.drift` (Section 5.7). The name stays the main form.

### 5.2 How a code is built

A fault code has two parts, separated by a dot:

```
     sensors . drift
     ───┬───   ──┬──
      area    problem
```

The **area** is where the problem is. The areas are the same as in Part 2, plus `system` for the whole system:

| Area | Number | Covers |
|---|---|---|
| `system` | 01 | The whole system |
| `power` | 02 | Supply, battery, charging |
| `connectivity` | 03 | Network and links to other systems |
| `storage` | 04 | Disk, memory cards, databases |
| `performance` | 05 | Speed, load, heat |
| `software` | 06 | Crashes, versions, updates, settings |
| `ai` | 07 | AI providers, models, budgets |
| `sensors` | 08 | Readings and calibration |
| `actuators` | 09 | Motors, valves, heaters, compressors |
| `data` | 10 | Data quality, sync, backups |
| `security` | 11 | Logins, certificates, tampering |

The **number** is the first part of the H-number of every code in that area (Section 5.7). A new area takes the next free number, 12 to 99.

The **problem** is one or more lower-case words joined by hyphens, such as `nearly-full`.

### 5.3 Three kinds of code

| Kind | Looks like | Who defines it | Example |
|---|---|---|---|
| **Standard** | `area.problem` | This standard (Section 5.4) | `storage.full` |
| **Profile** | `profile.problem` | A product profile (Part 8) | `hvac.refrigerant-low` |
| **Product** | reversed domain name, then the problem | The product's maker | `com.coolair.filter-clogged` |

Rules:

1. A system MUST use a **standard** code when one fits.
2. If none fits, it SHOULD use a **profile** code, and only then a **product** code.
3. Product codes MUST start with a domain the maker owns, so two makers never clash.
4. A product code SHOULD be described in the Card's `events`, with a sentence saying what it means.
5. Only standard codes have an H-number (Section 5.7). Profile and product codes have none: they are always written by name.

### 5.4 The standard fault codes

This is the starting list. It will grow through the registry process (Section 11). Each code has its H-number beside it (Section 5.7). These codes are all faults: things that are wrong. Things that simply happen, such as a start or a restart, are event types (Section 5.8).

A page for each code, with its likely causes, how to fix or prevent it, and what a HEART does by itself, is at `https://heartcortex.ai/faults/<code>`, such as [`/faults/storage.full`](https://heartcortex.ai/faults/storage.full). The same facts are in one machine-readable file, [`fault-codes/1.0/fault-codes.json`](fault-codes/1.0/fault-codes.json), checked against the tables below by `tools/validate.py`.

**`system`: the whole system**

| Code | H-number | Meaning |
|---|---|---|
| `system.main-function-lost` | H-01001 | The system cannot do its main job. Every HEART MUST be able to detect this (Part 2, A1). |
| `system.main-function-degraded` | H-01002 | The main job works, but less well than normal. |
| `system.flatline` | H-01003 | No Pulse for three intervals. Raised by whoever is watching, not by the system itself. |
| `system.unexpected-restart` | H-01004 | The system restarted without being asked to. |
| `system.heart-unwell` | H-01005 | The HEART itself has a problem. The product may be fine. |
| `system.undo-failed` | H-01006 | A change the HEART made could not be undone, so the system is left changed (HEART Core, Section 7.5). A person must look. |

**`power`**

| Code | H-number | Meaning |
|---|---|---|
| `power.supply-lost` | H-02001 | Main power is gone. Running on battery or backup, if any. |
| `power.supply-unstable` | H-02002 | Voltage or supply is outside the normal range. |
| `power.battery-low` | H-02003 | The battery needs charging soon. |
| `power.battery-failing` | H-02004 | The battery no longer holds charge as it should. |

**`connectivity`**

| Code | H-number | Meaning |
|---|---|---|
| `connectivity.link-lost` | H-03001 | A network link is down. |
| `connectivity.link-unstable` | H-03002 | A link keeps dropping or is very slow. |
| `connectivity.dependency-unreachable` | H-03003 | An outside service or system it relies on cannot be reached. |
| `connectivity.clock-unsynced` | H-03004 | The clock cannot be checked against a time source. |

**`storage`**

| Code | H-number | Meaning |
|---|---|---|
| `storage.nearly-full` | H-04001 | Storage is almost full (the system states the threshold). |
| `storage.full` | H-04002 | Storage is full. Writing new data fails. |
| `storage.write-failed` | H-04003 | Data could not be written, for a reason other than being full. |
| `storage.wearing-out` | H-04004 | Storage hardware is near the end of its life. |
| `storage.corrupted` | H-04005 | Stored data or the file system is damaged. |

**`performance`**

| Code | H-number | Meaning |
|---|---|---|
| `performance.slow` | H-05001 | Responses take much longer than normal. |
| `performance.overloaded` | H-05002 | More work is arriving than can be handled. |
| `performance.backlog` | H-05003 | A queue of waiting work keeps growing. |
| `performance.memory-exhausted` | H-05004 | Memory is (nearly) used up. |
| `performance.too-hot` | H-05005 | The device is overheating and may slow itself down. |
| `performance.overshoot` | H-05006 | A controlled value goes past its target by more than the system's stated limit, such as a room getting colder than its setpoint. |

**`software`**

| Code | H-number | Meaning |
|---|---|---|
| `software.error-spike` | H-06001 | Errors are happening far more often than normal. |
| `software.crashed` | H-06002 | A program or component crashed. |
| `software.crash-loop` | H-06003 | A component keeps crashing and restarting. |
| `software.update-failed` | H-06004 | An update did not install. |
| `software.config-invalid` | H-06005 | Settings are wrong or contradict each other. |
| `software.version-mismatch` | H-06006 | Parts that must work together have incompatible versions. |

**`ai`**

| Code | H-number | Meaning |
|---|---|---|
| `ai.provider-unavailable` | H-07001 | The AI model or provider cannot be reached. |
| `ai.cost-spike` | H-07002 | AI spending is rising much faster than normal. |
| `ai.budget-near-limit` | H-07003 | AI spending has reached 80% of the budget. |
| `ai.budget-exceeded` | H-07004 | The AI budget is used up. AI calls are stopped. |
| `ai.model-drift` | H-07005 | The AI model's answers have changed in quality or behaviour. |
| `ai.output-rejected` | H-07006 | AI suggestions keep failing the HEART's checks. |
| `ai.lease-expired` | H-07007 | The brain lease ran out without a renewal, so the system is brainless (API Standard 7.4). |
| `ai.brain-revoked` | H-07008 | The brain was cut: by a Command Center signal, by the owner, or by the system's own tripwire. |
| `ai.answer-rejected` | H-07009 | A brain answer was refused: unsigned, wrongly signed, for another request, or outside the lease. |
| `ai.disagreement` | H-07010 | The brain's answer disagreed with the system's own checks on something safety-relevant. |

**`sensors`**

| Code | H-number | Meaning |
|---|---|---|
| `sensors.no-reading` | H-08001 | A sensor gives no value. |
| `sensors.out-of-range` | H-08002 | A reading is outside what is physically possible. |
| `sensors.stuck` | H-08003 | A reading has not changed when it should have. |
| `sensors.noisy` | H-08004 | Readings jump around far more than normal. |
| `sensors.drift` | H-08005 | A sensor has slowly moved away from the true value. |
| `sensors.disagree` | H-08006 | Two sensors that should agree do not. |

**`actuators`**

| Code | H-number | Meaning |
|---|---|---|
| `actuators.not-responding` | H-09001 | A motor, valve or similar part does not react to commands. |
| `actuators.position-mismatch` | H-09002 | It ended up somewhere other than where it was told to go. |
| `actuators.stalled` | H-09003 | It is blocked or cannot move. |
| `actuators.short-cycling` | H-09004 | It switches on and off far too often. |
| `actuators.overcurrent` | H-09005 | It is drawing more current than it should. |

**`data`**

| Code | H-number | Meaning |
|---|---|---|
| `data.invalid` | H-10001 | Data fails its checks. |
| `data.missing` | H-10002 | Expected data has not arrived. |
| `data.out-of-sync` | H-10003 | Two copies of the same data disagree. |
| `data.backup-failed` | H-10004 | A backup did not complete. |
| `data.integrity-failed` | H-10005 | Data has been changed or damaged when it should not have been. |

**`security`**

| Code | H-number | Meaning |
|---|---|---|
| `security.login-failures` | H-11001 | Many failed login attempts. |
| `security.unexpected-access` | H-11002 | Access from an unknown place, or at an unusual time. |
| `security.certificate-expiring` | H-11003 | A security certificate expires soon. |
| `security.certificate-expired` | H-11004 | A security certificate has expired. |
| `security.tamper-detected` | H-11005 | Someone may have opened or altered the device or its software. |
| `security.policy-violation` | H-11006 | Something broke the owner's rules, such as data that should not have left. |
| `security.anomaly` | H-11007 | Unusual activity that may be an attack. |

### 5.5 The older `kind` box

Before fault codes, alerts had a `kind`: one of 11 broad types. To keep existing software working, a system SHOULD send both `code` and `kind` during the change-over, when an old kind fits:

| Old `kind` | Fault code |
|---|---|
| `error-spike` | `software.error-spike` |
| `latency-spike` | `performance.slow` |
| `resource-exhaustion` | `performance.memory-exhausted` or `storage.full` |
| `cost-spike` | `ai.cost-spike` |
| `dependency-failure` | `connectivity.dependency-unreachable` |
| `model-unavailable` | `ai.provider-unavailable` |
| `model-drift` | `ai.model-drift` |
| `security-anomaly` | `security.anomaly` |
| `policy-violation` | `security.policy-violation` |
| `data-integrity` | `data.integrity-failed` |
| `flatline` | `system.flatline` |

`kind` will be removed in version 2.0.

### 5.6 Writing a code outside messages

Inside a message, a code is written bare:

```json
"code": "storage.full"
```

Outside messages, in logs, tickets, documents, screens and searches, it is written with **`HEART`** and a space in front: **`HEART storage.full`**. The word tells a reader, or a search engine, which list the code comes from. The same holds for profile and product codes, such as `HEART hvac.refrigerant-low`.

A program reading a message MUST NOT expect the prefix. A program writing a message MUST NOT add it.

### 5.7 H-numbers

Each standard fault code has a short number for places where a name does not fit: a two-digit display, a blinking light, a code read out over the phone. It is written **`H-AACCC`**:

| Part | Meaning | Example |
|---|---|---|
| `H-` | A HEART fault code | |
| `AA` | The area's number (Section 5.2) | `04`: `storage` |
| `CCC` | The code's place in its area, from `001` | `002`: `full` |

So `H-04002` is `storage.full`, and `H-01001` is `system.main-function-lost`.

Rules:

1. An H-number is **fixed for ever**. It is never changed, never reused, and never given to another code, even if its code is deprecated.
2. A new code takes the **next free number** in its area. A new area takes the next free area number.
3. Only **standard** codes have H-numbers. Profile codes, product codes and event types have none.
4. The **name is the main form**. Messages carry the name, never the H-number. A screen, label or manual MAY show the H-number beside the name, or alone where the name does not fit.
5. A person can look up any H-number at `https://heartcortex.ai/system-code/H-AACCC`, such as `/system-code/H-04002`. A part of a number, such as `H-04`, lists every code that starts with it.

### 5.8 Event types: not fault codes

An **event type** names something that happened: the system started, a part was restarted, a message arrived. It is the `name` of a Rhythm event (Section 3.3). It is not a fault, and MUST NOT be used as an alert's `code` or `cause.code`.

Event types look like fault codes, `area.name`, but they are a separate list. They have no H-number. When an event shows that something is wrong, the HEART raises an alert with a fault code. For example, a `component.failed` event three times in 30 seconds may raise `connectivity.dependency-unreachable`, and a restart nobody asked for raises `system.unexpected-restart` beside the `system.started` event.

These standard event types are defined so far. Each has the category of its prefix (Section 3.3).

| Event type | Meaning |
|---|---|
| `system.started` | The system started. |
| `system.restarted` | The HEART resumed after a restart, from saved state. |
| `system.stopping` | The system is shutting down on purpose. |
| `system.port.fallback` | The standard port was taken, so the system runs on a fallback port. |
| `system.custom.changed` | A custom definition was added, changed or removed. |
| `system.lifecycle.started`, `system.lifecycle.done`, `system.lifecycle.failed`, `system.lifecycle.cancelled` | A lifecycle operation, such as a restart, started, finished, failed or was cancelled (API Standard 13.2 rule 3). |
| `system.lifecycle.maintenance-window` | A maintenance window began (module `schedules`). |
| `system.lifecycle.maintenance-ended` | Maintenance ended by itself at `maintenanceUntil`. |
| `component.failed` | A component failed or stopped answering. |
| `component.recovered` | A failed component works again. |
| `component.restarted` | A component was restarted. |
| `ai.call.failed` | A call to an AI model failed. |
| `ai.tier.changed` | The way of thinking in use changed, such as from `cloud` to `local`. |
| `tending.autonomy.changed` | A person changed the autonomy ceiling `max`. |
| `tending.silence.ended` | A silence on an alert's code was ended early. |
| `tending.stopped` | Stop was pressed: all autonomous action stopped. |
| `tending.action.failed` | An Action ended `failed`. |
| `tending.guard.refused` | A guard or interlock refused a command. |
| `tending.rule.proposed`, `tending.rule.suspended`, `tending.rule.failed`, `tending.rule.refused` | A Playbook was proposed, suspended, failed when run, or was refused by the gate. |
| `tending.proposal.received` | A brain's Proposal arrived. |
| `tending.trial.started`, `tending.trial.undoing`, `tending.trial.kept`, `tending.undo-failed` | A Trial started, began its undo, was kept, or could not be undone. |
| `tending.envelope.exceeded` | An automatic change would have gone outside its envelope, and was refused. |
| `circulation.received` | A message from another system arrived. |
| `circulation.gateway.joined`, `circulation.gateway.unreachable` | The system joined its Gateway, or cannot reach it. |
| `circulation.system.joined`, `circulation.system.left` | At a Gateway: a system joined or left. |
| `security.model.invented`, `security.model.ungrounded` | A model's answer named something that does not exist, or was not grounded in the system's own data, and was refused. |

More event types are added through the registry (Section 11).

---

## 6. Standard action names

If a product offers one of these actions, it SHOULD use the name given here. Then one command, such as "restart that", works across a whole building or fleet of different products.

| Area | Names |
|---|---|
| **Tending** | `retry_operation`, `restart_component`, `switch_provider`, `clear_cache`, `throttle`, `rollback` |
| **Devices** | `identify`, `calibrate`, `zero`, `self_test`, `sync_time`, `reset_counters` |
| **Actuators** | `start`, `halt`, `pause`, `resume`, `home`, `set_target` |
| **Data** | `flush_buffer`, `compact_storage`, `export_data` |
| **Connectivity** | `reconnect`, `rescan`, `switch_network` |

`halt` stops a machine or one of its parts. It is not the HEART's Stop, which stops only automatic action and leaves the machine as it is (Part 6, Section 6).

Product profiles (Part 8) add names for their own kind of product.

---

## 7. Feature Sets: how much a system must say

A **Feature Set** says how much of the information model a system offers. There are three. Each includes everything in the one before it.

| Feature Set | What it adds | Suits |
|---|---|---|
| **Basic** | Card, Pulse, Arrhythmias, the Valve | Every product, including small sensors |
| **Connected** | Rhythm history, questions in plain language, messages from other systems, subscriptions, Receipts, the full Card | Most apps and connected devices |

The **full Card** means the Card served at the caller's clearance (`GET /heart/v1/card`), and not only its public view (Part 4).
| **Full** | Capabilities and Actions with approval, the stop control, AI model tracking, AI reasoning in the standard format | Systems that fix themselves or act on request |

A system states its Feature Set in its Card. If something is asked for that is above its Feature Set, it MUST answer "not implemented" (Part 4 says how).

Every Feature Set, even Basic, MUST also meet Autonomy Level A1 (Part 2). A system that only reports, without noticing problems and telling someone, is not a HEART.

**Feature Sets and Autonomy Levels are different scales.** The Feature Set says *how much you can ask*. The Autonomy Level says *how much it does on its own*. See [Part 1](HEART-Part-1-Basics-1.0.md), Section 5.6.

---

## 8. Modules: optional extras

Feature Sets stay small, so a tiny sensor can take part. Everything else is an optional **module**. A system lists its modules in the Card's `modules`. It MUST offer all of a module, or none of it.

| Module | Adds | Typical user |
|---|---|---|
| `inventory` | A tree of the system's parts, and what it relies on | Most systems |
| `properties` | Named values to read or set, such as a temperature or a setpoint | Devices |
| `config` | Versioned settings that can be checked before use, and rolled back | Most systems |
| `telemetry` | Metrics and logs, for systems that do not use OpenTelemetry | Small devices |
| `diagnostics` | Self-tests and support bundles | Devices, appliances |
| `lifecycle` | Power and operating phases, such as sleep or maintenance | Devices |
| `software` | Versions, signed updates and lists of included software | Most systems |
| `artifacts` | Files and large data | Apps, printers, cameras |
| `schedules` | Do something later, and maintenance windows | Most systems |
| `control` | The stop button and Playbook rules. Required for Full. | Systems that act |
| `privacy` | Valve settings, consent and personal data requests | Systems with personal data |
| `ai` | AI providers, budgets and usage | Systems using AI |
| `brain` | The brain lease and the switch: a Command Center can cut the AI model, and it is cut by itself when the lease runs out (API Standard 7.4). Required at A3 and above with a brain | Systems using AI to act |
| `memory` | What the system remembers, and forgetting it on request | Systems using AI |
| `evolving` | AI model versions, lessons and feedback | Systems using AI |
| `security` | Keys, proof of identity, and audit records | Most systems |
| `platform` | Hardware, power, network and clock | Devices |
| `provisioning` | First setup, and changing owner | Devices |
| `peers` | Which other systems it trusts | Systems that talk to each other |
| `batch` | Several requests in one go, for slow links | Small or remote devices |
| `fleet` | One command to many systems | Gateways |
| `custom` | The product's own commands (Part 8) | Most devices |
| `native` | Direct access to the device's own protocol. Off by default. | Engineers only |
| `core` | The built-in HEART Core (Part 7) | Systems with a Core |

Two of the most useful module messages:

- **Component** (`inventory`): one part of the system, with an `id`, a `kind` (Section 4), its own `status`, and optionally its `parent`, `version`, maker and what it depends on.
- **Property** (`properties`): one value, with its `name`, `value`, `unit` (UCUM), whether it is `writable`, a `quality` of `good`, `uncertain` or `bad` (the three severities of OPC UA's StatusCode), the `time` it was measured, and its `privacy` level. The `value` takes the level given in `privacy`; this is the only box whose level is set per message (its schema marks it `dynamic`).

---

## 9. Naming rules

Names are how systems find each other and avoid clashes. These rules keep them unique and readable.

| What | Rule | Example |
|---|---|---|
| **System ID** | A reversed domain name the owner controls, then the product name | `com.example.cool-air.ac-controller` |
| **Instance** | Which copy of the system. MUST NOT reveal who the user or owner is. | `unit-12`, `browser-7f2c` |
| **Standard message types** | Start with `heart.` (reserved) | `heart.pulse` |
| **Product message types and fault codes** | Start with the maker's reversed domain name | `com.coolair.service-report` |
| **Capability names** | Up to 128 letters, digits, `_`, `.` or `-` (the same as MCP tool names) | `calibrate` |
| **Event types** | Dotted lower-case words (Section 5.8) | `wash.finished` |
| **Fault codes** | Section 5 | `sensors.drift` |

Using a reversed domain name works the way Java packages and Android apps do. Only the owner of `coolair.com` can create names starting `com.coolair`, so names never clash.

---

## 10. Rules for change

Products will mix HEART versions for years. These rules make sure old and new keep understanding each other.

1. **Ignore what you don't know.** A consumer MUST ignore boxes it does not recognise. It MUST NOT fail.
2. **Missing means "not available to you".** A missing box never means zero, false or empty. It may have been filtered out for privacy.
3. **Versions 1.x only add.** A minor version may add boxes, words and codes. It MUST NOT remove anything or change what something means. Removing or changing needs version 2.0.
4. **Codes are forever.** A fault code, its H-number, an action name or an event type, once published, never changes meaning and is never reused. It can only be marked *deprecated* (no longer recommended).
5. **Extras go in `ext`.** A product's own extra boxes go inside `ext`, under the maker's reversed domain name, so they never clash with future standard boxes. They are private unless the Card says otherwise.

```json
"ext": { "com.coolair": { "filterHours": 412 } }
```

---

## 11. Registries: the shared lists

A **registry** is an official, shared list that everyone uses, such as the list of country codes or of internet port numbers. HEART keeps these registries:

| Registry | Holds | Section |
|---|---|---|
| **Fault codes** | Standard fault codes and their H-numbers | 5.4, 5.7 |
| **Action names** | Standard capability names | 6 |
| **Areas** | The areas used by fault codes and Autonomy Levels | 5.2 |
| **Profiles** | Product profiles, and their prefixes for fault codes and names | Part 8 |
| **Event types** | Standard Rhythm event types | 5.8 |
| **Message types** | `heart.` message types | 3 |
| **Modules** | Module names | 8 |

### How to add an entry

Anyone may propose an entry. A proposal gives:

1. the **name**,
2. **one sentence** saying exactly what it means,
3. **an example** from a real product,
4. why no **existing** entry fits.

An entry is accepted when it is clear, does not overlap an existing entry, and is likely to be useful to more than one maker. Until HEART has a neutral home (see the [Standards Family](HEART-Standards-Family.md)), the editor of this standard keeps the registries in this repository.

---

## 12. Carrying the model in other standards

The model in this part is the **meaning**. Part 4 defines the main ways to carry it: HTTP and JSON, MQTT, local sockets and in-process use.

Other carriers can be added later without changing the model, as long as they follow one rule:

> **Same boxes, same meanings, same privacy.** Any carrier MUST keep every box with the meaning given here, and MUST apply the Valve's filtering (Part 5) before anything leaves the system.

Planned carriers:

| Carrier | Why |
|---|---|
| **CBOR** (compact binary JSON) | For very small devices that cannot afford text messages |
| **OPC UA** | So factory machines can offer HEART inside the standard factories already use |
| **Asset Administration Shell** | So a HEART can be one section ("submodel") of a factory digital twin |
| **OMA LwM2M** | For small managed IoT devices |

Each needs work with the group that owns that standard.

---

## 13. Changes needed in other files

| Change | Where |
|---|---|
| A machine-readable copy of each other registry, checked by `tools/validate.py` | New `registries/` folder |
| Rename "conformance level" to "Feature Set" in text | API Standard, Core Standard, paper. The Card box stays `level` until version 2.0. |

---

## 14. Open items

| Item | Why it matters |
|---|---|
| **Is the starting fault-code list right?** | It should be checked against real fault logs from several kinds of product before 1.0 final. |
| **Thresholds** | Codes such as `storage.nearly-full` and `performance.slow` depend on limits each product sets. Profiles may set defaults. |
| **Mapping to existing code systems** | NAMUR NE 107 (industrial device status), OBD-II and similar code lists could map to HEART codes, so existing devices can join easily. |
| **Translations** | Codes stay in English. The meaning sentences and explanations need agreed translations. |
| **CBOR profile** | Needed before the smallest microcontrollers can take part. |
