// Package devicebinding owns compatibility gates shared by all ownership writers. // Generation advancement and claim fencing are enforced by the database trigger // so background deletion and operator mutations cannot omit invalidation. package devicebinding import ( "context" "errors" "github.com/jackc/pgx/v5" ) var ErrConfirmationRequired = errors.New("device physical confirmation workflow required") type Queryer interface { QueryRow(context.Context, string, ...any) pgx.Row } func RequireLegacy(ctx context.Context, q Queryer, deviceID string) error { var mode string err := q.QueryRow(ctx, `SELECT enrollment_mode FROM device WHERE id=$1::uuid`, deviceID).Scan(&mode) if err != nil { return err } if mode != "legacy" { return ErrConfirmationRequired } return nil } func RequireUnboundForReset(ctx context.Context, q Queryer, deviceID string) error { var allowed bool err := q.QueryRow(ctx, `SELECT enrollment_mode='legacy' OR NOT EXISTS(SELECT 1 FROM device_binding WHERE device_id=d.id AND unbound_at IS NULL) FROM device d WHERE id=$1::uuid`, deviceID).Scan(&allowed) if err != nil { return err } if !allowed { return ErrConfirmationRequired } return nil }