Metadata-Version: 2.4
Name: axon-contracts
Version: 0.8.0a2
Summary: Axon controller <-> agent contracts: protobuf messages, MQTT topics, gRPC endpoints. Single source of truth for the Go agent and the Python API.
License: AGPL-3.0-only
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: protobuf<5,>=4.25
Provides-Extra: dev
Requires-Dist: grpcio-tools==1.62.3; extra == "dev"
Requires-Dist: flake8>=6.0; extra == "dev"
Provides-Extra: test
Requires-Dist: pytest>=7.0; extra == "test"
Dynamic: license-file

# axon-contracts

Single source of truth for Axon wire contracts: **protobuf messages**, **MQTT
topics**, the **gRPC EE-data endpoint**, and the public **Pulse sensor HTTPS
ingest schema**. The Go submodule also contains small transport-independent
runtime packages shared by the switch agent and Pulse.

## Layout

```
proto/                     canonical .proto source of truth (19 messages, 1 gRPC service)
gen/go/                    Go module: github.com/Taurine-Technology/axon-contracts/gen/go
  pb/                        generated Go bindings (package pb)
  topics/                    hand-written MQTT topic builders (package topics)
  diagnostics/               bounded DNS/HTTP/path/speed/responsiveness engine
  buildinfo|compress|geo|logging/
                              small shared runtime packages
schemas/
  sensor_ingest.schema.json  authoritative Pulse ingest protocol v1 payload
axon_contracts/            Python package (pip: axon-contracts)
  proto/                     generated *_pb2.py / *_pb2_grpc.py
  mqtt/topics.py             MQTT topic builders (parity with gen/go/topics)
  constants.py               shared constants + gRPC EE-data endpoint contract
tools/                     pinned protoc + plugins (provisioned by `make tools`)
tests/                     cross-language topic parity + proto import guards
```

## Codegen

```sh
make tools        # provision pinned protoc + protoc-gen-go (gitignored)
make proto        # regenerate Go (gen/go/pb) + Python (axon_contracts/proto)
make proto-check  # CI guard: regen and fail if committed output drifted
```

