Implemented confi snapshots
This commit is contained in:
@@ -6,6 +6,7 @@ import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
"net"
|
||||
@@ -105,6 +106,11 @@ func (h *Handler) Register(mux *http.ServeMux, prefix string) {
|
||||
mux.HandleFunc("GET "+prefix+"/synthetic/{name}", h.getSynthetic)
|
||||
mux.HandleFunc("PUT "+prefix+"/synthetic/{name}", h.updateSynthetic)
|
||||
mux.HandleFunc("DELETE "+prefix+"/synthetic/{name}", h.deleteSynthetic)
|
||||
// Synthetic signal git-style versioning (list / view / promote / fork).
|
||||
mux.HandleFunc("GET "+prefix+"/synthetic/{name}/versions", h.listSyntheticVersions)
|
||||
mux.HandleFunc("GET "+prefix+"/synthetic/{name}/versions/{version}", h.getSyntheticVersion)
|
||||
mux.HandleFunc("POST "+prefix+"/synthetic/{name}/versions/{version}/promote", h.promoteSyntheticVersion)
|
||||
mux.HandleFunc("POST "+prefix+"/synthetic/{name}/versions/{version}/fork", h.forkSyntheticVersion)
|
||||
// Server-side control logic CRUD (mutations are write-gated by the access
|
||||
// middleware; each mutation reloads the running engine).
|
||||
mux.HandleFunc("GET "+prefix+"/controllogic", h.listControlLogic)
|
||||
@@ -112,6 +118,11 @@ func (h *Handler) Register(mux *http.ServeMux, prefix string) {
|
||||
mux.HandleFunc("GET "+prefix+"/controllogic/{id}", h.getControlLogic)
|
||||
mux.HandleFunc("PUT "+prefix+"/controllogic/{id}", h.updateControlLogic)
|
||||
mux.HandleFunc("DELETE "+prefix+"/controllogic/{id}", h.deleteControlLogic)
|
||||
// Control-logic git-style versioning (list / view / promote / fork).
|
||||
mux.HandleFunc("GET "+prefix+"/controllogic/{id}/versions", h.listControlLogicVersions)
|
||||
mux.HandleFunc("GET "+prefix+"/controllogic/{id}/versions/{version}", h.getControlLogicVersion)
|
||||
mux.HandleFunc("POST "+prefix+"/controllogic/{id}/versions/{version}/promote", h.promoteControlLogicVersion)
|
||||
mux.HandleFunc("POST "+prefix+"/controllogic/{id}/versions/{version}/fork", h.forkControlLogicVersion)
|
||||
// Configuration manager — config sets (schemas) and instances (values),
|
||||
// both versioned git-style. Mutations are write-gated by the access
|
||||
// middleware; "apply" writes each value to its target signal.
|
||||
@@ -124,6 +135,7 @@ func (h *Handler) Register(mux *http.ServeMux, prefix string) {
|
||||
mux.HandleFunc("GET "+prefix+"/config/sets/{id}/versions/{version}", h.getConfigSetVersion)
|
||||
mux.HandleFunc("POST "+prefix+"/config/sets/{id}/versions/{version}/promote", h.promoteConfigSet)
|
||||
mux.HandleFunc("POST "+prefix+"/config/sets/{id}/versions/{version}/fork", h.forkConfigSet)
|
||||
mux.HandleFunc("POST "+prefix+"/config/sets/{id}/snapshot", h.snapshotConfigSet)
|
||||
mux.HandleFunc("GET "+prefix+"/config/sets/diff", h.diffConfigSets)
|
||||
mux.HandleFunc("GET "+prefix+"/config/instances", h.listConfigInstances)
|
||||
mux.HandleFunc("POST "+prefix+"/config/instances", h.createConfigInstance)
|
||||
@@ -135,7 +147,22 @@ func (h *Handler) Register(mux *http.ServeMux, prefix string) {
|
||||
mux.HandleFunc("POST "+prefix+"/config/instances/{id}/versions/{version}/promote", h.promoteConfigInstance)
|
||||
mux.HandleFunc("POST "+prefix+"/config/instances/{id}/versions/{version}/fork", h.forkConfigInstance)
|
||||
mux.HandleFunc("POST "+prefix+"/config/instances/{id}/apply", h.applyConfigInstance)
|
||||
mux.HandleFunc("POST "+prefix+"/config/instances/{id}/validate", h.validateConfigInstance)
|
||||
mux.HandleFunc("GET "+prefix+"/config/instances/{id}/livediff", h.diffConfigInstanceLive)
|
||||
mux.HandleFunc("GET "+prefix+"/config/instances/diff", h.diffConfigInstances)
|
||||
// Config rules — CUE validation/transformation logic bound to a set, run
|
||||
// when instances of that set are saved. Versioned git-style; "check"
|
||||
// evaluates an unsaved source for the live editor.
|
||||
mux.HandleFunc("GET "+prefix+"/config/rules", h.listConfigRules)
|
||||
mux.HandleFunc("POST "+prefix+"/config/rules", h.createConfigRule)
|
||||
mux.HandleFunc("POST "+prefix+"/config/rules/check", h.checkConfigRule)
|
||||
mux.HandleFunc("GET "+prefix+"/config/rules/{id}", h.getConfigRule)
|
||||
mux.HandleFunc("PUT "+prefix+"/config/rules/{id}", h.updateConfigRule)
|
||||
mux.HandleFunc("DELETE "+prefix+"/config/rules/{id}", h.deleteConfigRule)
|
||||
mux.HandleFunc("GET "+prefix+"/config/rules/{id}/versions", h.listConfigRuleVersions)
|
||||
mux.HandleFunc("GET "+prefix+"/config/rules/{id}/versions/{version}", h.getConfigRuleVersion)
|
||||
mux.HandleFunc("POST "+prefix+"/config/rules/{id}/versions/{version}/promote", h.promoteConfigRule)
|
||||
mux.HandleFunc("POST "+prefix+"/config/rules/{id}/versions/{version}/fork", h.forkConfigRule)
|
||||
}
|
||||
|
||||
// ── /me ─────────────────────────────────────────────────────────────────────
|
||||
@@ -1194,6 +1221,80 @@ func (h *Handler) deleteSynthetic(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
func (h *Handler) listSyntheticVersions(w http.ResponseWriter, r *http.Request) {
|
||||
if h.synthetic == nil {
|
||||
jsonError(w, http.StatusServiceUnavailable, "synthetic data source not enabled")
|
||||
return
|
||||
}
|
||||
name := r.PathValue("name")
|
||||
versions, err := h.synthetic.Versions(name)
|
||||
if err != nil {
|
||||
jsonError(w, http.StatusNotFound, "synthetic signal not found: "+name)
|
||||
return
|
||||
}
|
||||
jsonOK(w, versions)
|
||||
}
|
||||
|
||||
func (h *Handler) getSyntheticVersion(w http.ResponseWriter, r *http.Request) {
|
||||
if h.synthetic == nil {
|
||||
jsonError(w, http.StatusServiceUnavailable, "synthetic data source not enabled")
|
||||
return
|
||||
}
|
||||
name := r.PathValue("name")
|
||||
version, err := strconv.Atoi(r.PathValue("version"))
|
||||
if err != nil {
|
||||
jsonError(w, http.StatusBadRequest, "invalid version: "+r.PathValue("version"))
|
||||
return
|
||||
}
|
||||
def, err := h.synthetic.GetVersion(name, version)
|
||||
if err != nil {
|
||||
jsonError(w, http.StatusNotFound, "version not found")
|
||||
return
|
||||
}
|
||||
jsonOK(w, def)
|
||||
}
|
||||
|
||||
func (h *Handler) promoteSyntheticVersion(w http.ResponseWriter, r *http.Request) {
|
||||
if h.synthetic == nil {
|
||||
jsonError(w, http.StatusServiceUnavailable, "synthetic data source not enabled")
|
||||
return
|
||||
}
|
||||
name := r.PathValue("name")
|
||||
version, err := strconv.Atoi(r.PathValue("version"))
|
||||
if err != nil {
|
||||
jsonError(w, http.StatusBadRequest, "invalid version: "+r.PathValue("version"))
|
||||
return
|
||||
}
|
||||
def, err := h.synthetic.PromoteVersion(name, version)
|
||||
if err != nil {
|
||||
jsonError(w, http.StatusNotFound, "version not found")
|
||||
return
|
||||
}
|
||||
h.recordMutation(r, "synthetic.promote", fmt.Sprintf("%s v%d", name, version))
|
||||
jsonOK(w, def)
|
||||
}
|
||||
|
||||
func (h *Handler) forkSyntheticVersion(w http.ResponseWriter, r *http.Request) {
|
||||
if h.synthetic == nil {
|
||||
jsonError(w, http.StatusServiceUnavailable, "synthetic data source not enabled")
|
||||
return
|
||||
}
|
||||
name := r.PathValue("name")
|
||||
version, err := strconv.Atoi(r.PathValue("version"))
|
||||
if err != nil {
|
||||
jsonError(w, http.StatusBadRequest, "invalid version: "+r.PathValue("version"))
|
||||
return
|
||||
}
|
||||
def, err := h.synthetic.ForkVersion(name, version)
|
||||
if err != nil {
|
||||
jsonError(w, http.StatusNotFound, "version not found")
|
||||
return
|
||||
}
|
||||
h.recordMutation(r, "synthetic.fork", fmt.Sprintf("%s v%d -> %s", name, version, def.Name))
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
jsonOK(w, map[string]string{"id": def.Name})
|
||||
}
|
||||
|
||||
// ── Control logic ──────────────────────────────────────────────────────────────
|
||||
|
||||
func (h *Handler) listControlLogic(w http.ResponseWriter, _ *http.Request) {
|
||||
@@ -1294,6 +1395,88 @@ func (h *Handler) deleteControlLogic(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
func (h *Handler) listControlLogicVersions(w http.ResponseWriter, r *http.Request) {
|
||||
if h.ctrlLogic == nil {
|
||||
jsonError(w, http.StatusServiceUnavailable, "control logic not enabled")
|
||||
return
|
||||
}
|
||||
versions, err := h.ctrlLogic.Versions(r.PathValue("id"))
|
||||
if err != nil {
|
||||
jsonError(w, http.StatusNotFound, "control logic graph not found: "+r.PathValue("id"))
|
||||
return
|
||||
}
|
||||
jsonOK(w, versions)
|
||||
}
|
||||
|
||||
func (h *Handler) getControlLogicVersion(w http.ResponseWriter, r *http.Request) {
|
||||
if h.ctrlLogic == nil {
|
||||
jsonError(w, http.StatusServiceUnavailable, "control logic not enabled")
|
||||
return
|
||||
}
|
||||
version, err := strconv.Atoi(r.PathValue("version"))
|
||||
if err != nil {
|
||||
jsonError(w, http.StatusBadRequest, "invalid version: "+r.PathValue("version"))
|
||||
return
|
||||
}
|
||||
g, err := h.ctrlLogic.GetVersion(r.PathValue("id"), version)
|
||||
if err != nil {
|
||||
jsonError(w, http.StatusNotFound, "version not found")
|
||||
return
|
||||
}
|
||||
jsonOK(w, g)
|
||||
}
|
||||
|
||||
func (h *Handler) promoteControlLogicVersion(w http.ResponseWriter, r *http.Request) {
|
||||
if h.ctrlLogic == nil {
|
||||
jsonError(w, http.StatusServiceUnavailable, "control logic not enabled")
|
||||
return
|
||||
}
|
||||
if !h.policy.CanEditLogic(caller(r)) {
|
||||
jsonError(w, http.StatusForbidden, "you are not permitted to edit logic")
|
||||
return
|
||||
}
|
||||
id := r.PathValue("id")
|
||||
version, err := strconv.Atoi(r.PathValue("version"))
|
||||
if err != nil {
|
||||
jsonError(w, http.StatusBadRequest, "invalid version: "+r.PathValue("version"))
|
||||
return
|
||||
}
|
||||
g, err := h.ctrlLogic.Promote(id, version)
|
||||
if err != nil {
|
||||
jsonError(w, http.StatusNotFound, "version not found")
|
||||
return
|
||||
}
|
||||
h.ctrlEngine.Reload()
|
||||
h.recordMutation(r, "controllogic.promote", fmt.Sprintf("%s v%d", id, version))
|
||||
jsonOK(w, g)
|
||||
}
|
||||
|
||||
func (h *Handler) forkControlLogicVersion(w http.ResponseWriter, r *http.Request) {
|
||||
if h.ctrlLogic == nil {
|
||||
jsonError(w, http.StatusServiceUnavailable, "control logic not enabled")
|
||||
return
|
||||
}
|
||||
if !h.policy.CanEditLogic(caller(r)) {
|
||||
jsonError(w, http.StatusForbidden, "you are not permitted to edit logic")
|
||||
return
|
||||
}
|
||||
id := r.PathValue("id")
|
||||
version, err := strconv.Atoi(r.PathValue("version"))
|
||||
if err != nil {
|
||||
jsonError(w, http.StatusBadRequest, "invalid version: "+r.PathValue("version"))
|
||||
return
|
||||
}
|
||||
g, err := h.ctrlLogic.Fork(id, version)
|
||||
if err != nil {
|
||||
jsonError(w, http.StatusNotFound, "version not found")
|
||||
return
|
||||
}
|
||||
h.ctrlEngine.Reload()
|
||||
h.recordMutation(r, "controllogic.fork", fmt.Sprintf("%s v%d -> %s", id, version, g.ID))
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
jsonOK(w, map[string]string{"id": g.ID})
|
||||
}
|
||||
|
||||
// ── Helpers ───────────────────────────────────────────────────────────────────
|
||||
|
||||
// extractLogicBlock returns the verbatim <logic>…</logic> section of an
|
||||
|
||||
Reference in New Issue
Block a user