usage: vexa-station-verdict [-h] {render,sign} ...
vexa-station-verdict — render and sign a station's departure verdict.
THE OBJECT THIS WRITES. The delivery line's station cell is ADMIT (verify the
upstream station's verdict) -> MINT (the PRD) -> PROVE (the fills) -> DEPART.
Lane A built the currency that DEPART compiles: the `values_proven` block, out
of the station's committed fills. This is the signature over it. A downstream
contract that sets
require_attestations: [ { kind: station-verdict, station: <S> } ]
refuses cargo that never departed S.
WHY IT IS NOT `vexa_station.py`. That tool is about a CUSTOMER's station — it
ingests a station report and gates a publish on that customer's contract (S1..
S10). The word "station" means a different thing there: an installation we
deliver to, not a stop on our own line. Putting a verdict renderer inside it
would make one 1200-line tool answer two unrelated questions, and every reader
would have to work out which sense of "station" a given check meant. Separate
file, separate verb namespace.
render compute the verdict from the contract and the proof block, and write
station-verdict.json
sign cosign key-mode over that file, offline, same flags as every other
signature this repo makes
THE VERDICT IS COMPUTED, NEVER SUPPLIED. There is no --verdict flag and there
will not be one. ELIGIBLE iff every `required_values[]` row the contract marks
`enforcement: required` is answered by a `proven` or human-`waived` row in the
values_proven block. That rule is lane A's and it is IMPORTED, not restated:
`read_contract` decides what "required" means, `check_values_proven` decides
what a well-formed row is. A second copy of either would drift, and the drift
would show up as a station signing ELIGIBLE for a block the subscriber's
verifier then refuses.
A REFUSED VERDICT IS STILL AN OBJECT, and it is signable. A station that finds
a required value unproven could simply write nothing — that is what the older
accumulated-attestation path does, and it is why "no attestation" is ambiguous
downstream between "the station said no" and "the station never ran". A
REFUSED verdict names the values that caused it. `platform-entry` refuses to
carry one into an entry: an entry does not carry a station's no.
WHAT `values_proven_sha256` HASHES. The CANONICAL form of the block — keys
sorted recursively, no whitespace, no trailing newline, UTF-8 — not the bytes
of the file on disk. The block is re-serialised when `platform-entry` embeds
it, so a file hash would never match what a subscriber actually holds, and the
check would fail on every honest entry. The verifier recomputes the same string
with `jq -Sc '.values_proven'`; that agreement is tested, not assumed.
usage:
python3 publisher/vexa_station_verdict.py render \
--station vexa-staging-bbb \
--candidate-sha 0f9e... --manifest-sha256 4c1d... \
--contract <ledger>/channels/vexa-internal/contracts/internal-estate-2026-09.json \
--values-proven values-proven.json \
--out work/verdict
python3 publisher/vexa_station_verdict.py sign \
--verdict work/verdict/station-verdict.json --key cosign.key
Exit 0 wrote it · 2 usage · 3 refused, and says what is wrong.
positional arguments:
{render,sign}
render compute and write a station's departure verdict
sign cosign key-mode over a rendered verdict, offline
options:
-h, --help show this help message and exit