feat(host): name unclaimed requests, and record the FUT task vocabulary
Milestone 2 groundwork. The passthrough arm forwarded to Python without ever recording WHAT was asked for, so on staging -- where the upstream is deliberately dead -- an unhandled request produced an anonymous 502. It now logs method, path and body length before forwarding, which is how the next unclaimed route gets identified: utas-host owner=PYTHON route=passthrough method=GET path=/ut/... body_len=0 Also records the FUT TASK vocabulary read out of the live client. The client drives UTAS through named tasks held in a CardsDLL .rdata table of 0x20-byte MixedCase/UPPERCASE slots, with a .data descriptor table giving each a task id: ApplyCard 0x0d, ApplyCardByRes 0x0e, ConsumeCard, ActivateCard, AssingCard(sic), MoveCard, MoveCardByRes, SwapCard, DiscardCard, DiscardCardByRes, ViewCards, ... So consumable application IS a first-class client action even though the route table contains no /apply endpoint -- it must ride an existing route. The descriptor's function pointer is a `mov [rip+flag], cl; ret` setter, not a request builder, so the request is assembled elsewhere keyed by task id; that is cheaper to answer with one live capture than with more static tracing. Search tooling carries mandatory positive controls (tradePile, ut/%s/item, squad -- all FOUND), so the "no /apply route" result is a valid negative rather than a failed scan. Host 120 lib tests, fmt and clippy clean.
This commit is contained in:
@@ -105,3 +105,34 @@ So applying a consumable is **not** a dedicated server route. If it reaches the
|
||||
server at all it must ride `PUT ut/%s/item`, and L5/L6 should be pursued by
|
||||
capturing that PUT's payload while applying a card — not by looking for an
|
||||
endpoint that does not exist.
|
||||
|
||||
## FUT task vocabulary (2026-08-21, live)
|
||||
|
||||
The client drives UTAS through named TASKS, not just URLs. The task-name table
|
||||
lives in CardsDLL `.rdata` as 0x20-byte inline slots holding MixedCase/UPPERCASE
|
||||
pairs (`tools/apply_route_search.py`, controls `tradePile`/`ut/%s/item`/`squad`
|
||||
all FOUND):
|
||||
|
||||
```
|
||||
ViewCards AssingCard(sic) ApplyCard ApplyCardByRes
|
||||
ActivateCard ConsumeCard DiscardCard DiscardCardByRes
|
||||
DiscardACard MoveCard MoveCardByRes SwapCard
|
||||
CreateMatch MatchReady DestroyMatch PlayGame ResetMatch KeepAlive
|
||||
LoadCategoryDetails LoadSetChallenges StartChallenge LoadSquadChallenge
|
||||
SaveSquadChallenge SubmitChallenge TagSets SetSbcData
|
||||
TournamentList TournamentTeams SetUserInfo GetHistorical SetTutData ...
|
||||
```
|
||||
|
||||
A descriptor table in `.data` pairs each name with a task id and a small setter
|
||||
thunk, e.g. `ApplyCard` id **0x0d** at `0x1802cb170`, `ApplyCardByRes` id **0x0e**
|
||||
at `0x1802cb1a0`. The thunks are `mov [rip+flag], cl; ret` (a per-task flag), NOT
|
||||
request builders, so the request is assembled elsewhere keyed by task id.
|
||||
|
||||
**So consumable application IS a first-class client action (`ApplyCard` /
|
||||
`ApplyCardByRes` / `ConsumeCard`), even though no `/apply` URL exists.** It
|
||||
therefore rides an existing route. Which one is a one-capture question, and the
|
||||
host now names every unclaimed request:
|
||||
|
||||
```
|
||||
utas-host owner=PYTHON route=passthrough method=GET path=/ut/... body_len=N
|
||||
```
|
||||
|
||||
@@ -4022,10 +4022,21 @@ impl Server {
|
||||
Route::MarketData => self.handle_marketdata(path, target),
|
||||
Route::SecurityQuestion => self.handle_security_question(method, target, headers),
|
||||
Route::Passthrough => {
|
||||
// Name the request BEFORE forwarding. On staging the upstream is
|
||||
// deliberately dead, so this line is the only record of what the
|
||||
// client asked for -- which is exactly how an unclaimed route is
|
||||
// discovered (see docs/CLIENT_ROUTE_SURFACE.md). Bodies are not
|
||||
// logged; the path and method are enough to identify a route.
|
||||
eprintln!(
|
||||
"utas-host owner=PYTHON route=passthrough method={method} path={target} body_len={}",
|
||||
body.len()
|
||||
);
|
||||
let resp = match self.pass.forward(method, target, headers, body) {
|
||||
Ok(r) => r,
|
||||
Err(e) => {
|
||||
eprintln!("utas-host ERROR passthrough to Python failed: {e}");
|
||||
eprintln!(
|
||||
"utas-host ERROR passthrough failed method={method} path={target}: {e}"
|
||||
);
|
||||
WireResponse {
|
||||
status: 502,
|
||||
headers: vec![(
|
||||
|
||||
Reference in New Issue
Block a user