package hostops import ( "fmt" "regexp" "strings" "time" "github.com/carlosframework/platform/internal/controlname" "github.com/carlosframework/platform/internal/economics" "github.com/carlosframework/platform/internal/hostactors" "github.com/carlosframework/platform/internal/infraview" "github.com/carlosframework/platform/internal/moves" "github.com/carlosframework/platform/internal/release" "github.com/carlosframework/platform/internal/telemetry" "github.com/carlosframework/platform/internal/usage" ) // classifyDeploymentFamily reviews the deployment- and account-level // families the route/worker census found writing under HostClaim. Each is an // exact key shape owned by one writer, and its body must be what that writer // serializes there: decoded strictly (no unknown field, no trailing value) // into the writer's own type and bound to the key it is written at. None of // these writers ever deletes, so every family is Put-only and a delete stays // unclassified. ok=false means the key is none of these shapes; a lookalike // under the same roots (an extra segment, an app-shaped subkey, another // suffix) falls through to ErrUnclassified. func classifyDeploymentFamily(w *ControlWriter, t MutationTarget) (Classification, bool, error) { fam, check := deploymentFamily(t.Key) if check == nil { return Classification{}, false, nil } if t.Method != "Put" { return Classification{}, true, fmt.Errorf("%w: %s refuses %s", ErrUnclassified, t.Key, t.Method) } reason, err := check(w, t) if err != nil { return Classification{}, true, err } return Classification{Family: fam, NonApp: reason}, true, nil } type familyCheck func(*ControlWriter, MutationTarget) (string, error) // deploymentFamily matches the exact key shapes. It parses; the check // validates the body against the key. func deploymentFamily(key string) (string, familyCheck) { p := strings.Split(key, "/") switch { case key == economics.ActiveKey: return "economics-active", checkEconomicsActive case len(p) == 3 && p[0] == "economics" && p[1] == "bills": if month, ok := jsonName(p[2]); ok && validMonth(month) { return "economics-bill", func(_ *ControlWriter, t MutationTarget) (string, error) { return checkBill(month, t) } } case len(p) == 3 && p[0] == "economics" && p[1] == "scenarios": if name, ok := jsonName(p[2]); ok && economics.ValidScenarioName(name) { return "economics-scenario", func(_ *ControlWriter, t MutationTarget) (string, error) { return checkScenario(name, t) } } case key == infraview.InventoryKey: return "infra-inventory", checkInventory case len(p) == 3 && p[0] == "usage" && (p[1] == "census" || p[1] == "storage"): if date, ok := jsonName(p[2]); ok && validDate(date) { if p[1] == "census" { return "usage-census", func(_ *ControlWriter, t MutationTarget) (string, error) { return checkCensusDay(date, t) } } return "usage-storage", func(_ *ControlWriter, t MutationTarget) (string, error) { return checkStorageDay(date, t) } } case len(p) == 3 && p[0] == "usage" && p[1] == "scan": if date, ok := jsonName(p[2]); ok && validDate(date) { return "usage-scan", func(_ *ControlWriter, t MutationTarget) (string, error) { return checkScanDay(date, t) } } case len(p) == 4 && p[0] == "usage" && p[1] == "accounts" && controlname.ValidAccount(p[2]): if month, ok := jsonName(p[3]); ok && validMonth(month) { acct := p[2] return "usage-statement", func(_ *ControlWriter, t MutationTarget) (string, error) { return checkStatement(acct, month, t) } } case len(p) == 3 && p[0] == "status" && p[1] == "consoles": if id, ok := jsonName(p[2]); ok && consoleSegment(id) { return "console-report", func(_ *ControlWriter, t MutationTarget) (string, error) { return checkConsoleReport(id, t) } } case key == moves.ConsoleKey: return "moves-console", checkMovesConsole case len(p) == 3 && p[0] == "accounts" && p[2] == "telemetry.json" && controlname.ValidAccount(p[1]): return "account-telemetry", checkAccountTelemetry case key == release.SystemKey(release.AntivirusName): return "system-antivirus", checkAntivirus case len(p) == 4 && p[0] == "bootstrap" && p[1] == "router": version, platform := p[2], strings.TrimPrefix(p[3], "carlos-") if release.ValidVersion(version) && version != "dev" && systemPlatformRe.MatchString(platform) && key == release.SystemArtifactKey(version, platform) { return "system-artifact", checkSystemArtifact } case len(p) == 4 && p[0] == "control" && p[1] == "system" && p[2] == release.SystemComponentRouter: if channel, ok := jsonName(p[3]); ok && (channel == release.RungCanary || channel == release.RungStable) { return "system-pointer", func(w *ControlWriter, t MutationTarget) (string, error) { return checkSystemPointer(w, channel, t) } } } return "", nil } // systemPlatformRe mirrors release's unexported - shape. var systemPlatformRe = regexp.MustCompile(`^[a-z0-9]+-[a-z0-9]+$`) func jsonName(file string) (string, bool) { name, ok := strings.CutSuffix(file, ".json") return name, ok && name != "" } func validMonth(s string) bool { at, err := time.Parse("2006-01", s) return err == nil && at.Format("2006-01") == s } func validDate(s string) bool { at, err := time.Parse("2006-01-02", s) return err == nil && at.Format("2006-01-02") == s } // consoleSegment is hostactors' own segment rule for a console id, the one // its census reader applies before reading status/consoles/.json. func consoleSegment(s string) bool { return s != "" && s != "." && s != ".." && !strings.ContainsAny(s, "/\\\x00") } func requireCreateOnly(t MutationTarget) error { if t.ETag != "" { return fmt.Errorf("%w: %s is create-only", ErrIdentity, t.Key) } return nil } func checkEconomicsActive(_ *ControlWriter, t MutationTarget) (string, error) { var a economics.Active if err := strictJSON(t.After, &a); err != nil { return "", err } if a.Schema != 1 || !economics.ValidScenarioName(a.Name) { return "", ErrIdentity } return "operator economics: which pricing scenario the fleet dashboards load; holds no app record", nil } func checkBill(month string, t MutationTarget) (string, error) { var b economics.Bill if err := strictJSON(t.After, &b); err != nil { return "", err } if b.Schema != 1 || b.Month != month || (b.Source != economics.SourceManual && b.Source != economics.SourceCostExplorer) { return "", ErrIdentity } return "operator economics: the deployment's own cloud bill for one month, by service; holds no app record", nil } func checkScenario(name string, t MutationTarget) (string, error) { var sc economics.Scenario if err := strictJSON(t.After, &sc); err != nil { return "", err } if sc.Name != name || sc.Validate() != nil { return "", ErrIdentity } return "operator economics: a named pricing knob-set for the dashboards; holds no app record", nil } func checkInventory(_ *ControlWriter, t MutationTarget) (string, error) { var inv infraview.Inventory if err := strictJSON(t.After, &inv); err != nil { return "", err } return "operator infrastructure inventory snapshot of the deployment's cloud estate; holds no app record", nil } // Census and storage days are create-only history: a day once written is // never rewritten, by its writer or anything riding its key. func checkCensusDay(date string, t MutationTarget) (string, error) { if err := requireCreateOnly(t); err != nil { return "", err } var d usage.CensusDay if err := strictJSON(t.After, &d); err != nil { return "", err } if d.V != 1 || d.Date != date { return "", ErrIdentity } return "usage metering: one day's per-account app and route counts, create-only; holds no app record", nil } func checkStorageDay(date string, t MutationTarget) (string, error) { if err := requireCreateOnly(t); err != nil { return "", err } var d usage.StorageDay if err := strictJSON(t.After, &d); err != nil { return "", err } if d.V != 1 || d.Date != date { return "", ErrIdentity } return "usage metering: one day's measured storage sizes, create-only; a measurement of apps, not app state", nil } // checkScanDay admits one day's on-demand scan counts. NOT create-only, unlike // census and storage days: the drain folds every tick's deltas into the day // with a compare-and-swap (usage.UpsertScanDay), so the same key is rewritten // all day long. The body is still bound to its key and schema. func checkScanDay(date string, t MutationTarget) (string, error) { var d usage.ScanDay if err := strictJSON(t.After, &d); err != nil { return "", err } if d.V != 1 || d.Date != date { return "", ErrIdentity } return "usage metering: one day's per-account on-demand scan counts, folded per drain tick; a measurement of apps, not app state", nil } func checkStatement(acct, month string, t MutationTarget) (string, error) { var st usage.Statement if err := strictJSON(t.After, &st); err != nil { return "", err } if st.V != 1 || st.Account != acct || st.Month != month { return "", ErrIdentity } return "usage metering: one account's month-to-date meter totals for billing; holds no app record", nil } func checkConsoleReport(id string, t MutationTarget) (string, error) { var r hostactors.Report if err := strictJSON(t.After, &r); err != nil { return "", err } if r.Component != "console" || r.Actor != id || r.InstanceID != id { return "", ErrIdentity } return "console process report keyed by console id, the console analogue of status/boxes/; holds no app record", nil } func checkMovesConsole(_ *ControlWriter, t MutationTarget) (string, error) { var rec moves.ConsoleRecord if err := strictJSON(t.After, &rec); err != nil { return "", err } if rec.V != 1 || rec.Protocol < 1 { return "", ErrIdentity } return "the console's moves protocol and secrets-key fingerprint; deployment-wide, holds no app record", nil } // checkAccountTelemetry admits the account-wide default only: the analytics // and errors halves with their closed vocabularies. Apps inherit it, but an // app's own setting lives at apps///telemetry.json, which is app // scope; this object can carry nothing app-keyed. func checkAccountTelemetry(_ *ControlWriter, t MutationTarget) (string, error) { var st telemetry.Settings if err := strictJSON(t.After, &st); err != nil { return "", err } if a := st.Analytics; a != nil { if a.Count != "" && a.Count != telemetry.CountEdge && a.Count != telemetry.CountOff { return "", ErrIdentity } if a.Door != "" && a.Door != telemetry.DoorOn && a.Door != telemetry.DoorOff { return "", ErrIdentity } } if e := st.Errors; e != nil && e.LogLevel != "" && !telemetry.ValidLogLevel(e.LogLevel) { return "", ErrIdentity } return "account-wide telemetry default (analytics and error level); names no app, and an app's own setting is app scope", nil } func checkAntivirus(_ *ControlWriter, t MutationTarget) (string, error) { var av release.AntivirusSettings if err := strictJSON(t.After, &av); err != nil { return "", err } if av.Schema != release.AntivirusSchema { return "", ErrIdentity } return "deployment-wide object scanning switch; holds no app record", nil } // checkSystemArtifact admits a mirrored platform binary. Versions are // immutable, so the write is create-only; nothing adopts the bytes until a // root-signed system pointer names their sha256. func checkSystemArtifact(_ *ControlWriter, t MutationTarget) (string, error) { if err := requireCreateOnly(t); err != nil { return "", err } if len(t.After) == 0 { return "", ErrIdentity } return "mirrored platform binary for one version and platform, create-only; not app code", nil } // checkSystemPointer admits the platform's own channel pointer only as the // deployment root signed it, for exactly the component and channel of its // key. A promotion or rollback must also pass release's full pointer // validation; a cancellation fence is the root-signed prior pointer. func checkSystemPointer(w *ControlWriter, channel string, t MutationTarget) (string, error) { if w == nil { return "", ErrContract } var p release.SystemPointer if release.VerifyRootEnvelope(w.pub, t.After, &p) != nil { return "", ErrIdentity } if p.Schema != 1 || p.Component != release.SystemComponentRouter || p.Channel != channel { return "", ErrIdentity } if p.CancellationID == "" { pub := release.SystemPublication{Component: p.Component, Channel: channel, ID: p.ActivationID, After: t.After} if _, err := pub.Verify(w.pub); err != nil { return "", ErrIdentity } } return "root-signed platform channel pointer; names a platform release, never an app's", nil }