Metadata-Version: 2.4
Name: robot-fleet-connect
Version: 0.1.0
Summary: Stream your robot's policy decisions to a GROUND CONTROL deployment over WSS, and leave (with a verified purge) at any time.
License: MIT
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: websockets>=12

# robot-fleet-connect

Stream your robot's policy decisions to a GROUND CONTROL deployment over WSS
— and leave, with a verified purge, at any time.

```bash
pip install robot-fleet-connect                    # after first PyPI publish
pip install ./clients/robot-fleet-connect          # from a repo checkout, today

# 1. Mint a guest token (the scope card + consent text come back once):
curl -X POST https://robotfleet.vikramnag.com/ingest/tokens

# 2. Stream — a deterministic simulated G1 decision loop, your own NDJSON,
#    or a live ROS2 topic:
robot-fleet-connect --token <TOKEN> --simulate
cat decisions.ndjson | robot-fleet-connect --token <TOKEN> --ndjson
robot-fleet-connect --token <TOKEN> --ros2 --robot-id my_bot

# 3. Leave any time (never budget-blocked); --purge deletes every row you
#    streamed and prints the verified counts:
robot-fleet-connect --token <TOKEN> --disconnect --purge
```

`--host http://localhost:8000` points the same commands at a local
`make demo` stack. Ctrl-C is a graceful close: your data stays (until the
token TTL) and the token keeps working; `--disconnect` is the explicit
leave.

Consent note: the default host is a PUBLIC demo sandbox — telemetry you
stream (robot ids, policy decisions, observations) is visible to other
visitors. Do not send anything confidential.

Protocol (Phase 6A wave A, locked): first WS frame `{"auth": "<token>"}`,
then NDJSON batch frames with a `{"seq": N}` header line; every frame is
ACKed `{ack, landed, dropped, duplicates, skew_corrected, reasons[],
errors[]}` — losses are counted, never silent. Unacked batches are kept and
resent after a reconnect (the server dedups by event id, so a blip never
double-counts), with exponential backoff 500 ms → 8 s plus jitter.

The message-mapping core under `robot_fleet_connect/_vendor/gc_ingest_core`
is vendored byte-for-byte from the platform's `packages/gc_ingest_core`
(single source of truth; `scripts/sync_vendored_core.py --check` fails CI on
drift).

## Releasing (PyPI Trusted Publishing)

Releases publish to PyPI via **Trusted Publishing (OIDC)** — there is no stored
PyPI token anywhere in the repo or CI. The workflow
`.github/workflows/publish-client.yml` is triggered by a `client-v*` tag and:

1. runs `scripts/sync_vendored_core.py --check` first — a drifted vendored core
   fails the build, so it can never ship;
2. checks the tag matches the `version` in `pyproject.toml`;
3. builds the wheel + sdist from `clients/robot-fleet-connect/`;
4. installs the built wheel in a clean venv across Python 3.10 / 3.11 / 3.12 and
   smoke-tests the `robot-fleet-connect` console script (`--help`, `--version`, argument
   parse + transport generation — the live `--simulate` → server path is proven
   end to end over real TCP in `tests/phase1/test_phase6b_client_e2e.py`);
5. uploads via `pypa/gh-action-pypi-publish` using the workflow's OIDC identity.

To cut a release once the one-time owner setup below is done: bump `version` in
`pyproject.toml`, commit, then tag and push:

```bash
git tag client-v0.1.0
git push origin client-v0.1.0
```

`workflow_dispatch` runs the build + install matrix as a dry run without
publishing (the publish job is gated on a `client-v*` tag).

### One-time owner setup (gated — not automated here)

The **first** publish cannot be done by CI alone; the project owner must, on
pypi.org:

1. register the project name `robot-fleet-connect` (or reserve it via a first
   manual upload), and
2. add a **Trusted Publisher** for it: this GitHub repository, workflow filename
   `publish-client.yml`, and environment name `pypi`.

Until that config exists, the workflow still builds and tests green on every tag
— only the final upload step needs the owner's one-time PyPI setup. This is
called out explicitly rather than faked: no token is committed, and nothing
publishes until the owner wires the trusted publisher.
