Skip to content

universe: publish the pricing token universe as a live-loadable document - #83

Open
kaisbaccour wants to merge 3 commits into
mainfrom
2026-09-22-token-universe
Open

kaisbaccour wants to merge 3 commits into
mainfrom
2026-09-22-token-universe

Conversation

@kaisbaccour

@kaisbaccour kaisbaccour commented Sep 22, 2026

Copy link
Copy Markdown
Contributor

Part 2 of 3. Bucket: T0Trade/t0.devops#710. Loader: st0x.pricing, next.

Why

Listing a token is seven config edits across six repos and six PAM-gated releases, and every one of them restarts the service. A pricing restart refuses every quote for cold_start_hold_secs (420s today) and then needs an oracle bounce, so a listing costs real downtime in market hours.

The universe — which (chain, symbol) slots a plane serves — is pure enumeration: no prices, no spreads, no keys. So it is the one part of that config that can move to a live-loaded channel without weakening a release gate.

Why it is safe

A symbol published here with no spread row in the pricing plane's own released config cannot be quoted at all: the model returns NoSpreadProfile, and the service omits it from /snapshot and refuses it on the WS. Onboarding stays two deliberate steps:

  1. this file → the plane publishes a mark for the symbol;
  2. a spread row in st0x.pricing deploy/config/<env>.toml, released the normal PAM-gated way → the plane quotes it.

Step 1 is reversible in seconds and cannot misprice anything. That is the whole argument for letting this bypass the release gate, so it is written into the header of universe.toml. Never add a price-affecting field here.

A selection, not a projection

Base Robinhood (4663) Ethereum
token-lists/ carry 54 56 56
staging pricing serves 46 3 1

Every gap is a decision, and the file now records each one: wtSGOV is off staging until the broker position is bought back; 4663 carries only the launch pair plus the wtSGOV cross-chain parity probe, because nothing else is listed there until the volume cap and the external watchdog land; the European names (wtAIR.PA, wtBMW.DE, wtMC.PA) have no Alpaca upstream at all. wtRKLB runs the other way — in the pricing config, absent from the lists — so it gets an explicit [[override]] rather than being silently dropped.

The first publish is a no-op

Seeded from st0x.pricing deploy/config/staging.toml at origin/master, then diffed field by field:

token rows live/rendered: 50 50   only in live: []   only in rendered: []
asset rows live/rendered: 47 47   only in live: []   only in rendered: []
IDENTICAL

The two derivations it relies on were checked across all 103 token rows and 95 asset rows of both planes, not sampled: vault == address everywhere, and upstream == symbol minus the wt prefix everywhere.

The renderer refuses rather than guesses

Tested: a typo (wtAAPPL), an unknown env, an unknown chain, a duplicate symbol, and an empty selection are each a hard error. A missing address is never a silent drop — a silent drop would take a live symbol off the plane.

Output is canonical (sorted, no timestamp, revision = sha256 of the content), so re-rendering an unchanged selection is byte-identical and mints no generation. Per-commit provenance rides in the GCS object's custom metadata instead. Verified: two renders cmp clean.

CI judges the candidate inside the live image

Today a pricing config is parsed by --validate-config inside the exact image that will read it. A runtime-loaded document would bypass that, which is precisely the config-ahead-of-image hole (liquidity 2026-09-04, RAI-2266). So the workflow pulls the image the staging plane is running and validates against it.

An image with no --validate-universe fails the run rather than warning: until the st0x.pricing loader is released to staging there is nothing to publish to. That also makes the merge order explicit — t0.devops#710, then the pricing loader, then this.

Staging only. The publisher is conditioned to the staging/ prefix in t0.devops, so this workflow cannot reach a production plane.

🤖 Generated with Claude Code


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

kaisbaccour and others added 3 commits September 23, 2026 00:12
Listing a token today is seven config edits across six repos and six
PAM-gated releases, and every one of them restarts the service. A pricing
restart refuses every quote for cold_start_hold_secs (420s) and then needs
an oracle bounce, so a listing costs real downtime in market hours.

The universe -- which (chain, symbol) slots a plane serves -- is pure
enumeration: no prices, no spreads, no keys. So it is the one part of that
config that can move to a live-loaded channel without weakening a release
gate. This adds the selection file, the renderer and the publish workflow.
st0x.pricing loads the result; t0.devops #710 owns the bucket.

universe.toml is a SELECTION, not a projection of token-lists/. The lists
carry 166 rows across the three chains; staging pricing serves 50, and
every gap is a decision the file now records: wtSGOV is off staging until
the broker position is bought back, 4663 carries only the launch pair plus
the wtSGOV parity probe until the volume cap and the external watchdog
land, and the European names have no Alpaca upstream at all. wtRKLB runs
the other way -- in the pricing config, absent from the lists -- so it gets
an explicit [[override]] rather than being silently dropped.

Seeded from st0x.pricing deploy/config/staging.toml at origin/master and
diffed against it: 50 token slots and 47 assets, identical on every field.
The first publish is a deliberate no-op.

The renderer refuses rather than guesses. A symbol absent from the chain's
token list with no override is a hard error (a typo like wtAAPPL cannot
become a silent drop, which would take a live symbol off the plane), as is
an unknown env, an unknown chain, a duplicate, and an empty selection.
Output is canonical -- sorted, no timestamp, with a revision that is the
sha256 of the content -- so re-rendering an unchanged selection is
byte-identical and mints no generation. Per-commit provenance rides in the
GCS object's custom metadata instead.

CI judges the candidate INSIDE the live staging pricing image before
publishing, restoring the guarantee --validate-config gives the released
config today. That is the config-ahead-of-image hole (liquidity 2026-09-04,
RAI-2266), so an image with no --validate-universe fails the run rather
than warning: until the st0x.pricing loader is released to staging there is
nothing to publish to.

Staging only. The publisher is conditioned to the staging/ prefix, so this
workflow cannot reach a production plane.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The step claimed it validated against "the image the staging plane is
actually running". That is not what :latest resolves to -- the release flow
pins digests, so the newest build and the serving digest can differ.

Reading the serving digest needs run.viewer on t0-pricing-staging, which is
not granted. So keep :latest, matching what config-check.yml already does,
and name it for what it is: early warning. The loader re-validates on every
load, and that is the actual gate.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two fixes to the publish workflow.

It called --validate-universe with no --config while the validator still
required one, so the job would have failed the first time it ran. That is
an ordering fact rather than a typo: this repo renders the document,
st0x.pricing owns the config, and the image receives its own from a
deploy-time secret mount rather than from a layer, so neither the runner
nor the container has a file to point at. st0x.pricing now takes the
config as optional and reports which two checks it skipped without it
(chain existence and the spread-aware de-listing warning); everything else
still runs, and the loader re-validates in full on every load.

And the header now names the merge order. The bucket and this workflow's
identity come from t0.devops #710, a gated stack needing a human apply, so
a push here before that lands fails at the auth step. Better to read that
in the file than to diagnose it from a red run.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant