Why: Account disable could be followed by late Backend business writes, API key creation could implicitly create users, account bootstrap locks could invert, and directory cursors were process-local or leaked out-of-scope changes. Evidence: Local go test ./... and isolated PostgreSQL package suites passed on the staged candidate; exact committed-head SELF replay and stage 6 human review remain pending. Impact: Adds final account transactions and external-effect checks across HTTP, Host, Bus and run manager; tightens API key issuance and persists versioned cursor signing. Delegated RDM rename still needs the separately proposed real-operator contract, so A1 and stage 6 are not complete. Refs: Documents U3 r1, approved A1-A4 amendment fb3a821, pending delegated-projection amendment 5783940, Dev feature/iam-u3-account-directory-recovery.
84 lines
3.1 KiB
Go
84 lines
3.1 KiB
Go
package rag
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
|
|
"agent-system-backend/internal/store"
|
|
core "agent-system-core"
|
|
)
|
|
|
|
type checkedKnowledgeIndexer interface {
|
|
IndexDocumentAccountChecked(ctx context.Context, owner, projectID core.ID, source, title, ref, plainText string, allowImplicitDefault bool) error
|
|
IndexChunksAccountChecked(ctx context.Context, owner core.ID, docs []core.KnowledgeDoc, allowImplicitDefault bool) error
|
|
PublishChunksAccountChecked(ctx context.Context, owner core.ID, pluginID string, scoped bool, docs []core.KnowledgeDoc, allowImplicitDefault bool) error
|
|
}
|
|
|
|
func checkedIndexer(t *testing.T, p *Pipeline) checkedKnowledgeIndexer {
|
|
t.Helper()
|
|
i, ok := any(p).(checkedKnowledgeIndexer)
|
|
if !ok {
|
|
t.Fatal("Pipeline is missing account-checked indexing APIs")
|
|
}
|
|
return i
|
|
}
|
|
|
|
type disableDuringEmbed struct{ st *store.Store }
|
|
|
|
func (e disableDuringEmbed) Embed(_ context.Context, texts []string) ([][]float32, error) {
|
|
if _, err := e.st.DB().Exec(`UPDATE users SET enabled=FALSE WHERE owner='alice'`); err != nil {
|
|
return nil, err
|
|
}
|
|
vecs := make([][]float32, len(texts))
|
|
for i := range vecs {
|
|
vecs[i] = []float32{1, 0}
|
|
}
|
|
return vecs, nil
|
|
}
|
|
|
|
func TestCheckedIndexStopsVectorWriteAfterEmbeddingDisablesAccount(t *testing.T) {
|
|
p, st := newTestPipeline(t, nil, nil)
|
|
i := checkedIndexer(t, p)
|
|
if err := st.EnsureUser("alice", ""); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
p.SetEmbedder(disableDuringEmbed{st: st})
|
|
err := i.IndexChunksAccountChecked(context.Background(), "alice", []core.KnowledgeDoc{
|
|
{ID: "knw-during-embed", Owner: "alice", Ref: "embedded", Source: "manual", Chunk: "A searchable document"},
|
|
}, false)
|
|
if !errors.Is(err, store.ErrAccountForbidden) {
|
|
t.Fatalf("vector write after disable: got %v, want ErrAccountForbidden", err)
|
|
}
|
|
var lexical, vectors int
|
|
if err := st.DB().QueryRow(`SELECT COUNT(*) FROM knowledge WHERE owner='alice' AND ref='embedded'`).Scan(&lexical); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := st.DB().QueryRow(`SELECT COUNT(*) FROM knowledge_vectors WHERE owner='alice' AND ref='embedded'`).Scan(&vectors); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if lexical != 1 || vectors != 0 {
|
|
t.Fatalf("expected pre-disable lexical commit and no late vector: lexical=%d vectors=%d", lexical, vectors)
|
|
}
|
|
}
|
|
|
|
func TestCheckedIndexDocumentAndPublishUseGuardedKnowledge(t *testing.T) {
|
|
p, st := newTestPipeline(t, nil, nil)
|
|
i := checkedIndexer(t, p)
|
|
if err := st.EnsureUser("alice", ""); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := i.IndexDocumentAccountChecked(context.Background(), "alice", "p1", "upload", "minutes", "upload-ref", "A document with enough searchable text", false); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := i.PublishChunksAccountChecked(context.Background(), "alice", "alpha", true, []core.KnowledgeDoc{
|
|
{ID: "knw-published", Owner: "alice", ProjectID: "p1", Ref: "published", Source: "plugin:alpha", Chunk: "A published document"},
|
|
}, false); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var n int
|
|
if err := st.DB().QueryRow(`SELECT COUNT(*) FROM knowledge WHERE owner='alice'`).Scan(&n); err != nil || n != 2 {
|
|
t.Fatalf("guarded index and publish rows=%d, %v", n, err)
|
|
}
|
|
}
|