package state import ( "encoding/json" "fmt" "os" "path/filepath" "time" ) const ( IntentNormal = "normal" IntentStartupInProgress = "startup_in_progress" IntentShuttingDown = "shutting_down" IntentShutdownComplete = "shutdown_complete" ) type Intent struct { State string `json:"state"` Reason string `json:"reason,omitempty"` Source string `json:"source,omitempty"` UpdatedAt time.Time `json:"updated_at"` } // ReadIntent runs one orchestration or CLI step. // Signature: ReadIntent(path string) (Intent, error). // Why: keeps behavior explicit so startup/shutdown workflows remain maintainable as services evolve. func ReadIntent(path string) (Intent, error) { b, err := os.ReadFile(path) if err != nil { if os.IsNotExist(err) { return Intent{}, nil } return Intent{}, err } if len(b) == 0 { return Intent{}, nil } var in Intent if err := json.Unmarshal(b, &in); err != nil { if healErr := quarantineCorruptFile(path, b, []byte("{}\n"), 0o640); healErr != nil { return Intent{}, fmt.Errorf("decode intent: %w (auto-heal failed: %v)", err, healErr) } return Intent{}, nil } return in, nil } // WriteIntent runs one orchestration or CLI step. // Signature: WriteIntent(path string, in Intent) error. // Why: keeps behavior explicit so startup/shutdown workflows remain maintainable as services evolve. func WriteIntent(path string, in Intent) error { if in.UpdatedAt.IsZero() { in.UpdatedAt = time.Now().UTC() } if err := os.MkdirAll(filepath.Dir(path), 0o750); err != nil { return err } b, _ := json.MarshalIndent(in, "", " ") return os.WriteFile(path, b, 0o640) } // MustWriteIntent runs one orchestration or CLI step. // Signature: MustWriteIntent(path string, state string, reason string, source string) error. // Why: keeps behavior explicit so startup/shutdown workflows remain maintainable as services evolve. func MustWriteIntent(path string, state string, reason string, source string) error { switch state { case IntentNormal, IntentStartupInProgress, IntentShuttingDown, IntentShutdownComplete: default: return fmt.Errorf("invalid intent state: %s", state) } return WriteIntent(path, Intent{ State: state, Reason: reason, Source: source, UpdatedAt: time.Now().UTC(), }) }