Files
Agent-System-Backend/internal/store/system_identity.go
T
weichenandCursor dc47c10d68 Add agent_system_identity marker with ensure/verify CLI
Why: U1 I3 needs a DB-side instance identity anchor matching
manifest.instance_id so wrong/missing DB blocks startup (I4).
Evidence: go test ./internal/store/ ./internal/app/; CLI smoke
ensure/verify on SQLite temp dir.
Impact: migration v26; store Ensure/Verify; identity subcommand;
serve checks AGENT_SYSTEM_INSTANCE_ID when set.
Refs: docs/recovery/u1-identity-manifest.md §5.2; Dev feature/recovery-u1-db-identity

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-12 04:04:26 +00:00

119 lines
3.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package store
import (
"database/sql"
"errors"
"fmt"
)
// ErrSystemIdentityMismatch 库内身份与期望不符(错库/错实例/多行)——启动必须失败。
var ErrSystemIdentityMismatch = errors.New("system identity mismatch")
// ErrSystemIdentityMissing 表中无身份行——须先 Ensure(svc init / 首次建立),禁止静默插入。
var ErrSystemIdentityMissing = errors.New("system identity missing")
// SystemIdentity 是 agent_system_identity 表的一行(一库一服务身份)。
type SystemIdentity struct {
InstanceID string
DBRef string
SchemaVersion int
CreatedAt string
}
// EnsureSystemIdentity 仅用于显式建立路径:表空则插入;已存在且一致则幂等;
// 不一致或多余行 fail-closed。绝不在常规 start 路径调用以「补写」掩盖漂移。
func (s *Store) EnsureSystemIdentity(instanceID, dbref string) error {
if instanceID == "" {
return fmt.Errorf("EnsureSystemIdentity: instance_id 为空")
}
if dbref == "" {
dbref = "sqlite"
}
row, n, err := s.readSystemIdentity()
if err != nil {
return err
}
if n > 1 {
return fmt.Errorf("%w: agent_system_identity 有 %d 行(期望至多 1)", ErrSystemIdentityMismatch, n)
}
if n == 1 {
if row.InstanceID != instanceID || row.DBRef != dbref {
return fmt.Errorf("%w: 库内 instance_id=%q dbref=%q ≠ 期望 %q / %q",
ErrSystemIdentityMismatch, row.InstanceID, row.DBRef, instanceID, dbref)
}
return nil
}
ver := latestMigrationVersion()
_, err = s.db.Exec(s.q(`INSERT INTO agent_system_identity(instance_id, dbref, schema_version, created_at) VALUES(?,?,?,?)`),
instanceID, dbref, ver, nowISO())
if err != nil {
return fmt.Errorf("EnsureSystemIdentity insert: %w", err)
}
return nil
}
// VerifySystemIdentity 常规启动校验:必须恰有一行且与期望一致;缺失不插入。
func (s *Store) VerifySystemIdentity(instanceID, dbref string) error {
if instanceID == "" {
return fmt.Errorf("VerifySystemIdentity: instance_id 为空")
}
if dbref == "" {
dbref = "sqlite"
}
row, n, err := s.readSystemIdentity()
if err != nil {
return err
}
if n == 0 {
return fmt.Errorf("%w: agent_system_identity 无行(先 svc init / identity ensure,禁止静默建空身份)", ErrSystemIdentityMissing)
}
if n > 1 {
return fmt.Errorf("%w: agent_system_identity 有 %d 行(期望至多 1)", ErrSystemIdentityMismatch, n)
}
if row.InstanceID != instanceID || row.DBRef != dbref {
return fmt.Errorf("%w: 库内 instance_id=%q dbref=%q ≠ 期望 %q / %q",
ErrSystemIdentityMismatch, row.InstanceID, row.DBRef, instanceID, dbref)
}
return nil
}
func (s *Store) readSystemIdentity() (SystemIdentity, int, error) {
rows, err := s.db.Query(`SELECT instance_id, dbref, schema_version, created_at FROM agent_system_identity`)
if err != nil {
return SystemIdentity{}, 0, fmt.Errorf("read agent_system_identity: %w", err)
}
defer rows.Close()
var out SystemIdentity
n := 0
for rows.Next() {
n++
var id, ref, created string
var ver int
if err := rows.Scan(&id, &ref, &ver, &created); err != nil {
return SystemIdentity{}, 0, err
}
if n == 1 {
out = SystemIdentity{InstanceID: id, DBRef: ref, SchemaVersion: ver, CreatedAt: created}
}
}
if err := rows.Err(); err != nil {
return SystemIdentity{}, 0, err
}
return out, n, nil
}
// GetSystemIdentity 读取唯一身份行(无行返回 sql.ErrNoRows)。
func (s *Store) GetSystemIdentity() (SystemIdentity, error) {
row, n, err := s.readSystemIdentity()
if err != nil {
return SystemIdentity{}, err
}
if n == 0 {
return SystemIdentity{}, sql.ErrNoRows
}
if n > 1 {
return SystemIdentity{}, fmt.Errorf("%w: %d rows", ErrSystemIdentityMismatch, n)
}
return row, nil
}