Files
Agent-System-Backend/internal/store/notices.go
T
weichen adcd8fefd2 Fence U3 account writes and stabilize directory cursors
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.
2026-10-14 07:33:54 +00:00

83 lines
2.6 KiB
Go

package store
import (
"database/sql"
"errors"
"time"
core "agent-system-core"
)
// Notice 是一条待投递的后台子代理完成通知(N2 WP5)。
type Notice struct {
ID string
ConversationID string
Owner string
Name string
Payload core.AgentResultPayload
CreatedAt time.Time
}
// AppendNotice 落一条待投递通知(重启后可重投)。
func (s *Store) AppendNotice(n Notice) error {
_, err := s.db.Exec(s.q(`INSERT INTO pending_notices (id,conversation_id,owner,name,payload,created_at) VALUES(?,?,?,?,?,?)`),
n.ID, n.ConversationID, n.Owner, n.Name, marshalJSON(&n.Payload), nowISO())
return err
}
// AppendNoticeAccountChecked stores a new result notice only while the owner
// is effective in the same transaction that creates the notice.
func (s *Store) AppendNoticeAccountChecked(n Notice, allowImplicitDefault bool) error {
return s.withEffectiveAccountWrite(n.Owner, allowImplicitDefault, func(tx *sql.Tx, _ Account) error {
var owner string
if err := tx.QueryRow(s.q(`SELECT owner FROM conversations WHERE id=?`), n.ConversationID).Scan(&owner); errors.Is(err, sql.ErrNoRows) {
return ErrNotFound
} else if err != nil {
return err
}
if owner != n.Owner {
return ErrNotFound
}
_, err := tx.Exec(s.q(`INSERT INTO pending_notices (id,conversation_id,owner,name,payload,created_at) VALUES(?,?,?,?,?,?)`),
n.ID, n.ConversationID, n.Owner, n.Name, marshalJSON(&n.Payload), nowISO())
return err
})
}
// ListNotices 取回全部待投递通知(Manager 启动时装入内存队列)。
func (s *Store) ListNotices() ([]Notice, error) {
rows, err := s.db.Query(s.q(`SELECT id,conversation_id,owner,name,payload,created_at FROM pending_notices ORDER BY created_at`))
if err != nil {
return nil, err
}
defer rows.Close()
var out []Notice
for rows.Next() {
var n Notice
var payload, ca string
if err := rows.Scan(&n.ID, &n.ConversationID, &n.Owner, &n.Name, &payload, &ca); err != nil {
return nil, err
}
n.Payload, _ = unmarshalJSON[core.AgentResultPayload](payload)
n.CreatedAt, _ = parseTime(ca)
out = append(out, n)
}
return out, rows.Err()
}
// DeleteNotice 投递成功后删除(按 id 幂等)。
func (s *Store) DeleteNotice(id string) error {
_, err := s.db.Exec(s.q(`DELETE FROM pending_notices WHERE id=?`), id)
return err
}
// DeleteNotices 批量删除(投递成功批)。
func (s *Store) DeleteNotices(ids []string) error {
for _, id := range ids {
if err := s.DeleteNotice(id); err != nil && !errors.Is(err, sql.ErrNoRows) {
return err
}
}
return nil
}