feat: V2 microservices stack — backend services, gateway, JWT auth
Add full V2 architecture: identity, content, studio (.NET 10) and file, render, notification, gateway (Go) services with vendored deps, plus DB migrations, event/API contracts, and an init-db script. Wire the Next.js frontend to the gateway: server-side JWT auth routes (login/register/refresh/logout/me), gateway fetch helper, and session/ cookie/jwt helpers under src/lib. Containerize the stack via docker-compose.v2.yml and per-service Dockerfiles. Base images resolve through a Nexus mirror (Docker Hub) and MCR directly; npm/NuGet pull from Nexus groups. Self-host fonts via next/font/local to avoid Google Fonts (geo-blocked). Add CI workflow and ignore .env.v2, *.stackdump, and .NET bin/obj. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,122 @@
|
||||
# =====================================================================
|
||||
# Node Events — published by Node Agent → Render Orchestrator
|
||||
# Routing: flatrender.events (topic) — key: node.*.v1
|
||||
# =====================================================================
|
||||
events:
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
# node.online.v1 — node agent starts up
|
||||
# -------------------------------------------------------------------
|
||||
node.online.v1:
|
||||
routing_key: node.online.v1
|
||||
payload:
|
||||
type: object
|
||||
required: [node_id, region, node_agent_version, current_ae_version]
|
||||
properties:
|
||||
node_id: { type: string, format: uuid }
|
||||
node_ip: { type: string, format: ipv4 }
|
||||
region: { type: string }
|
||||
node_agent_version: { type: string }
|
||||
current_ae_version: { type: string }
|
||||
available_ae_versions:
|
||||
type: array
|
||||
items: { type: string }
|
||||
ram_gb: { type: integer }
|
||||
cpu_cores: { type: integer }
|
||||
cache_used_gb: { type: integer }
|
||||
cached_template_md5s:
|
||||
type: array
|
||||
items: { type: string }
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
# node.offline.v1 — graceful shutdown OR detected missed heartbeats
|
||||
# -------------------------------------------------------------------
|
||||
node.offline.v1:
|
||||
routing_key: node.offline.v1
|
||||
payload:
|
||||
type: object
|
||||
required: [node_id, reason]
|
||||
properties:
|
||||
node_id: { type: string, format: uuid }
|
||||
reason: { type: string, enum: [Shutdown, HeartbeatLost, Maintenance, Disabled] }
|
||||
last_heartbeat_at: { type: string, format: date-time }
|
||||
current_job_id: { type: string, format: uuid, nullable: true }
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
# node.heartbeat.v1 — every 5s (NOT broadcast on topic exchange,
|
||||
# sent direct to orchestrator HTTP endpoint OR a dedicated stream)
|
||||
# Documented here for completeness.
|
||||
# -------------------------------------------------------------------
|
||||
node.heartbeat.v1:
|
||||
routing_key: node.heartbeat.v1
|
||||
transport: HTTP POST /v1/internal/nodes/{node_id}/heartbeat
|
||||
payload:
|
||||
type: object
|
||||
required: [node_id, status, recorded_at]
|
||||
properties:
|
||||
node_id: { type: string, format: uuid }
|
||||
status: { type: string, enum: [Ready, Busy, Crashed, Updating] }
|
||||
recorded_at: { type: string, format: date-time }
|
||||
cpu_pct: { type: integer, minimum: 0, maximum: 100 }
|
||||
ram_available_mb: { type: integer }
|
||||
ae_running: { type: boolean }
|
||||
current_job_id: { type: string, format: uuid, nullable: true }
|
||||
current_frame_job_id: { type: string, format: uuid, nullable: true }
|
||||
current_frame: { type: integer, nullable: true }
|
||||
cache_used_gb: { type: integer }
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
# node.crashed.v1 — AfterFX crashed mid-render
|
||||
# -------------------------------------------------------------------
|
||||
node.crashed.v1:
|
||||
routing_key: node.crashed.v1
|
||||
description: AE process exited unexpectedly while rendering.
|
||||
payload:
|
||||
type: object
|
||||
required: [node_id, crashed_at]
|
||||
properties:
|
||||
node_id: { type: string, format: uuid }
|
||||
render_job_id: { type: string, format: uuid, nullable: true }
|
||||
frame_job_id: { type: string, format: uuid, nullable: true }
|
||||
crashed_at: { type: string, format: date-time }
|
||||
last_known_frame: { type: integer, nullable: true }
|
||||
crash_signal: { type: string, nullable: true }
|
||||
ae_version: { type: string }
|
||||
error_log_tail: { type: string, description: "Last ~50 lines of AE log" }
|
||||
log_file_url: { type: string, nullable: true }
|
||||
auto_recovery_started: { type: boolean }
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
# node.cache.updated.v1 — template cache changed (download or evict)
|
||||
# -------------------------------------------------------------------
|
||||
node.cache.updated.v1:
|
||||
routing_key: node.cache.updated.v1
|
||||
payload:
|
||||
type: object
|
||||
required: [node_id, action, project_id, aep_file_md5]
|
||||
properties:
|
||||
node_id: { type: string, format: uuid }
|
||||
action: { type: string, enum: [Downloaded, Evicted, Verified, Failed] }
|
||||
project_id: { type: string, format: uuid }
|
||||
aep_file_md5: { type: string }
|
||||
file_size_bytes: { type: integer, format: int64 }
|
||||
cache_used_gb: { type: integer, description: "Total cache size after action" }
|
||||
duration_ms: { type: integer, nullable: true }
|
||||
error_message: { type: string, nullable: true }
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
# node.frame.completed.v1 — single frame done (high-frequency)
|
||||
# NOT on topic; sent via direct push to orchestrator.
|
||||
# -------------------------------------------------------------------
|
||||
node.frame.completed.v1:
|
||||
routing_key: node.frame.completed.v1
|
||||
transport: HTTP POST /v1/internal/render/jobs/{job_id}/frames
|
||||
payload:
|
||||
type: object
|
||||
required: [render_job_id, frame_job_id, frame_number]
|
||||
properties:
|
||||
render_job_id: { type: string, format: uuid }
|
||||
frame_job_id: { type: string, format: uuid }
|
||||
frame_number: { type: integer }
|
||||
file_size_bytes: { type: integer }
|
||||
completed_at: { type: string, format: date-time }
|
||||
Reference in New Issue
Block a user