Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ members = [
"crates/trios-railway-mcp",
"crates/trios-railway-smoke",
"crates/trios-igla-race",
"crates/trios-igla-ops",
"bin/tri-railway",
"bin/seed-agent",
]
Expand Down
35 changes: 35 additions & 0 deletions crates/trios-igla-ops/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
[package]
name = "trios-igla-ops"
description = "O(1) operator utilities for the IGLA RACE fleet (7 Railway accounts, 6 lanes, Neon Postgres)."
version.workspace = true
edition.workspace = true
license.workspace = true
authors.workspace = true
repository.workspace = true
rust-version.workspace = true

# R1 from trios#143: Rust-only, no .py/.sh. Anchor: phi^2 + phi^-2 = 3.

[[bin]]
name = "fleet-probe"
path = "src/bin/fleet_probe.rs"

[[bin]]
name = "queue-stats"
path = "src/bin/queue_stats.rs"

[[bin]]
name = "queue-victory-hunt"
path = "src/bin/queue_victory_hunt.rs"

[dependencies]
anyhow.workspace = true
serde.workspace = true
serde_json.workspace = true
tokio.workspace = true
tokio-postgres.workspace = true
tokio-postgres-rustls.workspace = true
rustls.workspace = true
webpki-roots.workspace = true
chrono.workspace = true
reqwest.workspace = true
98 changes: 98 additions & 0 deletions crates/trios-igla-ops/src/accounts.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
//! Canonical Railway account registry (7 accounts per operator instruction 2026-05-02).
//!
//! Source of truth, do not hardcode elsewhere. Each record is a compile-time constant;
//! the token is read from the corresponding `RAILWAY_TOKEN_ACC{0..6}` env at runtime
//! to avoid leaking secrets into the binary.
//!
//! Lane→account mapping follows trios#445.

/// One of the 7 operator-supplied Railway accounts.
pub struct Account {
pub tag: &'static str,
pub env_tok: &'static str,
pub project: &'static str,
pub environment: &'static str,
pub kind: TokenKind,
pub lane: &'static str,
}

#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum TokenKind {
Project,
Personal,
}

impl TokenKind {
pub fn auth_header(self, tok: &str) -> (&'static str, String) {
match self {
TokenKind::Project => ("Project-Access-Token", tok.into()),
TokenKind::Personal => ("Authorization", format!("Bearer {tok}")),
}
}
}

pub const ACCOUNTS: &[Account] = &[
Account {
tag: "acc0",
env_tok: "RAILWAY_TOKEN_ACC0",
project: "f29aa9dd-ca0b-460f-ad24-c7680c6717fb",
environment: "fade0d77-af80-4d01-bc34-2ce27283d766",
kind: TokenKind::Project,
lane: "IGLA-RAILWAY-FOLLOWER-A",
},
Account {
tag: "acc1",
env_tok: "RAILWAY_TOKEN_ACC1",
project: "e4fe33bb-3b09-4842-9782-7d2dea1abc9b",
environment: "54e293b9-00a9-4102-814d-db151636d96e",
kind: TokenKind::Personal,
lane: "IGLA-RAILWAY-LEADER",
},
Account {
tag: "acc2",
env_tok: "RAILWAY_TOKEN_ACC2",
project: "12c508c7-1196-468d-b06d-d8de8cb77e93",
environment: "441bd3a6-f6d8-455e-b567-376b7538e9f1",
kind: TokenKind::Personal,
lane: "IGLA-RAILWAY-FOLLOWER-B",
},
Account {
tag: "acc3",
env_tok: "RAILWAY_TOKEN_ACC3",
project: "8ab06401-aa28-4af7-9faf-39a1548b7008",
environment: "cd2d987b-dbbb-49ba-953b-f5e9486b906c",
kind: TokenKind::Personal,
lane: "IGLA-RAILWAY-FOLLOWER-C",
},
Account {
tag: "acc4",
env_tok: "RAILWAY_TOKEN_ACC4",
project: "0247abaa-6487-4347-811c-168d7fe53078",
environment: "336c41a9-0d6a-4308-b266-1df6c91590ac",
kind: TokenKind::Personal,
lane: "IGLA-RAILWAY-FOLLOWER-D",
},
Account {
tag: "acc5",
env_tok: "RAILWAY_TOKEN_ACC5",
project: "475a2290-d990-426a-af57-594a934cf6f4",
environment: "5724292a-1c7d-42ca-8859-edcab337c5a9",
kind: TokenKind::Project,
lane: "IGLA-RAILWAY-FOLLOWER-E",
},
Account {
tag: "acc6",
env_tok: "RAILWAY_TOKEN_ACC6",
project: "475a2290-d990-426a-af57-594a934cf6f4",
environment: "5724292a-1c7d-42ca-8859-edcab337c5a9",
kind: TokenKind::Project,
lane: "IGLA-RAILWAY-SPRINT-X",
},
];

