fluid-core/cmd/fluid/main.go
tegwick d52dcc92a9
Some checks failed
ci / build (push) Has been cancelled
Add evidence store, intent store and the fluid CLI
Completes FLUID-WP-0003. The evidence store is append-only at the
database, not by convention in Go: a trigger blocks UPDATE and DELETE on
fluid_events in both SQLite and Postgres, so the guarantee binds a psql
session and the CLI equally, not just callers who go through the Go API.
Records are derived summary state and may be superseded; the event log
stays the authority.

The intent store makes a recorded version immutable and content
addressed, since reassigning what governed a revision after the fact
would break the one audit question Blueprint 27 exists to answer. The
unfilled InterfaceEvolutionIntent template is rejected rather than
defaulted, because a template still listing every FLUID-N mode has not
been completed and defaulting it would pick a permissive authority by
accident.

The CLI reads the evidence store directly rather than through the
control-plane API, so an operator can reconstruct history when the
control plane is down.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014KmVxhJ35tCo7rE7UnLwWu

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 1116572@bnt-lap001
Assistant-Session: 8ba9bb93-a72a-4883-b189-2499cce5c400
2026-09-04 02:26:23 +02:00

144 lines
3.4 KiB
Go

// Command fluid is the FLUID operator CLI.
//
// It reads and writes the evidence store directly rather than going through the
// control-plane API, so that an operator can still inspect an interface's
// history when the control plane is down. Auditability must not depend on the
// availability of the thing being audited.
package main
import (
"context"
"errors"
"flag"
"fmt"
"os"
"path/filepath"
"strings"
)
const usage = `fluid — FLUID operator CLI
Usage:
fluid [--store PATH] [--interface ID] <command> [arguments]
Commands:
intent put Record an interface evolution intent version
intent activate Make a recorded version the governing intent
intent show Show the active or a named intent version
revision publish Publish a signed revision descriptor
revision list List published revisions
revision show Show one revision descriptor
policy put Install a routing policy
policy show Show the current routing policy
events Show audit events
telemetry Show recorded telemetry
audit trace Reconstruct the history behind a revision
Global flags:
--store PATH Evidence store path (default $FLUID_STORE or ./fluid.db)
--interface ID Interface identifier (default $FLUID_INTERFACE)
`
func main() {
if err := run(os.Args[1:]); err != nil {
if errors.Is(err, flag.ErrHelp) {
os.Exit(0)
}
fmt.Fprintln(os.Stderr, "fluid:", err)
os.Exit(1)
}
}
type globals struct {
store string
iface string
remaining []string
}
func parseGlobals(args []string) (globals, error) {
g := globals{
store: envOr("FLUID_STORE", "fluid.db"),
iface: os.Getenv("FLUID_INTERFACE"),
}
i := 0
for i < len(args) {
switch args[i] {
case "--store":
if i+1 >= len(args) {
return g, errors.New("--store needs a value")
}
g.store, i = args[i+1], i+2
case "--interface":
if i+1 >= len(args) {
return g, errors.New("--interface needs a value")
}
g.iface, i = args[i+1], i+2
case "-h", "--help", "help":
fmt.Print(usage)
return g, flag.ErrHelp
default:
if strings.HasPrefix(args[i], "--") {
return g, fmt.Errorf("unknown global flag %q", args[i])
}
g.remaining = args[i:]
return g, nil
}
}
g.remaining = nil
return g, nil
}
func run(args []string) error {
g, err := parseGlobals(args)
if err != nil {
return err
}
if len(g.remaining) == 0 {
fmt.Print(usage)
return nil
}
ctx := context.Background()
cmd, rest := g.remaining[0], g.remaining[1:]
switch cmd {
case "intent":
return runIntent(ctx, g, rest)
case "revision":
return runRevision(ctx, g, rest)
case "policy":
return runPolicy(ctx, g, rest)
case "events":
return runEvents(ctx, g, rest)
case "telemetry":
return runTelemetry(ctx, g, rest)
case "audit":
return runAudit(ctx, g, rest)
default:
return fmt.Errorf("unknown command %q (try `fluid help`)", cmd)
}
}
func envOr(key, fallback string) string {
if v := os.Getenv(key); v != "" {
return v
}
return fallback
}
// requireInterface fails clearly rather than silently operating on the wrong
// interface's history.
func (g globals) requireInterface() (string, error) {
if g.iface == "" {
return "", errors.New("no interface selected: pass --interface or set FLUID_INTERFACE")
}
return g.iface, nil
}
func (g globals) storePath() string {
if abs, err := filepath.Abs(g.store); err == nil {
return abs
}
return g.store
}