# Calibration service (SDK 1.0, et.tune.v1)

The optional `ET_SERVICE_TUNE` table is an `et_tune_service`, layout-compatible
with `et_read_service`. Negotiate version 1.0. `tune.read` is needed to negotiate;
every operation is independently checked against the worker session's granted
capabilities at both ends. Existing C ABI 1.0/1.1 prefixes are unchanged.
Use `epictuner/tune.h` from C, or `epictuner/sdk/tune.hpp` from C++20.

All requests and callbacks run on the worker dispatcher. Inputs are copied before
return, replies are borrowed until callback return. Limits, cancellation,
acknowledgement and terminal-callback semantics are those in [READ.md](READ.md).
Cancelling before dispatch prevents local changes. An already accepted commit
is authoritative; cancellation is never a rollback. A transport timeout never
means it is safe to retry a write: refresh first.

## Request mapping

Initialize with `Reads::make(operation, projectGeneration)`. Unused fields must
remain at their defaults. `count` is 1..32 for catalog/snapshot pages. A revision
is a monotonically advancing 32-bit host token, returned in reply `schema`.
Project generations are the same as the project/read service. Revision changes
are conservative: manual edits, AutoTune, project/save and connection changes
invalidate an old preview. Never derive a revision by adding to it.

| Operation | Request | Reply |
|---|---|---|
| CATALOG | offset/count | Paginated field/table/curve IDs; item label is kind, units is field units or backing value field; text is definition SHA-256; schema is revision |
| SNAPSHOT | id is field/table/curve ID; offset/count are value indices | items contain actual stored physical values and element indices in sequence; text is bounded JSON metadata |
| PROPOSE | id is definition SHA-256; schema is base revision; text is proposal below | Host-owned proposal handle and normalized preview values; does not modify calibration |
| APPLY | same definition/base revision plus proposal handle | Consumes that session-owned proposal; actual stored values and local acceptance flags |
| UNDO / REDO | definition and current revision | One labeled coherent group for the last accepted plugin transaction; rejects if another edit intervened |
| STATUS | generation | Current RAM readback status in flags/text; revision in schema |
| BURN | definition and current revision | Opens host confirmation; does not burn on receipt; requires separate tune.burn capability |
| DISCARD | proposal handle | Releases an unused preview; no apply capability needed |

PROPOSE and DISCARD require `tune.propose`; APPLY/UNDO/REDO require `tune.apply`.
CATALOG/SNAPSHOT/STATUS require `tune.read`. Handles are scoped to worker session
and project generation. There are at most eight outstanding proposals per worker;
DISCARD unused previews. Refreshing a revision retires older previews. Restart,
project replacement and disconnect invalidate proposals without replaying them.

A proposal is UTF-8 TSV, with a nonempty label of at most 128 characters on its
first line, followed by 1..64 lines `field<TAB>index<TAB>expected<TAB>requested`.
The entire payload is at most 4096 bytes. Field names and labels cannot contain
embedded tab/newline/NUL. Use the supplied encoder rather than locale-dependent
number formatting:

```cpp
const auto body = et::sdk::Tune::proposal("Fuel correction", {
    {"fuel", 0, oldValue, newValue}
});
auto request = et::sdk::Reads::make(ET_TUNE_PROPOSE, generation);
request.id = et::sdk::string(definitionHash);
request.schema = baseRevision;
request.text = et::sdk::string(body);
tune.request(request, previewCallback);
```

Preview/result items use `id` = field, `sequence` = element index, `timestamp` =
expected old value, `value` = normalized stored value. This mapping is specific
to tune operations; `timestamp` is not a time for a tune item.

Snapshot JSON includes id, units, digits, bounds and whether each bound exists,
shape, enum value/label pairs, readOnly, and native field type. Table/curve metadata
also includes kind, title, axisLabels, backing x/y/z field IDs and read-only axes.
Read their backing fields separately to obtain axis values. String/PC-variable
and protected/read-only axis writes are rejected; this numeric service never
exposes raw page pointers. Unsupported metadata exceeding 4096 bytes returns a
limit error rather than truncating a definition.

## Correctness and ECU outcomes

The host validates every element on an isolated copy using ProjectManager's
storage/scaling rules. It rejects malformed numbers, bounds, dimensions, duplicate
cells, old-value conflicts, protected axes and overlapping/scaling changes whose
final stored values disagree with the proposed normalization. It serializes and
reads back the candidate before publishing the live state once. The normal host
write queue receives changed spans after local acceptance. There are no partial
local commits on validation failure; undo reverses the complete accepted group.

`LOCAL_ACCEPTED` is distinct from `RAM_UNVERIFIED`, `RAM_VERIFIED`, `RAM_FAILED`,
`OFFLINE`, and `FLASH_PENDING`. In connected mode the comms thread drains the
normal write queue and reads ECU pages to verify the exact accepted snapshot.
STATUS reports the result only while the project/revision still matches. A failed
or mismatched readback retains the local tune and directs the user to the normal
synchronization flow. It does not replay or silently roll back physical writes.
Whole-page readback briefly pauses polling on the communications thread.

Firmware updates, tune synchronization and packet-proxy ownership block edits.
Disconnected operation is rejected unless the project is explicitly offline.
Only verified Serial/TcpBridge/Mock local project connections are supported;
remote sessions are rejected. Burn is a separate confirmation tied to the current
session, generation and revision. The existing Burn flow reports its result;
opening a panel, reconnecting, proposing, applying or restoring state never burns.

See [PREFABS.md](PREFABS.md) and the two calibration examples. The fuel demo
accepts offline/Mock proposals only and has no Burn capability.
