Add evidence store, intent store and the fluid CLI
Some checks failed
ci / build (push) Has been cancelled
Some checks failed
ci / build (push) Has been cancelled
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
This commit is contained in:
parent
791e419973
commit
d52dcc92a9
11 changed files with 1836 additions and 3 deletions
166
internal/intent/store_test.go
Normal file
166
internal/intent/store_test.go
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
package intent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
_ "modernc.org/sqlite"
|
||||
|
||||
"github.com/tegwick/fluid-core/internal/contract"
|
||||
"github.com/tegwick/fluid-core/internal/evidence"
|
||||
)
|
||||
|
||||
func newStore(t *testing.T) *Store {
|
||||
t.Helper()
|
||||
ev, err := evidence.OpenSQLite(context.Background(), filepath.Join(t.TempDir(), "e.db"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Cleanup(func() { _ = ev.Close() })
|
||||
return New(ev, "hall-publishing")
|
||||
}
|
||||
|
||||
const filledIntent = `# Interface Evolution Intent
|
||||
|
||||
**Current operational authority mode:**
|
||||
FLUID-2
|
||||
|
||||
## Mission
|
||||
Publish hall-of-helix entries to a Telegram channel.
|
||||
`
|
||||
|
||||
func TestPutIsIdempotentAndImmutable(t *testing.T) {
|
||||
s := newStore(t)
|
||||
ctx := context.Background()
|
||||
|
||||
first, err := s.Put(ctx, "IEI-1", filledIntent)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if first.Mode != ModeAdvisory {
|
||||
t.Errorf("mode = %s, want FLUID-2", first.Mode)
|
||||
}
|
||||
|
||||
// Recording the same content again must be a no-op, so startup is safe to
|
||||
// repeat.
|
||||
again, err := s.Put(ctx, "IEI-1", filledIntent)
|
||||
if err != nil {
|
||||
t.Fatalf("re-recording identical intent failed: %v", err)
|
||||
}
|
||||
if again.Digest != first.Digest {
|
||||
t.Error("identical documents produced different digests")
|
||||
}
|
||||
|
||||
// Changing what a version says, after revisions may already be bound to it,
|
||||
// would break the audit question the store exists to answer.
|
||||
changed := strings.Replace(filledIntent, "FLUID-2", "FLUID-5", 1)
|
||||
if _, err := s.Put(ctx, "IEI-1", changed); !errors.Is(err, ErrImmutable) {
|
||||
t.Errorf("mutating a recorded version returned %v, want ErrImmutable", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseModeRejectsUnresolvedTemplate(t *testing.T) {
|
||||
// The shipped template lists every mode as alternatives. Accepting that and
|
||||
// defaulting to something permissive is exactly the wrong failure.
|
||||
template, err := os.ReadFile(filepath.Join("..", "..", "spec", "InterfaceEvolutionIntent.md"))
|
||||
if err != nil {
|
||||
t.Skipf("template not readable: %v", err)
|
||||
}
|
||||
if _, err := ParseMode(string(template)); err == nil {
|
||||
t.Error("the unfilled template was accepted as a governing intent")
|
||||
}
|
||||
|
||||
if _, err := ParseMode("no mode declared here"); err == nil {
|
||||
t.Error("a document with no authority mode was accepted")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthorityModeOrdering(t *testing.T) {
|
||||
if !ModeExperimental.Allows(ModeAdvisory) {
|
||||
t.Error("FLUID-4 should permit advisory authority")
|
||||
}
|
||||
if ModeAdvisory.Allows(ModeExperimental) {
|
||||
t.Error("FLUID-2 must not permit experimental authority")
|
||||
}
|
||||
if got := ModeBoundedAutonomous.String(); got != "FLUID-5" {
|
||||
t.Errorf("String() = %s", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestActiveIntent(t *testing.T) {
|
||||
s := newStore(t)
|
||||
ctx := context.Background()
|
||||
|
||||
if _, err := s.Active(ctx); !errors.Is(err, ErrNoActive) {
|
||||
t.Errorf("Active with nothing recorded returned %v, want ErrNoActive", err)
|
||||
}
|
||||
|
||||
if _, err := s.Put(ctx, "IEI-1", filledIntent); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := s.SetActive(ctx, "IEI-1"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
active, err := s.Active(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if active.Version != "IEI-1" {
|
||||
t.Errorf("active = %s, want IEI-1", active.Version)
|
||||
}
|
||||
|
||||
if err := s.SetActive(ctx, "IEI-missing"); err == nil {
|
||||
t.Error("activating an unrecorded version succeeded")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBindingIsRecordedAndFirstWins(t *testing.T) {
|
||||
s := newStore(t)
|
||||
ctx := context.Background()
|
||||
|
||||
if _, err := s.Put(ctx, "IEI-1", filledIntent); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
v2 := strings.Replace(filledIntent, "FLUID-2", "FLUID-4", 1)
|
||||
if _, err := s.Put(ctx, "IEI-2", v2); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
rev := contract.RevisionID("R-1")
|
||||
if err := s.Bind(ctx, rev, "IEI-1"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
got, err := s.GoverningIntent(ctx, rev)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got.Version != "IEI-1" {
|
||||
t.Errorf("governing intent = %s, want IEI-1", got.Version)
|
||||
}
|
||||
|
||||
// A later reassignment is recorded in the audit trail but must not silently
|
||||
// become the answer to "what governed this revision".
|
||||
if err := s.Bind(ctx, rev, "IEI-2"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got, err = s.GoverningIntent(ctx, rev)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got.Version != "IEI-1" {
|
||||
t.Errorf("a later binding overrode the original: got %s", got.Version)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnboundRevisionHasNoIntent(t *testing.T) {
|
||||
s := newStore(t)
|
||||
if _, err := s.GoverningIntent(context.Background(), "R-unbound"); !errors.Is(err, ErrNotFound) {
|
||||
t.Errorf("got %v, want ErrNotFound", err)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue