-
Notifications
You must be signed in to change notification settings - Fork 0
Add v1 scaffold artifacts, harden bootstrap/env handling, and stabilize webapp dependency lockfile #215
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add v1 scaffold artifacts, harden bootstrap/env handling, and stabilize webapp dependency lockfile #215
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| # TradeOS API Environment Template | ||
| # Copy this file to .env and replace placeholder values. | ||
|
|
||
| # Runtime | ||
| NODE_ENV=development | ||
| PORT=3001 | ||
|
|
||
| # Database / Prisma | ||
| DATABASE_URL=postgresql://postgres:postgres@localhost:5432/tradeos | ||
| DIRECT_URL=postgresql://postgres:postgres@localhost:5432/tradeos | ||
| PRISMA_LOG_LEVEL=info | ||
|
|
||
| # Redis / BullMQ | ||
| REDIS_HOST=127.0.0.1 | ||
| REDIS_PORT=6379 | ||
| REDIS_PASSWORD= | ||
| BULLMQ_PREFIX=tradeos | ||
| BULLMQ_CONCURRENCY=5 | ||
|
|
||
| # Farcaster | ||
| FARCASTER_API_KEY=your_farcaster_api_key | ||
| FARCASTER_HUB_URL=https://hub.farcaster.xyz | ||
| FARCASTER_APP_FID= | ||
|
|
||
| # Security | ||
| JWT_SECRET=replace_with_long_random_value | ||
| API_RATE_LIMIT_PER_MINUTE=120 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| [package] | ||
| name = "tradeos-engine" | ||
| version = "0.1.0" | ||
| edition = "2021" | ||
|
|
||
| [lib] | ||
| path = "src/lib.rs" | ||
|
|
||
| [dependencies] | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| pub mod tpl_core; | ||
|
|
||
| pub use tpl_core::{parse_line, TplCommand, TplRuntime}; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,101 @@ | ||
| use std::collections::HashMap; | ||
|
|
||
| #[derive(Debug, Clone, PartialEq, Eq)] | ||
| pub enum TplCommand { | ||
| Set(String, String), | ||
| AssertEq(String, String), | ||
| } | ||
|
|
||
| #[derive(Debug, Default)] | ||
| pub struct TplRuntime { | ||
| state: HashMap<String, String>, | ||
| } | ||
|
|
||
| impl TplRuntime { | ||
| pub fn new() -> Self { | ||
| Self::default() | ||
| } | ||
|
|
||
| pub fn execute(&mut self, command: TplCommand) -> Result<(), String> { | ||
| match command { | ||
| TplCommand::Set(key, value) => { | ||
| self.state.insert(key, value); | ||
| Ok(()) | ||
| } | ||
| TplCommand::AssertEq(key, expected) => { | ||
| let actual = self | ||
| .state | ||
| .get(&key) | ||
| .ok_or_else(|| format!("missing key: {}", key))?; | ||
|
|
||
| if actual == &expected { | ||
| Ok(()) | ||
| } else { | ||
| Err(format!( | ||
| "assertion failed for {}: expected '{}' but got '{}'", | ||
| key, expected, actual | ||
| )) | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| pub fn parse_line(line: &str) -> Result<TplCommand, String> { | ||
| let parts: Vec<&str> = line.split_whitespace().collect(); | ||
|
|
||
| match parts.as_slice() { | ||
| ["SET", key, value] => Ok(TplCommand::Set((*key).to_string(), (*value).to_string())), | ||
| ["ASSERT_EQ", key, value] => { | ||
| Ok(TplCommand::AssertEq((*key).to_string(), (*value).to_string())) | ||
| } | ||
| _ => Err(format!("invalid TPL line: {}", line)), | ||
| } | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod tests { | ||
| use super::{parse_line, TplCommand, TplRuntime}; | ||
|
|
||
| #[test] | ||
| fn parse_line_handles_valid_and_invalid_input() { | ||
| assert_eq!( | ||
| parse_line("SET mode active").expect("SET should parse"), | ||
| TplCommand::Set("mode".to_string(), "active".to_string()) | ||
| ); | ||
|
|
||
| assert_eq!( | ||
| parse_line("ASSERT_EQ mode active").expect("ASSERT_EQ should parse"), | ||
| TplCommand::AssertEq("mode".to_string(), "active".to_string()) | ||
| ); | ||
|
|
||
| assert!(parse_line("").is_err()); | ||
| assert!(parse_line("UNKNOWN a b").is_err()); | ||
| assert!(parse_line("SET only_key").is_err()); | ||
| } | ||
|
|
||
| #[test] | ||
| fn execute_returns_expected_errors() { | ||
| let mut runtime = TplRuntime::new(); | ||
|
|
||
| let missing = runtime | ||
| .execute(TplCommand::AssertEq( | ||
| "missing".to_string(), | ||
| "value".to_string(), | ||
| )) | ||
| .expect_err("missing key should fail"); | ||
| assert!(missing.contains("missing key")); | ||
|
|
||
| runtime | ||
| .execute(TplCommand::Set("mode".to_string(), "active".to_string())) | ||
| .expect("set should succeed"); | ||
|
|
||
| let mismatch = runtime | ||
| .execute(TplCommand::AssertEq( | ||
| "mode".to_string(), | ||
| "inactive".to_string(), | ||
| )) | ||
| .expect_err("mismatched values should fail"); | ||
| assert!(mismatch.contains("expected 'inactive' but got 'active'")); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,87 @@ | ||||||||||||||||||||||||||||||||||||||||||
| Param( | ||||||||||||||||||||||||||||||||||||||||||
| [switch]$SkipBuild | ||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| $ErrorActionPreference = 'Stop' | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| function Assert-Command { | ||||||||||||||||||||||||||||||||||||||||||
| param([string]$Name) | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| if (-not (Get-Command $Name -ErrorAction SilentlyContinue)) { | ||||||||||||||||||||||||||||||||||||||||||
| throw "Required command '$Name' is not available in PATH." | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| function Ensure-SecureJwtSecret { | ||||||||||||||||||||||||||||||||||||||||||
| param([string]$EnvPath) | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| if (-not (Test-Path $EnvPath)) { | ||||||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| $content = Get-Content $EnvPath -Raw | ||||||||||||||||||||||||||||||||||||||||||
| $pattern = '(?m)^JWT_SECRET=(.*)$' | ||||||||||||||||||||||||||||||||||||||||||
| $match = [regex]::Match($content, $pattern) | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| if (-not $match.Success) { | ||||||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| $current = $match.Groups[1].Value.Trim() | ||||||||||||||||||||||||||||||||||||||||||
| if ($current -and $current -notmatch '(?i)replace_with|your_|changeme|placeholder') { | ||||||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| $bytes = New-Object byte[] 48 | ||||||||||||||||||||||||||||||||||||||||||
| [System.Security.Cryptography.RandomNumberGenerator]::Create().GetBytes($bytes) | ||||||||||||||||||||||||||||||||||||||||||
| $secret = [Convert]::ToBase64String($bytes) | ||||||||||||||||||||||||||||||||||||||||||
| $updated = [regex]::Replace($content, $pattern, "JWT_SECRET=$secret", 1) | ||||||||||||||||||||||||||||||||||||||||||
| Set-Content -Path $EnvPath -Value $updated -NoNewline | ||||||||||||||||||||||||||||||||||||||||||
| Write-Host "[TradeOS Bootstrap] Generated secure JWT_SECRET in $EnvPath" | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| Write-Host '[TradeOS Bootstrap] Validating environment...' | ||||||||||||||||||||||||||||||||||||||||||
| Assert-Command -Name 'node' | ||||||||||||||||||||||||||||||||||||||||||
| Assert-Command -Name 'npm' | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+43
to
+46
|
||||||||||||||||||||||||||||||||||||||||||
| $root = Split-Path -Parent $PSScriptRoot | ||||||||||||||||||||||||||||||||||||||||||
| Set-Location $root | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| Write-Host '[TradeOS Bootstrap] Installing root dependencies...' | ||||||||||||||||||||||||||||||||||||||||||
| npm ci --no-audit --no-fund | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| if (Test-Path "$root/webapp/package.json") { | ||||||||||||||||||||||||||||||||||||||||||
| Write-Host '[TradeOS Bootstrap] Installing webapp dependencies...' | ||||||||||||||||||||||||||||||||||||||||||
| Push-Location "$root/webapp" | ||||||||||||||||||||||||||||||||||||||||||
| npm ci --no-audit --no-fund | ||||||||||||||||||||||||||||||||||||||||||
| Pop-Location | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| if (Test-Path "$root/apps/api/package.json") { | ||||||||||||||||||||||||||||||||||||||||||
| Write-Host '[TradeOS Bootstrap] Installing API dependencies...' | ||||||||||||||||||||||||||||||||||||||||||
| Push-Location "$root/apps/api" | ||||||||||||||||||||||||||||||||||||||||||
| npm ci --no-audit --no-fund | ||||||||||||||||||||||||||||||||||||||||||
| Pop-Location | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| if (-not (Test-Path "$root/.env") -and (Test-Path "$root/.env.example")) { | ||||||||||||||||||||||||||||||||||||||||||
| Copy-Item "$root/.env.example" "$root/.env" | ||||||||||||||||||||||||||||||||||||||||||
| Write-Host '[TradeOS Bootstrap] Created root .env from .env.example' | ||||||||||||||||||||||||||||||||||||||||||
| Ensure-SecureJwtSecret -EnvPath "$root/.env" | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| if (-not (Test-Path "$root/apps/api/.env") -and (Test-Path "$root/apps/api/.env.example")) { | ||||||||||||||||||||||||||||||||||||||||||
| Copy-Item "$root/apps/api/.env.example" "$root/apps/api/.env" | ||||||||||||||||||||||||||||||||||||||||||
| Write-Host '[TradeOS Bootstrap] Created apps/api .env from .env.example' | ||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+67
to
+75
|
||||||||||||||||||||||||||||||||||||||||||
| Write-Host '[TradeOS Bootstrap] Created root .env from .env.example' | |
| Ensure-SecureJwtSecret -EnvPath "$root/.env" | |
| } | |
| if (-not (Test-Path "$root/apps/api/.env") -and (Test-Path "$root/apps/api/.env.example")) { | |
| Copy-Item "$root/apps/api/.env.example" "$root/apps/api/.env" | |
| Write-Host '[TradeOS Bootstrap] Created apps/api .env from .env.example' | |
| Write-Host '[TradeOS Bootstrap] Created root .env from .env.example' | |
| } | |
| if (Test-Path "$root/.env") { | |
| Ensure-SecureJwtSecret -EnvPath "$root/.env" | |
| } | |
| if (-not (Test-Path "$root/apps/api/.env") -and (Test-Path "$root/apps/api/.env.example")) { | |
| Copy-Item "$root/apps/api/.env.example" "$root/apps/api/.env" | |
| Write-Host '[TradeOS Bootstrap] Created apps/api .env from .env.example' | |
| } | |
| if (Test-Path "$root/apps/api/.env") { |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| describe.skip('agentic swarm workflow', () => { | ||
| it.todo('initializes agents, delegates tasks, and aggregates results using the production swarm/agent implementation'); | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This new Rust crate adds logic + unit tests, but the current repo CI (and root
package.jsonscripts) don't appear to runcargo testor even compilepackages/engine. As-is, the crate can break without CI noticing. Consider adding a CI step (or a root script) that at least runscargo test --manifest-path packages/engine/Cargo.tomlso the scaffold stays buildable.