molecule-core/workspace-server/internal/registry/liveness.go
Hongming Wang d8026347e5 chore: open-source restructure — rename dirs, remove internal files, scrub secrets
Renames:
- platform/ → workspace-server/ (Go module path stays as "platform" for
  external dep compat — will update after plugin module republish)
- workspace-template/ → workspace/

Removed (moved to separate repos or deleted):
- PLAN.md — internal roadmap (move to private project board)
- HANDOFF.md, AGENTS.md — one-time internal session docs
- .claude/ — gitignored entirely (local agent config)
- infra/cloudflare-worker/ → Molecule-AI/molecule-tenant-proxy
- org-templates/molecule-dev/ → standalone template repo
- .mcp-eval/ → molecule-mcp-server repo
- test-results/ — ephemeral, gitignored

Security scrubbing:
- Cloudflare account/zone/KV IDs → placeholders
- Real EC2 IPs → <EC2_IP> in all docs
- CF token prefix, Neon project ID, Fly app names → redacted
- Langfuse dev credentials → parameterized
- Personal runner username/machine name → generic

Community files:
- CONTRIBUTING.md — build, test, branch conventions
- CODE_OF_CONDUCT.md — Contributor Covenant 2.1

All Dockerfiles, CI workflows, docker-compose, railway.toml, render.yaml,
README, CLAUDE.md updated for new directory names.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-18 00:24:44 -07:00

60 lines
1.5 KiB
Go

package registry
import (
"context"
"log"
"strings"
"github.com/Molecule-AI/molecule-monorepo/platform/internal/db"
)
// OfflineHandler is called when a workspace's liveness key expires.
type OfflineHandler func(ctx context.Context, workspaceID string)
// StartLivenessMonitor subscribes to Redis keyspace expiry events.
// When a workspace's liveness key (ws:{id}) expires, it marks the workspace offline
// and calls the onOffline handler.
func StartLivenessMonitor(ctx context.Context, onOffline OfflineHandler) {
sub := db.RDB.PSubscribe(ctx, "__keyevent@0__:expired")
log.Println("Liveness monitor started — listening for Redis key expirations")
ch := sub.Channel()
for {
select {
case <-ctx.Done():
sub.Close()
return
case msg := <-ch:
if msg == nil {
continue
}
key := msg.Payload
if !strings.HasPrefix(key, "ws:") {
continue
}
parts := strings.SplitN(key, ":", 3)
if len(parts) != 2 {
continue
}
workspaceID := parts[1]
log.Printf("Liveness: workspace %s TTL expired", workspaceID)
// Mark offline in Postgres — skip paused and hibernated workspaces (no active container)
_, err := db.DB.ExecContext(ctx, `
UPDATE workspaces SET status = 'offline', updated_at = now()
WHERE id = $1 AND status NOT IN ('removed', 'paused', 'hibernated')
`, workspaceID)
if err != nil {
log.Printf("Liveness: failed to mark %s offline: %v", workspaceID, err)
continue
}
if onOffline != nil {
onOffline(ctx, workspaceID)
}
}
}
}