Merge commit '5beb66b9ab5b955ec0c4273e07d3a59365cc47ba'

This commit is contained in:
Alexa Amundson
2025-11-25 13:40:46 -06:00
18 changed files with 3027 additions and 1 deletions

4
.envrc Normal file
View File

@@ -0,0 +1,4 @@
# Generated by br-orchestrate render
export ORCHESTRA_VERSION=0.1.0
export ORCHESTRA_PACKS=education,infra-devops,creator-studio,finance,legal,research-lab
export ORCHESTRA_ENVIRONMENTS=dev,prod

18
.eslintrc.json Normal file
View File

@@ -0,0 +1,18 @@
{
"env": {
"es2021": true,
"node": true
},
"extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 2021,
"sourceType": "module"
},
"plugins": ["@typescript-eslint"],
"ignorePatterns": ["public/**/*.json"],
"rules": {
"@typescript-eslint/explicit-module-boundary-types": "off",
"@typescript-eslint/no-explicit-any": "off"
}
}

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

@@ -0,0 +1,28 @@
name: ci
on:
push:
branches: ["*"]
pull_request:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v3
with:
version: 8
- uses: actions/setup-node@v4
with:
node-version: 20
- name: Install dependencies
run: pnpm install
- name: Lint
run: pnpm lint
- name: Test
run: pnpm test
- name: Type Check
run: pnpm build
- name: Render artifacts
run: pnpm br-orchestrate render

39
.matrix.json Normal file
View File

@@ -0,0 +1,39 @@
{
"version": "0.1.0",
"repos": {
"core": "BlackRoad-OS/blackroad-os-core",
"api": "BlackRoad-OS/blackroad-os-api",
"gateway": "BlackRoad-OS/blackroad-os-api-gateway",
"operator": "BlackRoad-OS/blackroad-os-operator",
"prism": "BlackRoad-OS/blackroad-os-prism-console",
"web": "BlackRoad-OS/blackroad-os-web"
},
"packs": [
"education",
"infra-devops",
"creator-studio",
"finance",
"legal",
"research-lab"
],
"environments": {
"dev": {
"domain_root": "dev.blackroad.io"
},
"prod": {
"domain_root": "blackroad.io"
}
},
"services": [
{
"name": "core-web",
"repo": "core",
"env": "prod",
"url": "https://web.blackroad.io",
"depends": [
"gateway",
"operator"
]
}
]
}

6
.prettierrc Normal file
View File

@@ -0,0 +1,6 @@
{
"singleQuote": true,
"semi": false,
"trailingComma": "es5",
"printWidth": 100
}

View File

@@ -82,3 +82,21 @@ The `/health` endpoint returns:
| `ENVIRONMENT` | Runtime environment | `production` | | `ENVIRONMENT` | Runtime environment | `production` |
| `APP_VERSION` | Application version | `1.0.0` | | `APP_VERSION` | Application version | `1.0.0` |
| `APP_COMMIT` | Git commit hash | Auto-detected | | `APP_COMMIT` | Git commit hash | Auto-detected |
# BlackRoad OS · Orchestrator
Welcome to the meta-orchestration layer for the BlackRoad ecosystem. This repository
describes the constellation of services, packs, and environments that make up the platform.
Run `pnpm br-orchestrate render` to regenerate this README based on `orchestra.yml`.
## Service Matrix
| Service | Env | Repo | URL | Health | Depends |
| --- | --- | --- | --- | --- | --- |
| core-web | prod | core | https://web.blackroad.io | /api/health | gateway, operator |
## Topology
```mermaid
graph LR
core-web --> gateway
core-web --> operator
```

6
README.stub.md Normal file
View File

@@ -0,0 +1,6 @@
# BlackRoad OS · Orchestrator
Welcome to the meta-orchestration layer for the BlackRoad ecosystem. This repository
describes the constellation of services, packs, and environments that make up the platform.
Run `pnpm br-orchestrate render` to regenerate this README based on `orchestra.yml`.

29
orchestra.yml Normal file
View File

@@ -0,0 +1,29 @@
version: 0.1.0
repos:
core: BlackRoad-OS/blackroad-os-core
api: BlackRoad-OS/blackroad-os-api
gateway: BlackRoad-OS/blackroad-os-api-gateway
operator: BlackRoad-OS/blackroad-os-operator
prism: BlackRoad-OS/blackroad-os-prism-console
web: BlackRoad-OS/blackroad-os-web
services:
core-web:
repo: core
env: prod
url: https://web.blackroad.io
health: /api/health
depends:
- gateway
- operator
packs:
- education
- infra-devops
- creator-studio
- finance
- legal
- research-lab
environments:
dev:
domain_root: dev.blackroad.io
prod:
domain_root: blackroad.io

3
orchestrator.env.example Normal file
View File

@@ -0,0 +1,3 @@
# Sample environment overrides for br-orchestrate
ORCHESTRA_VERSION=0.1.0
DEFAULT_ENV=dev

