merge: candidate rdev-p3-rerun-2026-09-26-r2 into dev
This commit is contained in:
@@ -119,7 +119,8 @@ func (e *GovernanceEngine) loadDocumentFromDBLocked(ctx context.Context, docID s
|
||||
approved_at, config, created_at, updated_at,
|
||||
COALESCE(lease_holder_type, ''), COALESCE(lease_holder_id, ''), COALESCE(lease_work_dir, ''),
|
||||
lease_expires_at, lease_heartbeat_at, COALESCE(lease_fencing_token, ''),
|
||||
COALESCE(duty_locked, false), COALESCE(duty_policy, '')
|
||||
COALESCE(duty_locked, false), COALESCE(duty_policy, ''),
|
||||
COALESCE(review_snapshot_id, 0)
|
||||
FROM document_records
|
||||
WHERE doc_id = $1
|
||||
`, docID)
|
||||
@@ -139,6 +140,7 @@ func (e *GovernanceEngine) loadDocumentFromDBLocked(ctx context.Context, docID s
|
||||
&doc.ApprovedAt, &cfgJSON, &doc.CreatedAt, &doc.UpdatedAt,
|
||||
&leaseType, &leaseID, &leaseWD, &doc.LeaseExpiresAt, &doc.LeaseHeartbeatAt, &doc.LeaseFencingToken,
|
||||
&doc.DutyLocked, &doc.DutyPolicy,
|
||||
&doc.ReviewSnapshotID,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
package docgov_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"chat-agent-system/internal/docgov"
|
||||
)
|
||||
|
||||
// Regression (2026-09-26): loadDocumentFromDBLocked did not select
|
||||
// review_snapshot_id, so any doc loaded from PG after a restart (or cache
|
||||
// miss) lost the frozen review snapshot and ApproverApprove failed with
|
||||
// "approved requires frozen review_snapshot_id". Freeze via SubmitForReview,
|
||||
// then re-load through a fresh engine and assert the snapshot id survives.
|
||||
func TestDocGov_LoadDocumentCarriesReviewSnapshotID(t *testing.T) {
|
||||
pool := openIsolatedPool(t)
|
||||
defer pool.Close()
|
||||
projectID := seedTestProject(t, pool)
|
||||
|
||||
ctx, cancel := context.WithTimeout(t.Context(), 15*time.Second)
|
||||
defer cancel()
|
||||
|
||||
engine := docgov.NewGovernanceEngine(pool)
|
||||
testDocID := fmt.Sprintf("doc_test_snap_%d", time.Now().UnixNano())
|
||||
|
||||
doc, err := engine.CreateOrGetDocument(ctx, testDocID, projectID, "feasibility_reg",
|
||||
"快照装载回归", "V0.5_feasibility", "data/dev/workspaces/test_snap",
|
||||
2, "张工", 3, "李主管", 1, "Seed Admin")
|
||||
if err != nil {
|
||||
t.Fatalf("create doc: %v", err)
|
||||
}
|
||||
if _, err := engine.RecordDocumentViewed(ctx, testDocID, 2, docgov.RoleAuthor, "张工"); err != nil {
|
||||
t.Fatalf("claim author: %v", err)
|
||||
}
|
||||
if err := engine.MarkDocumentContentUpdated(ctx, testDocID, "snap_sha256", false); err != nil {
|
||||
t.Fatalf("mark content: %v", err)
|
||||
}
|
||||
if _, err := engine.SubmitForReview(ctx, testDocID, 2, docgov.RoleAuthor, "张工"); err != nil {
|
||||
t.Fatalf("submit review: %v", err)
|
||||
}
|
||||
// Seed a frozen snapshot id directly: freezeReviewSnapshotLocked copies
|
||||
// head_snapshot_id which only exists once the ledger sealed a snapshot;
|
||||
// the regression under test is the load path, not the freeze path.
|
||||
var want int64
|
||||
err = pool.QueryRow(ctx, `
|
||||
WITH blob AS (
|
||||
INSERT INTO document_content_blobs (content_sha256, content_markdown, byte_size, created_at)
|
||||
VALUES ('snap_load_regression_sha', '# regression', 12, now())
|
||||
RETURNING content_sha256
|
||||
)
|
||||
INSERT INTO document_snapshots (doc_id, content_sha256, actor_user_id, actor_snapshot, source_kind, created_at)
|
||||
SELECT $1::text, blob.content_sha256, 2, '"张工"'::jsonb, 'human', now() FROM blob
|
||||
RETURNING id
|
||||
`, testDocID).Scan(&want)
|
||||
if err != nil || want <= 0 {
|
||||
t.Fatalf("seed snapshot: id=%d err=%v", want, err)
|
||||
}
|
||||
if _, err := pool.Exec(ctx, `UPDATE document_records SET review_snapshot_id=$1 WHERE doc_id=$2`, want, testDocID); err != nil {
|
||||
t.Fatalf("set snapshot id: %v", err)
|
||||
}
|
||||
|
||||
// Fresh engine simulates a restart: the doc must load with its snapshot.
|
||||
fresh := docgov.NewGovernanceEngine(pool)
|
||||
loaded, err := fresh.GetDocument(ctx, testDocID)
|
||||
if err != nil {
|
||||
t.Fatalf("fresh engine load: %v", err)
|
||||
}
|
||||
if loaded.ReviewSnapshotID != want {
|
||||
t.Fatalf("ReviewSnapshotID lost on load: got %d want %d", loaded.ReviewSnapshotID, want)
|
||||
}
|
||||
_ = doc
|
||||
}
|
||||
Reference in New Issue
Block a user