Scaffold core runtime with Railway deployment

Node.js HTTP server with /health, /v1/agents, /v1/agent endpoints.
Includes railway.toml, Dockerfile, CI/CD workflows, and tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Alexa Amundson
2026-02-20 16:04:13 -06:00
parent bb399808d3
commit 9bf37d4f03
8 changed files with 278 additions and 1 deletions

19
.github/workflows/ci.yml vendored Normal file
View File

@@ -0,0 +1,19 @@
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
name: Test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm ci
- run: npm test

37
.github/workflows/deploy-railway.yml vendored Normal file
View File

@@ -0,0 +1,37 @@
name: Deploy to Railway
on:
push:
branches: [main]
workflow_dispatch:
jobs:
deploy:
name: Deploy to Railway
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install Railway CLI
run: npm install -g @railway/cli
- name: Deploy to Railway
run: railway up --detach
env:
RAILWAY_TOKEN: ${{ secrets.RAILWAY_TOKEN }}
RAILWAY_PROJECT_ID: ${{ secrets.RAILWAY_PROJECT_ID }}
- name: Wait for deployment
run: sleep 30
- name: Health Check
if: success()
run: |
HEALTH_URL="${{ secrets.RAILWAY_SERVICE_URL }}/health"
if [ -n "${{ secrets.RAILWAY_SERVICE_URL }}" ]; then
echo "Checking health at: $HEALTH_URL"
curl -f --retry 3 --retry-delay 10 "$HEALTH_URL" || echo "Health check pending"
else
echo "RAILWAY_SERVICE_URL not set yet, skipping health check"
fi

7
Dockerfile Normal file
View File

@@ -0,0 +1,7 @@
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
EXPOSE 8080
CMD ["npm", "start"]

View File

@@ -1,2 +1,35 @@
# blackroad-core # blackroad-core
Core orchestration layer and runtime engine for BlackRoad OS. Core orchestration layer and runtime engine for BlackRoad OS.
## Quick Start
```bash
npm install
npm run dev # Development (auto-reload)
npm start # Production
npm test # Run tests
```
## Endpoints
| Method | Path | Description |
|--------|------|-------------|
| GET | `/health` | Health check |
| GET | `/metrics` | Runtime metrics |
| GET | `/v1/agents` | Agent roster |
| POST | `/v1/agent` | Agent invocation |
## Deployment
Deploys to Railway on push to `main`. See `railway.toml` for config.
## Environment Variables
| Variable | Default | Description |
|----------|---------|-------------|
| `PORT` | `8080` | Server port |
## License
Proprietary - BlackRoad OS, Inc. All rights reserved.

14
package.json Normal file
View File

@@ -0,0 +1,14 @@
{
"name": "@blackroad-os-inc/core",
"version": "0.1.0",
"private": true,
"description": "Core orchestration layer and runtime engine for BlackRoad OS",
"scripts": {
"start": "node server.js",
"dev": "node --watch server.js",
"test": "node tests/server.test.js"
},
"engines": {
"node": ">=20"
}
}

9
railway.toml Normal file
View File

@@ -0,0 +1,9 @@
[build]
builder = "NIXPACKS"
[deploy]
startCommand = "npm start"
healthcheckPath = "/health"
healthcheckTimeout = 300
restartPolicyType = "ON_FAILURE"
restartPolicyMaxRetries = 10

77
server.js Normal file
View File

@@ -0,0 +1,77 @@
'use strict'
const http = require('http')
const { randomUUID } = require('crypto')
const PORT = Number(process.env.PORT) || 8080
const VERSION = '0.1.0'
const metrics = {
totalRequests: 0,
startTime: Date.now(),
snapshot() {
return {
uptime_seconds: Math.floor((Date.now() - this.startTime) / 1000),
total_requests: this.totalRequests
}
}
}
const server = http.createServer((req, res) => {
metrics.totalRequests++
const requestId = randomUUID()
const send = (code, body) => {
res.writeHead(code, { 'Content-Type': 'application/json' })
res.end(JSON.stringify(body))
}
if (req.method === 'GET' && (req.url === '/health' || req.url === '/healthz')) {
return send(200, {
status: 'ok',
service: 'blackroad-core',
version: VERSION,
timestamp: new Date().toISOString()
})
}
if (req.method === 'GET' && req.url === '/metrics') {
return send(200, { status: 'ok', metrics: metrics.snapshot() })
}
if (req.method === 'GET' && req.url === '/v1/agents') {
return send(200, { status: 'ok', agents: [], request_id: requestId })
}
if (req.method === 'POST' && req.url === '/v1/agent') {
let body = ''
req.on('data', (chunk) => { body += chunk })
req.on('end', () => {
try {
const payload = JSON.parse(body)
if (!payload.agent || !payload.intent || typeof payload.input !== 'string') {
return send(400, { status: 'error', error: 'Missing agent, intent, or input', request_id: requestId })
}
return send(200, {
status: 'ok',
output: '',
request_id: requestId,
message: 'Agent invocation not yet connected to providers'
})
} catch {
return send(400, { status: 'error', error: 'Invalid JSON', request_id: requestId })
}
})
return
}
send(404, { status: 'error', error: 'Not found', request_id: requestId })
})
server.listen(PORT, '0.0.0.0', () => {
console.log(`BlackRoad Core v${VERSION} listening on 0.0.0.0:${PORT}`)
console.log(' POST /v1/agent - Agent invocation')
console.log(' GET /v1/agents - Agent roster')
console.log(' GET /health - Health check')
console.log(' GET /metrics - Metrics')
})

81
tests/server.test.js Normal file
View File

@@ -0,0 +1,81 @@
'use strict'
const http = require('http')
const { spawn } = require('child_process')
const path = require('path')
const PORT = 9876
let serverProcess
function request(method, urlPath) {
return new Promise((resolve, reject) => {
const req = http.request({ hostname: '127.0.0.1', port: PORT, path: urlPath, method }, (res) => {
let body = ''
res.on('data', (chunk) => { body += chunk })
res.on('end', () => {
try {
resolve({ status: res.statusCode, body: JSON.parse(body) })
} catch {
resolve({ status: res.statusCode, body })
}
})
})
req.on('error', reject)
req.end()
})
}
async function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms))
}
async function run() {
let passed = 0
let failed = 0
function assert(condition, message) {
if (condition) {
console.log(` PASS: ${message}`)
passed++
} else {
console.error(` FAIL: ${message}`)
failed++
}
}
console.log('Starting server...')
serverProcess = spawn('node', [path.join(__dirname, '..', 'server.js')], {
env: { ...process.env, PORT: String(PORT) },
stdio: 'pipe'
})
await sleep(1000)
console.log('\nRunning tests:\n')
const health = await request('GET', '/health')
assert(health.status === 200, '/health returns 200')
assert(health.body.status === 'ok', '/health status is ok')
assert(health.body.service === 'blackroad-core', '/health service name correct')
const agents = await request('GET', '/v1/agents')
assert(agents.status === 200, '/v1/agents returns 200')
assert(Array.isArray(agents.body.agents), '/v1/agents returns array')
const metrics = await request('GET', '/metrics')
assert(metrics.status === 200, '/metrics returns 200')
const notFound = await request('GET', '/nonexistent')
assert(notFound.status === 404, 'Unknown route returns 404')
console.log(`\nResults: ${passed} passed, ${failed} failed`)
serverProcess.kill()
process.exit(failed > 0 ? 1 : 0)
}
run().catch((err) => {
console.error(err)
if (serverProcess) serverProcess.kill()
process.exit(1)
})