/// Sanctioned seeds (quorum) per `enforce_seed_policy()` trigger in Neon.
/// Fibonacci F17..F21. Never queue a `priority=0` row with a seed not in this set.
pub const SANCTIONED_SEEDS: &[u64] = &[1597, 2584, 4181, 6765, 10946];

/// Quick-3 Fibonacci (smaller) used for phi-LR ladder Quick-3 gate.
pub const QUICK3_SEEDS: &[u64] = &[34, 55, 89];
135 changes: 135 additions & 0 deletions crates/trios-igla-ops/src/bin/fleet_probe.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
//! `fleet-probe` — O(1) parallel health check across all 7 Railway accounts.
//!
//! One HTTPS call per account, fanned out via `tokio::spawn`. Prints a single
//! human-readable table. No file writes, no side effects. Safe to run at any cadence.
//!
//! Usage:
//! ```bash
//! source .railway_creds.env
//! cargo run -p trios-igla-ops --bin fleet-probe
//! ```
use anyhow::Result;
use serde::Deserialize;
use serde_json::json;
use trios_igla_ops::accounts::{Account, ACCOUNTS};

#[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
struct ServiceNode {
#[allow(dead_code)]
id: String,
name: String,
}
#[derive(Deserialize, Debug)]
struct Edge {
node: ServiceNode,
}
#[derive(Deserialize, Debug)]
struct Edges {
edges: Vec<Edge>,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
struct ProjNode {
name: String,
services: Option<Edges>,
}
#[derive(Deserialize, Debug)]
struct RespData {
project: Option<ProjNode>,
}
#[derive(Deserialize, Debug)]
struct GqlResp {
data: Option<RespData>,
errors: Option<serde_json::Value>,
}

const Q: &str = "query($id:String!){project(id:$id){name services{edges{node{id name}}}}}";

async fn probe(acc: &'static Account) -> (String, String, String, Vec<String>) {
let tok = match std::env::var(acc.env_tok) {
Ok(t) => t,
Err(_) => {
return (
acc.tag.into(),
"NO_TOKEN".into(),
format!("env {} unset", acc.env_tok),
vec![],
)
}
};
let (h_name, h_val) = acc.kind.auth_header(&tok);
let client = match reqwest::Client::builder()
.timeout(std::time::Duration::from_secs(15))
.build()
{
Ok(c) => c,
Err(e) => return (acc.tag.into(), "CLIENT_ERR".into(), e.to_string(), vec![]),
};
let body = json!({"query": Q, "variables": {"id": acc.project}});
let resp = client
.post("https://backboard.railway.app/graphql/v2")
.header("Content-Type", "application/json")
.header(h_name, h_val)
.json(&body)
.send()
.await;
let resp = match resp {
Ok(r) => r,
Err(e) => return (acc.tag.into(), "HTTP_ERR".into(), e.to_string(), vec![]),
};
let gql: GqlResp = match resp.json().await {
Ok(g) => g,
Err(e) => return (acc.tag.into(), "PARSE_ERR".into(), e.to_string(), vec![]),
};
if let Some(errs) = gql.errors {
return (acc.tag.into(), "AUTH_ERR".into(), errs.to_string(), vec![]);
}
match gql.data.and_then(|d| d.project) {
Some(p) => {
let svcs = p
.services
.map(|s| s.edges.into_iter().map(|e| e.node.name).collect::<Vec<_>>())
.unwrap_or_default();
(acc.tag.into(), "OK".into(), p.name, svcs)
}
None => (
acc.tag.into(),
"NOT_FOUND".into(),
"project=null".into(),
vec![],
),
}
}

#[tokio::main(flavor = "multi_thread", worker_threads = 4)]
async fn main() -> Result<()> {
let handles: Vec<_> = ACCOUNTS.iter().map(|a| tokio::spawn(probe(a))).collect();
println!(
"{:<5} {:<10} {:<7} {:<32} {}",
"ACC", "STATUS", "LANE", "PROJECT", "SERVICES"
);
for (a, h) in ACCOUNTS.iter().zip(handles) {
let (tag, status, info, svcs) = h.await?;
let project_name = if svcs.is_empty() {
info.chars().take(30).collect::<String>()
} else {
info
};
let svc_str = if svcs.is_empty() {
"-".into()
} else {
format!("{} [{}]", svcs.len(), svcs.join(","))
};
let svc_trunc: String = svc_str.chars().take(100).collect();
println!(
"{:<5} {:<10} {:<7} {:<32} {}",
tag,
status,
&a.lane[13..].chars().take(6).collect::<String>(),
project_name.chars().take(30).collect::<String>(),
svc_trunc
);
}
Ok(())
}
90 changes: 90 additions & 0 deletions crates/trios-igla-ops/src/bin/queue_stats.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
//! `queue-stats` — O(1) Neon snapshot of strategy_queue, scarabs, bpb_samples.
//!
//! One Neon connection, four queries pipelined. Prints a single mission report
//! with: queue counts by status, active scarabs, latest emits, leaderboard top-10.
//!
//! Usage:
//! ```bash
//! NEON_DATABASE_URL=... cargo run -p trios-igla-ops --bin queue-stats
//! ```
use anyhow::Result;
use trios_igla_ops::neon::{connect, DEFAULT_DSN};

#[tokio::main(flavor = "multi_thread", worker_threads = 2)]
async fn main() -> Result<()> {
let dsn = std::env::var("NEON_DATABASE_URL").unwrap_or_else(|_| DEFAULT_DSN.into());
let client = connect(&dsn).await?;

println!("=== strategy_queue (IGLA-*) by status ===");
for r in client
.query(
"SELECT status, count(*) FROM public.strategy_queue WHERE canon_name LIKE 'IGLA-%' GROUP BY 1 ORDER BY 1",
&[],
)
.await?
{
let s: &str = r.get(0);
let c: i64 = r.get(1);
println!(" {:8} {}", s, c);
}

println!("\n=== running rows by lane (worker svc) ===");
let rows = client
.query(
"SELECT s.railway_svc_name, count(*) FROM public.strategy_queue sq \
JOIN public.scarabs s ON s.id = sq.worker_id \
WHERE sq.status='running' AND sq.canon_name LIKE 'IGLA-%' \
GROUP BY 1 ORDER BY 2 DESC",
&[],
)
.await?;
if rows.is_empty() {
println!(" (no running rows)");
}
for r in &rows {
let n: &str = r.get(0);
let c: i64 = r.get(1);
println!(" {:30} {}", n, c);
}

println!("\n=== active IGLA-RAILWAY-* scarabs (heartbeat <60s) ===");
let rows = client
.query(
"SELECT railway_svc_name, railway_acc, EXTRACT(EPOCH FROM now()-last_heartbeat)::int AS hb_s \
FROM public.scarabs \
WHERE railway_svc_name LIKE 'IGLA-RAILWAY-%' \
AND last_heartbeat > now() - interval '60 seconds' \
ORDER BY railway_svc_name",
&[],
)
.await?;
for r in &rows {
let n: &str = r.get(0);
let a: &str = r.get(1);
let s: i32 = r.get(2);
println!(" {:30} acc={} hb={}s", n, a, s);
}

println!("\n=== best-BPB leaderboard (top 15, IGLA-*) ===");
for r in client
.query(
"SELECT canon_name, seed, max(step) AS s, min(bpb) AS b, count(*) AS n \
FROM public.bpb_samples WHERE canon_name LIKE 'IGLA-%' \
GROUP BY 1,2 ORDER BY b ASC LIMIT 15",
&[],
)
.await?
{
let c: &str = r.get(0);
let s: i32 = r.get(1);
let st: i32 = r.get(2);
let b: f64 = r.get(3);
let n: i64 = r.get(4);
println!(
" bpb={:.4} step={:>5} seed={:>5} n={:>2} {}",
b, st, s, n, c
);
}

Ok(())
}
Loading
Loading