View File

@@ -38,5 +38,39 @@
"tsx": "^4.20.6", "tsx": "^4.20.6",
"typescript": "^5.9.3", "typescript": "^5.9.3",
"vitest": "^4.0.13" "vitest": "^4.0.13"
"version": "0.1.0",
"license": "MIT",
"private": true,
"type": "module",
"bin": {
"br-orchestrate": "tsx src/cli.ts"
},
"scripts": {
"lint": "pnpm run lint:ts && pnpm run lint:yaml && pnpm run format:check",
"lint:ts": "eslint . --ext .ts,.tsx,.js,.cjs,.mjs",
"lint:yaml": "yaml-lint orchestra.yml",
"format": "prettier --write .",
"format:check": "prettier --check .",
"test": "vitest run",
"typecheck": "tsc --noEmit",
"build": "tsc",
"postbuild": "tsx scripts/postbuild.ts",
"br-orchestrate": "tsx src/cli.ts"
},
"engines": {
"node": ">=18"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^7.0.0",
"@typescript-eslint/parser": "^7.0.0",
"eslint": "^8.56.0",
"js-yaml": "^4.1.0",
"prettier": "^3.1.0",
"ts-node": "^10.9.2",
"tsx": "^4.19.2",
"typescript": "^5.3.3",
"vitest": "^1.0.4",
"yaml-lint": "^1.2.4",
"zod": "^3.22.4"
} }
} }

2616
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

4
public/sig.beacon.json Normal file
View File

@@ -0,0 +1,4 @@
{
"agent": "Orchestrator-Gen-0",
"ts": 1718217600000
}

11
scripts/postbuild.ts Normal file
View File

@@ -0,0 +1,11 @@
import fs from 'fs'
import path from 'path'
const beacon = {
agent: 'Orchestrator-Gen-0',
ts: Date.now(),
}
const target = path.join(process.cwd(), 'public', 'sig.beacon.json')
fs.mkdirSync(path.dirname(target), { recursive: true })
fs.writeFileSync(target, JSON.stringify(beacon, null, 2))

54
src/cli.ts Normal file
View File

@@ -0,0 +1,54 @@
#!/usr/bin/env node
import fs from 'fs'
import path from 'path'
import { loadManifest, withContext } from './schema.js'
import { render } from './render.js'
function usage() {
return `br-orchestrate <command>\n\nCommands:\n lint Validate orchestra.yml with the schema\n render Emit .matrix.json, .envrc, README.md\n`
}
function lint(manifestPath?: string) {
const manifest = withContext(loadManifest(manifestPath))
const summary = [
`version: ${manifest.version}`,
`repos: ${Object.keys(manifest.repos).length}`,
`services: ${Object.keys(manifest.services).length}`,
`packs: ${manifest.packs.length}`,
`envs: ${Object.keys(manifest.environments).length}`,
]
process.stdout.write(`orchestra.yml is valid\n${summary.join('\n')}\n`)
}
function runRender(manifestPath?: string) {
const manifest = withContext(loadManifest(manifestPath))
render(manifest)
process.stdout.write('Rendered .matrix.json, .envrc, and README.md\n')
// TODO(orchestrator-next): add drift detection and multi-tenant shards
}
function main() {
const [, , command, manifestPath] = process.argv
if (!command || command === '--help' || command === '-h') {
process.stdout.write(usage())
return
}
if (command === 'lint' || command === 'render') {
if (!fs.existsSync(path.join(process.cwd(), 'orchestra.yml'))) {
throw new Error('orchestra.yml not found in current directory')
}
}
if (command === 'lint') {
lint(manifestPath)
return
}
if (command === 'render') {
runRender(manifestPath)
return
}
process.stderr.write(`Unknown command: ${command}\n`)
process.stderr.write(usage())
process.exitCode = 1
}
main()

64
src/render.ts Normal file
View File

