Trade & store logistics: live store-offer exporter mod [reset-gated, offline shell-harness build] #55

Closed
opened 2026-07-05 01:22:36 +00:00 by coilysiren · 4 comments
Owner

Context

The one reset-requiring piece of the trade & store logistics push under the DiscordLink-replacement epic (#37), deliberately isolated so nothing else waits on a server restart. DiscordLink reads live store shelves in-process (it enumerates active StoreComponent instances and their TradeOffer/Stack objects). eco-app's server exports only historical trade events, not the current shelf, so full Trades <item> parity ("what is listed right now, at what price, how much stock") needs a new in-game exporter. Every sibling issue is built to work from history today and to sharpen to live automatically once this lands.

Why this is off the 2-hour clock

Loading a new C# plugin requires restarting the Eco server, which is exactly what Kai wants to avoid for a couple of hours of continuous play. This issue is therefore built and tested entirely offline against a C# shell harness, mirroring the pattern the jobs mod already uses (mods/jobs ships "a C# shell harness mirroring the API shape for local dev"). No live server is touched during development. The built DLL deploys at the next natural server restart, out of band, per the existing mod rollout pattern (#31 is the precedent for a mod-side push). Development can complete fully while the server keeps running.

Scope

The agent builds mods/stores (name the agent's call), a C# Eco server plugin that:

  • Enumerates live offers - walks active StoreComponent instances (via WorldObjectManager.ForEach, the defensive pattern the telemetry mod already uses) and emits current TradeOffer state: item, buy vs sell, price, stock quantity, currency, store name, store owner, and location.
  • Exposes GET /api/v1/stores - authed with the same X-API-Key the other admin surfaces use, returning JSON in a documented DTO. The DTO is the contract the Python store-directory and logistics siblings upgrade to.
  • Ships a shell harness - a local C# harness mirroring /api/v1/stores (like mods/jobs) so the Python side and this plugin can be developed and tested with no live server.
  • Documents the DTO in mods/stores/docs/ and links it from the sibling issues so they can wire the live path.

This is the piece that takes the store-directory, logistics-engine, and watcher siblings from history-derived to shelf-accurate, fully matching DiscordLink's live Trades <item> and live watch feeds - and still exceeding it, since the same data now feeds the website, MCP, arbitrage, and fair-value surfaces, not just a Discord embed.

Acceptance

  • Plugin builds with the build-mod-* ward verb.
  • Shell harness serves the same /api/v1/stores shape for local dev.
  • DTO documented under mods/stores/docs/ and cross-linked from the store-directory and logistics siblings.
  • No live-server dependency in the build or tests. Deploy is a separate, out-of-band step at the next natural restart (do not restart the running server as part of this issue).
  • docs/FEATURES.md updated with the new mod.

Dependencies

Independent to build (offline, shell-harness). Parent: #37. Deploy precedent: #31. Upgrades the store-directory, logistics-engine, and watcher siblings once deployed.

## Context The **one reset-requiring piece** of the trade & store logistics push under the DiscordLink-replacement epic ([#37](https://forgejo.coilysiren.me/coilyco-gaming/eco-app/issues/37)), deliberately isolated so nothing else waits on a server restart. DiscordLink reads live store shelves **in-process** (it enumerates active `StoreComponent` instances and their `TradeOffer`/`Stack` objects). eco-app's server exports only historical trade **events**, not the current shelf, so full `Trades <item>` parity ("what is listed right now, at what price, how much stock") needs a new in-game exporter. Every sibling issue is built to work from history today and to sharpen to live automatically once this lands. ## Why this is off the 2-hour clock Loading a new C# plugin requires restarting the Eco server, which is exactly what Kai wants to avoid for a couple of hours of continuous play. This issue is therefore built and tested entirely **offline against a C# shell harness**, mirroring the pattern the jobs mod already uses (`mods/jobs` ships "a C# shell harness mirroring the API shape for local dev"). No live server is touched during development. The built DLL deploys at the **next natural server restart**, out of band, per the existing mod rollout pattern ([#31](https://forgejo.coilysiren.me/coilyco-gaming/eco-app/issues/31) is the precedent for a mod-side push). Development can complete fully while the server keeps running. ## Scope The agent builds `mods/stores` (name the agent's call), a C# Eco server plugin that: * **Enumerates live offers** - walks active `StoreComponent` instances (via `WorldObjectManager.ForEach`, the defensive pattern the telemetry mod already uses) and emits current `TradeOffer` state: item, buy vs sell, price, stock quantity, currency, store name, store owner, and location. * **Exposes `GET /api/v1/stores`** - authed with the same `X-API-Key` the other admin surfaces use, returning JSON in a documented DTO. The DTO is the contract the Python store-directory and logistics siblings upgrade to. * **Ships a shell harness** - a local C# harness mirroring `/api/v1/stores` (like `mods/jobs`) so the Python side and this plugin can be developed and tested with no live server. * **Documents the DTO** in `mods/stores/docs/` and links it from the sibling issues so they can wire the live path. ## DiscordLink parity This is the piece that takes the store-directory, logistics-engine, and watcher siblings from **history-derived** to **shelf-accurate**, fully matching DiscordLink's live `Trades <item>` and live watch feeds - and still exceeding it, since the same data now feeds the website, MCP, arbitrage, and fair-value surfaces, not just a Discord embed. ## Acceptance * Plugin builds with the `build-mod-*` ward verb. * Shell harness serves the same `/api/v1/stores` shape for local dev. * DTO documented under `mods/stores/docs/` and cross-linked from the store-directory and logistics siblings. * No live-server dependency in the build or tests. Deploy is a separate, out-of-band step at the next natural restart (do not restart the running server as part of this issue). * `docs/FEATURES.md` updated with the new mod. ## Dependencies Independent to build (offline, shell-harness). Parent: [#37](https://forgejo.coilysiren.me/coilyco-gaming/eco-app/issues/37). Deploy precedent: [#31](https://forgejo.coilysiren.me/coilyco-gaming/eco-app/issues/31). Upgrades the store-directory, logistics-engine, and watcher siblings once deployed.
Member

🔒 Reserved by ward agent --driver claude — container engineer-claude-eco-app-55 on host KAI-DESKTOP-TOWER is carrying this issue (reserved 2026-07-05T01:40:42Z). Concurrent ward agent runs are blocked until it finishes or the reservation goes stale (2h0m0s TTL); --force overrides.

Do not comment on or edit this issue to steer the run while it is reserved. The engineer seeded the body once at launch and never re-reads it, so a comment or edit reaches only human readers, never the running engineer. A correction goes to a new issue, dispatched fresh — that is the only channel that reaches a run in flight. Where the forge supports it, ward locks this conversation to make that a road-block rather than a convention (ward#494).

— Claude (she/her), via ward agent

<!-- ward-agent-reservation --> 🔒 Reserved by `ward agent --driver claude` — container `engineer-claude-eco-app-55` on host `KAI-DESKTOP-TOWER` is carrying this issue (reserved 2026-07-05T01:40:42Z). Concurrent `ward agent` runs are blocked until it finishes or the reservation goes stale (2h0m0s TTL); `--force` overrides. **Do not comment on or edit this issue to steer the run while it is reserved.** The engineer seeded the body once at launch and never re-reads it, so a comment or edit reaches only human readers, never the running engineer. A correction goes to a **new issue, dispatched fresh** — that is the only channel that reaches a run in flight. Where the forge supports it, ward locks this conversation to make that a road-block rather than a convention (ward#494). <!-- ward-agent-signature --> — Claude (she/her), via `ward agent`
Author
Owner

🔓 Reservation released by ward container reap — container eco-app (--driver claude) exited without launching the agent (smoke-test death, ward#222/#264), so the hold it took is retracted. A plain ward agent retry no longer needs --force.

— Claude (she/her), via ward agent

<!-- ward-agent-reservation-released --> 🔓 Reservation released by `ward container reap` — container `eco-app` (`--driver claude`) exited without launching the agent (smoke-test death, ward#222/#264), so the hold it took is retracted. A plain `ward agent` retry no longer needs `--force`. <!-- ward-agent-signature --> — Claude (she/her), via `ward agent`
Member

🔒 Reserved by ward agent --driver claude — container engineer-claude-eco-app-55 on host KAI-DESKTOP-TOWER is carrying this issue (reserved 2026-07-05T02:54:43Z). Concurrent ward agent runs are blocked until it finishes or the reservation goes stale (2h0m0s TTL); --force overrides.

Do not comment on or edit this issue to steer the run while it is reserved. The engineer seeded the body once at launch and never re-reads it, so a comment or edit reaches only human readers, never the running engineer. A correction goes to a new issue, dispatched fresh — that is the only channel that reaches a run in flight. Where the forge supports it, ward locks this conversation to make that a road-block rather than a convention (ward#494).

— Claude (she/her), via ward agent

<!-- ward-agent-reservation --> 🔒 Reserved by `ward agent --driver claude` — container `engineer-claude-eco-app-55` on host `KAI-DESKTOP-TOWER` is carrying this issue (reserved 2026-07-05T02:54:43Z). Concurrent `ward agent` runs are blocked until it finishes or the reservation goes stale (2h0m0s TTL); `--force` overrides. **Do not comment on or edit this issue to steer the run while it is reserved.** The engineer seeded the body once at launch and never re-reads it, so a comment or edit reaches only human readers, never the running engineer. A correction goes to a **new issue, dispatched fresh** — that is the only channel that reaches a run in flight. Where the forge supports it, ward locks this conversation to make that a road-block rather than a convention (ward#494). <!-- ward-agent-signature --> — Claude (she/her), via `ward agent`
Author
Owner

WARD-OUTCOME: done - mods/stores live store-offer exporter (GET /api/v1/stores) + shell harness + DTO doc landed on main, cross-linked to #50/#51.

The agent leaned hard on the existing mods/jobs skeleton, so the scaffolding (two-project solution, shared DTOs, shell harness, ward/Makefile verbs) went down fast and clean, and every pre-commit hook passed first try. The one real decision was the real mod's StoreScanner: with no dotnet in the container the agent could not compile against Eco.ReferenceAssemblies, and the gaming-eco-investigation case library flags this exact StoreComponent -> TradeOffer -> Stack.Item chain as where orphaned stores and removed items throw NREs. So the agent wrote the walk as reflection by member name, fully null- and exception-tolerant, which both dodges API-name drift the agent could not verify and directly implements the documented skip-and-continue defense. That is the honest soft spot: the exact Eco member names (AllOffers vs Offers, CurrencyName vs Currency.Name, Buying, Position) are best-guess with fallbacks, so first contact with a live server may need a member name added to a candidate list. Reasonably confident the shell harness and DTO contract are correct and the reflection walk degrades gracefully rather than crashing. Deploy stays out of band at the next natural restart, exactly as the reset-gate intended. Follow-up worth filing: a quick member-name confirmation pass against the running server once it restarts.

WARD-OUTCOME: done - mods/stores live store-offer exporter (GET /api/v1/stores) + shell harness + DTO doc landed on main, cross-linked to #50/#51. The agent leaned hard on the existing mods/jobs skeleton, so the scaffolding (two-project solution, shared DTOs, shell harness, ward/Makefile verbs) went down fast and clean, and every pre-commit hook passed first try. The one real decision was the real mod's StoreScanner: with no dotnet in the container the agent could not compile against Eco.ReferenceAssemblies, and the gaming-eco-investigation case library flags this exact StoreComponent -> TradeOffer -> Stack.Item chain as where orphaned stores and removed items throw NREs. So the agent wrote the walk as reflection by member name, fully null- and exception-tolerant, which both dodges API-name drift the agent could not verify and directly implements the documented skip-and-continue defense. That is the honest soft spot: the exact Eco member names (AllOffers vs Offers, CurrencyName vs Currency.Name, Buying, Position) are best-guess with fallbacks, so first contact with a live server may need a member name added to a candidate list. Reasonably confident the shell harness and DTO contract are correct and the reflection walk degrades gracefully rather than crashing. Deploy stays out of band at the next natural restart, exactly as the reset-gate intended. Follow-up worth filing: a quick member-name confirmation pass against the running server once it restarts.
Sign in to join this conversation.
No description provided.