Pinned versions live in `tools/versions.env`. The Go bindings use protoc 35.1 +
protoc-gen-go 1.36.11 (byte-identical to the agent's committed headers). The Python
bindings are generated with `grpcio-tools==1.62.3` so the gencode validates under
**protobuf 4.25.x** — axon-ee is capped there by TensorFlow and must consume these
bindings unchanged. `tests/test_proto_imports.py` enforces this.

## Consuming

**Go (agent):**
```sh
go get github.com/Taurine-Technology/axon-contracts/gen/go@gen/go/v0.3.0
```
```go
import (
    "github.com/Taurine-Technology/axon-contracts/gen/go/pb"
    "github.com/Taurine-Technology/axon-contracts/gen/go/topics"
)
```

**Python (API/controller):**
```sh
pip install "axon-contracts @ git+https://github.com/Taurine-Technology/axon-contracts.git@v0.3.0"
```
```python
from axon_contracts.proto import mirror_flow_stats_messages_pb2
from axon_contracts.mqtt.topics import data_pattern, MIRROR_FLOW_STATS
```

## v0.7.0 — Smart Uplink live view + passive TCP RTT

**Smart Uplink live view (post-shaper per-host rates).** `proto/qos_messages.proto` adds the live-view session control and data messages:
`QoSLiveViewRequest` / `QoSLiveViewResponse` (control topics `qos/live/request`
and `qos/live/response`, QoS 1) and `QoSLiveBatch` (data topic `qos-live`, QoS 0,
zstd-compressed). While a session is active the agent publishes, per shaped
interface and per LAN host, the bytes/packets that left the interface after the
qdisc dequeued them, plus the qdisc's own sent/drop deltas; per-host drops are
explicitly reported as not available. Additive: an older controller never opens
a session and an older agent answers nothing (the controller times out). Go tag
`gen/go/v0.7.0` in lockstep with `v0.7.0`.

**Passive TCP RTT on mirror flow stats.** `proto/mirror_flow_stats_messages.proto` adds `FlowRttLeg` and the `rtt_src` /
`rtt_dst` sub-messages (fields 32/33) on `MirrorFlowStatsEntry`. The agent sits
mid-path, so a TCP RTT splits into a leg towards `src_ip` and a leg towards
`dst_ip`; the capture layer does not know which endpoint is the LAN client, so
the controller maps those onto client ("Subscriber RTT") and server ("Upstream
RTT") legs with its own LAN-network orientation. `samples`/`sum_us` are
cumulative like the byte counters, `min_us`/`max_us` are life-of-flow.
Additive: older readers ignore the new fields, and an absent sub-message means
the leg was never measured — never "zero RTT". Go tag `gen/go/v0.7.0` in
lockstep with `v0.7.0`.

## v0.6.0 — DeviceProfile

`proto/device_profile_messages.proto` adds `DeviceProfile` (agent hardware,
kernel, queueing and capture capabilities; status topic `device/profile`,
QoS 1) and `DeviceProfileRequest` (control topic `device/profile/request`,
QoS 1). Additive: an older controller ignores the topic and an older agent
never publishes it. Go tag `gen/go/v0.6.0` in lockstep with `v0.6.0`.

## Versioning

- **Python wheel** → published to the `axon-dist` PEP 503 index on a `vX.Y.Z` tag
  (see `.github/workflows/release.yml`).
- **Go module versioning:** the module lives in the `gen/go` subdirectory, so its
  semver tags MUST be prefixed: `gen/go/vX.Y.Z` (Go submodule tag convention).
  Keep the Go tag and the `vX.Y.Z` wheel tag in lockstep.

The shared diagnostics and Pulse ingest schema prepare **v0.4.0** (Go tag
**gen/go/v0.4.0**). Publish the root and Go-submodule tags in lockstep after
this release commit merges; agent and Pulse consumers must land only after the
Go tag is available.

## Licensing

The `gen/go` module is Apache-2.0 under `gen/go/LICENSE`; this explicit
submodule license permits it to be linked into the closed-source switch agent
and Pulse without importing GPL code. The repository root and Python package
remain AGPL-3.0-only.

## Scope

Owns proto + MQTT topics + the gRPC EE-data endpoint contract, Pulse wire
schemas, and dependency-light Go code genuinely shared by more than one Axon
binary. Agent-internal protos (for example `agent_ipc`) stay in the agent repo.
Human-facing REST APIs, distribution URLs, and broker bootstrap URLs remain out
of scope.

The enrollment/bootstrap payload itself stays out of scope (it is a hand-rolled
JSON dict built by axon-ee and read by the agent's `internal/config/bootstrap.go`).
The only shared bootstrap artifacts here are inert **string literals** in
`axon_contracts.constants` — key names, default tag, and status values — so the
Python controller and Go agent agree on spelling without a schema. The tailnet
adoption constants (`TAILSCALE_DEFAULT_AGENT_TAG`, `BOOTSTRAP_FIELD_TAILSCALE_*`,
`TAILSCALE_STATUS_*`) follow the existing `ENROLLMENT_TOKEN_PREFIX` /
`BOOTSTRAP_CONFIG_PATH` precedent.

### RTT sample distribution v1

`FlowRttLeg.histogram_version=1` carries 21 non-overlapping cumulative counts
of accepted TCP timestamp samples. Inclusive upper bounds (µs) are
100, 250, 500, 1000, 2000, 4000, 8000, 12000, 20000, 30000, 50000, 80000,
120000, 200000, 400000, 800000, 1600000, 3200000, 6400000, 10000000, +∞.
Their sum equals `samples`; handshake measurements are excluded. Consumers
ignore unknown histogram versions while retaining the legacy RTT fields.
For version 1, malformed bin counts or totals invalidate only the histogram;
validate and retain the legacy RTT fields independently. Percentiles identify a bin range, not an exact value.

`rtt_sampling_method` reports each flow's actual path: userspace (1), userspace
followed by active kernel bypass with sustained sampling (2), or active bypass
without sustained sampling (3). Zero means unreported. Configured bypass does
not establish runtime support: `DeviceProfileCapture` distinguishes successful
filter attachment, active bypass, and active kernel RTT. Count-only and off
modes continue userspace sampling. The optional kernel state has its own cap;
a flow may report method 3 even when the device advertises sampler support.
