Initial commit: AetherForge Linux (forge-mesh) v0.1.0-dev
Some checks failed
Test / test (push) Has been cancelled
Some checks failed
Test / test (push) Has been cancelled
This commit is contained in:
253
internal/api/handlers/intelligence.go
Normal file
253
internal/api/handlers/intelligence.go
Normal file
@@ -0,0 +1,253 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"forge-mesh/internal/auth"
|
||||
"forge-mesh/internal/court"
|
||||
"forge-mesh/internal/erasure"
|
||||
"forge-mesh/internal/fleet"
|
||||
)
|
||||
|
||||
// IntelligenceDeps bundles triple-onion fleet intelligence handlers.
|
||||
type IntelligenceDeps struct {
|
||||
Store *fleet.Store
|
||||
Subnet *fleet.SubnetMapper
|
||||
Earn *fleet.EarnGate
|
||||
}
|
||||
|
||||
// RunLOTL handles POST /api/v1/fleet/{id}/lotl/run — execute tier chain with recon.
|
||||
func RunLOTL(deps IntelligenceDeps) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
hostID := r.PathValue("id")
|
||||
if hostID == "" {
|
||||
http.Error(w, "host id required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
strategy := fleet.AdaptiveStrategy{Store: deps.Store}
|
||||
order, err := strategy.OrderForHost(r.Context(), hostID)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
results, err := fleet.RunTierChain(r.Context(), deps.Store, hostID, order)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
auth.JSON(w, http.StatusOK, map[string]interface{}{
|
||||
"host_id": hostID,
|
||||
"order": order,
|
||||
"results": results,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// ListLOTL handles GET /api/v1/fleet/{id}/lotl.
|
||||
func ListLOTL(deps IntelligenceDeps) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
hostID := r.PathValue("id")
|
||||
attempts, err := deps.Store.ListLOTL(r.Context(), hostID, 100)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
auth.JSON(w, http.StatusOK, map[string]interface{}{
|
||||
"host_id": hostID,
|
||||
"attempts": attempts,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// SpreadGate handles GET /api/v1/fleet/{id}/spread-gate (earn-before-burn).
|
||||
func SpreadGate(deps IntelligenceDeps) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
hostID := r.PathValue("id")
|
||||
dec, err := deps.Earn.CanSpreadToSiblings(r.Context(), hostID)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
auth.JSON(w, http.StatusOK, dec)
|
||||
}
|
||||
}
|
||||
|
||||
// SubnetList handles GET /api/v1/fleet/subnets.
|
||||
func SubnetList(deps IntelligenceDeps) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
cidrs, err := deps.Subnet.ListCIDRs(r.Context())
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
auth.JSON(w, http.StatusOK, map[string]interface{}{"cidrs": cidrs})
|
||||
}
|
||||
}
|
||||
|
||||
// SubnetAdd handles POST /api/v1/fleet/subnets.
|
||||
func SubnetAdd(deps IntelligenceDeps) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req struct {
|
||||
CIDR string `json:"cidr"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil || req.CIDR == "" {
|
||||
http.Error(w, "cidr required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
c, err := deps.Subnet.AddCIDR(r.Context(), req.CIDR)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
auth.JSON(w, http.StatusCreated, c)
|
||||
}
|
||||
}
|
||||
|
||||
// SubnetSweep handles POST /api/v1/fleet/subnets/sweep.
|
||||
func SubnetSweep(deps IntelligenceDeps) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
results, err := deps.Subnet.SweepAll(r.Context())
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
auth.JSON(w, http.StatusOK, map[string]interface{}{"results": results})
|
||||
}
|
||||
}
|
||||
|
||||
// CourtDeps bundles court handler dependencies.
|
||||
type CourtDeps struct {
|
||||
Court *court.Court
|
||||
Seer *court.SeerHub
|
||||
}
|
||||
|
||||
// CourtOpen handles POST /api/v1/court/sessions.
|
||||
func CourtOpen(deps CourtDeps) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req struct {
|
||||
HostID string `json:"host_id"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil || req.HostID == "" {
|
||||
http.Error(w, "host_id required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
s, err := deps.Court.OpenSession(r.Context(), req.HostID)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
auth.JSON(w, http.StatusCreated, s)
|
||||
}
|
||||
}
|
||||
|
||||
// CourtDeliberate handles POST /api/v1/court/sessions/{id}/deliberate.
|
||||
func CourtDeliberate(deps CourtDeps) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
id := r.PathValue("id")
|
||||
s, err := deps.Court.Deliberate(r.Context(), id)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
auth.JSON(w, http.StatusOK, s)
|
||||
}
|
||||
}
|
||||
|
||||
// CourtVerdict handles POST /api/v1/court/sessions/{id}/verdict (L4).
|
||||
func CourtVerdict(deps CourtDeps) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
id := r.PathValue("id")
|
||||
var req struct {
|
||||
Verdict string `json:"verdict"`
|
||||
Clearance int `json:"clearance"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil || req.Verdict == "" {
|
||||
http.Error(w, "verdict required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if req.Clearance == 0 {
|
||||
req.Clearance = 4
|
||||
}
|
||||
if err := deps.Court.DispatchVerdict(r.Context(), id, req.Verdict, req.Clearance); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
auth.JSON(w, http.StatusOK, map[string]string{"ok": "true", "verdict": req.Verdict})
|
||||
}
|
||||
}
|
||||
|
||||
// Timeline handles GET /api/v1/fleet/{id}/timeline (LOTL + court).
|
||||
func Timeline(deps CourtDeps) http.HandlerFunc {
|
||||
return court.TimelineHandler(deps.Court)
|
||||
}
|
||||
|
||||
// ErasureDeps bundles public erasure routes.
|
||||
type ErasureDeps struct {
|
||||
Service *erasure.Service
|
||||
}
|
||||
|
||||
// ErasureEncode handles POST /api/v1/public/erasure/encode (dev/admin via basic elsewhere).
|
||||
func ErasureEncode(deps ErasureDeps) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
name := r.URL.Query().Get("name")
|
||||
if name == "" {
|
||||
name = "bundle"
|
||||
}
|
||||
data, err := io.ReadAll(io.LimitReader(r.Body, 16<<20))
|
||||
if err != nil {
|
||||
http.Error(w, "read error", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
b, err := deps.Service.Encode(r.Context(), name, data)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
auth.JSON(w, http.StatusCreated, b)
|
||||
}
|
||||
}
|
||||
|
||||
// ErasureShard handles GET /api/v1/public/erasure/{bundle_id}/shard/{index}.
|
||||
func ErasureShard(deps ErasureDeps) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
bundleID := r.PathValue("bundle_id")
|
||||
idx, err := strconv.Atoi(r.PathValue("index"))
|
||||
if err != nil {
|
||||
http.Error(w, "invalid index", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
sh, err := deps.Service.GetShard(r.Context(), bundleID, idx)
|
||||
if err != nil {
|
||||
http.Error(w, "not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/octet-stream")
|
||||
w.Header().Set("X-Shard-Index", strconv.Itoa(sh.ShardIndex))
|
||||
_, _ = w.Write(sh.Data)
|
||||
}
|
||||
}
|
||||
|
||||
// ErasureBundleMeta handles GET /api/v1/public/erasure/{bundle_id}.
|
||||
func ErasureBundleMeta(deps ErasureDeps) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
bundleID := r.PathValue("bundle_id")
|
||||
b, err := deps.Service.GetBundle(r.Context(), bundleID)
|
||||
if err != nil {
|
||||
http.Error(w, "not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
indices, _ := deps.Service.ListShards(r.Context(), bundleID)
|
||||
auth.JSON(w, http.StatusOK, map[string]interface{}{
|
||||
"bundle": b,
|
||||
"shards": indices,
|
||||
"public": true,
|
||||
"scheme": "RS_4_2",
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user