Files
Codex 82e2c91f42
Publish documentation / publish (push) Successful in 1m27s
feat: implement memory and evidence administration with guided repairs
Add PostgreSQL-backed memory, editable evidence with source review and activation, and human-approved archive repairs across the harness, API, and UI. Include migrations, deployment support, regression coverage, and validation documentation.

Refresh permissions from validated session roles so existing administrator logins can access newly deployed archive management features.
2026-09-10 10:31:34 +02:00

193 lines
6.4 KiB
Go

package workspaceops
import (
"context"
"encoding/json"
"io"
"os"
"path/filepath"
"reflect"
"strings"
"testing"
"github.com/aritmolab/thothii/tools/tht/internal/compose"
"github.com/aritmolab/thothii/tools/tht/internal/config"
)
type fakeRunner struct {
run func(args []string, stdin string) (compose.Result, error)
all [][]string
stdins []string
}
func TestEvidenceConsolidationUsesClosedMaintenanceContract(t *testing.T) {
request, err := Parse([]string{"evidence", "consolidate", "--workspace", "sales", "--json"})
if err != nil {
t.Fatal(err)
}
if request.operatorCommand() != "evidence-consolidate" || !request.JSONMode() {
t.Fatal("incorrect command")
}
envelope, err := request.stdinEnvelope()
if err != nil || envelope.WorkspaceID != "sales" {
t.Fatal("incorrect workspace envelope")
}
if _, err := Parse([]string{"evidence", "consolidate", "--workspace", "../foreign"}); err == nil {
t.Fatal("accepted an unsafe workspace")
}
}
func (r *fakeRunner) Run(_ context.Context, args []string, stdin io.Reader) (compose.Result, error) {
payload := ""
if stdin != nil {
contents, err := io.ReadAll(stdin)
if err != nil {
return compose.Result{}, err
}
payload = string(contents)
}
r.all = append(r.all, append([]string(nil), args...))
r.stdins = append(r.stdins, payload)
return r.run(args, payload)
}
func TestExecuteRunSendsOnlyTheOneShotEnvelope(t *testing.T) {
installation := testInstallation(t)
runner := &fakeRunner{run: func(args []string, stdin string) (compose.Result, error) {
switch {
case contains(args, "config", "--format", "json"):
return compose.Result{Stdout: `{"services":{"core":{"image":"thothii-core:local"}}}`}, nil
case reflect.DeepEqual(args, []string{"image", "inspect", "--format", "{{.Id}}", "thothii-core:local"}):
return compose.Result{Stdout: "sha256:" + strings.Repeat("a", 64)}, nil
case contains(args, "workspace-maintenance", "preprocess-run"):
var envelope map[string]any
if err := json.Unmarshal([]byte(stdin), &envelope); err != nil {
t.Fatalf("stdin JSON = %q, err=%v", stdin, err)
}
if !reflect.DeepEqual(envelope, map[string]any{
"schemaVersion": float64(1),
"workspaceId": "abc",
}) {
t.Fatalf("unexpected envelope: %#v", envelope)
}
return compose.Result{Stdout: successResult("preprocess run")}, nil
default:
t.Fatalf("unexpected Docker invocation: %#v", args)
return compose.Result{}, nil
}
}}
result, err := Execute(
context.Background(),
installation,
runner,
RunRequest{baseRequest: baseRequest{Workspace: "abc", JSON: true}},
)
if err != nil {
t.Fatalf("Execute() error = %v", err)
}
if result.Status != "succeeded" || result.Operation != "preprocess run" {
t.Fatalf("unexpected result: %#v", result)
}
}
func TestParseAcceptsOnlyInspectAndCompletePreprocessing(t *testing.T) {
for _, args := range [][]string{
{"inspect", "--workspace", "abc", "--json"},
{"preprocess", "run", "--workspace", "abc", "--json"},
{"preprocess", "clear", "--workspace", "abc", "--json"},
} {
if _, err := Parse(args); err != nil {
t.Fatalf("Parse(%v) error = %v", args, err)
}
}
retired := [][]string{
{"preprocess", "dwh", "--workspace", "abc"},
{"preprocess", "evidence", "--workspace", "abc"},
{"preprocess", "run", "--workspace", "abc", "--resume", strings.Repeat("a", 32)},
{"preprocess", "run", "--workspace", "abc", "--dry-run"},
{"schema", "suggest-fks", "--workspace", "abc"},
{"schema", "check", "--workspace", "abc"},
{"schema", "accept", "--workspace", "abc"},
{"index-schema", "--workspace", "abc"},
{"vector", "inspect", "--workspace", "abc"},
{"vector", "rebuild", "--workspace", "abc"},
}
for _, args := range retired {
if _, err := Parse(args); err == nil {
t.Fatalf("retired command accepted: %v", args)
}
}
}
func testInstallation(t *testing.T) config.Installation {
t.Helper()
temporaryRoot, err := filepath.EvalSymlinks(os.TempDir())
if err != nil {
t.Fatal(err)
}
root, err := os.MkdirTemp(temporaryRoot, "tht-workspaceops-")
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { _ = os.RemoveAll(root) })
project := filepath.Join(root, "project")
if err := os.MkdirAll(filepath.Join(project, "deploy"), 0o700); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(project, "compose.yaml"), []byte("services: {}\n"), 0o600); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(project, "deploy", "compose.local.yaml"), []byte("services: {}\n"), 0o600); err != nil {
t.Fatal(err)
}
envFile := filepath.Join(root, "installation.env")
if err := os.WriteFile(envFile, []byte("SAFE_VALUE=1\n"), 0o600); err != nil {
t.Fatal(err)
}
return config.Installation{
Path: filepath.Join(root, "thothii-installation.yaml"), Profile: "local",
ProjectDirectory: project, EnvFile: envFile,
}
}
func contains(values []string, sequence ...string) bool {
for start := range values {
if start+len(sequence) <= len(values) && reflect.DeepEqual(values[start:start+len(sequence)], sequence) {
return true
}
}
return false
}
func successResult(operation string) string {
return `{"schemaVersion":1,"status":"succeeded","code":"ok","workspaceId":"abc","workspaceRevision":"1234567890abcdef1234567890abcdef12345678","descriptorBlob":"sha256:` + strings.Repeat("f", 64) + `","operation":"` + operation + `","completedStages":[]}`
}
func TestEvidenceSourceCommandsAreClosedAndCarryReviewIdentity(t *testing.T) {
request, err := Parse([]string{"evidence", "refresh", "--workspace", "sales", "--json"})
if err != nil || request.operatorCommand() != "evidence-refresh" {
t.Fatalf("refresh: %v", err)
}
args := []string{"evidence", "decide", "--workspace", "sales", "--source-id", strings.Repeat("a", 64), "--revision", strings.Repeat("b", 64), "--decision", "keep"}
request, err = Parse(args)
if err != nil {
t.Fatal(err)
}
envelope, _ := request.stdinEnvelope()
if envelope.SourceID != strings.Repeat("a", 64) || envelope.Decision != "keep" || request.operatorCommand() != "evidence-decide" {
t.Fatal("review identity lost")
}
for _, invalid := range [][]string{
{"evidence", "refresh", "--workspace", "sales", "--url", "http://localhost"},
{"evidence", "decide", "--workspace", "sales"},
append(append([]string{}, args...), "--decision", "replace"),
{"evidence", "consolidate", "--workspace", "sales", "--source-id", strings.Repeat("a", 64)},
} {
if _, err := Parse(invalid); err == nil {
t.Fatalf("accepted invalid flags: %v", invalid)
}
}
}