@@ -0,0 +1,64 @@
import fs from 'fs'
import path from 'path'
import { Manifest } from './schema.js'
function matrix(manifest: Manifest) {
const services = Object.entries(manifest.services).map(([name, svc]) => ({
name,
repo: svc.repo,
env: svc.env,
url: svc.url,
depends: svc.depends,
}))
return {
version: manifest.version,
repos: manifest.repos,
packs: manifest.packs,
environments: manifest.environments,
services,
}
}
function envrc(manifest: Manifest) {
const lines = [
'# Generated by br-orchestrate render',
`export ORCHESTRA_VERSION=${manifest.version}`,
`export ORCHESTRA_PACKS=${manifest.packs.join(',')}`,
`export ORCHESTRA_ENVIRONMENTS=${Object.keys(manifest.environments).join(',')}`,
]
return lines.join('\n') + '\n'
}
function readme(manifest: Manifest) {
const rows = Object.entries(manifest.services)
.map(([name, svc]) => {
const depends = svc.depends?.length ? svc.depends.join(', ') : '—'
const health = svc.health ?? '—'
return `| ${name} | ${svc.env} | ${svc.repo} | ${svc.url} | ${health} | ${depends} |`
})
.join('\n')
const edges = Object.entries(manifest.services).flatMap(([name, svc]) =>
(svc.depends || []).map((dep) => `${name} --> ${dep}`)
)
const mermaid = ['```mermaid', 'graph LR', ...(edges.length ? edges : ['%% no deps']), '```'].join(
'\n'
)
let header: string;
try {
header = fs.readFileSync(path.join(process.cwd(), 'README.stub.md'), 'utf-8');
} catch (err) {
header = '# Service Overview\n\n> README.stub.md not found or unreadable. Please provide a stub file for a custom header.\n';
console.warn('Warning: README.stub.md not found or unreadable:', err);
}
const table = `| Service | Env | Repo | URL | Health | Depends |\n| --- | --- | --- | --- | --- | --- |\n${rows}`
return [header.trim(), '', '## Service Matrix', table, '', '## Topology', mermaid, ''].join('\n')
}
export function render(manifest: Manifest, cwd = process.cwd()) {
const matrixPath = path.join(cwd, '.matrix.json')
const envrcPath = path.join(cwd, '.envrc')
const readmePath = path.join(cwd, 'README.md')
fs.writeFileSync(matrixPath, JSON.stringify(matrix(manifest), null, 2))
fs.writeFileSync(envrcPath, envrc(manifest))
fs.writeFileSync(readmePath, readme(manifest))
}

50
src/schema.ts Normal file
View File

@@ -0,0 +1,50 @@
import { z } from 'zod'
import fs from 'fs'
import path from 'path'
import yaml from 'js-yaml'
const repoId = z.string().min(1, 'repo id required')
export const EnvironmentSchema = z.object({
domain_root: z.string().min(1, 'domain_root required'),
description: z.string().optional(),
})
export const ServiceSchema = z.object({
repo: repoId,
env: z.string().min(1, 'env required'),
url: z.string().url('url must be valid'),
health: z.string().optional(),
depends: z.array(z.string()).default([]),
notes: z.string().optional(),
})
export const ManifestSchema = z.object({
version: z.string().min(1),
repos: z.record(repoId),
services: z.record(ServiceSchema),
packs: z.array(z.string()),
environments: z.record(EnvironmentSchema),
})
export type Manifest = z.infer<typeof ManifestSchema>
export const MANIFEST_PATH = path.join(process.cwd(), 'orchestra.yml')
export function loadManifest(customPath?: string): Manifest {
const manifestPath = customPath ? path.resolve(customPath) : MANIFEST_PATH
const raw = fs.readFileSync(manifestPath, 'utf-8')
const data = yaml.load(raw)
const parsed = ManifestSchema.safeParse(data)
if (!parsed.success) {
const formatted = parsed.error.errors.map((err) => `${err.path.length ? err.path.join('.') : 'root'}: ${err.message}`)
throw new Error(`Invalid orchestra.yml\n${formatted.join('\n')}`)
}
return parsed.data
}
export function withContext(manifest: Manifest): Manifest {
const resolved = { ...manifest }
// TODO(orchestrator-next): enrich with GitOps sync targets and drift detection
return resolved
}

30
tests/schema.test.ts Normal file
View File

@@ -0,0 +1,30 @@
import { describe, expect, it } from 'vitest'
import { ManifestSchema } from '../src/schema.js'
const sample = {
version: '0.1.0',
repos: { core: 'BlackRoad-OS/blackroad-os-core' },
services: {
'core-web': {
repo: 'core',
env: 'prod',
url: 'https://web.blackroad.io',
health: '/api/health',
depends: ['gateway'],
},
},
packs: ['education'],
environments: { prod: { domain_root: 'blackroad.io' } },
}
describe('ManifestSchema', () => {
it('accepts valid manifest', () => {
const parsed = ManifestSchema.safeParse(sample)
expect(parsed.success).toBe(true)
})
it('rejects invalid manifest', () => {
const parsed = ManifestSchema.safeParse({ ...sample, version: '' })
expect(parsed.success).toBe(false)
})
})

View File

@@ -16,4 +16,16 @@
"exclude": ["node_modules", "dist"] "exclude": ["node_modules", "dist"]
"include": ["src", "lib", "components", "tests", "app", "chronicles", "vitest.config.ts"], "include": ["src", "lib", "components", "tests", "app", "chronicles", "vitest.config.ts"],
"exclude": ["node_modules"] "exclude": ["node_modules"]
"target": "ES2021",
"module": "ESNext",
"moduleResolution": "Node",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true,
"resolveJsonModule": true,
"outDir": "dist"
},
"include": ["src", "tests", "scripts"],
"exclude": ["node_modules", "dist"]
} }