Why: - Independent review found trusted-boundary, semantic parity, replay-budget, comparison, persistence, cancellation, provenance, and routing gaps. - Storage migration must include the new durable delivery table. Evidence: - go test -count=1 ./... - go test -race ./internal/docgen ./internal/tools ./internal/runmgr - go test ./... (deps/agent-document-renderer) - go vet ./... - go build ./... - CGO_ENABLED=0 go build -trimpath ./cmd/agent-system - Python house-style unittest suite and git diff --check Impact: - Word delivery now uses host-owned freezes, Markdown-first resumable persistence, closed registration boundaries, semantic shared rendering, and real comparison. - Controlled/project documents still fail closed to RDM and non-Word flows remain unchanged. Refs: - Unified Document Renderer implementation plan Task 10 - /tmp/task10-backend-review-last.md - Renderer d5cd8c126045a27d5a5cc5da23f64d3c737e116a
230 lines
10 KiB
Go
230 lines
10 KiB
Go
package store
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"errors"
|
|
"path/filepath"
|
|
"reflect"
|
|
"testing"
|
|
|
|
core "agent-system-core"
|
|
)
|
|
|
|
func TestDocumentDeliveryFreezesOnceAndRejectsOperationConflict(t *testing.T) {
|
|
st := openTestStore(t)
|
|
conversation := seedConversation(t, st)
|
|
input := deliveryBeginFixture(conversation, "operation-freeze")
|
|
|
|
first, err := st.BeginDocumentDeliveryAccountChecked(OwnerDefault, input, true)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
second, err := st.BeginDocumentDeliveryAccountChecked(OwnerDefault, input, true)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if first.FrozenAt.IsZero() || !first.FrozenAt.Equal(second.FrozenAt) || first.State != DocumentDeliveryFrozen {
|
|
t.Fatalf("freeze records differ: first=%#v second=%#v", first, second)
|
|
}
|
|
|
|
conflict := input
|
|
conflict.RequestSHA256 = hashText("different request")
|
|
if _, err := st.BeginDocumentDeliveryAccountChecked(OwnerDefault, conflict, true); !errors.Is(err, ErrDocumentDeliveryConflict) {
|
|
t.Fatalf("conflicting operation error = %v, want ErrDocumentDeliveryConflict", err)
|
|
}
|
|
}
|
|
|
|
func TestDocumentDeliveryPersistsMarkdownFirstAndCommitsDerivedSetAtomically(t *testing.T) {
|
|
st := openTestStore(t)
|
|
conversation := seedConversation(t, st)
|
|
input := deliveryBeginFixture(conversation, "operation-complete")
|
|
record, err := st.BeginDocumentDeliveryAccountChecked(OwnerDefault, input, true)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
markdown := deliveryArtifactFixture(DocumentArtifactMarkdown, "report.md", "text/markdown; charset=utf-8", []byte("# report\n"))
|
|
record, err = st.PersistDocumentDeliveryMarkdownAccountChecked(OwnerDefault, input.OperationID, markdown, hashText("canonical"), true)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if record.State != DocumentDeliverySourceReady || len(record.Artifacts) != 1 || record.Artifacts[0].Role != DocumentArtifactMarkdown {
|
|
t.Fatalf("source receipt = %#v", record)
|
|
}
|
|
if raw, err := st.ArtifactContent(OwnerDefault, record.Artifacts[0].Artifact.ID); err != nil || string(raw) != "# report\n" {
|
|
t.Fatalf("persisted Markdown = %q err=%v", raw, err)
|
|
}
|
|
|
|
derived := []DocumentDeliveryArtifactInput{
|
|
deliveryArtifactFixture(DocumentArtifactDOCX, "report.docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document", []byte("docx")),
|
|
deliveryArtifactFixture(DocumentArtifactManifest, "report.manifest.json", "application/json", []byte("manifest")),
|
|
deliveryArtifactFixture(DocumentArtifactArchive, "report.zip", "application/zip", []byte("archive")),
|
|
}
|
|
record, err = st.CommitDocumentDeliveryDerivedAccountChecked(OwnerDefault, input.OperationID, "delivery-key", "profile-hash", "review-hash", derived, true)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if record.State != DocumentDeliveryComplete || len(record.Artifacts) != 4 || record.DeliveryKey != "delivery-key" {
|
|
t.Fatalf("complete receipt = %#v", record)
|
|
}
|
|
firstIDs := deliveryArtifactIDs(record.Artifacts)
|
|
retry, err := st.CommitDocumentDeliveryDerivedAccountChecked(OwnerDefault, input.OperationID, "delivery-key", "profile-hash", "review-hash", derived, true)
|
|
if err != nil || !reflect.DeepEqual(firstIDs, deliveryArtifactIDs(retry.Artifacts)) {
|
|
t.Fatalf("idempotent derived retry = %#v err=%v", retry, err)
|
|
}
|
|
artifacts, err := st.ListArtifacts(OwnerDefault, string(conversation.ProjectID))
|
|
if err != nil || len(artifacts) != 4 {
|
|
t.Fatalf("visible artifact set = %d err=%v", len(artifacts), err)
|
|
}
|
|
}
|
|
|
|
func TestDocumentDeliveryFailureAfterEveryWriteIsDurableAndRetryable(t *testing.T) {
|
|
stages := []string{
|
|
DocumentDeliveryFaultAfterMarkdownBlob,
|
|
DocumentDeliveryFaultBeforeMarkdownCommit,
|
|
DocumentDeliveryFaultAfterDOCXBlob,
|
|
DocumentDeliveryFaultAfterManifestBlob,
|
|
DocumentDeliveryFaultAfterArchiveBlob,
|
|
DocumentDeliveryFaultBeforeDerivedCommit,
|
|
DocumentDeliveryFaultAfterDerivedCommit,
|
|
}
|
|
for _, stage := range stages {
|
|
t.Run(stage, func(t *testing.T) {
|
|
st := openTestStore(t)
|
|
conversation := seedConversation(t, st)
|
|
operationID := "fault-" + stage
|
|
input := deliveryBeginFixture(conversation, operationID)
|
|
if _, err := st.BeginDocumentDeliveryAccountChecked(OwnerDefault, input, true); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
markdown := deliveryArtifactFixture(DocumentArtifactMarkdown, "fault.md", "text/markdown", []byte("source"))
|
|
derived := []DocumentDeliveryArtifactInput{
|
|
deliveryArtifactFixture(DocumentArtifactDOCX, "fault.docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document", []byte("docx")),
|
|
deliveryArtifactFixture(DocumentArtifactManifest, "fault.manifest.json", "application/json", []byte("manifest")),
|
|
deliveryArtifactFixture(DocumentArtifactArchive, "fault.zip", "application/zip", []byte("archive")),
|
|
}
|
|
st.SetDocumentDeliveryFault(stage, errors.New("injected "+stage))
|
|
var receipt DocumentDeliveryRecord
|
|
var err error
|
|
if stage == DocumentDeliveryFaultAfterMarkdownBlob || stage == DocumentDeliveryFaultBeforeMarkdownCommit {
|
|
receipt, err = st.PersistDocumentDeliveryMarkdownAccountChecked(OwnerDefault, operationID, markdown, hashText("canonical"), true)
|
|
} else {
|
|
if _, err = st.PersistDocumentDeliveryMarkdownAccountChecked(OwnerDefault, operationID, markdown, hashText("canonical"), true); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
receipt, err = st.CommitDocumentDeliveryDerivedAccountChecked(OwnerDefault, operationID, "delivery-key", "profile-hash", "review-hash", derived, true)
|
|
}
|
|
if err == nil || receipt.OperationID != operationID {
|
|
t.Fatalf("fault receipt=%#v err=%v", receipt, err)
|
|
}
|
|
if stage == DocumentDeliveryFaultAfterDerivedCommit {
|
|
if !errors.Is(err, ErrDocumentDeliveryOutcomeUnknown) || receipt.State != DocumentDeliveryComplete {
|
|
t.Fatalf("post-commit outcome=%#v err=%v", receipt, err)
|
|
}
|
|
} else if stage != DocumentDeliveryFaultAfterMarkdownBlob && stage != DocumentDeliveryFaultBeforeMarkdownCommit && receipt.MarkdownArtifactID == "" {
|
|
t.Fatalf("derived failure lost authoritative Markdown: %#v", receipt)
|
|
}
|
|
visible, listErr := st.ListArtifacts(OwnerDefault, string(conversation.ProjectID))
|
|
wantVisible := 1
|
|
if stage == DocumentDeliveryFaultAfterMarkdownBlob || stage == DocumentDeliveryFaultBeforeMarkdownCommit {
|
|
wantVisible = 0
|
|
}
|
|
if stage == DocumentDeliveryFaultAfterDerivedCommit {
|
|
wantVisible = 4
|
|
}
|
|
if listErr != nil || len(visible) != wantVisible {
|
|
t.Fatalf("failure exposed %d artifacts, want %d: %v", len(visible), wantVisible, listErr)
|
|
}
|
|
|
|
st.SetDocumentDeliveryFault("", nil)
|
|
if receipt.MarkdownArtifactID == "" {
|
|
if _, err := st.PersistDocumentDeliveryMarkdownAccountChecked(OwnerDefault, operationID, markdown, hashText("canonical"), true); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
complete, err := st.CommitDocumentDeliveryDerivedAccountChecked(OwnerDefault, operationID, "delivery-key", "profile-hash", "review-hash", derived, true)
|
|
if err != nil || complete.State != DocumentDeliveryComplete || len(complete.Artifacts) != 4 {
|
|
t.Fatalf("retry did not complete: %#v err=%v", complete, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDocumentDeliveryRetryAfterRestartReturnsSameIDsAndHashes(t *testing.T) {
|
|
directory := t.TempDir()
|
|
database := filepath.Join(directory, "delivery.db")
|
|
st, err := OpenSQLite(database)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
project, err := st.CreateProject(OwnerDefault, "delivery")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
conversation, err := st.CreateConversation(OwnerDefault, project.ID, "delivery", "")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
input := deliveryBeginFixture(conversation, "restart-operation")
|
|
if _, err := st.BeginDocumentDeliveryAccountChecked(OwnerDefault, input, true); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
markdown := deliveryArtifactFixture(DocumentArtifactMarkdown, "restart.md", "text/markdown", []byte("source"))
|
|
if _, err := st.PersistDocumentDeliveryMarkdownAccountChecked(OwnerDefault, input.OperationID, markdown, hashText("canonical"), true); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
derived := []DocumentDeliveryArtifactInput{
|
|
deliveryArtifactFixture(DocumentArtifactDOCX, "restart.docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document", []byte("docx")),
|
|
deliveryArtifactFixture(DocumentArtifactManifest, "restart.manifest.json", "application/json", []byte("manifest")),
|
|
deliveryArtifactFixture(DocumentArtifactArchive, "restart.zip", "application/zip", []byte("archive")),
|
|
}
|
|
first, err := st.CommitDocumentDeliveryDerivedAccountChecked(OwnerDefault, input.OperationID, "restart-key", "profile", "review", derived, true)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := st.Close(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
reopened, err := OpenSQLite(database)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer reopened.Close()
|
|
begin, err := reopened.BeginDocumentDeliveryAccountChecked(OwnerDefault, input, true)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
retry, err := reopened.CommitDocumentDeliveryDerivedAccountChecked(OwnerDefault, input.OperationID, "restart-key", "profile", "review", derived, true)
|
|
if err != nil || !begin.FrozenAt.Equal(first.FrozenAt) || !reflect.DeepEqual(deliveryArtifactIDs(first.Artifacts), deliveryArtifactIDs(retry.Artifacts)) {
|
|
t.Fatalf("restart retry changed identity: first=%#v begin=%#v retry=%#v err=%v", first, begin, retry, err)
|
|
}
|
|
}
|
|
|
|
func deliveryBeginFixture(conversation core.Conversation, operationID string) DocumentDeliveryBeginInput {
|
|
return DocumentDeliveryBeginInput{
|
|
OperationID: operationID, RequestSHA256: hashText("request-" + operationID),
|
|
ProjectID: string(conversation.ProjectID), ConversationID: string(conversation.ID),
|
|
SourceRun: "run-1", DocumentID: "document-1", Mode: "preview", RendererIdentity: "renderer@revision",
|
|
}
|
|
}
|
|
|
|
func deliveryArtifactFixture(role DocumentDeliveryArtifactRole, name, mime string, content []byte) DocumentDeliveryArtifactInput {
|
|
return DocumentDeliveryArtifactInput{Role: role, Name: name, Kind: "document", MIME: mime, SHA256: hashBytesForTest(content), Content: content}
|
|
}
|
|
|
|
func deliveryArtifactIDs(artifacts []DocumentDeliveryArtifact) map[DocumentDeliveryArtifactRole]string {
|
|
ids := make(map[DocumentDeliveryArtifactRole]string, len(artifacts))
|
|
for _, artifact := range artifacts {
|
|
ids[artifact.Role] = string(artifact.Artifact.ID) + ":" + artifact.SHA256
|
|
}
|
|
return ids
|
|
}
|
|
|
|
func hashText(value string) string { return hashBytesForTest([]byte(value)) }
|
|
|
|
func hashBytesForTest(value []byte) string {
|
|
sum := sha256.Sum256(value)
|
|
return hex.EncodeToString(sum[:])
|
|
}
|