package console import ( "context" "encoding/json" "net" "net/http" "strings" "github.com/carlosframework/platform/internal/provision" ) // appDefaultView is §9.3's app_default. region is the STORED region (what // deploy --region compares against); effective is where a no-region create // lands: the fleet itself for a placed app, never the platform home // (codex R2-4). RegionKnown and PlacementKnown say which facts were READ: // an unread fact is left empty, never filled with the home (codex // plan-R2 3; spec R9a). InstanceDomain is the domain instance hosts take, // so canary --region can name its host before any write (codex plan-R3 1). type appDefaultView struct { Region string `json:"region"` Name string `json:"name"` Source string `json:"source"` Effective string `json:"effective"` Available *bool `json:"available,omitempty"` Reason string `json:"reason,omitempty"` RegionKnown bool `json:"region_known"` PlacementKnown bool `json:"placement_known"` InstanceDomain string `json:"instance_domain,omitempty"` } func boolp(b bool) *bool { return &b } func (s *Server) appDefault(ctx context.Context, acct *Account, app string) appDefaultView { stored, source, rerr := s.appRegion(acct.ID, app) snap, ferr := s.liveEdges(ctx) cands := homeOnlyCandidates() if ferr == nil { cands = s.regionCands(snap) } pol := s.appPolicy(ctx, acct, app) v := appDefaultView{RegionKnown: rerr == nil, PlacementKnown: pol.State != policyUnreadable} switch pol.State { case policyKnown: v.InstanceDomain = pol.M.Domain case policyAbsent: v.InstanceDomain = s.platformInstanceDomain(acct) } if rerr == nil { v.Region, v.Name, v.Source, v.Effective = s.homeRegionOut(stored), s.placeOf(stored, cands), source, s.homeRegionOut(stored) } switch { case pol.State == policyUnreadable: // policy-unreadable is reserved for placement_known being false // (spec §9.3). v.Reason = "policy-unreadable" return v case pol.Placed != "": // R9a: with the stored region unread, effective stays empty even // though the fleet is known; reason still says fleet (codex // plan-R4 1). v.Reason = "fleet" if rerr == nil { v.Effective = pol.Placed } return v case pol.pooled(): // A no-region pooled create lands on the home, so it is available // only when the home passes the allow-list (codex plan-R3 2). R9a // again: nothing about effective is stated when the region is unread. v.Reason = "pooled" if rerr == nil { v.Effective, v.Available = s.homeRegionOut(""), boolp(pol.allows("")) } return v case rerr != nil: // The stored region is unread, so where a create lands is unknown. // Nothing is stated about it, and no reason either: the placement // WAS read (controller ruling PF7). return v case ferr != nil, stored != "" && !snap.EvidenceComplete(): // A failed listing, or (for a non-home region) a listing with an // edges file or a box snapshot unread: the evidence is not there // to say the region can take an instance, and the create door // would answer C28 for the same reason (re-review r1). v.Reason = "edges-unreadable" return v } reason := "absent" if c, ok := findCandidate(cands, stored); ok { reason = c.unavailableReason() } if reason == "" && !pol.allows(stored) { reason = "not-allowed" } v.Reason, v.Available = reason, boolp(reason == "") return v } // platformInstanceDomain is the domain the instances-manifest PUT stamps on // a NEW manifest (handleAPIInstancesEnable): "" when this console has no // valid platform apps domain, which that PUT refuses. With a manifest, its // own domain governs instead. func (s *Server) platformInstanceDomain(acct *Account) string { if s.cfg.AppsDomain == "" { return "" } d := acct.Sqid + "." + s.cfg.AppsDomain if !provision.ValidHostShape(d) { return "" } return d } // handleAPIRegionGuess is POST …/regions/guess (spec §9.4). Hints come only // in the body, so no redirect can echo them. A malformed body is read as // no hints; a person's request with no ip uses its own address, a service // principal's never does. // // A fleet-placed app gets no guess (codex R4-1), and a failed placement // lookup is an explicit 502, never a guess from a policy that was not read. func (s *Server) handleAPIRegionGuess(w http.ResponseWriter, r *http.Request) { _, acct, app, ok := s.instanceGate(w, r) if !ok { return } var body struct { TZ string `json:"tz"` IP string `json:"ip"` } _ = json.NewDecoder(http.MaxBytesReader(w, r.Body, 64<<10)).Decode(&body) pol := s.appPolicy(r.Context(), acct, app) if pol.State == policyUnreadable { jsonError(w, http.StatusBadGateway, msgPolicyUnreadable) return } if pol.Placed != "" || pol.pooled() { writeJSON(w, http.StatusOK, map[string]any{"guess": nil}) return } ip := net.ParseIP(strings.TrimSpace(body.IP)) if ip == nil { ip = s.personIP(r) } files, err := s.liveEdges(r.Context()) cands := homeOnlyCandidates() if err == nil { cands = s.regionCands(files) } g, ok := guessRegion(cands, pol.allows, guessInput{TZ: body.TZ, IP: ip}, s.cfg.GeoIP) if !ok { writeJSON(w, http.StatusOK, map[string]any{"guess": nil}) return } writeJSON(w, http.StatusOK, map[string]any{"guess": map[string]string{ "code": s.homeRegionOut(g.Code), "name": s.placeOf(g.Code, cands), "source": g.Source, }}) }