> ## Documentation Index
> Fetch the complete documentation index at: https://delivery.vexa.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# vexa_stations.py

> Read and write the channel/station ledger that outlives the bucket.

Source: `publisher/vexa_stations.py` · audience: publisher

## Overview

Emitted from `python3 publisher/vexa_stations.py --help`.

```text theme={null}
usage: vexa-stations [-h] [--ledger LEDGER] {record-publish,record-ingest,pin,show} ...

vexa-stations — the reducer that writes the channel/station ledger.

The bucket behind `channel.vexa.ai` is a DISTRIBUTION copy. This module writes
the thing it is a copy of: a git repository (a private repository) that
holds, per channel, what has been published and where every station stands.

    channels/<channel>/channel.yaml                 last entry_seq, current
                                                    position, pin map, expiry
    channels/<channel>/stations/<station>/state.yaml   subscribed position,
                                                    last receipt, flags
    channels/<channel>/stations/<station>/receipts/     ingested bundles verbatim

**Git history IS the audit trail.** Every reducer call is one commit, made BY
PATHSPEC, naming what moved. There is no separate log to keep in sync, and
nothing here is a cache of the bucket: after this, `entry_seq` authority lives
in `channel.yaml` and the copy inside the published entry is derived.

**One writer per surface**, which is the whole design and the only rule that
matters when two operators run at once:

  - `vexa_channel.py push`  is the sole writer of `channel.yaml`
  - `vexa_station.py ingest` is the sole writer of `stations/<station>/*`

A surface with two writers does not error. It produces a plausible result and
loses one writer's intent, which is why the split is enforced here rather than
left to convention: `record_publish` refuses to touch a station directory and
`record_ingest` refuses to touch `channel.yaml`.

The one fact that legitimately appears on both sides is a station's position,
and it appears as TWO DIFFERENT FACTS. `channel.yaml`'s `pins:` is the
publisher's intent — what we promoted this station to, moved by a human's pin
commit. `state.yaml`'s `subscribed_position` is the observation — what the
station's own bundle said it was following when it last reported. A divergence
between them is a finding, not a bug: it means a promotion did not land, or a
station moved itself.

positional arguments:
  {record-publish,record-ingest,pin,show}
    record-publish      reduce a published entry into channel.yaml
    record-ingest       reduce an ingested station into state.yaml
    pin                 move a station's pin (a promotion)
    show                render the ledger

options:
  -h, --help            show this help message and exit
  --ledger LEDGER       checkout of the vexa-stations ledger (default: $VEXA_STATIONS_DIR)
```

## `record-publish`

Reduce a published entry into `channels/<channel>/channel.yaml`. You rarely type this: `vexa-channel push --ledger` calls it, so the ledger cannot drift from the act it records. Reach for it directly only to seed a channel from an entry that was published before the ledger existed.

```text theme={null}
usage: vexa-stations record-publish [-h] --entry ENTRY [--entry-digest ENTRY_DIGEST] [--ref REF]
                                    [--channel-tag CHANNEL_TAG] [--note NOTE]

options:
  -h, --help            show this help message and exit
  --entry ENTRY         built entry directory or entry.json
  --entry-digest ENTRY_DIGEST
                        digest the entry was pushed at
  --ref REF             registry ref the entry lives at
  --channel-tag CHANNEL_TAG
                        moving tag (default current)
  --note NOTE           one line about why this entry exists
```

## `record-ingest`

Reduce an already-ingested station directory into its `state.yaml` and store its receipt. Also rarely typed directly — `vexa-station ingest --ledger` calls it — but it is how you backfill a station that was ingested before the ledger, without asking the customer to send the report again.

```text theme={null}
usage: vexa-stations record-ingest [-h] --channel CHANNEL --station STATION
                                   --station-dir STATION_DIR [--bundle BUNDLE]

options:
  -h, --help            show this help message and exit
  --channel CHANNEL
  --station STATION
  --station-dir STATION_DIR
                        stations/<name>/ as written by vexa_station.py ingest
  --bundle BUNDLE       the station report, stored verbatim in receipts/
```

## `pin`

Move a station's pin: the promotion. This is the one write to `channel.yaml` a human makes, and it refuses to run without a justification, because a promotion with no stated reason is indistinguishable in history from a slip.

```text theme={null}
usage: vexa-stations pin [-h] --channel CHANNEL --station STATION --position POSITION
                         --justification JUSTIFICATION

options:
  -h, --help            show this help message and exit
  --channel CHANNEL
  --station STATION
  --position POSITION   chart version, or * to follow
  --justification JUSTIFICATION
                        the dev station's receipt that justifies the promotion
```

## `show`

Read the whole ledger in one screen: every channel's sequence and current entry, and every station's pin, observed position, last verdict and flags. The fastest answer to "which customers are behind, and on what".

```text theme={null}
usage: vexa-stations show [-h]

options:
  -h, --help  show this help message and exit
```
