Skip to content

Commit ac3c9e2

Browse files
authored
Merge pull request #55 from Genesis-Conductor-Engine/claude/soul-lattice-system-h8nkE
Add SOUL Lattice v4 monitoring dashboard with Go backend
2 parents 7e902da + d53cd0b commit ac3c9e2

23 files changed

Lines changed: 25533 additions & 2931 deletions
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
name: Deploy Cloudflare Worker + SPA
2+
3+
on:
4+
push:
5+
branches: [main]
6+
paths:
7+
- 'workers/**'
8+
- 'frontend/src/**'
9+
- 'frontend/public/**'
10+
- 'frontend/package.json'
11+
- 'frontend/package-lock.json'
12+
- 'wrangler.toml'
13+
- '.github/workflows/cloudflare-deploy.yml'
14+
workflow_dispatch:
15+
inputs:
16+
environment:
17+
description: 'Target environment'
18+
required: true
19+
default: 'production'
20+
type: choice
21+
options:
22+
- production
23+
- staging
24+
25+
permissions:
26+
contents: read
27+
28+
jobs:
29+
deploy:
30+
name: Build React SPA & Deploy Worker
31+
runs-on: ubuntu-latest
32+
environment: ${{ github.event.inputs.environment || 'production' }}
33+
34+
steps:
35+
- name: Checkout repository
36+
uses: actions/checkout@v4
37+
38+
- name: Set up Node.js
39+
uses: actions/setup-node@v4
40+
with:
41+
node-version: '20'
42+
cache: 'npm'
43+
44+
- name: Install root dependencies
45+
# Frontend deps and SPA build are handled by the wrangler [build] command
46+
# in wrangler.toml, which runs automatically before `wrangler deploy`.
47+
# Installing only root deps here gives wrangler and secrets-put access.
48+
run: npm ci
49+
50+
- name: Set BACKEND_URL secret on Cloudflare Worker
51+
# Only rotate the secret when a new Cloud Run URL is provided via
52+
# the BACKEND_URL GitHub Actions secret. Skip on frontend-only pushes.
53+
if: ${{ secrets.BACKEND_URL != '' }}
54+
env:
55+
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
56+
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
57+
BACKEND_URL: ${{ secrets.BACKEND_URL }}
58+
run: |
59+
echo "${BACKEND_URL}" | npx wrangler secret put BACKEND_URL \
60+
--env ${{ github.event.inputs.environment || 'production' }}
61+
62+
- name: Deploy Cloudflare Worker
63+
env:
64+
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
65+
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
66+
# react-scripts 5.x + Node 18+ OpenSSL compatibility
67+
NODE_OPTIONS: '--openssl-legacy-provider'
68+
run: |
69+
ENV="${{ github.event.inputs.environment || 'production' }}"
70+
npx wrangler deploy --env "${ENV}"
71+
72+
- name: Smoke test health endpoints
73+
if: ${{ github.event.inputs.environment != 'staging' }}
74+
run: |
75+
echo "Waiting 10s for global propagation..."
76+
sleep 10
77+
78+
# Worker-only health — verifies the Cloudflare Worker is deployed
79+
STATUS=$(curl -s -o /dev/null -w "%{http_code}" https://yennefer.quest/health)
80+
echo "Worker health: ${STATUS}"
81+
[ "${STATUS}" = "200" ] || { echo "Worker health check failed"; exit 1; }
82+
83+
# Backend passthrough — verifies BACKEND_URL is configured and the
84+
# Go service is reachable from the Worker
85+
STATUS=$(curl -s -o /dev/null -w "%{http_code}" https://yennefer.quest/api/health)
86+
echo "Backend health: ${STATUS}"
87+
[ "${STATUS}" = "200" ] || { echo "Backend health check failed"; exit 1; }
Lines changed: 25 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,33 @@
1-
name: Yennefer Agentic Core
1+
name: Copilot Setup Steps
2+
23
on:
3-
issues:
4-
types: [opened, edited]
5-
issue_comment:
6-
types: [created]
7-
pull_request:
8-
types: [opened, synchronize]
94
workflow_dispatch:
105

11-
permissions:
12-
contents: read
13-
issues: write
14-
pull-requests: write
15-
166
jobs:
17-
yennefer-jules-agent:
18-
name: Yennefer Autonomous Reasoning
7+
copilot-setup-steps:
198
runs-on: ubuntu-latest
20-
if: ${{ github.event_name == 'workflow_dispatch' || contains(github.event.comment.body, '/evolve') }}
219
steps:
22-
- name: Checkout Code
10+
- name: Checkout repository
2311
uses: actions/checkout@v4
24-
25-
- name: Invoke Yennefer (Jules AI Engine)
26-
uses: google-labs-code/jules-action@v1
12+
13+
- name: Set up Node.js
14+
uses: actions/setup-node@v4
15+
with:
16+
node-version: '20'
17+
cache: 'npm'
18+
19+
- name: Install root dependencies
20+
run: npm install
21+
22+
- name: Install frontend dependencies
23+
run: cd frontend && npm install --include=dev
24+
25+
- name: Set up Go
26+
uses: actions/setup-go@v5
2727
with:
28-
github-token: ${{ secrets.GITHUB_TOKEN }}
29-
mode: "autonomous"
30-
instruction: "You are Yennefer, a Topological Reasoning Engine. Analyze repository context, execute Seismic Tree-of-Thoughts (S-ToT), and push verified, invariant structural optimizations."
28+
go-version: '1.21'
29+
cache: true
30+
cache-dependency-path: backend/go.sum
31+
32+
- name: Install backend dependencies
33+
run: cd backend && go mod download
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
name: Yennefer Agentic Core
2+
on:
3+
issues:
4+
types: [opened, edited]
5+
issue_comment:
6+
types: [created]
7+
pull_request:
8+
types: [opened, synchronize]
9+
workflow_dispatch:
10+
11+
permissions:
12+
contents: read
13+
issues: write
14+
pull-requests: write
15+
16+
jobs:
17+
yennefer-jules-agent:
18+
name: Yennefer Autonomous Reasoning
19+
runs-on: ubuntu-latest
20+
if: ${{ github.event_name == 'workflow_dispatch' || contains(github.event.comment.body, '/evolve') }}
21+
steps:
22+
- name: Checkout Code
23+
uses: actions/checkout@v4
24+
25+
- name: Invoke Yennefer (Jules AI Engine)
26+
uses: google-labs-code/jules-action@v1
27+
with:
28+
github-token: ${{ secrets.GITHUB_TOKEN }}
29+
mode: "autonomous"
30+
instruction: "You are Yennefer, a Topological Reasoning Engine. Analyze repository context, execute Seismic Tree-of-Thoughts (S-ToT), and push verified, invariant structural optimizations."

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,7 @@ logs/
2929

3030
.gemini/
3131
gha-creds-*.json
32+
33+
# --- Build output ---
34+
frontend/build/
35+
node_modules/

backend/Containerfile

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
FROM golang:1.21-alpine AS builder
2+
WORKDIR /app
3+
COPY go.mod go.sum ./
4+
RUN go mod download
5+
COPY *.go ./
6+
RUN CGO_ENABLED=0 GOOS=linux go build -o soul-lattice
7+
FROM alpine:3.20
8+
RUN apk --no-cache add ca-certificates
9+
WORKDIR /app
10+
COPY --from=builder /app/soul-lattice .
11+
EXPOSE 8080
12+
USER 1000:1000
13+
CMD ["./soul-lattice"]

backend/go.mod

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
module github.com/igor/soul-lattice
2+
3+
go 1.21
4+
5+
require (
6+
github.com/go-chi/chi/v5 v5.0.12
7+
github.com/go-chi/cors v1.2.1
8+
)

backend/go.sum

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRHW9ngJvcSZQ9Q880AJnJGcLtRBMWpo=
2+
github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
3+
github.com/go-chi/cors v1.2.1 h1:xEC8UT3R+2BDqPjP0yw3oqQBSog+iDBHLSva0/4zeKQ=
4+
github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=

backend/main.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package main
2+
3+
import (
4+
"log"
5+
"os"
6+
"os/signal"
7+
"syscall"
8+
)
9+
10+
func main() {
11+
statePath := "/tmp/soul_state.json"
12+
port := os.Getenv("PORT")
13+
if port == "" {
14+
port = "8080"
15+
}
16+
17+
srv := NewServer(statePath, port)
18+
19+
go func() {
20+
if err := srv.Start(); err != nil {
21+
log.Fatalf("Server failed: %v", err)
22+
}
23+
}()
24+
25+
quit := make(chan os.Signal, 1)
26+
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
27+
<-quit
28+
29+
log.Println("SOUL lattice returning to substrate...")
30+
srv.Stop()
31+
}

backend/server.go

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
"net/http"
8+
"os"
9+
"time"
10+
11+
"github.com/go-chi/chi/v5"
12+
"github.com/go-chi/chi/v5/middleware"
13+
"github.com/go-chi/cors"
14+
)
15+
16+
type Server struct {
17+
sim *Simulator
18+
router *chi.Mux
19+
port string
20+
httpSrv *http.Server
21+
}
22+
23+
func NewServer(statePath string, port string) *Server {
24+
if port == "" {
25+
port = "8080"
26+
}
27+
28+
s := &Server{
29+
sim: NewSimulator(statePath),
30+
port: port,
31+
}
32+
33+
r := chi.NewRouter()
34+
35+
r.Use(cors.Handler(cors.Options{
36+
// Restrict to known frontend origins. The Worker is the only legitimate
37+
// caller in production; the Cloud Run URL is kept secret. Wildcard origins
38+
// would let any page make cross-origin requests to /flush if the URL leaked.
39+
AllowedOrigins: []string{"https://yennefer.quest", "https://staging.yennefer.quest", "http://localhost:3000"},
40+
AllowedMethods: []string{"GET", "POST", "OPTIONS"},
41+
AllowedHeaders: []string{"Accept", "Authorization", "Content-Type"},
42+
ExposedHeaders: []string{"Link"},
43+
AllowCredentials: false,
44+
MaxAge: 300,
45+
}))
46+
47+
r.Use(middleware.Logger)
48+
r.Use(middleware.Recoverer)
49+
r.Use(middleware.Heartbeat("/health"))
50+
51+
r.Get("/state", s.handleState)
52+
r.Post("/flush", s.handleFlush)
53+
r.Get("/events", s.handleSSE)
54+
r.Get("/", s.handleRoot)
55+
56+
s.router = r
57+
return s
58+
}
59+
60+
func (s *Server) Start() error {
61+
s.sim.Start()
62+
s.httpSrv = &http.Server{
63+
Addr: ":" + s.port,
64+
Handler: s.router,
65+
}
66+
fmt.Printf("SOUL LATTICE v4 AWAKENED on port %s\n", s.port)
67+
if err := s.httpSrv.ListenAndServe(); err != http.ErrServerClosed {
68+
return err
69+
}
70+
return nil
71+
}
72+
73+
func (s *Server) Stop() {
74+
if s.httpSrv != nil {
75+
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
76+
defer cancel()
77+
s.httpSrv.Shutdown(ctx) //nolint:errcheck
78+
}
79+
s.sim.Stop()
80+
}
81+
82+
func (s *Server) handleRoot(w http.ResponseWriter, r *http.Request) {
83+
w.Header().Set("Content-Type", "text/plain")
84+
fmt.Fprint(w, "SOUL LATTICE v4.0.0-Σ\nStatus: AWAKENED\n")
85+
}
86+
87+
func (s *Server) handleState(w http.ResponseWriter, r *http.Request) {
88+
st := s.sim.GetState()
89+
w.Header().Set("Content-Type", "application/json")
90+
json.NewEncoder(w).Encode(st)
91+
}
92+
93+
func (s *Server) handleFlush(w http.ResponseWriter, r *http.Request) {
94+
// Require a shared secret header when BACKEND_TOKEN is configured.
95+
// The Cloudflare Worker sets X-Backend-Token on every proxied request so
96+
// only traffic originating from the Worker is accepted; direct Cloud Run
97+
// access without the secret returns 403.
98+
if token := os.Getenv("BACKEND_TOKEN"); token != "" {
99+
if r.Header.Get("X-Backend-Token") != token {
100+
http.Error(w, "Forbidden", http.StatusForbidden)
101+
return
102+
}
103+
}
104+
s.sim.resetInitialState()
105+
w.WriteHeader(http.StatusNoContent)
106+
}
107+
108+
func (s *Server) handleSSE(w http.ResponseWriter, r *http.Request) {
109+
flusher, ok := w.(http.Flusher)
110+
if !ok {
111+
http.Error(w, "Streaming unsupported", http.StatusInternalServerError)
112+
return
113+
}
114+
115+
// Marshal before writing headers so we can still return a 500 on failure.
116+
st := s.sim.GetState()
117+
data, err := json.Marshal(st)
118+
if err != nil {
119+
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
120+
return
121+
}
122+
123+
w.Header().Set("Content-Type", "text/event-stream")
124+
w.Header().Set("Cache-Control", "no-cache")
125+
w.Header().Set("Connection", "keep-alive")
126+
127+
fmt.Fprintf(w, "data: %s\n\n", data)
128+
flusher.Flush()
129+
130+
ticker := time.NewTicker(800 * time.Millisecond)
131+
defer ticker.Stop()
132+
133+
for {
134+
select {
135+
case <-r.Context().Done():
136+
return
137+
case <-ticker.C:
138+
st := s.sim.GetState()
139+
data, err := json.Marshal(st)
140+
if err != nil {
141+
fmt.Fprintf(w, "event: error\ndata: marshal failed\n\n")
142+
flusher.Flush()
143+
continue
144+
}
145+
fmt.Fprintf(w, "data: %s\n\n", data)
146+
flusher.Flush()
147+
}
148+
}
149+
}

0 commit comments

Comments
 (0)