package main import ( "context" "encoding/json" "fmt" "git.cin.su/m/Assistant/adapters/postgres" "git.cin.su/m/Assistant/contracts/capability" "git.cin.su/m/Assistant/contracts/identity" "git.cin.su/m/Assistant/contracts/situation" "git.cin.su/m/Assistant/contracts/work" capabilitydomain "git.cin.su/m/Assistant/modules/capability/domain" identitydomain "git.cin.su/m/Assistant/modules/identity/domain" situationdomain "git.cin.su/m/Assistant/modules/situation/domain" "git.cin.su/m/Assistant/runtime/health" "io" "log" "net/http" "os" "strconv" "strings" "time" ) const maxCreatorCredentialsBytes = 4 << 10 func main() { addr := os.Getenv("LISTEN_ADDR") if addr == "" { addr = ":8080" } bootstrapToken, err := readRequiredSecret("CREATOR_BOOTSTRAP_TOKEN") if err != nil { log.Fatal(err) } sessionKeyValue, err := readRequiredSecret("CREATOR_SESSION_SIGNING_KEY") if err != nil { log.Fatal(err) } creatorPassword, err := readRequiredSecret("CREATOR_PASSWORD") if err != nil { log.Fatal(err) } sessionKey := []byte(sessionKeyValue) creatorUsername := os.Getenv("CREATOR_USERNAME") repository, err := postgres.OpenSituation(context.Background(), postgres.ContinuityConfig{Host: os.Getenv("POSTGRES_HOST"), Port: os.Getenv("POSTGRES_PORT"), Database: os.Getenv("POSTGRES_DATABASE"), User: os.Getenv("POSTGRES_USER"), Password: os.Getenv("POSTGRES_PASSWORD")}) if err != nil { log.Fatal(err) } defer repository.Close() workRepository, err := postgres.OpenWork(context.Background(), postgres.ContinuityConfig{Host: os.Getenv("POSTGRES_HOST"), Port: os.Getenv("POSTGRES_PORT"), Database: os.Getenv("POSTGRES_DATABASE"), User: os.Getenv("WORK_POSTGRES_USER"), Password: os.Getenv("WORK_POSTGRES_PASSWORD")}) if err != nil { log.Fatal(err) } defer workRepository.Close() capabilityRepository, err := postgres.OpenCapability(context.Background(), postgres.ContinuityConfig{Host: os.Getenv("POSTGRES_HOST"), Port: os.Getenv("POSTGRES_PORT"), Database: os.Getenv("POSTGRES_DATABASE"), User: os.Getenv("CAPABILITY_POSTGRES_USER"), Password: os.Getenv("CAPABILITY_POSTGRES_PASSWORD")}) if err != nil { log.Fatal(err) } defer capabilityRepository.Close() identityRepository, err := postgres.OpenIdentity(context.Background(), postgres.ContinuityConfig{Host: os.Getenv("POSTGRES_HOST"), Port: os.Getenv("POSTGRES_PORT"), Database: os.Getenv("POSTGRES_DATABASE"), User: os.Getenv("IDENTITY_POSTGRES_USER"), Password: os.Getenv("IDENTITY_POSTGRES_PASSWORD")}) if err != nil { log.Fatal(err) } defer identityRepository.Close() mux := http.NewServeMux() authenticateCreator := func(w http.ResponseWriter, r *http.Request) (identity.CreatorSession, bool) { session, err := identitydomain.ValidateCreatorSession(sessionKey, bearerToken(r), time.Now()) if err != nil { http.Error(w, "creator session denied", http.StatusUnauthorized) return identity.CreatorSession{}, false } return session, true } resolveFocus := func(r *http.Request) (situation.FocusBinding, error) { runtime := situation.RuntimeBinding{SessionID: situation.SessionID(r.URL.Query().Get("session_id")), Workspace: situation.WorkspaceID(r.URL.Query().Get("workspace_id"))} bindings, err := repository.ByRuntime(r.Context(), runtime) if err != nil { return situation.FocusBinding{}, err } return situationdomain.NewResolver(bindings).Resolve(runtime) } mux.Handle("/healthz", health.Handler{Runtime: "core-runtime", SemanticGeneration: 1, ArchitectureGeneration: 1}) mux.HandleFunc("POST /v1/creator/sessions", creatorSessionHandler(bootstrapToken, creatorUsername, creatorPassword, sessionKey, time.Now)) mux.HandleFunc("GET /v1/creator/sessions/current", func(w http.ResponseWriter, r *http.Request) { session, ok := authenticateCreator(w, r) if !ok { return } w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(session) }) mux.HandleFunc("GET /v1/creator/focus", func(w http.ResponseWriter, r *http.Request) { session, ok := authenticateCreator(w, r) if !ok { return } focus, err := resolveFocus(r) if err != nil { http.Error(w, "creator focus unavailable", http.StatusConflict) return } if focus.ActorID != session.ActorID { http.Error(w, "creator focus denied", http.StatusForbidden) return } w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(focus) }) mux.HandleFunc("GET /v1/creator/tasks/{id}", func(w http.ResponseWriter, r *http.Request) { session, ok := authenticateCreator(w, r) if !ok { return } focus, err := resolveFocus(r) if err != nil || focus.ActorID != session.ActorID { http.Error(w, "creator focus denied", http.StatusForbidden) return } target, err := workRepository.Load(r.Context(), work.TaskID(r.PathValue("id"))) if err != nil { http.Error(w, "task not found", http.StatusNotFound) return } if err := situationdomain.ValidateTaskTarget(focus, target); err != nil { http.Error(w, "task outside creator focus", http.StatusForbidden) return } w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(target) }) mux.HandleFunc("GET /v1/creator/capabilities/{id}/health", func(w http.ResponseWriter, r *http.Request) { if _, ok := authenticateCreator(w, r); !ok { return } status, err := capabilityRepository.LoadHealth(r.Context(), capability.CapabilityID(r.PathValue("id"))) if err != nil { http.Error(w, "capability health not found", http.StatusNotFound) return } w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(status) }) mux.HandleFunc("GET /v1/principal-bindings/{actor}/{principal}", func(w http.ResponseWriter, r *http.Request) { binding, err := identityRepository.LoadBinding(r.Context(), identity.ActorID(r.PathValue("actor")), identity.PrincipalID(r.PathValue("principal"))) if err != nil { http.Error(w, "principal binding not found", http.StatusNotFound) return } w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(binding) }) mux.HandleFunc("GET /v1/tasks/{id}", func(w http.ResponseWriter, r *http.Request) { target, err := workRepository.Load(r.Context(), work.TaskID(r.PathValue("id"))) if err != nil { http.Error(w, "task not found", http.StatusNotFound) return } w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(target) }) mux.HandleFunc("POST /v1/capabilities/health", func(w http.ResponseWriter, r *http.Request) { var status capability.Health if err := json.NewDecoder(r.Body).Decode(&status); err != nil { http.Error(w, "invalid capability health", http.StatusBadRequest) return } if err := capabilitydomain.ValidateHealth(status); err != nil { http.Error(w, "invalid capability health", http.StatusBadRequest) return } if err := capabilityRepository.SaveHealth(r.Context(), status); err != nil { http.Error(w, "save capability health", http.StatusInternalServerError) return } w.WriteHeader(http.StatusCreated) }) mux.HandleFunc("GET /v1/capabilities/{id}/health", func(w http.ResponseWriter, r *http.Request) { status, err := capabilityRepository.LoadHealth(r.Context(), capability.CapabilityID(r.PathValue("id"))) if err != nil { http.Error(w, "capability health not found", http.StatusNotFound) return } w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(status) }) mux.HandleFunc("GET /v1/capability-bindings/{id}/revisions/{revision}", func(w http.ResponseWriter, r *http.Request) { revision, err := strconv.ParseInt(r.PathValue("revision"), 10, 64) if err != nil || revision < 1 { http.Error(w, "invalid binding revision", http.StatusBadRequest) return } binding, err := capabilityRepository.LoadBinding(r.Context(), capability.CapabilityBindingID(r.PathValue("id")), revision) if err != nil { http.Error(w, "capability binding not found", http.StatusNotFound) return } if err = capabilitydomain.ValidateBinding(binding); err != nil { http.Error(w, "capability binding invalid", http.StatusConflict) return } w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(binding) }) mux.HandleFunc("GET /v1/capability-bindings/active", func(w http.ResponseWriter, r *http.Request) { capabilityID := capability.CapabilityID(r.URL.Query().Get("capability_id")) ownerID := identity.PrincipalID(r.URL.Query().Get("owner_principal_id")) if capabilityID == "" || ownerID == "" { http.Error(w, "invalid binding query", http.StatusBadRequest) return } binding, err := capabilityRepository.LoadActiveBinding(r.Context(), capabilityID, ownerID) if err != nil { http.Error(w, "active capability binding not found", http.StatusNotFound) return } if err = capabilitydomain.UsableBinding(binding); err != nil { http.Error(w, "active capability binding unavailable", http.StatusConflict) return } w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(binding) }) mux.HandleFunc("POST /v1/capabilities/health-projection/rebuild", func(w http.ResponseWriter, r *http.Request) { rebuilt, err := capabilityRepository.RebuildHealthProjection(r.Context()) if err != nil { http.Error(w, "rebuild capability health projection", http.StatusInternalServerError) return } _ = json.NewEncoder(w).Encode(struct{ Rebuilt int64 }{rebuilt}) }) mux.HandleFunc("POST /v1/focus-bindings", func(w http.ResponseWriter, r *http.Request) { var binding situation.FocusBinding if err := json.NewDecoder(r.Body).Decode(&binding); err != nil { http.Error(w, "invalid focus binding", http.StatusBadRequest) return } if err := repository.Save(r.Context(), binding); err != nil { http.Error(w, "save focus binding", http.StatusInternalServerError) return } w.WriteHeader(http.StatusCreated) }) mux.HandleFunc("GET /v1/focus", func(w http.ResponseWriter, r *http.Request) { focus, err := resolveFocus(r) if err != nil { http.Error(w, err.Error(), http.StatusConflict) return } w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(focus) }) mux.HandleFunc("POST /v1/tasks", func(w http.ResponseWriter, r *http.Request) { var target work.TaskTarget if err := json.NewDecoder(r.Body).Decode(&target); err != nil { http.Error(w, "invalid task", 400) return } if err := workRepository.Create(r.Context(), target); err != nil { http.Error(w, "create task", 500) return } w.WriteHeader(http.StatusCreated) }) mux.HandleFunc("POST /v1/tasks/{id}/commit", func(w http.ResponseWriter, r *http.Request) { var target work.TaskTarget if err := json.NewDecoder(r.Body).Decode(&target); err != nil { http.Error(w, "invalid task commit", 400) return } target.TaskID = work.TaskID(r.PathValue("id")) revision, err := workRepository.Commit(r.Context(), target) if err != nil { http.Error(w, "task revision conflict", http.StatusConflict) return } _ = json.NewEncoder(w).Encode(struct{ Revision work.TaskRevision }{revision}) }) mux.HandleFunc("POST /v1/focus-task-commits", func(w http.ResponseWriter, r *http.Request) { var request struct { SessionID situation.SessionID Workspace situation.WorkspaceID Target work.TaskTarget } if err := json.NewDecoder(r.Body).Decode(&request); err != nil { http.Error(w, "invalid guarded commit", http.StatusBadRequest) return } bindings, err := repository.ByRuntime(r.Context(), situation.RuntimeBinding{SessionID: request.SessionID, Workspace: request.Workspace}) if err != nil { http.Error(w, "load focus", http.StatusInternalServerError) return } focus, err := situationdomain.NewResolver(bindings).Resolve(situation.RuntimeBinding{SessionID: request.SessionID, Workspace: request.Workspace}) if err != nil { http.Error(w, "focus unavailable", http.StatusConflict) return } if err := situationdomain.ValidateTaskTarget(focus, request.Target); err != nil { http.Error(w, "target outside resolved focus", http.StatusConflict) return } revision, err := workRepository.Commit(r.Context(), request.Target) if err != nil { http.Error(w, "task revision conflict", http.StatusConflict) return } _ = json.NewEncoder(w).Encode(struct{ Revision work.TaskRevision }{revision}) }) log.Printf("core-runtime listening on %s", addr) if err := http.ListenAndServe(addr, mux); err != nil { log.Fatal(err) } } func creatorSessionHandler(bootstrapToken, creatorUsername, creatorPassword string, sessionKey []byte, now func() time.Time) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { authenticated := identitydomain.BootstrapMatches(bootstrapToken, bearerToken(r)) if !authenticated { var credentials identity.CreatorCredentials decoder := json.NewDecoder(io.LimitReader(r.Body, maxCreatorCredentialsBytes+1)) decoder.DisallowUnknownFields() if err := decoder.Decode(&credentials); err != nil || decoder.Decode(&struct{}{}) != io.EOF { http.Error(w, "creator authentication denied", http.StatusUnauthorized) return } authenticated = identitydomain.CreatorCredentialsMatch(creatorUsername, creatorPassword, credentials) } if !authenticated { http.Error(w, "creator authentication denied", http.StatusUnauthorized) return } token, session, err := identitydomain.IssueCreatorSession(sessionKey, now(), 8*time.Hour) if err != nil { http.Error(w, "creator session unavailable", http.StatusServiceUnavailable) return } w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(struct { Token string Session identity.CreatorSession }{token, session}) } } func bearerToken(r *http.Request) string { value := r.Header.Get("Authorization") if !strings.HasPrefix(value, "Bearer ") { return "" } return strings.TrimPrefix(value, "Bearer ") } func readRequiredSecret(name string) (string, error) { direct, directSet := os.LookupEnv(name) fileName, fileSet := os.LookupEnv(name + "_FILE") if directSet && fileSet { return "", fmt.Errorf("%s and %s_FILE cannot both be configured", name, name) } if fileSet { if fileName == "" { return "", fmt.Errorf("%s_FILE is empty", name) } contents, err := os.ReadFile(fileName) if err != nil { return "", fmt.Errorf("read %s_FILE: %w", name, err) } direct = strings.TrimRight(string(contents), "\r\n") if strings.ContainsAny(direct, "\r\n") { return "", fmt.Errorf("%s_FILE must contain one line", name) } } if direct == "" { return "", fmt.Errorf("%s is required", name) } return direct, nil }